Made context stack a tree; fixed function name in error printing

This commit is contained in:
Robert Bendun 2022-11-21 17:03:14 +01:00
parent a32bfa24d3
commit 7f73f3dcad
7 changed files with 51 additions and 35 deletions

View File

@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Fixed
- release build script was producing executable with wrong path
- `examples/for-elise.mq` had bug in it
- in error reporting printing garbage instead of function name was fixed
## [0.3.0]
### Added ### Added
- new builtins: map, while, set_len, set_oct, duration, pick - new builtins: map, while, set_len, set_oct, duration, pick

View File

@ -97,7 +97,7 @@ namespace errors
enum Type { Operator, Function } type; enum Type { Operator, Function } type;
/// Name of operation /// Name of operation
std::string_view name; std::string name;
/// Possible ways to use it correctly /// Possible ways to use it correctly
std::vector<std::string> possibilities; std::vector<std::string> possibilities;

View File

@ -87,15 +87,15 @@ static Result<Value> ctx_read_write_property(Interpreter &interpreter, std::vect
using Member_Type = std::remove_cvref_t<decltype(std::declval<Context>().*(Mem_Ptr))>; using Member_Type = std::remove_cvref_t<decltype(std::declval<Context>().*(Mem_Ptr))>;
if (args.size() == 0) { if (args.size() == 0) {
return Number(interpreter.context_stack.back().*(Mem_Ptr)); return Number((*interpreter.current_context).*(Mem_Ptr));
} }
ensure(std::holds_alternative<Number>(args.front().data), "Ctx only holds numeric values"); ensure(std::holds_alternative<Number>(args.front().data), "Ctx only holds numeric values");
if constexpr (std::is_same_v<Member_Type, Number>) { if constexpr (std::is_same_v<Member_Type, Number>) {
interpreter.context_stack.back().*(Mem_Ptr) = std::get<Number>(args.front().data); (*interpreter.current_context).*(Mem_Ptr) = std::get<Number>(args.front().data);
} else { } else {
interpreter.context_stack.back().*(Mem_Ptr) = static_cast<Member_Type>( (*interpreter.current_context).*(Mem_Ptr) = static_cast<Member_Type>(
std::get<Number>(args.front().data).as_int() std::get<Number>(args.front().data).as_int()
); );
} }
@ -266,19 +266,20 @@ static std::optional<Error> action_play(Interpreter &i, Value v)
/// Play notes /// Play notes
template<With_Index_Operator Container = std::vector<Value>> template<With_Index_Operator Container = std::vector<Value>>
static inline Result<Value> builtin_play(Interpreter &i, Container args) static inline Result<Value> builtin_play(Interpreter &interpreter, Container args)
{ {
Try(ensure_midi_connection_available(i, "play")); Try(ensure_midi_connection_available(interpreter, "play"));
auto previous_action = std::exchange(i.default_action, action_play); auto const previous_action = std::exchange(interpreter.default_action, action_play);
i.context_stack.push_back(i.context_stack.back()); auto const previous_context = std::exchange(interpreter.current_context,
std::make_shared<Context>(*interpreter.current_context));
auto const finally = [&] { auto const finally = [&] {
i.default_action = std::move(previous_action); interpreter.default_action = std::move(previous_action);
i.context_stack.pop_back(); interpreter.current_context = previous_context;
}; };
for (auto &el : args) { for (auto &el : args) {
if (std::optional<Error> error = sequential_play(i, std::move(el))) { if (std::optional<Error> error = sequential_play(interpreter, std::move(el))) {
finally(); finally();
return *std::move(error); return *std::move(error);
} }
@ -289,19 +290,19 @@ static inline Result<Value> builtin_play(Interpreter &i, Container args)
} }
/// Play first argument while playing all others /// Play first argument while playing all others
static Result<Value> builtin_par(Interpreter &i, std::vector<Value> args) { static Result<Value> builtin_par(Interpreter &interpreter, std::vector<Value> args) {
Try(ensure_midi_connection_available(i, "par")); Try(ensure_midi_connection_available(interpreter, "par"));
ensure(args.size() >= 1, "par only makes sense for at least one argument"); // TODO(assert) ensure(args.size() >= 1, "par only makes sense for at least one argument"); // TODO(assert)
if (args.size() == 1) { if (args.size() == 1) {
auto chord = get_if<Chord>(args.front()); auto chord = get_if<Chord>(args.front());
ensure(chord, "Par expects music value as first argument"); // TODO(assert) ensure(chord, "Par expects music value as first argument"); // TODO(assert)
Try(i.play(std::move(*chord))); Try(interpreter.play(std::move(*chord)));
return Value{}; return Value{};
} }
// Create chord that should sustain during playing of all other notes // Create chord that should sustain during playing of all other notes
auto &ctx = i.context_stack.back(); auto &ctx = *interpreter.current_context;
auto chord = get_if<Chord>(args.front()); auto chord = get_if<Chord>(args.front());
ensure(chord, "par expects music value as first argument"); // TODO(assert) ensure(chord, "par expects music value as first argument"); // TODO(assert)
@ -309,15 +310,15 @@ static Result<Value> builtin_par(Interpreter &i, std::vector<Value> args) {
for (auto const& note : chord->notes) { for (auto const& note : chord->notes) {
if (note.base) { if (note.base) {
i.midi_connection->send_note_on(0, *note.into_midi_note(), 127); interpreter.midi_connection->send_note_on(0, *note.into_midi_note(), 127);
} }
} }
auto result = builtin_play(i, std::span(args).subspan(1)); 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) {
i.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;
@ -386,7 +387,7 @@ static Result<Value> builtin_sim(Interpreter &interpreter, std::vector<Value> ar
}; };
std::vector<Instruction> schedule; std::vector<Instruction> schedule;
auto const& ctx = interpreter.context_stack.back(); auto const& ctx = *interpreter.current_context;
for (auto const& track : tracks) { for (auto const& track : tracks) {
auto passed_time = Number(0); auto passed_time = Number(0);
@ -997,19 +998,19 @@ static Result<Value> builtin_chord(Interpreter &i, std::vector<Value> args)
} }
/// Send MIDI message Note On /// Send MIDI message Note On
static Result<Value> builtin_note_on(Interpreter &i, std::vector<Value> args) static Result<Value> builtin_note_on(Interpreter &interpreter, std::vector<Value> args)
{ {
if (auto a = match<Number, Number, Number>(args)) { if (auto a = match<Number, Number, Number>(args)) {
auto [chan, note, vel] = *a; auto [chan, note, vel] = *a;
i.midi_connection->send_note_on(chan.as_int(), note.as_int(), vel.as_int()); interpreter.midi_connection->send_note_on(chan.as_int(), note.as_int(), vel.as_int());
return Value {}; return Value {};
} }
if (auto a = match<Number, Chord, Number>(args)) { if (auto a = match<Number, Chord, Number>(args)) {
auto [chan, chord, vel] = *a; auto [chan, chord, vel] = *a;
for (auto note : chord.notes) { for (auto note : chord.notes) {
note = i.context_stack.back().fill(note); note = interpreter.current_context->fill(note);
i.midi_connection->send_note_on(chan.as_int(), *note.into_midi_note(), vel.as_int()); interpreter.midi_connection->send_note_on(chan.as_int(), *note.into_midi_note(), vel.as_int());
} }
} }
@ -1027,11 +1028,11 @@ static Result<Value> builtin_note_on(Interpreter &i, std::vector<Value> args)
} }
/// Send MIDI message Note Off /// Send MIDI message Note Off
static Result<Value> builtin_note_off(Interpreter &i, std::vector<Value> args) static Result<Value> builtin_note_off(Interpreter &interpreter, std::vector<Value> args)
{ {
if (auto a = match<Number, Number>(args)) { if (auto a = match<Number, Number>(args)) {
auto [chan, note] = *a; auto [chan, note] = *a;
i.midi_connection->send_note_off(chan.as_int(), note.as_int(), 127); interpreter.midi_connection->send_note_off(chan.as_int(), note.as_int(), 127);
return Value {}; return Value {};
} }
@ -1039,8 +1040,8 @@ static Result<Value> builtin_note_off(Interpreter &i, std::vector<Value> args)
auto& [chan, chord] = *a; auto& [chan, chord] = *a;
for (auto note : chord.notes) { for (auto note : chord.notes) {
note = i.context_stack.back().fill(note); note = interpreter.current_context->fill(note);
i.midi_connection->send_note_off(chan.as_int(), *note.into_midi_note(), 127); interpreter.midi_connection->send_note_off(chan.as_int(), *note.into_midi_note(), 127);
} }
} }

View File

@ -5,6 +5,7 @@
#include <musique/value/note.hh> #include <musique/value/note.hh>
#include <musique/value/number.hh> #include <musique/value/number.hh>
#include <chrono> #include <chrono>
#include <memory>
/// Context holds default values for music related actions /// Context holds default values for music related actions
struct Context struct Context
@ -23,6 +24,8 @@ struct Context
/// 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

View File

@ -7,6 +7,9 @@
#include <random> #include <random>
#include <thread> #include <thread>
midi::Connection *Interpreter::midi_connection = nullptr;
std::unordered_map<std::string, Intrinsic> Interpreter::operators {};
/// 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()
{ {
@ -32,7 +35,7 @@ static inline void register_note_length_constants()
Interpreter::Interpreter() Interpreter::Interpreter()
{ {
// Context initialization // Context initialization
context_stack.emplace_back(); current_context = std::make_shared<Context>();
// Environment initlialization // Environment initlialization
ensure(!bool(Env::global), "Only one instance of interpreter can be at one time"); ensure(!bool(Env::global), "Only one instance of interpreter can be at one time");
@ -228,7 +231,7 @@ void Interpreter::leave_scope()
std::optional<Error> Interpreter::play(Chord chord) std::optional<Error> Interpreter::play(Chord chord)
{ {
Try(ensure_midi_connection_available(*this, "play")); Try(ensure_midi_connection_available(*this, "play"));
auto &ctx = context_stack.back(); 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)); std::this_thread::sleep_for(ctx.length_to_duration(ctx.length));
@ -389,7 +392,7 @@ static void snapshot(std::ostream& out, Value const& value) {
void Interpreter::snapshot(std::ostream& out) void Interpreter::snapshot(std::ostream& out)
{ {
auto const& ctx = context_stack.back(); auto const& ctx = *current_context;
out << ", oct " << int(ctx.octave) << '\n'; out << ", oct " << int(ctx.octave) << '\n';
out << ", len (" << ctx.length.num << "/" << ctx.length.den << ")\n"; out << ", len (" << ctx.length.num << "/" << ctx.length.den << ")\n";
out << ", bpm " << ctx.bpm << '\n'; out << ", bpm " << ctx.bpm << '\n';

View File

@ -11,24 +11,24 @@ struct Interpreter
{ {
/// MIDI connection that is used to play music. /// MIDI connection that is used to play music.
/// It's optional for simple interpreter testing. /// It's optional for simple interpreter testing.
midi::Connection *midi_connection = nullptr; static midi::Connection *midi_connection;
/// Operators defined for language /// Operators defined for language
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;
/// 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::vector<Context> context_stack; std::shared_ptr<Context> current_context;
std::function<std::optional<Error>(Interpreter&, Value)> default_action; std::function<std::optional<Error>(Interpreter&, Value)> default_action;
Interpreter(); Interpreter();
~Interpreter(); ~Interpreter();
Interpreter(Interpreter &&) = delete;
Interpreter(Interpreter const&) = delete; Interpreter(Interpreter const&) = delete;
Interpreter(Interpreter &&) = default;
/// Try to evaluate given program tree /// Try to evaluate given program tree
Result<Value> eval(Ast &&ast); Result<Value> eval(Ast &&ast);

View File

@ -26,7 +26,7 @@ cp bin/musique "$Target"/musique-x86_64-linux
sudo rm -rf bin/ sudo rm -rf bin/
make os=windows >/dev/null make os=windows >/dev/null
cp bin/musique.exe "$Target"/musique-x86_64-windows cp bin/musique.exe "$Target"/musique-x86_64-windows.exe
cp LICENSE "$Target"/LICENSE cp LICENSE "$Target"/LICENSE
cp CHANGELOG.md "$Target/CHANGELOG.md" cp CHANGELOG.md "$Target/CHANGELOG.md"
@ -37,6 +37,7 @@ chmod 0755 "$Target"/install.sh
lowdown -s doc/functions.md -m "title:Lista funkcji języka Musique" -o "$Target"/functions.html lowdown -s doc/functions.md -m "title:Lista funkcji języka Musique" -o "$Target"/functions.html
python scripts/language-cmp-cheatsheet.py doc/musique-vs-languages-cheatsheet.template python scripts/language-cmp-cheatsheet.py doc/musique-vs-languages-cheatsheet.template
mv doc/musique-vs-languages-cheatsheet.html "${Target}/musique-vs-others-cheatsheet.html" mv doc/musique-vs-languages-cheatsheet.html "${Target}/musique-vs-others-cheatsheet.html"
make doc/wprowadzenie.html && mv doc/wprowadzenie.html "${Target}/wprowadzenie.html"
git clone --recursive --quiet --depth=1 "$(git remote -v | awk '{ print $2 }' | head -n1)" "$Target"/source_code git clone --recursive --quiet --depth=1 "$(git remote -v | awk '{ print $2 }' | head -n1)" "$Target"/source_code
rm -rf "$Target"/source_code/.git rm -rf "$Target"/source_code/.git