Builtin operator handlers definiton now in separate file
Interpreter constructor and file in general is very unreadable and busy now. To limit noise separate builtin operators from src/interpreter.cc into their own file src/builtin_operators.cc This also allows to see which functions are used as implementation detail of which operations.
This commit is contained in:
parent
a3d0a942e4
commit
831de5abe5
27
Makefile
27
Makefile
@ -8,19 +8,20 @@ CXX=g++
|
|||||||
LDFLAGS=-L./lib/midi/
|
LDFLAGS=-L./lib/midi/
|
||||||
LDLIBS=-lmidi-alsa -lasound -lpthread
|
LDLIBS=-lmidi-alsa -lasound -lpthread
|
||||||
|
|
||||||
Obj= \
|
Obj= \
|
||||||
context.o \
|
builtin_operators.o \
|
||||||
environment.o \
|
context.o \
|
||||||
errors.o \
|
environment.o \
|
||||||
interpreter.o \
|
errors.o \
|
||||||
lexer.o \
|
interpreter.o \
|
||||||
lines.o \
|
lexer.o \
|
||||||
location.o \
|
lines.o \
|
||||||
number.o \
|
location.o \
|
||||||
parser.o \
|
number.o \
|
||||||
pretty.o \
|
parser.o \
|
||||||
unicode.o \
|
pretty.o \
|
||||||
unicode_tables.o \
|
unicode.o \
|
||||||
|
unicode_tables.o \
|
||||||
value.o
|
value.o
|
||||||
|
|
||||||
Tests= \
|
Tests= \
|
||||||
|
196
src/builtin_operators.cc
Normal file
196
src/builtin_operators.cc
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
#include <musique.hh>
|
||||||
|
|
||||||
|
/// Intrinsic implementation primitive to ease operation vectorization
|
||||||
|
/// @invariant args.size() == 2
|
||||||
|
Result<Value> vectorize(auto &&operation, Interpreter &interpreter, std::vector<Value> args)
|
||||||
|
{
|
||||||
|
assert(args.size() == 2, "Vectorization primitive only supports two arguments");
|
||||||
|
Array array;
|
||||||
|
|
||||||
|
auto lhs = std::move(args.front());
|
||||||
|
auto rhs = std::move(args.back());
|
||||||
|
|
||||||
|
if (is_indexable(lhs.type) && !is_indexable(rhs.type)) {
|
||||||
|
Array array;
|
||||||
|
for (auto i = 0u; i < lhs.size(); ++i) {
|
||||||
|
array.elements.push_back(
|
||||||
|
Try(operation(interpreter, { Try(lhs.index(interpreter, i)), rhs })));
|
||||||
|
}
|
||||||
|
return Value::from(std::move(array));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto i = 0u; i < rhs.size(); ++i) {
|
||||||
|
array.elements.push_back(
|
||||||
|
Try(operation(interpreter, { lhs, Try(rhs.index(interpreter, i)) })));
|
||||||
|
}
|
||||||
|
return Value::from(std::move(array));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates implementation of plus/minus operator that support following operations:
|
||||||
|
/// number, number -> number (standard math operations)
|
||||||
|
/// n: number, m: music -> music
|
||||||
|
/// m: music, n: number -> music moves m by n semitones (+ goes up, - goes down)
|
||||||
|
template<typename Binary_Operation>
|
||||||
|
static Result<Value> plus_minus_operator(Interpreter &interpreter, std::vector<Value> args)
|
||||||
|
{
|
||||||
|
static_assert(std::is_same_v<Binary_Operation, std::plus<>> || std::is_same_v<Binary_Operation, std::minus<>>,
|
||||||
|
"Error reporting depends on only one of this two types beeing provided");
|
||||||
|
|
||||||
|
using NN = Shape<Value::Type::Number, Value::Type::Number>;
|
||||||
|
using MN = Shape<Value::Type::Music, Value::Type::Number>;
|
||||||
|
using NM = Shape<Value::Type::Number, Value::Type::Music>;
|
||||||
|
|
||||||
|
if (NN::typecheck(args)) {
|
||||||
|
auto [a, b] = NN::move_from(args);
|
||||||
|
return Value::from(Binary_Operation{}(std::move(a), std::move(b)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MN::typecheck(args)) {
|
||||||
|
auto [chord, offset] = MN::move_from(args);
|
||||||
|
for (auto ¬e : chord.notes) {
|
||||||
|
note.base = Binary_Operation{}(note.base, offset.as_int());
|
||||||
|
note.simplify_inplace();
|
||||||
|
}
|
||||||
|
return Value::from(std::move(chord));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NM::typecheck(args)) {
|
||||||
|
auto [offset, chord] = NM::move_from(args);
|
||||||
|
for (auto ¬e : chord.notes) {
|
||||||
|
note.base = Binary_Operation{}(offset.as_int(), note.base);
|
||||||
|
note.simplify_inplace();
|
||||||
|
}
|
||||||
|
return Value::from(std::move(chord));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (may_be_vectorized(args)) {
|
||||||
|
return vectorize(plus_minus_operator<Binary_Operation>, interpreter, std::move(args));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Limit possibilities based on provided types
|
||||||
|
static_assert(std::is_same_v<std::plus<>, Binary_Operation> || std::is_same_v<std::minus<>, Binary_Operation>,
|
||||||
|
"Error message printing only supports operators given above");
|
||||||
|
return Error {
|
||||||
|
.details = errors::Unsupported_Types_For {
|
||||||
|
.type = errors::Unsupported_Types_For::Operator,
|
||||||
|
.name = std::is_same_v<std::plus<>, Binary_Operation> ? "+" : "-",
|
||||||
|
.possibilities = {
|
||||||
|
"(number, number) -> number",
|
||||||
|
"(music, number) -> music",
|
||||||
|
"(number, music) -> music",
|
||||||
|
"(array, number|music) -> array",
|
||||||
|
"(number|music, array) -> array",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.location = {}, // TODO fill location
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
template<typename Binary_Operation, char ...Chars>
|
||||||
|
static Result<Value> binary_operator(Interpreter& interpreter, std::vector<Value> args)
|
||||||
|
{
|
||||||
|
static constexpr char Name[] = { Chars..., '\0' };
|
||||||
|
|
||||||
|
using NN = Shape<Value::Type::Number, Value::Type::Number>;
|
||||||
|
|
||||||
|
if (NN::typecheck(args)) {
|
||||||
|
auto [lhs, rhs] = NN::move_from(args);
|
||||||
|
return Value::from(Binary_Operation{}(lhs, rhs));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (may_be_vectorized(args)) {
|
||||||
|
return vectorize(binary_operator<Binary_Operation, Chars...>, interpreter, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Error {
|
||||||
|
.details = errors::Unsupported_Types_For {
|
||||||
|
.type = errors::Unsupported_Types_For::Operator,
|
||||||
|
.name = Name,
|
||||||
|
.possibilities = {
|
||||||
|
"(number, number) -> number",
|
||||||
|
"(array, number) -> array",
|
||||||
|
"(number, array) -> array"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.location = {}, // TODO fill location
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Binary_Predicate>
|
||||||
|
static Result<Value> equality_operator(Interpreter&, std::vector<Value> args)
|
||||||
|
{
|
||||||
|
assert(args.size() == 2, "(in)Equality only allows for 2 operands"); // TODO(assert)
|
||||||
|
return Value::from(Binary_Predicate{}(std::move(args.front()), std::move(args.back())));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Binary_Predicate>
|
||||||
|
static Result<Value> comparison_operator(Interpreter&, std::vector<Value> args)
|
||||||
|
{
|
||||||
|
assert(args.size() == 2, "Operator handler cannot accept any shape different then 2 arguments");
|
||||||
|
return Value::from(Binary_Predicate{}(args.front(), args.back()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Operators definition table
|
||||||
|
static constexpr auto Operators = std::array {
|
||||||
|
std::tuple { "+", plus_minus_operator<std::plus<>> },
|
||||||
|
std::tuple { "-", plus_minus_operator<std::minus<>> },
|
||||||
|
std::tuple { "*", binary_operator<std::multiplies<>, '*'> },
|
||||||
|
std::tuple { "/", binary_operator<std::divides<>, '/'> },
|
||||||
|
|
||||||
|
std::tuple { "<", comparison_operator<std::less<>> },
|
||||||
|
std::tuple { ">", comparison_operator<std::greater<>> },
|
||||||
|
std::tuple { "<=", comparison_operator<std::less_equal<>> },
|
||||||
|
std::tuple { ">=", comparison_operator<std::greater_equal<>> },
|
||||||
|
|
||||||
|
std::tuple { "==", equality_operator<std::equal_to<>> },
|
||||||
|
std::tuple { "!=", equality_operator<std::not_equal_to<>> },
|
||||||
|
|
||||||
|
std::tuple { ".",
|
||||||
|
+[](Interpreter &i, std::vector<Value> args) -> Result<Value> {
|
||||||
|
assert(args.size() == 2, "Operator . requires two arguments"); // TODO(assert)
|
||||||
|
assert(args.back().type == Value::Type::Number, "Only numbers can be used for indexing"); // TODO(assert)
|
||||||
|
return std::move(args.front()).index(i, std::move(args.back()).n.as_int());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
std::tuple { "&",
|
||||||
|
+[](Interpreter&, std::vector<Value> args) -> Result<Value> {
|
||||||
|
using Chord_Chord = Shape<Value::Type::Music, Value::Type::Music>;
|
||||||
|
|
||||||
|
if (Chord_Chord::typecheck(args)) {
|
||||||
|
auto [lhs, rhs] = Chord_Chord::move_from(args);
|
||||||
|
auto &l = lhs.notes;
|
||||||
|
auto &r = rhs.notes;
|
||||||
|
|
||||||
|
// Append one set of notes to another to make bigger chord!
|
||||||
|
l.reserve(l.size() + r.size());
|
||||||
|
std::move(r.begin(), r.end(), std::back_inserter(l));
|
||||||
|
|
||||||
|
return Value::from(lhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Error {
|
||||||
|
.details = errors::Unsupported_Types_For {
|
||||||
|
.type = errors::Unsupported_Types_For::Operator,
|
||||||
|
.name = "&",
|
||||||
|
.possibilities = {
|
||||||
|
"(music, music) -> music",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.location = {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// All operators should be defined here except 'and' and 'or' which handle evaluation differently
|
||||||
|
// and are need unevaluated expressions for their proper evaluation. Exclusion of them is marked
|
||||||
|
// as subtraction of total excluded operators from expected constant
|
||||||
|
static_assert(Operators.size() == Operators_Count - 2, "All operators handlers are defined here");
|
||||||
|
|
||||||
|
void Interpreter::register_builtin_operators()
|
||||||
|
{
|
||||||
|
// Set all predefined operators into operators array
|
||||||
|
for (auto &[name, fptr] : Operators) { operators[name] = fptr; }
|
||||||
|
}
|
@ -68,60 +68,6 @@ void Interpreter::register_callbacks()
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Intrinsic implementation primitive providing a short way to check if arguments match required type signature
|
|
||||||
static inline bool typecheck(std::vector<Value> const& args, auto const& ...expected_types)
|
|
||||||
{
|
|
||||||
return (args.size() == sizeof...(expected_types)) &&
|
|
||||||
[&args, expected_types...]<std::size_t ...I>(std::index_sequence<I...>) {
|
|
||||||
return ((expected_types == args[I].type) && ...);
|
|
||||||
} (std::make_index_sequence<sizeof...(expected_types)>{});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Intrinsic implementation primitive providing a short way to move values based on matched type signature
|
|
||||||
static inline bool typecheck_front(std::vector<Value> const& args, auto const& ...expected_types)
|
|
||||||
{
|
|
||||||
return (args.size() >= sizeof...(expected_types)) &&
|
|
||||||
[&args, expected_types...]<std::size_t ...I>(std::index_sequence<I...>) {
|
|
||||||
return ((expected_types == args[I].type) && ...);
|
|
||||||
} (std::make_index_sequence<sizeof...(expected_types)>{});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Intrinsic implementation primitive providing a short way to move values based on matched type signature
|
|
||||||
template<auto ...Types>
|
|
||||||
static inline auto move_from(std::vector<Value>& args)
|
|
||||||
{
|
|
||||||
return [&args]<std::size_t ...I>(std::index_sequence<I...>) {
|
|
||||||
return std::tuple { (std::move(args[I]).*(Member_For_Value_Type<Types>::value)) ... };
|
|
||||||
} (std::make_index_sequence<sizeof...(Types)>{});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Shape abstraction to define what types are required once
|
|
||||||
template<auto ...Types>
|
|
||||||
struct Shape
|
|
||||||
{
|
|
||||||
static inline auto move_from(std::vector<Value>& args) { return ::move_from<Types...>(args); }
|
|
||||||
static inline auto typecheck(std::vector<Value>& args) { return ::typecheck(args, Types...); }
|
|
||||||
static inline auto typecheck_front(std::vector<Value>& args) { return ::typecheck_front(args, Types...); }
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Returns if type can be indexed
|
|
||||||
static constexpr bool is_indexable(Value::Type type)
|
|
||||||
{
|
|
||||||
return type == Value::Type::Array || type == Value::Type::Block;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns if type can be called
|
|
||||||
static constexpr bool is_callable(Value::Type type)
|
|
||||||
{
|
|
||||||
return type == Value::Type::Block || type == Value::Type::Intrinsic;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Binary operation may be vectorized when there are two argument which one is indexable and other is not
|
|
||||||
static bool may_be_vectorized(std::vector<Value> const& args)
|
|
||||||
{
|
|
||||||
return args.size() == 2 && (is_indexable(args[0].type) != is_indexable(args[1].type));
|
|
||||||
}
|
|
||||||
|
|
||||||
static Result<Array> into_flat_array(Interpreter &i, std::span<Value> args)
|
static Result<Array> into_flat_array(Interpreter &i, std::span<Value> args)
|
||||||
{
|
{
|
||||||
Array array;
|
Array array;
|
||||||
@ -149,136 +95,6 @@ static Result<Array> into_flat_array(Interpreter &i, std::vector<Value> args)
|
|||||||
return into_flat_array(i, std::span(args));
|
return into_flat_array(i, std::span(args));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Intrinsic implementation primitive to ease operation vectorization
|
|
||||||
/// @invariant args.size() == 2
|
|
||||||
Result<Value> vectorize(auto &&operation, Interpreter &interpreter, std::vector<Value> args)
|
|
||||||
{
|
|
||||||
assert(args.size() == 2, "Vectorization primitive only supports two arguments");
|
|
||||||
Array array;
|
|
||||||
|
|
||||||
auto lhs = std::move(args.front());
|
|
||||||
auto rhs = std::move(args.back());
|
|
||||||
|
|
||||||
if (is_indexable(lhs.type) && !is_indexable(rhs.type)) {
|
|
||||||
Array array;
|
|
||||||
for (auto i = 0u; i < lhs.size(); ++i) {
|
|
||||||
array.elements.push_back(
|
|
||||||
Try(operation(interpreter, { Try(lhs.index(interpreter, i)), rhs })));
|
|
||||||
}
|
|
||||||
return Value::from(std::move(array));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto i = 0u; i < rhs.size(); ++i) {
|
|
||||||
array.elements.push_back(
|
|
||||||
Try(operation(interpreter, { lhs, Try(rhs.index(interpreter, i)) })));
|
|
||||||
}
|
|
||||||
return Value::from(std::move(array));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates implementation of plus/minus operator that support following operations:
|
|
||||||
/// number, number -> number (standard math operations)
|
|
||||||
/// n: number, m: music -> music
|
|
||||||
/// m: music, n: number -> music moves m by n semitones (+ goes up, - goes down)
|
|
||||||
template<typename Binary_Operation>
|
|
||||||
static Result<Value> plus_minus_operator(Interpreter &interpreter, std::vector<Value> args)
|
|
||||||
{
|
|
||||||
static_assert(std::is_same_v<Binary_Operation, std::plus<>> || std::is_same_v<Binary_Operation, std::minus<>>,
|
|
||||||
"Error reporting depends on only one of this two types beeing provided");
|
|
||||||
|
|
||||||
using NN = Shape<Value::Type::Number, Value::Type::Number>;
|
|
||||||
using MN = Shape<Value::Type::Music, Value::Type::Number>;
|
|
||||||
using NM = Shape<Value::Type::Number, Value::Type::Music>;
|
|
||||||
|
|
||||||
if (NN::typecheck(args)) {
|
|
||||||
auto [a, b] = NN::move_from(args);
|
|
||||||
return Value::from(Binary_Operation{}(std::move(a), std::move(b)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (MN::typecheck(args)) {
|
|
||||||
auto [chord, offset] = MN::move_from(args);
|
|
||||||
for (auto ¬e : chord.notes) {
|
|
||||||
note.base = Binary_Operation{}(note.base, offset.as_int());
|
|
||||||
note.simplify_inplace();
|
|
||||||
}
|
|
||||||
return Value::from(std::move(chord));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (NM::typecheck(args)) {
|
|
||||||
auto [offset, chord] = NM::move_from(args);
|
|
||||||
for (auto ¬e : chord.notes) {
|
|
||||||
note.base = Binary_Operation{}(offset.as_int(), note.base);
|
|
||||||
note.simplify_inplace();
|
|
||||||
}
|
|
||||||
return Value::from(std::move(chord));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (may_be_vectorized(args)) {
|
|
||||||
return vectorize(plus_minus_operator<Binary_Operation>, interpreter, std::move(args));
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO Limit possibilities based on provided types
|
|
||||||
static_assert(std::is_same_v<std::plus<>, Binary_Operation> || std::is_same_v<std::minus<>, Binary_Operation>,
|
|
||||||
"Error message printing only supports operators given above");
|
|
||||||
return Error {
|
|
||||||
.details = errors::Unsupported_Types_For {
|
|
||||||
.type = errors::Unsupported_Types_For::Operator,
|
|
||||||
.name = std::is_same_v<std::plus<>, Binary_Operation> ? "+" : "-",
|
|
||||||
.possibilities = {
|
|
||||||
"(number, number) -> number",
|
|
||||||
"(music, number) -> music",
|
|
||||||
"(number, music) -> music",
|
|
||||||
"(array, number|music) -> array",
|
|
||||||
"(number|music, array) -> array",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
.location = {}, // TODO fill location
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
template<typename Binary_Operation, char ...Chars>
|
|
||||||
static Result<Value> binary_operator(Interpreter& interpreter, std::vector<Value> args)
|
|
||||||
{
|
|
||||||
static constexpr char Name[] = { Chars..., '\0' };
|
|
||||||
|
|
||||||
using NN = Shape<Value::Type::Number, Value::Type::Number>;
|
|
||||||
|
|
||||||
if (NN::typecheck(args)) {
|
|
||||||
auto [lhs, rhs] = NN::move_from(args);
|
|
||||||
return Value::from(Binary_Operation{}(lhs, rhs));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (may_be_vectorized(args)) {
|
|
||||||
return vectorize(binary_operator<Binary_Operation, Chars...>, interpreter, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Error {
|
|
||||||
.details = errors::Unsupported_Types_For {
|
|
||||||
.type = errors::Unsupported_Types_For::Operator,
|
|
||||||
.name = Name,
|
|
||||||
.possibilities = {
|
|
||||||
"(number, number) -> number",
|
|
||||||
"(array, number) -> array",
|
|
||||||
"(number, array) -> array"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
.location = {}, // TODO fill location
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Binary_Predicate>
|
|
||||||
static Result<Value> equality_operator(Interpreter&, std::vector<Value> args)
|
|
||||||
{
|
|
||||||
assert(args.size() == 2, "(in)Equality only allows for 2 operands"); // TODO(assert)
|
|
||||||
return Value::from(Binary_Predicate{}(std::move(args.front()), std::move(args.back())));
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Binary_Predicate>
|
|
||||||
static Result<Value> comparison_operator(Interpreter&, std::vector<Value> args)
|
|
||||||
{
|
|
||||||
assert(args.size() == 2, "Operator handler cannot accept any shape different then 2 arguments");
|
|
||||||
return Value::from(Binary_Predicate{}(args.front(), args.back()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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()
|
||||||
@ -710,68 +526,9 @@ error:
|
|||||||
global.force_define("pgmchange", pgmchange);
|
global.force_define("pgmchange", pgmchange);
|
||||||
global.force_define("program_change", pgmchange);
|
global.force_define("program_change", pgmchange);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Operators definition table
|
|
||||||
static constexpr auto Operators = std::array {
|
|
||||||
std::tuple { "+", plus_minus_operator<std::plus<>> },
|
|
||||||
std::tuple { "-", plus_minus_operator<std::minus<>> },
|
|
||||||
std::tuple { "*", binary_operator<std::multiplies<>, '*'> },
|
|
||||||
std::tuple { "/", binary_operator<std::divides<>, '/'> },
|
|
||||||
|
|
||||||
std::tuple { "<", comparison_operator<std::less<>> },
|
|
||||||
std::tuple { ">", comparison_operator<std::greater<>> },
|
|
||||||
std::tuple { "<=", comparison_operator<std::less_equal<>> },
|
|
||||||
std::tuple { ">=", comparison_operator<std::greater_equal<>> },
|
|
||||||
|
|
||||||
std::tuple { "==", equality_operator<std::equal_to<>> },
|
|
||||||
std::tuple { "!=", equality_operator<std::not_equal_to<>> },
|
|
||||||
|
|
||||||
std::tuple { ".",
|
|
||||||
+[](Interpreter &i, std::vector<Value> args) -> Result<Value> {
|
|
||||||
assert(args.size() == 2, "Operator . requires two arguments"); // TODO(assert)
|
|
||||||
assert(args.back().type == Value::Type::Number, "Only numbers can be used for indexing"); // TODO(assert)
|
|
||||||
return std::move(args.front()).index(i, std::move(args.back()).n.as_int());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
std::tuple { "&",
|
|
||||||
+[](Interpreter&, std::vector<Value> args) -> Result<Value> {
|
|
||||||
using Chord_Chord = Shape<Value::Type::Music, Value::Type::Music>;
|
|
||||||
|
|
||||||
if (Chord_Chord::typecheck(args)) {
|
|
||||||
auto [lhs, rhs] = Chord_Chord::move_from(args);
|
|
||||||
auto &l = lhs.notes;
|
|
||||||
auto &r = rhs.notes;
|
|
||||||
|
|
||||||
// Append one set of notes to another to make bigger chord!
|
|
||||||
l.reserve(l.size() + r.size());
|
|
||||||
std::move(r.begin(), r.end(), std::back_inserter(l));
|
|
||||||
|
|
||||||
return Value::from(lhs);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Error {
|
|
||||||
.details = errors::Unsupported_Types_For {
|
|
||||||
.type = errors::Unsupported_Types_For::Operator,
|
|
||||||
.name = "&",
|
|
||||||
.possibilities = {
|
|
||||||
"(music, music) -> music",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
.location = {}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// All operators should be defined here except 'and' and 'or' which handle evaluation differently
|
|
||||||
// and are need unevaluated expressions for their proper evaluation. Exclusion of them is marked
|
|
||||||
// as subtraction of total excluded operators from expected constant
|
|
||||||
static_assert(Operators.size() == Operators_Count - 2, "All operators handlers are defined here");
|
|
||||||
|
|
||||||
// Set all predefined operators into operators array
|
|
||||||
for (auto &[name, fptr] : Operators) { operators[name] = fptr; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
register_builtin_operators();
|
||||||
}
|
}
|
||||||
|
|
||||||
Interpreter::~Interpreter()
|
Interpreter::~Interpreter()
|
||||||
|
@ -952,6 +952,16 @@ struct Interpreter
|
|||||||
|
|
||||||
/// Play note resolving any missing parameters with context via `midi_connection` member.
|
/// Play note resolving any missing parameters with context via `midi_connection` member.
|
||||||
void play(Chord);
|
void play(Chord);
|
||||||
|
|
||||||
|
/// Add to global interpreter scope all builtin function definitions
|
||||||
|
///
|
||||||
|
/// Invoked during construction
|
||||||
|
void register_builtin_functions();
|
||||||
|
|
||||||
|
/// Add to interpreter operators table all operators
|
||||||
|
///
|
||||||
|
/// Invoked during construction
|
||||||
|
void register_builtin_operators();
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace errors
|
namespace errors
|
||||||
@ -959,3 +969,57 @@ namespace errors
|
|||||||
[[noreturn]]
|
[[noreturn]]
|
||||||
void all_tokens_were_not_parsed(std::span<Token>);
|
void all_tokens_were_not_parsed(std::span<Token>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Intrinsic implementation primitive providing a short way to check if arguments match required type signature
|
||||||
|
static inline bool typecheck(std::vector<Value> const& args, auto const& ...expected_types)
|
||||||
|
{
|
||||||
|
return (args.size() == sizeof...(expected_types)) &&
|
||||||
|
[&args, expected_types...]<std::size_t ...I>(std::index_sequence<I...>) {
|
||||||
|
return ((expected_types == args[I].type) && ...);
|
||||||
|
} (std::make_index_sequence<sizeof...(expected_types)>{});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Intrinsic implementation primitive providing a short way to move values based on matched type signature
|
||||||
|
static inline bool typecheck_front(std::vector<Value> const& args, auto const& ...expected_types)
|
||||||
|
{
|
||||||
|
return (args.size() >= sizeof...(expected_types)) &&
|
||||||
|
[&args, expected_types...]<std::size_t ...I>(std::index_sequence<I...>) {
|
||||||
|
return ((expected_types == args[I].type) && ...);
|
||||||
|
} (std::make_index_sequence<sizeof...(expected_types)>{});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Intrinsic implementation primitive providing a short way to move values based on matched type signature
|
||||||
|
template<auto ...Types>
|
||||||
|
static inline auto move_from(std::vector<Value>& args)
|
||||||
|
{
|
||||||
|
return [&args]<std::size_t ...I>(std::index_sequence<I...>) {
|
||||||
|
return std::tuple { (std::move(args[I]).*(Member_For_Value_Type<Types>::value)) ... };
|
||||||
|
} (std::make_index_sequence<sizeof...(Types)>{});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shape abstraction to define what types are required once
|
||||||
|
template<auto ...Types>
|
||||||
|
struct Shape
|
||||||
|
{
|
||||||
|
static inline auto move_from(std::vector<Value>& args) { return ::move_from<Types...>(args); }
|
||||||
|
static inline auto typecheck(std::vector<Value>& args) { return ::typecheck(args, Types...); }
|
||||||
|
static inline auto typecheck_front(std::vector<Value>& args) { return ::typecheck_front(args, Types...); }
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Returns if type can be indexed
|
||||||
|
static constexpr bool is_indexable(Value::Type type)
|
||||||
|
{
|
||||||
|
return type == Value::Type::Array || type == Value::Type::Block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns if type can be called
|
||||||
|
static constexpr bool is_callable(Value::Type type)
|
||||||
|
{
|
||||||
|
return type == Value::Type::Block || type == Value::Type::Intrinsic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Binary operation may be vectorized when there are two argument which one is indexable and other is not
|
||||||
|
static inline bool may_be_vectorized(std::vector<Value> const& args)
|
||||||
|
{
|
||||||
|
return args.size() == 2 && (is_indexable(args[0].type) != is_indexable(args[1].type));
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user