Project restructuring
include/musique.hh was getting large and musique_internal.hh emerged to help with that. Header file separation become a huge mess. The chosen approach is one hierarchy containing both header and implementation files
This commit is contained in:
parent
038de0fc27
commit
6a71614cab
5
Makefile
5
Makefile
@ -4,7 +4,7 @@ Obj= \
|
|||||||
builtin_functions.o \
|
builtin_functions.o \
|
||||||
builtin_operators.o \
|
builtin_operators.o \
|
||||||
context.o \
|
context.o \
|
||||||
environment.o \
|
env.o \
|
||||||
errors.o \
|
errors.o \
|
||||||
format.o \
|
format.o \
|
||||||
interpreter.o \
|
interpreter.o \
|
||||||
@ -29,7 +29,7 @@ bin/bestline.o: lib/bestline/bestline.c lib/bestline/bestline.h
|
|||||||
@echo "CC $@"
|
@echo "CC $@"
|
||||||
@$(CC) $< -c -O3 -o $@
|
@$(CC) $< -c -O3 -o $@
|
||||||
|
|
||||||
doc: Doxyfile src/*.cc include/*.hh
|
doc: Doxyfile musique/*.cc include/*.hh
|
||||||
doxygen
|
doxygen
|
||||||
|
|
||||||
doc-open: doc
|
doc-open: doc
|
||||||
@ -44,7 +44,6 @@ release: bin/musique
|
|||||||
install: bin/musique
|
install: bin/musique
|
||||||
scripts/install
|
scripts/install
|
||||||
|
|
||||||
|
|
||||||
.PHONY: clean doc doc-open all test unit-tests release install
|
.PHONY: clean doc doc-open all test unit-tests release install
|
||||||
|
|
||||||
$(shell mkdir -p bin/debug/tests)
|
$(shell mkdir -p bin/debug/tests)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
MAKEFLAGS="-j $(grep -c ^processor /proc/cpuinfo)"
|
MAKEFLAGS="-j $(grep -c ^processor /proc/cpuinfo)"
|
||||||
|
|
||||||
CXXFLAGS:=$(CXXFLAGS) -std=c++20 -Wall -Wextra -Werror=switch -Werror=return-type -Werror=unused-result -Wno-maybe-uninitialized
|
CXXFLAGS:=$(CXXFLAGS) -std=c++20 -Wall -Wextra -Werror=switch -Werror=return-type -Werror=unused-result -Wno-maybe-uninitialized
|
||||||
CPPFLAGS:=$(CPPFLAGS) -Ilib/expected/ -Ilib/ut/ -Ilib/midi/include -Iinclude/ -Ilib/bestline/
|
CPPFLAGS:=$(CPPFLAGS) -Ilib/expected/ -Ilib/ut/ -Ilib/midi/include -I. -Ilib/bestline/
|
||||||
|
|
||||||
RELEASE_FLAGS=-O3
|
RELEASE_FLAGS=-O3
|
||||||
DEBUG_FLAGS=-O0 -ggdb -fsanitize=undefined -DDebug
|
DEBUG_FLAGS=-O0 -ggdb -fsanitize=undefined -DDebug
|
||||||
|
1225
include/musique.hh
1225
include/musique.hh
File diff suppressed because it is too large
Load Diff
@ -1,148 +0,0 @@
|
|||||||
#ifndef Musique_Internal_HH
|
|
||||||
#define Musique_Internal_HH
|
|
||||||
|
|
||||||
#include <musique.hh>
|
|
||||||
#include <optional>
|
|
||||||
#include <numeric>
|
|
||||||
#include <ranges>
|
|
||||||
|
|
||||||
/// Allows creation of guards that ensure proper type
|
|
||||||
template<usize N>
|
|
||||||
struct Guard
|
|
||||||
{
|
|
||||||
std::string_view name;
|
|
||||||
std::array<std::string_view, N> possibilities;
|
|
||||||
errors::Unsupported_Types_For::Type type = errors::Unsupported_Types_For::Function;
|
|
||||||
|
|
||||||
inline Error yield_error() const
|
|
||||||
{
|
|
||||||
auto error = errors::Unsupported_Types_For {
|
|
||||||
.type = type,
|
|
||||||
.name = std::string(name),
|
|
||||||
.possibilities = {}
|
|
||||||
};
|
|
||||||
std::transform(possibilities.begin(), possibilities.end(), std::back_inserter(error.possibilities), [](auto s) {
|
|
||||||
return std::string(s);
|
|
||||||
});
|
|
||||||
return Error { std::move(error) };
|
|
||||||
}
|
|
||||||
|
|
||||||
inline std::optional<Error> yield_result() const
|
|
||||||
{
|
|
||||||
return yield_error();
|
|
||||||
}
|
|
||||||
|
|
||||||
inline std::optional<Error> operator()(bool(*predicate)(Value::Type), Value const& v) const
|
|
||||||
{
|
|
||||||
return predicate(v.type) ? std::optional<Error>{} : yield_result();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// 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));
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Interpreter::Incoming_Midi_Callbacks
|
|
||||||
{
|
|
||||||
Value note_on{};
|
|
||||||
Value note_off{};
|
|
||||||
|
|
||||||
inline Incoming_Midi_Callbacks() = default;
|
|
||||||
|
|
||||||
Incoming_Midi_Callbacks(Incoming_Midi_Callbacks &&) = delete;
|
|
||||||
Incoming_Midi_Callbacks(Incoming_Midi_Callbacks const&) = delete;
|
|
||||||
|
|
||||||
Incoming_Midi_Callbacks& operator=(Incoming_Midi_Callbacks &&) = delete;
|
|
||||||
Incoming_Midi_Callbacks& operator=(Incoming_Midi_Callbacks const&) = delete;
|
|
||||||
|
|
||||||
|
|
||||||
inline void add_callbacks(midi::Connection &midi, Interpreter &interpreter)
|
|
||||||
{
|
|
||||||
register_callback(midi.note_on_callback, note_on, interpreter);
|
|
||||||
register_callback(midi.note_off_callback, note_off, interpreter);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename ...T>
|
|
||||||
inline void register_callback(std::function<void(T...)> &target, Value &callback, Interpreter &i)
|
|
||||||
{
|
|
||||||
if (&callback == ¬e_on || &callback == ¬e_off) {
|
|
||||||
// This messages have MIDI note number as second value, so they should be represented
|
|
||||||
// in our own note abstraction, not as numbers.
|
|
||||||
target = [interpreter = &i, callback = &callback](T ...source_args)
|
|
||||||
{
|
|
||||||
if (callback->type != Value::Type::Nil) {
|
|
||||||
std::vector<Value> args { Value::from(Number(source_args))... };
|
|
||||||
args[1] = Value::from(Chord { .notes { Note {
|
|
||||||
.base = i32(args[1].n.num % 12),
|
|
||||||
.octave = args[1].n.num / 12
|
|
||||||
}}});
|
|
||||||
auto result = (*callback)(*interpreter, std::move(args));
|
|
||||||
// We discard this since callback is running in another thread.
|
|
||||||
(void) result;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
// Generic case, preserve all passed parameters as numbers
|
|
||||||
target = [interpreter = &i, callback = &callback](T ...source_args)
|
|
||||||
{
|
|
||||||
if (callback->type != Value::Type::Nil) {
|
|
||||||
auto result = (*callback)(*interpreter, { Value::from(Number(source_args))... });
|
|
||||||
// We discard this since callback is running in another thread.
|
|
||||||
(void) result;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class Midi_Connection_Type { Output, Input };
|
|
||||||
std::optional<Error> ensure_midi_connection_available(Interpreter&, Midi_Connection_Type, std::string_view operation_name);
|
|
||||||
|
|
||||||
constexpr std::size_t hash_combine(std::size_t lhs, std::size_t rhs) {
|
|
||||||
return lhs ^= rhs + 0x9e3779b9 + (lhs << 6) + (lhs >> 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
Result<Value> wrap_value(Result<T> &&value)
|
|
||||||
{
|
|
||||||
return std::move(value).map([](auto &&value) { return Value::from(std::move(value)); });
|
|
||||||
}
|
|
||||||
|
|
||||||
Value wrap_value(auto &&value)
|
|
||||||
{
|
|
||||||
return Value::from(std::move(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generic algorithms support
|
|
||||||
namespace algo
|
|
||||||
{
|
|
||||||
/// Check if predicate is true for all successive pairs of elements
|
|
||||||
constexpr bool pairwise_all(
|
|
||||||
std::ranges::forward_range auto &&range,
|
|
||||||
auto &&binary_predicate)
|
|
||||||
{
|
|
||||||
auto it = std::begin(range);
|
|
||||||
auto const end_it = std::end(range);
|
|
||||||
for (auto next_it = std::next(it); it != end_it && next_it != end_it; ++it, ++next_it) {
|
|
||||||
if (not binary_predicate(*it, *next_it)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fold that stops iteration on error value via Result type
|
|
||||||
template<typename T>
|
|
||||||
constexpr Result<T> fold(auto&& range, T init, auto &&reducer)
|
|
||||||
requires (is_template_v<Result, decltype(reducer(std::move(init), *range.begin()))>)
|
|
||||||
{
|
|
||||||
for (auto &&value : range) {
|
|
||||||
init = Try(reducer(std::move(init), value));
|
|
||||||
}
|
|
||||||
return init;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
43
musique/algo.hh
Normal file
43
musique/algo.hh
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#ifndef MUSIQUE_ALGO_HH
|
||||||
|
#define MUSIQUE_ALGO_HH
|
||||||
|
|
||||||
|
#include <musique/result.hh>
|
||||||
|
#include <musique/try.hh>
|
||||||
|
#include <musique/value.hh>
|
||||||
|
#include <ranges>
|
||||||
|
|
||||||
|
/// Generic algorithms support
|
||||||
|
namespace algo
|
||||||
|
{
|
||||||
|
/// Check if predicate is true for all successive pairs of elements
|
||||||
|
constexpr bool pairwise_all(
|
||||||
|
std::ranges::forward_range auto &&range,
|
||||||
|
auto &&binary_predicate)
|
||||||
|
{
|
||||||
|
auto it = std::begin(range);
|
||||||
|
auto const end_it = std::end(range);
|
||||||
|
for (auto next_it = std::next(it); it != end_it && next_it != end_it; ++it, ++next_it) {
|
||||||
|
if (not binary_predicate(*it, *next_it)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fold that stops iteration on error value via Result type
|
||||||
|
template<typename T>
|
||||||
|
constexpr Result<T> fold(auto&& range, T init, auto &&reducer)
|
||||||
|
requires (is_template_v<Result, decltype(reducer(std::move(init), *range.begin()))>)
|
||||||
|
{
|
||||||
|
for (auto &&value : range) {
|
||||||
|
init = Try(reducer(std::move(init), value));
|
||||||
|
}
|
||||||
|
return init;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Flattens one layer: `[[[1], 2], 3]` becomes `[[1], 2, 3]`
|
||||||
|
Result<std::vector<Value>> flatten(Interpreter &i, std::span<Value>);
|
||||||
|
Result<std::vector<Value>> flatten(Interpreter &i, std::vector<Value>);
|
||||||
|
|
||||||
|
#endif
|
27
musique/array.hh
Normal file
27
musique/array.hh
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#ifndef MUSIQUE_ARRAY_HH
|
||||||
|
#define MUSIQUE_ARRAY_HH
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include "result.hh"
|
||||||
|
|
||||||
|
struct Interpreter;
|
||||||
|
struct Value;
|
||||||
|
|
||||||
|
/// Eager Array
|
||||||
|
struct Array
|
||||||
|
{
|
||||||
|
/// Elements that are stored in array
|
||||||
|
std::vector<Value> elements;
|
||||||
|
|
||||||
|
/// Index element of an array
|
||||||
|
Result<Value> index(Interpreter &i, unsigned position) const;
|
||||||
|
|
||||||
|
/// Count of elements
|
||||||
|
usize size() const;
|
||||||
|
|
||||||
|
bool operator==(Array const&) const = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::ostream& operator<<(std::ostream& os, Array const& v);
|
||||||
|
|
||||||
|
#endif
|
66
musique/ast.hh
Normal file
66
musique/ast.hh
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
#ifndef MUSIQUE_AST_HH
|
||||||
|
#define MUSIQUE_AST_HH
|
||||||
|
|
||||||
|
#include "token.hh"
|
||||||
|
#include <vector>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
/// Representation of a node in program tree
|
||||||
|
struct Ast
|
||||||
|
{
|
||||||
|
/// Constructs binary operator
|
||||||
|
static Ast binary(Token, Ast lhs, Ast rhs);
|
||||||
|
|
||||||
|
/// Constructs block
|
||||||
|
static Ast block(Location location, Ast seq = sequence({}));
|
||||||
|
|
||||||
|
/// 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 = {});
|
||||||
|
|
||||||
|
/// Constructs constants, literals and variable identifiers
|
||||||
|
static Ast literal(Token);
|
||||||
|
|
||||||
|
/// Constructs sequence of operations
|
||||||
|
static Ast sequence(std::vector<Ast> call);
|
||||||
|
|
||||||
|
/// Constructs variable declaration
|
||||||
|
static Ast variable_declaration(Location loc, std::vector<Ast> lvalues, std::optional<Ast> rvalue);
|
||||||
|
|
||||||
|
/// Available ASt types
|
||||||
|
enum class Type
|
||||||
|
{
|
||||||
|
Binary, ///< Binary operator application like `1` + `2`
|
||||||
|
Block, ///< Block expressions like `[42; hello]`
|
||||||
|
Lambda, ///< Block expression beeing functions like `[i|i+1]`
|
||||||
|
Call, ///< Function call application like `print 42`
|
||||||
|
Literal, ///< Compile time known constant like `c` or `1`
|
||||||
|
Sequence, ///< Several expressions sequences like `42`, `42; 32`
|
||||||
|
Variable_Declaration, ///< Declaration of a variable with optional value assigment like `var x = 10` or `var y`
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Type of AST node
|
||||||
|
Type type;
|
||||||
|
|
||||||
|
/// Location that introduced this node
|
||||||
|
Location location;
|
||||||
|
|
||||||
|
/// Associated token
|
||||||
|
Token token;
|
||||||
|
|
||||||
|
/// Child nodes
|
||||||
|
std::vector<Ast> arguments{};
|
||||||
|
};
|
||||||
|
|
||||||
|
bool operator==(Ast const& lhs, Ast const& rhs);
|
||||||
|
std::ostream& operator<<(std::ostream& os, Ast::Type type);
|
||||||
|
std::ostream& operator<<(std::ostream& os, Ast const& tree);
|
||||||
|
|
||||||
|
/// Pretty print program tree for debugging purposes
|
||||||
|
void dump(Ast const& ast, unsigned indent = 0);
|
||||||
|
|
||||||
|
template<> struct std::hash<Ast> { std::size_t operator()(Ast const&) const; };
|
||||||
|
|
||||||
|
#endif
|
40
musique/block.hh
Normal file
40
musique/block.hh
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
#ifndef MUSIQUE_BLOCK_HH
|
||||||
|
#define MUSIQUE_BLOCK_HH
|
||||||
|
|
||||||
|
#include "result.hh"
|
||||||
|
#include "ast.hh"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
struct Env;
|
||||||
|
struct Interpreter;
|
||||||
|
struct Value;
|
||||||
|
|
||||||
|
using Intrinsic = Result<Value>(*)(Interpreter &i, std::vector<Value>);
|
||||||
|
|
||||||
|
/// Lazy Array / Continuation / Closure type thingy
|
||||||
|
struct Block
|
||||||
|
{
|
||||||
|
/// Location of definition / creation
|
||||||
|
Location location;
|
||||||
|
|
||||||
|
/// Names of expected parameters
|
||||||
|
std::vector<std::string> parameters;
|
||||||
|
|
||||||
|
/// Body that will be executed
|
||||||
|
Ast body;
|
||||||
|
|
||||||
|
/// Context from which block was created. Used for closures
|
||||||
|
std::shared_ptr<Env> context;
|
||||||
|
|
||||||
|
/// Calling block
|
||||||
|
Result<Value> operator()(Interpreter &i, std::vector<Value> params);
|
||||||
|
|
||||||
|
/// Indexing block
|
||||||
|
Result<Value> index(Interpreter &i, unsigned position) const;
|
||||||
|
|
||||||
|
/// Count of elements in block
|
||||||
|
usize size() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
@ -1,5 +1,10 @@
|
|||||||
#include <musique.hh>
|
#include <musique/algo.hh>
|
||||||
#include <musique_internal.hh>
|
#include <musique/env.hh>
|
||||||
|
#include <musique/guard.hh>
|
||||||
|
#include <musique/incoming_midi.hh>
|
||||||
|
#include <musique/interpreter.hh>
|
||||||
|
#include <musique/try.hh>
|
||||||
|
#include <musique/typecheck.hh>
|
||||||
|
|
||||||
#include <random>
|
#include <random>
|
||||||
#include <memory>
|
#include <memory>
|
@ -1,6 +1,9 @@
|
|||||||
#include <musique.hh>
|
|
||||||
#include <musique_internal.hh>
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <musique/algo.hh>
|
||||||
|
#include <musique/guard.hh>
|
||||||
|
#include <musique/interpreter.hh>
|
||||||
|
#include <musique/try.hh>
|
||||||
|
#include <musique/typecheck.hh>
|
||||||
|
|
||||||
/// Intrinsic implementation primitive to ease operation vectorization
|
/// Intrinsic implementation primitive to ease operation vectorization
|
||||||
static Result<Value> vectorize(auto &&operation, Interpreter &interpreter, Value lhs, Value rhs)
|
static Result<Value> vectorize(auto &&operation, Interpreter &interpreter, Value lhs, Value rhs)
|
22
musique/chord.hh
Normal file
22
musique/chord.hh
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "note.hh"
|
||||||
|
|
||||||
|
struct Interpreter;
|
||||||
|
struct Value;
|
||||||
|
|
||||||
|
/// Represantation of simultaneously played notes, aka musical chord
|
||||||
|
struct Chord
|
||||||
|
{
|
||||||
|
std::vector<Note> notes; ///< Notes composing a chord
|
||||||
|
|
||||||
|
/// Parse chord literal from provided source
|
||||||
|
static Chord from(std::string_view source);
|
||||||
|
|
||||||
|
bool operator==(Chord const&) const = default;
|
||||||
|
|
||||||
|
/// Fill length and octave or sequence multiple chords
|
||||||
|
Result<Value> operator()(Interpreter &i, std::vector<Value> args);
|
||||||
|
};
|
||||||
|
|
||||||
|
std::ostream& operator<<(std::ostream& os, Chord const& chord);
|
52
musique/common.hh
Normal file
52
musique/common.hh
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
#ifndef MUSIQUE_COMMON_HH
|
||||||
|
#define MUSIQUE_COMMON_HH
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
using namespace std::string_literals;
|
||||||
|
using namespace std::string_view_literals;
|
||||||
|
|
||||||
|
using u8 = std::uint8_t;
|
||||||
|
using u16 = std::uint16_t;
|
||||||
|
using u32 = std::uint32_t;
|
||||||
|
using u64 = std::uint64_t;
|
||||||
|
|
||||||
|
using i8 = std::int8_t;
|
||||||
|
using i16 = std::int16_t;
|
||||||
|
using i32 = std::int32_t;
|
||||||
|
using i64 = std::int64_t;
|
||||||
|
|
||||||
|
using usize = std::size_t;
|
||||||
|
using isize = std::ptrdiff_t;
|
||||||
|
|
||||||
|
/// Combine several lambdas into one for visiting std::variant
|
||||||
|
template<typename ...Lambdas>
|
||||||
|
struct Overloaded : Lambdas... { using Lambdas::operator()...; };
|
||||||
|
|
||||||
|
/// Returns if provided thingy is a given template
|
||||||
|
template<template<typename ...> typename Template, typename>
|
||||||
|
struct is_template : std::false_type {};
|
||||||
|
|
||||||
|
template<template<typename ...> typename Template, typename ...T>
|
||||||
|
struct is_template<Template, Template<T...>> : std::true_type {};
|
||||||
|
|
||||||
|
/// Returns if provided thingy is a given template
|
||||||
|
template<template<typename ...> typename Template, typename T>
|
||||||
|
constexpr auto is_template_v = is_template<Template, T>::value;
|
||||||
|
|
||||||
|
/// Drop in replacement for bool when C++ impilcit conversions stand in your way
|
||||||
|
struct Explicit_Bool
|
||||||
|
{
|
||||||
|
bool value;
|
||||||
|
constexpr Explicit_Bool(bool b) : value(b) { }
|
||||||
|
constexpr Explicit_Bool(auto &&) = delete;
|
||||||
|
constexpr operator bool() const { return value; }
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr std::size_t hash_combine(std::size_t lhs, std::size_t rhs) {
|
||||||
|
return lhs ^= rhs + 0x9e3779b9 + (lhs << 6) + (lhs >> 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -1,4 +1,4 @@
|
|||||||
#include <musique.hh>
|
#include <musique/context.hh>
|
||||||
|
|
||||||
Note Context::fill(Note note) const
|
Note Context::fill(Note note) const
|
||||||
{
|
{
|
28
musique/context.hh
Normal file
28
musique/context.hh
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
#ifndef MUSIQUE_CONTEXT_HH
|
||||||
|
#define MUSIQUE_CONTEXT_HH
|
||||||
|
|
||||||
|
#include "common.hh"
|
||||||
|
#include "note.hh"
|
||||||
|
#include "number.hh"
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
/// Context holds default values for music related actions
|
||||||
|
struct Context
|
||||||
|
{
|
||||||
|
/// Default note octave
|
||||||
|
i8 octave = 4;
|
||||||
|
|
||||||
|
/// Default note length
|
||||||
|
Number length = Number(1, 4);
|
||||||
|
|
||||||
|
/// Default BPM
|
||||||
|
unsigned bpm = 120;
|
||||||
|
|
||||||
|
/// Fills empty places in Note like octave and length with default values from context
|
||||||
|
Note fill(Note) const;
|
||||||
|
|
||||||
|
/// Converts length to seconds with current bpm
|
||||||
|
std::chrono::duration<float> length_to_duration(std::optional<Number> length) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
@ -1,4 +1,4 @@
|
|||||||
#include <musique.hh>
|
#include <musique/env.hh>
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
45
musique/env.hh
Normal file
45
musique/env.hh
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
#ifndef MUSIQUE_ENV_HH
|
||||||
|
#define MUSIQUE_ENV_HH
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include "value.hh"
|
||||||
|
|
||||||
|
/// Collection holding all variables in given scope.
|
||||||
|
struct Env : std::enable_shared_from_this<Env>
|
||||||
|
{
|
||||||
|
/// Constructor of Env class
|
||||||
|
static std::shared_ptr<Env> make();
|
||||||
|
|
||||||
|
/// Global scope that is beeing set by Interpreter
|
||||||
|
static std::shared_ptr<Env> global;
|
||||||
|
|
||||||
|
/// Variables in current scope
|
||||||
|
std::unordered_map<std::string, Value> variables;
|
||||||
|
|
||||||
|
/// Parent scope
|
||||||
|
std::shared_ptr<Env> parent;
|
||||||
|
|
||||||
|
Env(Env const&) = delete;
|
||||||
|
Env(Env &&) = default;
|
||||||
|
Env& operator=(Env const&) = delete;
|
||||||
|
Env& operator=(Env &&) = default;
|
||||||
|
|
||||||
|
/// Defines new variable regardless of it's current existance
|
||||||
|
Env& force_define(std::string name, Value new_value);
|
||||||
|
|
||||||
|
/// Finds variable in current or parent scopes
|
||||||
|
Value* find(std::string const& name);
|
||||||
|
|
||||||
|
/// Create new scope with self as parent
|
||||||
|
std::shared_ptr<Env> enter();
|
||||||
|
|
||||||
|
/// Leave current scope returning parent
|
||||||
|
std::shared_ptr<Env> leave();
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// Ensure that all values of this class are behind shared_ptr
|
||||||
|
Env() = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
@ -1,4 +1,8 @@
|
|||||||
#include <musique.hh>
|
#include <musique/errors.hh>
|
||||||
|
#include <musique/lines.hh>
|
||||||
|
#include <musique/pretty.hh>
|
||||||
|
#include <musique/unicode.hh>
|
||||||
|
#include <musique/number.hh>
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <sstream>
|
#include <sstream>
|
222
musique/errors.hh
Normal file
222
musique/errors.hh
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
#ifndef MUSIQUE_ERRORS_HH
|
||||||
|
#define MUSIQUE_ERRORS_HH
|
||||||
|
|
||||||
|
#if defined(__cpp_lib_source_location)
|
||||||
|
#include <source_location>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <system_error>
|
||||||
|
#include <variant>
|
||||||
|
#include <span>
|
||||||
|
#include <vector>
|
||||||
|
#include <numeric>
|
||||||
|
|
||||||
|
#include <musique/common.hh>
|
||||||
|
#include <musique/location.hh>
|
||||||
|
|
||||||
|
// To make sure, that we don't collide with <cassert> macro
|
||||||
|
#ifdef assert
|
||||||
|
#undef assert
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// Guards that program exits if condition does not hold
|
||||||
|
void assert(bool condition, std::string message, Location loc = Location::caller());
|
||||||
|
|
||||||
|
/// Marks part of code that was not implemented yet
|
||||||
|
[[noreturn]] void unimplemented(std::string_view message = {}, Location loc = Location::caller());
|
||||||
|
|
||||||
|
/// Marks location that should not be reached
|
||||||
|
[[noreturn]] void unreachable(Location loc = Location::caller());
|
||||||
|
|
||||||
|
/// Error handling related functions and definitions
|
||||||
|
namespace errors
|
||||||
|
{
|
||||||
|
/// When user puts emoji in the source code
|
||||||
|
struct Unrecognized_Character
|
||||||
|
{
|
||||||
|
u32 invalid_character;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// When parser was expecting code but encountered end of file
|
||||||
|
struct Unexpected_Empty_Source
|
||||||
|
{
|
||||||
|
enum { Block_Without_Closing_Bracket } reason;
|
||||||
|
std::optional<Location> start;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// When user passed numeric literal too big for numeric type
|
||||||
|
struct Failed_Numeric_Parsing
|
||||||
|
{
|
||||||
|
std::errc reason;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// When user forgot semicolon or brackets
|
||||||
|
struct Expected_Expression_Separator_Before
|
||||||
|
{
|
||||||
|
std::string_view what;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// When some keywords are not allowed in given context
|
||||||
|
struct Unexpected_Keyword
|
||||||
|
{
|
||||||
|
std::string_view keyword;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// When user tried to use operator that was not defined
|
||||||
|
struct Undefined_Operator
|
||||||
|
{
|
||||||
|
std::string_view op;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// When user tries to use operator with wrong arity of arguments
|
||||||
|
struct Wrong_Arity_Of
|
||||||
|
{
|
||||||
|
/// Type of operation
|
||||||
|
enum Type { Operator, Function } type;
|
||||||
|
|
||||||
|
/// Name of operation
|
||||||
|
std::string_view name;
|
||||||
|
|
||||||
|
/// Arity that was expected by given operation
|
||||||
|
size_t expected_arity;
|
||||||
|
|
||||||
|
/// Arit that user provided
|
||||||
|
size_t actual_arity;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// When user tried to call something that can't be called
|
||||||
|
struct Not_Callable
|
||||||
|
{
|
||||||
|
std::string_view type;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// When user provides literal where identifier should be
|
||||||
|
struct Literal_As_Identifier
|
||||||
|
{
|
||||||
|
std::string_view type_name;
|
||||||
|
std::string_view source;
|
||||||
|
std::string_view context;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// When user provides wrong type for given operation
|
||||||
|
struct Unsupported_Types_For
|
||||||
|
{
|
||||||
|
/// Type of operation
|
||||||
|
enum Type { Operator, Function } type;
|
||||||
|
|
||||||
|
/// Name of operation
|
||||||
|
std::string_view name;
|
||||||
|
|
||||||
|
/// Possible ways to use it correctly
|
||||||
|
std::vector<std::string> possibilities;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// When user tries to use variable that has not been defined yet.
|
||||||
|
struct Missing_Variable
|
||||||
|
{
|
||||||
|
/// Name of variable
|
||||||
|
std::string name;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// When user tries to invoke some MIDI action but haven't established MIDI connection
|
||||||
|
struct Operation_Requires_Midi_Connection
|
||||||
|
{
|
||||||
|
/// If its input or output connection missing
|
||||||
|
bool is_input;
|
||||||
|
|
||||||
|
/// Name of the operation that was beeing invoked
|
||||||
|
std::string name;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// When user tries to get element from collection with index higher then collection size
|
||||||
|
struct Out_Of_Range
|
||||||
|
{
|
||||||
|
/// Index that was required by the user
|
||||||
|
size_t required_index;
|
||||||
|
|
||||||
|
/// Size of accessed collection
|
||||||
|
size_t size;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Closing_Token_Without_Opening
|
||||||
|
{
|
||||||
|
enum {
|
||||||
|
Block = ']',
|
||||||
|
Paren = ')'
|
||||||
|
} type;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Arithmetic
|
||||||
|
{
|
||||||
|
enum Type
|
||||||
|
{
|
||||||
|
Division_By_Zero,
|
||||||
|
Fractional_Modulo,
|
||||||
|
Unable_To_Calculate_Modular_Multiplicative_Inverse
|
||||||
|
} type;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Collection of messages that are considered internal and should not be printed to the end user.
|
||||||
|
namespace internal
|
||||||
|
{
|
||||||
|
/// When encountered token that was supposed to be matched in higher branch of the parser
|
||||||
|
struct Unexpected_Token
|
||||||
|
{
|
||||||
|
/// Type of the token
|
||||||
|
std::string_view type;
|
||||||
|
|
||||||
|
/// Source of the token
|
||||||
|
std::string_view source;
|
||||||
|
|
||||||
|
/// Where this token was encountered that was unexpected?
|
||||||
|
std::string_view when;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// All possible error types
|
||||||
|
using Details = std::variant<
|
||||||
|
Arithmetic,
|
||||||
|
Closing_Token_Without_Opening,
|
||||||
|
Expected_Expression_Separator_Before,
|
||||||
|
Failed_Numeric_Parsing,
|
||||||
|
Literal_As_Identifier,
|
||||||
|
Missing_Variable,
|
||||||
|
Not_Callable,
|
||||||
|
Operation_Requires_Midi_Connection,
|
||||||
|
Out_Of_Range,
|
||||||
|
Undefined_Operator,
|
||||||
|
Unexpected_Empty_Source,
|
||||||
|
Unexpected_Keyword,
|
||||||
|
Unrecognized_Character,
|
||||||
|
Unsupported_Types_For,
|
||||||
|
Wrong_Arity_Of,
|
||||||
|
internal::Unexpected_Token
|
||||||
|
>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents all recoverable error messages that interpreter can produce
|
||||||
|
struct Error
|
||||||
|
{
|
||||||
|
/// Specific message details
|
||||||
|
errors::Details details;
|
||||||
|
|
||||||
|
/// Location that coused all this trouble
|
||||||
|
std::optional<Location> location = std::nullopt;
|
||||||
|
|
||||||
|
/// Return self with new location
|
||||||
|
Error with(Location) &&;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Error pretty printing
|
||||||
|
std::ostream& operator<<(std::ostream& os, Error const& err);
|
||||||
|
|
||||||
|
struct Token;
|
||||||
|
|
||||||
|
namespace errors
|
||||||
|
{
|
||||||
|
[[noreturn]]
|
||||||
|
void all_tokens_were_not_parsed(std::span<Token>);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // MUSIQUE_ERRORS_HH
|
@ -1,4 +1,7 @@
|
|||||||
#include <musique.hh>
|
#include <musique/env.hh>
|
||||||
|
#include <musique/format.hh>
|
||||||
|
#include <musique/try.hh>
|
||||||
|
#include <musique/value.hh>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
Result<std::string> format(Interpreter &i, Value const& value)
|
Result<std::string> format(Interpreter &i, Value const& value)
|
27
musique/format.hh
Normal file
27
musique/format.hh
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#ifndef MUSIQUE_FORMAT_HH
|
||||||
|
#define MUSIQUE_FORMAT_HH
|
||||||
|
|
||||||
|
#include "result.hh"
|
||||||
|
|
||||||
|
struct Interpreter;
|
||||||
|
struct Value;
|
||||||
|
|
||||||
|
struct Value_Formatter
|
||||||
|
{
|
||||||
|
enum Context
|
||||||
|
{
|
||||||
|
Free,
|
||||||
|
Inside_Block
|
||||||
|
};
|
||||||
|
|
||||||
|
Context context = Free;
|
||||||
|
unsigned indent = 0;
|
||||||
|
|
||||||
|
Value_Formatter nest(Context nested = Free) const;
|
||||||
|
|
||||||
|
std::optional<Error> format(std::ostream& os, Interpreter &interpreter, Value const& value);
|
||||||
|
};
|
||||||
|
|
||||||
|
Result<std::string> format(Interpreter &i, Value const& value);
|
||||||
|
|
||||||
|
#endif
|
42
musique/guard.hh
Normal file
42
musique/guard.hh
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
#ifndef MUSIQUE_GUARD_HH
|
||||||
|
#define MUSIQUE_GUARD_HH
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <musique/common.hh>
|
||||||
|
#include <musique/errors.hh>
|
||||||
|
#include <musique/value.hh>
|
||||||
|
|
||||||
|
/// Allows creation of guards that ensure proper type
|
||||||
|
template<usize N>
|
||||||
|
struct Guard
|
||||||
|
{
|
||||||
|
std::string_view name;
|
||||||
|
std::array<std::string_view, N> possibilities;
|
||||||
|
errors::Unsupported_Types_For::Type type = errors::Unsupported_Types_For::Function;
|
||||||
|
|
||||||
|
inline Error yield_error() const
|
||||||
|
{
|
||||||
|
auto error = errors::Unsupported_Types_For {
|
||||||
|
.type = type,
|
||||||
|
.name = std::string(name),
|
||||||
|
.possibilities = {}
|
||||||
|
};
|
||||||
|
std::transform(possibilities.begin(), possibilities.end(), std::back_inserter(error.possibilities), [](auto s) {
|
||||||
|
return std::string(s);
|
||||||
|
});
|
||||||
|
return Error { std::move(error) };
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::optional<Error> yield_result() const
|
||||||
|
{
|
||||||
|
return yield_error();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::optional<Error> operator()(bool(*predicate)(Value::Type), Value const& v) const
|
||||||
|
{
|
||||||
|
return predicate(v.type) ? std::optional<Error>{} : yield_result();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
60
musique/incoming_midi.hh
Normal file
60
musique/incoming_midi.hh
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
#ifndef MUSIQUE_INCOMING_MIDI
|
||||||
|
#define MUSIQUE_INCOMING_MIDI
|
||||||
|
|
||||||
|
#include "interpreter.hh"
|
||||||
|
|
||||||
|
struct Interpreter::Incoming_Midi_Callbacks
|
||||||
|
{
|
||||||
|
Value note_on{};
|
||||||
|
Value note_off{};
|
||||||
|
|
||||||
|
inline Incoming_Midi_Callbacks() = default;
|
||||||
|
|
||||||
|
Incoming_Midi_Callbacks(Incoming_Midi_Callbacks &&) = delete;
|
||||||
|
Incoming_Midi_Callbacks(Incoming_Midi_Callbacks const&) = delete;
|
||||||
|
|
||||||
|
Incoming_Midi_Callbacks& operator=(Incoming_Midi_Callbacks &&) = delete;
|
||||||
|
Incoming_Midi_Callbacks& operator=(Incoming_Midi_Callbacks const&) = delete;
|
||||||
|
|
||||||
|
|
||||||
|
inline void add_callbacks(midi::Connection &midi, Interpreter &interpreter)
|
||||||
|
{
|
||||||
|
register_callback(midi.note_on_callback, note_on, interpreter);
|
||||||
|
register_callback(midi.note_off_callback, note_off, interpreter);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename ...T>
|
||||||
|
inline void register_callback(std::function<void(T...)> &target, Value &callback, Interpreter &i)
|
||||||
|
{
|
||||||
|
if (&callback == ¬e_on || &callback == ¬e_off) {
|
||||||
|
// This messages have MIDI note number as second value, so they should be represented
|
||||||
|
// in our own note abstraction, not as numbers.
|
||||||
|
target = [interpreter = &i, callback = &callback](T ...source_args)
|
||||||
|
{
|
||||||
|
if (callback->type != Value::Type::Nil) {
|
||||||
|
std::vector<Value> args { Value::from(Number(source_args))... };
|
||||||
|
args[1] = Value::from(Chord { .notes { Note {
|
||||||
|
.base = i32(args[1].n.num % 12),
|
||||||
|
.octave = args[1].n.num / 12
|
||||||
|
}}});
|
||||||
|
auto result = (*callback)(*interpreter, std::move(args));
|
||||||
|
// We discard this since callback is running in another thread.
|
||||||
|
(void) result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// Generic case, preserve all passed parameters as numbers
|
||||||
|
target = [interpreter = &i, callback = &callback](T ...source_args)
|
||||||
|
{
|
||||||
|
if (callback->type != Value::Type::Nil) {
|
||||||
|
auto result = (*callback)(*interpreter, { Value::from(Number(source_args))... });
|
||||||
|
// We discard this since callback is running in another thread.
|
||||||
|
(void) result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
@ -1,5 +1,7 @@
|
|||||||
#include <musique.hh>
|
#include <musique/env.hh>
|
||||||
#include <musique_internal.hh>
|
#include <musique/incoming_midi.hh>
|
||||||
|
#include <musique/interpreter.hh>
|
||||||
|
#include <musique/try.hh>
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <iostream>
|
#include <iostream>
|
62
musique/interpreter.hh
Normal file
62
musique/interpreter.hh
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
#ifndef MUSIQUE_INTERPRETER_HH
|
||||||
|
#define MUSIQUE_INTERPRETER_HH
|
||||||
|
|
||||||
|
#include <midi.hh>
|
||||||
|
#include <musique/context.hh>
|
||||||
|
#include <musique/value.hh>
|
||||||
|
|
||||||
|
/// Given program tree evaluates it into Value
|
||||||
|
struct Interpreter
|
||||||
|
{
|
||||||
|
/// MIDI connection that is used to play music.
|
||||||
|
/// It's optional for simple interpreter testing.
|
||||||
|
midi::Connection *midi_connection = nullptr;
|
||||||
|
|
||||||
|
/// Operators defined for language
|
||||||
|
std::unordered_map<std::string, Intrinsic> operators;
|
||||||
|
|
||||||
|
/// Current environment (current scope)
|
||||||
|
std::shared_ptr<Env> env;
|
||||||
|
|
||||||
|
/// Context stack. `constext_stack.back()` is a current context.
|
||||||
|
/// There is always at least one context
|
||||||
|
std::vector<Context> context_stack;
|
||||||
|
|
||||||
|
std::function<std::optional<Error>(Interpreter&, Value)> default_action;
|
||||||
|
|
||||||
|
struct Incoming_Midi_Callbacks;
|
||||||
|
std::unique_ptr<Incoming_Midi_Callbacks> callbacks;
|
||||||
|
void register_callbacks();
|
||||||
|
|
||||||
|
Interpreter();
|
||||||
|
~Interpreter();
|
||||||
|
Interpreter(Interpreter const&) = delete;
|
||||||
|
Interpreter(Interpreter &&) = default;
|
||||||
|
|
||||||
|
/// Try to evaluate given program tree
|
||||||
|
Result<Value> eval(Ast &&ast);
|
||||||
|
|
||||||
|
// Enter scope by changing current environment
|
||||||
|
void enter_scope();
|
||||||
|
|
||||||
|
// Leave scope by changing current environment
|
||||||
|
void leave_scope();
|
||||||
|
|
||||||
|
/// Play note resolving any missing parameters with context via `midi_connection` member.
|
||||||
|
std::optional<Error> 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();
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Midi_Connection_Type { Output, Input };
|
||||||
|
std::optional<Error> ensure_midi_connection_available(Interpreter&, Midi_Connection_Type, std::string_view operation_name);
|
||||||
|
|
||||||
|
#endif
|
@ -1,7 +1,8 @@
|
|||||||
#include <musique.hh>
|
#include <musique/lexer.hh>
|
||||||
#include <musique_internal.hh>
|
#include <musique/unicode.hh>
|
||||||
|
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
constexpr std::string_view Notes_Symbols = "abcedefghp";
|
constexpr std::string_view Notes_Symbols = "abcedefghp";
|
||||||
constexpr std::string_view Valid_Operator_Chars =
|
constexpr std::string_view Valid_Operator_Chars =
|
81
musique/lexer.hh
Normal file
81
musique/lexer.hh
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
#ifndef MUSIQUE_LEXER_HH
|
||||||
|
#define MUSIQUE_LEXER_HH
|
||||||
|
|
||||||
|
#include <musique/location.hh>
|
||||||
|
#include <musique/result.hh>
|
||||||
|
#include <musique/token.hh>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
/// Explicit marker of the end of file
|
||||||
|
struct End_Of_File {};
|
||||||
|
|
||||||
|
/// Lexer takes source code and turns it into list of tokens
|
||||||
|
///
|
||||||
|
/// It allows for creating sequence of tokens by using next_token() method.
|
||||||
|
/// On each call to next_token() when source is non empty token is lexed and
|
||||||
|
/// source is beeing advanced by removing matched token from string.
|
||||||
|
struct Lexer
|
||||||
|
{
|
||||||
|
/// Source that is beeing lexed
|
||||||
|
std::string_view source;
|
||||||
|
|
||||||
|
/// Location in source of the last rune
|
||||||
|
///
|
||||||
|
/// Used only for rewinding
|
||||||
|
u32 last_rune_length = 0;
|
||||||
|
|
||||||
|
/// Start of the token that is currently beeing matched
|
||||||
|
char const* token_start = nullptr;
|
||||||
|
|
||||||
|
/// Bytes matched so far
|
||||||
|
usize token_length = 0;
|
||||||
|
|
||||||
|
/// Location of the start of a token that is currently beeing matched
|
||||||
|
Location token_location{};
|
||||||
|
|
||||||
|
/// Current location of Lexer in source
|
||||||
|
Location location{};
|
||||||
|
|
||||||
|
/// Previous location of Lexer in source
|
||||||
|
///
|
||||||
|
/// Used only for rewinding
|
||||||
|
Location prev_location{};
|
||||||
|
|
||||||
|
/// Try to tokenize next token.
|
||||||
|
auto next_token() -> Result<std::variant<Token, End_Of_File>>;
|
||||||
|
|
||||||
|
/// Skip whitespace and comments from the beggining of the source
|
||||||
|
///
|
||||||
|
/// Utility function for next_token()
|
||||||
|
void skip_whitespace_and_comments();
|
||||||
|
|
||||||
|
/// Finds next rune in source
|
||||||
|
auto peek() const -> u32;
|
||||||
|
|
||||||
|
/// Finds next rune in source and returns it, advancing the string
|
||||||
|
auto consume() -> u32;
|
||||||
|
|
||||||
|
/// For test beeing
|
||||||
|
/// callable, current rune is passed to test
|
||||||
|
/// integral, current rune is tested for equality with test
|
||||||
|
/// string, current rune is tested for beeing in it
|
||||||
|
/// otherwise, current rune is tested for beeing in test
|
||||||
|
///
|
||||||
|
/// When testing above yields truth, current rune is consumed.
|
||||||
|
/// Returns if rune was consumed
|
||||||
|
auto consume_if(auto test) -> bool;
|
||||||
|
|
||||||
|
/// Consume two runes with given tests otherwise backtrack
|
||||||
|
auto consume_if(auto first, auto second) -> bool;
|
||||||
|
|
||||||
|
/// Goes back last rune
|
||||||
|
void rewind();
|
||||||
|
|
||||||
|
/// Marks begin of token
|
||||||
|
void start();
|
||||||
|
|
||||||
|
/// Marks end of token and returns it's matching source
|
||||||
|
std::string_view finish();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
@ -1,6 +1,6 @@
|
|||||||
#include <musique.hh>
|
|
||||||
|
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
|
#include <musique/errors.hh>
|
||||||
|
#include <musique/lines.hh>
|
||||||
|
|
||||||
Lines Lines::the;
|
Lines Lines::the;
|
||||||
|
|
25
musique/lines.hh
Normal file
25
musique/lines.hh
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#ifndef MUSIQUE_LINES_HH
|
||||||
|
#define MUSIQUE_LINES_HH
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <string_view>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
struct Lines
|
||||||
|
{
|
||||||
|
static Lines the;
|
||||||
|
|
||||||
|
/// Region of lines in files
|
||||||
|
std::unordered_map<std::string, std::vector<std::string_view>> lines;
|
||||||
|
|
||||||
|
/// Add lines from file
|
||||||
|
void add_file(std::string filename, std::string_view source);
|
||||||
|
|
||||||
|
/// Add single line into file (REPL usage)
|
||||||
|
void add_line(std::string const& filename, std::string_view source, unsigned line_number);
|
||||||
|
|
||||||
|
/// Print selected region
|
||||||
|
void print(std::ostream& os, std::string const& file, unsigned first_line, unsigned last_line) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
@ -1,4 +1,4 @@
|
|||||||
#include <musique.hh>
|
#include <musique/location.hh>
|
||||||
|
|
||||||
Location Location::at(usize line, usize column)
|
Location Location::at(usize line, usize column)
|
||||||
{
|
{
|
48
musique/location.hh
Normal file
48
musique/location.hh
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
#ifndef MUSIQUE_LOCATION_HH
|
||||||
|
#define MUSIQUE_LOCATION_HH
|
||||||
|
|
||||||
|
#include <musique/common.hh>
|
||||||
|
#include <ostream>
|
||||||
|
|
||||||
|
/// \brief Location describes code position in `file line column` format.
|
||||||
|
/// It's used both to represent position in source files provided
|
||||||
|
// to interpreter and internal interpreter usage.
|
||||||
|
struct Location
|
||||||
|
{
|
||||||
|
std::string_view filename = "<unnamed>"; ///< File that location is pointing to
|
||||||
|
usize line = 1; ///< Line number (1 based) that location is pointing to
|
||||||
|
usize column = 1; ///< Column number (1 based) that location is pointing to
|
||||||
|
|
||||||
|
/// Advances line and column numbers based on provided rune
|
||||||
|
///
|
||||||
|
/// If rune is newline, then column is reset to 1, and line number is incremented.
|
||||||
|
/// Otherwise column number is incremented.
|
||||||
|
///
|
||||||
|
/// @param rune Rune from which column and line numbers advancements are made.
|
||||||
|
Location& advance(u32 rune);
|
||||||
|
|
||||||
|
bool operator==(Location const& rhs) const = default;
|
||||||
|
|
||||||
|
//! Creates location at default filename with specified line and column number
|
||||||
|
static Location at(usize line, usize column);
|
||||||
|
|
||||||
|
// Used to describe location of function call in interpreter (internal use only)
|
||||||
|
#if defined(__cpp_lib_source_location)
|
||||||
|
static Location caller(std::source_location loc = std::source_location::current());
|
||||||
|
#elif (__has_builtin(__builtin_FILE) and __has_builtin(__builtin_LINE))
|
||||||
|
static Location caller(char const* file = __builtin_FILE(), usize line = __builtin_LINE());
|
||||||
|
#else
|
||||||
|
#error Cannot implement Location::caller function
|
||||||
|
/// Returns location of call in interpreter source code.
|
||||||
|
///
|
||||||
|
/// Example of reporting where `foo()` was beeing called:
|
||||||
|
/// @code
|
||||||
|
/// void foo(Location loc = Location::caller()) { std::cout << loc << '\n'; }
|
||||||
|
/// @endcode
|
||||||
|
static Location caller();
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
std::ostream& operator<<(std::ostream& os, Location const& location);
|
||||||
|
|
||||||
|
#endif
|
@ -6,8 +6,15 @@
|
|||||||
#include <span>
|
#include <span>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
#include <musique.hh>
|
|
||||||
#include <midi.hh>
|
#include <midi.hh>
|
||||||
|
#include <musique/env.hh>
|
||||||
|
#include <musique/format.hh>
|
||||||
|
#include <musique/interpreter.hh>
|
||||||
|
#include <musique/lines.hh>
|
||||||
|
#include <musique/parser.hh>
|
||||||
|
#include <musique/pretty.hh>
|
||||||
|
#include <musique/try.hh>
|
||||||
|
#include <musique/unicode.hh>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include <bestline.h>
|
#include <bestline.h>
|
40
musique/note.hh
Normal file
40
musique/note.hh
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
#ifndef MUSIQUE_NOTE_HH
|
||||||
|
#define MUSIQUE_NOTE_HH
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#include "number.hh"
|
||||||
|
|
||||||
|
/// Representation of musical note or musical pause
|
||||||
|
struct Note
|
||||||
|
{
|
||||||
|
/// Base of a note, like `c` (=0), `c#` (=1) `d` (=2)
|
||||||
|
/// Or nullopt where there is no note - case when we have pause
|
||||||
|
std::optional<i32> base = std::nullopt;
|
||||||
|
|
||||||
|
/// Octave in MIDI acceptable range (from -1 to 9 inclusive)
|
||||||
|
std::optional<i8> octave = std::nullopt;
|
||||||
|
|
||||||
|
/// Length of playing note
|
||||||
|
std::optional<Number> length = std::nullopt;
|
||||||
|
|
||||||
|
/// Create Note from string
|
||||||
|
static std::optional<Note> from(std::string_view note);
|
||||||
|
|
||||||
|
/// Extract midi note number
|
||||||
|
std::optional<u8> into_midi_note() const;
|
||||||
|
|
||||||
|
/// Extract midi note number, but when octave is not present use provided default
|
||||||
|
u8 into_midi_note(i8 default_octave) const;
|
||||||
|
|
||||||
|
bool operator==(Note const&) const;
|
||||||
|
|
||||||
|
std::partial_ordering operator<=>(Note const&) const;
|
||||||
|
|
||||||
|
/// Simplify note by adding base to octave if octave is present
|
||||||
|
void simplify_inplace();
|
||||||
|
};
|
||||||
|
|
||||||
|
std::ostream& operator<<(std::ostream& os, Note note);
|
||||||
|
|
||||||
|
#endif
|
@ -1,5 +1,5 @@
|
|||||||
#include <musique.hh>
|
#include <musique/number.hh>
|
||||||
#include <musique_internal.hh>
|
#include <musique/try.hh>
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <numeric>
|
#include <numeric>
|
61
musique/number.hh
Normal file
61
musique/number.hh
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
#ifndef MUSIQUE_NUMBER_HH
|
||||||
|
#define MUSIQUE_NUMBER_HH
|
||||||
|
|
||||||
|
#include <musique/common.hh>
|
||||||
|
#include <musique/result.hh>
|
||||||
|
#include <musique/token.hh>
|
||||||
|
#include <ostream>
|
||||||
|
|
||||||
|
/// Number type supporting integer and fractional constants
|
||||||
|
///
|
||||||
|
/// \invariant gcd(num, den) == 1, after any operation
|
||||||
|
struct Number
|
||||||
|
{
|
||||||
|
/// Type that represents numerator and denominator values
|
||||||
|
using value_type = i64;
|
||||||
|
|
||||||
|
value_type num = 0; ///< Numerator of a fraction beeing represented
|
||||||
|
value_type den = 1; ///< Denominator of a fraction beeing represented
|
||||||
|
|
||||||
|
constexpr Number() = default;
|
||||||
|
constexpr Number(Number const&) = default;
|
||||||
|
constexpr Number(Number &&) = default;
|
||||||
|
constexpr Number& operator=(Number const&) = default;
|
||||||
|
constexpr Number& operator=(Number &&) = default;
|
||||||
|
|
||||||
|
explicit Number(value_type v); ///< Creates Number as fraction v / 1
|
||||||
|
Number(value_type num, value_type den); ///< Creates Number as fraction num / den
|
||||||
|
|
||||||
|
auto as_int() const -> value_type; ///< Returns self as int
|
||||||
|
auto simplify() const -> Number; ///< Returns self, but with gcd(num, den) == 1
|
||||||
|
void simplify_inplace(); ///< Update self, to have gcd(num, den) == 1
|
||||||
|
|
||||||
|
bool operator==(Number const&) const;
|
||||||
|
bool operator!=(Number const&) const;
|
||||||
|
std::strong_ordering operator<=>(Number const&) const;
|
||||||
|
|
||||||
|
Number operator+(Number const& rhs) const;
|
||||||
|
Number& operator+=(Number const& rhs);
|
||||||
|
Number operator-(Number const& rhs) const;
|
||||||
|
Number& operator-=(Number const& rhs);
|
||||||
|
Number operator*(Number const& rhs) const;
|
||||||
|
Number& operator*=(Number const& rhs);
|
||||||
|
Result<Number> operator/(Number const& rhs) const;
|
||||||
|
Result<Number> operator%(Number const& rhs) const;
|
||||||
|
|
||||||
|
Number floor() const; ///< Return number rounded down to nearest integer
|
||||||
|
Number ceil() const; ///< Return number rounded up to nearest integer
|
||||||
|
Number round() const; ///< Return number rounded to nearest integer
|
||||||
|
|
||||||
|
Result<Number> inverse() const; ///< Return number raised to power -1
|
||||||
|
Result<Number> pow(Number n) const; ///< Return number raised to power `n`.
|
||||||
|
|
||||||
|
/// Parses source contained by token into a Number instance
|
||||||
|
static Result<Number> from(Token token);
|
||||||
|
};
|
||||||
|
|
||||||
|
std::ostream& operator<<(std::ostream& os, Number const& num);
|
||||||
|
|
||||||
|
template<> struct std::hash<Number> { std::size_t operator()(Number const&) const; };
|
||||||
|
|
||||||
|
#endif
|
@ -1,5 +1,6 @@
|
|||||||
#include <musique.hh>
|
#include <musique/parser.hh>
|
||||||
#include <musique_internal.hh>
|
#include <musique/lexer.hh>
|
||||||
|
#include <musique/try.hh>
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <numeric>
|
#include <numeric>
|
68
musique/parser.hh
Normal file
68
musique/parser.hh
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
#ifndef MUSIQUE_PARSER_HH
|
||||||
|
#define MUSIQUE_PARSER_HH
|
||||||
|
|
||||||
|
#include "ast.hh"
|
||||||
|
#include "result.hh"
|
||||||
|
|
||||||
|
/// Source code to program tree converter
|
||||||
|
///
|
||||||
|
/// Intended to be used by library user only by Parser::parse() static function.
|
||||||
|
struct Parser
|
||||||
|
{
|
||||||
|
/// List of tokens yielded from source
|
||||||
|
std::vector<Token> tokens;
|
||||||
|
|
||||||
|
/// Current token id (offset in tokens array)
|
||||||
|
unsigned token_id = 0;
|
||||||
|
|
||||||
|
/// Parses whole source code producing Ast or Error
|
||||||
|
/// using Parser structure internally
|
||||||
|
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();
|
||||||
|
|
||||||
|
/// Parse either infix expression or variable declaration
|
||||||
|
Result<Ast> parse_expression();
|
||||||
|
|
||||||
|
/// Parse infix expression
|
||||||
|
Result<Ast> parse_infix_expression();
|
||||||
|
|
||||||
|
/// Parse right hand size of infix expression
|
||||||
|
Result<Ast> parse_rhs_of_infix_expression(Ast lhs);
|
||||||
|
|
||||||
|
/// Parse either index expression or atomic expression
|
||||||
|
Result<Ast> parse_index_expression();
|
||||||
|
|
||||||
|
/// Parse function call, literal etc
|
||||||
|
Result<Ast> parse_atomic_expression();
|
||||||
|
|
||||||
|
/// Parse variable declaration
|
||||||
|
Result<Ast> parse_variable_declaration();
|
||||||
|
|
||||||
|
/// Utility function for identifier parsing
|
||||||
|
Result<Ast> parse_identifier_with_trailing_separators();
|
||||||
|
|
||||||
|
/// Utility function for identifier parsing
|
||||||
|
Result<Ast> parse_identifier();
|
||||||
|
|
||||||
|
/// Peek current token
|
||||||
|
Result<Token> peek() const;
|
||||||
|
|
||||||
|
/// Peek type of the current token
|
||||||
|
Result<Token::Type> peek_type() const;
|
||||||
|
|
||||||
|
/// Consume current token
|
||||||
|
Token consume();
|
||||||
|
|
||||||
|
/// Tests if current token has given type
|
||||||
|
bool expect(Token::Type type) const;
|
||||||
|
|
||||||
|
/// Tests if current token has given type and source
|
||||||
|
bool expect(Token::Type type, std::string_view lexeme) const;
|
||||||
|
|
||||||
|
// Tests if current token has given type and the next token has given type and source
|
||||||
|
bool expect(Token::Type t1, Token::Type t2, std::string_view lexeme_for_t2) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
@ -1,4 +1,4 @@
|
|||||||
#include <musique.hh>
|
#include <musique/pretty.hh>
|
||||||
|
|
||||||
namespace starters
|
namespace starters
|
||||||
{
|
{
|
28
musique/pretty.hh
Normal file
28
musique/pretty.hh
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
#ifndef MUSIQUE_PRETTY_HH
|
||||||
|
#define MUSIQUE_PRETTY_HH
|
||||||
|
|
||||||
|
#include <ostream>
|
||||||
|
|
||||||
|
/// All code related to pretty printing. Default mode is no_color
|
||||||
|
namespace pretty
|
||||||
|
{
|
||||||
|
/// Mark start of printing an error
|
||||||
|
std::ostream& begin_error(std::ostream&);
|
||||||
|
|
||||||
|
/// Mark start of printing a path
|
||||||
|
std::ostream& begin_path(std::ostream&);
|
||||||
|
|
||||||
|
/// Mark start of printing a comment
|
||||||
|
std::ostream& begin_comment(std::ostream&);
|
||||||
|
|
||||||
|
/// Mark end of any above
|
||||||
|
std::ostream& end(std::ostream&);
|
||||||
|
|
||||||
|
/// Switch to colorful output via ANSI escape sequences
|
||||||
|
void terminal_mode();
|
||||||
|
|
||||||
|
/// Switch to colorless output (default one)
|
||||||
|
void no_color_mode();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
88
musique/result.hh
Normal file
88
musique/result.hh
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
#ifndef MUSIQUE_RESULT_HH
|
||||||
|
#define MUSIQUE_RESULT_HH
|
||||||
|
|
||||||
|
#include <tl/expected.hpp>
|
||||||
|
|
||||||
|
#include "errors.hh"
|
||||||
|
|
||||||
|
/// Holds either T or Error
|
||||||
|
template<typename T>
|
||||||
|
struct [[nodiscard("This value may contain critical error, so it should NOT be ignored")]] Result : tl::expected<T, Error>
|
||||||
|
{
|
||||||
|
using Storage = tl::expected<T, Error>;
|
||||||
|
|
||||||
|
constexpr Result() = default;
|
||||||
|
|
||||||
|
template<typename ...Args> requires (not std::is_void_v<T>) && std::is_constructible_v<T, Args...>
|
||||||
|
constexpr Result(Args&& ...args)
|
||||||
|
: Storage( T{ std::forward<Args>(args)... } )
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Arg> requires std::is_constructible_v<Storage, Arg>
|
||||||
|
constexpr Result(Arg &&arg)
|
||||||
|
: Storage(std::forward<Arg>(arg))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Result(Error error)
|
||||||
|
: Storage(tl::unexpected(std::move(error)))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Arg>
|
||||||
|
requires requires (Arg a) {
|
||||||
|
{ Error { .details = std::move(a) } };
|
||||||
|
}
|
||||||
|
inline Result(Arg a)
|
||||||
|
: Storage(tl::unexpected(Error { .details = std::move(a) }))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal function used for definition of Try macro
|
||||||
|
inline auto value() &&
|
||||||
|
{
|
||||||
|
if constexpr (not std::is_void_v<T>) {
|
||||||
|
// NOTE This line in ideal world should be `return Storage::value()`
|
||||||
|
// but C++ does not infer that this is rvalue context.
|
||||||
|
// `std::add_rvalue_reference_t<Storage>::value()`
|
||||||
|
// also does not work, so this is probably the best way to express this:
|
||||||
|
return std::move(*static_cast<Storage*>(this)).value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fill error location if it's empty and we have an error
|
||||||
|
inline Result<T> with_location(Location location) &&
|
||||||
|
{
|
||||||
|
if (!Storage::has_value()) {
|
||||||
|
if (auto& target = Storage::error().location; !target || target == Location{}) {
|
||||||
|
target = location;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline tl::expected<T, Error> to_expected() &&
|
||||||
|
{
|
||||||
|
return *static_cast<Storage*>(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Map>
|
||||||
|
requires is_template_v<Result, std::invoke_result_t<Map, T&&>>
|
||||||
|
auto and_then(Map &&map) &&
|
||||||
|
{
|
||||||
|
return std::move(*static_cast<Storage*>(this)).and_then(
|
||||||
|
[map = std::forward<Map>(map)](T &&value) {
|
||||||
|
return std::move(map)(std::move(value)).to_expected();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
using Storage::and_then;
|
||||||
|
|
||||||
|
operator std::optional<Error>() &&
|
||||||
|
{
|
||||||
|
return Storage::has_value() ? std::nullopt : std::optional(Storage::error());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
49
musique/token.hh
Normal file
49
musique/token.hh
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
#ifndef MUSIQUE_TOKEN_HH
|
||||||
|
#define MUSIQUE_TOKEN_HH
|
||||||
|
|
||||||
|
#include "common.hh"
|
||||||
|
#include "location.hh"
|
||||||
|
|
||||||
|
/// Lexical token representation for Musique language
|
||||||
|
struct Token
|
||||||
|
{
|
||||||
|
/// Type of Token
|
||||||
|
enum class Type
|
||||||
|
{
|
||||||
|
Symbol, ///< like repeat or choose or chord
|
||||||
|
Keyword, ///< like true, false, nil
|
||||||
|
Operator, ///< like "+", "-", "++", "<"
|
||||||
|
Chord, ///< chord or single note literal, like "c125"
|
||||||
|
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, ///< "[" delimits anonymous block of code (potentially a function)
|
||||||
|
Close_Block, ///< "]" delimits anonymous block of code (potentially a function)
|
||||||
|
Open_Paren, ///< "(" used in arithmetic or as function invocation sarrounding
|
||||||
|
Close_Paren ///< ")" used in arithmetic or as function invocation sarrounding
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Type of token
|
||||||
|
Type type;
|
||||||
|
|
||||||
|
/// Matched source code to the token type
|
||||||
|
std::string_view source;
|
||||||
|
|
||||||
|
/// Location of encountered token
|
||||||
|
Location location;
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr usize Keywords_Count = 5;
|
||||||
|
static constexpr usize Operators_Count = 17;
|
||||||
|
|
||||||
|
std::string_view type_name(Token::Type type);
|
||||||
|
|
||||||
|
/// Token debug printing
|
||||||
|
std::ostream& operator<<(std::ostream& os, Token const& tok);
|
||||||
|
|
||||||
|
/// Token type debug printing
|
||||||
|
std::ostream& operator<<(std::ostream& os, Token::Type type);
|
||||||
|
|
||||||
|
template<> struct std::hash<Token> { std::size_t operator()(Token const&) const; };
|
||||||
|
|
||||||
|
#endif
|
87
musique/try.hh
Normal file
87
musique/try.hh
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
#ifndef MUSIQUE_TRY_HH
|
||||||
|
#define MUSIQUE_TRY_HH
|
||||||
|
|
||||||
|
#include "result.hh"
|
||||||
|
|
||||||
|
/// Shorthand for forwarding error values with Result type family.
|
||||||
|
///
|
||||||
|
/// This implementation requires C++ language extension: statement expressions
|
||||||
|
/// It's supported by GCC and Clang, other compilers i don't know.
|
||||||
|
/// Inspired by SerenityOS TRY macro
|
||||||
|
#define Try(Value) \
|
||||||
|
({ \
|
||||||
|
auto try_value = (Value); \
|
||||||
|
using Trait [[maybe_unused]] = Try_Traits<std::decay_t<decltype(try_value)>>; \
|
||||||
|
if (not Trait::is_ok(try_value)) [[unlikely]] \
|
||||||
|
return Trait::yield_error(std::move(try_value)); \
|
||||||
|
Trait::yield_value(std::move(try_value)); \
|
||||||
|
})
|
||||||
|
|
||||||
|
/// Abstraction over any value that are either value or error
|
||||||
|
///
|
||||||
|
/// Inspired by P2561R0
|
||||||
|
template<typename = void>
|
||||||
|
struct Try_Traits
|
||||||
|
{
|
||||||
|
template<typename T>
|
||||||
|
static constexpr bool is_ok(T const& v) { return Try_Traits<T>::is_ok(v); }
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
static constexpr auto yield_value(T&& v) { return Try_Traits<T>::yield_value(std::forward<T>(v)); }
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
static constexpr auto yield_error(T&& v) { return Try_Traits<T>::yield_error(std::forward<T>(v)); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct Try_Traits<std::optional<Error>>
|
||||||
|
{
|
||||||
|
using Value_Type = std::nullopt_t;
|
||||||
|
using Error_Type = Error;
|
||||||
|
|
||||||
|
static constexpr bool is_ok(std::optional<Error> const& o)
|
||||||
|
{
|
||||||
|
return not o.has_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::nullopt_t yield_value(std::optional<Error>&& err)
|
||||||
|
{
|
||||||
|
assert(not err.has_value(), "Trying to yield value from optional that contains error");
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Error yield_error(std::optional<Error>&& err)
|
||||||
|
{
|
||||||
|
assert(err.has_value(), "Trying to yield value from optional that NOT constains error");
|
||||||
|
return std::move(*err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
struct Try_Traits<Result<T>>
|
||||||
|
{
|
||||||
|
using Value_Type = T;
|
||||||
|
using Error_Type = Error;
|
||||||
|
|
||||||
|
static constexpr bool is_ok(Result<T> const& o)
|
||||||
|
{
|
||||||
|
return o.has_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
static auto yield_value(Result<T> val)
|
||||||
|
{
|
||||||
|
assert(val.has_value(), "Trying to yield value from expected that contains error");
|
||||||
|
if constexpr (std::is_void_v<T>) {
|
||||||
|
} else {
|
||||||
|
return std::move(*val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Error yield_error(Result<T>&& val)
|
||||||
|
{
|
||||||
|
assert(not val.has_value(), "Trying to yield error from expected with value");
|
||||||
|
return std::move(val.error());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
47
musique/typecheck.hh
Normal file
47
musique/typecheck.hh
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
#ifndef MUSIQUE_TYPECHECK_HH
|
||||||
|
#define MUSIQUE_TYPECHECK_HH
|
||||||
|
|
||||||
|
#include <musique/value.hh>
|
||||||
|
|
||||||
|
/// 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...); }
|
||||||
|
|
||||||
|
static inline auto typecheck_and_move(std::vector<Value>& args)
|
||||||
|
{
|
||||||
|
return typecheck(args) ? std::optional { move_from(args) } : std::nullopt;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
@ -1,4 +1,5 @@
|
|||||||
#include "musique.hh"
|
#include <array>
|
||||||
|
#include <musique/unicode.hh>
|
||||||
|
|
||||||
static constexpr std::array<u8, 4> payloads {
|
static constexpr std::array<u8, 4> payloads {
|
||||||
0b0111'1111, 0b0001'1111, 0b0000'1111, 0b0000'0111
|
0b0111'1111, 0b0001'1111, 0b0000'1111, 0b0000'0111
|
50
musique/unicode.hh
Normal file
50
musique/unicode.hh
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
#ifndef MUSIQUE_UNICODE_HH
|
||||||
|
#define MUSIQUE_UNICODE_HH
|
||||||
|
|
||||||
|
#include <musique/common.hh>
|
||||||
|
#include <ostream>
|
||||||
|
|
||||||
|
/// All unicode related operations
|
||||||
|
namespace unicode
|
||||||
|
{
|
||||||
|
inline namespace special_runes
|
||||||
|
{
|
||||||
|
[[maybe_unused]] constexpr u32 Rune_Error = 0xfffd;
|
||||||
|
[[maybe_unused]] constexpr u32 Rune_Self = 0x80;
|
||||||
|
[[maybe_unused]] constexpr u32 Max_Bytes = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// is_digit returns true if `digit` is ASCII digit
|
||||||
|
bool is_digit(u32 digit);
|
||||||
|
|
||||||
|
/// is_space return true if `space` is ASCII blank character
|
||||||
|
bool is_space(u32 space);
|
||||||
|
|
||||||
|
/// is_letter returns true if `letter` is considered a letter by Unicode
|
||||||
|
bool is_letter(u32 letter);
|
||||||
|
|
||||||
|
/// is_identifier returns true if `letter` is valid character for identifier.
|
||||||
|
///
|
||||||
|
/// It's modifier by is_first_character flag to determine some character classes
|
||||||
|
/// allowance like numbers, which are only allowed NOT at the front of the identifier
|
||||||
|
enum class First_Character : bool { Yes = true, No = false };
|
||||||
|
bool is_identifier(u32 letter, First_Character is_first_character);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// utf8 encoding and decoding
|
||||||
|
namespace utf8
|
||||||
|
{
|
||||||
|
using namespace unicode::special_runes;
|
||||||
|
|
||||||
|
/// Decodes rune and returns remaining string
|
||||||
|
auto decode(std::string_view s) -> std::pair<u32, std::string_view>;
|
||||||
|
|
||||||
|
/// Returns length of the first rune in the provided string
|
||||||
|
auto length(std::string_view s) -> usize;
|
||||||
|
|
||||||
|
struct Print { u32 rune; };
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostream& operator<<(std::ostream& os, utf8::Print const& print);
|
||||||
|
|
||||||
|
#endif
|
@ -1,4 +1,6 @@
|
|||||||
#include <musique.hh>
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <musique/unicode.hh>
|
||||||
#include <span>
|
#include <span>
|
||||||
|
|
||||||
constexpr u32 Max_Latin1 = 0xff;
|
constexpr u32 Max_Latin1 = 0xff;
|
@ -1,5 +1,8 @@
|
|||||||
#include <musique.hh>
|
#include <musique/env.hh>
|
||||||
#include <musique_internal.hh>
|
#include <musique/guard.hh>
|
||||||
|
#include <musique/interpreter.hh>
|
||||||
|
#include <musique/try.hh>
|
||||||
|
#include <musique/value.hh>
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <numeric>
|
#include <numeric>
|
150
musique/value.hh
Normal file
150
musique/value.hh
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
#ifndef MUSIQUE_VALUE_HH
|
||||||
|
#define MUSIQUE_VALUE_HH
|
||||||
|
|
||||||
|
#include "array.hh"
|
||||||
|
#include "block.hh"
|
||||||
|
#include "chord.hh"
|
||||||
|
#include "common.hh"
|
||||||
|
#include "note.hh"
|
||||||
|
#include "result.hh"
|
||||||
|
#include "token.hh"
|
||||||
|
|
||||||
|
/// Representation of any value in language
|
||||||
|
struct Value
|
||||||
|
{
|
||||||
|
/// Creates value from literal contained in Token
|
||||||
|
static Result<Value> from(Token t);
|
||||||
|
|
||||||
|
/// Create value holding provided boolean
|
||||||
|
///
|
||||||
|
/// Using Explicit_Bool to prevent from implicit casts
|
||||||
|
static Value from(Explicit_Bool b);
|
||||||
|
|
||||||
|
static Value from(Array &&array); ///< Create value of type array holding provided array
|
||||||
|
static Value from(Block &&l); ///< Create value of type block holding provided block
|
||||||
|
static Value from(Chord chord); ///< Create value of type music holding provided chord
|
||||||
|
static Value from(Note n); ///< Create value of type music holding provided note
|
||||||
|
static Value from(Number n); ///< Create value of type number holding provided number
|
||||||
|
static Value from(char const* s); ///< Create value of type symbol holding provided symbol
|
||||||
|
static Value from(std::string s); ///< Create value of type symbol holding provided symbol
|
||||||
|
static Value from(std::string_view s); ///< Create value of type symbol holding provided symbol
|
||||||
|
static Value from(std::vector<Value> &&array); ///< Create value of type array holding provided array
|
||||||
|
|
||||||
|
enum class Type
|
||||||
|
{
|
||||||
|
Nil, ///< Unit type, used for denoting emptiness and result of some side effect only functions
|
||||||
|
Bool, ///< Boolean type, used for logic computations
|
||||||
|
Number, ///< Number type, representing only rational numbers
|
||||||
|
Symbol, ///< Symbol type, used to represent identifiers
|
||||||
|
Intrinsic, ///< Intrinsic functions that are implemented in C++
|
||||||
|
Block, ///< Block type, containing block value (lazy array/closure/lambda like)
|
||||||
|
Array, ///< Array type, eager array
|
||||||
|
Music, ///< Music type,
|
||||||
|
};
|
||||||
|
|
||||||
|
Value() = default;
|
||||||
|
Value(Value const&) = default;
|
||||||
|
Value(Value &&) = default;
|
||||||
|
Value& operator=(Value const&) = default;
|
||||||
|
Value& operator=(Value &&) = default;
|
||||||
|
|
||||||
|
/// Contructs Intrinsic, used to simplify definition of intrinsics
|
||||||
|
inline Value(Intrinsic intr) : type{Type::Intrinsic}, intr(intr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Type type = Type::Nil;
|
||||||
|
bool b;
|
||||||
|
Number n;
|
||||||
|
Intrinsic intr;
|
||||||
|
Block blk;
|
||||||
|
Chord chord;
|
||||||
|
Array array;
|
||||||
|
|
||||||
|
// TODO Most strings should not be allocated by Value, but reference to string allocated previously
|
||||||
|
// Wrapper for std::string is needed that will allocate only when needed, middle ground between:
|
||||||
|
// std::string - always owning string type
|
||||||
|
// std::string_view - not-owning string type
|
||||||
|
std::string s{};
|
||||||
|
|
||||||
|
/// Returns truth judgment for current type, used primarly for if function
|
||||||
|
bool truthy() const;
|
||||||
|
|
||||||
|
/// Returns false judgment for current type, used primarly for if function
|
||||||
|
bool falsy() const;
|
||||||
|
|
||||||
|
/// Calls contained value if it can be called
|
||||||
|
Result<Value> operator()(Interpreter &i, std::vector<Value> args);
|
||||||
|
|
||||||
|
/// Index contained value if it can be called
|
||||||
|
Result<Value> index(Interpreter &i, unsigned position) const;
|
||||||
|
|
||||||
|
/// Return elements count of contained value if it can be measured
|
||||||
|
usize size() const;
|
||||||
|
|
||||||
|
bool operator==(Value const& other) const;
|
||||||
|
|
||||||
|
std::partial_ordering operator<=>(Value const& other) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<Value::Type>
|
||||||
|
struct Member_For_Value_Type {};
|
||||||
|
|
||||||
|
template<> struct Member_For_Value_Type<Value::Type::Bool>
|
||||||
|
{ static constexpr auto value = &Value::b; };
|
||||||
|
|
||||||
|
template<> struct Member_For_Value_Type<Value::Type::Number>
|
||||||
|
{ static constexpr auto value = &Value::n; };
|
||||||
|
|
||||||
|
template<> struct Member_For_Value_Type<Value::Type::Symbol>
|
||||||
|
{ static constexpr auto value = &Value::s; };
|
||||||
|
|
||||||
|
template<> struct Member_For_Value_Type<Value::Type::Intrinsic>
|
||||||
|
{ static constexpr auto value = &Value::intr; };
|
||||||
|
|
||||||
|
template<> struct Member_For_Value_Type<Value::Type::Block>
|
||||||
|
{ static constexpr auto value = &Value::blk; };
|
||||||
|
|
||||||
|
template<> struct Member_For_Value_Type<Value::Type::Array>
|
||||||
|
{ static constexpr auto value = &Value::array; };
|
||||||
|
|
||||||
|
template<> struct Member_For_Value_Type<Value::Type::Music>
|
||||||
|
{ static constexpr auto value = &Value::chord; };
|
||||||
|
|
||||||
|
/// Returns type name of Value type
|
||||||
|
std::string_view type_name(Value::Type t);
|
||||||
|
|
||||||
|
std::ostream& operator<<(std::ostream& os, Value const& v);
|
||||||
|
template<> struct std::hash<Value> { std::size_t operator()(Value const&) const; };
|
||||||
|
|
||||||
|
/// 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
Result<Value> wrap_value(Result<T> &&value)
|
||||||
|
{
|
||||||
|
return std::move(value).map([](auto &&value) { return Value::from(std::move(value)); });
|
||||||
|
}
|
||||||
|
|
||||||
|
Value wrap_value(auto &&value)
|
||||||
|
{
|
||||||
|
return Value::from(std::move(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
1
musique/value_algorithms.hh
Normal file
1
musique/value_algorithms.hh
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
@ -6,6 +6,6 @@ bin/debug/musique: $(Debug_Obj) bin/debug/main.o bin/bestline.o include/*.hh
|
|||||||
@echo "CXX $@"
|
@echo "CXX $@"
|
||||||
@$(CXX) $(CXXFLAGS) $(DEBUG_FLAGS) $(CPPFLAGS) -o $@ $(Debug_Obj) bin/bestline.o bin/debug/main.o $(LDFLAGS) $(LDLIBS)
|
@$(CXX) $(CXXFLAGS) $(DEBUG_FLAGS) $(CPPFLAGS) -o $@ $(Debug_Obj) bin/bestline.o bin/debug/main.o $(LDFLAGS) $(LDLIBS)
|
||||||
|
|
||||||
bin/debug/%.o: src/%.cc include/*.hh
|
bin/debug/%.o: musique/%.cc include/*.hh
|
||||||
@echo "CXX $@"
|
@echo "CXX $@"
|
||||||
@$(CXX) $(CXXFLAGS) $(DEBUG_FLAGS) $(CPPFLAGS) -o $@ $< -c
|
@$(CXX) $(CXXFLAGS) $(DEBUG_FLAGS) $(CPPFLAGS) -o $@ $< -c
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
Release_Obj=$(addprefix bin/,$(Obj))
|
Release_Obj=$(addprefix bin/,$(Obj))
|
||||||
|
|
||||||
bin/%.o: src/%.cc include/*.hh
|
bin/%.o: musique/%.cc
|
||||||
@echo "CXX $@"
|
@echo "CXX $@"
|
||||||
@$(CXX) $(CXXFLAGS) $(RELEASE_FLAGS) $(CPPFLAGS) -o $@ $< -c
|
@$(CXX) $(CXXFLAGS) $(RELEASE_FLAGS) $(CPPFLAGS) -o $@ $< -c
|
||||||
|
|
||||||
bin/musique: $(Release_Obj) bin/main.o bin/bestline.o include/*.hh lib/midi/libmidi-alsa.a
|
bin/musique: $(Release_Obj) bin/main.o bin/bestline.o lib/midi/libmidi-alsa.a
|
||||||
@echo "CXX $@"
|
@echo "CXX $@"
|
||||||
@$(CXX) $(CXXFLAGS) $(RELEASE_FLAGS) $(CPPFLAGS) -o $@ $(Release_Obj) bin/bestline.o bin/main.o $(LDFLAGS) $(LDLIBS)
|
@$(CXX) $(CXXFLAGS) $(RELEASE_FLAGS) $(CPPFLAGS) -o $@ $(Release_Obj) bin/bestline.o bin/main.o $(LDFLAGS) $(LDLIBS)
|
||||||
|
@ -1,16 +1,3 @@
|
|||||||
Tests= \
|
|
||||||
context.o \
|
|
||||||
environment.o \
|
|
||||||
interpreter.o \
|
|
||||||
lex.o \
|
|
||||||
main.o \
|
|
||||||
number.o \
|
|
||||||
parser.o \
|
|
||||||
unicode.o \
|
|
||||||
value.o
|
|
||||||
|
|
||||||
Test_Obj=$(addprefix bin/debug/tests/,$(Tests))
|
|
||||||
|
|
||||||
test: bin/debug/musique
|
test: bin/debug/musique
|
||||||
scripts/test.py test examples
|
scripts/test.py test examples
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user