diff --git a/doc/grammar.bnf b/doc/grammar.bnf index 138f3e4..cf5950d 100644 --- a/doc/grammar.bnf +++ b/doc/grammar.bnf @@ -4,6 +4,7 @@ EXPR ::= | int | ident | float +| string | VARDECL | FUNDECL | FUNCALL diff --git a/examples/string.gri b/examples/string.gri new file mode 100644 index 0000000..b9a49d4 --- /dev/null +++ b/examples/string.gri @@ -0,0 +1,11 @@ +(assert= 'hello' 'hello') +(assert (not (eq? 'bim' 'bam'))) + +(assert= false (empty? 'coucou')) +(assert= true (empty? '')) + +(assert= 'e' (ref 'hello' 1)) +(assert= 5 (len 'hello')) + +(assert= 'aaa' (dup 'a' 3)) +(assert= 'abcdefg' (cat 'abc' 'def' 'g')) diff --git a/lib/core.cpp b/lib/core.cpp index 08368e4..907deee 100644 --- a/lib/core.cpp +++ b/lib/core.cpp @@ -5,7 +5,7 @@ GRINO_ERROR(assertion_error); -extern "C" void lib_array(grino::Loader& loader) +extern "C" void lib_collection(grino::Loader& loader) { loader.add_native("ref", [&loader](auto args){ std::vector idxs; @@ -14,6 +14,12 @@ extern "C" void lib_array(grino::Loader& loader) idxs.push_back(args[i]->as_int()); } + if (args[0]->type() == grino::TYPE_STRING) + { + auto char_val = std::string(1, args[0]->as_string()[args[1]->as_int()]); + return grino::Value::make_string(args[0]->loc(), char_val); + } + std::function (std::shared_ptr, std::vector)> f = [&](std::shared_ptr val, std::vector indexes){ @@ -37,14 +43,38 @@ extern "C" void lib_array(grino::Loader& loader) }); loader.add_native("empty?", [&loader](auto args){ - auto ref_val = args[0]; - size_t ref = ref_val->as_ref(); + auto val = args[0]; + if (val->type() == grino::TYPE_STRING) + { + std::string str = val->as_string(); + return grino::Value::make_bool(val->loc(), str.empty()); + } + + size_t ref = val->as_ref(); auto array_val = loader.vm().heap(ref); auto& array = array_val->as_array(); return grino::Value::make_bool(array_val->loc(), array.empty()); }); + loader.add_native("len", [&loader](auto args){ + auto val = args[0]; + + if (val->type() == grino::TYPE_STRING) + { + return grino::Value::make_int(val->loc(), val->as_string().size()); + } + + size_t ref = val->as_ref(); + auto array_val = loader.vm().heap(ref); + auto& array = array_val->as_array(); + + return grino::Value::make_int(array_val->loc(), array.size()); + }); +} + +extern "C" void lib_array(grino::Loader& loader) +{ loader.add_native("head", [&loader](auto args){ auto ref_val = args[0]; size_t ref = ref_val->as_ref(); @@ -87,15 +117,6 @@ extern "C" void lib_array(grino::Loader& loader) return grino::Value::make_ref(array_val->loc(), addr); }); - - loader.add_native("len", [&loader](auto args){ - auto ref_val = args[0]; - size_t ref = ref_val->as_ref(); - auto array_val = loader.vm().heap(ref); - auto& array = array_val->as_array(); - - return grino::Value::make_int(array_val->loc(), array.size()); - }); } extern "C" void lib_flow_control(grino::Loader& loader) @@ -611,6 +632,33 @@ extern "C" void lib_assert(grino::Loader& loader) }); } +extern "C" void lib_string(grino::Loader& loader) +{ + loader.add_native("dup", [](auto args) { + std::string value = args[0]->as_string(); + size_t count = args[1]->as_int(); + std::string result; + + for (size_t i=0; iloc(), result); + }); + + loader.add_native("cat", [](auto args) { + std::string result; + + for (auto arg: args) + { + result += arg->as_string(); + } + + return grino::Value::make_string(args[0]->loc(), result); + }); +} + extern "C" void lib(grino::Loader& loader) { lib_assert(loader); @@ -619,7 +667,9 @@ extern "C" void lib(grino::Loader& loader) lib_cmp(loader); lib_bool(loader); lib_flow_control(loader); + lib_collection(loader); lib_array(loader); + lib_string(loader); loader.add_native("dump", [](auto args){ std::string sep; diff --git a/src/Compiler.cpp b/src/Compiler.cpp index f6ab0e4..efe71ed 100644 --- a/src/Compiler.cpp +++ b/src/Compiler.cpp @@ -154,6 +154,16 @@ namespace grino program.push_constant(value)); } break; + case NODE_STRING: { + std::string repr = node->repr(); + std::string val = repr.substr(1, repr.size() - 2); + + auto value = Value::make_string(node->loc(), val); + + program.push_instr(OPCODE_LOAD_CONST, + program.push_constant(value)); + } break; + case NODE_VARDECL: { std::string ident = node->child(0).lock()->repr(); auto expr = node->child(1).lock(); diff --git a/src/Lexer.cpp b/src/Lexer.cpp index 428be8b..9c9d34f 100644 --- a/src/Lexer.cpp +++ b/src/Lexer.cpp @@ -20,6 +20,7 @@ namespace grino m_scanners.push_back(std::bind(&Lexer::scan_float, this)); m_scanners.push_back(std::bind(&Lexer::scan_int, this)); + m_scanners.push_back(std::bind(&Lexer::scan_string, this)); m_scanners.push_back(std::bind(&Lexer::scan_ident, this)); } @@ -150,6 +151,50 @@ namespace grino text, has_value)); } + std::optional Lexer::scan_string() + { + size_t cursor = m_cursor; + std::string repr = "'"; + + auto has_delim = [&](size_t idx){ + if (idx >= m_source.size()) { return false; } + bool is_escaped = idx > 0 && m_source[idx - 1] == '\\'; + return m_source[idx] == '\'' && !is_escaped; + }; + + if (!has_delim(cursor)) + { + return std::nullopt; + } + + cursor++; + + while (has_more(cursor) + && !has_delim(cursor)) + { + if (at(cursor) != '\\') + { + repr += at(cursor); + } + + cursor++; + } + + if (has_delim(cursor)) + { + repr += "'"; + cursor++; + + return ScanInfo { + cursor, + NODE_STRING, + repr + }; + } + + return std::nullopt; + } + std::optional Lexer::scan_text(NodeType type, std::string const& text, bool has_value) diff --git a/src/Lexer.hpp b/src/Lexer.hpp index cf02857..4cb59bb 100644 --- a/src/Lexer.hpp +++ b/src/Lexer.hpp @@ -49,6 +49,7 @@ namespace grino std::string const& text, bool has_value = false); + std::optional scan_string(); std::optional scan_text(NodeType type, std::string const& text, bool has_value); diff --git a/src/Node.hpp b/src/Node.hpp index e3d2e85..5657e17 100644 --- a/src/Node.hpp +++ b/src/Node.hpp @@ -22,7 +22,8 @@ G(NODE_CSQUARE), \ G(NODE_BLOCK), \ G(NODE_ARRAY), \ - G(NODE_FLOAT) + G(NODE_FLOAT), \ + G(NODE_STRING) namespace grino { diff --git a/src/Parser.cpp b/src/Parser.cpp index cc9839b..aeb796e 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -151,7 +151,8 @@ namespace grino if (type_is(NODE_IDENT) || type_is(NODE_BOOL) || type_is(NODE_INT) - || type_is(NODE_FLOAT)) + || type_is(NODE_FLOAT) + || type_is(NODE_STRING)) { return consume(); } diff --git a/src/Value.cpp b/src/Value.cpp index b9c8a78..523bfaa 100644 --- a/src/Value.cpp +++ b/src/Value.cpp @@ -1,5 +1,6 @@ #include "Value.hpp" #include "Program.hpp" +#include "src/types.hpp" namespace grino { @@ -104,6 +105,16 @@ namespace grino return value; } + /*static*/ + std::shared_ptr Value::make_string(Loc const& loc, + std::string const& val) + { + auto value = std::make_shared(loc); + value->m_type = TYPE_STRING; + value->m_string_val = val; + return value; + } + std::shared_ptr Value::as_program() const { return m_program_val; @@ -114,6 +125,7 @@ namespace grino switch (m_type) { case TYPE_NIL: return ""; + case TYPE_STRING: return *m_string_val; case TYPE_INT: return std::to_string(*m_int_val); case TYPE_FLOAT: return std::to_string(*m_float_val); case TYPE_BOOL: return *m_bool_val ? "true" : "false"; @@ -149,6 +161,7 @@ namespace grino case TYPE_NIL: return true; case TYPE_BOOL: return *m_bool_val == *other.m_bool_val; case TYPE_INT: return *m_int_val == *other.m_int_val; + case TYPE_STRING: return *m_string_val == *other.m_string_val; case TYPE_FLOAT: return std::fabs(*m_float_val - *other.m_float_val) < FLOAT_APPROX; case TYPE_REF: return *m_ref_val == *other.m_ref_val; diff --git a/src/Value.hpp b/src/Value.hpp index b013901..88ef268 100644 --- a/src/Value.hpp +++ b/src/Value.hpp @@ -37,6 +37,9 @@ namespace grino static std::shared_ptr make_array(Loc const& loc, val_array_t val); + static std::shared_ptr make_string(Loc const& loc, + std::string const& val); + explicit Value(Loc const& loc); virtual ~Value() = default; @@ -50,6 +53,7 @@ namespace grino std::shared_ptr as_program() const; val_array_t& as_array() { return *m_array_val; } val_array_t const& as_array() const { return *m_array_val; } + std::string const& as_string() const { return *m_string_val; } std::string string() const; bool equals(Value const& other) const; @@ -64,6 +68,7 @@ namespace grino std::shared_ptr m_program_val; std::optional m_ref_val; std::optional m_array_val; + std::optional m_string_val; }; } diff --git a/src/types.hpp b/src/types.hpp index de637cf..ebf44d9 100644 --- a/src/types.hpp +++ b/src/types.hpp @@ -11,7 +11,8 @@ G(TYPE_REF), \ G(TYPE_PROGRAM), \ G(TYPE_FLOAT), \ - G(TYPE_ARRAY) + G(TYPE_ARRAY), \ + G(TYPE_STRING) namespace grino diff --git a/tests/Lexer.cpp b/tests/Lexer.cpp index 5f9d7ee..82f5665 100644 --- a/tests/Lexer.cpp +++ b/tests/Lexer.cpp @@ -119,3 +119,13 @@ TEST_CASE_METHOD(LexerTest, "Lexer_float") test_next(lexer, "FLOAT[1.0]"); test_end(lexer); } + +TEST_CASE_METHOD(LexerTest, "Lexer_strings") +{ + grino::Lexer lexer {m_logger, "tests/lexer"}; + + lexer.scan(" ' hello ' '\\'bim\\'' "); + test_next(lexer, "STRING[' hello ']"); + test_next(lexer, "STRING[''bim'']"); + test_end(lexer); +} diff --git a/tests/Parser.cpp b/tests/Parser.cpp index 8cca787..e12b127 100644 --- a/tests/Parser.cpp +++ b/tests/Parser.cpp @@ -99,3 +99,9 @@ TEST_CASE_METHOD(ParserTest, "Parser_float") test_parse("MODULE(BLOCK(ARRAY(INT[1],FLOAT[28.5],IDENT[salut])))", "(: [1 28.5 salut] )"); } + +TEST_CASE_METHOD(ParserTest, "Parser_string") +{ + test_parse("MODULE(BLOCK(ARRAY(STRING['bim !'],FLOAT[28.5],IDENT[salut])))", + "(: ['bim !' 28.5 salut] )"); +}