#include "stdlib.hpp" #include "common.hpp" #include "error.hpp" #include "fio.hpp" #include "pod.hpp" #include "sourcerange.hpp" #include "writer.hpp" typedef Result (*StdlibFunctionIdPtr)(const StackFrame& stack); struct StdlibFunctionEntry { const char* name; StdlibFunctionId fun_id; StdlibFunctionIdPtr fun_ptr; }; typedef Result (*StdlibTaskIdPtr)(const Array& params); struct StdlibTaskEntry { const char* name; StdlibTaskId task_id; StdlibTaskIdPtr task_ptr; }; Result raise_task(const StackFrame& stack, StdlibTaskId task_id, const Array& params) { auto val = Value(TRY(Task::create((uint64_t)task_id, params))); auto cont = Value(TRY(Continuation::create(val, stack))); return ERROR_OBJ(Raise, cont); } Result raise_task(const StackFrame& stack, StdlibTaskId task_id, const Value& param) { Array params = TRY(Array::create()); params = TRY(params.append(param)); auto val = Value(TRY(Task::create((uint64_t)task_id, params))); auto cont = Value(TRY(Continuation::create(val, stack))); return ERROR_OBJ(Raise, cont); } Result task_return(const StackFrame& stack, uint64_t idx) { auto result = TRY(stack.get(idx)); if (!result.is()) return ERROR(TypeMismatch); const auto& task_result = *result.to(); auto error = TRY(task_result.error()); if (!error.is()) { return ERROR_OBJ(TaskFailed, error); } auto res = TRY(stack.set(0, TRY(task_result.result()))); res = TRY(res.ret(0)); return res; } Result stdlib_unknown(const StackFrame& stack) { return ERROR(NotImplemented); } Result tostring(const Value& params, bool newline = false) { auto size = TRY(params.size()); String str = TRY(String::create("")); for (uint64_t i = 0; i < size; i++) { Value param = TRY(params.get(i)); if (i != 0) { str = TRY(str.concat(" ")); } if (param.is()) { str = TRY(str.concat(*param.to())); } else { auto s = TRY(write_one(param)); str = TRY(str.concat(s)); } } if (newline) { str = TRY(str.concat("\n")); } return str; } Result stdlib_print(const StackFrame& stack) { auto params = TRY(stack.get(0)); auto stack_size = TRY(stack.size()); if (stack_size > 1) { return task_return(stack, 1); } auto str = Value(TRY(tostring(params))); return raise_task(stack, StdlibTaskId::Print, str); } Result stdlib_println(const StackFrame& stack) { auto params = TRY(stack.get(0)); auto stack_size = TRY(stack.size()); if (stack_size > 1) { return task_return(stack, 1); } auto str = Value(TRY(tostring(params, true))); return raise_task(stack, StdlibTaskId::Print, str); } Result stdlib_prn(const StackFrame& stack) { return ERROR(NotImplemented); } Result stdlib_slurp(const StackFrame& stack) { auto params = TRY(stack.get(0)); auto stack_size = TRY(stack.size()); if (stack_size > 1) { return task_return(stack, 1); } if (TRY(params.size()) != 1) { return ERROR(ArgumentCountMismatch); } auto fname = TRY(params.get(0)); return raise_task(stack, StdlibTaskId::ReadFile, fname); } Result stdlib_spit(const StackFrame& stack) { auto params = TRY(stack.get(0)); auto stack_size = TRY(stack.size()); if (stack_size > 1) { return task_return(stack, 1); } if (TRY(params.size()) != 2) { return ERROR(ArgumentCountMismatch); } if (!params.is()) { return ERROR(TypeMismatch); } return raise_task(stack, StdlibTaskId::WriteFile, *params.to()); } Result stdlib_assert(const StackFrame& stack) { auto params = TRY(stack.get(0)); auto size = TRY(params.size()); for (uint64_t i = 0; i < size; i++) { Value param = TRY(params.get(i)); if (!param.is()) return ERROR(AssertionFailed); auto v = param.to()->value(); if (!v) return ERROR(AssertionFailed); } auto nil = Value(TRY(Nil::create())); auto res = TRY(stack.set(0, nil)); res = TRY(res.ret(0)); return res; } Result stdlib_dict(const StackFrame& stack) { auto params = TRY(stack.get(0)); if (!params.is()) return ERROR(TypeMismatch); Value d = TRY(Dict::create(*params.to())); auto res = TRY(stack.set(0, d)); res = TRY(res.ret(0)); return res; } Result stdlib_list(const StackFrame& stack) { auto params = TRY(stack.get(0)); if (!params.is()) return ERROR(TypeMismatch); Value d = TRY(Pair::create(*params.to())); auto res = TRY(stack.set(0, d)); res = TRY(res.ret(0)); return res; } Result stdlib_array(const StackFrame& stack) { auto params = TRY(stack.get(0)); auto res = TRY(stack.set(0, params)); res = TRY(res.ret(0)); return res; } Result stdlib_get(const StackFrame& stack) { auto params = TRY(stack.get(0)); auto size = TRY(params.size()); if (size != 2) return ERROR(ArgumentCountMismatch); Value collection = TRY(params.get(0)); Value key = TRY(params.get(1)); auto val = TRY(collection.get(key)); auto res = TRY(stack.set(0, val)); res = TRY(res.ret(0)); return res; } Result stdlib_set(const StackFrame& stack) { auto params = TRY(stack.get(0)); auto size = TRY(params.size()); if (size != 3) return ERROR(ArgumentCountMismatch); Value collection = TRY(params.get(0)); Value key = TRY(params.get(1)); Value value = TRY(params.get(2)); collection = TRY(collection.set(key, value)); auto res = TRY(stack.set(0, collection)); res = TRY(res.ret(0)); return res; } Result stdlib_srcloc(const StackFrame& stack) { auto params = TRY(stack.get(0)); auto size = TRY(params.size()); if (size != 6) return ERROR(ArgumentCountMismatch); SourceRange sr; for (uint64_t i = 0; i < 6; i++) { auto val = TRY(params.get(i)); if (!val.is()) return ERROR(TypeMismatch); int64_t intval = val.to()->value(); switch (i) { case 0: sr.start.line = intval; break; case 1: sr.start.column = intval; break; case 2: sr.start.offset = intval; break; case 3: sr.end.line = intval; break; case 4: sr.end.column = intval; break; case 5: sr.end.offset = intval; break; default: break; } } auto val = Value(TRY(SrcLoc::create(sr))); auto res = TRY(stack.set(0, val)); res = TRY(res.ret(0)); return res; } Result stdlib_size(const StackFrame& stack) { auto params = TRY(stack.get(0)); auto size = TRY(params.size()); if (size != 1) return ERROR(ArgumentCountMismatch); auto param = TRY(params.get(0)); auto psize = TRY(param.size()); auto val = Value(TRY(Int64::create((int64_t)psize))); auto res = TRY(stack.set(0, val)); res = TRY(res.ret(0)); return res; } Result stdlib_map(const StackFrame& stack_param) { auto stack = TRY(stack_param.copy()); auto params = TRY(stack.get(0)); auto stack_size = TRY(stack.size()); auto fun = TRY(params.get(1)); if (stack_size == 1) { // This is the first call, need to set up arguments on the stack auto collection = TRY(params.get(0)); auto num_params = TRY(params.size()); if (num_params != 2) return ERROR(ArgumentCountMismatch); if (!collection.is()) return ERROR(TypeMismatch); // Result accumulator will contain nil value initially auto nil = Value(TRY(Nil::create())); stack = TRY(stack.set(1, nil)); // Remaining elements in the collection stack = TRY(stack.set(2, TRY(collection.rest()))); stack = TRY(stack.set(3, fun)); stack = TRY(stack.set(4, TRY(collection.first()))); return TRY(stack.call(fun, 3, 5)); } auto accumulator = TRY(stack.get(1)); auto remaining = TRY(stack.get(2)); auto fun_result = TRY(stack.get(3)); accumulator = Value(TRY(Pair::create(fun_result, accumulator))); stack = TRY(stack.set(1, accumulator)); if (!remaining.is()) { stack = TRY(stack.set(2, TRY(remaining.rest()))); stack = TRY(stack.set(3, fun)); stack = TRY(stack.set(4, TRY(remaining.first()))); return TRY(stack.call(fun, 3, 5)); } accumulator = TRY(reverse(accumulator)); auto res = TRY(stack.set(0, accumulator)); res = TRY(res.ret(0)); return res; } Result stdlib_error(const StackFrame& stack) { auto params = TRY(stack.get(0)); auto size = TRY(params.size()); if (size == 0 || size > 2) return ERROR(ArgumentCountMismatch); Value error_name; Value error_message; if (size == 2) { error_name = TRY(params.get(0)); error_message = TRY(params.get(1)); } else { auto param = TRY(params.get(0)); if (param.is()) { error_name = std::move(param); error_message = TRY(String::create("")); } else if (param.is()) { error_name = TRY(Symbol::create("error")); error_message = std::move(param); } else { return ERROR(TypeMismatch); } } if (!error_name.is() || !error_message.is()) return ERROR(TypeMismatch); auto val = Value(TRY( Error::create(*error_name.to(), *error_message.to()))); auto cont = Value(TRY(Continuation::create(val, stack))); return ERROR_OBJ(Raise, cont); } Result stdlib_raise(const StackFrame& stack) { auto stack_size = TRY(stack.size()); if (stack_size > 1) { return task_return(stack, 1); } auto params = TRY(stack.get(0)); auto size = TRY(params.size()); if (size != 1) return ERROR(ArgumentCountMismatch); auto val = TRY(params.get(0)); auto cont = Value(TRY(Continuation::create(val, stack))); return ERROR_OBJ(Raise, cont); } Result stdlib_task(const StackFrame& stack) { auto params = TRY(stack.get(0)); Array& params_array = *params.to(); auto size = TRY(params.size()); if (size == 0) return ERROR(ArgumentCountMismatch); Value task_id = TRY(params_array.get(0)); uint64_t num_task_id = 0; if (task_id.is()) { num_task_id = (uint64_t)task_id.to()->value(); } else if (task_id.is()) { num_task_id = (uint64_t)TRY(get_stdlib_task(*task_id.to())); } else { return ERROR(TypeMismatch); } Value task_params = TRY(params_array.slice(1, size)); auto val = Value(TRY(Task::create(num_task_id, *task_params.to()))); auto res = TRY(stack.set(0, val)); res = TRY(res.ret(0)); return res; } #define STDLIB_FUNCTION(name, id) \ [(uint64_t)StdlibFunctionId::id] = {#name, StdlibFunctionId::id, \ stdlib_##name} static StdlibFunctionEntry function_entries[] = { STDLIB_FUNCTION(unknown, Unknown), STDLIB_FUNCTION(print, Print), STDLIB_FUNCTION(println, PrintLn), STDLIB_FUNCTION(prn, Prn), STDLIB_FUNCTION(slurp, Slurp), STDLIB_FUNCTION(spit, Spit), STDLIB_FUNCTION(assert, Assert), STDLIB_FUNCTION(dict, Dict), STDLIB_FUNCTION(list, List), STDLIB_FUNCTION(array, Array), STDLIB_FUNCTION(get, Get), STDLIB_FUNCTION(set, Set), STDLIB_FUNCTION(srcloc, SrcLoc), STDLIB_FUNCTION(size, Size), STDLIB_FUNCTION(map, Map), STDLIB_FUNCTION(error, Error), STDLIB_FUNCTION(raise, Raise), STDLIB_FUNCTION(task, Task), [(uint64_t)StdlibFunctionId::Max] = {0, StdlibFunctionId::Max, stdlib_unknown}, }; Result stdlib_task_unknown(const Array& params) { return ERROR(NotImplemented); } Result stdlib_task_print(const Array& params) { auto size = TRY(params.size()); if (size != 1) { auto err = Value(TRY( Error::create("argument-count-mismatch", "Argument count mismatch"))); return ERROR_OBJ(ArgumentCountMismatch, err); } auto obj = TRY(params.get(0)); if (!obj.is()) { auto err = Value(TRY(Error::create("type-mismatch", "Type mismatch"))); return ERROR_OBJ(TypeMismatch, err); } print_string(*obj.to()); return Value(TRY(Nil::create())); } Result stdlib_task_read_file(const Array& params) { auto size = TRY(params.size()); if (size != 1) { auto err = Value(TRY( Error::create("argument-count-mismatch", "Argument count mismatch"))); return ERROR_OBJ(ArgumentCountMismatch, err); } auto obj = TRY(params.get(0)); if (!obj.is()) { auto err = Value(TRY(Error::create("type-mismatch", "Type mismatch"))); return ERROR_OBJ(TypeMismatch, err); } String& fname = *obj.to(); ByteArray fname_bytes = TRY(ByteArray::create(fname)); auto contents_bytes = TRY(read_file(fname_bytes)); auto contents = Value(TRY(String::create(contents_bytes))); return contents; } Result stdlib_task_write_file(const Array& params) { auto size = TRY(params.size()); if (size != 2) { auto err = Value(TRY( Error::create("argument-count-mismatch", "Argument count mismatch"))); return ERROR_OBJ(ArgumentCountMismatch, err); } auto fname = TRY(params.get(0)); if (!fname.is()) { auto err = Value(TRY(Error::create("type-mismatch", "Type mismatch"))); return ERROR_OBJ(TypeMismatch, err); } auto contents = TRY(params.get(1)); if (!contents.is()) { auto err = Value(TRY(Error::create("type-mismatch", "Type mismatch"))); return ERROR_OBJ(TypeMismatch, err); } String& fname_string = *fname.to(); ByteArray fname_bytes = TRY(ByteArray::create(fname_string)); String& contents_string = *contents.to(); ByteArray contents_bytes = TRY(ByteArray::create(contents_string)); TRY(write_file(fname_bytes, contents_bytes)); return Value(TRY(Nil::create())); } #define STDLIB_TASK(name, id) \ [(uint64_t)StdlibTaskId::id] = {#name, StdlibTaskId::id, stdlib_task_##name} static StdlibTaskEntry task_entries[] = { STDLIB_TASK(unknown, Unknown), STDLIB_TASK(print, Print), STDLIB_TASK(read_file, ReadFile), STDLIB_TASK(write_file, WriteFile), [(uint64_t)StdlibTaskId::Max] = {0, StdlibTaskId::Max, stdlib_task_unknown}, }; // TODO: this just scans through the array of functions linearly every time. // It doesn't have much effect at runtime, because such lookup is likely to // happen on compile-time only. But anyway, this is worth speeding up // eventually. StdlibFunctionEntry get_function_entry(const char* name) { for (uint64_t i = 1; i < (uint64_t)StdlibFunctionId::Max; i++) { if (strcmp(function_entries[i].name, name) == 0) { return function_entries[i]; } } return function_entries[(uint64_t)StdlibFunctionId::Unknown]; } Result get_stdlib_function(const Symbol& name) { const uint64_t bufsize = 256; char buf[bufsize]; auto size = TRY(name.size()); if (size + 1 > bufsize) { return ERROR(KeyError); } for (uint64_t i = 0; i < size; i++) { buf[i] = (char)TRY(name[i]); } buf[size] = 0; StdlibFunctionId fun_id = get_function_entry(buf).fun_id; if (fun_id == StdlibFunctionId::Unknown) { return ERROR(KeyError); } return fun_id; } Result call_stdlib_function(StdlibFunctionId fun_id, const StackFrame& stack) { if (fun_id == StdlibFunctionId(0) || fun_id >= StdlibFunctionId::Max) { return ERROR(KeyError); } return function_entries[(uint64_t)fun_id].fun_ptr(stack); } Result get_stdlib_function_name(StdlibFunctionId fun_id) { if (fun_id == StdlibFunctionId(0) || fun_id >= StdlibFunctionId::Max) { return ERROR(KeyError); } return function_entries[(uint64_t)fun_id].name; } StdlibTaskEntry get_task_entry(const char* name) { for (uint64_t i = 1; i < (uint64_t)StdlibTaskId::Max; i++) { if (strcmp(task_entries[i].name, name) == 0) { return task_entries[i]; } } return task_entries[(uint64_t)StdlibTaskId::Unknown]; } Result get_stdlib_task(const Symbol& name) { const uint64_t bufsize = 256; char buf[bufsize]; auto size = TRY(name.size()); if (size + 1 > bufsize) { return ERROR(KeyError); } for (uint64_t i = 0; i < size; i++) { buf[i] = (char)TRY(name[i]); } buf[size] = 0; StdlibTaskId task_id = get_task_entry(buf).task_id; if (task_id == StdlibTaskId::Unknown) { return ERROR(KeyError); } return task_id; } Result call_stdlib_task(StdlibTaskId fun_id, const Array& params) { if (fun_id == StdlibTaskId(0) || fun_id >= StdlibTaskId::Max) { return ERROR(KeyError); } return task_entries[(uint64_t)fun_id].task_ptr(params); } Result get_stdlib_task_name(StdlibTaskId fun_id) { if (fun_id == StdlibTaskId(0) || fun_id >= StdlibTaskId::Max) { return ERROR(KeyError); } return task_entries[(uint64_t)fun_id].name; }