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
----------------------------------
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;

View File

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

View File

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

View File

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

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

View File

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

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

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

View File

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

View File

@ -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

View File

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

View File

@ -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,61 +70,49 @@ 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 &note : 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<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));
}
return Value::from(std::move(chord));
}
if (NM::typecheck(args)) {
auto [offset, chord] = NM::move_from(args);
for (auto &note : 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 &note : 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<Binary_Operation>, interpreter, std::move(args));
}
if (is_indexable(lhs.type) != is_indexable(rhs.type)) {
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>,
"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<std::plus<>, 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<std::plus<>, Binary_Operation> || std::is_same_v<std::minus<>, 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<std::plus<>, 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<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()
{

View File

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

View File

@ -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, "=")) {
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<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;

View File

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