New declaration syntax x := foo instead of var x = foo

Additionaly moving into multiparameter binary operations
This commit is contained in:
Robert Bendun 2022-09-18 14:50:20 +02:00
parent e57a60c36f
commit ddf9cc8f8c
16 changed files with 137 additions and 133 deletions

View File

@ -1,8 +1,8 @@
---------------------------------- ----------------------------------
Simple variable assigment Simple variable assigment
---------------------------------- ----------------------------------
var x = 10; x := 10;
var y = 20; y := 20;
say x; say x;
say y; say y;
@ -17,7 +17,7 @@ say y;
---------------------------------- ----------------------------------
Array updates Array updates
---------------------------------- ----------------------------------
var array = flat 1 2 3; array := flat 1 2 3;
say array; say array;
array = update array 1 10; array = update array 1 10;

View File

@ -1,34 +1,34 @@
-- PAIR definition -- PAIR definition
var pair = [x y | [z | z x y]]; pair := [x y | [z | z x y]];
var car = [p | p [x y | x]]; car := [pair | pair [x y | x]];
var cdr = [p | p [x y | y]]; cdr := [pair | pair [x y | y]];
var x = pair 100 200; x := pair 100 200;
say (car x); say (car x);
say (cdr x); say (cdr x);
-- LIST definition -- LIST definition
var null = pair true true; null := pair true true;
var is_empty = car; is_empty := car;
var head = [list | car (cdr list)]; head := [list | car (cdr list)];
var tail = [list | cdr (cdr list)]; tail := [list | cdr (cdr list)];
var cons = [head tail | pair false (pair head tail)]; cons := [head tail | pair false (pair head tail)];
var for_each = [list iterator | for_each := [list iterator |
if (is_empty list) [ nil ] [ if (is_empty list) [ nil ] [
iterator (head list); iterator (head list);
for_each (tail list) iterator ]]; for_each (tail list) iterator ]];
var map = [list iterator | map := [list iterator |
if (is_empty list) [ null ] [| if (is_empty list) [ null ] [|
cons (iterator (head list)) (map (tail list) iterator) ]]; cons (iterator (head list)) (map (tail list) iterator) ]];
var foldr = [list init folder | foldr := [list init folder |
if (is_empty list) if (is_empty list)
[ init ] [ init ]
[ foldr (tail list) (folder (head list) init) folder ]]; [ 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]); say (foldr xs 1 [x y|x*y]);

View File

@ -1,4 +1,4 @@
var map = [fn array | var result = []; for array [v | result = result & [fn v] ]; result]; map := [fn array | var result = []; for array [v | result = result & [fn v] ]; result];
var drop = [n arr | arr.(range n (len arr))]; drop := [n arr | arr.(range n (len arr))];
var take = [n arr | arr.(up n)]; take := [n arr | arr.(up n)];
var filter = [predicate arr | arr.(map predicate arr)]; filter := [predicate arr | arr.(map predicate arr)];

View File

@ -1,14 +1,14 @@
var for = [ start stop iteration | for := [ start stop iteration |
if (start > stop) if (start > stop)
[nil] [nil]
[iteration start; for (start + 1) stop iteration] [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)]; for 1 10 [i | say (factorial i)];
var factorial_iterative = [n | factorial_iterative := [n |
var x = 1; x := 1;
for 1 n [i|x *= i]; for 1 n [i|x *= i];
x x
]; ];

View File

@ -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) ] ];

View File

