diff --git a/examples/common-array-operations.mq b/examples/common-array-operations.mq index c67cec9..5b4afb7 100644 --- a/examples/common-array-operations.mq +++ b/examples/common-array-operations.mq @@ -1,5 +1,5 @@ map := (fn array | result := (), for array (v | result = result & (| fn v) ), result), -drop := (n arr | arr.(range n (len arr))), -take := (n arr | arr.(up n)), -filter := (predicate arr | arr.(map predicate arr)), +drop := (n arr | arr[range n (len arr)]), +take := (n arr | arr[up n]), +filter := (predicate arr | arr[map predicate arr]), diff --git a/examples/random-rythm.mq b/examples/random-rythm.mq index ea87e9a..1bc16b7 100644 --- a/examples/random-rythm.mq +++ b/examples/random-rythm.mq @@ -1,5 +1,5 @@ for (up 10) (n | - snd := c + (shuffle (up 12)).0, + snd := c + (pick (up 12)), o := if (n % 2 == 0) 3 4, say snd o, play (oct o, snd qn), diff --git a/examples/rhythm-of-primes.mq b/examples/rhythm-of-primes.mq index 5afa97d..8d507f1 100644 --- a/examples/rhythm-of-primes.mq +++ b/examples/rhythm-of-primes.mq @@ -36,5 +36,5 @@ oct 3, len (1/16), for (2 + up Length) ( i | - play (chord scale.(indicies.(i % primes == 0))), + play (chord scale[indicies[i % primes == 0]]), ), diff --git a/musique/errors.hh b/musique/errors.hh index 769345d..7c481b2 100644 --- a/musique/errors.hh +++ b/musique/errors.hh @@ -1,10 +1,6 @@ #ifndef MUSIQUE_ERRORS_HH #define MUSIQUE_ERRORS_HH -#if defined(__cpp_lib_source_location) -#include -#endif - #include #include #include diff --git a/musique/interpreter/builtin_operators.cc b/musique/interpreter/builtin_operators.cc index 7a57d4e..c2ee6cf 100644 --- a/musique/interpreter/builtin_operators.cc +++ b/musique/interpreter/builtin_operators.cc @@ -306,11 +306,11 @@ static constexpr auto Operators = std::array { Operator_Entry { ">", builtin_operator_compare> }, Operator_Entry { ">=", builtin_operator_compare> }, - Operator_Entry { ".", builtin_operator_index }, + Operator_Entry { "[", builtin_operator_index }, Operator_Entry { "&", builtin_operator_join }, }; -// All operators should be defined here except 'and' and 'or' which handle evaluation differently +// All operators should be defined here except '=', 'and' and 'or' which handle evaluation differently // and are need unevaluated expressions for their proper evaluation. Exclusion of them is marked // as subtraction of total excluded operators from expected constant static_assert(Operators.size() == Operators_Count - 3, "All operators handlers are defined here"); diff --git a/musique/lexer/lexer.cc b/musique/lexer/lexer.cc index ab7da1d..6d31517 100644 --- a/musique/lexer/lexer.cc +++ b/musique/lexer/lexer.cc @@ -90,6 +90,8 @@ auto Lexer::next_token() -> Result> switch (peek()) { case '(': consume(); return Token { Token::Type::Open_Block, finish(), token_location }; case ')': consume(); return Token { Token::Type::Close_Block, finish(), token_location }; + case '[': consume(); return Token { Token::Type::Open_Index, finish(), token_location }; + case ']': consume(); return Token { Token::Type::Close_Index, finish(), token_location }; case ',': consume(); return Token { Token::Type::Expression_Separator, finish(), token_location }; case '|': @@ -263,6 +265,8 @@ std::ostream& operator<<(std::ostream& os, Token::Type type) case Token::Type::Operator: return os << "OPERATOR"; case Token::Type::Parameter_Separator: return os << "PARAMETER SEPARATOR"; case Token::Type::Symbol: return os << "SYMBOL"; + case Token::Type::Open_Index: return os << "OPEN INDEX"; + case Token::Type::Close_Index: return os << "CLOSE INDEX"; } unreachable(); } @@ -272,10 +276,12 @@ std::string_view type_name(Token::Type type) switch (type) { case Token::Type::Chord: return "chord"; case Token::Type::Close_Block: return ")"; + case Token::Type::Close_Index: return "]"; case Token::Type::Expression_Separator: return "|"; case Token::Type::Keyword: return "keyword"; case Token::Type::Numeric: return "numeric"; case Token::Type::Open_Block: return "("; + case Token::Type::Open_Index: return "["; case Token::Type::Operator: return "operator"; case Token::Type::Parameter_Separator: return "parameter separator"; case Token::Type::Symbol: return "symbol"; diff --git a/musique/lexer/token.hh b/musique/lexer/token.hh index 638f4b6..f542a85 100644 --- a/musique/lexer/token.hh +++ b/musique/lexer/token.hh @@ -13,12 +13,14 @@ struct Token Symbol, ///< like repeat or choose or chord Keyword, ///< like true, false, nil Operator, ///< like "+", "-", "++", "<" - Chord, ///< chord or single note literal, like "c125" + Chord, ///< chord or single note literal, like "c4" 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) + Expression_Separator, ///< "," separates expressions. Used mainly to separate calls, like `foo 1 2; bar 3 4` + Open_Block, ///< "(" starts anonymous block of code (potentially a function) + Close_Block, ///< ")" ends anonymous block of code (potentially a function) + Open_Index, ///< "[" starts index section of index expression + Close_Index ///< "]" ends index section of index expression }; /// Type of token diff --git a/musique/location.hh b/musique/location.hh index 173bfc0..166afe4 100644 --- a/musique/location.hh +++ b/musique/location.hh @@ -1,6 +1,10 @@ #ifndef MUSIQUE_LOCATION_HH #define MUSIQUE_LOCATION_HH +#if defined(__cpp_lib_source_location) +#include +#endif + #include #include diff --git a/musique/parser/parser.cc b/musique/parser/parser.cc index 38abd13..54129a9 100644 --- a/musique/parser/parser.cc +++ b/musique/parser/parser.cc @@ -131,7 +131,6 @@ Result Parser::parse_infix_expression() || expect(Token::Type::Keyword, "or"); if (next_is_operator) { - ensure(not expect(Token::Type::Operator, "."), "This should be handled by parse_index_expression"); auto op = consume(); Ast ast; @@ -159,7 +158,6 @@ Result Parser::parse_rhs_of_infix_expression(Ast lhs) return lhs; } - ensure(not expect(Token::Type::Operator, "."), "This should be handled by parse_index_expression"); auto op = consume(); if (precedense(lhs.token.source) >= precedense(op.source)) { @@ -181,18 +179,61 @@ Result Parser::parse_rhs_of_infix_expression(Ast lhs) return lhs; } +Result parse_sequence_inside( + Parser &parser, + Token::Type closing_token, + Location start_location, + bool is_lambda, + std::vector &¶meters, + auto &&dont_arrived_at_closing_token +) +{ + auto ast = Try(parser.parse_sequence()); + if (not parser.expect(closing_token)) { + Try(dont_arrived_at_closing_token()); + return Error { + .details = errors::Unexpected_Empty_Source { + .reason = errors::Unexpected_Empty_Source::Block_Without_Closing_Bracket, + .start = start_location + }, + .location = parser.tokens.back().location + }; + } + + parser.consume(); + + if (is_lambda) { + return Ast::lambda(start_location, std::move(ast), std::move(parameters)); + } + + ensure(ast.type == Ast::Type::Sequence, "I dunno if this is a valid assumption tbh"); + if (ast.arguments.size() == 1) { + return std::move(ast.arguments.front()); + } + return Ast::block(start_location, std::move(ast)); +} + Result Parser::parse_index_expression() { auto result = Try(parse_atomic_expression()); - while (expect(Token::Type::Operator, ".")) { + while (expect(Token::Type::Open_Index)) { auto op = consume(); - if (auto maybe_index = parse_atomic_expression(); maybe_index.has_value()) { - result = Ast::binary(std::move(op), std::move(result), *std::move(maybe_index)); - } else { - // TODO Report that error occured during parsing of index expression - return std::move(maybe_index).error(); - } + auto start_location = op.location; + result = Ast::binary( + std::move(op), + std::move(result), + Try(parse_sequence_inside( + *this, + Token::Type::Close_Index, + start_location, + false, + {}, + []() -> std::optional { + return std::nullopt; + } + )) + ); } return result; @@ -243,55 +284,45 @@ Result Parser::parse_atomic_expression() } } - return parse_sequence().and_then([&](Ast &&ast) -> Result { - if (not expect(Token::Type::Close_Block)) { - if (expect(Token::Type::Parameter_Separator)) { - if (is_lambda) { - ensure(false, "There should be error message that you cannot put multiple parameter separators in one block"); - } else { - // This may be a result of user trying to specify parameters from things that cannot be parameters (like chord literals) - // or accidential hit of "|" on the keyboard. We can detect first case by ensuring that all tokens between this place - // and beggining of a block are identifier looking: keywords, symbols, literal chord declarations, boolean literals etc - std::optional invalid_token = std::nullopt; - auto const success = std::all_of(tokens.begin() + start, tokens.begin() + (token_id - start + 1), [&](Token const& token) { - if (!invalid_token && token.type != Token::Type::Symbol) { - // TODO Maybe gather all tokens to provide all the help needed in the one iteration - invalid_token = token; - } - return is_identifier_looking(token.type); - }); + return parse_sequence_inside( + *this, + Token::Type::Close_Block, + opening.location, + is_lambda, + std::move(parameters), + [&]() -> std::optional { + if (!expect(Token::Type::Parameter_Separator)) { + return std::nullopt; + } + if (is_lambda) { + ensure(false, "There should be error message that you cannot put multiple parameter separators in one block"); + } - if (success && invalid_token) { - return Error { - .details = errors::Literal_As_Identifier { - .type_name = type_name(invalid_token->type), - .source = invalid_token->source, - .context = "block parameter list" - }, - .location = invalid_token->location - }; - } + // This may be a result of user trying to specify parameters from things that cannot be parameters (like chord literals) + // or accidential hit of "|" on the keyboard. We can detect first case by ensuring that all tokens between this place + // and beggining of a block are identifier looking: keywords, symbols, literal chord declarations, boolean literals etc + std::optional invalid_token = std::nullopt; + auto const success = std::all_of(tokens.begin() + start, tokens.begin() + (token_id - start + 1), [&](Token const& token) { + if (!invalid_token && token.type != Token::Type::Symbol) { + // TODO Maybe gather all tokens to provide all the help needed in the one iteration + invalid_token = token; } + return is_identifier_looking(token.type); + }); + + if (success && invalid_token) { + return Error { + .details = errors::Literal_As_Identifier { + .type_name = type_name(invalid_token->type), + .source = invalid_token->source, + .context = "block parameter list" + }, + .location = invalid_token->location + }; } - return Error { - .details = errors::Unexpected_Empty_Source { - .reason = errors::Unexpected_Empty_Source::Block_Without_Closing_Bracket, - .start = opening.location - }, - .location = tokens.back().location - }; + return std::nullopt; } - consume(); - if (is_lambda) { - return Ast::lambda(opening.location, std::move(ast), std::move(parameters)); - } else { - ensure(ast.type == Ast::Type::Sequence, "I dunno if this is a valid assumption tbh"); - if (ast.arguments.size() == 1) { - return std::move(ast.arguments.front()); - } - return Ast::block(opening.location, std::move(ast)); - } - }); + ); } break; case Token::Type::Operator: @@ -516,7 +547,8 @@ constexpr bool one_of(std::string_view id, auto const& ...args) static usize precedense(std::string_view op) { // Operators that are not included below are - // '.' since it have own precedense rules and is not binary expression but its own kind of expression + // postfix index operator [] since it have own precedense rules and is not binary expression but its own kind of expression + // in terms of our parsing function hierarchy // // Exclusion of them is marked by subtracting total number of excluded operators. static_assert(Operators_Count - 1 == 16, "Ensure that all operators have defined precedense below"); @@ -530,9 +562,6 @@ static usize precedense(std::string_view op) if (one_of(op, "*", "/", "%", "&")) return 400; if (one_of(op, "**")) return 500; - - std::cerr << op << std::endl; - unreachable(); }