Compare commits

...

7 Commits

Author SHA1 Message Date
Robert Bendun
948243febd Integrated Set type into existing infrastructure 2022-11-24 18:35:51 +01:00
Robert Bendun
f19240d931 Added Set value type 2022-11-24 14:32:57 +01:00
Robert Bendun
532727b7d1 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
2022-11-24 02:08:34 +01:00
Robert Bendun
c509b6ccc5 Don't deadlock during nested evaluation in print context
Lock for stdout should only hold when things are ready to be printed.
This prevents any double locking issues
2022-11-24 01:23:31 +01:00
Robert Bendun
381f79f63c Making this example how it should be in the first place 2022-11-23 22:26:51 +01:00
Robert Bendun
25cf883d03 Fixed crushing midi outputs 2022-11-23 21:51:27 +01:00
Robert Bendun
07fa4f894a Added concurrent execution primitive 2022-11-22 01:16:38 +01:00
25 changed files with 499 additions and 157 deletions

View File

@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- `{}` notation for concurrently executing blocks and describing set of values
- `set` data type which contains an unordered collection of unique elements
### Fixed
- release build script was producing executable with wrong path

View File

@ -5,61 +5,63 @@ WIP implemntation
oct 5, bpm 72, len (1/16),
subsection1 := (
sim (a4 en) (a2 e3 a3),
play (oct 4, c e a),
{ a4 en, a2 e3 a3 },
oct 4, c e a,
sim (b4 en) (e2 e3 g#3),
play (oct 4, e g# b),
{ b4 en, e2 e3 g#3 },
oct 4, e g# b,
sim (c5 en) (a2 e3 a3),
play (e4 e5 d#5),
{ c5 en, a2 e3 a3 },
e4 e5 d#5,
play e d# e b4 d c,
oct 5, e d# e b4 d c,
sim (a4 en) (a2 e3 a3),
play c4 e4 a4,
{ a4 en, a2 e3 a3 },
c4 e4 a4,
sim (b4 en) (e2 e3 g#3),
play d4 c5 b4,
{ b4 en, e2 e3 g#3 },
d4 c5 b4,
),
section1 := ( n |
play e d#,
play e d# e b4 d c,
e d#,
e d# e b4 d c,
call subsection1,
if (n == 1)
( sim (a4 qn) (a2 e3 a3) )
( sim (a4 en) (a2 e3 a3)
, play b4 c5 d5
{ a4 qn, a2 e3 a3 }
( { a4 en, a2 e3 a3 }
, b4 c5 d5
)
),
section2 := ( n |
sim (e5 den) (c3 g3 c4),
play g4 f e,
{ e5 den, c3 g3 c4 },
g4 f e,
sim (d 5 den) (g2 g3 b4),
play f4 e d,
{ d5 den, g2 g3 b4 },
f4 e d,
sim (c5 den) (a2 e3 a3),
play e4 d c,
{ c5 den, a2 e3 a3 },
e4 d c,
sim (b4 en) (e2 e3 e4),
play (e4 e5 e4 e4 e5 e6 d#5 e5 d#5 e5 en),
{ b4 en, e2 e3 e4 },
e4 e5 e4 e4 e5 e6 d#5 e5 d#5 e5 en,
play d# e d# e d#,
play e d# e b4 d c,
d# e d# e d#,
e d# e b4 d c,
call subsection1,
if (n == 1)
( sim (a4 en) (a2 e3 a3)
( { a4 en, a2 e3 a3 }
, play (b4 c5 d5)
)
),
section1 1,
section1 2,
section2 1,
play (
section1 1,
section1 2,
section2 1,
)

View File

@ -7,15 +7,8 @@ hand1_pool := (
hand2_pool := (d8, d#8, g8, g#8, d9, d#9),
for (up 10) (
hand1_length := pick (hn, dhn),
say hand1_length,
hand1 := (set_len hand1_length (pick hand1_pool)),
play {
while true ((pick hand1_pool) (pick hn dhn)),
while true ((pick hand2_pool) (1/64))
}
hand2 := (),
while (duration hand2 != hand1_length) (
hand2 = flat hand2 (set_len (1/64) (pick hand2_pool)),
),
sim hand1 hand2,
),

View File

@ -2,6 +2,7 @@
#define MUSIQUE_COMMON_HH
#include <cstdint>
#include <mutex>
#include <string>
#include <string_view>
@ -62,4 +63,6 @@ concept Three_Way_Comparable = requires (T const& lhs, T const& rhs) {
{ lhs <=> rhs };
};
extern std::mutex stdio_mutex;
#endif

View File

@ -19,6 +19,7 @@ Value_Formatter Value_Formatter::nest(Context nested) const
std::optional<Error> Value_Formatter::format(std::ostream& os, Interpreter &interpreter, Value const& value)
{
static_assert(requires { os << value; });
return std::visit(Overloaded {
[&](Intrinsic const& intrinsic) -> std::optional<Error> {
for (auto const& [key, val] : Env::global->variables) {
@ -49,17 +50,36 @@ std::optional<Error> Value_Formatter::format(std::ostream& os, Interpreter &inte
},
[&](Block const& block) -> std::optional<Error> {
if (block.is_collection()) {
os << '(';
auto const [open, close] = block.body.type == Ast::Type::Concurrent
? std::pair { '{', '}' }
: std::pair { '(', ')' };
os << open;
for (auto i = 0u; i < block.size(); ++i) {
if (i > 0) {
os << ", ";
}
Try(nest(Inside_Block).format(os, interpreter, Try(block.index(interpreter, i))));
}
os << ')';
os << close;
} else {
os << "<block>";
if (block.body.type == Ast::Type::Concurrent) {
os << "<concurrent block>";
} else {
os << "<sequential block>";
}
}
return {};
},
[&](Set const& set) -> std::optional<Error> {
os << '{';
for (auto it = set.elements.begin(); it != set.elements.end(); ++it) {
if (it != set.elements.begin()) {
os << ", ";
}
Try(nest(Inside_Block).format(os, interpreter, *it));
}
os << '}';
return {};
},
[&](auto&&) -> std::optional<Error> {

View File

@ -1,15 +1,19 @@
#include <musique/algo.hh>
#include <musique/interpreter/env.hh>
#include <musique/guard.hh>
#include <musique/interpreter/env.hh>
#include <musique/interpreter/interpreter.hh>
#include <musique/scope_exit.hh>
#include <musique/try.hh>
#include <random>
#include <memory>
#include <iostream>
#include <unordered_set>
#include <chrono>
#include <future>
#include <iostream>
#include <latch>
#include <memory>
#include <random>
#include <thread>
#include <unordered_set>
#include <future>
/// Check if type has index method
template<typename T>
@ -240,52 +244,63 @@ static auto builtin_program_change(Interpreter &i, std::vector<Value> args) -> R
/// Plays sequentialy notes walking into arrays and evaluation blocks
///
/// @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)) {
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)) {
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)) {
return i.play(*chord);
return interpreter.play(*chord);
} else if (auto set = get_if<Set>(v)) {
std::vector<std::future<std::optional<Error>>> futures;
for (auto&& value : set->elements) {
futures.push_back(std::async(std::launch::async,
[interpreter = interpreter.clone(), value = std::move(value)]() mutable {
return sequential_play(interpreter, std::move(value));
}
));
}
for (auto &fut : futures) {
Try(fut.get());
}
}
return {};
}
/// 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 {};
}
/// Play notes
template<With_Index_Operator Container = std::vector<Value>>
static inline Result<Value> builtin_play(Interpreter &interpreter, Container args)
static inline Result<Value> builtin_play(Interpreter &interpreter, std::span<Ast> args)
{
Try(ensure_midi_connection_available(interpreter, "play"));
auto const previous_action = std::exchange(interpreter.default_action, action_play);
auto const previous_context = std::exchange(interpreter.current_context,
std::make_shared<Context>(*interpreter.current_context));
auto const finally = [&] {
Scope_Exit {
interpreter.default_action = std::move(previous_action);
interpreter.current_context = previous_context;
};
for (auto &el : args) {
if (std::optional<Error> error = sequential_play(interpreter, std::move(el))) {
finally();
for (auto &node : args) {
auto value = Try(interpreter.eval((Ast)node));
if (std::optional<Error> error = sequential_play(interpreter, std::move(value))) {
return *std::move(error);
}
}
finally();
return {};
}
@ -314,14 +329,15 @@ static Result<Value> builtin_par(Interpreter &interpreter, std::vector<Value> ar
}
}
auto result = builtin_play(interpreter, std::span(args).subspan(1));
unimplemented();
// auto result = builtin_play(interpreter, std::span(args).subspan(1));
for (auto const& note : chord->notes) {
if (note.base) {
interpreter.midi_connection->send_note_off(0, *note.into_midi_note(), 127);
}
}
return result;
// for (auto const& note : chord->notes) {
// if (note.base) {
// interpreter.midi_connection->send_note_off(0, *note.into_midi_note(), 127);
// }
// }
// return result;
}
/// Plays each argument simultaneously
@ -632,7 +648,7 @@ static Result<Value> builtin_if(Interpreter &i, std::span<Ast> args) {
}
/// 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> {
.name = "while",
.possibilities = {
@ -644,11 +660,16 @@ static Result<Value> builtin_while(Interpreter &i, std::span<Ast> args) {
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) {
Try(i.eval((Ast)args[1].arguments.front()));
result = Try(interpreter.eval((Ast)args[1].arguments.front()));
} 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{};
@ -831,6 +852,14 @@ static Result<Value> builtin_flat(Interpreter &i, std::vector<Value> args)
return Try(into_flat_array(i, std::move(args)));
}
/// Constructs set from given arguments
static Result<Value> builtin_set(Interpreter&, std::vector<Value> args)
{
Set set;
std::move(args.begin(), args.end(), std::inserter(set.elements, set.elements.end()));
return set;
}
/// Pick random value from arugments
static Result<Value> builtin_pick(Interpreter &i, std::vector<Value> args)
{
@ -1147,6 +1176,7 @@ void Interpreter::register_builtin_functions()
global.force_define("rotate", builtin_rotate);
global.force_define("round", apply_numeric_transform<&Number::round>);
global.force_define("scan", builtin_scan);
global.force_define("set", builtin_set);
global.force_define("set_len", builtin_set_len);
global.force_define("set_oct", builtin_set_oct);
global.force_define("shuffle", builtin_shuffle);

View File

@ -22,10 +22,10 @@ struct Context
/// Fills empty places in Note like octave and length with default values from context
Note fill(Note) const;
std::shared_ptr<Context> parent = nullptr;
/// Converts length to seconds with current bpm
std::chrono::duration<float> length_to_duration(std::optional<Number> length) const;
std::shared_ptr<Context> parent;
};
#endif

View File

@ -1,15 +1,28 @@
#include <musique/interpreter/env.hh>
#include <musique/interpreter/interpreter.hh>
#include <musique/options.hh>
#include <musique/try.hh>
#include <chrono>
#include <iostream>
#include <random>
#include <thread>
#include <future>
#include <mutex>
midi::Connection *Interpreter::midi_connection = nullptr;
std::unordered_map<std::string, Intrinsic> Interpreter::operators {};
Interpreter Interpreter::clone() const
{
Interpreter interpreter(Clone{});
interpreter.default_action = default_action;
interpreter.env = env->enter();
interpreter.current_context = std::make_shared<Context>(*current_context);
interpreter.current_context->parent = current_context;
return interpreter;
}
/// Registers constants like `fn = full note = 1/1`
static inline void register_note_length_constants()
{
@ -47,9 +60,52 @@ Interpreter::Interpreter()
register_builtin_functions();
}
Interpreter::~Interpreter()
Interpreter::Interpreter(Interpreter::Clone)
{
Env::global.reset();
}
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{};
}));
}
Set set;
for (auto& future : futures) {
if (error) {
return *error;
}
set.elements.insert(future.get());
}
return set;
}
Result<Value> Interpreter::eval(Ast &&ast)
@ -154,6 +210,9 @@ Result<Value> Interpreter::eval(Ast &&ast)
}
break;
case Ast::Type::Concurrent:
return eval_concurrent(*this, std::move(ast));
case Ast::Type::Sequence:
{
Value v;
@ -230,11 +289,15 @@ void Interpreter::leave_scope()
std::optional<Error> Interpreter::play(Chord chord)
{
if (global_options::dump_play_actions) {
std::lock_guard guard{stdio_mutex};
std::cerr << "[DEBUG] Interpreter::play " << chord << std::endl;
}
Try(ensure_midi_connection_available(*this, "play"));
auto &ctx = *current_context;
if (chord.notes.size() == 0) {
std::this_thread::sleep_for(ctx.length_to_duration(ctx.length));
return {};
}
@ -291,7 +354,9 @@ static void snapshot(std::ostream& out, Note const& note) {
static void snapshot(std::ostream &out, Ast const& ast) {
switch (ast.type) {
break; case Ast::Type::Sequence:
break;
case Ast::Type::Sequence:
case Ast::Type::Concurrent:
{
for (auto const& a : ast.arguments) {
snapshot(out, a);
@ -299,7 +364,10 @@ static void snapshot(std::ostream &out, Ast const& ast) {
}
}
break; case Ast::Type::Block:
// TODO
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 << "(";
snapshot(out, ast.arguments.front());
out << ")";
@ -386,7 +454,8 @@ static void snapshot(std::ostream& out, Value const& value) {
out << ")";
},
[](Intrinsic const&) { unreachable(); },
[](Macro const&) { unreachable(); }
[](Macro const&) { unreachable(); },
[](Set const&) { unimplemented("Snapshoting is not supported yet"); }
}, value.data);
}

View File

@ -17,26 +17,34 @@ struct Interpreter
static std::unordered_map<std::string, Intrinsic> operators;
/// Current environment (current scope)
std::shared_ptr<Env> env;
std::shared_ptr<Env> env = nullptr;
/// Context stack. `constext_stack.back()` is a current context.
/// There is always at least one context
std::shared_ptr<Context> current_context;
std::shared_ptr<Context> current_context = nullptr;
std::function<std::optional<Error>(Interpreter&, Value)> default_action;
std::function<std::optional<Error>(Interpreter&, Value)> default_action = nullptr;
Interpreter();
~Interpreter();
Interpreter(Interpreter &&) = delete;
~Interpreter() = default;
Interpreter(Interpreter const&) = delete;
Interpreter(Interpreter &&) = default;
private:
struct Clone {};
Interpreter(Clone);
public:
/// Explicit clone method
Interpreter clone() const;
/// Try to evaluate given program tree
Result<Value> eval(Ast &&ast);
// Enter scope by changing current environment
/// Enter scope by changing current environment
void enter_scope();
// Leave scope by changing current environment
/// Leave scope by changing current environment
void leave_scope();
/// Play note resolving any missing parameters with context via `midi_connection` member.

View File

@ -88,8 +88,10 @@ auto Lexer::next_token() -> Result<std::variant<Token, End_Of_File>>
}
switch (peek()) {
case '(': consume(); return Token { Token::Type::Open_Block, finish(), token_location };
case ')': consume(); return Token { Token::Type::Close_Block, finish(), token_location };
case '(': consume(); return Token { Token::Type::Open_Sequential, 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::Close_Index, 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) {
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::Keyword: return os << "KEYWORD";
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::Parameter_Separator: return os << "PARAMETER SEPARATOR";
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();
}
@ -275,13 +279,15 @@ std::string_view type_name(Token::Type type)
{
switch (type) {
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_Sequential: return ")";
case Token::Type::Expression_Separator: return "|";
case Token::Type::Keyword: return "keyword";
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_Sequential: return "(";
case Token::Type::Operator: return "operator";
case Token::Type::Parameter_Separator: return "parameter separator";
case Token::Type::Symbol: return "symbol";

View File

@ -17,10 +17,12 @@ struct Token
Numeric, ///< numeric literal (floating point or integer)
Parameter_Separator, ///< "|" separaters arguments from block body
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
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

View File

@ -6,6 +6,7 @@
#include <thread>
#include <cstring>
#include <cstdio>
#include <mutex>
#include <musique/format.hh>
#include <musique/interpreter/env.hh>
@ -36,6 +37,8 @@ static bool ast_only_mode = false;
static bool enable_repl = false;
static unsigned repl_line_number = 1;
std::mutex stdio_mutex;
#define Ignore(Call) do { auto const ignore_ ## __LINE__ = (Call); (void) ignore_ ## __LINE__; } while(0)
/// Pop string from front of an array
@ -188,12 +191,20 @@ struct Runner
}
}
Env::global->force_define("say", +[](Interpreter &interpreter, std::vector<Value> args) -> Result<Value> {
Env::global->force_define("print", +[](Interpreter &interpreter, std::vector<Value> args) -> Result<Value> {
for (auto it = args.begin(); it != args.end(); ++it) {
std::cout << Try(format(interpreter, *it));
if (std::next(it) != args.end())
{
auto result = Try(format(interpreter, *it));
std::lock_guard guard{stdio_mutex};
std::cout << result;
}
if (std::next(it) != args.end()) {
std::lock_guard guard{stdio_mutex};
std::cout << ' ';
}
}
std::lock_guard guard{stdio_mutex};
std::cout << std::endl;
return {};
});

View File

@ -1,5 +1,6 @@
#include <musique/midi/midi.hh>
#include <musique/errors.hh>
#include <mutex>
// Copyright notice for RtMidi library
__asm__(R"license(.ident "\
@ -7,6 +8,8 @@ RtMidi: realtime MIDI i/o C++ classes\
Copyright (c) 2003-2021 Gary P. Scavone"
)license");
static std::mutex midi_output_mutex;
void midi::Rt_Midi::list_ports(std::ostream &out) const
try {
RtMidiIn input;
@ -80,6 +83,7 @@ bool midi::Rt_Midi::supports_output() const
template<std::size_t N>
inline void send_message(RtMidiOut &out, std::array<std::uint8_t, N> message)
try {
std::lock_guard guard{midi_output_mutex};
out.sendMessage(message.data(), message.size());
} catch (RtMidiError &error) {
// TODO(error)

View File

@ -12,19 +12,22 @@ struct Ast
static Ast binary(Token, Ast lhs, Ast rhs);
/// Constructs block
static Ast block(Location location, Ast seq = sequence({}));
static Ast block(Location location, Ast seq);
/// Constructs call expression
static Ast call(std::vector<Ast> call);
/// 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
static Ast literal(Token);
/// 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
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`
Literal, ///< Compile time known constant like `c` or `1`
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`
};

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 (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();
return Error {
.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;
}
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));
return Ast::sequence(std::move(seq));
return is_sequential ? Ast::sequential(std::move(seq)) : Ast::concurrent(std::move(seq));
}
Result<Ast> Parser::parse_expression()
@ -185,10 +186,11 @@ Result<Ast> parse_sequence_inside(
Location start_location,
bool is_lambda,
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)) {
Try(dont_arrived_at_closing_token());
return Error {
@ -206,7 +208,7 @@ Result<Ast> parse_sequence_inside(
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) {
return std::move(ast.arguments.front());
}
@ -231,7 +233,8 @@ Result<Ast> Parser::parse_index_expression()
{},
[]() -> std::optional<Error> {
return std::nullopt;
}
},
true
))
);
}
@ -258,12 +261,16 @@ Result<Ast> Parser::parse_atomic_expression()
case Token::Type::Symbol:
return Ast::literal(consume());
case Token::Type::Open_Block:
case Token::Type::Open_Sequential:
case Token::Type::Open_Concurrent:
{
auto opening = consume();
if (expect(Token::Type::Close_Block)) {
auto const opening = consume();
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();
return Ast::block(std::move(opening).location);
return Ast::block(std::move(opening).location, is_sequential ? Ast::sequential({}) : Ast::concurrent({}));
}
auto start = token_id;
@ -286,7 +293,7 @@ Result<Ast> Parser::parse_atomic_expression()
return parse_sequence_inside(
*this,
Token::Type::Close_Block,
closing_token_type,
opening.location,
is_lambda,
std::move(parameters),
@ -321,7 +328,8 @@ Result<Ast> Parser::parse_atomic_expression()
};
}
return std::nullopt;
}
},
is_sequential
);
}
@ -490,7 +498,7 @@ Ast Ast::call(std::vector<Ast> call)
return ast;
}
Ast Ast::sequence(std::vector<Ast> expressions)
Ast Ast::sequential(std::vector<Ast> expressions)
{
Ast ast;
ast.type = Type::Sequence;
@ -501,6 +509,17 @@ Ast Ast::sequence(std::vector<Ast> expressions)
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;
@ -586,6 +605,10 @@ bool operator==(Ast const& lhs, Ast const& rhs)
case Ast::Type::Lambda:
case Ast::Type::Sequence:
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()
&& 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::Sequence: return os << "SEQUENCE";
case Ast::Type::Variable_Declaration: return os << "VAR";
case Ast::Type::Concurrent: return os << "CONCURRENT";
}
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);
/// Parse sequence, collection of expressions
Result<Ast> parse_sequence();
Result<Ast> parse_sequence(bool is_sequential);
/// Parse either infix expression or variable declaration
Result<Ast> parse_expression();

View File

@ -8,6 +8,8 @@
struct Interpreter;
struct Value;
// TODO Array should have an ability to be invoked with duration parameter like singular note
/// Eager Array
struct Array : Collection
{

View File

@ -4,6 +4,11 @@
#include <musique/value/block.hh>
#include <musique/value/value.hh>
inline bool is_ast_collection(Ast::Type type)
{
return type == Ast::Type::Sequence || type == Ast::Type::Concurrent;
}
/// Helper that produces error when trying to access container with too few elements for given index
static inline std::optional<Error> guard_index(unsigned index, unsigned size)
{
@ -17,18 +22,18 @@ static inline std::optional<Error> guard_index(unsigned index, unsigned size)
Result<Value> Block::index(Interpreter &i, unsigned position) const
{
ensure(parameters.empty(), "cannot index into block with parameters (for now)");
if (body.type != Ast::Type::Sequence) {
Try(guard_index(position, 1));
return i.eval((Ast)body);
}
if (is_ast_collection(body.type)) {
Try(guard_index(position, body.arguments.size()));
return i.eval((Ast)body.arguments[position]);
}
Try(guard_index(position, 1));
return i.eval((Ast)body);
}
usize Block::size() const
{
return body.type == Ast::Type::Sequence ? body.arguments.size() : 1;
return is_ast_collection(body.type) ? body.arguments.size() : 1;
}
Result<Value> Block::operator()(Interpreter &i, std::vector<Value> arguments) const

View File

@ -11,11 +11,11 @@ struct Env;
struct Interpreter;
struct Value;
// TODO Block should have an ability to be invoked with duration parameter like singular note
/// Lazy Array / Continuation / Closure type thingy
struct Block : Collection, Function
{
~Block() override = default;
/// Location of definition / creation
Location location;
@ -28,6 +28,8 @@ struct Block : Collection, Function
/// Context from which block was created. Used for closures
std::shared_ptr<Env> context;
~Block() override = default;
/// Calling block
Result<Value> operator()(Interpreter &i, std::vector<Value> params) const override;

50
musique/value/hash.cc Normal file
View File

@ -0,0 +1,50 @@
#include <musique/value/hash.hh>
#include <musique/value/value.hh>
size_t std::hash<Set>::operator()(Set const& set) const noexcept
{
return std::accumulate(
set.elements.cbegin(), set.elements.cend(),
size_t(0),
[](size_t hash, Value const& element) {
return hash_combine(hash, std::hash<Value>{}(element));
}
);
}
std::size_t std::hash<Value>::operator()(Value const& value) const noexcept
{
auto const value_hash = std::visit(Overloaded {
[](Nil) {
return std::size_t(0);
},
[](Intrinsic i) {
return size_t(i.function_pointer);
},
[](Block const& b) {
return hash_combine(std::hash<Ast>{}(b.body), b.parameters.size());
},
[this](Array const& array) {
return std::accumulate(
array.elements.begin(), array.elements.end(), size_t(0),
[this](size_t h, Value const& v) { return hash_combine(h, operator()(v)); }
);
},
[](Chord const& chord) {
return std::accumulate(chord.notes.begin(), chord.notes.end(), size_t(0), [](size_t h, Note const& n) {
h = hash_combine(h, std::hash<std::optional<int>>{}(n.base));
h = hash_combine(h, std::hash<std::optional<Number>>{}(n.length));
h = hash_combine(h, std::hash<std::optional<i8>>{}(n.octave));
return h;
});
},
[]<typename T>(T const& t) { return std::hash<T>{}(t); },
}, value.data);
return hash_combine(value_hash, size_t(value.data.index()));
}

17
musique/value/hash.hh Normal file
View File

@ -0,0 +1,17 @@
#ifndef MUSIQUE_VALUE_HASH_HH
#define MUSIQUE_VALUE_HASH_HH
#include <functional>
#define Hash_For(Name) \
struct Name; \
template<> \
struct std::hash<Name> { \
size_t operator()(Name const&) const noexcept; \
}; \
Hash_For(Block)
Hash_For(Set)
Hash_For(Value)
#endif // MUSIQUE_VALUE_HASH_HH

60
musique/value/set.cc Normal file
View File

@ -0,0 +1,60 @@
#include <musique/value/value.hh>
#include <musique/value/set.hh>
Result<Value> Set::operator()(Interpreter&, std::vector<Value> params) const
{
auto copy = *this;
if (auto a = match<Number>(params)) {
auto [length] = *a;
for (auto& value : copy.elements) {
if (auto chord = get_if<Chord>(value)) {
// FIXME This const_cast should be unnesesary
for (auto &note : const_cast<Chord*>(chord)->notes) {
note.length = length;
}
continue;
}
}
}
// TODO Error reporting when parameters doesnt match
// TODO reconsider different options for this data structure invocation
return copy;
}
Result<Value> Set::index(Interpreter&, unsigned position) const
{
if (elements.size() < position) {
return errors::Out_Of_Range {
.required_index = position,
.size = elements.size()
};
}
// FIXME std::unordered_set::iterator has forward iterator, which means
// that any element lookup has complecity O(n). This may have serious
// performance implications and further investigation is needed.
return *std::next(elements.begin(), position);
}
usize Set::size() const
{
return elements.size();
}
bool Set::is_collection() const
{
return true;
}
std::strong_ordering Set::operator<=>(Set const&) const
{
unimplemented();
}
std::ostream& operator<<(std::ostream& out, Set const& set)
{
unimplemented();
return out;
}

36
musique/value/set.hh Normal file
View File

@ -0,0 +1,36 @@
#ifndef MUSIQUE_VALUE_SET_HH
#define MUSIQUE_VALUE_SET_HH
#include <unordered_set>
#include <utility>
// Needs to be always first, before all the implicit template instantiations
#include <musique/value/hash.hh>
#include <musique/value/collection.hh>
#include <musique/value/function.hh>
#include <musique/common.hh>
struct Value;
struct Set : Collection, Function
{
std::unordered_set<Value> elements;
~Set() = default;
Result<Value> operator()(Interpreter &i, std::vector<Value> params) const override;
Result<Value> index(Interpreter &i, unsigned position) const override;
usize size() const override;
bool is_collection() const override;
bool operator==(Set const&) const = default;
std::strong_ordering operator<=>(Set const&) const;
};
std::ostream& operator<<(std::ostream& out, Set const& set);
#endif

View File

@ -107,6 +107,11 @@ Value::Value(Chord chord)
{
}
Value::Value(Set &&set)
: data(std::move(set))
{
}
Result<Value> Value::operator()(Interpreter &i, std::vector<Value> args) const
{
if (auto func = get_if<Function>(data)) {
@ -188,7 +193,10 @@ std::ostream& operator<<(std::ostream& os, Value const& v)
[&](Nil) { os << "nil"; },
[&](Intrinsic) { os << "<intrinisic>"; },
[&](Block const&) { os << "<block>"; },
[&](auto const& s) { os << s; }
[&](auto const& s) {
static_assert(requires { os << s; });
os << s;
}
}, v.data);
return os;
}
@ -205,6 +213,7 @@ std::string_view type_name(Value const& v)
[&](Nil const&) { return "nil"; },
[&](Number const&) { return "number"; },
[&](Symbol const&) { return "symbol"; },
[&](Set const&) { return "set"; },
}, v.data);
}
@ -228,28 +237,3 @@ Result<std::vector<Value>> flatten(Interpreter &i, std::vector<Value> args)
return flatten(i, std::span(args));
}
std::size_t std::hash<Value>::operator()(Value const& value) const
{
auto const value_hash = std::visit(Overloaded {
[](Nil) { return std::size_t(0); },
[](Intrinsic i) { return size_t(i.function_pointer); },
[](Block const& b) { return hash_combine(std::hash<Ast>{}(b.body), b.parameters.size()); },
[this](Array const& array) {
return std::accumulate(
array.elements.begin(), array.elements.end(), size_t(0),
[this](size_t h, Value const& v) { return hash_combine(h, operator()(v)); }
);
},
[](Chord const& chord) {
return std::accumulate(chord.notes.begin(), chord.notes.end(), size_t(0), [](size_t h, Note const& n) {
h = hash_combine(h, std::hash<std::optional<int>>{}(n.base));
h = hash_combine(h, std::hash<std::optional<Number>>{}(n.length));
h = hash_combine(h, std::hash<std::optional<i8>>{}(n.octave));
return h;
});
},
[]<typename T>(T const& t) { return std::hash<T>{}(t); },
}, value.data);
return hash_combine(value_hash, size_t(value.data.index()));
}