@ -3,7 +3,7 @@
---------------------------------------------- ----------------------------------------------
oct 5; bpm 72; len (1/16); oct 5; bpm 72; len (1/16);
var subsection1 = [ n | subsection1 := [ n |
sim (a 4 en) (a 2 e 3 a 3); sim (a 4 en) (a 2 e 3 a 3);
play [oct 4; c e a]; play [oct 4; c e a];
@ -22,7 +22,7 @@ var subsection1 = [ n |
play (d 4 c 5 b 4); play (d 4 c 5 b 4);
]; ];
var section1 = [ n | section1 := [ n |
play (e d#); play (e d#);
play (e d# e b 4 d c); 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); sim (e 5 den) (c 3 g 3 c 4);
play (g 4 f e); play (g 4 f e);

View File

@ -1,5 +1,5 @@
var C = chord (c 3 1) (g 3 1) (c 4 1); C := chord (c 3 1) (g 3 1) (c 4 1);
var G = chord (g 3 1) (d 4 1) (g 4 1); G := chord (g 3 1) (d 4 1) (g 4 1);
oct 5; oct 5;
par C e e f g; par C e e f g;

View File

@ -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 | for_all_permutations := [array fun |
var iter = [start stop x | if (start >= stop) [x] [iter (start+1) stop (fun x)]]; iter := [start stop x | if (start >= stop) [x] [iter (start+1) stop (fun x)]];
iter 0 (factorial (len array)) array iter 0 (factorial (len array)) array
]; ];

View File

@ -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]; cmajor := [c;d;e;f;g];
var scale = reverse cmajor; scale := reverse cmajor;
var primes = nprimes (len scale); primes := nprimes (len scale);
var indicies = up (len scale); indicies := up (len scale);
oct 3; oct 3;
len (1/16); len (1/16);

View File

@ -1,2 +1,2 @@
var x = 10; x := 10;
say (x + 1) say (x + 1)

View File

@ -475,8 +475,8 @@ struct Token
Location location; Location location;
}; };
static constexpr usize Keywords_Count = 6; static constexpr usize Keywords_Count = 5;
static constexpr usize Operators_Count = 16; static constexpr usize Operators_Count = 17;
std::string_view type_name(Token::Type type); std::string_view type_name(Token::Type type);
@ -687,6 +687,9 @@ struct Parser
/// Tests if current token has given type and source /// Tests if current token has given type and source
bool expect(Token::Type type, std::string_view lexeme) const; 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 /// Number type supporting integer and fractional constants

View File

@ -136,6 +136,7 @@ namespace algo
/// Fold that stops iteration on error value via Result type /// Fold that stops iteration on error value via Result type
template<typename T> template<typename T>
constexpr Result<T> fold(auto&& range, T init, auto &&reducer) constexpr Result<T> fold(auto&& range, T init, auto &&reducer)
requires (is_template_v<Result, decltype(reducer(std::move(init), *range.begin()))>)
{ {
for (auto &&value : range) { for (auto &&value : range) {
init = Try(reducer(std::move(init), value)); init = Try(reducer(std::move(init), value));

View File

@ -1,10 +1,10 @@
#include <musique.hh> #include <musique.hh>
#include <musique_internal.hh> #include <musique_internal.hh>
#include <functional>
/// Intrinsic implementation primitive to ease operation vectorization /// Intrinsic implementation primitive to ease operation vectorization
Result<Value> vectorize(auto &&operation, Interpreter &interpreter, Value lhs, Value rhs) static Result<Value> vectorize(auto &&operation, Interpreter &interpreter, Value lhs, Value rhs)
{ {
Array array;
if (is_indexable(lhs.type) && !is_indexable(rhs.type)) { if (is_indexable(lhs.type) && !is_indexable(rhs.type)) {
Array array; Array array;
@ -15,6 +15,7 @@ Result<Value> vectorize(auto &&operation, Interpreter &interpreter, Value lhs, V
return Value::from(std::move(array)); return Value::from(std::move(array));
} }
Array array;
for (auto i = 0u; i < rhs.size(); ++i) { for (auto i = 0u; i < rhs.size(); ++i) {
array.elements.push_back( array.elements.push_back(
Try(operation(interpreter, { lhs, Try(rhs.index(interpreter, i)) }))); Try(operation(interpreter, { lhs, Try(rhs.index(interpreter, i)) })));
@ -24,19 +25,39 @@ Result<Value> vectorize(auto &&operation, Interpreter &interpreter, Value lhs, V
/// Intrinsic implementation primitive to ease operation vectorization /// Intrinsic implementation primitive to ease operation vectorization
/// @invariant args.size() == 2 /// @invariant args.size() == 2
Result<Value> vectorize(auto &&operation, Interpreter &interpreter, std::vector<Value> args) static Result<Value> vectorize(auto &&operation, Interpreter &interpreter, std::vector<Value> args)
{ {
assert(args.size() == 2, "Vectorization primitive only supports two arguments"); assert(args.size() == 2, "Vectorization primitive only supports two arguments");
return vectorize(std::move(operation), interpreter, std::move(args.front()), std::move(args.back())); return vectorize(std::move(operation), interpreter, std::move(args.front()), std::move(args.back()));
} }
/// Helper simlifiing implementation of symetric binary operations.
std::optional<Value> symetric(Value::Type t1, Value::Type t2, Value &lhs, Value &rhs, auto binary_predicate) ///
/// Calls binary if values matches types any permutation of {t1, t2}, always in shape (t1, t2)
inline std::optional<Value> symetric(Value::Type t1, Value::Type t2, Value &lhs, Value &rhs, auto binary)
{ {
if (lhs.type == t1 && rhs.type == t2) { 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) { } 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<Value::Type> auto&& p1,
std::predicate<Value::Type> auto&& p2,
Value &lhs, Value &rhs,
auto binary) -> std::optional<decltype(binary(std::move(lhs), std::move(rhs)))>
{
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 { } else {
return std::nullopt; return std::nullopt;
} }
@ -49,61 +70,49 @@ std::optional<Value> symetric(Value::Type t1, Value::Type t2, Value &lhs, Value
template<typename Binary_Operation> template<typename Binary_Operation>
static Result<Value> plus_minus_operator(Interpreter &interpreter, std::vector<Value> args) static Result<Value> plus_minus_operator(Interpreter &interpreter, std::vector<Value> args)
{ {
static_assert(std::is_same_v<Binary_Operation, std::plus<>> || std::is_same_v<Binary_Operation, std::minus<>>, if (args.empty()) {
"Error reporting depends on only one of this two types beeing provided"); return Value::from(Number(0));
using NN = Shape<Value::Type::Number, Value::Type::Number>;
using MN = Shape<Value::Type::Music, Value::Type::Number>;
using NM = Shape<Value::Type::Number, Value::Type::Music>;
if (NN::typecheck(args)) {
auto [a, b] = NN::move_from(args);
return Value::from(Binary_Operation{}(std::move(a), std::move(b)));
} }
if (MN::typecheck(args)) { Value init = args.front();
auto [chord, offset] = MN::move_from(args); return algo::fold(std::span(args).subspan(1), std::move(init), [&interpreter](Value lhs, Value &rhs) -> Result<Value> {
for (auto &note : chord.notes) { if (lhs.type == Value::Type::Number && rhs.type == Value::Type::Number) {
if (note.base) { return Value::from(Binary_Operation{}(std::move(lhs).n, std::move(rhs).n));
*note.base = Binary_Operation{}(*note.base, offset.as_int());
note.simplify_inplace();
}
} }
return Value::from(std::move(chord));
}
if (NM::typecheck(args)) { auto result = symetric(Value::Type::Music, Value::Type::Number, lhs, rhs, [](Value lhs, Value rhs) {
auto [offset, chord] = NM::move_from(args); for (auto &note : lhs.chord.notes) {
for (auto &note : chord.notes) { if (note.base) {
if (note.base) { *note.base = Binary_Operation{}(*note.base, rhs.n.as_int());
*note.base = Binary_Operation{}(*note.base, offset.as_int()); note.simplify_inplace();
note.simplify_inplace(); }
} }
return lhs;
});
if (result.has_value()) {
return *std::move(result);
} }
return Value::from(std::move(chord));
}
if (may_be_vectorized(args)) { if (is_indexable(lhs.type) != is_indexable(rhs.type)) {
return vectorize(plus_minus_operator<Binary_Operation>, interpreter, std::move(args)); return vectorize(plus_minus_operator<Binary_Operation>, interpreter, std::move(lhs), std::move(rhs));
} }
// TODO Limit possibilities based on provided types static_assert(std::is_same_v<std::plus<>, Binary_Operation> || std::is_same_v<std::minus<>, Binary_Operation>,
static_assert(std::is_same_v<std::plus<>, Binary_Operation> || std::is_same_v<std::minus<>, Binary_Operation>, "Error message printing only supports operators given above");
"Error message printing only supports operators given above"); return Error {
return Error { .details = errors::Unsupported_Types_For {
.details = errors::Unsupported_Types_For { .type = errors::Unsupported_Types_For::Operator,
.type = errors::Unsupported_Types_For::Operator, .name = std::is_same_v<std::plus<>, Binary_Operation> ? "+" : "-",
.name = std::is_same_v<std::plus<>, Binary_Operation> ? "+" : "-", .possibilities = {
.possibilities = { "(number, number) -> number",
"(number, number) -> number", "(music, number) -> music",
"(music, number) -> music", "(number, music) -> music",
"(number, music) -> music", "(array, number|music) -> array",
"(array, number|music) -> array", "(number|music, array) -> array",
"(number|music, array) -> array", }
} }
}, };
.location = {}, // TODO fill location });
};
} }
template<typename Binary_Operation, char ...Chars> template<typename Binary_Operation, char ...Chars>
@ -144,26 +153,24 @@ static Result<Value> comparison_operator(Interpreter &interpreter, std::vector<V
return Value::from(algo::pairwise_all(std::move(args), Binary_Predicate{})); return Value::from(algo::pairwise_all(std::move(args), Binary_Predicate{}));
} }
if (is_indexable(args[1].type) && !is_indexable(args[0].type)) { auto result = symetric(is_indexable, std::not_fn(is_indexable), args.front(), args.back(), [&interpreter](Value lhs, Value rhs) -> Result<Value> {
std::swap(args[1], args[0]);
goto vectorized;
}
if (is_indexable(args[0].type) && !is_indexable(args[1].type)) {
vectorized:
std::vector<Value> result; std::vector<Value> result;
result.reserve(args[0].size()); result.reserve(lhs.size());
for (auto i = 0u; i < args[0].size(); ++i) { for (auto i = 0u; i < lhs.size(); ++i) {
result.push_back( result.push_back(
Value::from( Value::from(
Binary_Predicate{}( Binary_Predicate{}(
Try(args[0].index(interpreter, i)), Try(lhs.index(interpreter, i)),
args[1] rhs
) )
) )
); );
} }
return Value::from(Array { std::move(result) }); 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()))); 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 // 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 // and are need unevaluated expressions for their proper evaluation. Exclusion of them is marked
// as subtraction of total excluded operators from expected constant // 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() void Interpreter::register_builtin_operators()
{ {

View File

@ -15,7 +15,6 @@ constexpr auto Keywords = std::array {
"false"sv, "false"sv,
"nil"sv, "nil"sv,
"true"sv, "true"sv,
"var"sv,
"and"sv, "and"sv,
"or"sv "or"sv
}; };

View File

@ -26,14 +26,14 @@ constexpr auto Literal_Keywords = std::array {
"true"sv, "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 { constexpr auto Operator_Keywords = std::array {
"and"sv, "and"sv,
"or"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 enum class At_Least : bool
{ {
@ -67,13 +67,6 @@ Result<Ast> Parser::parse(std::string_view source, std::string_view filename, un
auto const result = parser.parse_sequence(); auto const result = parser.parse_sequence();
if (result.has_value() && parser.token_id < parser.tokens.size()) { 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)) { if (parser.expect(Token::Type::Close_Paren) || parser.expect(Token::Type::Close_Block)) {
auto const tok = parser.consume(); auto const tok = parser.consume();
return Error { return Error {
@ -100,7 +93,7 @@ Result<Ast> Parser::parse_sequence()
Result<Ast> Parser::parse_expression() Result<Ast> Parser::parse_expression()
{ {
if (expect(Token::Type::Keyword, "var")) { if (expect(Token::Type::Symbol, Token::Type::Operator, ":=")) {
return parse_variable_declaration(); return parse_variable_declaration();
} }
return parse_infix_expression(); return parse_infix_expression();
@ -108,10 +101,7 @@ Result<Ast> Parser::parse_expression()
Result<Ast> Parser::parse_variable_declaration() Result<Ast> 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 lvalue = parse_identifier();
auto var = consume();
auto lvalue = parse_many(*this, &Parser::parse_identifier, std::nullopt, At_Least::One);
if (not lvalue.has_value()) { if (not lvalue.has_value()) {
auto details = lvalue.error().details; auto details = lvalue.error().details;
if (auto ut = std::get_if<errors::internal::Unexpected_Token>(&details); ut) { if (auto ut = std::get_if<errors::internal::Unexpected_Token>(&details); ut) {
@ -127,13 +117,9 @@ Result<Ast> Parser::parse_variable_declaration()
return std::move(lvalue).error(); return std::move(lvalue).error();
} }
assert(expect(Token::Type::Operator, ":="), "This function should always be called with valid sequence");
if (expect(Token::Type::Operator, "=")) { consume();
consume(); return Ast::variable_declaration(lvalue->location, { *std::move(lvalue) }, Try(parse_expression()));
return Ast::variable_declaration(var.location, *std::move(lvalue), Try(parse_expression()));
}
return Ast::variable_declaration(var.location, *std::move(lvalue), std::nullopt);
} }
Result<Ast> Parser::parse_infix_expression() Result<Ast> 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; 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. // 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. // This may create unexpected performance degradation during program evaluation.
Ast Ast::literal(Token token) 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 // '.' 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. // 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, "or")) return 100;
if (one_of(op, "and")) return 150; if (one_of(op, "and")) return 150;
if (one_of(op, "<", ">", "<=", ">=", "==", "!=")) return 200; if (one_of(op, "<", ">", "<=", ">=", "==", "!=")) return 200;

View File

@ -1,6 +1,7 @@
#include <musique.hh> #include <musique.hh>
#include <musique_internal.hh> #include <musique_internal.hh>
#include <iostream>
#include <numeric> #include <numeric>
/// Finds numeric value of note. This form is later used as in /// Finds numeric value of note. This form is later used as in
@ -21,8 +22,6 @@ constexpr u8 note_index(u8 note)
unreachable(); unreachable();
} }
#include <iostream>
constexpr std::string_view note_index_to_string(int note_index) constexpr std::string_view note_index_to_string(int note_index)
{ {
note_index %= 12; note_index %= 12;