├── .gitignore ├── src ├── networkprotocoldsl │ ├── optree.cpp │ ├── value.cpp │ ├── entrypoint.cpp │ ├── interpreter.cpp │ ├── lexer │ │ ├── token.cpp │ │ ├── token │ │ │ ├── keyword.cpp │ │ │ ├── literal.cpp │ │ │ ├── identifier.cpp │ │ │ ├── punctuation.cpp │ │ │ ├── identifier.hpp │ │ │ ├── keyword.hpp │ │ │ ├── literal.hpp │ │ │ └── punctuation.hpp │ │ ├── tokenize.hpp │ │ └── token.hpp │ ├── operation.cpp │ ├── parser │ │ ├── tree │ │ │ ├── type.cpp │ │ │ ├── message.cpp │ │ │ ├── tokenpart.cpp │ │ │ ├── messagedata.cpp │ │ │ ├── terminator.cpp │ │ │ ├── booleanliteral.cpp │ │ │ ├── integerliteral.cpp │ │ │ ├── messageforloop.cpp │ │ │ ├── messagepart.cpp │ │ │ ├── stringliteral.cpp │ │ │ ├── tokensequence.cpp │ │ │ ├── messagesequence.cpp │ │ │ ├── typeparametermap.cpp │ │ │ ├── protocoldescription.cpp │ │ │ ├── identifierreference.cpp │ │ │ ├── typeparametervalue.cpp │ │ │ ├── booleanliteral.hpp │ │ │ ├── integerliteral.hpp │ │ │ ├── stringliteral.hpp │ │ │ ├── protocoldescription.hpp │ │ │ ├── messagedata.hpp │ │ │ ├── type.hpp │ │ │ ├── messagesequence.hpp │ │ │ ├── terminator.hpp │ │ │ ├── tokenpart.hpp │ │ │ ├── typeparametermap.hpp │ │ │ ├── identifierreference.hpp │ │ │ ├── messagepart.hpp │ │ │ ├── message.hpp │ │ │ ├── typeparametervalue.hpp │ │ │ ├── messageforloop.hpp │ │ │ ├── tokensequenceoptions.hpp │ │ │ └── tokensequence.hpp │ │ ├── grammar │ │ │ ├── traits.cpp │ │ │ ├── literals.cpp │ │ │ ├── identifier.cpp │ │ │ ├── message.cpp │ │ │ ├── messagedata.cpp │ │ │ ├── tokenparts.cpp │ │ │ ├── typeparameter.cpp │ │ │ ├── messagesequence.cpp │ │ │ ├── protocoldescription.cpp │ │ │ ├── protocoldescription.hpp │ │ │ ├── literals.hpp │ │ │ └── identifier.hpp │ │ ├── support │ │ │ └── recursiveparser.cpp │ │ ├── parse.hpp │ │ └── parse.cpp │ ├── sema │ │ ├── ast │ │ │ ├── agent.cpp │ │ │ ├── action.cpp │ │ │ ├── state.cpp │ │ │ ├── action │ │ │ │ ├── loop.cpp │ │ │ │ ├── read.cpp │ │ │ │ ├── write.cpp │ │ │ │ ├── write.hpp │ │ │ │ ├── read.hpp │ │ │ │ └── loop.hpp │ │ │ ├── transition.cpp │ │ │ ├── protocol.hpp │ │ │ ├── agent.hpp │ │ │ ├── state.hpp │ │ │ ├── transition.hpp │ │ │ └── action.hpp │ │ ├── support.cpp │ │ ├── analyze.hpp │ │ ├── partstoreadactions.hpp │ │ ├── partstowriteactions.hpp │ │ └── support.hpp │ ├── interpretercontext.cpp │ ├── operationconcepts.cpp │ ├── interpretercollection.cpp │ ├── operation │ │ ├── dictionaryget.cpp │ │ ├── dictionaryset.cpp │ │ ├── int32literal.cpp │ │ ├── lexicalpadget.cpp │ │ ├── lexicalpadset.cpp │ │ ├── lexicalpadasdict.cpp │ │ ├── dictionaryinitialize.cpp │ │ ├── lexicalpadinitialize.cpp │ │ ├── lexicalpadinitializeglobal.cpp │ │ ├── dynamiclist.cpp │ │ ├── opsequence.cpp │ │ ├── dynamiclist.hpp │ │ ├── inttoascii.hpp │ │ ├── lexicalpadasdict.hpp │ │ ├── add.hpp │ │ ├── eq.hpp │ │ ├── lexicalpadget.hpp │ │ ├── lexicalpadset.hpp │ │ ├── inttoascii.cpp │ │ ├── subtract.hpp │ │ ├── multiply.hpp │ │ ├── lesserequal.hpp │ │ ├── eq.cpp │ │ ├── add.cpp │ │ ├── opsequence.hpp │ │ ├── lexicalpadinitialize.hpp │ │ ├── dictionaryinitialize.hpp │ │ ├── staticcallable.cpp │ │ ├── lexicalpadinitializeglobal.hpp │ │ ├── generatelist.hpp │ │ ├── functioncall.hpp │ │ ├── unarycallback.cpp │ │ ├── int32literal.hpp │ │ ├── unarycallback.hpp │ │ ├── staticcallable.hpp │ │ ├── if.hpp │ │ ├── functioncallforeach.hpp │ │ ├── readint32native.hpp │ │ ├── multiply.cpp │ │ ├── subtract.cpp │ │ ├── readintfromascii.hpp │ │ ├── writeoctets.hpp │ │ ├── dictionaryget.hpp │ │ ├── lesserequal.cpp │ │ ├── terminatelistifreadahead.hpp │ │ ├── writeint32native.hpp │ │ ├── readstaticoctets.hpp │ │ ├── writestaticoctets.hpp │ │ ├── writestaticoctets.cpp │ │ ├── writeint32native.cpp │ │ ├── statemachineoperation.hpp │ │ ├── dictionaryset.hpp │ │ ├── readint32native.cpp │ │ ├── readstaticoctets.cpp │ │ ├── writeoctetswithescape.hpp │ │ ├── readintfromascii.cpp │ │ ├── readoctetsuntilterminator.hpp │ │ ├── writeoctets.cpp │ │ ├── functioncall.cpp │ │ └── transitionlookahead.hpp │ ├── support │ │ ├── mutexlockqueue.cpp │ │ ├── notificationsignal.cpp │ │ ├── transactionalcontainer.cpp │ │ ├── transactionalcontainer.hpp │ │ ├── mutexlockqueue.hpp │ │ └── notificationsignal.hpp │ ├── entrypoint.hpp │ ├── print_optreenode.hpp │ ├── optree.hpp │ ├── generate.hpp │ ├── interpreterrunner.hpp │ ├── lexicalpad.hpp │ ├── interpretercollectionmanager.hpp │ ├── interpretercollection.hpp │ ├── codegen │ │ ├── generate_states.hpp │ │ ├── generate_data_types.hpp │ │ ├── generate_serializer.hpp │ │ ├── generate_parser.hpp │ │ ├── generate_state_machine.hpp │ │ ├── outputcontext.hpp │ │ ├── generate_runner.hpp │ │ ├── typemapping.hpp │ │ └── outputcontext.cpp │ ├── lexicalpad.cpp │ ├── interpretercontext.hpp │ ├── print_optreenode.cpp │ ├── executionstackframe.hpp │ ├── continuation.hpp │ ├── interpretedprogram.hpp │ └── interpretercollectionmanager.cpp └── networkprotocoldsl_uv │ ├── asyncworkqueue.hpp │ ├── asyncworkqueue.cpp │ ├── libuvclientwrapper.hpp │ ├── libuvserverwrapper.hpp │ ├── libuvclientwrapper.cpp │ └── libuvserverwrapper.cpp ├── examples ├── CMakeLists.txt ├── smtpserver │ ├── server_processor.hpp │ ├── interpreted_program.hpp │ ├── interpreted_program.cpp │ ├── generate_string_literals.cmake │ ├── server_core.hpp │ ├── main.cpp │ └── CMakeLists.txt └── smtpserver_generated │ └── CMakeLists.txt ├── tests ├── 001-empty.cpp ├── data │ ├── 002-empty-program.networkprotocoldsl │ └── 038-http-with-continuation.txt ├── 003-literal-and-add-operations.cpp ├── testlibs │ └── http_message_optrees.hpp ├── 002-interpreter-state.cpp ├── codegen_integration │ ├── test_greeting.cpp │ ├── test_partial.cpp │ └── test_roundtrip.cpp ├── 017-grammar-identifier.cpp ├── 011-write-inttoascii.cpp ├── 005-operator-eq.cpp ├── 012-read-int-and-terminator.cpp ├── 016-grammar-literals.cpp └── 013-parse-and-write-http-message.cpp ├── tsan_suppressions.txt ├── notes ├── lexing-rules.txt ├── operations-write-http-requets.txt ├── operations-write-http-response.txt ├── operations-read-http-request.txt └── operations-read-http-response.txt ├── Dockerfile ├── .github └── workflows │ └── c-cpp.yml ├── LICENSE └── .vscode ├── launch.json └── settings.json /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | build 3 | .cache 4 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/optree.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/value.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/entrypoint.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/interpreter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/lexer/token.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/tree/type.cpp: -------------------------------------------------------------------------------- 1 | #include -------------------------------------------------------------------------------- /src/networkprotocoldsl/sema/ast/agent.cpp: -------------------------------------------------------------------------------- 1 | #include -------------------------------------------------------------------------------- /src/networkprotocoldsl/sema/support.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(smtpserver) 2 | add_subdirectory(smtpserver_generated) -------------------------------------------------------------------------------- /src/networkprotocoldsl/sema/ast/action.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/sema/ast/state.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/interpretercontext.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/operationconcepts.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/grammar/traits.cpp: -------------------------------------------------------------------------------- 1 | #include -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/tree/message.cpp: -------------------------------------------------------------------------------- 1 | #include -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/tree/tokenpart.cpp: -------------------------------------------------------------------------------- 1 | #include -------------------------------------------------------------------------------- /src/networkprotocoldsl/sema/ast/action/loop.cpp: -------------------------------------------------------------------------------- 1 | #include -------------------------------------------------------------------------------- /src/networkprotocoldsl/interpretercollection.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/lexer/token/keyword.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/lexer/token/literal.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/dictionaryget.cpp: -------------------------------------------------------------------------------- 1 | #include -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/dictionaryset.cpp: -------------------------------------------------------------------------------- 1 | #include -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/grammar/literals.cpp: -------------------------------------------------------------------------------- 1 | #include -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/tree/messagedata.cpp: -------------------------------------------------------------------------------- 1 | #include -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/tree/terminator.cpp: -------------------------------------------------------------------------------- 1 | #include -------------------------------------------------------------------------------- /src/networkprotocoldsl/sema/ast/action/read.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/sema/ast/action/write.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/sema/ast/transition.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/lexer/token/identifier.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/lexer/token/punctuation.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/int32literal.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/lexicalpadget.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/lexicalpadset.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/grammar/identifier.cpp: -------------------------------------------------------------------------------- 1 | #include -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/grammar/message.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/grammar/messagedata.cpp: -------------------------------------------------------------------------------- 1 | #include -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/grammar/tokenparts.cpp: -------------------------------------------------------------------------------- 1 | #include -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/tree/booleanliteral.cpp: -------------------------------------------------------------------------------- 1 | #include -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/tree/integerliteral.cpp: -------------------------------------------------------------------------------- 1 | #include -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/tree/messageforloop.cpp: -------------------------------------------------------------------------------- 1 | #include -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/tree/messagepart.cpp: -------------------------------------------------------------------------------- 1 | #include -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/tree/stringliteral.cpp: -------------------------------------------------------------------------------- 1 | #include -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/tree/tokensequence.cpp: -------------------------------------------------------------------------------- 1 | #include -------------------------------------------------------------------------------- /src/networkprotocoldsl/support/mutexlockqueue.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/support/notificationsignal.cpp: -------------------------------------------------------------------------------- 1 | #include -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/lexicalpadasdict.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/grammar/typeparameter.cpp: -------------------------------------------------------------------------------- 1 | #include -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/tree/messagesequence.cpp: -------------------------------------------------------------------------------- 1 | #include -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/tree/typeparametermap.cpp: -------------------------------------------------------------------------------- 1 | #include -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/dictionaryinitialize.cpp: -------------------------------------------------------------------------------- 1 | #include -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/support/recursiveparser.cpp: -------------------------------------------------------------------------------- 1 | #include -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/tree/protocoldescription.cpp: -------------------------------------------------------------------------------- 1 | #include -------------------------------------------------------------------------------- /src/networkprotocoldsl/support/transactionalcontainer.cpp: -------------------------------------------------------------------------------- 1 | #include -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/lexicalpadinitialize.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/grammar/messagesequence.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/tree/identifierreference.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/tree/typeparametervalue.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /tests/001-empty.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | TEST(Empty, Empty) {} 6 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/lexicalpadinitializeglobal.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/grammar/protocoldescription.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /tests/data/002-empty-program.networkprotocoldsl: -------------------------------------------------------------------------------- 1 | message "Server Automatically Closes" { 2 | agent: Sever; 3 | when: Open; 4 | then: Closed; 5 | } 6 | -------------------------------------------------------------------------------- /tsan_suppressions.txt: -------------------------------------------------------------------------------- 1 | # this seems to be a false positive, it is 2 | # warning about data races on the atomic operations 3 | race:insert_interpreter 4 | race:remove_interpreter 5 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/entrypoint.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NETWORKPROTOCOLDSL_ENTRYPOINT_HPP 2 | #define NETWORKPROTOCOLDSL_ENTRYPOINT_HPP 3 | 4 | namespace networkprotocoldsl {}; 5 | 6 | #endif // NETWORKPROTOCOLDSL_ENTRYPOINT_HPP 7 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/tree/booleanliteral.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_BOOLEANLITERAL_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_BOOLEANLITERAL_HPP 3 | 4 | namespace networkprotocoldsl::parser::tree { 5 | 6 | struct BooleanLiteral { 7 | bool value; 8 | }; 9 | 10 | } // namespace networkprotocoldsl::parser::tree 11 | 12 | #endif -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/tree/integerliteral.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_INTEGERLITERAL_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_INTEGERLITERAL_HPP 3 | 4 | namespace networkprotocoldsl::parser::tree { 5 | 6 | struct IntegerLiteral { 7 | int value; 8 | }; 9 | 10 | } // namespace networkprotocoldsl::parser::tree 11 | 12 | #endif -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/dynamiclist.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | namespace networkprotocoldsl::operation { 7 | 8 | Value DynamicList::operator()(std::shared_ptr> args) const { 9 | return value::DynamicList({args}); 10 | } 11 | 12 | } // namespace networkprotocoldsl::operation -------------------------------------------------------------------------------- /src/networkprotocoldsl/lexer/tokenize.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_LEXER_TOKENIZE_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_LEXER_TOKENIZE_HPP 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace networkprotocoldsl::lexer { 10 | 11 | std::optional> tokenize(const std::string &input); 12 | 13 | }; 14 | 15 | #endif -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/opsequence.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | namespace networkprotocoldsl::operation { 6 | 7 | Value OpSequence::operator()(std::shared_ptr> args) const { 8 | if (args->empty()) { 9 | return false; 10 | } else { 11 | return args->back(); 12 | } 13 | } 14 | 15 | } // namespace networkprotocoldsl::operation -------------------------------------------------------------------------------- /tests/003-literal-and-add-operations.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | TEST(operations, literal) { 6 | networkprotocoldsl::operation::Int32Literal i1(10); 7 | networkprotocoldsl::operation::Int32Literal i2(20); 8 | networkprotocoldsl::operation::Add a1; 9 | networkprotocoldsl::Value v = a1({i1({}), i2({})}); 10 | ASSERT_EQ(std::get(v), 30); 11 | } 12 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/lexer/token/identifier.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_LEXER_TOKEN_IDENTIFIER_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_LEXER_TOKEN_IDENTIFIER_HPP 3 | 4 | #include 5 | 6 | namespace networkprotocoldsl::lexer::token { 7 | 8 | struct Identifier { 9 | std::string name; 10 | std::string stringify() const { return name; } 11 | }; 12 | 13 | } // namespace networkprotocoldsl::lexer::token 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /examples/smtpserver/server_processor.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERVER_PROCESSOR_HPP 2 | #define SERVER_PROCESSOR_HPP 3 | 4 | #include "server_core.hpp" 5 | #include 6 | // ...existing includes if needed... 7 | 8 | namespace smtpserver { 9 | 10 | networkprotocoldsl::InterpreterRunner::callback_map 11 | get_sever_callbacks(const ServerConfiguration &config); 12 | 13 | } // namespace smtpserver 14 | 15 | #endif // SERVER_PROCESSOR_HPP 16 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/sema/ast/protocol.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_SEMA_AST_PROTOCOL_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_SEMA_AST_PROTOCOL_HPP 3 | 4 | #include 5 | 6 | #include 7 | 8 | namespace networkprotocoldsl::sema::ast { 9 | 10 | struct Protocol { 11 | std::shared_ptr client; 12 | std::shared_ptr server; 13 | }; 14 | 15 | } // namespace networkprotocoldsl::sema::ast 16 | 17 | #endif -------------------------------------------------------------------------------- /src/networkprotocoldsl/sema/ast/agent.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_SEMA_AST_AGENT_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_SEMA_AST_AGENT_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | namespace networkprotocoldsl::sema::ast { 10 | 11 | struct Agent { 12 | std::unordered_map> states; 13 | }; 14 | 15 | } // namespace networkprotocoldsl::sema::ast 16 | 17 | #endif -------------------------------------------------------------------------------- /examples/smtpserver/interpreted_program.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SMTPSERVER_INTERPRETED_PROGRAM_HPP 2 | #define SMTPSERVER_INTERPRETED_PROGRAM_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace smtpserver { 8 | // Loads the embedded static source and returns an interpreted program. 9 | std::optional 10 | load_interpreted_program(); 11 | } // namespace smtpserver 12 | 13 | #endif // SMTPSERVER_INTERPRETED_PROGRAM_HPP 14 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/sema/ast/state.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_SEMA_AST_STATE_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_SEMA_AST_STATE_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | namespace networkprotocoldsl::sema::ast { 10 | 11 | struct State { 12 | std::unordered_map> 13 | transitions; 14 | }; 15 | 16 | } // namespace networkprotocoldsl::sema::ast 17 | 18 | #endif -------------------------------------------------------------------------------- /tests/testlibs/http_message_optrees.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_TESTLIBS_HTTP_MESSAGE_OPTREES_HPP 2 | #define INCLUDED_TESTLIBS_HTTP_MESSAGE_OPTREES_HPP 3 | 4 | #include 5 | 6 | namespace testlibs { 7 | networkprotocoldsl::Operation get_read_request_callable(); 8 | networkprotocoldsl::Operation get_read_response_callable(); 9 | networkprotocoldsl::Operation get_write_request_callable(); 10 | networkprotocoldsl::Operation get_write_response_callable(); 11 | } // namespace testlibs 12 | 13 | #endif -------------------------------------------------------------------------------- /notes/lexing-rules.txt: -------------------------------------------------------------------------------- 1 | KeywordFor for 2 | KeywordIn in 3 | KeywordMessage message 4 | KeywordParts parts 5 | KeywordTerminator terminator 6 | KeywordTokens tokens 7 | LiteralInteger [0-9]+ 8 | LiteralString \"[^"]*\" 9 | PunctuationAngleBracketClose > 10 | PunctuationAngleBracketOpen < 11 | PunctuationComma , 12 | PunctuationCurlyBraceClose \} 13 | PunctuationCurlyBraceOpen \{ 14 | PunctuationEqual = 15 | PunctuationKeyValueSeparator : 16 | PunctiationStatementEnd ; 17 | WhiteSpace [ \t]+ 18 | Itentifier [a-zA-Z][a-zA-Z0-9]* 19 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/tree/stringliteral.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_STRINGLITERAL_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_STRINGLITERAL_HPP 3 | 4 | #include 5 | 6 | namespace networkprotocoldsl::parser::tree { 7 | 8 | struct StringLiteral { 9 | std::string value; 10 | std::string stringify() const { return "\"" + value + "\""; } 11 | }; 12 | 13 | } // namespace networkprotocoldsl::parser::tree 14 | 15 | #endif // INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_STRINGLITERAL_HPP -------------------------------------------------------------------------------- /src/networkprotocoldsl/sema/analyze.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_SEMA_ANALYZE_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_SEMA_ANALYZE_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | namespace networkprotocoldsl::sema { 11 | 12 | std::optional> 13 | analyze(std::shared_ptr &protocol); 14 | 15 | } 16 | 17 | #endif -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/parse.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_PARSER_PARSE_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_PARSER_PARSE_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | namespace networkprotocoldsl::parser { 12 | 13 | std::optional> 14 | parse(const std::vector &tokens); 15 | 16 | } 17 | 18 | #endif -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/tree/protocoldescription.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_PROTOCOLDESCRIPTION_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_PROTOCOLDESCRIPTION_HPP 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | namespace networkprotocoldsl::parser::tree { 11 | 12 | using ProtocolDescription = 13 | std::map>; 14 | 15 | } 16 | 17 | #endif // INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_PROTOCOLDESCRIPTION_HPP -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:latest 2 | 3 | # Depenedencies to fetch, build llvm and clang 4 | RUN apt-get -y update && apt-get -y dist-upgrade && apt-get install -y \ 5 | cmake \ 6 | build-essential \ 7 | g++ \ 8 | libgtest-dev \ 9 | libuv1-dev \ 10 | git \ 11 | libcli11-dev \ 12 | && apt-get clean 13 | 14 | COPY . src/ 15 | WORKDIR build/ 16 | 17 | # Build tool, run tests, and do a test install 18 | RUN cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo ../src 19 | RUN cmake --build . --verbose 20 | RUN ctest --output-on-failure 21 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/tree/messagedata.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_MESSAGEDATA_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_MESSAGEDATA_HPP 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | namespace networkprotocoldsl::parser::tree { 11 | 12 | using MessageDataPair = std::pair>; 13 | 14 | using MessageData = std::map>; 15 | 16 | } // namespace networkprotocoldsl::parser::tree 17 | 18 | #endif -------------------------------------------------------------------------------- /src/networkprotocoldsl/print_optreenode.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NETWORKPROTOCOLDSL_PRINT_OPTREENODE_HPP 2 | #define NETWORKPROTOCOLDSL_PRINT_OPTREENODE_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace networkprotocoldsl { 10 | 11 | void print_optreenode(const OpTreeNode &node, std::ostream &os, 12 | const std::string &prefix_firstline, 13 | const std::string &prefix_others); 14 | 15 | } // namespace networkprotocoldsl 16 | 17 | #endif // NETWORKPROTOCOLDSL_PRINT_OPTREENODE_HPP 18 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/dynamiclist.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKINGPROTOCOLDSL_OPERATION_DYNAMICLIST_HPP 2 | #define INCLUDED_NETWORKINGPROTOCOLDSL_OPERATION_DYNAMICLIST_HPP 3 | 4 | #include 5 | 6 | namespace networkprotocoldsl::operation { 7 | 8 | class DynamicList { 9 | public: 10 | Value operator()(std::shared_ptr> args) const; 11 | std::string stringify() const { return "DynamicList{}"; } 12 | }; 13 | static_assert(DynamicInputOperationConcept); 14 | 15 | } // namespace networkprotocoldsl::operation 16 | 17 | #endif -------------------------------------------------------------------------------- /src/networkprotocoldsl/sema/partstoreadactions.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_SEMA_PARTSTOREADACTIONS_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_SEMA_PARTSTOREADACTIONS_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | namespace networkprotocoldsl::sema { 11 | 12 | std::optional> parts_to_read_actions( 13 | const std::shared_ptr &parts); 14 | 15 | } 16 | 17 | #endif // INCLUDED_NETWORKPROTOCOLDSL_SEMA_PARTSTOREADACTIONS_HPP -------------------------------------------------------------------------------- /tests/002-interpreter-state.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | TEST(starting_the_interpreter, empty_program) { 8 | std::string test_file = 9 | std::string(TEST_DATA_DIR) + "/002-empty-program.networkprotocoldsl"; 10 | auto maybe_program = 11 | networkprotocoldsl::InterpretedProgram::generate_client(test_file); 12 | ASSERT_TRUE(maybe_program.has_value()); 13 | auto program = maybe_program.value(); 14 | networkprotocoldsl::Interpreter interpreter = program.get_instance(); 15 | } 16 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/tree/type.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_TYPE_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_TYPE_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace networkprotocoldsl::parser::tree { 9 | 10 | struct Type { 11 | std::shared_ptr name; 12 | std::shared_ptr parameters; 13 | }; 14 | 15 | } // namespace networkprotocoldsl::parser::tree 16 | 17 | #endif // INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_TYPE_HPP -------------------------------------------------------------------------------- /examples/smtpserver/interpreted_program.cpp: -------------------------------------------------------------------------------- 1 | #include "interpreted_program.hpp" 2 | #include 3 | 4 | namespace smtpserver { 5 | std::optional 6 | load_interpreted_program() { 7 | // Load the embedded static source code via the compile definition. 8 | std::string source_code = std::string( 9 | #ifndef SMTP_NETWORKPROTOCOLDSL_LITERAL 10 | #error "SMTP_NETWORKPROTOCOLDSL_LITERAL not defined" 11 | #endif 12 | #include SMTP_NETWORKPROTOCOLDSL_LITERAL 13 | ); 14 | return networkprotocoldsl::InterpretedProgram::generate_server_from_source( 15 | source_code); 16 | } 17 | } // namespace smtpserver -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/tree/messagesequence.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_MESSAGESEQUENCE_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_MESSAGESEQUENCE_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace networkprotocoldsl::parser::tree { 9 | 10 | struct MessageSequence 11 | : public std::vector> { 12 | std::string stringify() const { return "..."; } 13 | }; 14 | } // namespace networkprotocoldsl::parser::tree 15 | 16 | #endif // INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_MESSAGESEQUENCE_HPP -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/tree/terminator.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_TERMINATOR_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_TERMINATOR_HPP 3 | 4 | #include 5 | 6 | #include 7 | 8 | namespace networkprotocoldsl::parser::tree { 9 | 10 | struct Terminator { 11 | std::shared_ptr value; 12 | std::string stringify() const { 13 | return "terminator {" + value->stringify() + "}"; 14 | } 15 | }; 16 | 17 | } // namespace networkprotocoldsl::parser::tree 18 | 19 | #endif // INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_TERMINATOR_HPP -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/tree/tokenpart.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_TOKENPART_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_TOKENPART_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | namespace networkprotocoldsl::parser::tree { 11 | 12 | using TokenPart = std::variant, 13 | std::shared_ptr>; 14 | 15 | } 16 | 17 | #endif // INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_TOKENPART_HPP -------------------------------------------------------------------------------- /src/networkprotocoldsl/sema/partstowriteactions.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_SEMA_PARTSTOWRITEACTIONS_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_SEMA_PARTSTOWRITEACTIONS_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | namespace networkprotocoldsl::sema { 12 | 13 | std::optional> parts_to_write_actions( 14 | const std::shared_ptr &parts); 15 | 16 | } 17 | 18 | #endif // INCLUDED_NETWORKPROTOCOLDSL_SEMA_PARTSTOWRITEACTIONS_HPP -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/tree/typeparametermap.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_TYPEPARAMETERMAP_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_TYPEPARAMETERMAP_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | namespace networkprotocoldsl::parser::tree { 11 | 12 | using TypeParameterPair = std::pair; 13 | 14 | using TypeParameterMap = std::map; 15 | 16 | } // namespace networkprotocoldsl::parser::tree 17 | 18 | #endif // INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_TYPEPARAMETERMAP_HPP -------------------------------------------------------------------------------- /src/networkprotocoldsl/optree.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NETWORKPROTOCOLDSL_OPTREE_HPP 2 | #define NETWORKPROTOCOLDSL_OPTREE_HPP 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace networkprotocoldsl { 10 | 11 | /** 12 | * A node in the operation tree. 13 | */ 14 | struct OpTreeNode { 15 | const Operation operation; 16 | const std::vector children; 17 | }; 18 | 19 | /** 20 | * The immutable representation 21 | */ 22 | struct OpTree { 23 | const OpTreeNode root; 24 | OpTree(const OpTreeNode r) : root(r){}; 25 | }; 26 | 27 | } // namespace networkprotocoldsl 28 | 29 | #endif // NETWORKPROTOCOLDSL_OPTREE_HPP 30 | -------------------------------------------------------------------------------- /.github/workflows/c-cpp.yml: -------------------------------------------------------------------------------- 1 | name: C/C++ CI 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-24.04 13 | 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: build-depends 17 | run: sudo apt update && sudo apt -y install libgtest-dev build-essential cmake libgtest-dev libuv1-dev libcli11-dev && sudo apt clean 18 | - name: configure 19 | run: mkdir build && cd build && cmake ../ -DCMAKE_BUILD_TYPE=Debug 20 | - name: make 21 | run: make -j8 -C build/ VERBOSE=1 22 | - name: make test 23 | run: ctest --test-dir build/ --output-on-failure 24 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/generate.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NETWORKPROTOCOLDSL_GENERATE_HPP 2 | #define NETWORKPROTOCOLDSL_GENERATE_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | namespace networkprotocoldsl { 11 | namespace generate { 12 | 13 | std::optional> 14 | client(const std::shared_ptr &protocol); 15 | std::optional> 16 | server(const std::shared_ptr &protocol); 17 | 18 | } // namespace generate 19 | } // namespace networkprotocoldsl 20 | 21 | #endif // NETWORKPROTOCOLDSL_GENERATE_HPP -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/tree/identifierreference.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_IDENTIFIERREFERENCE_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_IDENTIFIERREFERENCE_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace networkprotocoldsl::parser::tree { 9 | 10 | struct IdentifierReference { 11 | std::string name; 12 | std::optional> member; 13 | std::string stringify() const { 14 | std::string result = name; 15 | if (member.has_value()) { 16 | result += "." + member.value()->stringify(); 17 | } 18 | return result; 19 | } 20 | }; 21 | 22 | } // namespace networkprotocoldsl::parser::tree 23 | 24 | #endif -------------------------------------------------------------------------------- /examples/smtpserver/generate_string_literals.cmake: -------------------------------------------------------------------------------- 1 | if(NOT DEFINED INPUT_FILE OR NOT DEFINED OUTPUT_FILE) 2 | message(FATAL_ERROR "Usage: cmake -DINPUT_FILE= -DOUTPUT_FILE= -P generate_string_literals.cmake") 3 | endif() 4 | 5 | file(READ "${INPUT_FILE}" RAW_CONTENT) 6 | # Escape backslashes. 7 | string(REPLACE "\\" "\\\\" RAW_CONTENT_ESCAPED_BS "${RAW_CONTENT}") 8 | # Escape double quotes. 9 | string(REPLACE "\"" "\\\"" RAW_CONTENT_ESCAPED "${RAW_CONTENT_ESCAPED_BS}") 10 | # Replace newlines with newline escape and closing/opening quotes. 11 | string(REPLACE "\n" "\\n\"\n\"" TRANSFORMED_CONTENT "${RAW_CONTENT_ESCAPED}") 12 | set(TRANSFORMED_CONTENT "\"${TRANSFORMED_CONTENT}\"") 13 | file(WRITE "${OUTPUT_FILE}" "${TRANSFORMED_CONTENT}") 14 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/inttoascii.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NEWORKPROTOCOLDSL_OPERATION_INTTOASCII_HPP 2 | #define INCLUDED_NEWORKPROTOCOLDSL_OPERATION_INTTOASCII_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace networkprotocoldsl { 8 | 9 | namespace operation { 10 | 11 | /** 12 | * Converts an integer to ascii octets. 13 | */ 14 | class IntToAscii { 15 | public: 16 | using Arguments = std::tuple; 17 | Value operator()(Arguments a) const; 18 | std::string stringify() const { return "IntToAscii{}"; } 19 | }; 20 | static_assert(InterpretedOperationConcept); 21 | 22 | }; // namespace operation 23 | 24 | } // namespace networkprotocoldsl 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/lexicalpadasdict.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_OPERATION_LEXICALPADASDICT_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_OPERATION_LEXICALPADASDICT_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | namespace networkprotocoldsl::operation { 10 | 11 | class LexicalPadAsDict { 12 | public: 13 | using Arguments = std::tuple<>; 14 | Value operator()(Arguments args, std::shared_ptr pad) const { 15 | return pad->as_dict(); 16 | } 17 | std::string stringify() const { return "LexicalPadAsDict{}"; } 18 | }; 19 | static_assert(LexicalPadOperationConcept); 20 | 21 | } // namespace networkprotocoldsl::operation 22 | 23 | #endif -------------------------------------------------------------------------------- /src/networkprotocoldsl/interpreterrunner.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_INTERPRETERRUNNER_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_INTERPRETERRUNNER_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | namespace networkprotocoldsl { 10 | 11 | struct InterpreterRunner { 12 | using callback_function = std::function &)>; 13 | using callback_map = std::unordered_map; 14 | callback_map callbacks; 15 | std::atomic exit_when_done = false; 16 | void interpreter_loop(InterpreterCollectionManager &mgr); 17 | void callback_loop(InterpreterCollectionManager &mgr); 18 | }; 19 | 20 | } // namespace networkprotocoldsl 21 | 22 | #endif -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/tree/messagepart.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_MESSAGEPART_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_MESSAGEPART_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | namespace networkprotocoldsl::parser::tree { 11 | 12 | struct MessageForLoop; 13 | using MessagePart = std::variant, 14 | std::shared_ptr, 15 | std::shared_ptr>; 16 | 17 | } // namespace networkprotocoldsl::parser::tree 18 | 19 | #endif // INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_MESSAGEPART_HPP -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/add.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NETWORKPROTOCOLDSL_OPERATION_ADD_HPP 2 | #define NETWORKPROTOCOLDSL_OPERATION_ADD_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace networkprotocoldsl { 14 | 15 | namespace operation { 16 | 17 | /** 18 | * Add two values. 19 | */ 20 | class Add { 21 | public: 22 | using Arguments = std::tuple; 23 | Value operator()(Arguments a) const; 24 | std::string stringify() const { return "Add{}"; } 25 | }; 26 | static_assert(InterpretedOperationConcept); 27 | 28 | }; // namespace operation 29 | 30 | } // namespace networkprotocoldsl 31 | 32 | #endif // NETWORKPROTOCOLDSL_OPERATION_HPP 33 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/eq.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NETWORKPROTOCOLDSL_OPERATION_EQ_HPP 2 | #define NETWORKPROTOCOLDSL_OPERATION_EQ_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace networkprotocoldsl { 14 | 15 | namespace operation { 16 | 17 | /** 18 | * Compares two values. 19 | */ 20 | class Eq { 21 | public: 22 | using Arguments = std::tuple; 23 | Value operator()(Arguments a) const; 24 | std::string stringify() const { return "Eq{}"; } 25 | }; 26 | static_assert(InterpretedOperationConcept); 27 | 28 | }; // namespace operation 29 | 30 | } // namespace networkprotocoldsl 31 | 32 | #endif // NETWORKPROTOCOLDSL_OPERATION_HPP 33 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/lexicalpadget.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_OPERATION_LEXICALPADGET_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_OPERATION_LEXICALPADGET_HPP 3 | 4 | #include 5 | 6 | #include 7 | 8 | namespace networkprotocoldsl::operation { 9 | 10 | class LexicalPadGet { 11 | std::string name; 12 | 13 | public: 14 | using Arguments = std::tuple<>; 15 | LexicalPadGet(const std::string &n) : name(n){}; 16 | Value operator()(Arguments args, std::shared_ptr pad) const { 17 | return pad->get(name); 18 | } 19 | std::string stringify() const { 20 | return "LexicalPadGet{name: \"" + name + "\"}"; 21 | } 22 | }; 23 | static_assert(LexicalPadOperationConcept); 24 | 25 | } // namespace networkprotocoldsl::operation 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/lexer/token/keyword.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_LEXER_TOKEN_KEYWORD_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_LEXER_TOKEN_KEYWORD_HPP 3 | 4 | #include 5 | 6 | namespace networkprotocoldsl::lexer::token::keyword { 7 | 8 | struct For { 9 | std::string stringify() const { return "for"; } 10 | }; 11 | struct In { 12 | std::string stringify() const { return "in"; } 13 | }; 14 | struct Message { 15 | std::string stringify() const { return "message"; } 16 | }; 17 | struct Parts { 18 | std::string stringify() const { return "parts"; } 19 | }; 20 | struct Terminator { 21 | std::string stringify() const { return "terminator"; } 22 | }; 23 | struct Tokens { 24 | std::string stringify() const { return "tokens"; } 25 | }; 26 | 27 | } // namespace networkprotocoldsl::lexer::token::keyword 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/lexicalpadset.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_OPERATION_LEXICALPADSET_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_OPERATION_LEXICALPADSET_HPP 3 | 4 | #include 5 | 6 | #include 7 | 8 | namespace networkprotocoldsl::operation { 9 | 10 | class LexicalPadSet { 11 | std::string name; 12 | 13 | public: 14 | using Arguments = std::tuple; 15 | LexicalPadSet(const std::string &n) : name(n){}; 16 | Value operator()(Arguments args, std::shared_ptr pad) const { 17 | return pad->set(name, std::get<0>(args)); 18 | } 19 | std::string stringify() const { 20 | return "LexicalPadSet{name: \"" + name + "\"}"; 21 | } 22 | }; 23 | static_assert(LexicalPadOperationConcept); 24 | 25 | } // namespace networkprotocoldsl::operation 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/sema/ast/action/write.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_SEMA_AST_ACTION_WRITE_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_SEMA_AST_ACTION_WRITE_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | namespace networkprotocoldsl::sema::ast::action { 12 | 13 | struct WriteFromIdentifier { 14 | std::shared_ptr identifier; 15 | // Optional escape replacement - if present: 16 | // when serializing: replace escape_char with escape_sequence in output 17 | std::optional escape; 18 | }; 19 | 20 | struct WriteStaticOctets { 21 | std::string octets; 22 | }; 23 | 24 | } // namespace networkprotocoldsl::sema::ast::action 25 | 26 | #endif -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/inttoascii.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | namespace networkprotocoldsl::operation { 8 | 9 | static Value _inttoascii(int32_t v) { 10 | std::ostringstream os; 11 | os << v; 12 | return value::Octets{ 13 | std::make_shared(std::string(os.str()))}; 14 | } 15 | 16 | static Value _inttoascii(value::RuntimeError v) { return v; } 17 | 18 | static Value _inttoascii(value::ControlFlowInstruction v) { return v; } 19 | 20 | static Value _inttoascii(auto v) { return value::RuntimeError::TypeError; } 21 | 22 | Value IntToAscii::operator()(Arguments a) const { 23 | return std::visit([](auto in) { return _inttoascii(in); }, std::get<0>(a)); 24 | } 25 | 26 | } // namespace networkprotocoldsl::operation 27 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/subtract.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NETWORKPROTOCOLDSL_OPERATION_SUBTRACT_HPP 2 | #define NETWORKPROTOCOLDSL_OPERATION_SUBTRACT_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace networkprotocoldsl { 14 | 15 | namespace operation { 16 | 17 | /** 18 | * subtract two values. 19 | */ 20 | class Subtract { 21 | public: 22 | using Arguments = std::tuple; 23 | Value operator()(Arguments a) const; 24 | std::string stringify() const { return "Subtract{}"; } 25 | }; 26 | static_assert(InterpretedOperationConcept); 27 | 28 | }; // namespace operation 29 | 30 | } // namespace networkprotocoldsl 31 | 32 | #endif // NETWORKPROTOCOLDSL_OPERATION_HPP 33 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/multiply.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NETWORKPROTOCOLDSL_OPERATION_MULTIPLY_HPP 2 | #define NETWORKPROTOCOLDSL_OPERATION_MULTIPLY_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace networkprotocoldsl { 14 | 15 | namespace operation { 16 | 17 | /** 18 | * multiply two values. 19 | */ 20 | class Multiply { 21 | public: 22 | using Arguments = std::tuple; 23 | Value operator()(Arguments a) const; 24 | std::string stringify() const { return "Multiply{}"; } 25 | }; 26 | static_assert(InterpretedOperationConcept); 27 | 28 | }; // namespace operation 29 | 30 | } // namespace networkprotocoldsl 31 | 32 | #endif // NETWORKPROTOCOLDSL_OPERATION_MULTIPLY_HPP 33 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/lesserequal.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NETWORKPROTOCOLDSL_OPERATION_LESSEREQUAL_HPP 2 | #define NETWORKPROTOCOLDSL_OPERATION_LESSEREQUAL_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace networkprotocoldsl { 14 | 15 | namespace operation { 16 | 17 | /** 18 | * lesser equal of two values. 19 | */ 20 | class LesserEqual { 21 | public: 22 | using Arguments = std::tuple; 23 | Value operator()(Arguments a) const; 24 | std::string stringify() const { return "LesserEqual{}"; } 25 | }; 26 | static_assert(InterpretedOperationConcept); 27 | 28 | }; // namespace operation 29 | 30 | } // namespace networkprotocoldsl 31 | 32 | #endif // NETWORKPROTOCOLDSL_OPERATION_HPP 33 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/eq.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace networkprotocoldsl::operation { 5 | 6 | static Value _eq(int32_t lhs, int32_t rhs) { return (bool)(rhs == lhs); } 7 | 8 | static Value _eq(auto &, auto &) { return value::RuntimeError::TypeError; } 9 | 10 | static Value _eq(int32_t lhs, value::RuntimeError rhs) { return rhs; } 11 | 12 | static Value _eq(int32_t lhs, value::ControlFlowInstruction rhs) { return rhs; } 13 | 14 | static Value _eq(value::RuntimeError lhs, auto rhs) { return lhs; } 15 | 16 | static Value _eq(value::ControlFlowInstruction lhs, auto rhs) { return lhs; } 17 | 18 | Value Eq::operator()(Arguments a) const { 19 | return std::visit([&a](auto lhs, auto rhs) { return _eq(lhs, rhs); }, 20 | std::get<0>(a), std::get<1>(a)); 21 | } 22 | 23 | } // namespace networkprotocoldsl::operation 24 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/tree/message.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_MESSAGE_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_MESSAGE_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace networkprotocoldsl::parser::tree { 11 | 12 | struct Message { 13 | std::shared_ptr name; 14 | std::shared_ptr when; 15 | std::shared_ptr then; 16 | std::shared_ptr agent; 17 | std::shared_ptr data; 18 | std::shared_ptr parts; 19 | }; 20 | 21 | } // namespace networkprotocoldsl::parser::tree 22 | 23 | #endif -------------------------------------------------------------------------------- /src/networkprotocoldsl/lexicalpad.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_LEXICALPAD_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_LEXICALPAD_HPP 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace networkprotocoldsl { 12 | 13 | class LexicalPad { 14 | std::unordered_map pad; 15 | std::optional> parent; 16 | 17 | public: 18 | LexicalPad(std::shared_ptr _parent) : parent(_parent) {} 19 | LexicalPad() = default; 20 | ~LexicalPad() = default; 21 | Value get(const std::string &name); 22 | Value set(const std::string &name, Value v); 23 | void initialize(const std::string &name, Value v); 24 | void initialize_global(const std::string &name, Value v); 25 | Value as_dict() const; 26 | }; 27 | 28 | } // namespace networkprotocoldsl 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/add.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace networkprotocoldsl::operation { 5 | 6 | static Value _add(int32_t lhs, int32_t rhs) { return rhs + lhs; } 7 | 8 | static Value _add(auto &, auto &) { return value::RuntimeError::TypeError; } 9 | 10 | static Value _add(value::RuntimeError lhs, auto rhs) { return lhs; } 11 | 12 | static Value _add(value::ControlFlowInstruction lhs, auto rhs) { return lhs; } 13 | 14 | static Value _add(int32_t lhs, value::RuntimeError rhs) { return rhs; } 15 | 16 | static Value _add(int32_t lhs, value::ControlFlowInstruction rhs) { 17 | return rhs; 18 | } 19 | 20 | Value Add::operator()(Arguments a) const { 21 | return std::visit([](auto lhs, auto rhs) { return _add(lhs, rhs); }, 22 | std::get<0>(a), std::get<1>(a)); 23 | } 24 | 25 | } // namespace networkprotocoldsl::operation 26 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/opsequence.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NETWORKPROTOCOLDSL_OPERATION_OPSEQUENCE_HPP 2 | #define NETWORKPROTOCOLDSL_OPERATION_OPSEQUENCE_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace networkprotocoldsl { 14 | 15 | namespace operation { 16 | 17 | /** 18 | * Operation that instructs the interpreter to execute the child 19 | * operations one at a time. 20 | */ 21 | class OpSequence { 22 | public: 23 | Value operator()(std::shared_ptr> args) const; 24 | std::string stringify() const { return "OpSequence{}"; } 25 | }; 26 | static_assert(DynamicInputOperationConcept); 27 | 28 | }; // namespace operation 29 | 30 | } // namespace networkprotocoldsl 31 | 32 | #endif // NETWORKPROTOCOLDSL_OPERATION_HPP 33 | -------------------------------------------------------------------------------- /notes/operations-write-http-requets.txt: -------------------------------------------------------------------------------- 1 | (StaticCallable 2 | <(OpSequence 3 | (WriteOctets (LexicalPadGet <"verb">)), 4 | (WriteStaticOctets <" ">), 5 | (WriteOctets (LexicalPadGet <"request_target">)), 6 | (WriteStaticOctets <" ">), 7 | (WriteStaticOctets <"HTTP/">), 8 | (WriteOctets 9 | (IntToAscii(LexicalPadGet <"major_version">))), 10 | (WriteStaticOctets <".">), 11 | (WriteOctets 12 | (IntToAscii(LexicalPadGet <"minor_version">))), 13 | (WriteStaticOctets <"\r\n">), 14 | (FunctionCallForEach 15 | (LexicalPadGet <"headers">), 16 | (StaticCallable 17 | <(OpSequence 18 | (WriteOctets (LexicalPadGet <"key">), 19 | (WriteStaticOctets <":">), 20 | (WriteOctets (LexicalPadGet <"value">), 21 | (WriteStaticOctets <"\r\n">)))), 22 | ("key, "value")>)), 23 | (WriteStaticOctets <"\r\n">)), 24 | ( "verb", "request_taget", "major_version", 25 | "minor_version", "headers" )>) 26 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/lexicalpadinitialize.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_OPERATION_LEXICALPADINITIALIZE_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_OPERATION_LEXICALPADINITIALIZE_HPP 3 | 4 | #include 5 | 6 | #include 7 | 8 | namespace networkprotocoldsl::operation { 9 | 10 | class LexicalPadInitialize { 11 | std::string name; 12 | 13 | public: 14 | using Arguments = std::tuple; 15 | LexicalPadInitialize(const std::string &n) : name(n){}; 16 | Value operator()(Arguments args, std::shared_ptr pad) const { 17 | pad->initialize(name, std::get<0>(args)); 18 | return std::get<0>(args); 19 | } 20 | std::string stringify() const { 21 | return "LexicalPadInitialize{name: \"" + name + "\"}"; 22 | } 23 | }; 24 | static_assert(LexicalPadOperationConcept); 25 | 26 | } // namespace networkprotocoldsl::operation 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /notes/operations-write-http-response.txt: -------------------------------------------------------------------------------- 1 | (StaticCallable 2 | <(OpSequence 3 | (WriteStaticOctets <"HTTP/">), 4 | (WriteOctets 5 | (IntToAscii(LexicalPadGet <"major_version">))), 6 | (WriteStaticOctets <".">), 7 | (WriteOctets 8 | (IntToAscii(LexicalPadGet <"minor_version">))), 9 | (WriteStaticOctets <" ">), 10 | (WriteOctets 11 | (IntToAscii(LexicalPadGet <"status_code">))), 12 | (WriteOctets 13 | (LexicalPadGet <"reason_phrase">)), 14 | (WriteStaticOctets <"\r\n">), 15 | (FunctionCallForEach 16 | (LexicalPadGet <"headers">), 17 | (StaticCallable 18 | <(OpSequence 19 | (WriteOctets (LexicalPadGet <"key">), 20 | (WriteStaticOctets <":">), 21 | (WriteOctets (LexicalPadGet <"value">), 22 | (WriteStaticOctets <"\r\n">)))), 23 | ("key, "value")>)), 24 | (WriteStaticOctets <"\r\n">)), 25 | ( "major_version", "minor_version", "status_code", "reason_phrase", "headers" )>) 26 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/lexer/token/literal.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_LEXER_TOKEN_LITERAL_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_LEXER_TOKEN_LITERAL_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace networkprotocoldsl::lexer::token::literal { 8 | 9 | struct Integer { 10 | int value; 11 | std::string stringify() const { 12 | std::stringstream ss = std::stringstream(); 13 | ss << value; 14 | return ss.str(); 15 | } 16 | }; 17 | struct String { 18 | std::string value; 19 | std::string stringify() const { 20 | std::stringstream ss = std::stringstream(); 21 | ss << "\"" << value << "\""; 22 | return ss.str(); 23 | } 24 | }; 25 | struct Boolean { 26 | bool value; 27 | std::string stringify() const { 28 | std::stringstream ss = std::stringstream(); 29 | ss << value; 30 | return ss.str(); 31 | } 32 | }; 33 | 34 | } // namespace networkprotocoldsl::lexer::token::literal 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /notes/operations-read-http-request.txt: -------------------------------------------------------------------------------- 1 | (OpSequence 2 | (LexicalPadInitialize <"verb"> 3 | (ReadOctetUntilTerminator <" ">) ), 4 | (LexicalPadInitialize <"request_target"> 5 | (ReadOctetUntilTerminator <" ">)), 6 | (ReadStaticOctets <"HTTP/">), 7 | (LexicalPadInitialize <"major_version"> 8 | (ReadIntegerFromASCII)), 9 | (ReadStaticOctets <".">), 10 | (LexicalPadInitialize <"minor_version"> 11 | (ReadIntegerFromASCII)), 12 | (ReadStaticOctets <"\r\n">), 13 | (LexicalPadInitialize <"headers"> 14 | (GenerateList 15 | (StaticCallable 16 | <(OpSequence 17 | (TerminateListIfReadAhead <"\r\n">), 18 | (DynamicList 19 | (ReadOctetUntilTerminator <":">), 20 | (ReadOctetUntilTerminator <"\r\n">)))>))), 21 | (DynamicList 22 | (LexicalPadGet <”verb”>), 23 | (LexicalPadGet <”request_target”>), 24 | (LexicalPadGet <”major_version”>), 25 | (LexicalPadGet <”minor_version”>), 26 | (LexicalPadGet <”headers”>))) -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/dictionaryinitialize.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NETWORKPROTOCOLDSL_OPERATION_DICTIONARYINITIALIZE_HPP 2 | #define NETWORKPROTOCOLDSL_OPERATION_DICTIONARYINITIALIZE_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace networkprotocoldsl::operation { 8 | 9 | struct DictionaryInitialize { 10 | DictionaryInitialize() {} 11 | 12 | using Arguments = std::tuple<>; 13 | 14 | Value operator()(const Arguments &args) const { 15 | return value::Dictionary{ 16 | std::make_shared>()}; 17 | } 18 | 19 | std::string stringify() const { return "DictionaryInitialize{}"; } 20 | }; 21 | 22 | static_assert( 23 | InterpretedOperationConcept, 24 | "DictionaryInitialize must conform to InterpretedOperationConcept"); 25 | 26 | } // namespace networkprotocoldsl::operation 27 | 28 | #endif // NETWORKPROTOCOLDSL_OPERATION_DICTIONARYINITIALIZE_HPP -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/staticcallable.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace networkprotocoldsl::operation { 5 | 6 | Value StaticCallable::operator()(Arguments a) const { 7 | return value::Callable(optree, argument_names, inherits_lexical_pad); 8 | } 9 | 10 | std::string StaticCallable::stringify() const { 11 | std::ostringstream os; 12 | os << "StaticCallable{\n"; 13 | os << " inherits_lexical_pad: " << std::to_string(inherits_lexical_pad) 14 | << "\n"; 15 | os << " argument_names: ["; 16 | for (size_t i = 0; i < argument_names.size(); ++i) { 17 | os << "\"" << argument_names[i] << "\""; 18 | if (i < argument_names.size() - 1) { 19 | os << ", "; 20 | } 21 | } 22 | os << "]\n optree: \n"; 23 | print_optreenode(optree->root, os, " │ ", " │ "); 24 | os << "}"; 25 | return os.str(); 26 | } 27 | 28 | } // namespace networkprotocoldsl::operation 29 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/interpretercollectionmanager.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_INTERPRETERMANAGER_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_INTERPRETERMANAGER_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | namespace networkprotocoldsl { 13 | 14 | class InterpreterCollectionManager { 15 | support::TransactionalContainer _collection; 16 | 17 | public: 18 | const std::shared_ptr get_collection(); 19 | std::future 20 | insert_interpreter(int fd, InterpretedProgram program, 21 | std::optional arglist = std::nullopt, 22 | void *additional_data = NULL); 23 | void remove_interpreter(int fd); 24 | }; 25 | 26 | } // namespace networkprotocoldsl 27 | 28 | #endif -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/lexicalpadinitializeglobal.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_OPERATION_LEXICALPADINITIALIZEGLOBAL_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_OPERATION_LEXICALPADINITIALIZEGLOBAL_HPP 3 | 4 | #include 5 | 6 | #include 7 | 8 | namespace networkprotocoldsl::operation { 9 | 10 | class LexicalPadInitializeGlobal { 11 | std::string name; 12 | 13 | public: 14 | using Arguments = std::tuple; 15 | LexicalPadInitializeGlobal(const std::string &n) : name(n){}; 16 | Value operator()(Arguments args, std::shared_ptr pad) const { 17 | pad->initialize_global(name, std::get<0>(args)); 18 | return std::get<0>(args); 19 | } 20 | std::string stringify() const { 21 | return "LexicalPadInitializeGlobal{name: \"" + name + "\"}"; 22 | } 23 | }; 24 | static_assert(LexicalPadOperationConcept); 25 | 26 | } // namespace networkprotocoldsl::operation 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /tests/codegen_integration/test_greeting.cpp: -------------------------------------------------------------------------------- 1 | #include "protocol.hpp" 2 | #include 3 | #include 4 | 5 | int main() { 6 | smtp::generated::ServerStateMachine sm; 7 | 8 | // Create a server greeting with known values 9 | smtp::generated::SMTPServerGreetingData greeting; 10 | greeting.code_tens = 50; // This makes "250" when combined with the "2" prefix 11 | greeting.msg = "Hello SMTP"; 12 | 13 | // Send the greeting 14 | sm.send_SMTPServerGreeting(greeting); 15 | 16 | // Output the serialized bytes 17 | auto output = sm.pending_output(); 18 | std::cout << "OUTPUT:"; 19 | for (char c : output) { 20 | std::cout << std::hex << std::setw(2) << std::setfill('0') 21 | << (static_cast(c) & 0xFF) << " "; 22 | } 23 | std::cout << std::endl; 24 | 25 | // Also output as string for easy comparison 26 | std::cout << "STRING:" << output << std::endl; 27 | 28 | return 0; 29 | } 30 | -------------------------------------------------------------------------------- /notes/operations-read-http-response.txt: -------------------------------------------------------------------------------- 1 | (OpSequence 2 | (ReadStaticOctets <"HTTP/">), 3 | (LexicalPadInitialize <"major_version"> 4 | (ReadIntegerFromASCII)), 5 | (ReadStaticOctets <".">), 6 | (LexicalPadInitialize <"minor_version"> 7 | (ReadIntegerFromASCII)), 8 | (ReadStaticOctets <" ">), 9 | (LexicalPadInitialize <"status_code"> 10 | (ReadIntegerFromASCII)), 11 | (ReadStaticOctets <" ">), 12 | (LexicalPadInitialize <"reason_phrase"> 13 | (ReadOctetUntilTerminator <"\r\n">) ), 14 | (LexicalPadInitialize <"headers"> 15 | (GenerateList 16 | (StaticCallable 17 | <(OpSequence 18 | (TerminateListIfReadAhead <"\r\n">), 19 | (DynamicList 20 | (ReadOctetUntilTerminator <":">), 21 | (ReadOctetUntilTerminator <"\r\n">)))>))), 22 | (DynamicList 23 | (LexicalPadGet <”major_version”>), 24 | (LexicalPadGet <”minor_version”>), 25 | (LexicalPadGet <”status_code">), 26 | (LexicalPadGet <"reason_phrase">), 27 | (LexicalPadGet <”headers”>))) -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/generatelist.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_OPERATION_GENERATELIST_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_OPERATION_GENERATELIST_HPP 3 | 4 | #include 5 | 6 | namespace networkprotocoldsl::operation { 7 | 8 | class GenerateList { 9 | public: 10 | using Arguments = std::tuple; 11 | OperationResult operator()(ControlFlowOperationContext &ctx, 12 | Arguments a) const; 13 | Value get_callable(ControlFlowOperationContext &ctx) const; 14 | std::shared_ptr> 15 | get_argument_list(ControlFlowOperationContext &ctx) const; 16 | void set_callable_invoked(ControlFlowOperationContext &ctx) const; 17 | void set_callable_return(ControlFlowOperationContext &ctx, Value v) const; 18 | std::string stringify() const { return "GenerateList{}"; } 19 | }; 20 | static_assert(ControlFlowOperationConcept); 21 | 22 | } // namespace networkprotocoldsl::operation 23 | 24 | #endif -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/functioncall.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_OPERATION_FUNCTIONCALL_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_OPERATION_FUNCTIONCALL_HPP 3 | 4 | #include 5 | 6 | namespace networkprotocoldsl::operation { 7 | 8 | class FunctionCall { 9 | public: 10 | using Arguments = std::tuple; 11 | OperationResult operator()(ControlFlowOperationContext &ctx, 12 | Arguments a) const; 13 | Value get_callable(ControlFlowOperationContext &ctx) const; 14 | std::shared_ptr> 15 | get_argument_list(ControlFlowOperationContext &ctx) const; 16 | void set_callable_invoked(ControlFlowOperationContext &ctx) const; 17 | void set_callable_return(ControlFlowOperationContext &ctx, Value v) const; 18 | std::string stringify() const { return "FunctionCall{}"; } 19 | }; 20 | static_assert(ControlFlowOperationConcept); 21 | 22 | } // namespace networkprotocoldsl::operation 23 | 24 | #endif -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/unarycallback.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace networkprotocoldsl::operation { 4 | 5 | OperationResult UnaryCallback::operator()(CallbackOperationContext &ctx, 6 | Arguments a) const { 7 | if (!ctx.callback_called) { 8 | return ReasonForBlockedOperation::WaitingForCallback; 9 | } else if (!ctx.value.has_value()) { 10 | return ReasonForBlockedOperation::WaitingCallbackData; 11 | } else { 12 | return *ctx.value; 13 | } 14 | } 15 | 16 | std::string UnaryCallback::callback_key(CallbackOperationContext &ctx) const { 17 | return key; 18 | } 19 | 20 | void UnaryCallback::set_callback_return(CallbackOperationContext &ctx, 21 | Value v) const { 22 | ctx.value = v; 23 | } 24 | 25 | void UnaryCallback::set_callback_called(CallbackOperationContext &ctx) const { 26 | ctx.callback_called = true; 27 | } 28 | 29 | } // namespace networkprotocoldsl::operation 30 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/int32literal.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NETWORKPROTOCOLDSL_OPERATION_INT32LITERAL_HPP 2 | #define NETWORKPROTOCOLDSL_OPERATION_INT32LITERAL_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace networkprotocoldsl { 14 | 15 | namespace operation { 16 | 17 | /** 18 | * An int32_t literal type. 19 | */ 20 | class Int32Literal { 21 | int32_t int32_v; 22 | 23 | public: 24 | using Arguments = std::tuple<>; 25 | Int32Literal(int32_t v) : int32_v(v) {} 26 | Value operator()(Arguments a) const { return int32_v; } 27 | std::string stringify() const { 28 | return "Int32Literal{int32_v: " + std::to_string(int32_v) + "}"; 29 | } 30 | }; 31 | static_assert(InterpretedOperationConcept); 32 | 33 | }; // namespace operation 34 | 35 | } // namespace networkprotocoldsl 36 | 37 | #endif // NETWORKPROTOCOLDSL_OPERATION_HPP 38 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/tree/typeparametervalue.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_TYPEPARAMETERVALUE_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_TYPEPARAMETERVALUE_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | namespace networkprotocoldsl::parser::tree { 13 | 14 | class Type; // Forward declaration 15 | using TypeParameterValue = std::variant, 16 | std::shared_ptr, 17 | std::shared_ptr, 18 | std::shared_ptr>; 19 | 20 | } // namespace networkprotocoldsl::parser::tree 21 | 22 | #endif // INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_TYPEPARAMETERVALUE_HPP -------------------------------------------------------------------------------- /examples/smtpserver/server_core.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SMTPSERVER_SERVER_CORE_HPP 2 | #define SMTPSERVER_SERVER_CORE_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | // ...existing code... 14 | namespace smtpserver { 15 | 16 | struct ServerConfiguration { 17 | std::string server_name; 18 | std::unordered_map> 19 | valid_recipients; 20 | std::unordered_map> 21 | blocked_senders; 22 | std::unordered_set blocked_client_domains; 23 | std::string maildir; 24 | std::string bind_ip; 25 | int bind_port; 26 | }; 27 | 28 | std::optional 29 | main_server(const std::span &args, 30 | const networkprotocoldsl::InterpretedProgram &program, 31 | networkprotocoldsl_uv::AsyncWorkQueue &async_queue); 32 | } // namespace smtpserver 33 | 34 | #endif // SMTPSERVER_SERVER_CORE_HPP 35 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/sema/ast/transition.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_SEMA_AST_TRANSITION_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_SEMA_AST_TRANSITION_HPP 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace networkprotocoldsl::sema::ast { 10 | 11 | struct AbstractTransition { 12 | std::shared_ptr data; 13 | std::vector actions; 14 | AbstractTransition( 15 | const std::shared_ptr &d, 16 | const std::vector &a) 17 | : data(d), actions(a) {} 18 | }; 19 | struct ReadTransition : public AbstractTransition { 20 | using AbstractTransition::AbstractTransition; 21 | }; 22 | struct WriteTransition : public AbstractTransition { 23 | using AbstractTransition::AbstractTransition; 24 | }; 25 | using Transition = std::variant, 26 | std::shared_ptr>; 27 | 28 | } // namespace networkprotocoldsl::sema::ast 29 | 30 | #endif -------------------------------------------------------------------------------- /src/networkprotocoldsl/lexer/token.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_LEXER_TOKEN_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_LEXER_TOKEN_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | namespace networkprotocoldsl::lexer { 12 | 13 | using Token = std::variant< 14 | token::Identifier, token::keyword::For, token::keyword::In, 15 | token::keyword::Message, token::keyword::Parts, token::keyword::Terminator, 16 | token::keyword::Tokens, token::literal::Integer, token::literal::String, 17 | token::literal::Boolean, token::punctuation::AngleBracketClose, 18 | token::punctuation::AngleBracketOpen, token::punctuation::Comma, 19 | token::punctuation::Dot, token::punctuation::CurlyBraceClose, 20 | token::punctuation::CurlyBraceOpen, token::punctuation::Equal, 21 | token::punctuation::KeyValueSeparator, token::punctuation::StatementEnd>; 22 | 23 | } 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/lexer/token/punctuation.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_LEXER_TOKEN_PUNCTUATION_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_LEXER_TOKEN_PUNCTUATION_HPP 3 | 4 | #include 5 | 6 | namespace networkprotocoldsl::lexer::token::punctuation { 7 | 8 | struct AngleBracketClose { 9 | std::string stringify() const { return ">"; } 10 | }; 11 | struct AngleBracketOpen { 12 | std::string stringify() const { return "<"; } 13 | }; 14 | struct Comma { 15 | std::string stringify() const { return ","; } 16 | }; 17 | struct CurlyBraceClose { 18 | std::string stringify() const { return "}"; } 19 | }; 20 | struct CurlyBraceOpen { 21 | std::string stringify() const { return "{"; } 22 | }; 23 | struct Equal { 24 | std::string stringify() const { return "="; } 25 | }; 26 | struct KeyValueSeparator { 27 | std::string stringify() const { return ":"; } 28 | }; 29 | struct StatementEnd { 30 | std::string stringify() const { return ";"; } 31 | }; 32 | struct Dot { 33 | std::string stringify() const { return "."; } 34 | }; 35 | 36 | } // namespace networkprotocoldsl::lexer::token::punctuation 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Packt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/tree/messageforloop.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_MESSAGEFORLOOP_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_MESSAGEFORLOOP_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace networkprotocoldsl::parser::tree { 11 | 12 | struct MessageSequence; 13 | struct MessageForLoop { 14 | std::shared_ptr variable; 15 | std::shared_ptr collection; 16 | std::shared_ptr block; 17 | std::optional terminator; 18 | std::string stringify() const { 19 | std::string result = "for"; 20 | if (terminator.has_value()) { 21 | result += " "; 22 | } 23 | result += " " + variable->stringify() + " in " + collection->stringify() + 24 | " {...}"; 25 | return result; 26 | } 27 | }; 28 | 29 | } // namespace networkprotocoldsl::parser::tree 30 | 31 | #endif // INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_MESSAGEFORLOOP_HPP -------------------------------------------------------------------------------- /src/networkprotocoldsl/sema/ast/action.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_SEMA_AST_ACTION_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_SEMA_AST_ACTION_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | namespace networkprotocoldsl::sema::ast { 11 | 12 | namespace action { 13 | struct Loop; // this is recursive. we need to forward declare it here because 14 | // Action is a template alias and that can't be forward declared. 15 | } 16 | 17 | using Action = 18 | std::variant, 19 | std::shared_ptr, 20 | std::shared_ptr, 21 | std::shared_ptr, 22 | std::shared_ptr>; 23 | 24 | } // namespace networkprotocoldsl::sema::ast 25 | 26 | // Include the rest of the action headers here because they are needed for the 27 | // definition of the Action variant. 28 | #include 29 | 30 | #endif -------------------------------------------------------------------------------- /src/networkprotocoldsl/sema/ast/action/read.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_SEMA_AST_ACTION_READ_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_SEMA_AST_ACTION_READ_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | namespace networkprotocoldsl::sema::ast::action { 12 | 13 | struct ReadStaticOctets { 14 | std::string octets; 15 | }; 16 | 17 | // Escape replacement info for ReadOctetsUntilTerminator 18 | struct EscapeInfo { 19 | std::string character; // what to insert in the captured value (e.g., "\n") 20 | std::string sequence; // what appears on the wire (e.g., "\r\n ") 21 | }; 22 | 23 | struct ReadOctetsUntilTerminator { 24 | std::string terminator; 25 | std::shared_ptr identifier; 26 | // Optional escape replacement - if present: 27 | // - when parsing: replace escape_sequence with escape_char in captured value 28 | // - when serializing: replace escape_char with escape_sequence in output 29 | std::optional escape; 30 | }; 31 | 32 | } // namespace networkprotocoldsl::sema::ast::action 33 | 34 | #endif -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/unarycallback.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NETWORKPROTOCOLDSL_OPERATION_UNARYCALLBACK_HPP 2 | #define NETWORKPROTOCOLDSL_OPERATION_UNARYCALLBACK_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace networkprotocoldsl { 14 | 15 | namespace operation { 16 | 17 | class UnaryCallback { 18 | const std::string key; 19 | 20 | public: 21 | UnaryCallback(std::string c) : key(c) {} 22 | using Arguments = std::tuple; 23 | OperationResult operator()(CallbackOperationContext &ctx, Arguments a) const; 24 | std::string callback_key(CallbackOperationContext &ctx) const; 25 | void set_callback_return(CallbackOperationContext &ctx, Value v) const; 26 | void set_callback_called(CallbackOperationContext &ctx) const; 27 | 28 | std::string stringify() const { 29 | return "UnaryCallback{key: \"" + key + "\"}"; 30 | } 31 | }; 32 | static_assert(CallbackOperationConcept); 33 | 34 | }; // namespace operation 35 | 36 | } // namespace networkprotocoldsl 37 | 38 | #endif // NETWORKPROTOCOLDSL_OPERATION_HPP 39 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/support/transactionalcontainer.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_SUPPORT_TRANSACTIONALCONTAINER_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_SUPPORT_TRANSACTIONALCONTAINER_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace networkprotocoldsl::support { 9 | 10 | template class TransactionalContainer { 11 | std::atomic> _container; 12 | 13 | public: 14 | TransactionalContainer() 15 | : _container({std::make_shared()}) {} 16 | TransactionalContainer(const TransactionalContainer &other) = delete; 17 | TransactionalContainer(TransactionalContainer &&other) = delete; 18 | 19 | template void do_transaction(Callable func) { 20 | while (true) { 21 | auto current = _container.load(); 22 | const std::shared_ptr replacement = func(current); 23 | if (_container.compare_exchange_weak(current, replacement)) { 24 | break; 25 | } 26 | } 27 | } 28 | 29 | const std::shared_ptr current() const { 30 | return _container.load(); 31 | } 32 | }; 33 | 34 | } // namespace networkprotocoldsl::support 35 | 36 | #endif -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/staticcallable.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NETWORKPROTOCOLDSL_OPERATION_STATICCALLABLE_HPP 2 | #define NETWORKPROTOCOLDSL_OPERATION_STATICCALLABLE_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace networkprotocoldsl { 14 | 15 | namespace operation { 16 | 17 | class StaticCallable { 18 | std::shared_ptr optree; 19 | std::vector argument_names; 20 | bool inherits_lexical_pad; 21 | 22 | public: 23 | StaticCallable(std::shared_ptr o) 24 | : optree(o), argument_names({}), inherits_lexical_pad(true) {} 25 | StaticCallable(std::shared_ptr o, const std::vector &n, 26 | bool i) 27 | : optree(o), argument_names(n), inherits_lexical_pad(i) {} 28 | using Arguments = std::tuple<>; 29 | Value operator()(Arguments a) const; 30 | std::string stringify() const; 31 | }; 32 | static_assert(InterpretedOperationConcept); 33 | 34 | }; // namespace operation 35 | 36 | } // namespace networkprotocoldsl 37 | 38 | #endif // NETWORKPROTOCOLDSL_OPERATION_HPP 39 | -------------------------------------------------------------------------------- /src/networkprotocoldsl_uv/asyncworkqueue.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NETWORKPROTOCOLDSL_UV_ASYNCWORKQUEUE_HPP 2 | #define NETWORKPROTOCOLDSL_UV_ASYNCWORKQUEUE_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace networkprotocoldsl_uv { 11 | 12 | class AsyncWorkQueue { 13 | public: 14 | AsyncWorkQueue(uv_loop_t *loop); 15 | ~AsyncWorkQueue(); 16 | 17 | // Enqueue a work item and signal the loop. 18 | void push_work(std::function work); 19 | 20 | // Process all pending work items. 21 | void process(); 22 | 23 | // Returns the uv_async_t handle. 24 | uv_async_t *get_async_handle(); 25 | 26 | // Safe shutdown: returns a future that is fulfilled when shutdown completes. 27 | std::future shutdown(); 28 | std::promise shutdown_promise; 29 | 30 | private: 31 | uv_async_t async_handle_; 32 | networkprotocoldsl::support::MutexLockQueue> 33 | work_queue_; 34 | // Updated to use std::atomic_bool for safe concurrent access. 35 | std::atomic_bool shutdown_called_{false}; 36 | }; 37 | 38 | } // namespace networkprotocoldsl_uv 39 | 40 | #endif // NETWORKPROTOCOLDSL_UV_ASYNCWORKQUEUE_HPP 41 | -------------------------------------------------------------------------------- /examples/smtpserver/main.cpp: -------------------------------------------------------------------------------- 1 | #include "interpreted_program.hpp" 2 | #include "server_core.hpp" 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | int main(int argc, const char **argv) { 14 | std::cerr << "SMTP Server example started." << std::endl; 15 | 16 | // Retrieve the interpreted program from the embedded static source. 17 | auto maybe_program = smtpserver::load_interpreted_program(); 18 | if (!maybe_program.has_value()) { 19 | std::cerr << "Failed to load the interpreted program." << std::endl; 20 | return 1; 21 | } 22 | 23 | // Set up libuv loop and async work queue. 24 | uv_loop_t *loop = uv_default_loop(); 25 | networkprotocoldsl_uv::AsyncWorkQueue async_queue(loop); 26 | 27 | // Start the libuv loop in a separate thread. 28 | std::thread io_thread([&]() { uv_run(loop, UV_RUN_DEFAULT); }); 29 | 30 | // Create a span from argv without creating a vector. 31 | std::span args(argv, static_cast(argc)); 32 | smtpserver::main_server(args, *maybe_program, async_queue); 33 | 34 | async_queue.shutdown().wait(); 35 | io_thread.join(); 36 | 37 | return 0; 38 | } -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/if.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NETWORKPROTOCOLDSL_OPERATION_IF_HPP 2 | #define NETWORKPROTOCOLDSL_OPERATION_IF_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace networkprotocoldsl { 15 | 16 | namespace operation { 17 | 18 | /** 19 | * Compares two values. 20 | */ 21 | class If { 22 | public: 23 | using Arguments = std::tuple; 24 | OperationResult operator()(ControlFlowOperationContext &ctx, 25 | Arguments a) const; 26 | Value get_callable(ControlFlowOperationContext &ctx) const; 27 | std::shared_ptr> 28 | get_argument_list(ControlFlowOperationContext &ctx) const; 29 | void set_callable_invoked(ControlFlowOperationContext &ctx) const; 30 | void set_callable_return(ControlFlowOperationContext &ctx, Value v) const; 31 | std::string stringify() const { return "If{}"; } 32 | }; 33 | static_assert(ControlFlowOperationConcept); 34 | 35 | }; // namespace operation 36 | 37 | } // namespace networkprotocoldsl 38 | 39 | #endif // NETWORKPROTOCOLDSL_OPERATION_HPP 40 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/interpretercollection.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_INTERPRETERCOLLECTION_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_INTERPRETERCOLLECTION_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | namespace networkprotocoldsl { 11 | 12 | struct InterpreterSignals { 13 | support::NotificationSignal wake_up_interpreter; 14 | support::NotificationSignal wake_up_for_output; 15 | support::NotificationSignal wake_up_for_input; 16 | support::NotificationSignal wake_up_for_callback; 17 | InterpreterSignals() 18 | : wake_up_interpreter(support::NotificationSignal("interpreter")), 19 | wake_up_for_output(support::NotificationSignal("output")), 20 | wake_up_for_input(support::NotificationSignal("input")), 21 | wake_up_for_callback(support::NotificationSignal("callback")) {} 22 | }; 23 | 24 | struct InterpreterCollection { 25 | const std::unordered_map> 26 | interpreters; 27 | const std::shared_ptr signals = 28 | std::make_shared(); 29 | }; 30 | 31 | } // namespace networkprotocoldsl 32 | 33 | #endif -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/functioncallforeach.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_OPERATION_FUNCTIONCALLFOREACH_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_OPERATION_FUNCTIONCALLFOREACH_HPP 3 | 4 | #include 5 | 6 | namespace networkprotocoldsl::operation { 7 | 8 | class FunctionCallForEach { 9 | public: 10 | bool element_is_single_argument = false; 11 | using Arguments = std::tuple; 12 | OperationResult operator()(ControlFlowOperationContext &ctx, 13 | Arguments a) const; 14 | Value get_callable(ControlFlowOperationContext &ctx) const; 15 | std::shared_ptr> 16 | get_argument_list(ControlFlowOperationContext &ctx) const; 17 | void set_callable_invoked(ControlFlowOperationContext &ctx) const; 18 | void set_callable_return(ControlFlowOperationContext &ctx, Value v) const; 19 | std::string stringify() const { 20 | if (element_is_single_argument) { 21 | return "FunctionCallForEach{element_is_single_argument: true}"; 22 | } else { 23 | return "FunctionCallForEach{element_is_single_argument: false}"; 24 | } 25 | } 26 | }; 27 | static_assert(ControlFlowOperationConcept); 28 | 29 | } // namespace networkprotocoldsl::operation 30 | 31 | #endif -------------------------------------------------------------------------------- /src/networkprotocoldsl/sema/ast/action/loop.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_SEMA_AST_ACTION_LOOP_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_SEMA_AST_ACTION_LOOP_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | namespace networkprotocoldsl::sema::ast::action { 12 | 13 | struct Loop { 14 | std::shared_ptr variable; 15 | std::shared_ptr collection; 16 | std::string terminator; 17 | std::vector actions; 18 | Loop(const std::shared_ptr &var, 19 | const std::shared_ptr &col, 20 | const std::string &term, const std::vector &acts) 21 | : variable(var), collection(col), terminator(term), actions(acts) {} 22 | Loop(const std::shared_ptr &var, 23 | const std::shared_ptr &col, 24 | const std::string &term) 25 | : variable(var), collection(col), terminator(term) {} 26 | }; 27 | 28 | } // namespace networkprotocoldsl::sema::ast::action 29 | 30 | #endif -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/tree/tokensequenceoptions.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_TOKENSEQUENCEOPTIONS_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_TOKENSEQUENCEOPTIONS_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace networkprotocoldsl::parser::tree { 11 | 12 | // Escape replacement: replace<"char", "sequence"> 13 | // - character: the character to represent in the captured value (e.g., "\n") 14 | // - sequence: the escape sequence on the wire (e.g., "\r\n ") 15 | struct EscapeReplacement { 16 | std::string character; // what appears in the captured/serialized value 17 | std::string sequence; // what appears on the wire 18 | }; 19 | 20 | // A token sequence option value can be a simple string or an escape replacement 21 | using TokenSequenceOptionValue = std::variant; 22 | 23 | // A key-value pair for token sequence options (e.g., terminator="\r\n") 24 | using TokenSequenceOptionPair = std::pair; 25 | 26 | // A map of option names to their values 27 | using TokenSequenceOptionsMap = std::map; 28 | 29 | } // namespace networkprotocoldsl::parser::tree 30 | 31 | #endif // INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_TOKENSEQUENCEOPTIONS_HPP 32 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/codegen/generate_states.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_CODEGEN_GENERATE_STATES_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_CODEGEN_GENERATE_STATES_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace networkprotocoldsl::codegen { 12 | 13 | /** 14 | * Result of states generation. 15 | */ 16 | struct StatesResult { 17 | std::string header; // Content of states.hpp 18 | std::string source; // Content of states.cpp 19 | std::vector errors; 20 | }; 21 | 22 | /** 23 | * Generate the states header and source files. 24 | * 25 | * Creates: 26 | * - State enum with all protocol states 27 | * - Input variant types for each state (possible incoming messages) 28 | * - Output variant types for each state (possible outgoing messages) 29 | * 30 | * @param ctx The output context for namespace and formatting 31 | * @param info The protocol information 32 | * @return The generated header and source content, plus any errors 33 | */ 34 | StatesResult generate_states(const OutputContext &ctx, 35 | const ProtocolInfo &info); 36 | 37 | } // namespace networkprotocoldsl::codegen 38 | 39 | #endif // INCLUDED_NETWORKPROTOCOLDSL_CODEGEN_GENERATE_STATES_HPP 40 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/lexicalpad.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace networkprotocoldsl { 4 | 5 | Value LexicalPad::get(const std::string &name) { 6 | auto it = pad.find(name); 7 | if (it == pad.end()) { 8 | if (parent.has_value()) { 9 | return parent.value()->get(name); 10 | } else { 11 | return value::RuntimeError::NameError; 12 | } 13 | } else { 14 | return it->second; 15 | } 16 | } 17 | 18 | Value LexicalPad::set(const std::string &name, Value v) { 19 | auto it = pad.find(name); 20 | if (it == pad.end()) { 21 | if (parent.has_value()) { 22 | return parent.value()->set(name, v); 23 | } else { 24 | return value::RuntimeError::NameError; 25 | } 26 | } else { 27 | Value old = it->second; 28 | it->second = v; 29 | return old; 30 | } 31 | } 32 | 33 | void LexicalPad::initialize(const std::string &name, Value v) { 34 | pad.insert({name, v}); 35 | } 36 | 37 | void LexicalPad::initialize_global(const std::string &name, Value v) { 38 | if (parent.has_value()) { 39 | parent.value()->initialize_global(name, v); 40 | } else { 41 | pad.insert({name, v}); 42 | } 43 | } 44 | 45 | Value LexicalPad::as_dict() const { 46 | return value::Dictionary{ 47 | std::make_shared>(pad)}; 48 | } 49 | 50 | } // namespace networkprotocoldsl 51 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/codegen/generate_data_types.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_CODEGEN_GENERATE_DATA_TYPES_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_CODEGEN_GENERATE_DATA_TYPES_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace networkprotocoldsl::codegen { 12 | 13 | /** 14 | * Result of data types generation. 15 | */ 16 | struct DataTypesResult { 17 | std::string header; // Content of data_types.hpp 18 | std::string source; // Content of data_types.cpp 19 | std::vector errors; 20 | }; 21 | 22 | /** 23 | * Generate the data types header and source files. 24 | * 25 | * Creates struct definitions for each message's data, mapping DSL types 26 | * to C++ types. Each message gets a corresponding Data struct 27 | * (e.g., SMTPServerGreetingData). 28 | * 29 | * @param ctx The output context for namespace and formatting 30 | * @param info The protocol information 31 | * @return The generated header and source content, plus any errors 32 | */ 33 | DataTypesResult generate_data_types(const OutputContext &ctx, 34 | const ProtocolInfo &info); 35 | 36 | } // namespace networkprotocoldsl::codegen 37 | 38 | #endif // INCLUDED_NETWORKPROTOCOLDSL_CODEGEN_GENERATE_DATA_TYPES_HPP 39 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/readint32native.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NETWORKPROTOCOLDSL_OPERATION_READINT32NATIVE_HPP 2 | #define NETWORKPROTOCOLDSL_OPERATION_READINT32NATIVE_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace networkprotocoldsl { 15 | 16 | namespace operation { 17 | 18 | class ReadInt32Native { 19 | public: 20 | using Arguments = std::tuple<>; 21 | ReadInt32Native() {} 22 | 23 | OperationResult operator()(InputOutputOperationContext &ctx, 24 | Arguments a) const; 25 | size_t handle_read(InputOutputOperationContext &ctx, 26 | std::string_view in) const; 27 | void handle_eof(InputOutputOperationContext &ctx) const; 28 | std::string_view get_write_buffer(InputOutputOperationContext &ctx) const; 29 | 30 | size_t handle_write(InputOutputOperationContext &ctx, size_t s) const; 31 | 32 | bool ready_to_evaluate(InputOutputOperationContext &ctx) const; 33 | 34 | std::string stringify() const { return "ReadInt32Native{}"; } 35 | }; 36 | static_assert(InputOutputOperationConcept); 37 | 38 | } // namespace operation 39 | 40 | } // namespace networkprotocoldsl 41 | 42 | #endif // NETWORKPROTOCOLDSL_OPERATION_HPP 43 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/support/mutexlockqueue.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_SUPPORT_MUTEXLOCKQUEUE_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_SUPPORT_MUTEXLOCKQUEUE_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace networkprotocoldsl::support { 11 | 12 | template class MutexLockQueue { 13 | std::mutex mtx; 14 | std::deque queue; 15 | 16 | public: 17 | MutexLockQueue() = default; 18 | MutexLockQueue(const MutexLockQueue &in) = delete; 19 | MutexLockQueue(MutexLockQueue &&in) = delete; 20 | MutexLockQueue &operator=(const MutexLockQueue &) = delete; 21 | 22 | void push_back(const T &input) { 23 | std::lock_guard lock(mtx); 24 | queue.push_back(input); 25 | } 26 | void push_back(T &&input) { 27 | std::lock_guard lock(mtx); 28 | queue.push_back(std::move(input)); 29 | } 30 | std::optional pop() { 31 | std::lock_guard lock(mtx); 32 | if (queue.empty()) { 33 | return std::nullopt; 34 | } else { 35 | T out = std::move(queue.front()); 36 | queue.pop_front(); 37 | return out; 38 | } 39 | } 40 | void push_front(const T &input) { 41 | std::lock_guard lock(mtx); 42 | queue.push_front(input); 43 | } 44 | }; 45 | 46 | } // namespace networkprotocoldsl::support 47 | 48 | #endif -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/multiply.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace networkprotocoldsl::operation { 5 | 6 | static Value _multiply(int32_t lhs, int32_t rhs) { return rhs * lhs; } 7 | 8 | static Value _multiply(int32_t lhs, auto rhs) { 9 | return value::RuntimeError::TypeError; 10 | } 11 | 12 | static Value _multiply(int32_t lhs, value::RuntimeError rhs) { return rhs; } 13 | 14 | static Value _multiply(int32_t lhs, value::ControlFlowInstruction rhs) { 15 | return rhs; 16 | } 17 | 18 | static Value _multiply(int32_t lhs, Value rhs) { 19 | return std::visit( 20 | [&lhs](auto rhs_v) -> Value { return _multiply(lhs, rhs_v); }, rhs); 21 | } 22 | 23 | static Value _multiply(value::Callable lhs, auto rhs) { 24 | return value::RuntimeError::TypeError; 25 | } 26 | 27 | static Value _multiply(value::RuntimeError lhs, auto rhs) { return lhs; } 28 | 29 | static Value _multiply(value::ControlFlowInstruction lhs, auto rhs) { 30 | return lhs; 31 | } 32 | 33 | template static Value _multiply(LHS lhs, RHS rhs) { 34 | return value::RuntimeError::TypeError; 35 | } 36 | 37 | Value Multiply::operator()(Arguments a) const { 38 | return std::visit([&a](auto lhs) { return _multiply(lhs, std::get<1>(a)); }, 39 | std::get<0>(a)); 40 | } 41 | 42 | } // namespace networkprotocoldsl::operation 43 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/subtract.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace networkprotocoldsl::operation { 5 | 6 | static Value _subtract(int32_t lhs, int32_t rhs) { return lhs - rhs; } 7 | 8 | static Value _subtract(int32_t lhs, auto rhs) { 9 | return value::RuntimeError::TypeError; 10 | } 11 | 12 | static Value _subtract(int32_t lhs, value::RuntimeError rhs) { return rhs; } 13 | 14 | static Value _subtract(int32_t lhs, value::ControlFlowInstruction rhs) { 15 | return rhs; 16 | } 17 | 18 | static Value _subtract(int32_t lhs, Value rhs) { 19 | return std::visit( 20 | [&lhs](auto rhs_v) -> Value { return _subtract(lhs, rhs_v); }, rhs); 21 | } 22 | 23 | static Value _subtract(value::Callable lhs, auto rhs) { 24 | return value::RuntimeError::TypeError; 25 | } 26 | 27 | static Value _subtract(value::RuntimeError lhs, auto rhs) { return lhs; } 28 | 29 | static Value _subtract(value::ControlFlowInstruction lhs, auto rhs) { 30 | return lhs; 31 | } 32 | 33 | template static Value _subtract(LHS lhs, RHS rhs) { 34 | return value::RuntimeError::TypeError; 35 | } 36 | 37 | Value Subtract::operator()(Arguments a) const { 38 | return std::visit([&a](auto lhs) { return _subtract(lhs, std::get<1>(a)); }, 39 | std::get<0>(a)); 40 | } 41 | 42 | } // namespace networkprotocoldsl::operation 43 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/grammar/protocoldescription.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_PARSER_GRAMMAR_PROTOCOLDESCRIPTION_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_PARSER_GRAMMAR_PROTOCOLDESCRIPTION_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | namespace networkprotocoldsl::parser::grammar { 15 | 16 | class ProtocolDescription 17 | : public support::RecursiveParser> { 19 | public: 20 | static constexpr const char *name = "ProtocolDescription"; 21 | static Message *recurse_many() { return nullptr; }; 22 | static ParseStateReturn 23 | match(TokenIterator begin, TokenIterator end, 24 | std::vector> messages) { 25 | auto protocol_description = std::make_shared(); 26 | for (auto &message : messages) { 27 | protocol_description->emplace(message->name->value, message); 28 | } 29 | return {protocol_description, begin, end}; 30 | }; 31 | }; 32 | 33 | } // namespace networkprotocoldsl::parser::grammar 34 | 35 | #endif -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/readintfromascii.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NETWORKPROTOCOLDSL_OPERATION_READINTFROMASCII_HPP 2 | #define NETWORKPROTOCOLDSL_OPERATION_READINTFROMASCII_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace networkprotocoldsl { 15 | 16 | namespace operation { 17 | 18 | class ReadIntFromAscii { 19 | 20 | public: 21 | using Arguments = std::tuple<>; 22 | ReadIntFromAscii() {} 23 | 24 | OperationResult operator()(InputOutputOperationContext &ctx, 25 | Arguments a) const; 26 | size_t handle_read(InputOutputOperationContext &ctx, 27 | std::string_view in) const; 28 | void handle_eof(InputOutputOperationContext &ctx) const; 29 | 30 | std::string_view get_write_buffer(InputOutputOperationContext &ctx) const; 31 | 32 | size_t handle_write(InputOutputOperationContext &ctx, size_t s) const; 33 | 34 | bool ready_to_evaluate(InputOutputOperationContext &ctx) const; 35 | 36 | std::string stringify() const { return "ReadIntFromAscii{}"; } 37 | }; 38 | static_assert(InputOutputOperationConcept); 39 | 40 | } // namespace operation 41 | 42 | } // namespace networkprotocoldsl 43 | 44 | #endif // NETWORKPROTOCOLDSL_OPERATION_HPP 45 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/writeoctets.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_OPERATION_WRITEOCTETS_H 2 | #define INCLUDED_NETWORKPROTOCOLDSL_OPERATION_WRITEOCTETS_H 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace networkprotocoldsl { 15 | 16 | namespace operation { 17 | 18 | class WriteOctets { 19 | public: 20 | using Arguments = std::tuple; 21 | WriteOctets() {} 22 | 23 | OperationResult operator()(InputOutputOperationContext &ctx, 24 | Arguments a) const; 25 | size_t handle_read(InputOutputOperationContext &ctx, 26 | std::string_view in) const; 27 | 28 | std::string_view get_write_buffer(InputOutputOperationContext &ctx) const; 29 | void handle_eof(InputOutputOperationContext &ctx) const; 30 | 31 | size_t handle_write(InputOutputOperationContext &ctx, size_t s) const; 32 | 33 | bool ready_to_evaluate(InputOutputOperationContext &ctx) const { 34 | return true; // Write operations don't wait for input 35 | } 36 | 37 | std::string stringify() const { return "WriteOctets{}"; } 38 | }; 39 | static_assert(InputOutputOperationConcept); 40 | 41 | } // namespace operation 42 | 43 | } // namespace networkprotocoldsl 44 | 45 | #endif -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/dictionaryget.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NETWORKPROTOCOLDSL_OPERATION_DICTIONARYGET_HPP 2 | #define NETWORKPROTOCOLDSL_OPERATION_DICTIONARYGET_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace networkprotocoldsl::operation { 8 | 9 | struct DictionaryGet { 10 | private: 11 | Value get(const value::Dictionary &dict) const { 12 | auto it = dict.members->find(key); 13 | if (it != dict.members->end()) { 14 | return it->second; 15 | } 16 | return value::RuntimeError::NameError; 17 | } 18 | 19 | Value get(auto &) const { return value::RuntimeError::TypeError; } 20 | 21 | Value get(value::RuntimeError &err) const { return err; } 22 | 23 | public: 24 | std::string key; 25 | DictionaryGet(const std::string &key) : key(key) {} 26 | 27 | using Arguments = std::tuple; 28 | 29 | Value operator()(const Arguments &args) const { 30 | return std::visit([&](auto &&dict) -> Value { return get(dict); }, 31 | std::get<0>(args)); 32 | } 33 | 34 | std::string stringify() const { 35 | return "DictionaryGet{key: \"" + key + "\"}"; 36 | } 37 | }; 38 | 39 | static_assert(InterpretedOperationConcept, 40 | "DictionaryGet must conform to InterpretedOperationConcept"); 41 | 42 | } // namespace networkprotocoldsl::operation 43 | 44 | #endif // NETWORKPROTOCOLDSL_OPERATION_DICTIONARYGET_HPP -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/lesserequal.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace networkprotocoldsl::operation { 5 | 6 | static Value _lesserequal(int32_t lhs, int32_t rhs) { return lhs <= rhs; } 7 | 8 | static Value _lesserequal(int32_t lhs, auto rhs) { 9 | return value::RuntimeError::TypeError; 10 | } 11 | 12 | static Value _lesserequal(int32_t lhs, value::RuntimeError rhs) { return rhs; } 13 | 14 | static Value _lesserequal(int32_t lhs, value::ControlFlowInstruction rhs) { 15 | return rhs; 16 | } 17 | 18 | static Value _lesserequal(int32_t lhs, Value rhs) { 19 | return std::visit( 20 | [&lhs](auto rhs_v) -> Value { return _lesserequal(lhs, rhs_v); }, rhs); 21 | } 22 | 23 | static Value _lesserequal(value::Callable lhs, auto rhs) { 24 | return value::RuntimeError::TypeError; 25 | } 26 | 27 | static Value _lesserequal(value::RuntimeError lhs, auto rhs) { return lhs; } 28 | 29 | static Value _lesserequal(value::ControlFlowInstruction lhs, auto rhs) { 30 | return lhs; 31 | } 32 | 33 | template 34 | static Value _lesserequal(LHS lhs, RHS rhs) { 35 | return value::RuntimeError::TypeError; 36 | } 37 | 38 | Value LesserEqual::operator()(Arguments a) const { 39 | return std::visit( 40 | [&a](auto lhs) { return _lesserequal(lhs, std::get<1>(a)); }, 41 | std::get<0>(a)); 42 | } 43 | 44 | } // namespace networkprotocoldsl::operation 45 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/terminatelistifreadahead.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_OPERATION_TERMINATELISTIFREAD 2 | #define INCLUDED_NETWORKPROTOCOLDSL_OPERATION_TERMINATELISTIFREAD 3 | 4 | #include 5 | #include 6 | 7 | namespace networkprotocoldsl { 8 | 9 | namespace operation { 10 | 11 | class TerminateListIfReadAhead { 12 | std::string terminator; 13 | 14 | public: 15 | using Arguments = std::tuple<>; 16 | TerminateListIfReadAhead(const std::string &_t) : terminator(_t) {} 17 | 18 | OperationResult operator()(InputOutputOperationContext &ctx, 19 | Arguments a) const; 20 | size_t handle_read(InputOutputOperationContext &ctx, 21 | std::string_view in) const; 22 | 23 | std::string_view get_write_buffer(InputOutputOperationContext &ctx) const; 24 | void handle_eof(InputOutputOperationContext &ctx) const; 25 | 26 | size_t handle_write(InputOutputOperationContext &ctx, size_t s) const; 27 | 28 | // Returns true if the operation has enough data to produce a result. 29 | bool ready_to_evaluate(InputOutputOperationContext &ctx) const; 30 | 31 | std::string stringify() const { 32 | return "TerminateListIfReadAhead{terminator: \"" + terminator + "\"}"; 33 | } 34 | }; 35 | static_assert(InputOutputOperationConcept); 36 | 37 | } // namespace operation 38 | 39 | } // namespace networkprotocoldsl 40 | 41 | #endif -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/writeint32native.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NETWORKPROTOCOLDSL_OPERATION_WRITEINT32NATIVE_HPP 2 | #define NETWORKPROTOCOLDSL_OPERATION_WRITEINT32NATIVE_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace networkprotocoldsl { 15 | 16 | namespace operation { 17 | 18 | class WriteInt32Native { 19 | public: 20 | using Arguments = std::tuple; 21 | WriteInt32Native() {} 22 | 23 | OperationResult operator()(InputOutputOperationContext &ctx, 24 | Arguments a) const; 25 | size_t handle_read(InputOutputOperationContext &ctx, 26 | std::string_view in) const; 27 | 28 | std::string_view get_write_buffer(InputOutputOperationContext &ctx) const; 29 | void handle_eof(InputOutputOperationContext &ctx) const; 30 | 31 | size_t handle_write(InputOutputOperationContext &ctx, size_t s) const; 32 | 33 | bool ready_to_evaluate(InputOutputOperationContext &ctx) const { 34 | return true; // Write operations don't wait for input 35 | } 36 | 37 | std::string stringify() const { return "WriteInt32Native{}"; } 38 | }; 39 | static_assert(InputOutputOperationConcept); 40 | 41 | } // namespace operation 42 | 43 | } // namespace networkprotocoldsl 44 | 45 | #endif // NETWORKPROTOCOLDSL_OPERATION_HPP 46 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "(gdb) Launch", 9 | "type": "lldb", 10 | "request": "launch", 11 | // Resolved by CMake Tools: 12 | "program": "${command:cmake.launchTargetPath}", 13 | "args": [], 14 | "stopAtEntry": false, 15 | "cwd": "${workspaceFolder}", 16 | "environment": [ 17 | { 18 | // add the directory where our target was built to the PATHs 19 | // it gets resolved by CMake Tools: 20 | "name": "PATH", 21 | "value": "$PATH:${command:cmake.launchTargetDirectory}" 22 | }, 23 | { 24 | "name": "OTHER_VALUE", 25 | "value": "Something something" 26 | } 27 | ], 28 | "externalConsole": true, 29 | "MIMode": "gdb", 30 | "setupCommands": [ 31 | { 32 | "description": "Enable pretty-printing for gdb", 33 | "text": "-enable-pretty-printing", 34 | "ignoreFailures": true 35 | } 36 | ] 37 | } 38 | ] 39 | 40 | } -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/parse.cpp: -------------------------------------------------------------------------------- 1 | #include // Add this include for std::cerr 2 | #include 3 | #include 4 | 5 | namespace networkprotocoldsl::parser { 6 | 7 | std::optional> 8 | parse(const std::vector &tokens) { 9 | auto result = 10 | grammar::ProtocolDescription::parse(tokens.cbegin(), tokens.cend()); 11 | if (result.begin != tokens.cend()) { 12 | // Define a visitor lambda that uses stringify if available. 13 | auto tokenToStringVisitor = [](const auto &token) -> std::string { 14 | if constexpr (requires { token.stringify(); }) { 15 | return token.stringify(); 16 | } else { 17 | return ""; 18 | } 19 | }; 20 | // Print to stderr the next 10 tokens starting at the current position. 21 | std::cerr << "Parsing stopped. Next tokens: "; 22 | auto it = result.begin; 23 | int count = 0; 24 | while (it != tokens.cend() && count < 10) { 25 | std::cerr << std::visit(tokenToStringVisitor, *it) << " "; 26 | ++it; 27 | ++count; 28 | } 29 | std::cerr << std::endl; 30 | return std::nullopt; 31 | } 32 | if (result.node.has_value()) { 33 | return std::get>( 34 | result.node.value()); 35 | } else { 36 | return std::nullopt; 37 | } 38 | } 39 | 40 | } // namespace networkprotocoldsl::parser -------------------------------------------------------------------------------- /examples/smtpserver/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Add a custom command to generate the embedded file. 2 | 3 | set(SMTP_SOURCE_BASENAME smtp.networkprotocoldsl) 4 | set(SMTP_SOURCE_FILE ${CMAKE_CURRENT_SOURCE_DIR}/${SMTP_SOURCE_BASENAME}) 5 | set(SMTP_STRING_LITERAL_FILE ${CMAKE_CURRENT_BINARY_DIR}/${SMTP_SOURCE_BASENAME}.literal) 6 | add_custom_command( 7 | OUTPUT ${SMTP_STRING_LITERAL_FILE} 8 | COMMAND ${CMAKE_COMMAND} 9 | -DINPUT_FILE="${SMTP_SOURCE_FILE}" 10 | -DOUTPUT_FILE="${SMTP_STRING_LITERAL_FILE}" 11 | -P "${CMAKE_CURRENT_SOURCE_DIR}/generate_string_literals.cmake" 12 | DEPENDS ${SMTP_SOURCE_FILE} generate_string_literals.cmake 13 | COMMENT "Generating ${SMTP_STRING_LITERAL_FILE} from ${SMTP_SOURCE_FILE}" 14 | ) 15 | 16 | # Instead of defining a separate target, mark main.cpp as dependent on the generated file. 17 | set_source_files_properties( 18 | interpreted_program.cpp PROPERTIES OBJECT_DEPENDS ${SMTP_STRING_LITERAL_FILE} 19 | ) 20 | 21 | # Build the smtpserver executable and make it depend on the generated file. 22 | find_package(CLI11 REQUIRED) 23 | 24 | add_executable(smtpserver 25 | main.cpp 26 | interpreted_program.cpp 27 | interpreted_program.hpp 28 | server_core.cpp 29 | server_core.hpp 30 | server_processor.cpp 31 | server_processor.hpp 32 | ) 33 | target_link_libraries(smtpserver PRIVATE networkprotocoldsl networkprotocoldsl_uv CLI11::CLI11) 34 | target_compile_definitions(smtpserver PRIVATE -DSMTP_NETWORKPROTOCOLDSL_LITERAL="${SMTP_STRING_LITERAL_FILE}") 35 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/readstaticoctets.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NETWORKPROTOCOLDSL_OPERATION_READSTATICOCTETS_HPP 2 | #define NETWORKPROTOCOLDSL_OPERATION_READSTATICOCTETS_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace networkprotocoldsl { 15 | 16 | namespace operation { 17 | 18 | class ReadStaticOctets { 19 | const std::string contents; 20 | 21 | public: 22 | using Arguments = std::tuple<>; 23 | ReadStaticOctets(const std::string &_c) : contents(_c) {} 24 | 25 | OperationResult operator()(InputOutputOperationContext &ctx, 26 | Arguments a) const; 27 | size_t handle_read(InputOutputOperationContext &ctx, 28 | std::string_view in) const; 29 | 30 | std::string_view get_write_buffer(InputOutputOperationContext &ctx) const; 31 | void handle_eof(InputOutputOperationContext &ctx) const; 32 | 33 | size_t handle_write(InputOutputOperationContext &ctx, size_t s) const; 34 | 35 | bool ready_to_evaluate(InputOutputOperationContext &ctx) const; 36 | 37 | std::string stringify() const { 38 | return "ReadStaticOctets{contents: \"" + contents + "\"}"; 39 | } 40 | }; 41 | static_assert(InputOutputOperationConcept); 42 | 43 | } // namespace operation 44 | 45 | } // namespace networkprotocoldsl 46 | 47 | #endif // NETWORKPROTOCOLDSL_OPERATION_HPP 48 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/tree/tokensequence.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_TOKENSEQUENCE_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_TOKENSEQUENCE_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace networkprotocoldsl::parser::tree { 12 | 13 | struct TokenSequence { 14 | std::vector> tokens; 15 | std::optional terminator; 16 | std::optional escape; 17 | std::string stringify() const { 18 | std::string result = "tokens"; 19 | if (terminator.has_value() || escape.has_value()) { 20 | result += " <"; 21 | if (terminator.has_value()) { 22 | result += "terminator=\"" + terminator.value() + "\""; 23 | if (escape.has_value()) { 24 | result += ", "; 25 | } 26 | } 27 | if (escape.has_value()) { 28 | result += "escape=replace<\"" + escape.value().character + "\", \"" + escape.value().sequence + "\">"; 29 | } 30 | result += ">"; 31 | } 32 | result += " {"; 33 | for (const auto &part : tokens) { 34 | result += std::visit([](auto &&arg) { return arg->stringify(); }, *part); 35 | } 36 | result += "}"; 37 | return result; 38 | } 39 | }; 40 | 41 | } // namespace networkprotocoldsl::parser::tree 42 | 43 | #endif // INCLUDED_NETWORKPROTOCOLDSL_PARSER_TREE_TOKENSEQUENCE_HPP -------------------------------------------------------------------------------- /src/networkprotocoldsl/interpretercontext.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_INTERPRETERCONTEXT_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_INTERPRETERCONTEXT_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace networkprotocoldsl { 14 | 15 | class InterpreterResultIsNotValue : std::exception { 16 | public: 17 | OperationResult result; 18 | InterpreterResultIsNotValue(OperationResult r) : result(r){}; 19 | }; 20 | 21 | struct InterpreterContext { 22 | Interpreter interpreter; 23 | support::MutexLockQueue input_buffer; 24 | support::MutexLockQueue output_buffer; 25 | support::MutexLockQueue>> 26 | callback_request_queue; 27 | support::MutexLockQueue callback_response_queue; 28 | std::promise interpreter_result; 29 | void *additional_data; 30 | 31 | std::atomic eof = false; 32 | std::atomic exited = false; 33 | 34 | InterpreterContext(const Interpreter &interp) : interpreter(interp) {} 35 | InterpreterContext(Interpreter &&interp) : interpreter(std::move(interp)) {} 36 | 37 | InterpreterContext() = delete; 38 | InterpreterContext(const InterpreterContext &) = delete; 39 | InterpreterContext &operator=(const InterpreterContext &) = delete; 40 | }; 41 | 42 | } // namespace networkprotocoldsl 43 | 44 | #endif -------------------------------------------------------------------------------- /tests/017-grammar-identifier.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | using namespace networkprotocoldsl; 10 | 11 | TEST(IdentifierReferenceTest, IdentifierReferenceMatch) { 12 | std::vector tokens = {lexer::token::Identifier("myIdentifier")}; 13 | auto result = parser::grammar::IdentifierReference::parse(tokens.cbegin(), 14 | tokens.cend()); 15 | ASSERT_TRUE(result.node.has_value()); 16 | ASSERT_EQ(std::get>( 17 | result.node.value()) 18 | ->name, 19 | "myIdentifier"); 20 | } 21 | 22 | TEST(IdentifierReferenceTest, IdentifierReferenceWithMemberMatch) { 23 | auto maybe_tokens = lexer::tokenize("myIdentifier.member"); 24 | ASSERT_TRUE(maybe_tokens.has_value()); 25 | std::vector tokens = maybe_tokens.value(); 26 | auto result = parser::grammar::IdentifierReference::parse(tokens.cbegin(), 27 | tokens.cend()); 28 | ASSERT_TRUE(result.node.has_value()); 29 | auto id = std::get>( 30 | result.node.value()); 31 | ASSERT_EQ(id->name, "myIdentifier"); 32 | ASSERT_TRUE(id->member.has_value()); 33 | ASSERT_EQ(id->member.value()->name, "member"); 34 | } 35 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/codegen/generate_serializer.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_CODEGEN_GENERATE_SERIALIZER_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_CODEGEN_GENERATE_SERIALIZER_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace networkprotocoldsl::codegen { 12 | 13 | /** 14 | * Result of serializer generation. 15 | */ 16 | struct SerializerResult { 17 | std::string header; // Content of serializer.hpp 18 | std::string source; // Content of serializer.cpp 19 | std::vector errors; 20 | }; 21 | 22 | /** 23 | * Generate the serializer header and source files. 24 | * 25 | * Creates sans-IO serializers that: 26 | * - Accept typed message data structs 27 | * - Produce output bytes incrementally 28 | * - Track serialization progress for large messages 29 | * 30 | * Generated components: 31 | * - Individual message serializers (e.g., serialize_SMTPServerGreeting) 32 | * - Main Serializer class with state-based dispatch 33 | * 34 | * @param ctx The output context for namespace and formatting 35 | * @param info The protocol information 36 | * @return The generated header and source content, plus any errors 37 | */ 38 | SerializerResult generate_serializer(const OutputContext &ctx, 39 | const ProtocolInfo &info); 40 | 41 | } // namespace networkprotocoldsl::codegen 42 | 43 | #endif // INCLUDED_NETWORKPROTOCOLDSL_CODEGEN_GENERATE_SERIALIZER_HPP 44 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/writestaticoctets.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_OPERATION_WRITESTATICOCTETS_H 2 | #define INCLUDED_NETWORKPROTOCOLDSL_OPERATION_WRITESTATICOCTETS_H 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace networkprotocoldsl { 15 | 16 | namespace operation { 17 | 18 | class WriteStaticOctets { 19 | const std::string contents; 20 | 21 | public: 22 | using Arguments = std::tuple<>; 23 | WriteStaticOctets(const std::string &c) : contents(c) {} 24 | 25 | OperationResult operator()(InputOutputOperationContext &ctx, 26 | Arguments a) const; 27 | size_t handle_read(InputOutputOperationContext &ctx, 28 | std::string_view in) const; 29 | 30 | std::string_view get_write_buffer(InputOutputOperationContext &ctx) const; 31 | void handle_eof(InputOutputOperationContext &ctx) const; 32 | 33 | size_t handle_write(InputOutputOperationContext &ctx, size_t s) const; 34 | 35 | bool ready_to_evaluate(InputOutputOperationContext &ctx) const { 36 | return true; // Write operations don't wait for input 37 | } 38 | 39 | std::string stringify() const { 40 | return "WriteStaticOctets{contents: \"" + contents + "\"}"; 41 | } 42 | }; 43 | static_assert(InputOutputOperationConcept); 44 | 45 | } // namespace operation 46 | 47 | } // namespace networkprotocoldsl 48 | 49 | #endif -------------------------------------------------------------------------------- /src/networkprotocoldsl/codegen/generate_parser.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_CODEGEN_GENERATE_PARSER_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_CODEGEN_GENERATE_PARSER_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace networkprotocoldsl::codegen { 12 | 13 | /** 14 | * Result of parser generation. 15 | */ 16 | struct ParserResult { 17 | std::string header; // Content of parser.hpp 18 | std::string source; // Content of parser.cpp 19 | std::vector errors; 20 | }; 21 | 22 | /** 23 | * Generate the parser header and source files. 24 | * 25 | * Creates sans-IO parsers that: 26 | * - Accept input bytes incrementally 27 | * - Return parse status (NeedMoreData, Complete, Error) 28 | * - Track parsing state for resumption 29 | * - Extract message data into typed structs 30 | * 31 | * Generated components: 32 | * - ParseStatus enum 33 | * - ParseResult struct 34 | * - Individual message parsers (e.g., SMTPServerGreetingParser) 35 | * - Main Parser class with state-based dispatch 36 | * 37 | * @param ctx The output context for namespace and formatting 38 | * @param info The protocol information 39 | * @return The generated header and source content, plus any errors 40 | */ 41 | ParserResult generate_parser(const OutputContext &ctx, 42 | const ProtocolInfo &info); 43 | 44 | } // namespace networkprotocoldsl::codegen 45 | 46 | #endif // INCLUDED_NETWORKPROTOCOLDSL_CODEGEN_GENERATE_PARSER_HPP 47 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/print_optreenode.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace networkprotocoldsl { 10 | 11 | void print_optreenode(const OpTreeNode &node, std::ostream &os, 12 | const std::string &prefix_firstline, 13 | const std::string &prefix_others) { 14 | 15 | std::visit( 16 | [&](const auto &op) { 17 | if constexpr (StringifiableOperationConcept) { 18 | std::string opStr = op.stringify(); 19 | std::istringstream stream(opStr); 20 | std::string line; 21 | bool first = true; 22 | while (std::getline(stream, line)) { 23 | os << (first ? prefix_firstline : prefix_others) << line 24 | << std::endl; 25 | first = false; 26 | } 27 | } else { 28 | os << prefix_firstline << "" << std::endl; 29 | } 30 | }, 31 | node.operation); 32 | 33 | for (size_t i = 0; i < node.children.size(); ++i) { 34 | const auto &child = node.children[i]; 35 | std::string new_prefix_firstline = 36 | prefix_others + (i == node.children.size() - 1 ? " └── " : " ├── "); 37 | std::string new_prefix_others = 38 | prefix_others + (i == node.children.size() - 1 ? " " : " │ "); 39 | print_optreenode(child, os, new_prefix_firstline, new_prefix_others); 40 | } 41 | } 42 | 43 | } // namespace networkprotocoldsl 44 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/writestaticoctets.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | namespace networkprotocoldsl::operation { 8 | 9 | OperationResult WriteStaticOctets::operator()(InputOutputOperationContext &ctx, 10 | Arguments a) const { 11 | if (ctx.buffer.length() == 0) { 12 | ctx.buffer = contents; 13 | ctx.it = ctx.buffer.begin(); 14 | } 15 | if (ctx.it != ctx.buffer.end()) { 16 | if (ctx.eof) { 17 | return value::RuntimeError::ProtocolMismatchError; 18 | } else { 19 | return ReasonForBlockedOperation::WaitingForWrite; 20 | } 21 | } else { 22 | return 0; 23 | } 24 | } 25 | 26 | size_t WriteStaticOctets::handle_read(InputOutputOperationContext &ctx, 27 | std::string_view in) const { 28 | return 0; 29 | } 30 | 31 | void WriteStaticOctets::handle_eof(InputOutputOperationContext &ctx) const { 32 | ctx.eof = true; 33 | } 34 | 35 | std::string_view 36 | WriteStaticOctets::get_write_buffer(InputOutputOperationContext &ctx) const { 37 | return std::string_view(ctx.it, ctx.buffer.end()); 38 | } 39 | 40 | size_t WriteStaticOctets::handle_write(InputOutputOperationContext &ctx, 41 | size_t s) const { 42 | size_t consumed = 0; 43 | while (s > 0 && ctx.it != ctx.buffer.end()) { 44 | s--; 45 | consumed++; 46 | ctx.it++; 47 | } 48 | return consumed; 49 | } 50 | 51 | } // namespace networkprotocoldsl::operation 52 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/codegen/generate_state_machine.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_CODEGEN_GENERATE_STATE_MACHINE_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_CODEGEN_GENERATE_STATE_MACHINE_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace networkprotocoldsl::codegen { 12 | 13 | /** 14 | * Result of state machine generation. 15 | */ 16 | struct StateMachineResult { 17 | std::string header; // Content of state_machine.hpp 18 | std::string source; // Content of state_machine.cpp 19 | std::vector errors; 20 | }; 21 | 22 | /** 23 | * Generate the state machine header and source files. 24 | * 25 | * Creates state machine coordinators that: 26 | * - Coordinate between IO, parser, and callback threads 27 | * - Manage state transitions 28 | * - Provide thread-safe queues for data passing 29 | * - Support the sans-IO architecture 30 | * 31 | * Generated components: 32 | * - ClientStateMachine - State machine from client perspective 33 | * - ServerStateMachine - State machine from server perspective 34 | * 35 | * @param ctx The output context for namespace and formatting 36 | * @param info The protocol information 37 | * @return The generated header and source content, plus any errors 38 | */ 39 | StateMachineResult generate_state_machine(const OutputContext &ctx, 40 | const ProtocolInfo &info); 41 | 42 | } // namespace networkprotocoldsl::codegen 43 | 44 | #endif // INCLUDED_NETWORKPROTOCOLDSL_CODEGEN_GENERATE_STATE_MACHINE_HPP 45 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/executionstackframe.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_EXECUTIONSTACKFRAME_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_EXECUTIONSTACKFRAME_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace networkprotocoldsl { 14 | 15 | using OperationContextVariant = 16 | std::variant; 18 | 19 | /** 20 | * The execution frame points to a specific operation and the 21 | * accumulation of inputs for that operation. The actual operation can 22 | * only happen when the right number of inputs has been provided. 23 | */ 24 | class ExecutionStackFrame { 25 | public: 26 | const OpTreeNode &optreenode; 27 | 28 | private: 29 | std::shared_ptr> accumulator; 30 | OperationContextVariant ctx; 31 | std::shared_ptr pad; 32 | 33 | public: 34 | ExecutionStackFrame(const OpTreeNode &o, std::shared_ptr pad); 35 | 36 | bool has_arguments_ready(); 37 | 38 | OperationResult execute(); 39 | 40 | void push_back(Value v); 41 | 42 | const OpTreeNode &next_op(); 43 | 44 | std::shared_ptr get_pad(); 45 | 46 | const Operation &get_operation(); 47 | 48 | size_t get_children_count(); 49 | 50 | OperationContextVariant &get_context(); 51 | 52 | std::shared_ptr> get_accumulator(); 53 | }; 54 | 55 | } // namespace networkprotocoldsl 56 | 57 | #endif 58 | -------------------------------------------------------------------------------- /tests/011-write-inttoascii.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | TEST(write_inttoascii, good_write) { 13 | using namespace networkprotocoldsl; 14 | 15 | operation::Int32Literal lit(42); 16 | operation::IntToAscii itoa; 17 | operation::WriteOctets wo; 18 | 19 | auto optree1 = std::make_shared(OpTree({wo, {{itoa, {{lit, {}}}}}})); 20 | InterpretedProgram p1(optree1); 21 | Interpreter i1 = p1.get_instance(); 22 | 23 | ASSERT_EQ(ContinuationState::Ready, i1.step()); 24 | ASSERT_EQ(42, std::get(std::get(i1.get_result()))); 25 | 26 | ASSERT_EQ(ContinuationState::Ready, i1.step()); 27 | ASSERT_EQ("42", 28 | *(std::get(std::get(i1.get_result())).data)); 29 | 30 | ASSERT_EQ(ContinuationState::Blocked, i1.step()); 31 | ASSERT_EQ(ReasonForBlockedOperation::WaitingForWrite, 32 | std::get(i1.get_result())); 33 | 34 | auto buf = i1.get_write_buffer(); 35 | ASSERT_EQ(2, buf.length()); 36 | ASSERT_EQ("42", buf); 37 | i1.handle_write(buf.length()); 38 | 39 | ASSERT_EQ(ContinuationState::Exited, i1.step()); 40 | ASSERT_EQ(true, std::holds_alternative(i1.get_result())); 41 | ASSERT_EQ(true, 42 | std::holds_alternative(std::get(i1.get_result()))); 43 | } 44 | -------------------------------------------------------------------------------- /tests/005-operator-eq.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | TEST(operator_eq, is_equal) { 8 | using namespace networkprotocoldsl; 9 | 10 | operation::Int32Literal il1(10); 11 | operation::Int32Literal il2(20); 12 | operation::Eq eq; 13 | 14 | auto optree = std::make_shared(OpTree({eq, {{il1, {}}, {il2, {}}}})); 15 | 16 | InterpretedProgram p(optree); 17 | Interpreter i1 = p.get_instance(); 18 | ASSERT_EQ(ContinuationState::Ready, i1.step()); 19 | ASSERT_EQ(10, std::get(std::get(i1.get_result()))); 20 | ASSERT_EQ(ContinuationState::Ready, i1.step()); 21 | ASSERT_EQ(20, std::get(std::get(i1.get_result()))); 22 | ASSERT_EQ(ContinuationState::Exited, i1.step()); 23 | ASSERT_EQ(false, std::get(std::get(i1.get_result()))); 24 | } 25 | 26 | TEST(operator_eq, is_different) { 27 | using namespace networkprotocoldsl; 28 | 29 | operation::Int32Literal il1(10); 30 | operation::Int32Literal il2(10); 31 | operation::Eq eq; 32 | 33 | auto optree = std::make_shared(OpTree({eq, {{il1, {}}, {il2, {}}}})); 34 | 35 | InterpretedProgram p(optree); 36 | Interpreter i1 = p.get_instance(); 37 | ASSERT_EQ(ContinuationState::Ready, i1.step()); 38 | ASSERT_EQ(10, std::get(std::get(i1.get_result()))); 39 | ASSERT_EQ(ContinuationState::Ready, i1.step()); 40 | ASSERT_EQ(10, std::get(std::get(i1.get_result()))); 41 | ASSERT_EQ(ContinuationState::Exited, i1.step()); 42 | ASSERT_EQ(true, std::get(std::get(i1.get_result()))); 43 | } 44 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "*.tcc": "cpp", 4 | "optional": "cpp", 5 | "system_error": "cpp", 6 | "array": "cpp", 7 | "deque": "cpp", 8 | "list": "cpp", 9 | "string": "cpp", 10 | "unordered_map": "cpp", 11 | "vector": "cpp", 12 | "string_view": "cpp", 13 | "initializer_list": "cpp", 14 | "any": "cpp", 15 | "atomic": "cpp", 16 | "bit": "cpp", 17 | "cctype": "cpp", 18 | "clocale": "cpp", 19 | "cmath": "cpp", 20 | "codecvt": "cpp", 21 | "compare": "cpp", 22 | "concepts": "cpp", 23 | "condition_variable": "cpp", 24 | "cstdarg": "cpp", 25 | "cstddef": "cpp", 26 | "cstdint": "cpp", 27 | "cstdio": "cpp", 28 | "cstdlib": "cpp", 29 | "cstring": "cpp", 30 | "ctime": "cpp", 31 | "cwchar": "cpp", 32 | "cwctype": "cpp", 33 | "map": "cpp", 34 | "set": "cpp", 35 | "exception": "cpp", 36 | "algorithm": "cpp", 37 | "functional": "cpp", 38 | "iterator": "cpp", 39 | "memory": "cpp", 40 | "memory_resource": "cpp", 41 | "numeric": "cpp", 42 | "random": "cpp", 43 | "ratio": "cpp", 44 | "tuple": "cpp", 45 | "type_traits": "cpp", 46 | "utility": "cpp", 47 | "fstream": "cpp", 48 | "future": "cpp", 49 | "iomanip": "cpp", 50 | "iosfwd": "cpp", 51 | "iostream": "cpp", 52 | "istream": "cpp", 53 | "limits": "cpp", 54 | "mutex": "cpp", 55 | "new": "cpp", 56 | "numbers": "cpp", 57 | "ostream": "cpp", 58 | "semaphore": "cpp", 59 | "sstream": "cpp", 60 | "stdexcept": "cpp", 61 | "stop_token": "cpp", 62 | "streambuf": "cpp", 63 | "thread": "cpp", 64 | "cinttypes": "cpp", 65 | "typeinfo": "cpp", 66 | "variant": "cpp" 67 | } 68 | } -------------------------------------------------------------------------------- /tests/data/038-http-with-continuation.txt: -------------------------------------------------------------------------------- 1 | // HTTP/1.1-like protocol with header continuation line support 2 | // This tests the escape=replace<"\n", "\r\n "> feature 3 | // where "\r\n " on the wire represents a newline in the header value 4 | 5 | message "Request Line" { 6 | when: Open; 7 | then: ExpectResponse; 8 | agent: Client; 9 | data: { 10 | method: str; 11 | path: str; 12 | host: str; 13 | } 14 | parts { 15 | tokens { method } 16 | terminator { " " } 17 | tokens { path } 18 | terminator { " HTTP/1.1\r\nHost: " } 19 | // host field with escape support - embedded terminator means no terminator block needed 20 | tokens> { host } 21 | } 22 | } 23 | 24 | message "Response Line" { 25 | when: ExpectResponse; 26 | then: Done; 27 | agent: Server; 28 | data: { 29 | status: int; 30 | reason: str; 31 | content_type: str; 32 | } 33 | parts { 34 | tokens { "HTTP/1.1 " status } 35 | terminator { " " } 36 | tokens { reason } 37 | terminator { "\r\nContent-Type: " } 38 | // content_type field with escape support - embedded terminator 39 | tokens> { content_type } 40 | } 41 | } 42 | 43 | message "Close Connection" { 44 | when: Done; 45 | then: Closed; 46 | agent: Client; 47 | } 48 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/writeint32native.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | namespace networkprotocoldsl::operation { 9 | 10 | OperationResult WriteInt32Native::operator()(InputOutputOperationContext &ctx, 11 | Arguments a) const { 12 | if (ctx.buffer.length() == 0) { 13 | int v = std::get(std::get<0>(a)); 14 | char b[4] = {0, 0, 0, 0}; 15 | std::memcpy(b, &v, 4); 16 | ctx.buffer = {&(b[0]), &(b[4])}; 17 | ctx.it = ctx.buffer.begin(); 18 | } 19 | if (ctx.it != ctx.buffer.end()) { 20 | if (ctx.eof) { 21 | return value::RuntimeError::ProtocolMismatchError; 22 | } else { 23 | return ReasonForBlockedOperation::WaitingForWrite; 24 | } 25 | } else { 26 | return 0; 27 | } 28 | } 29 | 30 | size_t WriteInt32Native::handle_read(InputOutputOperationContext &ctx, 31 | std::string_view in) const { 32 | return 0; 33 | } 34 | 35 | std::string_view 36 | WriteInt32Native::get_write_buffer(InputOutputOperationContext &ctx) const { 37 | return std::string_view(ctx.it, ctx.buffer.end()); 38 | } 39 | 40 | void WriteInt32Native::handle_eof(InputOutputOperationContext &ctx) const { 41 | ctx.eof = true; 42 | } 43 | 44 | size_t WriteInt32Native::handle_write(InputOutputOperationContext &ctx, 45 | size_t s) const { 46 | size_t consumed = 0; 47 | while (s > 0 && ctx.it != ctx.buffer.end()) { 48 | s--; 49 | consumed++; 50 | ctx.it++; 51 | } 52 | return consumed; 53 | } 54 | 55 | } // namespace networkprotocoldsl::operation 56 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/statemachineoperation.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_OPERATION_STATEMACHINEOPERATION_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_OPERATION_STATEMACHINEOPERATION_HPP 3 | 4 | #include 5 | #include 6 | #include // Added for std::ostringstream 7 | #include 8 | 9 | namespace networkprotocoldsl { 10 | class OpTree; // Forward declaration 11 | } 12 | 13 | namespace networkprotocoldsl::operation { 14 | 15 | class StateMachineOperation { 16 | public: 17 | struct TransitionInfo { 18 | std::shared_ptr callback_optree; 19 | std::vector argument_names; 20 | std::string target_state; 21 | }; 22 | 23 | using StateTransitionMap = std::unordered_map; 24 | 25 | struct StateInfo { 26 | std::shared_ptr callback_optree; 27 | StateTransitionMap transitions; 28 | }; 29 | 30 | using StateMap = std::unordered_map; 31 | 32 | using Arguments = std::tuple<>; 33 | 34 | StateMachineOperation(StateMap states) : states(states) {} 35 | OperationResult operator()(ControlFlowOperationContext &ctx, 36 | Arguments a) const; 37 | Value get_callable(ControlFlowOperationContext &ctx) const; 38 | std::shared_ptr> 39 | get_argument_list(ControlFlowOperationContext &ctx) const; 40 | void set_callable_invoked(ControlFlowOperationContext &ctx) const; 41 | void set_callable_return(ControlFlowOperationContext &ctx, Value v) const; 42 | std::string stringify() const; 43 | 44 | private: 45 | StateMap states; 46 | }; 47 | static_assert(ControlFlowOperationConcept); 48 | 49 | } // namespace networkprotocoldsl::operation 50 | 51 | #endif -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/dictionaryset.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NETWORKPROTOCOLDSL_OPERATION_DICTIONARYSET_HPP 2 | #define NETWORKPROTOCOLDSL_OPERATION_DICTIONARYSET_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace networkprotocoldsl::operation { 8 | 9 | struct DictionarySet { 10 | private: 11 | Value set(const value::Dictionary &dict, const auto &value) const { 12 | auto new_dict = std::make_shared(); 13 | for (const auto &[k, v] : *dict.members) { 14 | if (k != key) { 15 | new_dict->emplace(k, v); 16 | } 17 | } 18 | new_dict->emplace(key, value); 19 | return value::Dictionary(new_dict); 20 | } 21 | Value set(const value::Dictionary &dict, 22 | const value::RuntimeError &err) const { 23 | return err; 24 | } 25 | Value set(const value::RuntimeError &err, const auto &) const { return err; } 26 | Value set(const auto &, const auto &) const { 27 | return value::RuntimeError::TypeError; 28 | } 29 | 30 | public: 31 | std::string key; 32 | Value value; 33 | DictionarySet(const std::string &key) : key(key) {} 34 | 35 | using Arguments = std::tuple; 36 | 37 | Value operator()(const Arguments &args) const { 38 | return std::visit( 39 | [&](auto &&dict, auto &&value) -> Value { return set(dict, value); }, 40 | std::get<0>(args), std::get<1>(args)); 41 | } 42 | 43 | std::string stringify() const { 44 | return "DictionarySet(key: \"" + key + "\")"; 45 | } 46 | }; 47 | 48 | static_assert(InterpretedOperationConcept, 49 | "DictionarySet must conform to InterpretedOperationConcept"); 50 | 51 | } // namespace networkprotocoldsl::operation 52 | 53 | #endif // NETWORKPROTOCOLDSL_OPERATION_DICTIONARYSET_HPP 54 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/readint32native.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | namespace networkprotocoldsl::operation { 7 | 8 | OperationResult ReadInt32Native::operator()(InputOutputOperationContext &ctx, 9 | Arguments a) const { 10 | if (ctx.buffer.length() < 4) { 11 | if (ctx.eof) 12 | return value::RuntimeError::ProtocolMismatchError; 13 | return ReasonForBlockedOperation::WaitingForRead; 14 | } else { 15 | int v = 0; 16 | std::memcpy(&v, ctx.buffer.c_str(), 4); 17 | return v; 18 | } 19 | } 20 | 21 | size_t ReadInt32Native::handle_read(InputOutputOperationContext &ctx, 22 | std::string_view in) const { 23 | size_t expecting = 4 - ctx.buffer.length(); 24 | if (expecting > 0) { 25 | int coming = in.length(); 26 | if (in.length() > expecting) { 27 | ctx.buffer.append(in, 0, expecting); 28 | return expecting; 29 | } else { 30 | ctx.buffer.append(in, 0, coming); 31 | return coming; 32 | } 33 | } else { 34 | return 0; 35 | } 36 | } 37 | 38 | void ReadInt32Native::handle_eof(InputOutputOperationContext &ctx) const { 39 | ctx.eof = true; 40 | } 41 | 42 | std::string_view 43 | ReadInt32Native::get_write_buffer(InputOutputOperationContext &ctx) const { 44 | return ctx.buffer; 45 | } 46 | 47 | size_t ReadInt32Native::handle_write(InputOutputOperationContext &ctx, 48 | size_t s) const { 49 | return 0; 50 | } 51 | 52 | bool ReadInt32Native::ready_to_evaluate( 53 | InputOutputOperationContext &ctx) const { 54 | // Ready when we have 4 bytes, or at EOF 55 | return ctx.buffer.length() >= 4 || ctx.eof; 56 | } 57 | 58 | } // namespace networkprotocoldsl::operation 59 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/readstaticoctets.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | namespace networkprotocoldsl::operation { 7 | 8 | OperationResult ReadStaticOctets::operator()(InputOutputOperationContext &ctx, 9 | Arguments a) const { 10 | if (ctx.buffer.length() < contents.length()) { 11 | if (ctx.eof) { 12 | return value::RuntimeError::ProtocolMismatchError; 13 | } else { 14 | return ReasonForBlockedOperation::WaitingForRead; 15 | } 16 | } else { 17 | if (strncmp(ctx.buffer.c_str(), contents.c_str(), contents.length()) == 0) { 18 | return true; 19 | } else { 20 | return value::RuntimeError::ProtocolMismatchError; 21 | } 22 | } 23 | } 24 | 25 | size_t ReadStaticOctets::handle_read(InputOutputOperationContext &ctx, 26 | std::string_view in) const { 27 | if (in.length() < contents.length()) { 28 | return 0; 29 | } else { 30 | ctx.buffer = std::string(in.begin(), contents.length()); 31 | return contents.length(); 32 | } 33 | } 34 | 35 | void ReadStaticOctets::handle_eof(InputOutputOperationContext &ctx) const { 36 | ctx.eof = true; 37 | } 38 | 39 | std::string_view 40 | ReadStaticOctets::get_write_buffer(InputOutputOperationContext &ctx) const { 41 | return ctx.buffer; 42 | } 43 | 44 | size_t ReadStaticOctets::handle_write(InputOutputOperationContext &ctx, 45 | size_t s) const { 46 | return 0; 47 | } 48 | 49 | bool ReadStaticOctets::ready_to_evaluate( 50 | InputOutputOperationContext &ctx) const { 51 | // Ready when we have enough bytes to compare, or at EOF 52 | return ctx.buffer.length() >= contents.length() || ctx.eof; 53 | } 54 | 55 | } // namespace networkprotocoldsl::operation 56 | -------------------------------------------------------------------------------- /src/networkprotocoldsl_uv/asyncworkqueue.cpp: -------------------------------------------------------------------------------- 1 | #include "asyncworkqueue.hpp" 2 | #include 3 | 4 | namespace networkprotocoldsl_uv { 5 | 6 | static void async_work_callback(uv_async_t *handle) { 7 | AsyncWorkQueue *queue = static_cast(handle->data); 8 | queue->process(); 9 | } 10 | 11 | // Callback invoked by uv_close during shutdown. 12 | static void shutdown_close_cb(uv_handle_t *handle) { 13 | AsyncWorkQueue *self = static_cast(handle->data); 14 | self->shutdown_promise.set_value(); 15 | } 16 | 17 | AsyncWorkQueue::AsyncWorkQueue(uv_loop_t *loop) { 18 | uv_async_init(loop, &async_handle_, async_work_callback); 19 | async_handle_.data = this; 20 | } 21 | 22 | AsyncWorkQueue::~AsyncWorkQueue() { 23 | // If shutdown wasn't invoked already, trigger it and wait. 24 | if (!shutdown_called_) { 25 | shutdown().wait(); 26 | } 27 | } 28 | 29 | void AsyncWorkQueue::push_work(std::function work) { 30 | if (shutdown_called_.load()) { 31 | throw std::runtime_error( 32 | "Cannot push work after shutdown has been initiated"); 33 | } 34 | work_queue_.push_back(work); 35 | uv_async_send(&async_handle_); 36 | } 37 | 38 | void AsyncWorkQueue::process() { 39 | while (true) { 40 | auto work_opt = work_queue_.pop(); 41 | if (!work_opt.has_value()) 42 | break; 43 | work_opt.value()(); 44 | } 45 | } 46 | 47 | uv_async_t *AsyncWorkQueue::get_async_handle() { return &async_handle_; } 48 | 49 | std::future AsyncWorkQueue::shutdown() { 50 | if (!shutdown_called_.load()) { 51 | // Submit a work item that calls uv_close with a callback that fulfills 52 | // shutdown_promise_. 53 | push_work([this]() { 54 | uv_close(reinterpret_cast(&async_handle_), 55 | shutdown_close_cb); 56 | shutdown_called_.store(true); 57 | }); 58 | } 59 | return shutdown_promise.get_future(); 60 | } 61 | 62 | } // namespace networkprotocoldsl_uv 63 | -------------------------------------------------------------------------------- /src/networkprotocoldsl_uv/libuvclientwrapper.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NETWORKPROTOCOLDSL_UV_LIBUVCLIENTWRAPPER_HPP 2 | #define NETWORKPROTOCOLDSL_UV_LIBUVCLIENTWRAPPER_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace networkprotocoldsl_uv { 15 | 16 | class LibuvClientWrapper { 17 | public: 18 | LibuvClientWrapper( 19 | const networkprotocoldsl::InterpretedProgram &program, 20 | const networkprotocoldsl::InterpreterRunner::callback_map &callbacks, 21 | AsyncWorkQueue &async_queue); 22 | ~LibuvClientWrapper(); 23 | 24 | // Start connecting; returns a future holding the connection result. 25 | std::future start(const std::string &ip, 26 | int port); 27 | 28 | // Returns a future for the interpreter's result. 29 | std::future &result(); 30 | 31 | // ...no stop() needed as completion is indicated by result future. 32 | 33 | private: 34 | networkprotocoldsl::InterpreterCollectionManager mgr_; 35 | networkprotocoldsl::InterpreterRunner runner_; 36 | std::unique_ptr uv_client_runner_; 37 | AsyncWorkQueue *async_queue_; 38 | uv_loop_t *loop_; 39 | networkprotocoldsl::InterpretedProgram program_; 40 | 41 | std::thread interpreter_thread_; 42 | std::thread callback_thread_; 43 | 44 | // Disable copy. 45 | LibuvClientWrapper(const LibuvClientWrapper &) = delete; 46 | LibuvClientWrapper &operator=(const LibuvClientWrapper &) = delete; 47 | }; 48 | 49 | } // namespace networkprotocoldsl_uv 50 | 51 | #endif // NETWORKPROTOCOLDSL_UV_LIBUVCLIENTWRAPPER_HPP 52 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/support/notificationsignal.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_SUPPORT_NOTIFICATIONSIGNAL_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_SUPPORT_NOTIFICATIONSIGNAL_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #define NOTIFICATIONSIGNAL_DEBUG(x) 11 | //#define NOTIFICATIONSIGNAL_DEBUG(x) std::cerr << "NotificationSignal" << "[" 12 | //<< name << "/" << std::this_thread::get_id() << "] " << __func__ << ": " << x 13 | //<< std::endl 14 | 15 | namespace networkprotocoldsl::support { 16 | 17 | class NotificationSignal { 18 | std::string name; 19 | std::mutex mtx; 20 | std::condition_variable cv; 21 | std::atomic notified = false; 22 | 23 | public: 24 | NotificationSignal(const std::string &n) 25 | : name(n), mtx(std::mutex()), cv(std::condition_variable()) {} 26 | NotificationSignal(const NotificationSignal &in) = delete; 27 | NotificationSignal(NotificationSignal &&in) = delete; 28 | NotificationSignal &operator=(const NotificationSignal &) = delete; 29 | 30 | void notify() { 31 | { 32 | NOTIFICATIONSIGNAL_DEBUG("before guard"); 33 | std::lock_guard lk(mtx); 34 | NOTIFICATIONSIGNAL_DEBUG("after guard"); 35 | notified.store(true); 36 | } 37 | NOTIFICATIONSIGNAL_DEBUG("before notify"); 38 | cv.notify_all(); 39 | NOTIFICATIONSIGNAL_DEBUG("after notify"); 40 | } 41 | 42 | void wait() { 43 | NOTIFICATIONSIGNAL_DEBUG("before lock"); 44 | std::unique_lock lk(mtx); 45 | NOTIFICATIONSIGNAL_DEBUG("after lock"); 46 | if (!notified.load()) { 47 | NOTIFICATIONSIGNAL_DEBUG("not notified, before wait"); 48 | cv.wait(lk); 49 | NOTIFICATIONSIGNAL_DEBUG("not notified, after wait"); 50 | } else { 51 | NOTIFICATIONSIGNAL_DEBUG("no wait"); 52 | } 53 | notified.store(false); 54 | } 55 | }; 56 | 57 | } // namespace networkprotocoldsl::support 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /tests/012-read-int-and-terminator.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | TEST(read_int_and_terminator, good_read) { 17 | using namespace networkprotocoldsl; 18 | 19 | const std::string_view input = "a=42 "; 20 | 21 | operation::OpSequence ops; 22 | operation::ReadOctetsUntilTerminator read_until_eq("="); 23 | operation::ReadIntFromAscii read_i; 24 | 25 | auto optree1 = std::make_shared( 26 | OpTree({ops, {{read_until_eq, {}}, {read_i, {}}}})); 27 | InterpretedProgram p1(optree1); 28 | Interpreter i1 = p1.get_instance(); 29 | 30 | ASSERT_EQ(ContinuationState::Blocked, i1.step()); 31 | ASSERT_EQ(ReasonForBlockedOperation::WaitingForRead, 32 | std::get(i1.get_result())); 33 | 34 | size_t consumed = i1.handle_read(input); 35 | ASSERT_EQ(2, consumed); 36 | 37 | ASSERT_EQ(ContinuationState::Ready, i1.step()); 38 | ASSERT_EQ("a", 39 | *(std::get(std::get(i1.get_result())).data)); 40 | 41 | ASSERT_EQ(ContinuationState::Blocked, i1.step()); 42 | ASSERT_EQ(ReasonForBlockedOperation::WaitingForRead, 43 | std::get(i1.get_result())); 44 | 45 | consumed = i1.handle_read(input.substr(consumed)); 46 | ASSERT_EQ(2, consumed); 47 | 48 | ASSERT_EQ(ContinuationState::Ready, i1.step()); 49 | ASSERT_EQ(42, std::get(std::get(i1.get_result()))); 50 | } 51 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/continuation.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_CONTINUATION_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_CONTINUATION_HPP 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace networkprotocoldsl { 10 | 11 | /** 12 | * Represents the state of the execution consider external factors, 13 | * such as waiting for a callback to be evaluated. 14 | */ 15 | enum class ContinuationState { MissingArguments, Ready, Blocked, Exited }; 16 | 17 | /** 18 | * A continuation represents the state of a single thread of execution 19 | * within a intepreter. The interpreter may have many of those 20 | * running. 21 | */ 22 | class Continuation { 23 | std::shared_ptr optree; 24 | std::stack stack; 25 | std::shared_ptr pad; 26 | ContinuationState state = ContinuationState::MissingArguments; 27 | OperationResult result = false; 28 | 29 | public: 30 | Continuation(std::shared_ptr ot, 31 | std::shared_ptr pad); 32 | 33 | ExecutionStackFrame &top(); 34 | 35 | ContinuationState result_to_state(); 36 | 37 | ContinuationState prepare(); 38 | 39 | std::shared_ptr get_pad(); 40 | 41 | ContinuationState step(); 42 | 43 | OperationResult get_result(); 44 | 45 | Value get_callable(); 46 | 47 | std::shared_ptr> get_argument_list(); 48 | 49 | void set_callable_invoked(); 50 | 51 | void set_callable_return(Value v); 52 | 53 | std::string get_callback_key(); 54 | 55 | void set_callback_called(); 56 | 57 | const std::vector &get_callback_arguments(); 58 | 59 | void set_callback_return(Value v); 60 | 61 | size_t handle_read(std::string_view in); 62 | 63 | std::string_view get_write_buffer(); 64 | 65 | size_t handle_write(size_t s); 66 | 67 | void handle_eof(); 68 | 69 | bool ready_to_evaluate(); 70 | }; 71 | 72 | } // namespace networkprotocoldsl 73 | 74 | #endif 75 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/sema/support.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_SEMA_SUPPORT_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_SEMA_SUPPORT_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | namespace networkprotocoldsl::sema { 12 | 13 | using ParseStateTraits = parser::support::ParseStateTraits< 14 | std::vector::const_iterator, 15 | std::variant, ast::Action>>; 16 | 17 | template 18 | inline std::vector 19 | unroll_variant(const V &v) { 20 | auto r = std::vector(); 21 | for (const auto &part : v) { 22 | std::visit([&](auto &&arg) { r.push_back(arg); }, *part); 23 | } 24 | return r; 25 | } 26 | 27 | inline void append_actions(std::vector &actions, 28 | const std::vector &new_actions) { 29 | actions.insert(actions.end(), new_actions.cbegin(), new_actions.cend()); 30 | } 31 | 32 | inline void append_actions(std::vector &actions, 33 | ast::Action new_action) { 34 | actions.push_back(new_action); 35 | } 36 | 37 | inline std::vector 38 | flatten_actions(ParseStateTraits::ParseNode node) { 39 | std::vector actions; 40 | std::visit([&](auto &&t) { append_actions(actions, t); }, node); 41 | return actions; 42 | } 43 | 44 | inline std::vector 45 | flatten_actions(std::vector nodes) { 46 | std::vector actions; 47 | for (auto &node : nodes) { 48 | auto flattened = flatten_actions(node); 49 | actions.insert(actions.end(), flattened.cbegin(), flattened.cend()); 50 | } 51 | return actions; 52 | } 53 | 54 | } // namespace networkprotocoldsl::sema 55 | 56 | #endif -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/grammar/literals.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_PARSER_GRAMMAR_LITERALS_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_PARSER_GRAMMAR_LITERALS_HPP 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | namespace networkprotocoldsl::parser::grammar { 11 | 12 | class BooleanLiteral 13 | : public support::RecursiveParser> { 15 | public: 16 | static constexpr const char *name = "BooleanLiteral"; 17 | static void partial_match() {} 18 | static ParseStateReturn match(TokenIterator begin, TokenIterator end, 19 | lexer::token::literal::Boolean b) { 20 | return {std::make_shared(b.value), begin, end}; 21 | } 22 | }; 23 | 24 | class IntegerLiteral 25 | : public support::RecursiveParser> { 27 | public: 28 | static constexpr const char *name = "IntegerLiteral"; 29 | static void partial_match() {} 30 | static ParseStateReturn match(TokenIterator begin, TokenIterator end, 31 | lexer::token::literal::Integer i) { 32 | return {std::make_shared(i.value), begin, end}; 33 | } 34 | }; 35 | 36 | class StringLiteral 37 | : public support::RecursiveParser> { 39 | public: 40 | static constexpr const char *name = "StringLiteral"; 41 | static void partial_match() {} 42 | static ParseStateReturn match(TokenIterator begin, TokenIterator end, 43 | lexer::token::literal::String s) { 44 | return {std::make_shared(s.value), begin, end}; 45 | } 46 | }; 47 | 48 | } // namespace networkprotocoldsl::parser::grammar 49 | 50 | #endif -------------------------------------------------------------------------------- /tests/016-grammar-literals.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace networkprotocoldsl; 9 | 10 | TEST(LiteralsTest, BooleanLiteralMatchTrue) { 11 | std::vector tokens = {lexer::token::literal::Boolean(true)}; 12 | auto result = 13 | parser::grammar::BooleanLiteral::parse(tokens.cbegin(), tokens.cend()); 14 | ASSERT_TRUE(result.node.has_value()); 15 | ASSERT_EQ(std::get>( 16 | result.node.value()) 17 | ->value, 18 | true); 19 | } 20 | 21 | TEST(LiteralsTest, BooleanLiteralMatchFalse) { 22 | std::vector tokens = {lexer::token::literal::Boolean(false)}; 23 | auto result = 24 | parser::grammar::BooleanLiteral::parse(tokens.cbegin(), tokens.cend()); 25 | ASSERT_TRUE(result.node.has_value()); 26 | ASSERT_EQ(std::get>( 27 | result.node.value()) 28 | ->value, 29 | false); 30 | } 31 | 32 | TEST(LiteralsTest, IntegerLiteralMatch) { 33 | std::vector tokens = {lexer::token::literal::Integer(42)}; 34 | auto result = 35 | parser::grammar::IntegerLiteral::parse(tokens.cbegin(), tokens.cend()); 36 | ASSERT_TRUE(result.node.has_value()); 37 | ASSERT_EQ(std::get>( 38 | result.node.value()) 39 | ->value, 40 | 42); 41 | } 42 | 43 | TEST(LiteralsTest, StringLiteralMatch) { 44 | std::vector tokens = {lexer::token::literal::String("hello")}; 45 | auto result = 46 | parser::grammar::StringLiteral::parse(tokens.cbegin(), tokens.cend()); 47 | ASSERT_TRUE(result.node.has_value()); 48 | ASSERT_EQ(std::get>( 49 | result.node.value()) 50 | ->value, 51 | "hello"); 52 | } -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/writeoctetswithescape.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_OPERATION_WRITEOCTETSWITHESCAPE_H 2 | #define INCLUDED_NETWORKPROTOCOLDSL_OPERATION_WRITEOCTETSWITHESCAPE_H 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace networkprotocoldsl { 15 | 16 | namespace operation { 17 | 18 | // WriteOctetsWithEscape writes octets, replacing escape_char with escape_sequence 19 | // For example, if escape_char="\n" and escape_sequence="\r\n ", 20 | // writing "hello\nworld" produces "hello\r\n world" 21 | class WriteOctetsWithEscape { 22 | const std::string escape_char; 23 | const std::string escape_sequence; 24 | 25 | public: 26 | using Arguments = std::tuple; 27 | WriteOctetsWithEscape(const std::string &_escape_char, 28 | const std::string &_escape_sequence) 29 | : escape_char(_escape_char), escape_sequence(_escape_sequence) {} 30 | 31 | OperationResult operator()(InputOutputOperationContext &ctx, 32 | Arguments a) const; 33 | size_t handle_read(InputOutputOperationContext &ctx, 34 | std::string_view in) const; 35 | 36 | std::string_view get_write_buffer(InputOutputOperationContext &ctx) const; 37 | void handle_eof(InputOutputOperationContext &ctx) const; 38 | 39 | size_t handle_write(InputOutputOperationContext &ctx, size_t s) const; 40 | 41 | bool ready_to_evaluate(InputOutputOperationContext &ctx) const { 42 | return true; // Write operations don't wait for input 43 | } 44 | 45 | std::string stringify() const { 46 | return "WriteOctetsWithEscape{escape_char: \"" + escape_char + 47 | "\", escape_sequence: \"" + escape_sequence + "\"}"; 48 | } 49 | }; 50 | static_assert(InputOutputOperationConcept); 51 | 52 | } // namespace operation 53 | 54 | } // namespace networkprotocoldsl 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/interpretedprogram.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NETWORKPROTOCOLDSL_INTERPRETEDPROGRAM_HPP 2 | #define NETWORKPROTOCOLDSL_INTERPRETEDPROGRAM_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | namespace networkprotocoldsl { 13 | 14 | /*** 15 | * The InterpretedProgram object represents the usage of a single program. 16 | * 17 | * Many instances of an interpreter can be spawned for a 18 | * program. This is where the parsing of the programming language 19 | * will take place, and from which a new instance can be 20 | * instantiated to deal with a specific connection. 21 | */ 22 | class InterpretedProgram { 23 | std::string source_file; 24 | std::shared_ptr optree; 25 | 26 | public: 27 | static std::optional 28 | generate_client(const std::string &sf); 29 | static std::optional 30 | generate_server(const std::string &sf); 31 | 32 | // New interfaces accepting source code contents directly. 33 | static std::optional 34 | generate_client_from_source(const std::string &source); 35 | static std::optional 36 | generate_server_from_source(const std::string &source); 37 | 38 | /** 39 | * Skips the parsing and receives an optree instead. 40 | */ 41 | InterpretedProgram(std::shared_ptr o) 42 | : source_file("-"), optree(o) {} 43 | 44 | /** 45 | * Obtains an instance of an interpreter for this program. 46 | */ 47 | Interpreter get_instance(std::optional arglist = std::nullopt) { 48 | std::shared_ptr rootpad = 49 | std::make_shared(LexicalPad()); 50 | if (arglist.has_value()) { 51 | rootpad->initialize_global("argv", arglist.value()); 52 | } 53 | return Interpreter(optree, rootpad); 54 | }; 55 | }; 56 | 57 | } // namespace networkprotocoldsl 58 | 59 | #endif // NETWORKPROTOCOLDSL_INTERPRETER_HPP 60 | -------------------------------------------------------------------------------- /src/networkprotocoldsl_uv/libuvserverwrapper.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NETWORKPROTOCOLDSL_UV_LIBUVSERVERWRAPPER_HPP 2 | #define NETWORKPROTOCOLDSL_UV_LIBUVSERVERWRAPPER_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | // For brevity, assume networkprotocoldsl::Value is defined. 18 | namespace networkprotocoldsl_uv { 19 | 20 | class LibuvServerWrapper { 21 | public: 22 | // Now receives program, callbacks, and an async work queue reference. 23 | LibuvServerWrapper( 24 | const networkprotocoldsl::InterpretedProgram &program, 25 | const networkprotocoldsl::InterpreterRunner::callback_map &callbacks, 26 | AsyncWorkQueue &async_queue); 27 | ~LibuvServerWrapper(); 28 | 29 | // start now receives the ip and port to bind on and returns the bind result 30 | // future. 31 | std::future start(const std::string &ip, 32 | int port); 33 | 34 | // Stop the server and join threads. 35 | void stop(); 36 | 37 | private: 38 | networkprotocoldsl::InterpreterCollectionManager mgr_; 39 | networkprotocoldsl::InterpreterRunner runner_; 40 | std::unique_ptr uv_server_runner_; 41 | AsyncWorkQueue *async_queue_; // Changed: no longer owned internally. 42 | uv_loop_t *loop_; 43 | 44 | // Save the program for use in start(). 45 | networkprotocoldsl::InterpretedProgram program_; 46 | 47 | std::thread interpreter_thread_; 48 | std::thread callback_thread_; 49 | 50 | // Disable copy. 51 | LibuvServerWrapper(const LibuvServerWrapper &) = delete; 52 | LibuvServerWrapper &operator=(const LibuvServerWrapper &) = delete; 53 | }; 54 | 55 | } // namespace networkprotocoldsl_uv 56 | 57 | #endif // NETWORKPROTOCOLDSL_UV_LIBUVSERVERWRAPPER_HPP 58 | -------------------------------------------------------------------------------- /src/networkprotocoldsl_uv/libuvclientwrapper.cpp: -------------------------------------------------------------------------------- 1 | #include "libuvclientwrapper.hpp" 2 | #include "asyncworkqueue.hpp" 3 | #include "libuvclientrunner.hpp" 4 | #include 5 | #include 6 | #include 7 | 8 | namespace networkprotocoldsl_uv { 9 | 10 | LibuvClientWrapper::LibuvClientWrapper( 11 | const networkprotocoldsl::InterpretedProgram &program, 12 | const networkprotocoldsl::InterpreterRunner::callback_map &callbacks, 13 | AsyncWorkQueue &async_queue) 14 | : runner_{networkprotocoldsl::InterpreterRunner{callbacks, false}}, 15 | async_queue_{&async_queue}, loop_{uv_default_loop()}, program_{program} { 16 | // ...existing initialization... 17 | } 18 | 19 | LibuvClientWrapper::~LibuvClientWrapper() { 20 | // Signal threads to exit before joining. 21 | runner_.exit_when_done.store(true); 22 | auto collection = mgr_.get_collection(); 23 | collection->signals->wake_up_for_output.notify(); 24 | collection->signals->wake_up_for_input.notify(); 25 | collection->signals->wake_up_for_callback.notify(); 26 | collection->signals->wake_up_interpreter.notify(); 27 | 28 | if (interpreter_thread_.joinable()) 29 | interpreter_thread_.join(); 30 | if (callback_thread_.joinable()) 31 | callback_thread_.join(); 32 | } 33 | 34 | std::future 35 | LibuvClientWrapper::start(const std::string &ip, int port) { 36 | uv_client_runner_ = std::make_unique( 37 | mgr_, loop_, ip, port, program_, *async_queue_); 38 | interpreter_thread_ = 39 | std::thread([this]() { runner_.interpreter_loop(mgr_); }); 40 | callback_thread_ = std::thread([this]() { runner_.callback_loop(mgr_); }); 41 | return uv_client_runner_->connection_result.get_future(); 42 | } 43 | 44 | std::future &LibuvClientWrapper::result() { 45 | runner_.exit_when_done.store(true); 46 | mgr_.get_collection()->signals->wake_up_for_output.notify(); 47 | mgr_.get_collection()->signals->wake_up_for_input.notify(); 48 | mgr_.get_collection()->signals->wake_up_for_callback.notify(); 49 | mgr_.get_collection()->signals->wake_up_interpreter.notify(); 50 | return uv_client_runner_->client_result; 51 | } 52 | 53 | } // namespace networkprotocoldsl_uv 54 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/codegen/outputcontext.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_CODEGEN_OUTPUTCONTEXT_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_CODEGEN_OUTPUTCONTEXT_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace networkprotocoldsl::codegen { 8 | 9 | /** 10 | * OutputContext provides utilities for generating C++ code output. 11 | * 12 | * This class encapsulates namespace management, header guard generation, 13 | * and other common output formatting needs for the code generator. 14 | */ 15 | class OutputContext { 16 | public: 17 | /** 18 | * Construct an OutputContext. 19 | * 20 | * @param target_namespace The C++ namespace for generated code 21 | * (e.g., "myapp::smtp" or "protocols::http") 22 | */ 23 | explicit OutputContext(std::string target_namespace); 24 | 25 | /** 26 | * Get the opening namespace declaration. 27 | * @return String like "namespace myapp::smtp {\n" 28 | */ 29 | std::string open_namespace() const; 30 | 31 | /** 32 | * Get the closing namespace declaration. 33 | * @return String like "} // namespace myapp::smtp\n" 34 | */ 35 | std::string close_namespace() const; 36 | 37 | /** 38 | * Generate a header guard for a given filename. 39 | * 40 | * Converts namespace and filename to a valid header guard. 41 | * e.g., "myapp::smtp" + "data_types.hpp" -> "GENERATED_MYAPP_SMTP_DATA_TYPES_HPP" 42 | * 43 | * @param filename The filename (e.g., "data_types.hpp") 44 | * @return The header guard string 45 | */ 46 | std::string header_guard(const std::string &filename) const; 47 | 48 | /** 49 | * Get the target namespace. 50 | */ 51 | const std::string &target_namespace() const { return target_namespace_; } 52 | 53 | /** 54 | * Escape a string for use as a C++ string literal. 55 | * 56 | * Handles special characters like \r, \n, \t, etc. 57 | * 58 | * @param s The string to escape 59 | * @return A quoted C++ string literal (e.g., "\"hello\\r\\n\"") 60 | */ 61 | static std::string escape_string_literal(const std::string &s); 62 | 63 | private: 64 | std::string target_namespace_; 65 | }; 66 | 67 | } // namespace networkprotocoldsl::codegen 68 | 69 | #endif // INCLUDED_NETWORKPROTOCOLDSL_CODEGEN_OUTPUTCONTEXT_HPP 70 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/codegen/generate_runner.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_CODEGEN_GENERATE_RUNNER_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_CODEGEN_GENERATE_RUNNER_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | namespace networkprotocoldsl::codegen { 11 | 12 | /** 13 | * Result of generating the runner component. 14 | * 15 | * The runner provides a callback-based interface using C++20 concepts, 16 | * wrapping the lower-level state machine with a higher-level API. 17 | * 18 | * Generated types: 19 | * - ServerHandlerConcept - Concept defining required handler methods 20 | * - ClientHandlerConcept - Concept defining required handler methods 21 | * - ServerRunner - Template runner for server side 22 | * - ClientRunner - Template runner for client side 23 | * 24 | * Handlers implement on_() methods with overloads for each 25 | * message type that can arrive at that state. The runner uses std::visit 26 | * to dispatch to the correct overload based on the parsed message type. 27 | */ 28 | struct RunnerResult { 29 | std::string header; 30 | std::string source; 31 | std::vector errors; 32 | }; 33 | 34 | /** 35 | * Generate the runner component. 36 | * 37 | * This generates a concept-based wrapper around the state machine. 38 | * Handlers must satisfy the generated concept by implementing 39 | * on_() methods for each state where messages are received. 40 | * 41 | * Example usage: 42 | * struct MyHandler { 43 | * StateOutput on_StateName(const MessageData& msg) const; 44 | * }; 45 | * ServerRunner runner(handler); 46 | * runner.on_bytes_received(data); 47 | * // Handler methods are invoked when messages are parsed 48 | * 49 | * @param ctx The output context (namespace, etc.) 50 | * @param info The protocol information 51 | * @return The generated header and source code 52 | */ 53 | RunnerResult generate_runner(const OutputContext &ctx, 54 | const ProtocolInfo &info); 55 | 56 | } // namespace networkprotocoldsl::codegen 57 | 58 | #endif // INCLUDED_NETWORKPROTOCOLDSL_CODEGEN_GENERATE_RUNNER_HPP 59 | -------------------------------------------------------------------------------- /tests/codegen_integration/test_partial.cpp: -------------------------------------------------------------------------------- 1 | #include "protocol.hpp" 2 | #include 3 | 4 | int main() { 5 | smtp::generated::ServerStateMachine server; 6 | 7 | // Send greeting to set up state 8 | smtp::generated::SMTPServerGreetingData greeting; 9 | greeting.code_tens = 50; 10 | greeting.msg = "Ready"; 11 | server.send_SMTPServerGreeting(greeting); 12 | server.bytes_written(server.pending_output().size()); 13 | 14 | std::cout << "Initial state set" << std::endl; 15 | 16 | // Feed partial EHLO command in chunks 17 | std::string full_cmd = "EHLO test.domain.com\r\n"; 18 | 19 | // Feed first part: "EHLO " 20 | std::string part1 = full_cmd.substr(0, 5); 21 | size_t consumed1 = server.on_bytes_received(part1); 22 | std::cout << "Part1 consumed: " << consumed1 << ", has_message: " 23 | << (server.has_message() ? "yes" : "no") << std::endl; 24 | 25 | // Sans-IO property: should return without blocking even with incomplete data 26 | // (consumed might be 0 or partial, has_message should be false) 27 | 28 | // Feed second part: "test.domain" 29 | std::string part2 = full_cmd.substr(5, 11); 30 | size_t consumed2 = server.on_bytes_received(part2); 31 | std::cout << "Part2 consumed: " << consumed2 << ", has_message: " 32 | << (server.has_message() ? "yes" : "no") << std::endl; 33 | 34 | // Still incomplete - no terminator yet 35 | 36 | // Feed final part with terminator: ".com\r\n" 37 | std::string part3 = full_cmd.substr(16); 38 | size_t consumed3 = server.on_bytes_received(part3); 39 | std::cout << "Part3 consumed: " << consumed3 << ", has_message: " 40 | << (server.has_message() ? "yes" : "no") << std::endl; 41 | 42 | // Now the message should be complete 43 | if (server.has_message()) { 44 | auto msg = server.take_AwaitServerEHLOResponse_message(); 45 | if (auto* data = std::get_if(&msg)) { 46 | std::cout << "PARSED_DOMAIN:" << data->client_domain << std::endl; 47 | } 48 | std::cout << "SANS_IO_SUCCESS" << std::endl; 49 | } else { 50 | std::cout << "SANS_IO_FAILED" << std::endl; 51 | } 52 | 53 | return 0; 54 | } 55 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/parser/grammar/identifier.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_PARSER_GRAMMAR_IDENTIFIER_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_PARSER_GRAMMAR_IDENTIFIER_HPP 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace networkprotocoldsl::parser::grammar { 12 | 13 | class IdentifierReference; // Forward declaration 14 | 15 | class IdentifierMemberAccess 16 | : public support::RecursiveParser> { 18 | public: 19 | static constexpr const char *name = "IdentiferMemberAccess"; 20 | static void partial_match() {} 21 | static IdentifierReference *recurse_one(lexer::token::punctuation::Dot) { 22 | return nullptr; 23 | } 24 | static ParseStateReturn 25 | match(TokenIterator begin, TokenIterator end, lexer::token::punctuation::Dot, 26 | std::shared_ptr member) { 27 | return {member, begin, end}; 28 | } 29 | }; 30 | 31 | class IdentifierReference 32 | : public support::RecursiveParser> { 34 | public: 35 | static constexpr const char *name = "IdentiferReference"; 36 | static void partial_match() {} 37 | static IdentifierMemberAccess *recurse_maybe(lexer::token::Identifier) { 38 | return nullptr; 39 | } 40 | static ParseStateReturn match(TokenIterator begin, TokenIterator end, 41 | lexer::token::Identifier i, std::nullopt_t) { 42 | return {std::make_shared(i.name), begin, 43 | end}; 44 | } 45 | static ParseStateReturn 46 | match(TokenIterator begin, TokenIterator end, lexer::token::Identifier i, 47 | std::shared_ptr member) { 48 | return {std::make_shared(i.name, member), 49 | begin, end}; 50 | } 51 | }; 52 | 53 | } // namespace networkprotocoldsl::parser::grammar 54 | 55 | #endif -------------------------------------------------------------------------------- /src/networkprotocoldsl_uv/libuvserverwrapper.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | namespace networkprotocoldsl_uv { 9 | 10 | LibuvServerWrapper::LibuvServerWrapper( 11 | const networkprotocoldsl::InterpretedProgram &program, 12 | const networkprotocoldsl::InterpreterRunner::callback_map &callbacks, 13 | AsyncWorkQueue &async_queue) 14 | : runner_{networkprotocoldsl::InterpreterRunner{callbacks, false}}, 15 | loop_{uv_default_loop()}, program_{program}, async_queue_{&async_queue} 16 | // use the passed async queue. 17 | { 18 | // Note: No creation of async work queue here. 19 | } 20 | 21 | LibuvServerWrapper::~LibuvServerWrapper() { 22 | stop(); 23 | // ...existing cleanup... 24 | } 25 | 26 | std::future 27 | LibuvServerWrapper::start(const std::string &ip, int port) { 28 | // Create the libuv server runner (bind happens asynchronously). 29 | uv_server_runner_ = std::make_unique( 30 | mgr_, loop_, ip, port, program_, *async_queue_); 31 | // Launch interpreter loop thread. 32 | interpreter_thread_ = 33 | std::thread([this]() { runner_.interpreter_loop(mgr_); }); 34 | // Launch callback loop thread. 35 | callback_thread_ = std::thread([this]() { runner_.callback_loop(mgr_); }); 36 | // Return the future associated with the bind result. 37 | return uv_server_runner_->bind_result.get_future(); 38 | } 39 | 40 | void LibuvServerWrapper::stop() { 41 | if (uv_server_runner_) { 42 | uv_server_runner_->stop_accepting(); 43 | } 44 | // Signal the interpreter to exit. 45 | runner_.exit_when_done.store(true); 46 | mgr_.get_collection()->signals->wake_up_for_output.notify(); 47 | mgr_.get_collection()->signals->wake_up_for_input.notify(); 48 | mgr_.get_collection()->signals->wake_up_for_callback.notify(); 49 | mgr_.get_collection()->signals->wake_up_interpreter.notify(); 50 | // Join threads. 51 | if (interpreter_thread_.joinable()) 52 | interpreter_thread_.join(); 53 | if (callback_thread_.joinable()) 54 | callback_thread_.join(); 55 | uv_server_runner_->server_stopped.wait(); 56 | } 57 | 58 | } // namespace networkprotocoldsl_uv 59 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/readintfromascii.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace networkprotocoldsl::operation { 14 | 15 | OperationResult ReadIntFromAscii::operator()(InputOutputOperationContext &ctx, 16 | Arguments a) const { 17 | if (ctx.ready) { 18 | if (ctx.buffer == "0") { 19 | return 0; 20 | } else { 21 | long converted = strtol(ctx.buffer.c_str(), NULL, 10); 22 | if (converted == 0) { 23 | return value::RuntimeError::ProtocolMismatchError; 24 | } else { 25 | if (errno == ERANGE || converted > INT_MAX || converted < INT_MIN) { 26 | return value::RuntimeError::ProtocolMismatchError; 27 | } else { 28 | return (int32_t)converted; 29 | } 30 | } 31 | } 32 | } else if (ctx.eof) { 33 | return value::RuntimeError::ProtocolMismatchError; 34 | } else { 35 | return ReasonForBlockedOperation::WaitingForRead; 36 | } 37 | } 38 | 39 | size_t ReadIntFromAscii::handle_read(InputOutputOperationContext &ctx, 40 | std::string_view in) const { 41 | size_t consumed = 0; 42 | auto it = in.begin(); 43 | for (; it != in.end() && *it >= '0' && *it < '9'; it++) { 44 | consumed++; 45 | } 46 | if (it != in.end()) { 47 | ctx.buffer = std::string(in.begin(), consumed); 48 | ctx.ready = true; 49 | return consumed; 50 | } else { 51 | return 0; 52 | } 53 | } 54 | 55 | void ReadIntFromAscii::handle_eof(InputOutputOperationContext &ctx) const { 56 | ctx.eof = true; 57 | } 58 | 59 | std::string_view 60 | ReadIntFromAscii::get_write_buffer(InputOutputOperationContext &ctx) const { 61 | return ctx.buffer; 62 | } 63 | 64 | size_t ReadIntFromAscii::handle_write(InputOutputOperationContext &ctx, 65 | size_t s) const { 66 | return 0; 67 | } 68 | 69 | bool ReadIntFromAscii::ready_to_evaluate( 70 | InputOutputOperationContext &ctx) const { 71 | // Ready when we've found a non-digit (ctx.ready) or at EOF 72 | return ctx.ready || ctx.eof; 73 | } 74 | 75 | } // namespace networkprotocoldsl::operation 76 | -------------------------------------------------------------------------------- /tests/codegen_integration/test_roundtrip.cpp: -------------------------------------------------------------------------------- 1 | #include "protocol.hpp" 2 | #include 3 | 4 | int main() { 5 | smtp::generated::ServerStateMachine server; 6 | smtp::generated::ClientStateMachine client; 7 | 8 | // === Server sends greeting === 9 | smtp::generated::SMTPServerGreetingData greeting; 10 | greeting.code_tens = 50; 11 | greeting.msg = "Welcome to test server"; 12 | server.send_SMTPServerGreeting(greeting); 13 | 14 | std::string greeting_bytes(server.pending_output()); 15 | server.bytes_written(greeting_bytes.size()); 16 | 17 | std::cout << "SERVER_SENT:" << greeting_bytes.size() << " bytes" << std::endl; 18 | 19 | // === Client receives greeting === 20 | size_t consumed = client.on_bytes_received(greeting_bytes); 21 | std::cout << "CLIENT_CONSUMED:" << consumed << std::endl; 22 | std::cout << "CLIENT_HAS_MESSAGE:" << (client.has_message() ? "yes" : "no") << std::endl; 23 | 24 | if (client.has_message()) { 25 | auto msg = client.take_ClientSendEHLO_message(); 26 | if (auto* data = std::get_if(&msg)) { 27 | std::cout << "GREETING_CODE:" << static_cast(data->code_tens) << std::endl; 28 | std::cout << "GREETING_MSG:" << data->msg << std::endl; 29 | } 30 | } 31 | 32 | // === Client sends EHLO === 33 | smtp::generated::SMTPEHLOCommandData ehlo; 34 | ehlo.client_domain = "test.example.org"; 35 | client.send_SMTPEHLOCommand(ehlo); 36 | 37 | std::string ehlo_bytes(client.pending_output()); 38 | client.bytes_written(ehlo_bytes.size()); 39 | 40 | std::cout << "CLIENT_SENT:" << ehlo_bytes.size() << " bytes" << std::endl; 41 | std::cout << "EHLO_WIRE:" << ehlo_bytes; 42 | 43 | // === Server receives EHLO === 44 | consumed = server.on_bytes_received(ehlo_bytes); 45 | std::cout << "SERVER_CONSUMED:" << consumed << std::endl; 46 | std::cout << "SERVER_HAS_MESSAGE:" << (server.has_message() ? "yes" : "no") << std::endl; 47 | 48 | if (server.has_message()) { 49 | auto msg = server.take_AwaitServerEHLOResponse_message(); 50 | if (auto* data = std::get_if(&msg)) { 51 | std::cout << "EHLO_DOMAIN:" << data->client_domain << std::endl; 52 | } 53 | } 54 | 55 | std::cout << "SUCCESS" << std::endl; 56 | return 0; 57 | } 58 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/readoctetsuntilterminator.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NETWORKPROTOCOLDSL_OPERATION_READOCTETSUNTILTERMINATOR_HPP 2 | #define NETWORKPROTOCOLDSL_OPERATION_READOCTETSUNTILTERMINATOR_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace networkprotocoldsl { 15 | 16 | namespace operation { 17 | 18 | class ReadOctetsUntilTerminator { 19 | const std::string terminator; 20 | // Optional escape replacement: when escape_sequence is found in input, 21 | // it is replaced with escape_char in the captured value 22 | const std::optional escape_char; 23 | const std::optional escape_sequence; 24 | 25 | public: 26 | using Arguments = std::tuple<>; 27 | ReadOctetsUntilTerminator(const std::string &_t) : terminator(_t) {} 28 | ReadOctetsUntilTerminator(const std::string &_t, 29 | const std::string &_escape_char, 30 | const std::string &_escape_sequence) 31 | : terminator(_t), escape_char(_escape_char), 32 | escape_sequence(_escape_sequence) {} 33 | 34 | OperationResult operator()(InputOutputOperationContext &ctx, 35 | Arguments a) const; 36 | size_t handle_read(InputOutputOperationContext &ctx, 37 | std::string_view in) const; 38 | 39 | std::string_view get_write_buffer(InputOutputOperationContext &ctx) const; 40 | void handle_eof(InputOutputOperationContext &ctx) const; 41 | 42 | size_t handle_write(InputOutputOperationContext &ctx, size_t s) const; 43 | 44 | // Returns true if the operation has enough data to produce a result. 45 | bool ready_to_evaluate(InputOutputOperationContext &ctx) const; 46 | 47 | std::string stringify() const { 48 | std::string result = "ReadOctetsUntilTerminator{terminator: \"" + terminator + "\""; 49 | if (escape_char.has_value() && escape_sequence.has_value()) { 50 | result += ", escape_char: \"" + *escape_char + "\", escape_sequence: \"" + *escape_sequence + "\""; 51 | } 52 | result += "}"; 53 | return result; 54 | } 55 | }; 56 | static_assert(InputOutputOperationConcept); 57 | 58 | } // namespace operation 59 | 60 | } // namespace networkprotocoldsl 61 | 62 | #endif // NETWORKPROTOCOLDSL_OPERATION_HPP 63 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/writeoctets.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | namespace networkprotocoldsl::operation { 8 | 9 | static OperationResult _execute_operation(InputOutputOperationContext &ctx, 10 | value::Octets &oct) { 11 | if (oct.data->length() == 0) { 12 | return 0; 13 | } else if (ctx.buffer.length() == 0) { 14 | ctx.buffer = *(oct.data); 15 | ctx.it = ctx.buffer.begin(); 16 | } 17 | if (ctx.it != ctx.buffer.end()) { 18 | if (ctx.eof) { 19 | return value::RuntimeError::ProtocolMismatchError; 20 | } else { 21 | return ReasonForBlockedOperation::WaitingForWrite; 22 | } 23 | } else { 24 | return 0; 25 | } 26 | } 27 | 28 | static OperationResult _execute_operation(InputOutputOperationContext &ctx, 29 | value::RuntimeError v) { 30 | return v; 31 | } 32 | 33 | static OperationResult _execute_operation(InputOutputOperationContext &ctx, 34 | value::ControlFlowInstruction v) { 35 | return v; 36 | } 37 | 38 | static OperationResult _execute_operation(InputOutputOperationContext &ctx, 39 | auto unknown) { 40 | return value::RuntimeError::TypeError; 41 | } 42 | 43 | OperationResult WriteOctets::operator()(InputOutputOperationContext &ctx, 44 | Arguments a) const { 45 | return std::visit([&ctx](auto arg) { return _execute_operation(ctx, arg); }, 46 | std::get<0>(a)); 47 | } 48 | 49 | void WriteOctets::handle_eof(InputOutputOperationContext &ctx) const { 50 | ctx.eof = true; 51 | } 52 | 53 | size_t WriteOctets::handle_read(InputOutputOperationContext &ctx, 54 | std::string_view in) const { 55 | return 0; 56 | } 57 | 58 | std::string_view 59 | WriteOctets::get_write_buffer(InputOutputOperationContext &ctx) const { 60 | return std::string_view(ctx.it, ctx.buffer.end()); 61 | } 62 | 63 | size_t WriteOctets::handle_write(InputOutputOperationContext &ctx, 64 | size_t s) const { 65 | size_t consumed = 0; 66 | while (s > 0 && ctx.it != ctx.buffer.end()) { 67 | s--; 68 | consumed++; 69 | ctx.it++; 70 | } 71 | return consumed; 72 | } 73 | 74 | } // namespace networkprotocoldsl::operation 75 | -------------------------------------------------------------------------------- /examples/smtpserver_generated/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # CMakeLists.txt for generated SMTP server example 2 | # 3 | # This example uses the protocol_generator to generate C++ code from the 4 | # SMTP protocol definition, then builds an SMTP server using the generated 5 | # code with type-safe callbacks. 6 | 7 | # Path to the protocol definition file 8 | set(SMTP_SOURCE_FILE ${CMAKE_CURRENT_SOURCE_DIR}/../smtpserver/smtp.networkprotocoldsl) 9 | 10 | # Output directory for generated files 11 | set(GENERATED_DIR ${CMAKE_CURRENT_BINARY_DIR}/generated) 12 | 13 | # List of generated source files 14 | set(GENERATED_SOURCES 15 | ${GENERATED_DIR}/data_types.cpp 16 | ${GENERATED_DIR}/states.cpp 17 | ${GENERATED_DIR}/parser.cpp 18 | ${GENERATED_DIR}/serializer.cpp 19 | ${GENERATED_DIR}/state_machine.cpp 20 | ${GENERATED_DIR}/runner.cpp 21 | ) 22 | 23 | set(GENERATED_HEADERS 24 | ${GENERATED_DIR}/data_types.hpp 25 | ${GENERATED_DIR}/states.hpp 26 | ${GENERATED_DIR}/parser.hpp 27 | ${GENERATED_DIR}/serializer.hpp 28 | ${GENERATED_DIR}/state_machine.hpp 29 | ${GENERATED_DIR}/runner.hpp 30 | ${GENERATED_DIR}/protocol.hpp 31 | ) 32 | 33 | # Create directory for generated files 34 | file(MAKE_DIRECTORY ${GENERATED_DIR}) 35 | 36 | # Custom command to generate the protocol code 37 | add_custom_command( 38 | OUTPUT ${GENERATED_SOURCES} ${GENERATED_HEADERS} 39 | COMMAND protocol_generator 40 | ${SMTP_SOURCE_FILE} 41 | --output ${GENERATED_DIR} 42 | --namespace "smtp::generated" 43 | --library "smtp_protocol" 44 | DEPENDS ${SMTP_SOURCE_FILE} protocol_generator 45 | COMMENT "Generating SMTP protocol code from ${SMTP_SOURCE_FILE}" 46 | ) 47 | 48 | # Create a library from the generated code 49 | add_library(smtp_protocol_generated STATIC ${GENERATED_SOURCES}) 50 | target_include_directories(smtp_protocol_generated PUBLIC ${GENERATED_DIR}) 51 | target_compile_features(smtp_protocol_generated PUBLIC cxx_std_20) 52 | 53 | # Find CLI11 for command-line parsing 54 | find_package(CLI11 REQUIRED) 55 | 56 | # Build the smtpserver_generated executable 57 | add_executable(smtpserver_generated 58 | main.cpp 59 | smtp_handler.cpp 60 | smtp_handler.hpp 61 | ) 62 | 63 | target_link_libraries(smtpserver_generated PRIVATE 64 | smtp_protocol_generated 65 | networkprotocoldsl_uv 66 | CLI11::CLI11 67 | ) 68 | target_include_directories(smtpserver_generated PRIVATE ${GENERATED_DIR}) 69 | target_compile_features(smtpserver_generated PRIVATE cxx_std_20) 70 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/functioncall.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace networkprotocoldsl::operation { 10 | 11 | static OperationResult _function_call(ControlFlowOperationContext &ctx, 12 | value::Callable callable, 13 | value::DynamicList arglist) { 14 | ctx.callable = callable; 15 | ctx.arglist = arglist.values; 16 | return ReasonForBlockedOperation::WaitingForCallableInvocation; 17 | } 18 | 19 | static OperationResult _function_call(ControlFlowOperationContext &ctx, 20 | value::Callable a, 21 | value::RuntimeError b) { 22 | return b; 23 | } 24 | 25 | static OperationResult _function_call(ControlFlowOperationContext &ctx, auto &, 26 | auto &) { 27 | return value::RuntimeError::TypeError; 28 | } 29 | 30 | OperationResult FunctionCall::operator()(ControlFlowOperationContext &ctx, 31 | Arguments a) const { 32 | if (ctx.callable.has_value()) { 33 | if (ctx.callable_invoked) { 34 | if (ctx.value.has_value()) { 35 | return ctx.value.value(); 36 | } else { 37 | return ReasonForBlockedOperation::WaitingForCallableResult; 38 | } 39 | } else { 40 | return ReasonForBlockedOperation::WaitingForCallableInvocation; 41 | } 42 | } else { 43 | return std::visit( 44 | [&ctx](auto &callable, auto &arglist) { 45 | return _function_call(ctx, callable, arglist); 46 | }, 47 | std::get<0>(a), std::get<1>(a)); 48 | } 49 | } 50 | 51 | Value FunctionCall::get_callable(ControlFlowOperationContext &ctx) const { 52 | assert(ctx.callable.has_value()); 53 | return ctx.callable.value(); 54 | } 55 | void FunctionCall::set_callable_invoked( 56 | ControlFlowOperationContext &ctx) const { 57 | ctx.callable_invoked = true; 58 | } 59 | void FunctionCall::set_callable_return(ControlFlowOperationContext &ctx, 60 | Value v) const { 61 | ctx.value = v; 62 | } 63 | 64 | std::shared_ptr> 65 | FunctionCall::get_argument_list(ControlFlowOperationContext &ctx) const { 66 | return ctx.arglist; 67 | } 68 | 69 | } // namespace networkprotocoldsl::operation 70 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/operation/transitionlookahead.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NETWORKPROTOCOLDSL_OPERATION_TRANSITIONLOOKAHEAD_HPP 2 | #define NETWORKPROTOCOLDSL_OPERATION_TRANSITIONLOOKAHEAD_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace networkprotocoldsl { 14 | namespace operation { 15 | 16 | struct TransitionLookahead { 17 | struct EOFCondition {}; 18 | 19 | struct MatchUntilTerminator { 20 | std::string terminator; 21 | }; 22 | 23 | using TransitionCondition = 24 | std::variant; 25 | 26 | std::vector> 27 | conditions; // pair of condition and target state 28 | 29 | using Arguments = std::tuple<>; 30 | 31 | OperationResult operator()(InputOutputOperationContext &ctx, 32 | const Arguments &) const; 33 | std::size_t handle_read(InputOutputOperationContext &ctx, 34 | std::string_view sv) const; 35 | std::string_view get_write_buffer(InputOutputOperationContext &ctx) const; 36 | std::size_t handle_write(InputOutputOperationContext &ctx, 37 | std::size_t s) const; 38 | void handle_eof(InputOutputOperationContext &ctx) const; 39 | 40 | // Returns true if the operation has enough data to produce a result. 41 | // This lookahead is ready when any condition can be definitively matched or rejected. 42 | bool ready_to_evaluate(InputOutputOperationContext &ctx) const; 43 | 44 | std::string stringify() const; 45 | 46 | private: 47 | static std::pair match_condition(InputOutputOperationContext &ctx, 48 | const EOFCondition &); 49 | static std::pair match_condition(InputOutputOperationContext &ctx, 50 | const MatchUntilTerminator &c); 51 | static std::pair match_condition(InputOutputOperationContext &ctx, 52 | const std::string &c); 53 | static std::string condition_to_string(const EOFCondition &); 54 | static std::string condition_to_string(const MatchUntilTerminator &c); 55 | static std::string condition_to_string(const std::string &c); 56 | }; 57 | 58 | } // namespace operation 59 | } // namespace networkprotocoldsl 60 | 61 | #endif // NETWORKPROTOCOLDSL_OPERATION_TRANSITIONLOOKAHEAD_HPP 62 | -------------------------------------------------------------------------------- /tests/013-parse-and-write-http-message.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "testlibs/http_message_optrees.hpp" 15 | 16 | TEST(parse_and_write_http_message, complete) { 17 | using namespace networkprotocoldsl; 18 | operation::FunctionCall function_call; 19 | operation::DynamicList dynamic_list; 20 | auto main_optree = std::make_shared( 21 | OpTree({{function_call, 22 | {{testlibs::get_write_request_callable(), {}}, 23 | {function_call, 24 | {{testlibs::get_read_request_callable(), {}}, 25 | {dynamic_list, {}}}}}}})); 26 | InterpretedProgram p(main_optree); 27 | Interpreter i = p.get_instance(); 28 | 29 | std::string input = "GET /foo/bar/baz HTTP/1.1\r\n" 30 | "Accept: application/json\r\n" 31 | "Host: Test Value\r\n" 32 | "\r\n"; 33 | auto input_cursor = input.cbegin(); 34 | std::stringstream output_message_stream(std::ios_base::out); 35 | 36 | while (true) { 37 | ContinuationState s = i.step(); 38 | if (s == ContinuationState::Blocked) { 39 | auto reason = std::get(i.get_result()); 40 | if (reason == ReasonForBlockedOperation::WaitingForWrite) { 41 | auto buffer = i.get_write_buffer(); 42 | output_message_stream.write(buffer.cbegin(), buffer.size()); 43 | i.handle_write(buffer.size()); 44 | } else if (reason == ReasonForBlockedOperation::WaitingForRead) { 45 | ASSERT_NE(input_cursor, input.cend()); 46 | size_t movement = 47 | i.handle_read(std::string_view{input_cursor, input.cend()}); 48 | int buffer_left = std::distance(input_cursor, input.cend()); 49 | ASSERT_GT(buffer_left, 0); 50 | if (movement <= static_cast(buffer_left)) { 51 | std::advance(input_cursor, movement); 52 | } else { 53 | input_cursor = input.cend(); 54 | } 55 | } 56 | } else if (s == ContinuationState::Exited) { 57 | break; 58 | } 59 | } 60 | 61 | ASSERT_EQ(input, output_message_stream.str()); 62 | } 63 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/codegen/typemapping.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_NETWORKPROTOCOLDSL_CODEGEN_TYPEMAPPING_HPP 2 | #define INCLUDED_NETWORKPROTOCOLDSL_CODEGEN_TYPEMAPPING_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | namespace networkprotocoldsl::codegen { 11 | 12 | /** 13 | * Maps DSL types to their C++ equivalents. 14 | * 15 | * Type mapping rules: 16 | * int -> uint8_t 17 | * int -> uint16_t 18 | * int -> uint32_t 19 | * int -> int8_t 20 | * int -> int16_t 21 | * int -> int32_t 22 | * str<...> -> std::string 23 | * array -> std::vector 24 | * tuple -> generated struct 25 | */ 26 | 27 | /** 28 | * Result of type mapping - contains both the C++ type string 29 | * and any auxiliary definitions needed (e.g., nested struct definitions). 30 | */ 31 | struct TypeMappingResult { 32 | std::string cpp_type; // The C++ type to use (e.g., "std::string") 33 | std::string auxiliary_definitions; // Any struct definitions needed 34 | bool is_struct = false; // True if this is a generated struct type 35 | }; 36 | 37 | /** 38 | * Convert a DSL type to its C++ equivalent. 39 | * 40 | * @param type The DSL type from the parser tree 41 | * @param struct_name_prefix Prefix for generated struct names (for nested tuples) 42 | * @return The mapping result, or nullopt if the type is not recognized 43 | */ 44 | std::optional 45 | type_to_cpp(const std::shared_ptr &type, 46 | const std::string &struct_name_prefix = ""); 47 | 48 | /** 49 | * Convert a message name to a valid C++ identifier. 50 | * E.g., "SMTP Server Greeting" -> "SMTPServerGreeting" 51 | */ 52 | std::string message_name_to_identifier(const std::string &message_name); 53 | 54 | /** 55 | * Convert a state name to a valid C++ enum identifier. 56 | * E.g., "ClientSendEHLO" -> "ClientSendEHLO" 57 | */ 58 | std::string state_name_to_identifier(const std::string &state_name); 59 | 60 | } // namespace networkprotocoldsl::codegen 61 | 62 | #endif // INCLUDED_NETWORKPROTOCOLDSL_CODEGEN_TYPEMAPPING_HPP 63 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/codegen/outputcontext.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace networkprotocoldsl::codegen { 9 | 10 | OutputContext::OutputContext(std::string target_namespace) 11 | : target_namespace_(std::move(target_namespace)) {} 12 | 13 | std::string OutputContext::open_namespace() const { 14 | std::ostringstream oss; 15 | oss << "namespace " << target_namespace_ << " {\n"; 16 | return oss.str(); 17 | } 18 | 19 | std::string OutputContext::close_namespace() const { 20 | std::ostringstream oss; 21 | oss << "} // namespace " << target_namespace_ << "\n"; 22 | return oss.str(); 23 | } 24 | 25 | std::string OutputContext::header_guard(const std::string &filename) const { 26 | // Convert namespace and filename to a valid header guard 27 | // e.g., "myapp::smtp" + "data_types.hpp" -> "MYAPP_SMTP_DATA_TYPES_HPP" 28 | std::string guard = target_namespace_ + "_" + filename; 29 | std::transform(guard.begin(), guard.end(), guard.begin(), [](char c) { 30 | if (c == ':' || c == '/' || c == '\\' || c == '.') { 31 | return '_'; 32 | } 33 | return static_cast(std::toupper(static_cast(c))); 34 | }); 35 | 36 | // Remove consecutive underscores 37 | std::string result; 38 | result.reserve(guard.size()); 39 | bool last_was_underscore = false; 40 | for (char c : guard) { 41 | if (c == '_') { 42 | if (!last_was_underscore) { 43 | result += c; 44 | last_was_underscore = true; 45 | } 46 | } else { 47 | result += c; 48 | last_was_underscore = false; 49 | } 50 | } 51 | return "GENERATED_" + result; 52 | } 53 | 54 | std::string OutputContext::escape_string_literal(const std::string &s) { 55 | std::ostringstream oss; 56 | oss << '"'; 57 | for (char c : s) { 58 | switch (c) { 59 | case '\r': 60 | oss << "\\r"; 61 | break; 62 | case '\n': 63 | oss << "\\n"; 64 | break; 65 | case '\t': 66 | oss << "\\t"; 67 | break; 68 | case '\\': 69 | oss << "\\\\"; 70 | break; 71 | case '"': 72 | oss << "\\\""; 73 | break; 74 | default: 75 | if (std::isprint(static_cast(c))) { 76 | oss << c; 77 | } else { 78 | // Output as hex escape 79 | oss << "\\x" << std::hex << std::setfill('0') << std::setw(2) 80 | << static_cast(static_cast(c)); 81 | } 82 | break; 83 | } 84 | } 85 | oss << '"'; 86 | return oss.str(); 87 | } 88 | 89 | } // namespace networkprotocoldsl::codegen 90 | -------------------------------------------------------------------------------- /src/networkprotocoldsl/interpretercollectionmanager.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace networkprotocoldsl { 9 | 10 | const std::shared_ptr 11 | InterpreterCollectionManager::get_collection() { 12 | return _collection.current(); 13 | } 14 | 15 | std::future InterpreterCollectionManager::insert_interpreter( 16 | int fd, InterpretedProgram program, std::optional arglist, 17 | void *additional_data) { 18 | std::shared_ptr ctx = 19 | std::make_shared(program.get_instance(arglist)); 20 | ctx->additional_data = additional_data; 21 | _collection.do_transaction( 22 | [&fd, &ctx](std::shared_ptr current) 23 | -> std::shared_ptr { 24 | auto new_interpreters = current->interpreters; 25 | auto old_interpreter_it = new_interpreters.find(fd); 26 | if (old_interpreter_it != new_interpreters.end()) { 27 | if (old_interpreter_it->second->exited.load()) { 28 | new_interpreters.erase(fd); 29 | } else { 30 | throw std::runtime_error("Interpreter already exists for fd"); 31 | } 32 | } 33 | new_interpreters.insert({fd, ctx}); 34 | return std::make_shared( 35 | std::move(new_interpreters), current->signals); 36 | }); 37 | auto signals = _collection.current()->signals; 38 | signals->wake_up_interpreter.notify(); 39 | signals->wake_up_for_output.notify(); 40 | signals->wake_up_for_input.notify(); 41 | signals->wake_up_for_callback.notify(); 42 | 43 | return ctx->interpreter_result.get_future(); 44 | } 45 | 46 | void InterpreterCollectionManager::remove_interpreter(int fd) { 47 | _collection.do_transaction( 48 | [fd](std::shared_ptr current) 49 | -> const std::shared_ptr { 50 | auto new_interpreters = current->interpreters; 51 | new_interpreters.erase(fd); 52 | return std::make_shared( 53 | std::move(new_interpreters), current->signals); 54 | }); 55 | auto signals = _collection.current()->signals; 56 | signals->wake_up_interpreter.notify(); 57 | signals->wake_up_for_output.notify(); 58 | signals->wake_up_for_input.notify(); 59 | signals->wake_up_for_callback.notify(); 60 | } 61 | } // namespace networkprotocoldsl --------------------------------------------------------------------------------