Interpreter started!
This commit is contained in:
parent
14ea494686
commit
ccd2166231
11
Makefile
11
Makefile
@ -1,14 +1,17 @@
|
|||||||
MAKEFLAGS="-j $(grep -c ^processor /proc/cpuinfo)"
|
MAKEFLAGS="-j $(grep -c ^processor /proc/cpuinfo)"
|
||||||
CXXFLAGS:=$(CXXFLAGS) -std=c++20 -Wall -Wextra -Werror=switch -Werror=unused-result
|
CXXFLAGS:=$(CXXFLAGS) -std=c++20 -Wall -Wextra -Werror=switch -Werror=return-type -Werror=unused-result
|
||||||
CPPFLAGS:=$(CPPFLAGS) -Ilib/expected/ -Ilib/ut/ -Isrc/
|
CPPFLAGS:=$(CPPFLAGS) -Ilib/expected/ -Ilib/ut/ -Isrc/
|
||||||
|
|
||||||
Obj=bin/errors.o \
|
Obj= \
|
||||||
|
bin/errors.o \
|
||||||
|
bin/interpreter.o \
|
||||||
bin/lexer.o \
|
bin/lexer.o \
|
||||||
bin/location.o \
|
bin/location.o \
|
||||||
|
bin/number.o \
|
||||||
bin/parser.o \
|
bin/parser.o \
|
||||||
bin/unicode.o \
|
bin/unicode.o \
|
||||||
bin/number.o \
|
bin/unicode_tables.o \
|
||||||
bin/unicode_tables.o
|
bin/value.o
|
||||||
|
|
||||||
all: bin/musique bin/unit-tests
|
all: bin/musique bin/unit-tests
|
||||||
|
|
||||||
|
3
examples/arithmetic.mq
Normal file
3
examples/arithmetic.mq
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
say (1 + 3);
|
||||||
|
say (3 * 10);
|
||||||
|
say 42
|
@ -69,8 +69,9 @@ std::ostream& operator<<(std::ostream& os, Error const& err)
|
|||||||
case errors::Unrecognized_Character:
|
case errors::Unrecognized_Character:
|
||||||
return err.message.empty() ? os << "unrecognized character\n" : os << err.message;
|
return err.message.empty() ? os << "unrecognized character\n" : os << err.message;
|
||||||
|
|
||||||
|
case errors::Function_Not_Defined:
|
||||||
case errors::Unexpected_Token_Type:
|
case errors::Unexpected_Token_Type:
|
||||||
return os << err.message;
|
return os << err.message << '\n';
|
||||||
|
|
||||||
case errors::Unexpected_Empty_Source:
|
case errors::Unexpected_Empty_Source:
|
||||||
return os << "unexpected end of input\n";
|
return os << "unexpected end of input\n";
|
||||||
@ -144,6 +145,14 @@ Error errors::failed_numeric_parsing(Location location, std::errc errc, std::str
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Error errors::function_not_defined(Value const& value)
|
||||||
|
{
|
||||||
|
Error err;
|
||||||
|
err.type = Function_Not_Defined;
|
||||||
|
err.message = "Function '" + value.s + "' has not been defined yet";
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
void errors::all_tokens_were_not_parsed(std::span<Token> tokens)
|
void errors::all_tokens_were_not_parsed(std::span<Token> tokens)
|
||||||
{
|
{
|
||||||
error_heading(std::cerr, std::nullopt, Error_Level::Bug);
|
error_heading(std::cerr, std::nullopt, Error_Level::Bug);
|
||||||
|
96
src/interpreter.cc
Normal file
96
src/interpreter.cc
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
#include <musique.hh>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
Interpreter::Interpreter()
|
||||||
|
: Interpreter(std::cout)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Interpreter::Interpreter(std::ostream& out)
|
||||||
|
: out(out)
|
||||||
|
{
|
||||||
|
functions["typeof"] = [](std::vector<Value> args) -> Value {
|
||||||
|
assert(args.size() == 1, "typeof expects only one argument");
|
||||||
|
switch (args.front().type) {
|
||||||
|
case Value::Type::Nil: return Value::symbol("nil");
|
||||||
|
case Value::Type::Number: return Value::symbol("number");
|
||||||
|
case Value::Type::Symbol: return Value::symbol("symbol");
|
||||||
|
}
|
||||||
|
unreachable();
|
||||||
|
};
|
||||||
|
|
||||||
|
functions["say"] = [&](std::vector<Value> args) -> Value {
|
||||||
|
for (auto it = args.begin(); it != args.end(); ++it) {
|
||||||
|
out << *it;
|
||||||
|
if (std::next(it) != args.end())
|
||||||
|
out << ' ';
|
||||||
|
}
|
||||||
|
out << '\n';
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<Value> Interpreter::eval(Ast &&ast)
|
||||||
|
{
|
||||||
|
switch (ast.type) {
|
||||||
|
case Ast::Type::Literal:
|
||||||
|
return Value::from(ast.token);
|
||||||
|
|
||||||
|
case Ast::Type::Binary:
|
||||||
|
{
|
||||||
|
std::vector<Value> values;
|
||||||
|
values.reserve(ast.arguments.size());
|
||||||
|
for (auto& a : ast.arguments) {
|
||||||
|
values.push_back(Try(eval(std::move(a))));
|
||||||
|
}
|
||||||
|
|
||||||
|
Value result = values.front();
|
||||||
|
if (ast.token.source == "+") {
|
||||||
|
for (auto &v : std::span(values).subspan(1)) {
|
||||||
|
assert(result.type == Value::Type::Number, "LHS of + should be a number");
|
||||||
|
assert(v.type == Value::Type::Number, "RHS of + should be a number");
|
||||||
|
result.n += v.n;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} else if (ast.token.source == "*") {
|
||||||
|
for (auto &v : std::span(values).subspan(1)) {
|
||||||
|
assert(result.type == Value::Type::Number, "LHS of * should be a number");
|
||||||
|
assert(v.type == Value::Type::Number, "RHS of * should be a number");
|
||||||
|
result.n *= v.n;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
unimplemented();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Ast::Type::Sequence:
|
||||||
|
{
|
||||||
|
Value v;
|
||||||
|
for (auto &a : ast.arguments)
|
||||||
|
v = Try(eval(std::move(a)));
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Ast::Type::Call:
|
||||||
|
{
|
||||||
|
Value func = Try(eval(std::move(ast.arguments.front())));
|
||||||
|
assert(func.type == Value::Type::Symbol, "Currently only symbols can be called");
|
||||||
|
if (auto it = functions.find(func.s); it != functions.end()) {
|
||||||
|
std::vector<Value> values;
|
||||||
|
values.reserve(ast.arguments.size());
|
||||||
|
for (auto& a : std::span(ast.arguments).subspan(1)) {
|
||||||
|
values.push_back(Try(eval(std::move(a))));
|
||||||
|
}
|
||||||
|
return it->second(std::move(values));
|
||||||
|
} else {
|
||||||
|
return errors::function_not_defined(func);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
unimplemented();
|
||||||
|
}
|
||||||
|
}
|
32
src/main.cc
32
src/main.cc
@ -7,6 +7,8 @@
|
|||||||
|
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
static bool ast_only_mode = false;
|
||||||
|
|
||||||
static std::string_view pop(std::span<char const*> &span)
|
static std::string_view pop(std::span<char const*> &span)
|
||||||
{
|
{
|
||||||
auto element = span.front();
|
auto element = span.front();
|
||||||
@ -23,16 +25,24 @@ void usage()
|
|||||||
" where options are:\n"
|
" where options are:\n"
|
||||||
" -c CODE\n"
|
" -c CODE\n"
|
||||||
" --run CODE\n"
|
" --run CODE\n"
|
||||||
" executes given code\n";
|
" executes given code\n"
|
||||||
|
"\n"
|
||||||
|
" --ast\n"
|
||||||
|
" prints ast for given code\n";
|
||||||
std::exit(1);
|
std::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Result<void> run(std::string_view source, std::string_view filename)
|
static Result<void> run(std::string_view source, std::string_view filename)
|
||||||
{
|
{
|
||||||
auto ast = Try(Parser::parse(source, filename));
|
auto ast = Try(Parser::parse(source, filename));
|
||||||
std::cout << "successfully parsed: " << source << " \n";
|
|
||||||
|
if (ast_only_mode) {
|
||||||
dump(ast);
|
dump(ast);
|
||||||
return {};
|
return {};
|
||||||
|
}
|
||||||
|
Interpreter interpreter;
|
||||||
|
std::cout << Try(interpreter.eval(std::move(ast))) << std::endl;
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// We make sure that through life of interpreter source code is allways allocated
|
// We make sure that through life of interpreter source code is allways allocated
|
||||||
@ -44,12 +54,13 @@ static Result<void> Main(std::span<char const*> args)
|
|||||||
usage();
|
usage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool runned_something = false;
|
||||||
std::vector<std::string_view> files;
|
std::vector<std::string_view> files;
|
||||||
|
|
||||||
while (not args.empty()) {
|
while (not args.empty()) {
|
||||||
std::string_view arg = pop(args);
|
std::string_view arg = pop(args);
|
||||||
|
|
||||||
if (!arg.starts_with('-')) {
|
if (arg == "-" || !arg.starts_with('-')) {
|
||||||
files.push_back(std::move(arg));
|
files.push_back(std::move(arg));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -62,6 +73,12 @@ static Result<void> Main(std::span<char const*> args)
|
|||||||
|
|
||||||
auto const source = pop(args);
|
auto const source = pop(args);
|
||||||
Try(run(source, "arguments"));
|
Try(run(source, "arguments"));
|
||||||
|
runned_something = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg == "--ast") {
|
||||||
|
ast_only_mode = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,13 +86,18 @@ static Result<void> Main(std::span<char const*> args)
|
|||||||
std::exit(1);
|
std::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!runned_something && files.empty()) {
|
||||||
|
usage();
|
||||||
|
}
|
||||||
|
|
||||||
for (auto const& path : files) {
|
for (auto const& path : files) {
|
||||||
|
if (path == "-") {
|
||||||
|
eternal_sources.emplace_back(std::istreambuf_iterator<char>(std::cin), std::istreambuf_iterator<char>());
|
||||||
|
} else {
|
||||||
if (not fs::exists(path)) {
|
if (not fs::exists(path)) {
|
||||||
std::cerr << "musique: error: couldn't open file: " << path << std::endl;
|
std::cerr << "musique: error: couldn't open file: " << path << std::endl;
|
||||||
std::exit(1);
|
std::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
|
||||||
std::ifstream source_file{fs::path(path)};
|
std::ifstream source_file{fs::path(path)};
|
||||||
eternal_sources.emplace_back(std::istreambuf_iterator<char>(source_file), std::istreambuf_iterator<char>());
|
eternal_sources.emplace_back(std::istreambuf_iterator<char>(source_file), std::istreambuf_iterator<char>());
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,8 @@ namespace errors
|
|||||||
Unexpected_Token_Type,
|
Unexpected_Token_Type,
|
||||||
Unexpected_Empty_Source,
|
Unexpected_Empty_Source,
|
||||||
Failed_Numeric_Parsing,
|
Failed_Numeric_Parsing,
|
||||||
|
|
||||||
|
Function_Not_Defined
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -380,6 +382,49 @@ struct Number
|
|||||||
|
|
||||||
std::ostream& operator<<(std::ostream& os, Number const& num);
|
std::ostream& operator<<(std::ostream& os, Number const& num);
|
||||||
|
|
||||||
|
// TODO Add location
|
||||||
|
struct Value
|
||||||
|
{
|
||||||
|
static Result<Value> from(Token t);
|
||||||
|
static Value number(Number n);
|
||||||
|
static Value symbol(std::string s);
|
||||||
|
|
||||||
|
enum class Type
|
||||||
|
{
|
||||||
|
Nil,
|
||||||
|
Number,
|
||||||
|
Symbol,
|
||||||
|
};
|
||||||
|
|
||||||
|
Type type = Type::Nil;
|
||||||
|
Number n{};
|
||||||
|
|
||||||
|
// 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{};
|
||||||
|
|
||||||
|
bool operator==(Value const& other) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::ostream& operator<<(std::ostream& os, Value const& v);
|
||||||
|
|
||||||
|
using Function = std::function<Value(std::vector<Value>)>;
|
||||||
|
|
||||||
|
struct Interpreter
|
||||||
|
{
|
||||||
|
std::ostream &out;
|
||||||
|
std::unordered_map<std::string, Function> functions;
|
||||||
|
|
||||||
|
Interpreter();
|
||||||
|
Interpreter(std::ostream& out);
|
||||||
|
Interpreter(Interpreter const&) = delete;
|
||||||
|
Interpreter(Interpreter &&) = default;
|
||||||
|
|
||||||
|
Result<Value> eval(Ast &&ast);
|
||||||
|
};
|
||||||
|
|
||||||
namespace errors
|
namespace errors
|
||||||
{
|
{
|
||||||
Error unrecognized_character(u32 invalid_character);
|
Error unrecognized_character(u32 invalid_character);
|
||||||
@ -391,6 +436,8 @@ namespace errors
|
|||||||
|
|
||||||
Error failed_numeric_parsing(Location location, std::errc errc, std::string_view source);
|
Error failed_numeric_parsing(Location location, std::errc errc, std::string_view source);
|
||||||
|
|
||||||
|
Error function_not_defined(Value const& v);
|
||||||
|
|
||||||
[[noreturn]]
|
[[noreturn]]
|
||||||
void all_tokens_were_not_parsed(std::span<Token>);
|
void all_tokens_were_not_parsed(std::span<Token>);
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,19 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
static Ast wrap_if_several(std::vector<Ast> &&ast, Ast(*wrapper)(std::vector<Ast>));
|
static Ast wrap_if_several(std::vector<Ast> &&ast, Ast(*wrapper)(std::vector<Ast>));
|
||||||
static Result<std::vector<Ast>> parse_one_or_more(Parser &p, Result<Ast> (Parser::*parser)(), std::optional<Token::Type> separator = std::nullopt);
|
|
||||||
|
enum class At_Least : bool
|
||||||
|
{
|
||||||
|
Zero,
|
||||||
|
One
|
||||||
|
};
|
||||||
|
|
||||||
|
static Result<std::vector<Ast>> parse_many(
|
||||||
|
Parser &p,
|
||||||
|
Result<Ast> (Parser::*parser)(),
|
||||||
|
std::optional<Token::Type> separator,
|
||||||
|
At_Least at_least);
|
||||||
|
|
||||||
|
|
||||||
Result<Ast> Parser::parse(std::string_view source, std::string_view filename)
|
Result<Ast> Parser::parse(std::string_view source, std::string_view filename)
|
||||||
{
|
{
|
||||||
@ -29,7 +41,7 @@ Result<Ast> Parser::parse(std::string_view source, std::string_view filename)
|
|||||||
|
|
||||||
Result<Ast> Parser::parse_sequence()
|
Result<Ast> Parser::parse_sequence()
|
||||||
{
|
{
|
||||||
auto seq = Try(parse_one_or_more(*this, &Parser::parse_expression, Token::Type::Expression_Separator));
|
auto seq = Try(parse_many(*this, &Parser::parse_expression, Token::Type::Expression_Separator, At_Least::Zero));
|
||||||
return wrap_if_several(std::move(seq), Ast::sequence);
|
return wrap_if_several(std::move(seq), Ast::sequence);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,7 +52,7 @@ Result<Ast> Parser::parse_expression()
|
|||||||
|
|
||||||
Result<Ast> Parser::parse_infix_expression()
|
Result<Ast> Parser::parse_infix_expression()
|
||||||
{
|
{
|
||||||
auto atomics = Try(parse_one_or_more(*this, &Parser::parse_atomic_expression));
|
auto atomics = Try(parse_many(*this, &Parser::parse_atomic_expression, std::nullopt, At_Least::One));
|
||||||
auto lhs = wrap_if_several(std::move(atomics), Ast::call);
|
auto lhs = wrap_if_several(std::move(atomics), Ast::call);
|
||||||
|
|
||||||
if (expect(Token::Type::Operator)) {
|
if (expect(Token::Type::Operator)) {
|
||||||
@ -71,7 +83,7 @@ Result<Ast> Parser::parse_atomic_expression()
|
|||||||
auto start = token_id;
|
auto start = token_id;
|
||||||
std::vector<Ast> parameters;
|
std::vector<Ast> parameters;
|
||||||
|
|
||||||
if (auto p = parse_one_or_more(*this, &Parser::parse_identifier); p && expect(Token::Type::Variable_Separator)) {
|
if (auto p = parse_many(*this, &Parser::parse_identifier, std::nullopt, At_Least::One); p && expect(Token::Type::Variable_Separator)) {
|
||||||
consume();
|
consume();
|
||||||
parameters = std::move(p).value();
|
parameters = std::move(p).value();
|
||||||
} else {
|
} else {
|
||||||
@ -108,10 +120,20 @@ Result<Ast> Parser::parse_identifier()
|
|||||||
return lit;
|
return lit;
|
||||||
}
|
}
|
||||||
|
|
||||||
Result<std::vector<Ast>> parse_one_or_more(Parser &p, Result<Ast> (Parser::*parser)(), std::optional<Token::Type> separator)
|
|
||||||
|
|
||||||
|
static Result<std::vector<Ast>> parse_many(
|
||||||
|
Parser &p,
|
||||||
|
Result<Ast> (Parser::*parser)(),
|
||||||
|
std::optional<Token::Type> separator,
|
||||||
|
At_Least at_least)
|
||||||
{
|
{
|
||||||
std::vector<Ast> trees;
|
std::vector<Ast> trees;
|
||||||
Result<Ast> expr;
|
Result<Ast> expr;
|
||||||
|
|
||||||
|
if (at_least == At_Least::Zero && p.token_id >= p.tokens.size())
|
||||||
|
return {};
|
||||||
|
|
||||||
while ((expr = (p.*parser)()).has_value()) {
|
while ((expr = (p.*parser)()).has_value()) {
|
||||||
trees.push_back(std::move(expr).value());
|
trees.push_back(std::move(expr).value());
|
||||||
if (separator) {
|
if (separator) {
|
||||||
@ -197,8 +219,8 @@ Ast Ast::sequence(std::vector<Ast> expressions)
|
|||||||
ast.type = Type::Sequence;
|
ast.type = Type::Sequence;
|
||||||
if (!expressions.empty()) {
|
if (!expressions.empty()) {
|
||||||
ast.location = expressions.front().location;
|
ast.location = expressions.front().location;
|
||||||
}
|
|
||||||
ast.arguments = std::move(expressions);
|
ast.arguments = std::move(expressions);
|
||||||
|
}
|
||||||
return ast;
|
return ast;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -280,8 +302,7 @@ struct Indent
|
|||||||
|
|
||||||
std::ostream& operator<<(std::ostream& os, Indent n)
|
std::ostream& operator<<(std::ostream& os, Indent n)
|
||||||
{
|
{
|
||||||
while (n.count-- > 0)
|
std::fill_n(std::ostreambuf_iterator<char>(os), n.count, ' ');
|
||||||
os.put(' ');
|
|
||||||
return os;
|
return os;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
61
src/tests/interpreter.cc
Normal file
61
src/tests/interpreter.cc
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
#include <boost/ut.hpp>
|
||||||
|
#include <musique.hh>
|
||||||
|
|
||||||
|
using namespace boost::ut;
|
||||||
|
using namespace std::string_view_literals;
|
||||||
|
|
||||||
|
static auto capture_errors(auto lambda, reflection::source_location sl = reflection::source_location::current())
|
||||||
|
{
|
||||||
|
return [=] {
|
||||||
|
auto result = lambda();
|
||||||
|
if (not result.has_value()) {
|
||||||
|
expect(false, sl) << "failed test with error: " << result.error();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void evaluates_to(Value value, std::string_view source_code, reflection::source_location sl = reflection::source_location::current())
|
||||||
|
{
|
||||||
|
capture_errors([=]() -> Result<void> {
|
||||||
|
Interpreter interpreter;
|
||||||
|
auto result = Try(interpreter.eval(Try(Parser::parse(source_code, "test"))));
|
||||||
|
expect(eq(result, value));
|
||||||
|
return {};
|
||||||
|
}, sl)();
|
||||||
|
}
|
||||||
|
|
||||||
|
void produces_output(std::string_view source_code, std::string_view expected_output, reflection::source_location sl = reflection::source_location::current())
|
||||||
|
{
|
||||||
|
capture_errors([=]() -> Result<void> {
|
||||||
|
std::stringstream ss;
|
||||||
|
Interpreter interpreter(ss);
|
||||||
|
Try(interpreter.eval(Try(Parser::parse(source_code, "test"))));
|
||||||
|
expect(eq(ss.str(), expected_output)) << "different evaluation output";
|
||||||
|
return {};
|
||||||
|
}, sl)();
|
||||||
|
}
|
||||||
|
|
||||||
|
suite intepreter_test = [] {
|
||||||
|
"Interpreter"_test = [] {
|
||||||
|
should("evaluate literals") = [] {
|
||||||
|
evaluates_to(Value{}, "nil");
|
||||||
|
evaluates_to(Value::number(Number(10)), "10");
|
||||||
|
evaluates_to(Value::symbol("notexistingsymbol"), "notexistingsymbol");
|
||||||
|
};
|
||||||
|
|
||||||
|
should("evaluate arithmetic") = [] {
|
||||||
|
evaluates_to(Value::number(Number(10)), "5 + 3 + 2");
|
||||||
|
evaluates_to(Value::number(Number(25)), "5 * (3 + 2)");
|
||||||
|
};
|
||||||
|
|
||||||
|
should("call builtin functions") = [] {
|
||||||
|
evaluates_to(Value::symbol("nil"), "typeof nil");
|
||||||
|
evaluates_to(Value::symbol("symbol"), "typeof foo");
|
||||||
|
evaluates_to(Value::symbol("number"), "typeof 100");
|
||||||
|
|
||||||
|
produces_output("say 5", "5\n");
|
||||||
|
produces_output("say 1; say 2; say 3", "1\n2\n3\n");
|
||||||
|
produces_output("say 1 2 3", "1 2 3\n");
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
@ -14,6 +14,10 @@ void expect_ast(
|
|||||||
}
|
}
|
||||||
|
|
||||||
suite parser_test = [] {
|
suite parser_test = [] {
|
||||||
|
"Empty file parsing"_test = [] {
|
||||||
|
expect_ast("", Ast::sequence({}));
|
||||||
|
};
|
||||||
|
|
||||||
"Literal parsing"_test = [] {
|
"Literal parsing"_test = [] {
|
||||||
expect_ast("1", Ast::literal(Token { Token::Type::Numeric, "1", {} }));
|
expect_ast("1", Ast::literal(Token { Token::Type::Numeric, "1", {} }));
|
||||||
};
|
};
|
||||||
|
62
src/value.cc
Normal file
62
src/value.cc
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
#include <musique.hh>
|
||||||
|
|
||||||
|
Result<Value> Value::from(Token t)
|
||||||
|
{
|
||||||
|
switch (t.type) {
|
||||||
|
case Token::Type::Numeric:
|
||||||
|
return Number::from(std::move(t)).map(Value::number);
|
||||||
|
|
||||||
|
case Token::Type::Symbol:
|
||||||
|
if (t.source == "nil")
|
||||||
|
return Value{};
|
||||||
|
return Value::symbol(std::string(t.source));
|
||||||
|
|
||||||
|
default:
|
||||||
|
unimplemented();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Value::number(Number n)
|
||||||
|
{
|
||||||
|
Value v;
|
||||||
|
v.type = Type::Number;
|
||||||
|
v.n = n;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Value::symbol(std::string s)
|
||||||
|
{
|
||||||
|
Value v;
|
||||||
|
v.type = Type::Symbol;
|
||||||
|
v.s = s;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Value::operator==(Value const& other) const
|
||||||
|
{
|
||||||
|
if (type != other.type)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case Type::Nil: return true;
|
||||||
|
case Type::Number: return n == other.n;
|
||||||
|
case Type::Symbol: return s == other.s;
|
||||||
|
}
|
||||||
|
|
||||||
|
unreachable();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostream& operator<<(std::ostream& os, Value const& v)
|
||||||
|
{
|
||||||
|
switch (v.type) {
|
||||||
|
case Value::Type::Nil:
|
||||||
|
return os << "nil";
|
||||||
|
|
||||||
|
case Value::Type::Number:
|
||||||
|
return os << v.n;
|
||||||
|
|
||||||
|
case Value::Type::Symbol:
|
||||||
|
return os << v.s;
|
||||||
|
}
|
||||||
|
unreachable();
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user