diff --git a/CMakeLists.txt b/CMakeLists.txt index e631623..4e799ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,6 +15,7 @@ target_sources(vm_lib src/common.cpp src/arena.cpp src/reader.cpp + src/writer.cpp src/utf8.cpp PUBLIC @@ -29,6 +30,7 @@ target_sources(vm_lib src/vm.hpp src/sourcerange.hpp src/reader.hpp + src/writer.hpp src/utf8.hpp ) diff --git a/src/common.hpp b/src/common.hpp index 41aaa98..d21ec9e 100644 --- a/src/common.hpp +++ b/src/common.hpp @@ -268,6 +268,13 @@ class Symbol : public Object { static Result create(Arena& arena, String& rhs); virtual Result copy(Arena& arena) final; + uint64_t size() { return _value->size; } + + Result operator[](uint64_t idx) { + if (idx >= _value->size) return ErrorCode::IndexOutOfRange; + return _value->data[idx]; + } + private: GcRoot _value; }; @@ -434,6 +441,7 @@ class Value { } PodObject* pod() { return ((Object*)buf)->pod(); } + Tag tag() { return ((Object*)buf)->tag(); } Object& operator*() { return *(Object*)(buf); } Object* operator->() { return (Object*)(buf); } diff --git a/src/error.hpp b/src/error.hpp index 4818cb2..88e7d2c 100644 --- a/src/error.hpp +++ b/src/error.hpp @@ -7,5 +7,8 @@ enum class ErrorCode { TypeMismatch, ReadError, UnterminatedStringLiteral, - InvalidNumericLiteral + InvalidNumericLiteral, + NotImplemented, + InvalidSymbol, + MalformedList, }; diff --git a/src/vli.cpp b/src/vli.cpp index a684972..2cb020e 100644 --- a/src/vli.cpp +++ b/src/vli.cpp @@ -3,6 +3,7 @@ #include "arena.hpp" #include "common.hpp" #include "reader.hpp" +#include "writer.hpp" StaticArena<64 * 1024 * 1024> arena; @@ -31,5 +32,9 @@ int main() { auto reader = Reader(arena, s); auto r = DIEIF(reader.read_one()); + + auto writer = Writer(arena); + auto s2 = DIEIF(writer.write_one(r)); + return 0; } diff --git a/src/writer.cpp b/src/writer.cpp new file mode 100644 index 0000000..2c91ca8 --- /dev/null +++ b/src/writer.cpp @@ -0,0 +1,166 @@ +#include "writer.hpp" + +Result Writer::write_one(Value& obj) { + switch (obj.tag()) { + case Tag::Nil: + return write_nil(*obj.to()); + case Tag::Int64: + return write_int64(*obj.to()); + case Tag::Float: + return write_float(*obj.to()); + case Tag::Bool: + return write_bool(*obj.to()); + case Tag::ByteArray: + return write_bytearray(*obj.to()); + case Tag::String: + return write_string(*obj.to()); + case Tag::Symbol: + return write_symbol(*obj.to()); + case Tag::Syntax: + return write_syntax(*obj.to()); + case Tag::Pair: + return write_pair(*obj.to()); + }; + return String(); +} + +Result Writer::write_int64(Int64& val) { + char tmp[32]; + sprintf(tmp, "%luu64", val.value()); + size_t len = strlen(tmp); + + return String::create(_arena, tmp); +} + +bool needs_dot(char* str) { + bool has_e = 0; + bool has_dot = 0; + for (char* c = str; *c != 0; c++) { + if (*c == 'e' || *c == 'E') has_e = true; + if (*c == '.') has_dot = true; + } + return !has_e && !has_dot; +} + +Result Writer::write_float(Float& val) { + char tmp[32]; + sprintf(tmp, "%g", val.value()); + size_t len = strlen(tmp); + + if (needs_dot(tmp)) { + tmp[len] = '.'; + tmp[len + 1] = '0'; + tmp[len + 2] = 0; + len += 2; + } + + return String::create(_arena, tmp); +} + +Result Writer::write_nil(Nil& val) { + return String::create(_arena, "nil"); +} + +Result Writer::write_bool(Bool& val) { + if (val.value()) return String::create(_arena, "true"); + + return String::create(_arena, "false"); +} + +Result Writer::write_bytearray(ByteArray& val) { + return ErrorCode::NotImplemented; +} + +Result Writer::write_string(String& val) { + String res = TRY(String::create(_arena, "\"")); + + // TODO: optimize this + for (uint64_t i = 0; i < val.size(); i++) { + char32_t c = TRY(val[i]); + const char* replace = 0; + switch (c) { + case '\r': + replace = "\\r"; + break; + case '\n': + replace = "\\n"; + break; + case '\t': + replace = "\\t"; + break; + case '\\': + replace = "\\\\"; + break; + case '"': + replace = "\\\""; + break; + default: + break; + }; + + if (replace != 0) { + res = TRY(res.concat(_arena, replace)); + } else { + res = TRY(res.concat(_arena, &c, 1)); + } + } + + res = TRY(res.concat(_arena, "\"")); + + return res; +} + +int is_valid_symbol_char(char32_t codepoint) { + return ('a' <= codepoint && codepoint <= 'z') || + ('A' <= codepoint && codepoint <= 'Z') || + ('0' <= codepoint && codepoint <= '9') || codepoint == '!' || + codepoint == '$' || codepoint == '%' || codepoint == '&' || + codepoint == '*' || codepoint == '+' || codepoint == '-' || + codepoint == '.' || codepoint == '/' || codepoint == ':' || + codepoint == '<' || codepoint == '=' || codepoint == '>' || + codepoint == '?' || codepoint == '@' || codepoint == '^' || + codepoint == '_' || codepoint == '~'; +} + +Result Writer::write_symbol(Symbol& val) { + String res = TRY(String::create(_arena, "")); + + // TODO: optimize this + for (uint64_t i = 0; i < val.size(); i++) { + char32_t c = TRY(val[i]); + + if (!is_valid_symbol_char(c)) return ErrorCode::InvalidSymbol; + + res = TRY(res.concat(_arena, &c, 1)); + } + + return res; +} + +Result Writer::write_syntax(Syntax& val) { + return ErrorCode::NotImplemented; +} + +Result Writer::write_pair(Pair& val) { + String res = TRY(String::create(_arena, "(")); + + Value cur = TRY(val.copy(_arena)); + + bool is_first = true; + while (!cur.is()) { + if (!cur.is()) return ErrorCode::MalformedList; + + Pair& pair = *cur.to(); + + Value first = TRY(pair.first(_arena)); + if (!is_first) res = TRY(res.concat(_arena, " ")); + + String first_str = TRY(write_one(first)); + res = TRY(res.concat(_arena, first_str)); + + cur = TRY(pair.rest(_arena)); + } + + res = TRY(res.concat(_arena, ")")); + return res; +} diff --git a/src/writer.hpp b/src/writer.hpp new file mode 100644 index 0000000..606561b --- /dev/null +++ b/src/writer.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include "arena.hpp" +#include "common.hpp" + +class Writer { + public: + Writer(Arena& arena) : _arena(arena){}; + + Result write_one(Value& obj); + Result write_multiple(Value& obj); + + private: + Result write_int64(Int64& val); + Result write_float(Float& val); + Result write_string(String& val); + Result write_pair(Pair& val); + Result write_nil(Nil& val); + Result write_bool(Bool& val); + Result write_bytearray(ByteArray& val); + Result write_symbol(Symbol& val); + Result write_syntax(Syntax& val); + + Arena& _arena; +};