View File

@ -1,6 +1,9 @@
#ifndef MUSIQUE_VALUE_HH
#define MUSIQUE_VALUE_HH
// Needs to be always first, before all the implicit template instantiations
#include <musique/value/hash.hh>
#include <musique/accessors.hh>
#include <musique/common.hh>
#include <musique/lexer/token.hh>
@ -10,6 +13,7 @@
#include <musique/value/chord.hh>
#include <musique/value/intrinsic.hh>
#include <musique/value/note.hh>
#include <musique/value/set.hh>
struct Nil
{
@ -40,6 +44,7 @@ struct Value
Value(char const* s); ///< Create value of type symbol holding provided symbol
Value(std::string s); ///< Create value of type symbol holding provided symbol
Value(std::string_view s); ///< Create value of type symbol holding provided symbol
Value(Set &&set); ///< Create value of type set holding provided set
explicit Value(std::vector<Value> &&array); ///< Create value of type array holding provided array
// TODO Most strings should not be allocated by Value, but reference to string allocated previously
@ -54,6 +59,7 @@ struct Value
Intrinsic,
Block,
Array,
Set,
Chord,
Macro
> data = Nil{};
@ -108,9 +114,6 @@ inline T* get_if(Value& v) { return get_if<T>(v.data); }
/// Returns type name of Value type
std::string_view type_name(Value const& v);
std::ostream& operator<<(std::ostream& os, Value const& v);
template<> struct std::hash<Value> { std::size_t operator()(Value const&) const; };
template<typename T>
Result<Value> wrap_value(Result<T> &&value)
{
@ -170,4 +173,6 @@ constexpr auto match(Values& ...values) -> std::optional<std::tuple<T&...>>
} (std::make_index_sequence<sizeof...(T)>{});
}
std::ostream& operator<<(std::ostream& os, Value const& v);
#endif