diff --git a/examples/assigments-state-management.mq b/examples/assigments-state-management.mq index 9111ed1..48d8f0e 100644 --- a/examples/assigments-state-management.mq +++ b/examples/assigments-state-management.mq @@ -1,8 +1,8 @@ ---------------------------------- Simple variable assigment ---------------------------------- -var x = 10; -var y = 20; +x := 10; +y := 20; say x; say y; @@ -17,7 +17,7 @@ say y; ---------------------------------- Array updates ---------------------------------- -var array = flat 1 2 3; +array := flat 1 2 3; say array; array = update array 1 10; diff --git a/examples/church.mq b/examples/church.mq index 4aa9a1c..dbec7cf 100644 --- a/examples/church.mq +++ b/examples/church.mq @@ -1,34 +1,34 @@ -- PAIR definition -var pair = [x y | [z | z x y]]; -var car = [p | p [x y | x]]; -var cdr = [p | p [x y | y]]; +pair := [x y | [z | z x y]]; +car := [pair | pair [x y | x]]; +cdr := [pair | pair [x y | y]]; -var x = pair 100 200; +x := pair 100 200; say (car x); say (cdr x); -- LIST definition -var null = pair true true; -var is_empty = car; -var head = [list | car (cdr list)]; -var tail = [list | cdr (cdr list)]; -var cons = [head tail | pair false (pair head tail)]; +null := pair true true; +is_empty := car; +head := [list | car (cdr list)]; +tail := [list | cdr (cdr list)]; +cons := [head tail | pair false (pair head tail)]; -var for_each = [list iterator | +for_each := [list iterator | if (is_empty list) [ nil ] [ iterator (head list); for_each (tail list) iterator ]]; -var map = [list iterator | +map := [list iterator | if (is_empty list) [ null ] [| cons (iterator (head list)) (map (tail list) iterator) ]]; -var foldr = [list init folder | +foldr := [list init folder | if (is_empty list) [ init ] [ foldr (tail list) (folder (head list) init) folder ]]; -var range = [start stop | if (start >= stop) [cons start null] [cons start (range (start+1) stop)]]; +range := [start stop | if (start >= stop) [cons start null] [cons start (range (start+1) stop)]]; -var xs = range 1 5; +xs := range 1 5; say (foldr xs 1 [x y|x*y]); diff --git a/examples/common-array-operations.mq b/examples/common-array-operations.mq index faaf169..c425034 100644 --- a/examples/common-array-operations.mq +++ b/examples/common-array-operations.mq @@ -1,4 +1,4 @@ -var map = [fn array | var result = []; for array [v | result = result & [fn v] ]; result]; -var drop = [n arr | arr.(range n (len arr))]; -var take = [n arr | arr.(up n)]; -var filter = [predicate arr | arr.(map predicate arr)]; +map := [fn array | var 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)]; diff --git a/examples/factorial.mq b/examples/factorial.mq index 6119a1a..c896995 100644 --- a/examples/factorial.mq +++ b/examples/factorial.mq @@ -1,14 +1,14 @@ -var for = [ start stop iteration | +for := [ start stop iteration | if (start > stop) [nil] [iteration start; for (start + 1) stop iteration] ]; -var factorial = [n | if (n <= 1) [1] [n * (factorial (n-1))]]; +factorial := [n | if (n <= 1) [1] [n * (factorial (n-1))]]; for 1 10 [i | say (factorial i)]; -var factorial_iterative = [n | - var x = 1; +factorial_iterative := [n | + x := 1; for 1 n [i|x *= i]; x ]; diff --git a/examples/fib.mq b/examples/fib.mq index 2910a13..102bd9a 100644 --- a/examples/fib.mq +++ b/examples/fib.mq @@ -1 +1 @@ -var fib = [n | if (n <= 1) [ n ] [ fib (n-1) + fib (n-2) ] ]; +fib := [n | if (n <= 1) [ n ] [ fib (n-1) + fib (n-2) ] ]; diff --git a/examples/for-elise.mq b/examples/for-elise.mq index 3664c20..b8e2f4a 100644 --- a/examples/for-elise.mq +++ b/examples/for-elise.mq @@ -3,7 +3,7 @@ ---------------------------------------------- oct 5; bpm 72; len (1/16); -var subsection1 = [ n | +subsection1 := [ n | sim (a 4 en) (a 2 e 3 a 3); play [oct 4; c e a]; @@ -22,7 +22,7 @@ var subsection1 = [ n | play (d 4 c 5 b 4); ]; -var section1 = [ n | +section1 := [ n | play (e d#); play (e d# e b 4 d c); @@ -35,7 +35,7 @@ var section1 = [ n | ] ]; -var section2 = [ n | +section2 := [ n | sim (e 5 den) (c 3 g 3 c 4); play (g 4 f e); diff --git a/examples/ode-to-joy.mq b/examples/ode-to-joy.mq index e356450..7adf97e 100644 --- a/examples/ode-to-joy.mq +++ b/examples/ode-to-joy.mq @@ -1,5 +1,5 @@ -var C = chord (c 3 1) (g 3 1) (c 4 1); -var G = chord (g 3 1) (d 4 1) (g 4 1); +C := chord (c 3 1) (g 3 1) (c 4 1); +G := chord (g 3 1) (d 4 1) (g 4 1); oct 5; par C e e f g; diff --git a/examples/permutations.mq b/examples/permutations.mq index 8b30873..2a33e45 100644 --- a/examples/permutations.mq +++ b/examples/permutations.mq @@ -1,7 +1,7 @@ -var factorial = [n | if (n < 2) [1] [factorial (n-1) * n]]; +factorial := [n | if (n < 2) [1] [factorial (n-1) * n]]; -var for_all_permutations = [array fun | - var iter = [start stop x | if (start >= stop) [x] [iter (start+1) stop (fun x)]]; +for_all_permutations := [array fun | + iter := [start stop x | if (start >= stop) [x] [iter (start+1) stop (fun x)]]; iter 0 (factorial (len array)) array ]; diff --git a/examples/rhythm-of-primes.mq b/examples/rhythm-of-primes.mq index 806e158..6f67af9 100644 --- a/examples/rhythm-of-primes.mq +++ b/examples/rhythm-of-primes.mq @@ -25,12 +25,12 @@ play c d c e (c & d) c d (c & e) -------------------------------------------------------------- -var Length = 20; +Length := 20; -var cmajor = [c;d;e;f;g]; -var scale = reverse cmajor; -var primes = nprimes (len scale); -var indicies = up (len scale); +cmajor := [c;d;e;f;g]; +scale := reverse cmajor; +primes := nprimes (len scale); +indicies := up (len scale); oct 3; len (1/16); diff --git a/examples/variables.mq b/examples/variables.mq index cbcd8ee..8719f9a 100644 --- a/examples/variables.mq +++ b/examples/variables.mq @@ -1,2 +1,2 @@ -var x = 10; +x := 10; say (x + 1) diff --git a/include/musique.hh b/include/musique.hh index a62bba7..7d64318 100644 --- a/include/musique.hh +++ b/include/musique.hh @@ -475,8 +475,8 @@ struct Token Location location; }; -static constexpr usize Keywords_Count = 6; -static constexpr usize Operators_Count = 16; +static constexpr usize Keywords_Count = 5; +static constexpr usize Operators_Count = 17; std::string_view type_name(Token::Type type); @@ -687,6 +687,9 @@ struct Parser /// Tests if current token has given type and source bool expect(Token::Type type, std::string_view lexeme) const; + + // Tests if current token has given type and the next token has given type and source + bool expect(Token::Type t1, Token::Type t2, std::string_view lexeme_for_t2) const; }; /// Number type supporting integer and fractional constants diff --git a/include/musique_internal.hh b/include/musique_internal.hh index 4db056b..b806408 100644 --- a/include/musique_internal.hh +++ b/include/musique_internal.hh @@ -136,6 +136,7 @@ namespace algo /// Fold that stops iteration on error value via Result type template constexpr Result fold(auto&& range, T init, auto &&reducer) + requires (is_template_v) { for (auto &&value : range) { init = Try(reducer(std::move(init), value)); diff --git a/src/builtin_operators.cc b/src/builtin_operators.cc index b656357..752af67 100644 --- a/src/builtin_operators.cc +++ b/src/builtin_operators.cc @@ -1,10 +1,10 @@ #include #include +#include /// Intrinsic implementation primitive to ease operation vectorization -Result vectorize(auto &&operation, Interpreter &interpreter, Value lhs, Value rhs) +static Result vectorize(auto &&operation, Interpreter &interpreter, Value lhs, Value rhs) { - Array array; if (is_indexable(lhs.type) && !is_indexable(rhs.type)) { Array array; @@ -15,6 +15,7 @@ Result vectorize(auto &&operation, Interpreter &interpreter, Value lhs, V return Value::from(std::move(array)); } + Array array; for (auto i = 0u; i < rhs.size(); ++i) { array.elements.push_back( Try(operation(interpreter, { lhs, Try(rhs.index(interpreter, i)) }))); @@ -24,19 +25,39 @@ Result vectorize(auto &&operation, Interpreter &interpreter, Value lhs, V /// Intrinsic implementation primitive to ease operation vectorization /// @invariant args.size() == 2 -Result vectorize(auto &&operation, Interpreter &interpreter, std::vector args) +static Result vectorize(auto &&operation, Interpreter &interpreter, std::vector args) { assert(args.size() == 2, "Vectorization primitive only supports two arguments"); return vectorize(std::move(operation), interpreter, std::move(args.front()), std::move(args.back())); } - -std::optional symetric(Value::Type t1, Value::Type t2, Value &lhs, Value &rhs, auto binary_predicate) +/// Helper simlifiing implementation of symetric binary operations. +/// +/// Calls binary if values matches types any permutation of {t1, t2}, always in shape (t1, t2) +inline std::optional symetric(Value::Type t1, Value::Type t2, Value &lhs, Value &rhs, auto binary) { if (lhs.type == t1 && rhs.type == t2) { - return binary_predicate(std::move(lhs), std::move(rhs)); + return binary(std::move(lhs), std::move(rhs)); } else if (lhs.type == t2 && rhs.type == t1) { - return binary_predicate(std::move(rhs), std::move(lhs)); + return binary(std::move(rhs), std::move(lhs)); + } else { + return std::nullopt; + } +} + +/// Helper simlifiing implementation of symetric binary operations. +/// +/// Calls binary if values matches predicates in any permutation; always with shape (p1, p2) +inline auto symetric( + std::predicate auto&& p1, + std::predicate auto&& p2, + Value &lhs, Value &rhs, + auto binary) -> std::optional +{ + if (p1(lhs.type) && p2(rhs.type)) { + return binary(std::move(lhs), std::move(rhs)); + } else if (p2(lhs.type) && p1(rhs.type)) { + return binary(std::move(rhs), std::move(lhs)); } else { return std::nullopt; } @@ -49,61 +70,49 @@ std::optional symetric(Value::Type t1, Value::Type t2, Value &lhs, Value template static Result plus_minus_operator(Interpreter &interpreter, std::vector args) { - static_assert(std::is_same_v> || std::is_same_v>, - "Error reporting depends on only one of this two types beeing provided"); - - using NN = Shape; - using MN = Shape; - using NM = Shape; - - if (NN::typecheck(args)) { - auto [a, b] = NN::move_from(args); - return Value::from(Binary_Operation{}(std::move(a), std::move(b))); + if (args.empty()) { + return Value::from(Number(0)); } - if (MN::typecheck(args)) { - auto [chord, offset] = MN::move_from(args); - for (auto ¬e : chord.notes) { - if (note.base) { - *note.base = Binary_Operation{}(*note.base, offset.as_int()); - note.simplify_inplace(); - } + Value init = args.front(); + return algo::fold(std::span(args).subspan(1), std::move(init), [&interpreter](Value lhs, Value &rhs) -> Result { + if (lhs.type == Value::Type::Number && rhs.type == Value::Type::Number) { + return Value::from(Binary_Operation{}(std::move(lhs).n, std::move(rhs).n)); } - return Value::from(std::move(chord)); - } - if (NM::typecheck(args)) { - auto [offset, chord] = NM::move_from(args); - for (auto ¬e : chord.notes) { - if (note.base) { - *note.base = Binary_Operation{}(*note.base, offset.as_int()); - note.simplify_inplace(); + auto result = symetric(Value::Type::Music, Value::Type::Number, lhs, rhs, [](Value lhs, Value rhs) { + for (auto ¬e : lhs.chord.notes) { + if (note.base) { + *note.base = Binary_Operation{}(*note.base, rhs.n.as_int()); + note.simplify_inplace(); + } } + return lhs; + }); + if (result.has_value()) { + return *std::move(result); } - return Value::from(std::move(chord)); - } - if (may_be_vectorized(args)) { - return vectorize(plus_minus_operator, interpreter, std::move(args)); - } + if (is_indexable(lhs.type) != is_indexable(rhs.type)) { + return vectorize(plus_minus_operator, interpreter, std::move(lhs), std::move(rhs)); + } - // TODO Limit possibilities based on provided types - static_assert(std::is_same_v, Binary_Operation> || std::is_same_v, Binary_Operation>, - "Error message printing only supports operators given above"); - return Error { - .details = errors::Unsupported_Types_For { - .type = errors::Unsupported_Types_For::Operator, - .name = std::is_same_v, Binary_Operation> ? "+" : "-", - .possibilities = { - "(number, number) -> number", - "(music, number) -> music", - "(number, music) -> music", - "(array, number|music) -> array", - "(number|music, array) -> array", + static_assert(std::is_same_v, Binary_Operation> || std::is_same_v, Binary_Operation>, + "Error message printing only supports operators given above"); + return Error { + .details = errors::Unsupported_Types_For { + .type = errors::Unsupported_Types_For::Operator, + .name = std::is_same_v, Binary_Operation> ? "+" : "-", + .possibilities = { + "(number, number) -> number", + "(music, number) -> music", + "(number, music) -> music", + "(array, number|music) -> array", + "(number|music, array) -> array", + } } - }, - .location = {}, // TODO fill location - }; + }; + }); } template @@ -144,26 +153,24 @@ static Result comparison_operator(Interpreter &interpreter, std::vector Result { std::vector result; - result.reserve(args[0].size()); - for (auto i = 0u; i < args[0].size(); ++i) { + result.reserve(lhs.size()); + for (auto i = 0u; i < lhs.size(); ++i) { result.push_back( Value::from( Binary_Predicate{}( - Try(args[0].index(interpreter, i)), - args[1] + Try(lhs.index(interpreter, i)), + rhs ) ) ); } return Value::from(Array { std::move(result) }); + }); + + if (result.has_value()) { + return *std::move(result); } return Value::from(Binary_Predicate{}(std::move(args.front()), std::move(args.back()))); @@ -308,7 +315,7 @@ static constexpr auto Operators = std::array { // 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 - 2, "All operators handlers are defined here"); +static_assert(Operators.size() == Operators_Count - 3, "All operators handlers are defined here"); void Interpreter::register_builtin_operators() { diff --git a/src/lexer.cc b/src/lexer.cc index 80113d9..e3c9243 100644 --- a/src/lexer.cc +++ b/src/lexer.cc @@ -15,7 +15,6 @@ constexpr auto Keywords = std::array { "false"sv, "nil"sv, "true"sv, - "var"sv, "and"sv, "or"sv }; diff --git a/src/parser.cc b/src/parser.cc index 9351868..05213fa 100644 --- a/src/parser.cc +++ b/src/parser.cc @@ -26,14 +26,14 @@ constexpr auto Literal_Keywords = std::array { "true"sv, }; -static_assert(Keywords_Count == Literal_Keywords.size() + 3, "Ensure that all literal keywords are listed"); +static_assert(Keywords_Count == Literal_Keywords.size() + 2, "Ensure that all literal keywords are listed"); constexpr auto Operator_Keywords = std::array { "and"sv, "or"sv }; -static_assert(Keywords_Count == Operator_Keywords.size() + 4, "Ensure that all keywords that are operators are listed here"); +static_assert(Keywords_Count == Operator_Keywords.size() + 3, "Ensure that all keywords that are operators are listed here"); enum class At_Least : bool { @@ -67,13 +67,6 @@ Result Parser::parse(std::string_view source, std::string_view filename, un auto const result = parser.parse_sequence(); if (result.has_value() && parser.token_id < parser.tokens.size()) { - if (parser.expect(Token::Type::Keyword, "var")) { - return Error { - .details = errors::Expected_Expression_Separator_Before { .what = "var" }, - .location = parser.peek()->location - }; - } - if (parser.expect(Token::Type::Close_Paren) || parser.expect(Token::Type::Close_Block)) { auto const tok = parser.consume(); return Error { @@ -100,7 +93,7 @@ Result Parser::parse_sequence() Result Parser::parse_expression() { - if (expect(Token::Type::Keyword, "var")) { + if (expect(Token::Type::Symbol, Token::Type::Operator, ":=")) { return parse_variable_declaration(); } return parse_infix_expression(); @@ -108,10 +101,7 @@ Result Parser::parse_expression() Result Parser::parse_variable_declaration() { - assert(expect(Token::Type::Keyword, "var"), "Parser::parse_variable_declaration must be called only on expressions that starts with 'var'"); - auto var = consume(); - - auto lvalue = parse_many(*this, &Parser::parse_identifier, std::nullopt, At_Least::One); + auto lvalue = parse_identifier(); if (not lvalue.has_value()) { auto details = lvalue.error().details; if (auto ut = std::get_if(&details); ut) { @@ -127,13 +117,9 @@ Result Parser::parse_variable_declaration() return std::move(lvalue).error(); } - - if (expect(Token::Type::Operator, "=")) { - consume(); - return Ast::variable_declaration(var.location, *std::move(lvalue), Try(parse_expression())); - } - - return Ast::variable_declaration(var.location, *std::move(lvalue), std::nullopt); + assert(expect(Token::Type::Operator, ":="), "This function should always be called with valid sequence"); + consume(); + return Ast::variable_declaration(lvalue->location, { *std::move(lvalue) }, Try(parse_expression())); } Result Parser::parse_infix_expression() @@ -436,6 +422,14 @@ bool Parser::expect(Token::Type type, std::string_view lexeme) const return token_id < tokens.size() && tokens[token_id].type == type && tokens[token_id].source == lexeme; } +bool Parser::expect(Token::Type t1, Token::Type t2, std::string_view lexeme_for_t2) const +{ + return token_id+1 < tokens.size() + && tokens[token_id].type == t1 + && tokens[token_id+1].type == t2 + && tokens[token_id+1].source == lexeme_for_t2; +} + // Don't know if it's a good idea to defer parsing of literal values up to value creation, which is current approach. // This may create unexpected performance degradation during program evaluation. Ast Ast::literal(Token token) @@ -529,9 +523,10 @@ static usize precedense(std::string_view op) // '.' since it have own precedense rules and is not binary expression but its own kind of expression // // Exclusion of them is marked by subtracting total number of excluded operators. - static_assert(Operators_Count - 1 == 15, "Ensure that all operators have defined precedense below"); + static_assert(Operators_Count - 1 == 16, "Ensure that all operators have defined precedense below"); - if (one_of(op, "=")) return 0; + if (one_of(op, ":=")) return 0; + if (one_of(op, "=")) return 10; if (one_of(op, "or")) return 100; if (one_of(op, "and")) return 150; if (one_of(op, "<", ">", "<=", ">=", "==", "!=")) return 200; diff --git a/src/value.cc b/src/value.cc index f3605f2..60bce30 100644 --- a/src/value.cc +++ b/src/value.cc @@ -1,6 +1,7 @@ #include #include +#include #include /// Finds numeric value of note. This form is later used as in @@ -21,8 +22,6 @@ constexpr u8 note_index(u8 note) unreachable(); } -#include - constexpr std::string_view note_index_to_string(int note_index) { note_index %= 12;