Skip to content

Commit c06e9a6

Browse files
authored
feat: single qoute str (#125)
1 parent e4df49a commit c06e9a6

File tree

5 files changed

+67
-37
lines changed

5 files changed

+67
-37
lines changed

docs/syntax.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ var func return if elif else loop in true false as and or not str num bool list
1111

1212
numbers: Can start with 0x (base16), 0b (base2) or nothing (base10) denoting the base. Then a set of number characters valid for the particular base in addition to '-' as a visual seperator. Base ten numbers can also have a decimal point.
1313

14-
string: "" any characters enclosed inside double-qoutes
14+
string:
15+
- "" any characters enclosed inside double-qoutes. Hanldes escape sequences (for example \n becomes newline).
16+
- '' any characters enclosed inside single-qoutes Does not hanlde escape sequences.
1517

1618
identifiers: Must start with a letter (a, b, .. z), underscore (_) or hyphen (-). Can then be any sequence of the aformentioned characters plus any number character (0, 1, .. 9).
1719

src/interpreter/ast.c

+10-5
Original file line numberDiff line numberDiff line change
@@ -234,11 +234,16 @@ Stmt *stmt_copy(Arena *arena, Stmt *to_copy)
234234
}
235235
case STMT_CMD: {
236236
((CmdStmt *)copy)->cmd_name = str_view_arena_copy(arena, ((CmdStmt *)to_copy)->cmd_name);
237-
((CmdStmt *)copy)->arg_exprs = arena_ll_alloc(arena);
238-
LLItem *item;
239-
ARENA_LL_FOR_EACH(((CmdStmt *)to_copy)->arg_exprs, item)
240-
{
241-
arena_ll_append(((CmdStmt *)copy)->arg_exprs, expr_copy(arena, item->value));
237+
238+
if (((CmdStmt *)to_copy)->arg_exprs == NULL) {
239+
((CmdStmt *)copy)->arg_exprs = NULL;
240+
} else {
241+
((CmdStmt *)copy)->arg_exprs = arena_ll_alloc(arena);
242+
LLItem *item;
243+
ARENA_LL_FOR_EACH(((CmdStmt *)to_copy)->arg_exprs, item)
244+
{
245+
arena_ll_append(((CmdStmt *)copy)->arg_exprs, expr_copy(arena, item->value));
246+
}
242247
}
243248
break;
244249
}

src/interpreter/lexer.c

+21-8
Original file line numberDiff line numberDiff line change
@@ -269,9 +269,9 @@ StateFn lex_any(Lexer *lexer)
269269
case '\\':
270270
emit(lexer, t_backslash);
271271
break;
272-
case '\'':
273-
emit(lexer, t_qoute);
274-
break;
272+
// case '\'':
273+
// emit(lexer, t_qoute);
274+
// break;
275275

276276
/* one or two character tokens */
277277
case '=':
@@ -342,6 +342,7 @@ StateFn lex_any(Lexer *lexer)
342342
return STATE_FN(lex_access);
343343

344344
case '"':
345+
case '\'':
345346
return STATE_FN(lex_string);
346347

347348
case '#':
@@ -388,7 +389,7 @@ StateFn lex_shell_arg_list(Lexer *lexer)
388389
* Lexing rules for shell arg list:
389390
* whitespace, tab -> backup, emit, advance, ignore and continue
390391
* $ -> backup, emit, advance and lex access
391-
* " -> backup, emit, advance and lex string
392+
* ", ' -> backup, emit, advance and lex string
392393
* ( -> backup, emit, advance, lex any until rparen and continue
393394
* ) -> backup, emit, advance, stop and return lex rparen
394395
* \n, }, ;, |, >, <, &, EOF -> backup, emit, and stop
@@ -411,6 +412,7 @@ StateFn lex_shell_arg_list(Lexer *lexer)
411412
lex_access(lexer);
412413
break;
413414
case '"':
415+
case '\'':
414416
shell_arg_emit(lexer);
415417
lex_string(lexer);
416418
break;
@@ -524,30 +526,41 @@ StateFn lex_string(Lexer *lexer)
524526
{
525527
/* ignore starting qoute */
526528
ignore(lexer);
529+
530+
/* find which string type this is (' or ") */
531+
char str_type = peek_ahead(lexer, -1);
527532
size_t str_start = lexer->pos_in_line;
528533
size_t str_end;
529534

530535
StrBuilder sb;
531536
str_builder_init(&sb, lexer->arena);
532537
char c;
533538
continue_str_lexing:
534-
while ((c = next(lexer)) != '"') {
539+
while ((c = next(lexer)) != str_type) {
535540
switch (c) {
536541
case EOF:
537542
case '\n':
538543
backup(lexer);
539544
report_lex_err(lexer, true, "Unterminated string literal");
540545
return STATE_FN(lex_any);
541546
case '\\': {
542-
char next_char = next(lexer);
547+
/* For single qouted strings we do escaping */
548+
if (str_type == '\'') {
549+
str_builder_append_char(&sb, '\\');
550+
break;
551+
}
543552

553+
char next_char = next(lexer);
544554
switch (next_char) {
545555
case '"':
546556
str_builder_append_char(&sb, next_char);
547557
break;
548558
case 'n':
549559
str_builder_append_char(&sb, '\n');
550560
break;
561+
case '\\':
562+
str_builder_append_char(&sb, '\\');
563+
break;
551564
default:
552565
report_lex_err(lexer, true, "Unknown escape sequence");
553566
/* Error occured. Continue parsing after string is terminated. */
@@ -588,9 +601,9 @@ StateFn lex_string(Lexer *lexer)
588601
accept_run(lexer, " \t\v");
589602
lexer->start = lexer->pos;
590603

591-
if (!match(lexer, '"')) {
604+
if (!match(lexer, str_type)) {
592605
lexer->pos++; // Gives an underline where the double qoute should be in the error msg
593-
report_lex_err(lexer, true, "Expected double qoute to continue multiline string");
606+
report_lex_err(lexer, true, "Expected another string after '\'.");
594607
ignore(lexer);
595608
return STATE_FN(lex_any);
596609
}

src/interpreter/value/slash_value.c

+22-3
Original file line numberDiff line numberDiff line change
@@ -670,8 +670,6 @@ SlashValue str_to_str(Interpreter *interpreter, SlashValue self)
670670
return self;
671671
}
672672

673-
674-
// SlashValue range_item_get(Interpreter *interpreter, SlashValue self, SlashValue other)
675673
SlashValue str_item_get(Interpreter *interpreter, SlashValue self, SlashValue other)
676674
{
677675
assert(IS_STR(self));
@@ -705,6 +703,27 @@ SlashValue str_item_get(Interpreter *interpreter, SlashValue self, SlashValue ot
705703
return AS_VALUE(new);
706704
}
707705

706+
void str_item_assign(Interpreter *interpreter, SlashValue self, SlashValue index, SlashValue other)
707+
{
708+
assert(IS_STR(self));
709+
assert(IS_STR(other));
710+
assert(IS_NUM(index)); // TODO: implement for range
711+
if (!NUM_IS_INT(index))
712+
REPORT_RUNTIME_ERROR("Str index can not be a floating point number: '%f'", index.num);
713+
714+
SlashStr *str = AS_STR(self);
715+
/* Ensure the index is valid */
716+
int idx = (int)index.num;
717+
if (idx < 0 || (size_t)idx >= str->len)
718+
REPORT_RUNTIME_ERROR("Str index '%d' out of range for str with len '%zu'", idx, str->len);
719+
720+
SlashStr *str_other = AS_STR(other);
721+
if (str_other->len != 1)
722+
REPORT_RUNTIME_ERROR("Can only assign a string of length one, not length.");
723+
724+
str->str[idx] = str_other->str[0];
725+
}
726+
708727
bool str_item_in(SlashValue self, SlashValue other)
709728
{
710729
assert(IS_STR(self) && IS_STR(other));
@@ -968,7 +987,7 @@ SlashTypeInfo str_type_info = { .name = "str",
968987
.print = str_print,
969988
.to_str = str_to_str,
970989
.item_get = str_item_get,
971-
.item_assign = NULL,
990+
.item_assign = str_item_assign,
972991
.item_in = str_item_in,
973992
.truthy = str_truthy,
974993
.eq = str_eq,

test/str.slash

+11-20
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,10 @@
44
assert $a[0] == "h"
55
assert $a[..5] == "hello"
66
# item assign
7-
# $a[0] = "H"
8-
# assert $a[0] == "H"
9-
# item in
10-
# assert ("Hello" in $a) and ("World!" in $a)
7+
$a[0] = "H"
8+
assert $a[0] == "H"
119
}
1210

13-
# test every method
14-
# {
15-
# var a = "a,b,c"
16-
# var res = $a.split(",")
17-
# assert $res.len() == 3
18-
# assert $res[0] == "a" and $res[1] == "b" and $res[2] == "c"
19-
#
20-
# $a = "a!!b!!c!!"
21-
# $res = $a.split("!!")
22-
# assert $res.len() == 3
23-
# assert $res[0] == "a" and $res[1] == "b" and $res[2] == "c"
24-
# }
25-
26-
assert "hello"\
27-
"world" == "helloworld"
28-
2911
# string comparison
3012
assert "hello" == "hello"
3113
assert "b" > "a"
@@ -40,3 +22,12 @@ assert "something is true"
4022
assert "Hello" in "Hello World"
4123
assert not ("Foo" in "Bar")
4224
assert "" in "something"
25+
26+
# multiline string
27+
assert "hello"\
28+
"world" == "helloworld"
29+
30+
# escape sequences
31+
assert '\n' != "\n" # "\n" becomes newline
32+
assert '\n' == ("\\" + "n") # does not become newline
33+
assert '\' == "\\"

0 commit comments

Comments
 (0)