New declaration syntax x := foo
instead of var x = foo
Additionaly moving into multiparameter binary operations
This commit is contained in:
parent
e57a60c36f
commit
ddf9cc8f8c
@ -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;
|
||||
|
@ -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]);
|
||||
|
@ -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)];
|
||||
|
@ -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
|
||||
];
|
||||
|
@ -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) ] ];
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
];
|
||||
|
||||
|
@ -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);
|
||||
|
@ -1,2 +1,2 @@
|
||||
var x = 10;
|
||||
x := 10;
|
||||
say (x + 1)
|
||||
|
@ -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
|
||||
|
@ -136,6 +136,7 @@ namespace algo
|
||||
/// Fold that stops iteration on error value via Result type
|
||||
template<typename T>
|
||||
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) {
|
||||
init = Try(reducer(std::move(init), value));
|
||||
|
@ -1,10 +1,10 @@
|
||||
#include <musique.hh>
|
||||
#include <musique_internal.hh>
|
||||
#include <functional>
|
||||
|
||||
/// 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)) {
|
||||
Array array;
|
||||
@ -15,6 +15,7 @@ Result<Value> 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<Value> vectorize(auto &&operation, Interpreter &interpreter, Value lhs, V
|
||||
|
||||
/// Intrinsic implementation primitive to ease operation vectorization
|
||||
/// @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");
|
||||
return vectorize(std::move(operation), interpreter, std::move(args.front()), std::move(args.back()));
|
||||
}
|
||||
|
||||
|
||||
std::optional<Value> 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<Value> 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<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 {
|
||||
return std::nullopt;
|
||||
}
|
||||
@ -49,45 +70,33 @@ std::optional<Value> symetric(Value::Type t1, Value::Type t2, Value &lhs, Value
|
||||
template<typename Binary_Operation>
|
||||
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<>>,
|
||||
"Error reporting depends on only one of this two types beeing provided");
|
||||
|
||||
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 (args.empty()) {
|
||||
return Value::from(Number(0));
|
||||
}
|
||||
|
||||
if (MN::typecheck(args)) {
|
||||
auto [chord, offset] = MN::move_from(args);
|
||||
for (auto ¬e : chord.notes) {
|
||||
Value init = args.front();
|
||||
return algo::fold(std::span(args).subspan(1), std::move(init), [&interpreter](Value lhs, Value &rhs) -> Result<Value> {
|
||||
if (lhs.type == Value::Type::Number && rhs.type == Value::Type::Number) {
|
||||
return Value::from(Binary_Operation{}(std::move(lhs).n, std::move(rhs).n));
|
||||
}
|
||||
|
||||
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, offset.as_int());
|
||||
*note.base = Binary_Operation{}(*note.base, rhs.n.as_int());
|
||||
note.simplify_inplace();
|
||||
}
|
||||
}
|
||||
return Value::from(std::move(chord));
|
||||
return lhs;
|
||||
});
|
||||
if (result.has_value()) {
|
||||
return *std::move(result);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
return Value::from(std::move(chord));
|
||||
if (is_indexable(lhs.type) != is_indexable(rhs.type)) {
|
||||
return vectorize(plus_minus_operator<Binary_Operation>, interpreter, std::move(lhs), std::move(rhs));
|
||||
}
|
||||
|
||||
if (may_be_vectorized(args)) {
|
||||
return vectorize(plus_minus_operator<Binary_Operation>, interpreter, std::move(args));
|
||||
}
|
||||
|
||||
// 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>,
|
||||
"Error message printing only supports operators given above");
|
||||
return Error {
|
||||
@ -101,9 +110,9 @@ static Result<Value> plus_minus_operator(Interpreter &interpreter, std::vector<V
|
||||
"(array, number|music) -> array",
|
||||
"(number|music, array) -> array",
|
||||
}
|
||||
},
|
||||
.location = {}, // TODO fill location
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
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{}));
|
||||
}
|
||||
|
||||
if (is_indexable(args[1].type) && !is_indexable(args[0].type)) {
|
||||
std::swap(args[1], args[0]);
|
||||
goto vectorized;
|
||||
}
|
||||
|
||||
if (is_indexable(args[0].type) && !is_indexable(args[1].type)) {
|
||||
vectorized:
|
||||
auto result = symetric(is_indexable, std::not_fn(is_indexable), args.front(), args.back(), [&interpreter](Value lhs, Value rhs) -> Result<Value> {
|
||||
std::vector<Value> 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()
|
||||
{
|
||||
|
@ -15,7 +15,6 @@ constexpr auto Keywords = std::array {
|
||||
"false"sv,
|
||||
"nil"sv,
|
||||
"true"sv,
|
||||
"var"sv,
|
||||
"and"sv,
|
||||
"or"sv
|
||||
};
|
||||
|
@ -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<Ast> 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<Ast> Parser::parse_sequence()
|
||||
|
||||
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_infix_expression();
|
||||
@ -108,10 +101,7 @@ Result<Ast> Parser::parse_expression()
|
||||
|
||||
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 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<errors::internal::Unexpected_Token>(&details); ut) {
|
||||
@ -127,13 +117,9 @@ Result<Ast> Parser::parse_variable_declaration()
|
||||
return std::move(lvalue).error();
|
||||
}
|
||||
|
||||
|
||||
if (expect(Token::Type::Operator, "=")) {
|
||||
assert(expect(Token::Type::Operator, ":="), "This function should always be called with valid sequence");
|
||||
consume();
|
||||
return Ast::variable_declaration(var.location, *std::move(lvalue), Try(parse_expression()));
|
||||
}
|
||||
|
||||
return Ast::variable_declaration(var.location, *std::move(lvalue), std::nullopt);
|
||||
return Ast::variable_declaration(lvalue->location, { *std::move(lvalue) }, Try(parse_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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include <musique.hh>
|
||||
#include <musique_internal.hh>
|
||||
|
||||
#include <iostream>
|
||||
#include <numeric>
|
||||
|
||||
/// Finds numeric value of note. This form is later used as in
|
||||
@ -21,8 +22,6 @@ constexpr u8 note_index(u8 note)
|
||||
unreachable();
|
||||
}
|
||||
|
||||
#include <iostream>
|
||||
|
||||
constexpr std::string_view note_index_to_string(int note_index)
|
||||
{
|
||||
note_index %= 12;
|
||||
|
Loading…
Reference in New Issue
Block a user