✨ strings arithmetic.
parent
5cb141e4cf
commit
e7e0789cbb
|
@ -17,3 +17,4 @@ BUILTIN ::=
|
|||
| int
|
||||
| bool
|
||||
| float
|
||||
| string
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
assert "hello" eq "hello"
|
||||
|
||||
assert "hello " + "world" eq "hello world"
|
||||
assert "a" + "b" + "c" eq "abc"
|
||||
|
||||
assert "a" * 3 eq "aaa"
|
||||
assert 4 * "b" eq "bbbb"
|
||||
assert ("a" + "b") * 2 eq "abab"
|
|
@ -31,6 +31,7 @@ void lexer_consume_next(struct lexer* self);
|
|||
struct token* lexer_try_new_next(struct lexer* self);
|
||||
struct token* lexer_try_scan_int(struct lexer* self);
|
||||
struct token* lexer_try_scan_float(struct lexer* self);
|
||||
struct token* lexer_try_scan_string(struct lexer* self);
|
||||
struct token* lexer_try_scan_text(struct lexer* self,
|
||||
char const* text,
|
||||
TokenKind kind);
|
||||
|
|
|
@ -10,7 +10,7 @@ G(NODE_ADD), G(NODE_SUB), G(NODE_MUL),\
|
|||
G(NODE_DIV), G(NODE_POW), G(NODE_MOD),\
|
||||
G(NODE_USUB), G(NODE_ASSERT_EQ), \
|
||||
G(NODE_BOOL), G(NODE_AND), G(NODE_OR), \
|
||||
G(NODE_NOT), G(NODE_FLOAT)
|
||||
G(NODE_NOT), G(NODE_FLOAT), G(NODE_STRING)
|
||||
|
||||
SK_ENUM_H(NodeKind, NODE_KIND);
|
||||
|
||||
|
|
|
@ -52,8 +52,10 @@ SK state_push(struct state* self,
|
|||
SK state_push_int(struct state* self, int integer, int line);
|
||||
SK state_push_bool(struct state* self, bool boolean, int line);
|
||||
SK state_push_float(struct state* self, double real, int line);
|
||||
SK state_push_string(struct state* self, char const* str, int line);
|
||||
|
||||
TypeKind state_common_num_type(struct state* self, SK lhs, SK rhs);
|
||||
TypeKind state_type(struct state* self, SK value);
|
||||
double state_as_real(struct state* self, SK lhs);
|
||||
int state_line(struct state* self, SK lhs);
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ G(TOKEN_MUL), G(TOKEN_DIV), G(TOKEN_MOD), \
|
|||
G(TOKEN_POW), G(TOKEN_OPAR), G(TOKEN_CPAR), \
|
||||
G(TOKEN_ASSERT), G(TOKEN_ASSERT_EQ), \
|
||||
G(TOKEN_BOOL), G(TOKEN_AND), G(TOKEN_OR), \
|
||||
G(TOKEN_NOT), G(TOKEN_FLOAT)
|
||||
G(TOKEN_NOT), G(TOKEN_FLOAT), G(TOKEN_STRING)
|
||||
|
||||
SK_ENUM_H(TokenKind, TOKEN_KIND);
|
||||
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
#include "node.h"
|
||||
|
||||
#define TYPE_KIND(G) \
|
||||
G(TYPE_INT), G(TYPE_BOOL), G(TYPE_FLOAT)
|
||||
G(TYPE_INT), G(TYPE_BOOL), G(TYPE_FLOAT), \
|
||||
G(TYPE_STRING)
|
||||
|
||||
SK_ENUM_H(TypeKind, TYPE_KIND);
|
||||
|
||||
|
@ -14,6 +15,7 @@ union val
|
|||
int integer;
|
||||
double real;
|
||||
bool boolean;
|
||||
char* str;
|
||||
};
|
||||
|
||||
struct value
|
||||
|
|
|
@ -69,6 +69,13 @@ void compiler_compile(struct compiler* self,
|
|||
val);
|
||||
} break;
|
||||
|
||||
case NODE_STRING: {
|
||||
union val val;
|
||||
val.str = strdup(node->token->value);
|
||||
compiler_compile_value(self, node, prog, TYPE_STRING,
|
||||
val);
|
||||
} break;
|
||||
|
||||
case NODE_ADD: {
|
||||
compiler_compile_children(self, node, prog);
|
||||
prog_add_instr(prog, OP_ADD, SK_NO_PARAM);
|
||||
|
|
|
@ -82,6 +82,14 @@ void exec_execute(struct exec* self,
|
|||
);
|
||||
} break;
|
||||
|
||||
case TYPE_STRING: {
|
||||
state_push_string(
|
||||
state,
|
||||
constant->val.str,
|
||||
constant->line
|
||||
);
|
||||
} break;
|
||||
|
||||
case TYPE_BOOL: {
|
||||
state_push_bool(
|
||||
state,
|
||||
|
|
|
@ -143,6 +143,10 @@ struct token* lexer_try_new_next(struct lexer* self)
|
|||
return tok;
|
||||
}
|
||||
|
||||
if ( (tok=lexer_try_scan_string(self)) )
|
||||
{
|
||||
return tok;
|
||||
}
|
||||
SK_SCAN_TEXT("+", TOKEN_ADD);
|
||||
SK_SCAN_TEXT("-", TOKEN_SUB);
|
||||
SK_SCAN_TEXT("*", TOKEN_MUL);
|
||||
|
@ -271,6 +275,88 @@ struct token* lexer_try_scan_float(struct lexer* self)
|
|||
return tok;
|
||||
}
|
||||
|
||||
struct token* lexer_try_scan_string(struct lexer* self)
|
||||
{
|
||||
assert(self);
|
||||
size_t cursor = self->context.cursor;
|
||||
|
||||
if (cursor >= self->len
|
||||
|| self->source[cursor] != '"')
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cursor++;
|
||||
|
||||
struct str value;
|
||||
str_init(&value);
|
||||
bool escaped = false;
|
||||
|
||||
while (cursor < self->len
|
||||
&& self->source[cursor] != '"')
|
||||
{
|
||||
if (self->source[cursor] == '\\')
|
||||
{
|
||||
escaped = true;
|
||||
}
|
||||
|
||||
if (escaped)
|
||||
{
|
||||
cursor++;
|
||||
|
||||
switch (self->source[cursor])
|
||||
{
|
||||
case '\\': {
|
||||
str_push(&value, '\\');
|
||||
} break;
|
||||
case 'n': {
|
||||
str_push(&value, '\n');
|
||||
} break;
|
||||
case 'r': {
|
||||
str_push(&value, '\r');
|
||||
} break;
|
||||
case 't': {
|
||||
str_push(&value, '\t');
|
||||
} break;
|
||||
case 'e': {
|
||||
str_push(&value, '\e');
|
||||
} break;
|
||||
case '"': {
|
||||
str_push(&value, '"');
|
||||
} break;
|
||||
default: {
|
||||
errors_push(self->context.line,
|
||||
"unknown escape symbol");
|
||||
str_free(&value);
|
||||
return NULL;
|
||||
} break;
|
||||
}
|
||||
|
||||
escaped = false;
|
||||
cursor++;
|
||||
continue;
|
||||
}
|
||||
|
||||
str_push(&value, self->source[cursor]);
|
||||
cursor++;
|
||||
}
|
||||
|
||||
if (cursor >= self->len
|
||||
|| self->source[cursor] != '"')
|
||||
{
|
||||
str_free(&value);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cursor++;
|
||||
|
||||
struct token* tok = malloc(sizeof(struct token));
|
||||
token_init(tok, TOKEN_STRING, value.value, self->context.line);
|
||||
self->context.cursor = cursor;
|
||||
str_free(&value);
|
||||
return tok;
|
||||
}
|
||||
|
||||
struct token* lexer_try_scan_text(struct lexer* self,
|
||||
char const* text,
|
||||
TokenKind kind)
|
||||
|
|
|
@ -440,5 +440,16 @@ struct node* parser_try_builtin(struct parser* self)
|
|||
return node;
|
||||
}
|
||||
|
||||
if (lexer_next_is(&self->lexer, TOKEN_STRING))
|
||||
{
|
||||
struct node* node = malloc(sizeof(struct node));
|
||||
|
||||
node_init(node,
|
||||
NODE_STRING,
|
||||
lexer_try_new_next(&self->lexer));
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
@ -154,6 +154,14 @@ SK state_push_float(struct state* self, double real, int line)
|
|||
return state_push(self, TYPE_FLOAT, val ,line);
|
||||
}
|
||||
|
||||
SK state_push_string(struct state* self, char const* str, int line)
|
||||
{
|
||||
assert(self);
|
||||
union val val;
|
||||
val.str = strdup(str);
|
||||
return state_push(self, TYPE_STRING, val ,line);
|
||||
}
|
||||
|
||||
TypeKind state_common_num_type(struct state* self, SK lhs, SK rhs)
|
||||
{
|
||||
assert(self);
|
||||
|
@ -170,6 +178,13 @@ TypeKind state_common_num_type(struct state* self, SK lhs, SK rhs)
|
|||
return TYPE_INT;
|
||||
}
|
||||
|
||||
TypeKind state_type(struct state* self, SK value)
|
||||
{
|
||||
assert(self);
|
||||
struct value const* val = state_try_get_value(self, value);
|
||||
return val->type;
|
||||
}
|
||||
|
||||
double state_as_real(struct state* self, SK lhs)
|
||||
{
|
||||
assert(self);
|
||||
|
@ -204,7 +219,20 @@ SK state_add(struct state* self)
|
|||
|
||||
TypeKind type = state_common_num_type(self, lhs, rhs);
|
||||
|
||||
if (type == TYPE_INT)
|
||||
if(state_type(self, lhs) == TYPE_STRING
|
||||
&& state_type(self, rhs) == TYPE_STRING)
|
||||
{
|
||||
struct value* left = state_try_get_value(self, lhs);
|
||||
struct value* right = state_try_get_value(self, rhs);
|
||||
struct str val;
|
||||
str_init(&val);
|
||||
str_extend(&val, left->val.str);
|
||||
str_extend(&val, right->val.str);
|
||||
SK res = state_push_string(self, val.value, left->line);
|
||||
str_free(&val);
|
||||
return res;
|
||||
}
|
||||
else if (type == TYPE_INT)
|
||||
{
|
||||
return state_push_int(
|
||||
self,
|
||||
|
@ -296,7 +324,37 @@ SK state_mul(struct state* self)
|
|||
|
||||
TypeKind type = state_common_num_type(self, lhs, rhs);
|
||||
|
||||
if (type == TYPE_INT)
|
||||
if(state_type(self, lhs) == TYPE_STRING
|
||||
&& state_type(self, rhs) == TYPE_INT)
|
||||
{
|
||||
struct value* left = state_try_get_value(self, lhs);
|
||||
struct value* right = state_try_get_value(self, rhs);
|
||||
struct str val;
|
||||
str_init(&val);
|
||||
for (int i=0; i<right->val.integer; i++)
|
||||
{
|
||||
str_extend(&val, left->val.str);
|
||||
}
|
||||
SK res = state_push_string(self, val.value, left->line);
|
||||
str_free(&val);
|
||||
return res;
|
||||
}
|
||||
else if(state_type(self, lhs) == TYPE_INT
|
||||
&& state_type(self, rhs) == TYPE_STRING)
|
||||
{
|
||||
struct value* left = state_try_get_value(self, lhs);
|
||||
struct value* right = state_try_get_value(self, rhs);
|
||||
struct str val;
|
||||
str_init(&val);
|
||||
for (int i=0; i<left->val.integer; i++)
|
||||
{
|
||||
str_extend(&val, right->val.str);
|
||||
}
|
||||
SK res = state_push_string(self, val.value, left->line);
|
||||
str_free(&val);
|
||||
return res;
|
||||
}
|
||||
else if (type == TYPE_INT)
|
||||
{
|
||||
return state_push_int(
|
||||
self,
|
||||
|
|
|
@ -16,6 +16,10 @@ void value_init(struct value* self,
|
|||
void value_free(struct value* self)
|
||||
{
|
||||
assert(self);
|
||||
if (self->type == TYPE_STRING)
|
||||
{
|
||||
free(self->val.str);
|
||||
}
|
||||
}
|
||||
|
||||
bool value_equals(struct value* self, struct value* rhs)
|
||||
|
@ -43,6 +47,10 @@ void value_str(struct value* self, struct str* dest)
|
|||
|
||||
switch (self->type)
|
||||
{
|
||||
case TYPE_STRING: {
|
||||
str_format(dest, "%s", self->val.str);
|
||||
} break;
|
||||
|
||||
case TYPE_INT: {
|
||||
str_format(dest, "%d", self->val.integer);
|
||||
} break;
|
||||
|
|
|
@ -16,6 +16,10 @@ static void test_lexer(char const* source, int count, ...)
|
|||
for (int i=0; i<count; i++)
|
||||
{
|
||||
struct token* tok = lexer_try_new_next(&lexer);
|
||||
if (tok == NULL)
|
||||
{
|
||||
fprintf(stderr, "%s == NULL\n", source);
|
||||
}
|
||||
CU_ASSERT_FATAL(tok != NULL);
|
||||
struct str tok_str;
|
||||
str_init(&tok_str);
|
||||
|
@ -81,6 +85,17 @@ static void test_lexer_float()
|
|||
);
|
||||
}
|
||||
|
||||
static void test_lexer_string()
|
||||
{
|
||||
test_lexer("\" hello \" \"\\\"world\\\"\"", 2,
|
||||
"STRING[ hello ]",
|
||||
"STRING[\"world\"]"
|
||||
);
|
||||
|
||||
test_lexer("\"\\n\\r\\t\\e\"", 1,
|
||||
"STRING[\n\r\t\e]"
|
||||
);
|
||||
}
|
||||
void register_lexer()
|
||||
{
|
||||
CU_pSuite suite = CU_add_suite("Lexer", 0, 0);
|
||||
|
@ -88,6 +103,7 @@ void register_lexer()
|
|||
CU_add_test(suite, "Assertions", test_lexer_assert);
|
||||
CU_add_test(suite, "Booleans", test_lexer_bool);
|
||||
CU_add_test(suite, "Floats", test_lexer_float);
|
||||
CU_add_test(suite, "Strings", test_lexer_string);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -64,12 +64,20 @@ static void test_parser_bool()
|
|||
"BOOL[false])))",
|
||||
"not false and (true or false)");
|
||||
}
|
||||
|
||||
static void test_parser_string()
|
||||
{
|
||||
test_parser("ROOT(STRING[ok pizza NOW])",
|
||||
"\"ok pizza NOW\"");
|
||||
}
|
||||
|
||||
void register_parser()
|
||||
{
|
||||
CU_pSuite suite = CU_add_suite("Parser", 0, 0);
|
||||
CU_add_test(suite, "Integers", test_parser_int);
|
||||
CU_add_test(suite, "Assertions", test_parser_assert);
|
||||
CU_add_test(suite, "Booleans", test_parser_bool);
|
||||
CU_add_test(suite, "Strings", test_parser_string);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Reference in New Issue