aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexis Lockwood2021-07-01 18:45:30 -0400
committerAlexis Lockwood2021-07-01 18:45:30 -0400
commitf7c234e273e0f3d6233dd10b9c7ad819558b09c1 (patch)
tree6a1e9d1352d0a7867c5bd9245905bb4b7b4f842d
parent5b705f7ddd394872ce7d1277fd781d82ea815bba (diff)
FOR loop
-rw-r--r--README.md13
-rw-r--r--examples/for.bas24
-rwxr-xr-xgen_kws.py2
-rw-r--r--src/ls_kw_impl.c114
-rw-r--r--src/ls_kws.c3
-rw-r--r--src/ls_types.h6
6 files changed, 154 insertions, 8 deletions
diff --git a/README.md b/README.md
index b207d2b..335fd02 100644
--- a/README.md
+++ b/README.md
@@ -106,6 +106,8 @@ The following things use pool entries:
- Hooks
- Label cache
- Fetcher (always execute in RAM)
+- BUG: return PC should not go on the caller frame but on the callee frame -
+ the caller frame might not be `SCTX_CALL`!
# THE SCRIPTING LANGUAGE
@@ -252,7 +254,7 @@ FILE device /path/to/file
| `IF cond GOTO ...` | Equivalent to `IF cond THEN GOTO ...` |
| `LEN(s$)` | Return the length of the string or list |
| `[LET] ident = val` | Assign the variable. |
-| `NEXT ident` | Go back to `FOR` (see that keyword). `ident` is mandatory. |
+| `NEXT` | Go back to `FOR` (see that keyword). |
| `OCT s$ n` | Append the octal representation of `n` to `s$` |
| `ON expr GOTO n1, n2, ...` | Go to the `expr`th line given. |
| `ON expr GOSUB [AS s] s1, s2, ...` | Call the `expr`th subroutine given. |
@@ -281,12 +283,17 @@ erasing any unused variables.
### `FOR ident = a TO b [STEP c]`
If the iterator does not land directly on `b` because of the step size,
-iteration stops when it passes `b`.
+iteration stops just before it passes `b`.
FOR loops, like WHILE loops, do not create a full scope. The loop iterator
can shadow other variables, but all other variables created inside the loop
exist in its enclosing context.
-`
+
+Starting and terminating limits may be any valid integer value. Step is
+limited to the range of a signed 16-bit integer, from -32768 to 32767. Loops
+that will iterate forever (step = 0) or for a very long time (overflowing and
+wrapping around) are not guaranteed to produce errors.
+
### `GOSUB f[(args...)] [AS s]`
Pushes a new stack frame and jumps to label `f`. Label must be named, not
diff --git a/examples/for.bas b/examples/for.bas
new file mode 100644
index 0000000..db9fe81
--- /dev/null
+++ b/examples/for.bas
@@ -0,0 +1,24 @@
+FOR i = 1 TO 10
+ PRINT "foo ", i
+NEXT
+PRINT
+
+FOR i = 10 TO 1 STEP -1
+ PRINT "bar ", i
+NEXT
+PRINT
+
+FOR i = 2 TO 20 STEP 2
+ PRINT "baz ", i
+NEXT
+PRINT
+
+FOR i = 0 TO 21 STEP 5
+ PRINT "spam ", i
+NEXT
+PRINT
+
+FOR i = -1000 TO 1000 STEP 250
+ PRINT "eggs ", i
+NEXT
+PRINT
diff --git a/gen_kws.py b/gen_kws.py
index 9fb409d..63c5ac6 100755
--- a/gen_kws.py
+++ b/gen_kws.py
@@ -100,7 +100,7 @@ else:
hashmap = []
indices = []
-for h in range(MAX_HASH):
+for h in range(MAX_HASH + 1):
kws = [i.index - OFFSET for i in KEYWORDS if i.h == h]
if kws:
diff --git a/src/ls_kw_impl.c b/src/ls_kw_impl.c
index 5cd1db9..9070ab7 100644
--- a/src/ls_kw_impl.c
+++ b/src/ls_kw_impl.c
@@ -54,10 +54,80 @@ void ls_kw_fun_EQV(ls_t * self) { _no_impl(self); }
void ls_kw_fun_ERASE(ls_t * self) { _no_impl(self); }
void ls_kw_fun_ERROR(ls_t * self) { _no_impl(self); }
void ls_kw_fun_FN(ls_t * self) { _no_impl(self); }
-void ls_kw_fun_FOR(ls_t * self) { _no_impl(self); }
+
+void ls_kw_fun_FOR(ls_t * self)
+{
+ // FOR ident = a TO b [STEP c]
+ ls_value_t * ctx = ls_alloc(self);
+ *ctx = (ls_value_t) {
+ .ty = LS_TY_SCTX_FOR,
+ .body.sctx_for = {
+ .term = 0,
+ .step = 1,
+ .for_pc = LS_ADDR_NULL,
+ },
+ .prev = self->_callstack,
+ .next = NULL,
+ };
+ self->_callstack = ctx;
+
+ ls_value_t * iterator = ls_alloc(self);
+ *iterator = (ls_value_t) {
+ .ty = LS_TY_INT_VAR,
+ .body.int_var = {{0}},
+ };
+
+ ctx->next = iterator;
+
+ // FOR ident = a TO b [STEP c]
+ if (ls_lex(self) != LS_TOK_WORD)
+ ls_throw_err(self, LS_SYNTAX_ERROR);
+
+ strncpy(iterator->body.int_var.ident, self->_token.word, LS_IDENT_LEN);
+
+ if (ls_lex(self) != LS_OP_EQ)
+ ls_throw_err(self, LS_SYNTAX_ERROR);
+
+ ls_value_t val;
+ ls_eval_expr(self, &val, LS_TOK_NONE);
+ if (val.ty != LS_TY_INT)
+ ls_throw_err(self, LS_SYNTAX_ERROR);
+ iterator->body.int_var.value = val.body.integer.value;
+
+ if (ls_lex(self) != LS_KW_TO)
+ ls_throw_err(self, LS_SYNTAX_ERROR);
+
+ ls_eval_expr(self, &val, LS_TOK_NONE);
+ if (val.ty != LS_TY_INT)
+ ls_throw_err(self, LS_SYNTAX_ERROR);
+ ctx->body.sctx_for.term = val.body.integer.value;
+
+ ls_token_t tok = ls_lex(self);
+
+ if (tok == LS_KW_STEP)
+ {
+ ls_eval_expr(self, &val, LS_TOK_NONE);
+ if (val.ty != LS_TY_INT)
+ ls_throw_err(self, LS_SYNTAX_ERROR);
+
+ if (val.body.integer.value > INT16_MAX ||
+ val.body.integer.value < INT16_MIN)
+ ls_throw_err(self, LS_FOR_STEP_TOO_LARGE);
+
+ ctx->body.sctx_for.step = (int16_t) val.body.integer.value;
+ tok = ls_lex(self);
+ }
+
+ if (tok == LS_TOK_STATEMENT_SEP)
+ ctx->body.sctx_for.for_pc = self->_pc;
+ else
+ ls_throw_err(self, LS_SYNTAX_ERROR);
+}
+
void ls_kw_fun_HEX(ls_t * self) { _no_impl(self); }
-void ls_kw_fun_IF(ls_t * self) {
+void ls_kw_fun_IF(ls_t * self)
+{
// TODO how to support ELSE - the current parsing method doesn't
// really allow it. Should we bother?
ls_value_t cond;
@@ -92,7 +162,45 @@ void ls_kw_fun_LEFT(ls_t * self) { _no_impl(self); }
void ls_kw_fun_LET(ls_t * self) { _no_impl(self); }
void ls_kw_fun_LOG(ls_t * self) { _no_impl(self); }
void ls_kw_fun_MID(ls_t * self) { _no_impl(self); }
-void ls_kw_fun_NEXT(ls_t * self) { _no_impl(self); }
+
+void ls_kw_fun_NEXT(ls_t * self)
+{
+ if (self->_callstack->ty != LS_TY_SCTX_FOR)
+ ls_throw_err(self, LS_FOR_NEXT_MISMATCH);
+
+ ls_value_t * ctx = self->_callstack;
+ ls_value_t * iterator = ctx->next;
+ ls_int_t itval = iterator->body.int_var.value;
+ ls_int_t term = ctx->body.sctx_for.term;
+ ls_int_t step = (ls_int_t) ctx->body.sctx_for.step;
+
+ bool done = false;
+
+ if (step < 0)
+ {
+ if (itval <= term || (itval + step < term))
+ done = true;
+ }
+ else
+ {
+ if (itval >= term || (itval + step > term))
+ done = true;
+ }
+
+ if (done)
+ {
+ self->_callstack = ctx->prev;
+ ls_free_val(self, ctx);
+ _consume_to_eol(self);
+ }
+ else
+ {
+ self->_pc = ctx->body.sctx_for.for_pc;
+ itval += step;
+ iterator->body.int_var.value = itval;
+ }
+}
+
void ls_kw_fun_NOT(ls_t * self) { _no_impl(self); }
void ls_kw_fun_OCT(ls_t * self) { _no_impl(self); }
void ls_kw_fun_ON(ls_t * self) { _no_impl(self); }
diff --git a/src/ls_kws.c b/src/ls_kws.c
index 2eaa8bf..ecf0c3c 100644
--- a/src/ls_kws.c
+++ b/src/ls_kws.c
@@ -34,6 +34,7 @@ const LS_PROGMEM uint8_t ls_kw_hashmap_indices[] = {
/* 28 */ 52,
/* 29 */ 55,
/* 30 */ 57,
+ /* 31 */ 58,
};
const LS_PROGMEM uint8_t ls_kw_hashmap[] = {
@@ -95,6 +96,8 @@ const LS_PROGMEM uint8_t ls_kw_hashmap[] = {
/* 55 */ 8,
/* 56 */ 0x80 | 34,
/* 57 */ 0x80 | 44,
+ /* 58 */ 31,
+ /* 59 */ 0x80 | 37,
};
LS_KW_FUN(ABS);
diff --git a/src/ls_types.h b/src/ls_types.h
index 26a6103..98a3e40 100644
--- a/src/ls_types.h
+++ b/src/ls_types.h
@@ -168,7 +168,9 @@ typedef struct {
/// Terminating value
ls_int_t term;
/// Step value
- ls_int_t step;
+ int16_t step;
+ /// Program counter of the statement after FOR
+ ls_addr_t for_pc;
} ls_ty_sctx_for_t;
/// WHILE loop stackframe. The NEXT pointer is not used here --- WHILE loops
@@ -269,6 +271,8 @@ typedef enum {
LS_DEVICE_TIMEOUT,
LS_DEVICE_FAULT,
LS_WHILE_WEND_MISMATCH,
+ LS_FOR_NEXT_MISMATCH,
+ LS_FOR_STEP_TOO_LARGE,
LS_INTERNAL_ERROR,
LS_BAD_FILE_NUMBER,
LS_FILE_NOT_FOUND,