Report errors with syntactic context in the compiler

This commit is contained in:
Konstantin Nazarov 2024-09-11 23:25:17 +01:00
parent d280c34e66
commit b7d51f647f
Signed by: knazarov
GPG key ID: 4CFE0A42FA409C22
3 changed files with 94 additions and 69 deletions

View file

@ -819,10 +819,10 @@ Result<Value> Object::rest() const { return ERROR(TypeMismatch); }
Result<uint64_t> Object::size() const { return ERROR(TypeMismatch); }
Result<bool> syntax_is_list(const Value& value) {
if (!value.is<Syntax>()) return value.is<Pair>();
if (!value.is<Syntax>()) return value.is<Pair>() || value.is<Nil>();
auto val = TRY(value.to<Syntax>()->expression());
return val.is<Pair>();
return val.is<Pair>() || val.is<Nil>();
}
Result<bool> syntax_is_nil(const Value& value) {
@ -876,6 +876,10 @@ Result<Value> Syntax::rest() const {
auto val = TRY(expression());
return val.rest();
}
Result<uint64_t> Syntax::size() const {
auto val = TRY(expression());
return val.size();
}
Result<String> build_string(const String& value) { return value.copy(); }

View file

@ -725,6 +725,7 @@ class Syntax : public Object {
Result<Value> second() const final;
Result<Value> third() const final;
Result<Value> rest() const final;
Result<uint64_t> size() const final;
virtual Result<Value> copy_value() const final;
Result<Syntax> copy() const;

View file

@ -339,7 +339,7 @@ Result<Expression> Compiler::compile_comparison(Context& context, Symbol& op,
}
if (cur.is<Nil>()) {
return ERROR(CompilationError);
return syntax_error(expr, "Comparison must have at least one argument");
}
uint64_t result = context.alloc_reg();
@ -382,25 +382,18 @@ Result<Expression> Compiler::compile_comparison(Context& context, Symbol& op,
Result<Expression> Compiler::compile_if(Context& context, Symbol& op,
const Value& expr) {
Value first = TRY(expr.rest());
Value rest = TRY(expr.rest());
Expression ex = TRY(Expression::create());
if (!TRY(syntax_is_list(first))) {
return ERROR(CompilationError);
auto num_params = TRY(rest.size());
if (num_params != 3) {
return syntax_error(TRY(expr.first()),
"\"if\" form must have exactly 3 arguments");
}
Value second = TRY(first.rest());
if (!TRY(syntax_is_list(second))) {
return ERROR(CompilationError);
}
Value third = TRY(second.rest());
if (!TRY(syntax_is_list(third))) {
return ERROR(CompilationError);
}
auto condition = TRY(first.first());
auto condition = TRY(rest.first());
auto option1 = TRY(rest.second());
auto option2 = TRY(rest.third());
auto condition_comp = TRY(compile_expr(context, condition));
@ -408,14 +401,12 @@ Result<Expression> Compiler::compile_if(Context& context, Symbol& op,
uint64_t firstreg = condition_comp.reg;
uint64_t reg = firstreg;
auto option1 = TRY(second.first());
auto option1_comp = TRY(compile_expr(context, option1));
uint64_t option1_reg = option1_comp.reg;
context.maxreg = firstreg + 1;
auto option2 = TRY(third.first());
auto option2_comp = TRY(compile_expr(context, option2));
int64_t true_const = TRY(context.add_const(TRY(Bool::create(true))));
@ -443,8 +434,10 @@ Result<Expression> Compiler::compile_and(Context& context, Symbol& op,
const Value& expr) {
Value param = TRY(expr.rest());
Expression ex = TRY(Expression::create());
if (!TRY(syntax_is_list(param))) {
return ERROR(CompilationError);
if (param.is<Nil>()) {
return syntax_error(TRY(expr.first()),
"\"and\" form must have at least one parameter");
}
uint64_t prev_reg = 0;
@ -456,10 +449,6 @@ Result<Expression> Compiler::compile_and(Context& context, Symbol& op,
bool is_first = true;
while (!param.is<Nil>()) {
if (!TRY(syntax_is_list(param))) {
return ERROR(CompilationError);
}
auto rest = TRY(param.rest());
Value param_val = TRY(param.first());
@ -490,8 +479,9 @@ Result<Expression> Compiler::compile_or(Context& context, Symbol& op,
const Value& expr) {
Value param = TRY(expr.rest());
Expression ex = TRY(Expression::create());
if (!TRY(syntax_is_list(param))) {
return ERROR(CompilationError);
if (param.is<Nil>()) {
return syntax_error(TRY(expr.first()),
"\"or\" form must have at least one parameter");
}
uint64_t prev_reg = 0;
@ -503,10 +493,6 @@ Result<Expression> Compiler::compile_or(Context& context, Symbol& op,
bool is_first = true;
while (!param.is<Nil>()) {
if (!TRY(syntax_is_list(param))) {
return ERROR(CompilationError);
}
auto rest = TRY(param.rest());
Value param_val = TRY(param.first());
@ -537,8 +523,9 @@ Result<Expression> Compiler::compile_not(Context& context, Symbol& op,
const Value& expr) {
Value first = TRY(expr.rest());
Expression ex = TRY(Expression::create());
if (!TRY(syntax_is_list(first))) {
return ERROR(CompilationError);
if (TRY(first.size()) != 1) {
return syntax_error(TRY(expr.first()),
"\"not\" form must have exactly one parameter");
}
auto first_expr = TRY(first.first());
@ -562,19 +549,44 @@ Result<Expression> Compiler::compile_not(Context& context, Symbol& op,
Result<Expression> Compiler::compile_def(Context& context, Symbol& op,
const Value& expr) {
uint64_t expr_size = TRY(expr.size());
if (expr_size < 2) {
return syntax_error(TRY(expr.first()),
"\"def\" form must have at least one parameter");
}
if (expr_size > 3) {
return syntax_error(TRY(expr.first()),
"Too many parameters for \"def\" form");
}
auto varname = TRY(expr.second());
auto varname_unwrapped = TRY(syntax_unwrap(varname));
if (!varname_unwrapped.is<Symbol>()) return ERROR(CompilationError);
if (!varname_unwrapped.is<Symbol>()) {
return syntax_error(varname,
"Variable name in \"def\" form must be a symbol");
}
Expression ex = TRY(Expression::create());
int64_t gname = TRY(context.add_const(varname_unwrapped));
uint64_t maxreg = context.maxreg;
// No value for the variable is specified - just load "nil" into it
if (expr_size == 2) {
int64_t c = TRY(context.add_const(Value(TRY(Nil::create()))));
TRY(ex.add_opcode(Oc::GlobalStore, {1, (int64_t)gname}, {1, (int64_t)c}));
TRY(ex.add_opcode(Oc::Mov, {0, (int64_t)maxreg}, {1, (int64_t)c}));
ex.reg = maxreg;
context.maxreg = maxreg + 1;
return std::move(ex);
}
auto var_expr = TRY(expr.third());
uint64_t maxreg = context.maxreg;
Expression ex = TRY(Expression::create());
auto comp = TRY(compile_expr(context, var_expr));
ex.add_code(comp.code);
int64_t gname = TRY(context.add_const(varname_unwrapped));
TRY(ex.add_opcode(Oc::GlobalStore, {1, (int64_t)gname},
{0, (int64_t)comp.reg}));
@ -586,6 +598,12 @@ Result<Expression> Compiler::compile_def(Context& context, Symbol& op,
Result<Expression> Compiler::compile_quote(Context& context, Symbol& op,
const Value& expr) {
uint64_t expr_size = TRY(expr.size());
if (expr_size != 2) {
return syntax_error(TRY(expr.first()),
"\"quote\" form must have exactly one parameter");
}
auto quoted = TRY(expr.second());
quoted = TRY(syntax_unwrap_all(quoted));
@ -603,6 +621,12 @@ Result<Expression> Compiler::compile_quote(Context& context, Symbol& op,
Result<Expression> Compiler::compile_syntax(Context& context, Symbol& op,
const Value& expr) {
uint64_t expr_size = TRY(expr.size());
if (expr_size != 2) {
return syntax_error(TRY(expr.first()),
"\"syntax\" form must have exactly one parameter");
}
auto quoted = TRY(expr.second());
uint64_t maxreg = context.maxreg;
@ -634,19 +658,16 @@ Result<Expression> Compiler::compile_fn(Context& context, Symbol& op,
ctx.fname = TRY(name.copy());
rest = TRY(rest.rest());
if (!TRY(syntax_is_list(rest))) {
return ERROR(CompilationError);
}
}
auto param = TRY(rest.first());
if (!TRY(syntax_is_list(param))) {
return syntax_error(param, "Function parameters must be a list");
}
uint64_t arity = 0;
while (!TRY(syntax_is_nil(param))) {
if (!TRY(syntax_is_list(param))) {
return ERROR(CompilationError);
}
auto param_name = TRY(param.first());
auto param_name_unwrapped = TRY(syntax_unwrap(param_name));
@ -662,10 +683,6 @@ Result<Expression> Compiler::compile_fn(Context& context, Symbol& op,
Value body = TRY(rest.rest());
if (!TRY(syntax_is_list(body))) {
return ERROR(CompilationError);
}
auto ex = TRY(compile_body(ctx, body));
TRY(ex.add_opcode(Oc::Mov, {0, (int64_t)0}, {0, (int64_t)ex.reg}));
@ -756,30 +773,30 @@ Result<Expression> Compiler::compile_let(Context& context, Symbol& op,
// Save the variable bindings to restore it later
Dict saved_vars = TRY(context.variables_dict.copy());
auto first = TRY(expr.rest());
auto rest = TRY(expr.rest());
if (!TRY(syntax_is_list(first))) {
return ERROR(CompilationError);
if (TRY(expr.size()) < 2) {
return syntax_error(TRY(expr.first()),
"\"let\" form must have at least one parameter");
}
uint64_t maxreg = context.maxreg;
auto bindings = TRY(first.first());
auto bindings = TRY(rest.first());
bindings = TRY(syntax_unwrap(bindings));
Expression ex_res = TRY(Expression::create());
while (!bindings.is<Nil>()) {
auto binding = TRY(bindings.first());
if (!TRY(syntax_is_list(binding))) {
return ERROR(CompilationError);
}
auto binding_name = TRY(binding.first());
auto binding_name_unwrapped = TRY(syntax_unwrap(binding_name));
auto binding_expr = TRY(binding.second());
if (!binding_name_unwrapped.is<Symbol>()) return ERROR(CompilationError);
if (!binding_name_unwrapped.is<Symbol>()) {
return syntax_error(binding_name, "Binding name must be a symbol");
}
int64_t reg = TRY(context.update_var(binding_name_unwrapped));
auto ex = TRY(compile_expr(context, binding_expr));
@ -791,13 +808,9 @@ Result<Expression> Compiler::compile_let(Context& context, Symbol& op,
bindings = TRY(bindings.rest());
}
Value second = TRY(first.rest());
Value body = TRY(rest.rest());
if (!TRY(syntax_is_list(second))) {
return ERROR(CompilationError);
}
auto ex = TRY(compile_body(context, second));
auto ex = TRY(compile_body(context, body));
TRY(ex.add_opcode(Oc::Mov, {0, (int64_t)maxreg}, {0, (int64_t)ex.reg}));
ex_res.add_code(ex.code);
@ -816,6 +829,16 @@ Result<Expression> Compiler::compile_body(Context& context, const Value& expr) {
int64_t maxreg = context.maxreg;
// Body is empty, in which case just replace it with "nil" value
if (TRY(expr.size()) == 0) {
int64_t c = TRY(context.add_const(Value(TRY(Nil::create()))));
TRY(ex_res.add_opcode(Oc::Mov, {0, (int64_t)maxreg}, {1, (int64_t)c}));
ex_res.reg = maxreg;
context.maxreg = maxreg + 1;
return std::move(ex_res);
}
cur = TRY(syntax_unwrap(cur));
while (!cur.is<Nil>()) {
auto expr_val = TRY(cur.first());
@ -860,9 +883,6 @@ Result<Expression> Compiler::compile_function_call(Context& context,
int64_t maxreg = context.maxreg;
while (!param.is<Nil>()) {
if (!TRY(syntax_is_list(param))) {
return ERROR(CompilationError);
}
Value param_val = TRY(param.first());
auto param_ex = TRY(compile_expr(context, param_val));