Introduced concurrent block notation

Additionally removed wierd behaviour with Interpreter::play where empty
chords were played as default length. Don't know why this was introduced
This commit is contained in:
Robert Bendun 2022-11-24 02:08:34 +01:00
parent c509b6ccc5
commit 532727b7d1
10 changed files with 146 additions and 81 deletions

View File

@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Added
- `{}` notation for concurrently executing blocks
### Fixed ### Fixed
- release build script was producing executable with wrong path - release build script was producing executable with wrong path

View File

@ -7,8 +7,8 @@ hand1_pool := (
hand2_pool := (d8, d#8, g8, g#8, d9, d#9), hand2_pool := (d8, d#8, g8, g#8, d9, d#9),
concurrent play {
(while true (play ((pick hand1_pool) (pick hn dhn)))) while true ((pick hand1_pool) (pick hn dhn)),
(while true (play ((pick hand2_pool) (1/64)))) while true ((pick hand2_pool) (1/64))
}

View File

@ -242,27 +242,27 @@ static auto builtin_program_change(Interpreter &i, std::vector<Value> args) -> R
/// Plays sequentialy notes walking into arrays and evaluation blocks /// Plays sequentialy notes walking into arrays and evaluation blocks
/// ///
/// @invariant default_action is play one /// @invariant default_action is play one
static inline std::optional<Error> sequential_play(Interpreter &i, Value v) static inline std::optional<Error> sequential_play(Interpreter &interpreter, Value v)
{ {
if (auto array = get_if<Array>(v)) { if (auto array = get_if<Array>(v)) {
for (auto &el : array->elements) { for (auto &el : array->elements) {
Try(sequential_play(i, std::move(el))); Try(sequential_play(interpreter, std::move(el)));
} }
} }
else if (auto block = get_if<Block>(v)) { else if (auto block = get_if<Block>(v)) {
Try(sequential_play(i, Try(i.eval(std::move(block->body))))); Try(sequential_play(interpreter, Try(interpreter.eval(std::move(block->body)))));
} }
else if (auto chord = get_if<Chord>(v)) { else if (auto chord = get_if<Chord>(v)) {
return i.play(*chord); return interpreter.play(*chord);
} }
return {}; return {};
} }
/// Play what's given /// Play what's given
static std::optional<Error> action_play(Interpreter &i, Value v) static std::optional<Error> action_play(Interpreter &interpreter, Value v)
{ {
Try(sequential_play(i, std::move(v))); Try(sequential_play(interpreter, std::move(v)));
return {}; return {};
} }
@ -602,38 +602,6 @@ static Result<Value> builtin_scan(Interpreter &interpreter, std::vector<Value> a
}; };
} }
static Result<Value> builtin_concurrent(Interpreter &interpreter, std::span<Ast> args)
{
auto const jobs_count = args.size();
std::vector<std::future<Value>> futures;
std::optional<Error> error;
std::mutex mutex;
for (unsigned i = 0; i < jobs_count; ++i) {
futures.push_back(std::async(std::launch::async, [interpreter = interpreter.clone(), i, args, &mutex, &error]() mutable -> Value {
auto result = interpreter.eval((Ast)args[i]);
if (result) {
return *std::move(result);
}
std::lock_guard guard{mutex};
if (!error) {
error = result.error();
}
return Value{};
}));
}
std::vector<Value> results;
for (auto& future : futures) {
if (error) {
return *error;
}
results.push_back(future.get());
}
return results;
}
/// Execute blocks depending on condition /// Execute blocks depending on condition
static Result<Value> builtin_if(Interpreter &i, std::span<Ast> args) { static Result<Value> builtin_if(Interpreter &i, std::span<Ast> args) {
static constexpr auto guard = Guard<2> { static constexpr auto guard = Guard<2> {
@ -666,7 +634,7 @@ static Result<Value> builtin_if(Interpreter &i, std::span<Ast> args) {
} }
/// Loop block depending on condition /// Loop block depending on condition
static Result<Value> builtin_while(Interpreter &i, std::span<Ast> args) { static Result<Value> builtin_while(Interpreter &interpreter, std::span<Ast> args) {
static constexpr auto guard = Guard<2> { static constexpr auto guard = Guard<2> {
.name = "while", .name = "while",
.possibilities = { .possibilities = {
@ -678,11 +646,16 @@ static Result<Value> builtin_while(Interpreter &i, std::span<Ast> args) {
return guard.yield_error(); return guard.yield_error();
} }
while (Try(i.eval((Ast)args.front())).truthy()) { while (Try(interpreter.eval((Ast)args.front())).truthy()) {
Value result;
if (args[1].type == Ast::Type::Block) { if (args[1].type == Ast::Type::Block) {
Try(i.eval((Ast)args[1].arguments.front())); result = Try(interpreter.eval((Ast)args[1].arguments.front()));
} else { } else {
Try(i.eval((Ast)args[1])); result = Try(interpreter.eval((Ast)args[1]));
}
if (interpreter.default_action) {
Try(interpreter.default_action(interpreter, std::move(result)));
} }
} }
return Value{}; return Value{};
@ -1151,7 +1124,6 @@ void Interpreter::register_builtin_functions()
global.force_define("call", builtin_call); global.force_define("call", builtin_call);
global.force_define("ceil", apply_numeric_transform<&Number::ceil>); global.force_define("ceil", apply_numeric_transform<&Number::ceil>);
global.force_define("chord", builtin_chord); global.force_define("chord", builtin_chord);
global.force_define("concurrent", builtin_concurrent);
global.force_define("down", builtin_range<Range_Direction::Down>); global.force_define("down", builtin_range<Range_Direction::Down>);
global.force_define("duration", builtin_duration); global.force_define("duration", builtin_duration);
global.force_define("flat", builtin_flat); global.force_define("flat", builtin_flat);

View File

@ -6,6 +6,8 @@
#include <iostream> #include <iostream>
#include <random> #include <random>
#include <thread> #include <thread>
#include <future>
#include <mutex>
midi::Connection *Interpreter::midi_connection = nullptr; midi::Connection *Interpreter::midi_connection = nullptr;
std::unordered_map<std::string, Intrinsic> Interpreter::operators {}; std::unordered_map<std::string, Intrinsic> Interpreter::operators {};
@ -61,6 +63,50 @@ Interpreter::Interpreter(Interpreter::Clone)
{ {
} }
static Result<Value> eval_concurrent(Interpreter &interpreter, Ast &&ast)
{
ensure(ast.type == Ast::Type::Concurrent, "Only conccurent AST nodes can be evaluated in eval_concurrent");
std::span<Ast> jobs = ast.arguments;
std::vector<std::future<Value>> futures;
std::optional<Error> error;
std::mutex mutex;
for (unsigned i = 0; i < jobs.size(); ++i) {
futures.push_back(std::async(std::launch::async, [interpreter = interpreter.clone(), i, jobs, &mutex, &error]() mutable -> Value {
auto result = interpreter.eval(std::move(jobs[i]));
if (result) {
if (interpreter.default_action) {
// TODO Code duplication between this section and last section in this lambda function
if (auto produced_error = interpreter.default_action(interpreter, *std::move(result))) {
std::lock_guard guard{mutex};
if (!error) {
error = produced_error;
}
return Value{};
}
}
return *std::move(result);
}
std::lock_guard guard{mutex};
if (!error) {
error = result.error();
}
return Value{};
}));
}
std::vector<Value> results;
for (auto& future : futures) {
if (error) {
return *error;
}
results.push_back(future.get());
}
return results;
}
Result<Value> Interpreter::eval(Ast &&ast) Result<Value> Interpreter::eval(Ast &&ast)
{ {
switch (ast.type) { switch (ast.type) {
@ -163,6 +209,9 @@ Result<Value> Interpreter::eval(Ast &&ast)
} }
break; break;
case Ast::Type::Concurrent:
return eval_concurrent(*this, std::move(ast));
case Ast::Type::Sequence: case Ast::Type::Sequence:
{ {
Value v; Value v;
@ -243,7 +292,6 @@ std::optional<Error> Interpreter::play(Chord chord)
auto &ctx = *current_context; auto &ctx = *current_context;
if (chord.notes.size() == 0) { if (chord.notes.size() == 0) {
std::this_thread::sleep_for(ctx.length_to_duration(ctx.length));
return {}; return {};
} }
@ -300,7 +348,9 @@ static void snapshot(std::ostream& out, Note const& note) {
static void snapshot(std::ostream &out, Ast const& ast) { static void snapshot(std::ostream &out, Ast const& ast) {
switch (ast.type) { switch (ast.type) {
break; case Ast::Type::Sequence: break;
case Ast::Type::Sequence:
case Ast::Type::Concurrent:
{ {
for (auto const& a : ast.arguments) { for (auto const& a : ast.arguments) {
snapshot(out, a); snapshot(out, a);
@ -308,7 +358,10 @@ static void snapshot(std::ostream &out, Ast const& ast) {
} }
} }
break; case Ast::Type::Block: break; case Ast::Type::Block:
// TODO
ensure(ast.arguments.size() == 1, "Block can contain only one node which contains its body"); ensure(ast.arguments.size() == 1, "Block can contain only one node which contains its body");
if (ast.arguments.front().type == Ast::Type::Concurrent)
unimplemented("Concurrent code snapshoting is not supported yet");
out << "("; out << "(";
snapshot(out, ast.arguments.front()); snapshot(out, ast.arguments.front());
out << ")"; out << ")";

View File

@ -88,8 +88,10 @@ auto Lexer::next_token() -> Result<std::variant<Token, End_Of_File>>
} }
switch (peek()) { switch (peek()) {
case '(': consume(); return Token { Token::Type::Open_Block, finish(), token_location }; case '(': consume(); return Token { Token::Type::Open_Sequential, finish(), token_location };
case ')': consume(); return Token { Token::Type::Close_Block, finish(), token_location }; case ')': consume(); return Token { Token::Type::Close_Sequential, finish(), token_location };
case '{': consume(); return Token { Token::Type::Open_Concurrent, finish(), token_location };
case '}': consume(); return Token { Token::Type::Close_Concurrent, finish(), token_location };
case '[': consume(); return Token { Token::Type::Open_Index, 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::Close_Index, finish(), token_location };
case ',': consume(); return Token { Token::Type::Expression_Separator, finish(), token_location }; case ',': consume(); return Token { Token::Type::Expression_Separator, finish(), token_location };
@ -257,16 +259,18 @@ std::ostream& operator<<(std::ostream& os, Token::Type type)
{ {
switch (type) { switch (type) {
case Token::Type::Chord: return os << "CHORD"; case Token::Type::Chord: return os << "CHORD";
case Token::Type::Close_Block: return os << "CLOSE BLOCK"; case Token::Type::Close_Concurrent: return os << "CLOSE CONCURRENT";
case Token::Type::Close_Index: return os << "CLOSE INDEX";
case Token::Type::Close_Sequential: return os << "CLOSE SEQUENTIAL";
case Token::Type::Expression_Separator: return os << "EXPRESSION SEPARATOR"; case Token::Type::Expression_Separator: return os << "EXPRESSION SEPARATOR";
case Token::Type::Keyword: return os << "KEYWORD"; case Token::Type::Keyword: return os << "KEYWORD";
case Token::Type::Numeric: return os << "NUMERIC"; case Token::Type::Numeric: return os << "NUMERIC";
case Token::Type::Open_Block: return os << "OPEN BLOCK"; case Token::Type::Open_Concurrent: return os << "OPEN CONCURRENT";
case Token::Type::Open_Index: return os << "OPEN INDEX";
case Token::Type::Open_Sequential: return os << "OPEN SEQUENTIAL";
case Token::Type::Operator: return os << "OPERATOR"; case Token::Type::Operator: return os << "OPERATOR";
case Token::Type::Parameter_Separator: return os << "PARAMETER SEPARATOR"; case Token::Type::Parameter_Separator: return os << "PARAMETER SEPARATOR";
case Token::Type::Symbol: return os << "SYMBOL"; 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(); unreachable();
} }
@ -275,13 +279,15 @@ std::string_view type_name(Token::Type type)
{ {
switch (type) { switch (type) {
case Token::Type::Chord: return "chord"; case Token::Type::Chord: return "chord";
case Token::Type::Close_Block: return ")"; case Token::Type::Close_Concurrent: return "}";
case Token::Type::Close_Index: return "]"; case Token::Type::Close_Index: return "]";
case Token::Type::Close_Sequential: return ")";
case Token::Type::Expression_Separator: return "|"; case Token::Type::Expression_Separator: return "|";
case Token::Type::Keyword: return "keyword"; case Token::Type::Keyword: return "keyword";
case Token::Type::Numeric: return "numeric"; case Token::Type::Numeric: return "numeric";
case Token::Type::Open_Block: return "("; case Token::Type::Open_Concurrent: return "{";
case Token::Type::Open_Index: return "["; case Token::Type::Open_Index: return "[";
case Token::Type::Open_Sequential: return "(";
case Token::Type::Operator: return "operator"; case Token::Type::Operator: return "operator";
case Token::Type::Parameter_Separator: return "parameter separator"; case Token::Type::Parameter_Separator: return "parameter separator";
case Token::Type::Symbol: return "symbol"; case Token::Type::Symbol: return "symbol";

View File

@ -17,10 +17,12 @@ struct Token
Numeric, ///< numeric literal (floating point or integer) Numeric, ///< numeric literal (floating point or integer)
Parameter_Separator, ///< "|" separaters arguments from block body Parameter_Separator, ///< "|" separaters arguments from block body
Expression_Separator, ///< "," separates expressions. Used mainly to separate calls, like `foo 1 2; bar 3 4` 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 Open_Index, ///< "[" starts index section of index expression
Close_Index ///< "]" ends index section of index expression Close_Index, ///< "]" ends index section of index expression
Open_Sequential, ///< "(" starts anonymous sequential block of code (potentially a function)
Close_Sequential, ///< ")" ends anonymous sequential block of code (potentially a function)
Open_Concurrent, ///< "{" starts anonymous concurrent block
Close_Concurrent, ///< "}" ends anonymous concurrent block
}; };
/// Type of token /// Type of token

View File

@ -12,19 +12,22 @@ struct Ast
static Ast binary(Token, Ast lhs, Ast rhs); static Ast binary(Token, Ast lhs, Ast rhs);
/// Constructs block /// Constructs block
static Ast block(Location location, Ast seq = sequence({})); static Ast block(Location location, Ast seq);
/// Constructs call expression /// Constructs call expression
static Ast call(std::vector<Ast> call); static Ast call(std::vector<Ast> call);
/// Constructs block with parameters /// Constructs block with parameters
static Ast lambda(Location location, Ast seq = sequence({}), std::vector<Ast> parameters = {}); static Ast lambda(Location location, Ast body = sequential({}), std::vector<Ast> parameters = {});
/// Constructs constants, literals and variable identifiers /// Constructs constants, literals and variable identifiers
static Ast literal(Token); static Ast literal(Token);
/// Constructs sequence of operations /// Constructs sequence of operations
static Ast sequence(std::vector<Ast> call); static Ast sequential(std::vector<Ast> call);
/// Constructs concurrent collection of operations
static Ast concurrent(std::vector<Ast> ops);
/// Constructs variable declaration /// Constructs variable declaration
static Ast variable_declaration(Location loc, std::vector<Ast> lvalues, std::optional<Ast> rvalue); static Ast variable_declaration(Location loc, std::vector<Ast> lvalues, std::optional<Ast> rvalue);
@ -38,6 +41,7 @@ struct Ast
Call, ///< Function call application like `print 42` Call, ///< Function call application like `print 42`
Literal, ///< Compile time known constant like `c` or `1` Literal, ///< Compile time known constant like `c` or `1`
Sequence, ///< Several expressions sequences like `42`, `42; 32` Sequence, ///< Several expressions sequences like `42`, `42; 32`
Concurrent, ///< Conccurrent collection of expressions like inside {} block
Variable_Declaration, ///< Declaration of a variable with optional value assigment like `var x = 10` or `var y` Variable_Declaration, ///< Declaration of a variable with optional value assigment like `var x = 10` or `var y`
}; };

View File

@ -65,10 +65,11 @@ 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(true);
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::Close_Block)) { // FIXME There should be also check for closing index ] and closing concurrent block }
if (parser.expect(Token::Type::Close_Sequential)) {
auto const tok = parser.consume(); auto const tok = parser.consume();
return Error { return Error {
.details = errors::Closing_Token_Without_Opening { .details = errors::Closing_Token_Without_Opening {
@ -84,10 +85,10 @@ Result<Ast> Parser::parse(std::string_view source, std::string_view filename, un
return result; return result;
} }
Result<Ast> Parser::parse_sequence() Result<Ast> Parser::parse_sequence(bool is_sequential)
{ {
auto seq = Try(parse_many(*this, &Parser::parse_expression, Token::Type::Expression_Separator, At_Least::Zero)); auto seq = Try(parse_many(*this, &Parser::parse_expression, Token::Type::Expression_Separator, At_Least::Zero));
return Ast::sequence(std::move(seq)); return is_sequential ? Ast::sequential(std::move(seq)) : Ast::concurrent(std::move(seq));
} }
Result<Ast> Parser::parse_expression() Result<Ast> Parser::parse_expression()
@ -185,10 +186,11 @@ Result<Ast> parse_sequence_inside(
Location start_location, Location start_location,
bool is_lambda, bool is_lambda,
std::vector<Ast> &&parameters, std::vector<Ast> &&parameters,
auto &&dont_arrived_at_closing_token auto &&dont_arrived_at_closing_token,
bool is_sequential
) )
{ {
auto ast = Try(parser.parse_sequence()); auto ast = Try(parser.parse_sequence(is_sequential));
if (not parser.expect(closing_token)) { if (not parser.expect(closing_token)) {
Try(dont_arrived_at_closing_token()); Try(dont_arrived_at_closing_token());
return Error { return Error {
@ -206,7 +208,7 @@ Result<Ast> parse_sequence_inside(
return Ast::lambda(start_location, std::move(ast), std::move(parameters)); 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"); ensure(ast.type == Ast::Type::Sequence || ast.type == Ast::Type::Concurrent, "I dunno if this is a valid assumption tbh");
if (ast.arguments.size() == 1) { if (ast.arguments.size() == 1) {
return std::move(ast.arguments.front()); return std::move(ast.arguments.front());
} }
@ -231,7 +233,8 @@ Result<Ast> Parser::parse_index_expression()
{}, {},
[]() -> std::optional<Error> { []() -> std::optional<Error> {
return std::nullopt; return std::nullopt;
} },
true
)) ))
); );
} }
@ -258,12 +261,16 @@ Result<Ast> Parser::parse_atomic_expression()
case Token::Type::Symbol: case Token::Type::Symbol:
return Ast::literal(consume()); return Ast::literal(consume());
case Token::Type::Open_Block: case Token::Type::Open_Sequential:
case Token::Type::Open_Concurrent:
{ {
auto opening = consume(); auto const opening = consume();
if (expect(Token::Type::Close_Block)) { bool const is_sequential = opening.type == Token::Type::Open_Sequential;
auto const closing_token_type = is_sequential ? Token::Type::Close_Sequential : Token::Type::Close_Concurrent;
if (expect(closing_token_type)) {
consume(); consume();
return Ast::block(std::move(opening).location); return Ast::block(std::move(opening).location, is_sequential ? Ast::sequential({}) : Ast::concurrent({}));
} }
auto start = token_id; auto start = token_id;
@ -286,7 +293,7 @@ Result<Ast> Parser::parse_atomic_expression()
return parse_sequence_inside( return parse_sequence_inside(
*this, *this,
Token::Type::Close_Block, closing_token_type,
opening.location, opening.location,
is_lambda, is_lambda,
std::move(parameters), std::move(parameters),
@ -321,7 +328,8 @@ Result<Ast> Parser::parse_atomic_expression()
}; };
} }
return std::nullopt; return std::nullopt;
} },
is_sequential
); );
} }
@ -490,7 +498,7 @@ Ast Ast::call(std::vector<Ast> call)
return ast; return ast;
} }
Ast Ast::sequence(std::vector<Ast> expressions) Ast Ast::sequential(std::vector<Ast> expressions)
{ {
Ast ast; Ast ast;
ast.type = Type::Sequence; ast.type = Type::Sequence;
@ -501,6 +509,17 @@ Ast Ast::sequence(std::vector<Ast> expressions)
return ast; return ast;
} }
Ast Ast::concurrent(std::vector<Ast> expressions)
{
Ast ast;
ast.type = Type::Concurrent;
if (!expressions.empty()) {
ast.location = expressions.front().location;
ast.arguments = std::move(expressions);
}
return ast;
}
Ast Ast::block(Location location, Ast seq) Ast Ast::block(Location location, Ast seq)
{ {
Ast ast; Ast ast;
@ -586,6 +605,10 @@ bool operator==(Ast const& lhs, Ast const& rhs)
case Ast::Type::Lambda: case Ast::Type::Lambda:
case Ast::Type::Sequence: case Ast::Type::Sequence:
case Ast::Type::Variable_Declaration: case Ast::Type::Variable_Declaration:
// TODO Maybe concurrent blocks should resolve duplicates at AST level, and not execution level?
// Statements { play c, play c } should be parse time known to be { play c } I think.
// Anyway thing to reconsider later
case Ast::Type::Concurrent:
return lhs.arguments.size() == rhs.arguments.size() return lhs.arguments.size() == rhs.arguments.size()
&& std::equal(lhs.arguments.begin(), lhs.arguments.end(), rhs.arguments.begin()); && std::equal(lhs.arguments.begin(), lhs.arguments.end(), rhs.arguments.begin());
} }
@ -603,6 +626,7 @@ std::ostream& operator<<(std::ostream& os, Ast::Type type)
case Ast::Type::Literal: return os << "LITERAL"; case Ast::Type::Literal: return os << "LITERAL";
case Ast::Type::Sequence: return os << "SEQUENCE"; case Ast::Type::Sequence: return os << "SEQUENCE";
case Ast::Type::Variable_Declaration: return os << "VAR"; case Ast::Type::Variable_Declaration: return os << "VAR";
case Ast::Type::Concurrent: return os << "CONCURRENT";
} }
unreachable(); unreachable();
} }

View File

@ -20,7 +20,7 @@ struct Parser
static Result<Ast> parse(std::string_view source, std::string_view filename, unsigned line_number = 0); static Result<Ast> parse(std::string_view source, std::string_view filename, unsigned line_number = 0);
/// Parse sequence, collection of expressions /// Parse sequence, collection of expressions
Result<Ast> parse_sequence(); Result<Ast> parse_sequence(bool is_sequential);
/// Parse either infix expression or variable declaration /// Parse either infix expression or variable declaration
Result<Ast> parse_expression(); Result<Ast> parse_expression();

View File

@ -14,8 +14,6 @@ struct Value;
/// Lazy Array / Continuation / Closure type thingy /// Lazy Array / Continuation / Closure type thingy
struct Block : Collection, Function struct Block : Collection, Function
{ {
~Block() override = default;
/// Location of definition / creation /// Location of definition / creation
Location location; Location location;
@ -28,6 +26,8 @@ struct Block : Collection, Function
/// Context from which block was created. Used for closures /// Context from which block was created. Used for closures
std::shared_ptr<Env> context; std::shared_ptr<Env> context;
~Block() override = default;
/// Calling block /// Calling block
Result<Value> operator()(Interpreter &i, std::vector<Value> params) const override; Result<Value> operator()(Interpreter &i, std::vector<Value> params) const override;