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]
|
||||
|
||||
### 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
|
||||
|
@ -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,
|
||||
)
|
||||
|
@ -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,
|
||||
),
|
||||
|
@ -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
|
||||
|
@ -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> {
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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";
|
||||
|
@ -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
|
||||
|
@ -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 {};
|
||||
});
|
||||
|
@ -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)
|
||||
|
@ -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`
|
||||
};
|
||||
|
||||
|
@ -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> &¶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)) {
|
||||
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();
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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
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
|
||||
{
|
||||
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()));
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user