Compare commits
7 Commits
main
...
concurrenc
Author | SHA1 | Date | |
---|---|---|---|
|
948243febd | ||
|
f19240d931 | ||
|
532727b7d1 | ||
|
c509b6ccc5 | ||
|
381f79f63c | ||
|
25cf883d03 | ||
|
07fa4f894a |
@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `{}` notation for concurrently executing blocks and describing set of values
|
||||||
|
- `set` data type which contains an unordered collection of unique elements
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- release build script was producing executable with wrong path
|
- release build script was producing executable with wrong path
|
||||||
|
@ -5,61 +5,63 @@ WIP implemntation
|
|||||||
oct 5, bpm 72, len (1/16),
|
oct 5, bpm 72, len (1/16),
|
||||||
|
|
||||||
subsection1 := (
|
subsection1 := (
|
||||||
sim (a4 en) (a2 e3 a3),
|
{ a4 en, a2 e3 a3 },
|
||||||
play (oct 4, c e a),
|
oct 4, c e a,
|
||||||
|
|
||||||
sim (b4 en) (e2 e3 g#3),
|
{ b4 en, e2 e3 g#3 },
|
||||||
play (oct 4, e g# b),
|
oct 4, e g# b,
|
||||||
|
|
||||||
sim (c5 en) (a2 e3 a3),
|
{ c5 en, a2 e3 a3 },
|
||||||
play (e4 e5 d#5),
|
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),
|
{ a4 en, a2 e3 a3 },
|
||||||
play c4 e4 a4,
|
c4 e4 a4,
|
||||||
|
|
||||||
sim (b4 en) (e2 e3 g#3),
|
{ b4 en, e2 e3 g#3 },
|
||||||
play d4 c5 b4,
|
d4 c5 b4,
|
||||||
),
|
),
|
||||||
|
|
||||||
section1 := ( n |
|
section1 := ( n |
|
||||||
play e d#,
|
e d#,
|
||||||
play e d# e b4 d c,
|
e d# e b4 d c,
|
||||||
|
|
||||||
call subsection1,
|
call subsection1,
|
||||||
|
|
||||||
if (n == 1)
|
if (n == 1)
|
||||||
( sim (a4 qn) (a2 e3 a3) )
|
{ a4 qn, a2 e3 a3 }
|
||||||
( sim (a4 en) (a2 e3 a3)
|
( { a4 en, a2 e3 a3 }
|
||||||
, play b4 c5 d5
|
, b4 c5 d5
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
||||||
section2 := ( n |
|
section2 := ( n |
|
||||||
sim (e5 den) (c3 g3 c4),
|
{ e5 den, c3 g3 c4 },
|
||||||
play g4 f e,
|
g4 f e,
|
||||||
|
|
||||||
sim (d 5 den) (g2 g3 b4),
|
{ d5 den, g2 g3 b4 },
|
||||||
play f4 e d,
|
f4 e d,
|
||||||
|
|
||||||
sim (c5 den) (a2 e3 a3),
|
{ c5 den, a2 e3 a3 },
|
||||||
play e4 d c,
|
e4 d c,
|
||||||
|
|
||||||
sim (b4 en) (e2 e3 e4),
|
{ b4 en, e2 e3 e4 },
|
||||||
play (e4 e5 e4 e4 e5 e6 d#5 e5 d#5 e5 en),
|
e4 e5 e4 e4 e5 e6 d#5 e5 d#5 e5 en,
|
||||||
|
|
||||||
play d# e d# e d#,
|
d# e d# e d#,
|
||||||
play e d# e b4 d c,
|
e d# e b4 d c,
|
||||||
|
|
||||||
call subsection1,
|
call subsection1,
|
||||||
|
|
||||||
if (n == 1)
|
if (n == 1)
|
||||||
( sim (a4 en) (a2 e3 a3)
|
( { a4 en, a2 e3 a3 }
|
||||||
, play (b4 c5 d5)
|
, play (b4 c5 d5)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
||||||
|
play (
|
||||||
section1 1,
|
section1 1,
|
||||||
section1 2,
|
section1 2,
|
||||||
section2 1,
|
section2 1,
|
||||||
|
)
|
||||||
|
@ -7,15 +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),
|
||||||
|
|
||||||
for (up 10) (
|
play {
|
||||||
hand1_length := pick (hn, dhn),
|
while true ((pick hand1_pool) (pick hn dhn)),
|
||||||
say hand1_length,
|
while true ((pick hand2_pool) (1/64))
|
||||||
hand1 := (set_len hand1_length (pick hand1_pool)),
|
}
|
||||||
|
|
||||||
hand2 := (),
|
|
||||||
while (duration hand2 != hand1_length) (
|
|
||||||
hand2 = flat hand2 (set_len (1/64) (pick hand2_pool)),
|
|
||||||
),
|
|
||||||
|
|
||||||
sim hand1 hand2,
|
|
||||||
),
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
#define MUSIQUE_COMMON_HH
|
#define MUSIQUE_COMMON_HH
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
|
||||||
@ -62,4 +63,6 @@ concept Three_Way_Comparable = requires (T const& lhs, T const& rhs) {
|
|||||||
{ lhs <=> rhs };
|
{ lhs <=> rhs };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
extern std::mutex stdio_mutex;
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -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)
|
std::optional<Error> Value_Formatter::format(std::ostream& os, Interpreter &interpreter, Value const& value)
|
||||||
{
|
{
|
||||||
|
static_assert(requires { os << value; });
|
||||||
return std::visit(Overloaded {
|
return std::visit(Overloaded {
|
||||||
[&](Intrinsic const& intrinsic) -> std::optional<Error> {
|
[&](Intrinsic const& intrinsic) -> std::optional<Error> {
|
||||||
for (auto const& [key, val] : Env::global->variables) {
|
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> {
|
[&](Block const& block) -> std::optional<Error> {
|
||||||
if (block.is_collection()) {
|
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) {
|
for (auto i = 0u; i < block.size(); ++i) {
|
||||||
if (i > 0) {
|
if (i > 0) {
|
||||||
os << ", ";
|
os << ", ";
|
||||||
}
|
}
|
||||||
Try(nest(Inside_Block).format(os, interpreter, Try(block.index(interpreter, i))));
|
Try(nest(Inside_Block).format(os, interpreter, Try(block.index(interpreter, i))));
|
||||||
}
|
}
|
||||||
os << ')';
|
os << close;
|
||||||
} else {
|
} 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 {};
|
return {};
|
||||||
},
|
},
|
||||||
[&](auto&&) -> std::optional<Error> {
|
[&](auto&&) -> std::optional<Error> {
|
||||||
|
@ -1,15 +1,19 @@
|
|||||||
#include <musique/algo.hh>
|
#include <musique/algo.hh>
|
||||||
#include <musique/interpreter/env.hh>
|
|
||||||
#include <musique/guard.hh>
|
#include <musique/guard.hh>
|
||||||
|
#include <musique/interpreter/env.hh>
|
||||||
#include <musique/interpreter/interpreter.hh>
|
#include <musique/interpreter/interpreter.hh>
|
||||||
|
#include <musique/scope_exit.hh>
|
||||||
#include <musique/try.hh>
|
#include <musique/try.hh>
|
||||||
|
|
||||||
#include <random>
|
|
||||||
#include <memory>
|
|
||||||
#include <iostream>
|
|
||||||
#include <unordered_set>
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <future>
|
||||||
|
#include <iostream>
|
||||||
|
#include <latch>
|
||||||
|
#include <memory>
|
||||||
|
#include <random>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <future>
|
||||||
|
|
||||||
/// Check if type has index method
|
/// Check if type has index method
|
||||||
template<typename T>
|
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
|
/// 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);
|
||||||
|
} 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 {};
|
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 {};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Play notes
|
/// Play notes
|
||||||
template<With_Index_Operator Container = std::vector<Value>>
|
static inline Result<Value> builtin_play(Interpreter &interpreter, std::span<Ast> args)
|
||||||
static inline Result<Value> builtin_play(Interpreter &interpreter, Container args)
|
|
||||||
{
|
{
|
||||||
Try(ensure_midi_connection_available(interpreter, "play"));
|
Try(ensure_midi_connection_available(interpreter, "play"));
|
||||||
auto const previous_action = std::exchange(interpreter.default_action, action_play);
|
auto const previous_action = std::exchange(interpreter.default_action, action_play);
|
||||||
auto const previous_context = std::exchange(interpreter.current_context,
|
auto const previous_context = std::exchange(interpreter.current_context,
|
||||||
std::make_shared<Context>(*interpreter.current_context));
|
std::make_shared<Context>(*interpreter.current_context));
|
||||||
|
|
||||||
auto const finally = [&] {
|
Scope_Exit {
|
||||||
interpreter.default_action = std::move(previous_action);
|
interpreter.default_action = std::move(previous_action);
|
||||||
interpreter.current_context = previous_context;
|
interpreter.current_context = previous_context;
|
||||||
};
|
};
|
||||||
|
|
||||||
for (auto &el : args) {
|
for (auto &node : args) {
|
||||||
if (std::optional<Error> error = sequential_play(interpreter, std::move(el))) {
|
auto value = Try(interpreter.eval((Ast)node));
|
||||||
finally();
|
if (std::optional<Error> error = sequential_play(interpreter, std::move(value))) {
|
||||||
return *std::move(error);
|
return *std::move(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
finally();
|
|
||||||
return {};
|
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) {
|
// for (auto const& note : chord->notes) {
|
||||||
if (note.base) {
|
// if (note.base) {
|
||||||
interpreter.midi_connection->send_note_off(0, *note.into_midi_note(), 127);
|
// interpreter.midi_connection->send_note_off(0, *note.into_midi_note(), 127);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
return result;
|
// return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Plays each argument simultaneously
|
/// Plays each argument simultaneously
|
||||||
@ -632,7 +648,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 = {
|
||||||
@ -644,11 +660,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{};
|
||||||
@ -831,6 +852,14 @@ static Result<Value> builtin_flat(Interpreter &i, std::vector<Value> args)
|
|||||||
return Try(into_flat_array(i, std::move(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
|
/// Pick random value from arugments
|
||||||
static Result<Value> builtin_pick(Interpreter &i, std::vector<Value> args)
|
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("rotate", builtin_rotate);
|
||||||
global.force_define("round", apply_numeric_transform<&Number::round>);
|
global.force_define("round", apply_numeric_transform<&Number::round>);
|
||||||
global.force_define("scan", builtin_scan);
|
global.force_define("scan", builtin_scan);
|
||||||
|
global.force_define("set", builtin_set);
|
||||||
global.force_define("set_len", builtin_set_len);
|
global.force_define("set_len", builtin_set_len);
|
||||||
global.force_define("set_oct", builtin_set_oct);
|
global.force_define("set_oct", builtin_set_oct);
|
||||||
global.force_define("shuffle", builtin_shuffle);
|
global.force_define("shuffle", builtin_shuffle);
|
||||||
|
@ -22,10 +22,10 @@ struct Context
|
|||||||
/// Fills empty places in Note like octave and length with default values from context
|
/// Fills empty places in Note like octave and length with default values from context
|
||||||
Note fill(Note) const;
|
Note fill(Note) const;
|
||||||
|
|
||||||
|
std::shared_ptr<Context> parent = nullptr;
|
||||||
|
|
||||||
/// Converts length to seconds with current bpm
|
/// Converts length to seconds with current bpm
|
||||||
std::chrono::duration<float> length_to_duration(std::optional<Number> length) const;
|
std::chrono::duration<float> length_to_duration(std::optional<Number> length) const;
|
||||||
|
|
||||||
std::shared_ptr<Context> parent;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -1,15 +1,28 @@
|
|||||||
#include <musique/interpreter/env.hh>
|
#include <musique/interpreter/env.hh>
|
||||||
#include <musique/interpreter/interpreter.hh>
|
#include <musique/interpreter/interpreter.hh>
|
||||||
|
#include <musique/options.hh>
|
||||||
#include <musique/try.hh>
|
#include <musique/try.hh>
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#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 {};
|
||||||
|
|
||||||
|
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`
|
/// Registers constants like `fn = full note = 1/1`
|
||||||
static inline void register_note_length_constants()
|
static inline void register_note_length_constants()
|
||||||
{
|
{
|
||||||
@ -47,9 +60,52 @@ Interpreter::Interpreter()
|
|||||||
register_builtin_functions();
|
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)
|
Result<Value> Interpreter::eval(Ast &&ast)
|
||||||
@ -154,6 +210,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;
|
||||||
@ -230,11 +289,15 @@ void Interpreter::leave_scope()
|
|||||||
|
|
||||||
std::optional<Error> Interpreter::play(Chord chord)
|
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"));
|
Try(ensure_midi_connection_available(*this, "play"));
|
||||||
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 {};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -291,7 +354,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);
|
||||||
@ -299,7 +364,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 << ")";
|
||||||
@ -386,7 +454,8 @@ static void snapshot(std::ostream& out, Value const& value) {
|
|||||||
out << ")";
|
out << ")";
|
||||||
},
|
},
|
||||||
[](Intrinsic const&) { unreachable(); },
|
[](Intrinsic const&) { unreachable(); },
|
||||||
[](Macro const&) { unreachable(); }
|
[](Macro const&) { unreachable(); },
|
||||||
|
[](Set const&) { unimplemented("Snapshoting is not supported yet"); }
|
||||||
}, value.data);
|
}, value.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,26 +17,34 @@ struct Interpreter
|
|||||||
static std::unordered_map<std::string, Intrinsic> operators;
|
static std::unordered_map<std::string, Intrinsic> operators;
|
||||||
|
|
||||||
/// Current environment (current scope)
|
/// Current environment (current scope)
|
||||||
std::shared_ptr<Env> env;
|
std::shared_ptr<Env> env = nullptr;
|
||||||
|
|
||||||
/// Context stack. `constext_stack.back()` is a current context.
|
/// Context stack. `constext_stack.back()` is a current context.
|
||||||
/// There is always at least one 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() = default;
|
||||||
Interpreter(Interpreter &&) = delete;
|
|
||||||
Interpreter(Interpreter const&) = delete;
|
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
|
/// Try to evaluate given program tree
|
||||||
Result<Value> eval(Ast &&ast);
|
Result<Value> eval(Ast &&ast);
|
||||||
|
|
||||||
// Enter scope by changing current environment
|
/// Enter scope by changing current environment
|
||||||
void enter_scope();
|
void enter_scope();
|
||||||
|
|
||||||
// Leave scope by changing current environment
|
/// Leave scope by changing current environment
|
||||||
void leave_scope();
|
void leave_scope();
|
||||||
|
|
||||||
/// Play note resolving any missing parameters with context via `midi_connection` member.
|
/// Play note resolving any missing parameters with context via `midi_connection` member.
|
||||||
|
@ -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";
|
||||||
|
@ -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
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
#include <thread>
|
#include <thread>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
#include <musique/format.hh>
|
#include <musique/format.hh>
|
||||||
#include <musique/interpreter/env.hh>
|
#include <musique/interpreter/env.hh>
|
||||||
@ -36,6 +37,8 @@ static bool ast_only_mode = false;
|
|||||||
static bool enable_repl = false;
|
static bool enable_repl = false;
|
||||||
static unsigned repl_line_number = 1;
|
static unsigned repl_line_number = 1;
|
||||||
|
|
||||||
|
std::mutex stdio_mutex;
|
||||||
|
|
||||||
#define Ignore(Call) do { auto const ignore_ ## __LINE__ = (Call); (void) ignore_ ## __LINE__; } while(0)
|
#define Ignore(Call) do { auto const ignore_ ## __LINE__ = (Call); (void) ignore_ ## __LINE__; } while(0)
|
||||||
|
|
||||||
/// Pop string from front of an array
|
/// 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) {
|
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::cout << ' ';
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
std::lock_guard guard{stdio_mutex};
|
||||||
std::cout << std::endl;
|
std::cout << std::endl;
|
||||||
return {};
|
return {};
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#include <musique/midi/midi.hh>
|
#include <musique/midi/midi.hh>
|
||||||
#include <musique/errors.hh>
|
#include <musique/errors.hh>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
// Copyright notice for RtMidi library
|
// Copyright notice for RtMidi library
|
||||||
__asm__(R"license(.ident "\
|
__asm__(R"license(.ident "\
|
||||||
@ -7,6 +8,8 @@ RtMidi: realtime MIDI i/o C++ classes\
|
|||||||
Copyright (c) 2003-2021 Gary P. Scavone"
|
Copyright (c) 2003-2021 Gary P. Scavone"
|
||||||
)license");
|
)license");
|
||||||
|
|
||||||
|
static std::mutex midi_output_mutex;
|
||||||
|
|
||||||
void midi::Rt_Midi::list_ports(std::ostream &out) const
|
void midi::Rt_Midi::list_ports(std::ostream &out) const
|
||||||
try {
|
try {
|
||||||
RtMidiIn input;
|
RtMidiIn input;
|
||||||
@ -80,6 +83,7 @@ bool midi::Rt_Midi::supports_output() const
|
|||||||
template<std::size_t N>
|
template<std::size_t N>
|
||||||
inline void send_message(RtMidiOut &out, std::array<std::uint8_t, N> message)
|
inline void send_message(RtMidiOut &out, std::array<std::uint8_t, N> message)
|
||||||
try {
|
try {
|
||||||
|
std::lock_guard guard{midi_output_mutex};
|
||||||
out.sendMessage(message.data(), message.size());
|
out.sendMessage(message.data(), message.size());
|
||||||
} catch (RtMidiError &error) {
|
} catch (RtMidiError &error) {
|
||||||
// TODO(error)
|
// TODO(error)
|
||||||
|
@ -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`
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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> &¶meters,
|
std::vector<Ast> &¶meters,
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
@ -8,6 +8,8 @@
|
|||||||
struct Interpreter;
|
struct Interpreter;
|
||||||
struct Value;
|
struct Value;
|
||||||
|
|
||||||
|
// TODO Array should have an ability to be invoked with duration parameter like singular note
|
||||||
|
|
||||||
/// Eager Array
|
/// Eager Array
|
||||||
struct Array : Collection
|
struct Array : Collection
|
||||||
{
|
{
|
||||||
|
@ -4,6 +4,11 @@
|
|||||||
#include <musique/value/block.hh>
|
#include <musique/value/block.hh>
|
||||||
#include <musique/value/value.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
|
/// 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)
|
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
|
Result<Value> Block::index(Interpreter &i, unsigned position) const
|
||||||
{
|
{
|
||||||
ensure(parameters.empty(), "cannot index into block with parameters (for now)");
|
ensure(parameters.empty(), "cannot index into block with parameters (for now)");
|
||||||
if (body.type != Ast::Type::Sequence) {
|
if (is_ast_collection(body.type)) {
|
||||||
Try(guard_index(position, 1));
|
|
||||||
return i.eval((Ast)body);
|
|
||||||
}
|
|
||||||
|
|
||||||
Try(guard_index(position, body.arguments.size()));
|
Try(guard_index(position, body.arguments.size()));
|
||||||
return i.eval((Ast)body.arguments[position]);
|
return i.eval((Ast)body.arguments[position]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Try(guard_index(position, 1));
|
||||||
|
return i.eval((Ast)body);
|
||||||
|
}
|
||||||
|
|
||||||
usize Block::size() const
|
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
|
Result<Value> Block::operator()(Interpreter &i, std::vector<Value> arguments) const
|
||||||
|
@ -11,11 +11,11 @@ struct Env;
|
|||||||
struct Interpreter;
|
struct Interpreter;
|
||||||
struct Value;
|
struct Value;
|
||||||
|
|
||||||
|
// TODO Block should have an ability to be invoked with duration parameter like singular note
|
||||||
|
|
||||||
/// 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 +28,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;
|
||||||
|
|
||||||
|
50
musique/value/hash.cc
Normal file
50
musique/value/hash.cc
Normal 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
17
musique/value/hash.hh
Normal 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
60
musique/value/set.cc
Normal 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 ¬e : 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
36
musique/value/set.hh
Normal 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
|
@ -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
|
Result<Value> Value::operator()(Interpreter &i, std::vector<Value> args) const
|
||||||
{
|
{
|
||||||
if (auto func = get_if<Function>(data)) {
|
if (auto func = get_if<Function>(data)) {
|
||||||
@ -188,7 +193,10 @@ std::ostream& operator<<(std::ostream& os, Value const& v)
|
|||||||
[&](Nil) { os << "nil"; },
|
[&](Nil) { os << "nil"; },
|
||||||
[&](Intrinsic) { os << "<intrinisic>"; },
|
[&](Intrinsic) { os << "<intrinisic>"; },
|
||||||
[&](Block const&) { os << "<block>"; },
|
[&](Block const&) { os << "<block>"; },
|
||||||
[&](auto const& s) { os << s; }
|
[&](auto const& s) {
|
||||||
|
static_assert(requires { os << s; });
|
||||||
|
os << s;
|
||||||
|
}
|
||||||
}, v.data);
|
}, v.data);
|
||||||
return os;
|
return os;
|
||||||
}
|
}
|
||||||
@ -205,6 +213,7 @@ std::string_view type_name(Value const& v)
|
|||||||
[&](Nil const&) { return "nil"; },
|
[&](Nil const&) { return "nil"; },
|
||||||
[&](Number const&) { return "number"; },
|
[&](Number const&) { return "number"; },
|
||||||
[&](Symbol const&) { return "symbol"; },
|
[&](Symbol const&) { return "symbol"; },
|
||||||
|
[&](Set const&) { return "set"; },
|
||||||
}, v.data);
|
}, v.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,28 +237,3 @@ Result<std::vector<Value>> flatten(Interpreter &i, std::vector<Value> args)
|
|||||||
return flatten(i, std::span(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()));
|
|
||||||
}
|
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
#ifndef MUSIQUE_VALUE_HH
|
#ifndef MUSIQUE_VALUE_HH
|
||||||
#define 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/accessors.hh>
|
||||||
#include <musique/common.hh>
|
#include <musique/common.hh>
|
||||||
#include <musique/lexer/token.hh>
|
#include <musique/lexer/token.hh>
|
||||||
@ -10,6 +13,7 @@
|
|||||||
#include <musique/value/chord.hh>
|
#include <musique/value/chord.hh>
|
||||||
#include <musique/value/intrinsic.hh>
|
#include <musique/value/intrinsic.hh>
|
||||||
#include <musique/value/note.hh>
|
#include <musique/value/note.hh>
|
||||||
|
#include <musique/value/set.hh>
|
||||||
|
|
||||||
struct Nil
|
struct Nil
|
||||||
{
|
{
|
||||||
@ -40,6 +44,7 @@ struct Value
|
|||||||
Value(char const* s); ///< Create value of type symbol holding provided symbol
|
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 s); ///< Create value of type symbol holding provided symbol
|
||||||
Value(std::string_view 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
|
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
|
// TODO Most strings should not be allocated by Value, but reference to string allocated previously
|
||||||
@ -54,6 +59,7 @@ struct Value
|
|||||||
Intrinsic,
|
Intrinsic,
|
||||||
Block,
|
Block,
|
||||||
Array,
|
Array,
|
||||||
|
Set,
|
||||||
Chord,
|
Chord,
|
||||||
Macro
|
Macro
|
||||||
> data = Nil{};
|
> data = Nil{};
|
||||||
@ -108,9 +114,6 @@ inline T* get_if(Value& v) { return get_if<T>(v.data); }
|
|||||||
/// Returns type name of Value type
|
/// Returns type name of Value type
|
||||||
std::string_view type_name(Value const& v);
|
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>
|
template<typename T>
|
||||||
Result<Value> wrap_value(Result<T> &&value)
|
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::make_index_sequence<sizeof...(T)>{});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::ostream& operator<<(std::ostream& os, Value const& v);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
Loading…
Reference in New Issue
Block a user