diff --git a/Doxyfile b/Doxyfile index c839c2a..3ab9535 100644 --- a/Doxyfile +++ b/Doxyfile @@ -952,7 +952,7 @@ RECURSIVE = YES # Note that relative paths are relative to the directory from which doxygen is # run. -EXCLUDE = tests +EXCLUDE = tests src/main.cc # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded diff --git a/src/main.cc b/src/main.cc index eee4bb9..3f13698 100644 --- a/src/main.cc +++ b/src/main.cc @@ -17,6 +17,7 @@ static bool enable_repl = false; #define Ignore(Call) do { auto const ignore_ ## __LINE__ = (Call); (void) ignore_ ## __LINE__; } while(0) +/// Pop string from front of an array static std::string_view pop(std::span &span) { auto element = span.front(); @@ -24,8 +25,8 @@ static std::string_view pop(std::span &span) return element; } -[[noreturn]] -void usage() +/// Print usage and exit +[[noreturn]] void usage() { std::cerr << "usage: musique [filename]\n" @@ -42,6 +43,7 @@ void usage() std::exit(1); } +/// Trim spaces from left an right static void trim(std::string_view &s) { // left trim @@ -61,11 +63,13 @@ static void trim(std::string_view &s) } } +/// Runs interpreter on given source code struct Runner { midi::ALSA alsa; Interpreter interpreter; + /// Setup interpreter and midi connection with given port Runner(std::string port) : alsa("musique") { @@ -84,6 +88,7 @@ struct Runner }); } + /// Run given source Result run(std::string_view source, std::string_view filename) { auto ast = Try(Parser::parse(source, filename)); @@ -99,17 +104,20 @@ struct Runner } }; -// We make sure that through life of interpreter source code is allways allocated +/// All source code through life of the program should stay allocated, since +/// some of the strings are only views into source std::vector eternal_sources; -struct Run -{ - bool is_file = true; - std::string_view argument; -}; - +/// Fancy main that supports Result forwarding on error (Try macro) static Result Main(std::span args) { + /// Describes all arguments that will be run + struct Run + { + bool is_file = true; + std::string_view argument; + }; + std::vector runnables; while (not args.empty()) { diff --git a/src/musique.hh b/src/musique.hh index 2f72fab..c16f817 100644 --- a/src/musique.hh +++ b/src/musique.hh @@ -39,8 +39,10 @@ using i64 = std::int64_t; using usize = std::size_t; using isize = std::ptrdiff_t; -// Error handling mechanism inspired by Andrew Kelly approach, that was implemented -// as first class feature in Zig programming language. +/// Error handling related functions and definitions +/// +/// Error handling mechanism inspired by Andrew Kelly approach, that was implemented +/// as first class feature in Zig programming language. namespace errors { enum Type @@ -100,12 +102,13 @@ struct Location std::ostream& operator<<(std::ostream& os, Location const& location); +/// Guards that program exits if condition does not hold void assert(bool condition, std::string message, Location loc = Location::caller()); -// Marks part of code that was not implemented yet +/// Marks part of code that was not implemented yet [[noreturn]] void unimplemented(std::string_view message = {}, Location loc = Location::caller()); -// Marks location that should not be reached +/// Marks location that should not be reached [[noreturn]] void unreachable(Location loc = Location::caller()); struct Error @@ -189,10 +192,11 @@ struct [[nodiscard("This value may contain critical error, so it should NOT be i using Storage::and_then; }; -// NOTE This implementation requires C++ language extension: statement expressions -// It's supported by GCC and Clang, other compilers i don't know -// -// Inspired by SerenityOS TRY macro +/// Shorthand for forwarding error values with Result type family. +/// +/// This implementation requires C++ language extension: statement expressions +/// It's supported by GCC and Clang, other compilers i don't know. +/// Inspired by SerenityOS TRY macro #define Try(Value) \ ({ \ auto try_value = (Value); \ @@ -218,6 +222,7 @@ struct Explicit_Bool } }; +/// All unicode related operations namespace unicode { inline namespace special_runes @@ -227,29 +232,32 @@ namespace unicode [[maybe_unused]] constexpr u32 Max_Bytes = 4; } - // is_digit returns true if `digit` is ASCII digit + /// is_digit returns true if `digit` is ASCII digit bool is_digit(u32 digit); - // is_space return true if `space` is ASCII blank character + /// is_space return true if `space` is ASCII blank character bool is_space(u32 space); - // is_letter returns true if `letter` is considered a letter by Unicode + /// is_letter returns true if `letter` is considered a letter by Unicode bool is_letter(u32 letter); - // is_identifier returns true if `letter` is valid character for identifier. - // - // It's modifier by is_first_character flag to determine some character classes - // allowance like numbers, which are only allowed NOT at the front of the identifier + /// is_identifier returns true if `letter` is valid character for identifier. + /// + /// It's modifier by is_first_character flag to determine some character classes + /// allowance like numbers, which are only allowed NOT at the front of the identifier enum class First_Character : bool { Yes = true, No = false }; bool is_identifier(u32 letter, First_Character is_first_character); } +/// utf8 encoding and decoding namespace utf8 { using namespace unicode::special_runes; - // Decodes rune and returns remaining string + /// Decodes rune and returns remaining string auto decode(std::string_view s) -> std::pair; + + /// Returns length of the first rune in the provided string auto length(std::string_view s) -> usize; struct Print { u32 rune; }; @@ -257,167 +265,231 @@ namespace utf8 std::ostream& operator<<(std::ostream& os, utf8::Print const& print); +/// Lexical token representation for Musique language struct Token { + /// Type of Token enum class Type { - // like repeat or choose or chord - Symbol, - - // like true, false, nil - Keyword, - - // like + - ++ < > - Operator, - - // chord literal, like c125 - Chord, - - // numeric literal (floating point or integer) - Numeric, - - // "|" separaters arguments from block body, and provides variable introduction syntax - Parameter_Separator, - - // ";" separates expressions. Used to separate calls, like `foo 1 2; bar 3 4` - Expression_Separator, - - // "[" and "]", delimit anonymous block of code (potentially a function) - Open_Block, - Close_Block, - - // "(" and ")", used in arithmetic or as function invocation sarrounding (like in Haskell) - Open_Paren, - Close_Paren + Symbol, ///< like repeat or choose or chord + Keyword, ///< like true, false, nil + Operator, ///< like "+", "-", "++", "<" + Chord, ///< chord or single note literal, like "c125" + Numeric, ///< numeric literal (floating point or integer) + Parameter_Separator, ///< "|" separaters arguments from block body + Expression_Separator, ///< ";" separates expressions. Used mainly to separate calls, like `foo 1 2; bar 3 4` + Open_Block, ///< "[" delimits anonymous block of code (potentially a function) + Close_Block, ///< "]" delimits anonymous block of code (potentially a function) + Open_Paren, ///< "(" used in arithmetic or as function invocation sarrounding + Close_Paren ///< ")" used in arithmetic or as function invocation sarrounding }; + /// Type of token Type type; + + /// Matched source code to the token type std::string_view source; + + /// Location of encountered token Location location; }; +/// Token debug printing std::ostream& operator<<(std::ostream& os, Token const& tok); + +/// Token type debug printing std::ostream& operator<<(std::ostream& os, Token::Type type); +/// Lexer takes source code and turns it into list of tokens +/// +/// It allows for creating sequence of tokens by using next_token() method. +/// On each call to next_token() when source is non empty token is lexed and +/// source is beeing advanced by removing matched token from string. struct Lexer { - // Source that is beeing lexed + /// Source that is beeing lexed std::string_view source; - // Used for rewinding + /// Location in source of the last rune + /// + /// Used only for rewinding u32 last_rune_length = 0; + /// Start of the token that is currently beeing matched char const* token_start = nullptr; + + /// Bytes matched so far usize token_length = 0; + + /// Location of the start of a token that is currently beeing matched Location token_location{}; - Location prev_location{}; + /// Current location of Lexer in source Location location{}; + /// Previous location of Lexer in source + /// + /// Used only for rewinding + Location prev_location{}; + + /// Try to tokenize next token auto next_token() -> Result; - // Utility function for next_token() + /// Skip whitespace and comments from the beggining of the source + /// + /// Utility function for next_token() void skip_whitespace_and_comments(); - // Finds next rune in source + /// Finds next rune in source auto peek() const -> u32; - // Finds next rune in source and returns it, advancing the string + /// Finds next rune in source and returns it, advancing the string auto consume() -> u32; - // For test beeing - // callable, current rune is passed to test - // integral, current rune is tested for equality with test - // string, current rune is tested for beeing in it - // otherwise, current rune is tested for beeing in test - // - // When testing above yields truth, current rune is consumed. - // Returns if rune was consumed + /// For test beeing + /// callable, current rune is passed to test + /// integral, current rune is tested for equality with test + /// string, current rune is tested for beeing in it + /// otherwise, current rune is tested for beeing in test + /// + /// When testing above yields truth, current rune is consumed. + /// Returns if rune was consumed auto consume_if(auto test) -> bool; - // Consume two runes with given tests otherwise backtrack + /// Consume two runes with given tests otherwise backtrack auto consume_if(auto first, auto second) -> bool; - // Goes back last rune + /// Goes back last rune void rewind(); - // Marks begin of token + /// Marks begin of token void start(); - // Marks end of token and returns it's matching source + /// Marks end of token and returns it's matching source std::string_view finish(); }; +/// Representation of a node in program tree struct Ast { - // Named constructors of AST structure + /// Constructs binary operator static Ast binary(Token, Ast lhs, Ast rhs); + + /// Constructs block static Ast block(Location location, Ast seq = sequence({})); + + /// Constructs call expression static Ast call(std::vector call); + + /// Constructs block with parameters static Ast lambda(Location location, Ast seq = sequence({}), std::vector parameters = {}); + + /// Constructs constants, literals and variable identifiers static Ast literal(Token); + + /// Constructs sequence of operations static Ast sequence(std::vector call); + + /// Constructs variable declaration static Ast variable_declaration(Location loc, std::vector lvalues, std::optional rvalue); + /// Available ASt types enum class Type { - Binary, // Binary operator application like `1` + `2` - Block, // Block expressions like `[42; hello]` - Lambda, // Block expression beeing functions like `[i|i+1]` - Call, // Function call application like `print 42` - Literal, // Compile time known constant like `c` or `1` - Sequence, // Several expressions sequences like `42`, `42; 32` - Variable_Declaration, // Declaration of a variable with optional value assigment like `var x = 10` or `var y` + Binary, ///< Binary operator application like `1` + `2` + Block, ///< Block expressions like `[42; hello]` + Lambda, ///< Block expression beeing functions like `[i|i+1]` + Call, ///< Function call application like `print 42` + Literal, ///< Compile time known constant like `c` or `1` + Sequence, ///< Several expressions sequences like `42`, `42; 32` + Variable_Declaration, ///< Declaration of a variable with optional value assigment like `var x = 10` or `var y` }; + /// Type of AST node Type type; + + /// Location that introduced this node Location location; + + /// Associated token Token token; + + /// Child nodes std::vector arguments{}; }; bool operator==(Ast const& lhs, Ast const& rhs); std::ostream& operator<<(std::ostream& os, Ast::Type type); std::ostream& operator<<(std::ostream& os, Ast const& tree); + +/// Pretty print program tree for debugging purposes void dump(Ast const& ast, unsigned indent = 0); +/// Source code to program tree converter +/// +/// Intended to be used by library user only by Parser::parse() static function. struct Parser { + /// List of tokens yielded from source std::vector tokens; + + /// Current token id (offset in tokens array) unsigned token_id = 0; - // Parses whole source code producing Ast or Error - // using Parser structure internally + /// Parses whole source code producing Ast or Error + /// using Parser structure internally static Result parse(std::string_view source, std::string_view filename); + /// Parse sequence, collection of expressions Result parse_sequence(); + + /// Parse either infix expression or variable declaration Result parse_expression(); + + /// Parse infix expression Result parse_infix_expression(); + + /// Parse function call, literal etc Result parse_atomic_expression(); + + /// Parse variable declaration Result parse_variable_declaration(); + /// Utility function for identifier parsing Result parse_identifier_with_trailing_separators(); + + /// Utility function for identifier parsing Result parse_identifier(); + /// Peek current token Result peek() const; + + /// Peek type of the current token Result peek_type() const; + + /// Consume current token Token consume(); - // Tests if current token has given type + /// Tests if current token has given type bool expect(Token::Type type) const; + + /// Tests if current token has given type and source bool expect(Token::Type type, std::string_view lexeme) const; - // Ensures that current token has one of types given. - // Otherwise returns error + /// Ensures that current token has one of types given, otherwise returns error Result ensure(Token::Type type) const; }; -// Number type supporting integer and fractional constants -// Invariant: gcd(num, den) == 1, after any operation +/// Number type supporting integer and fractional constants +/// +/// \invariant gcd(num, den) == 1, after any operation struct Number { + /// Type that represents numerator and denominator values using value_type = i64; - value_type num = 0, den = 1; + + value_type num = 0; ///< Numerator of a fraction beeing represented + value_type den = 1; ///< Denominator of a fraction beeing represented constexpr Number() = default; constexpr Number(Number const&) = default; @@ -425,12 +497,12 @@ struct Number constexpr Number& operator=(Number const&) = default; constexpr Number& operator=(Number &&) = default; - explicit Number(value_type v); - Number(value_type num, value_type den); + explicit Number(value_type v); ///< Creates Number as fraction v / 1 + Number(value_type num, value_type den); ///< Creates Number as fraction num / den - auto as_int() const -> value_type; // Returns self as int - auto simplify() const -> Number; // Returns self, but with gcd(num, den) == 1 - void simplify_inplace(); // Update self, to have gcd(num, den) == 1 + auto as_int() const -> value_type; ///< Returns self as int + auto simplify() const -> Number; ///< Returns self, but with gcd(num, den) == 1 + void simplify_inplace(); ///< Update self, to have gcd(num, den) == 1 bool operator==(Number const&) const; bool operator!=(Number const&) const; @@ -445,6 +517,7 @@ struct Number Number operator/(Number const& rhs) const; Number& operator/=(Number const& rhs); + /// Parses source contained by token into a Number instance static Result from(Token token); }; @@ -459,16 +532,29 @@ using Intrinsic = Result(*)(Interpreter &i, std::vector); /// Lazy Array / Continuation / Closure type thingy struct Block { + /// Location of definition / creation Location location; + + /// Names of expected parameters std::vector parameters; + + /// Body that will be executed Ast body; + + /// Context from which block was created. Used for closures std::shared_ptr context; + /// Calling block Result operator()(Interpreter &i, std::vector params); + + /// Indexing block Result index(Interpreter &i, unsigned position); + + /// Count of elements in block usize size() const; }; +/// Representation of musical note struct Note { /// Base of a note, like `c` (=0), `c#` (=1) `d` (=2) @@ -491,15 +577,18 @@ struct Note bool operator==(Note const&) const; + /// Simplify note by adding base to octave if octave is present void simplify_inplace(); }; std::ostream& operator<<(std::ostream& os, Note note); +/// Represantation of simultaneously played notes, aka musical chord struct Chord { - std::vector notes; + std::vector notes; ///< Notes composing a chord + /// Parse chord literal from provided source static Chord from(std::string_view source); bool operator==(Chord const&) const = default; @@ -513,7 +602,10 @@ struct Array /// Elements that are stored in array std::vector elements; + /// Index element of an array Result index(Interpreter &i, unsigned position); + + /// Count of elements usize size() const; bool operator==(Array const&) const = default; @@ -521,33 +613,37 @@ struct Array std::ostream& operator<<(std::ostream& os, Array const& v); -// TODO Add location +/// Representation of any value in language struct Value { + /// Creates value from literal contained in Token static Result from(Token t); + + /// Create value holding provided boolean + /// + /// Using Explicit_Bool to prevent from implicit casts static Value from(Explicit_Bool b); - static Value from(Number n); - // Symbol creating functions - static Value from(std::string s); - static Value from(std::string_view s); - static Value from(char const* s); - static Value from(Block &&l); - static Value from(Array &&array); - static Value from(Note n); - static Value from(Chord chord); + static Value from(Number n); ///< Create value of type number holding provided number + static Value from(std::string s); ///< Create value of type symbol holding provided symbol + static Value from(std::string_view s); ///< Create value of type symbol holding provided symbol + static Value from(char const* s); ///< Create value of type symbol holding provided symbol + static Value from(Block &&l); ///< Create value of type block holding provided block + static Value from(Array &&array); ///< Create value of type array holding provided array + static Value from(Note n); ///< Create value of type music holding provided note + static Value from(Chord chord); ///< Create value of type music holding provided chord enum class Type { - Nil, - Bool, - Number, - Symbol, - Intrinsic, - Block, - Array, - Music, + Nil, ///< Unit type, used for denoting emptiness and result of some side effect only functions + Bool, ///< Boolean type, used for logic computations + Number, ///< Number type, representing only rational numbers + Symbol, ///< Symbol type, used to represent identifiers + Intrinsic, ///< Intrinsic functions that are implemented in C++ + Block, ///< Block type, containing block value (lazy array/closure/lambda like) + Array, ///< Array type, eager array + Music, ///< Music type, }; Value() = default; @@ -556,6 +652,7 @@ struct Value Value& operator=(Value const&) = default; Value& operator=(Value &&) = default; + /// Contructs Intrinsic, used to simplify definition of intrinsics inline Value(Intrinsic intr) : type{Type::Intrinsic}, intr(intr) { } @@ -574,26 +671,42 @@ struct Value // std::string_view - not-owning string type std::string s{}; + /// Returns truth judgment for current type, used primarly for if function bool truthy() const; + + /// Returns false judgment for current type, used primarly for if function bool falsy() const; + + /// Calls contained value if it can be called Result operator()(Interpreter &i, std::vector args); + + /// Index contained value if it can be called Result index(Interpreter &i, unsigned position); + + /// Return elements count of contained value if it can be measured usize size() const; bool operator==(Value const& other) const; }; +/// Returns type name of Value type std::string_view type_name(Value::Type t); std::ostream& operator<<(std::ostream& os, Value const& v); +/// Collection holding all variables in given scope. struct Env : std::enable_shared_from_this { - // Constructor of Env class + /// Constructor of Env class static std::shared_ptr make(); + /// Global scope that is beeing set by Interpreter static std::shared_ptr global; + + /// Variables in current scope std::unordered_map variables; + + /// Parent scope std::shared_ptr parent; Env(Env const&) = delete; @@ -603,14 +716,18 @@ struct Env : std::enable_shared_from_this /// Defines new variable regardless of it's current existance Env& force_define(std::string name, Value new_value); + + /// Finds variable in current or parent scopes Value* find(std::string const& name); - // Scope menagment + /// Create new scope with self as parent std::shared_ptr enter(); + + /// Leave current scope returning parent std::shared_ptr leave(); private: - // Ensure that all values of this class are behind shared_ptr + /// Ensure that all values of this class are behind shared_ptr Env() = default; }; @@ -633,6 +750,7 @@ struct Context std::chrono::duration length_to_duration(std::optional length) const; }; +/// Given program tree evaluates it into Value struct Interpreter { /// MIDI connection that is used to play music. @@ -654,10 +772,13 @@ struct Interpreter Interpreter(Interpreter const&) = delete; Interpreter(Interpreter &&) = default; + /// Try to evaluate given program tree Result eval(Ast &&ast); - // Scope managment + // Enter scope by changing current environment void enter_scope(); + + // Leave scope by changing current environment void leave_scope(); /// Play note resolving any missing parameters with context via `midi_connection` member.