aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexis Lockwood2021-06-27 14:19:38 -0400
committerAlexis Lockwood2021-06-27 14:19:38 -0400
commitcc99c38a874652c4efe7101c792be6106999c736 (patch)
treec1fbb1435e203853d74a5648a1f37577e5a604b6
parent6890f1f40a386851c7af168def075044b101be60 (diff)
Arguments and return values
-rw-r--r--Makefile3
-rw-r--r--README42
-rw-r--r--examples/add.bas13
-rw-r--r--src/ls_expr.c14
-rw-r--r--src/ls_internal.c49
-rw-r--r--src/ls_internal.h7
-rw-r--r--src/ls_kw_impl.c63
-rw-r--r--src/ls_kw_impl_GOSUB_RETURN.c146
-rw-r--r--src/ls_types.h11
-rw-r--r--test/tsupport.c1
10 files changed, 255 insertions, 94 deletions
diff --git a/Makefile b/Makefile
index 4cb1098..d7665b5 100644
--- a/Makefile
+++ b/Makefile
@@ -2,7 +2,8 @@ PYTHON ?= python3
LS_INCLUDE := src
LS_SOURCES := src/ls_internal.c src/ls_kws.c src/ls_expr.c src/ls.c \
src/ls_lex.c src/ls_goto.c \
- src/ls_kw_impl.c src/ls_kw_impl_PRINT.c src/ls_kw_impl_GOTO.c
+ src/ls_kw_impl.c src/ls_kw_impl_PRINT.c src/ls_kw_impl_GOTO.c \
+ src/ls_kw_impl_GOSUB_RETURN.c
LS_ARGS := -I${LS_INCLUDE} -Lsrc -lls
TEST_ARGS := -Imunit -Lmunit -lmunit test/tsupport.c
diff --git a/README b/README
index 6b6bff9..ddabb6c 100644
--- a/README
+++ b/README
@@ -49,7 +49,7 @@ Originally written by Alexis Lockwood in 2021. Ⓐ
- Label cache
- Fetcher (always execute in RAM)
- Delete GOFUN, it doesn't exist anymore
-- Argument and return value support for GOSUB and RETURN
+- OPER_* should be LS_OPER_*
╒════════════════════════╕
│ THE SCRIPTING LANGUAGE │
@@ -63,6 +63,7 @@ Unlike BASIC which uses line numbers, LittleScript uses a system of named
labels and numbered small directional jumps. These are inspired by GNU
Assembler jump targets:
+ REM examples/positions.bas
GOSUB hello
PRINT "Returned"
END
@@ -125,32 +126,29 @@ replaced by single-byte abbreviations - these are listed in the keyword
documentation.
-┌─────────────────────────┐
-│ Functions and the stack │
-└─────────────────────────┘
+┌───────────┐
+│ Functions │
+└───────────┘
-The interpreter has a stack that is used in the process of parsing expressions
-and tracking control flow. This stack may also be used to pass arguments and
-return values to and from functions, a feature most older BASIC types did not
-have. This is done like this:
+Littlescript supports true functions as an extention to the GOSUB syntax.
+Arguments are given in parentheses in key=value format, and a new scope is
+created in the called function with these values. The AS keyword can create
+a return value:
- GOSUB PrintN("Hello", 5) - this is sugar for PUSH 5 "Hello"
- GOSUB PrintN
+ REM examples/add.bas
+ a = 2
+ b = 3
+ GOSUB Add(a = a, b = b) AS c
+ PRINT "a + b = ", c
END
- PrintN:
- POP s$ n
- FOR i = 1 to n
- PRINT s$
- NEXT
- RETURN
-
-Return values can be passed back from both functions and subroutines (as the
-former is just syntactic sugar for the latter). The return value is given to
-the return keyword like `RETURN 42`, and retrieved using the extended AS syntax
-for GOSUB (e.g. `GOSUB Add(2, 2) AS four`). If a return value is not
-needed, it can be ignored.
+ Add: 'Add(a, b) => a + b
+ RETURN a + b
+Because a new scope is created, values are passed by value: if the function
+modifies them, the modification only occurs in the new scope. To pass by
+value, name the variables accordingly and omit from the GOSUB statement; they
+will be located and modified in the next scope up.
┌─────────────────────┐
│ Variables and scope │
diff --git a/examples/add.bas b/examples/add.bas
new file mode 100644
index 0000000..0acf261
--- /dev/null
+++ b/examples/add.bas
@@ -0,0 +1,13 @@
+REM examples/add.bas
+a = 2
+b = 3
+GOSUB Add(a = a, b = b) AS c
+PRINT "a = ", a, ", b = ", b
+PRINT "a + b = ", c
+END
+
+Add: 'Add(a, b) => a + b
+ ' try to tinker with the scope to make sure it's set up right
+ d = a
+ a = 42
+ RETURN d + b
diff --git a/src/ls_expr.c b/src/ls_expr.c
index f6bfd2a..29a49f6 100644
--- a/src/ls_expr.c
+++ b/src/ls_expr.c
@@ -70,7 +70,8 @@ static void _handle_number(ls_shuntingyard_t * yard, ls_token_t const * tok);
/// to _handle_operator().
static bool _handle_keyword(ls_shuntingyard_t * yard, ls_token_t * tok);
/// Handle a LS_TOK_OPERATOR
-static void _handle_operator(ls_shuntingyard_t * yard, ls_token_t const * tok);
+/// @return true if parsing should halt
+static bool _handle_operator(ls_shuntingyard_t * yard, ls_token_t const * tok);
/// Handle all other tokens
///
/// @return true if parsing should halt
@@ -179,7 +180,7 @@ have_token:
// fall through
case LS_TOK_OPERATOR:
- _handle_operator(&yard, &tok);
+ done = _handle_operator(&yard, &tok);
break;
default:
@@ -307,7 +308,7 @@ static bool _handle_keyword(ls_shuntingyard_t * yard, ls_token_t * tok)
return false;
}
-static void _handle_operator(ls_shuntingyard_t * yard, ls_token_t const * tok)
+static bool _handle_operator(ls_shuntingyard_t * yard, ls_token_t const * tok)
{
ls_oper_t op = tok->body.oper_val;
@@ -319,6 +320,11 @@ static void _handle_operator(ls_shuntingyard_t * yard, ls_token_t const * tok)
if (op == OPER_RPAREN)
{
+ // This is likely an enclosing paren for something else
+ // (e.g. function call syntax in GOSUB).
+ if (yard->nest == 0)
+ return true;
+
while (_top(yard) != OPER_LPAREN)
_pop_oper_and_apply(yard);
_pop(yard);
@@ -354,6 +360,8 @@ static void _handle_operator(ls_shuntingyard_t * yard, ls_token_t const * tok)
_push(yard, op);
yard->allow_unary = true;
}
+
+ return false;
}
static bool _handle_other(ls_shuntingyard_t * yard, ls_token_t const * tok)
diff --git a/src/ls_internal.c b/src/ls_internal.c
index 8d63fa3..0f2dfa9 100644
--- a/src/ls_internal.c
+++ b/src/ls_internal.c
@@ -154,6 +154,33 @@ void ls_free(ls_context_t * ctx, ls_value_t * v)
ctx->pool = v;
}
+void ls_free_val(ls_context_t * ctx, ls_value_t * v)
+{
+ ls_value_t * child;
+
+ switch (v->ty)
+ {
+ case LS_TY_STR:
+ child = v->body.str.chunk;
+ break;
+ case LS_TY_LIST:
+ child = v->body.list.first;
+ break;
+ default:
+ child = NULL;
+ break;
+ }
+
+ ls_value_t * next;
+ for (; child; child = next)
+ {
+ next = child->next;
+ ls_free(ctx, child);
+ }
+
+ ls_free(ctx, v);
+}
+
size_t ls_mem_avail(ls_context_t * ctx)
{
size_t count = 0;
@@ -228,6 +255,28 @@ void ls_write_int_var(ls_context_t * ctx, ls_value_t * var, ls_int_t val)
var->body.int_var.value = val;
}
+void ls_write_var(ls_context_t * ctx, ls_value_t * var, ls_value_t * val)
+{
+ if (val->ty == LS_TY_INT)
+ {
+ ls_write_int_var(ctx, var, val->body.integer.value);
+ }
+ else
+ {
+ if (var->ty == LS_TY_INT_VAR)
+ {
+ var->ty = LS_TY_VAR;
+ var->body.var.value = NULL;
+ }
+
+ if (var->body.var.value)
+ ls_free_val(ctx, var->body.var.value);
+
+ var->body.var.value = ls_alloc(ctx);
+ memcpy(var->body.var.value, val, sizeof(ls_value_t));
+ }
+}
+
bool ls_exec_line(ls_context_t * ctx)
{
ls_token_t tok = {.ty = LS_TOK_STATEMENT_SEP};
diff --git a/src/ls_internal.h b/src/ls_internal.h
index e5b38da..45892b1 100644
--- a/src/ls_internal.h
+++ b/src/ls_internal.h
@@ -138,6 +138,9 @@ ls_value_t * ls_alloc(ls_context_t * ctx);
/// Deallocate, returning to the pool.
void ls_free(ls_context_t * ctx, ls_value_t * v);
+/// Free a value, including anything it points to
+void ls_free_val(ls_context_t * ctx, ls_value_t * v);
+
/// Count the number of free blocks
size_t ls_mem_avail(ls_context_t * ctx);
@@ -159,6 +162,10 @@ ls_int_t ls_read_int_var(ls_context_t * ctx, ls_value_t * var);
/// type will be changed.
void ls_write_int_var(ls_context_t * ctx, ls_value_t * var, ls_int_t val);
+/// Write a variable of any type. val is not consumed and is assumed not
+/// to live in the pool (space is allocated as needed).
+void ls_write_var(ls_context_t * ctx, ls_value_t * var, ls_value_t * val);
+
/// Execute one line.
///
/// @return whether there was a line to execute. This will return false if
diff --git a/src/ls_kw_impl.c b/src/ls_kw_impl.c
index 4ef7ca0..7c52bf6 100644
--- a/src/ls_kw_impl.c
+++ b/src/ls_kw_impl.c
@@ -54,39 +54,6 @@ void ls_kw_fun_ERROR(ls_context_t * ctx) { _no_impl(ctx); }
void ls_kw_fun_FN(ls_context_t * ctx) { _no_impl(ctx); }
void ls_kw_fun_FOR(ls_context_t * ctx) { _no_impl(ctx); }
void ls_kw_fun_GOFUN(ls_context_t * ctx) { _no_impl(ctx); }
-
-void ls_kw_fun_GOSUB(ls_context_t * ctx)
-{
- // TODO: arguments
- // TODO: AS
-
- ctx->callstack->body.sctx_call.pc = ctx->pc;
-
- ls_token_t tok;
- ls_lex(ctx, &tok);
-
- if (tok.ty != LS_TOK_WORD)
- ls_throw_err(ctx, LS_SYNTAX_ERROR);
-
- char ident[LS_IDENT_LEN];
- memcpy(ident, tok.body.word_val, LS_IDENT_LEN);
-
- ls_value_t * frame = ls_alloc(ctx);
- *frame = (ls_value_t) {
- .ty = LS_TY_SCTX_CALL,
- .prev = ctx->callstack,
- .next = NULL,
- .body.sctx_call = {
- .pc = LS_ADDR_NULL,
- .readptr = LS_ADDR_NULL,
- },
- };
-
- ctx->callstack = frame;
-
- ls_goto_ident(ctx, ident);
-}
-
void ls_kw_fun_HEX(ls_context_t * ctx) { _no_impl(ctx); }
void ls_kw_fun_IF(ls_context_t * ctx) {
@@ -158,36 +125,6 @@ void ls_kw_fun_REM(ls_context_t * ctx)
}
void ls_kw_fun_RESTORE(ls_context_t * ctx) { _no_impl(ctx); }
-
-void ls_kw_fun_RETURN(ls_context_t * ctx)
-{
- // TODO: return value
-
- // Free the scope
- ls_value_t * next;
- for (ls_value_t * i = ctx->callstack->next; i; i = next)
- {
- next = i->next;
- ls_free(ctx, i);
- }
-
- ls_value_t * frame = ctx->callstack->prev;
- ls_free(ctx, ctx->callstack);
- ctx->callstack = frame;
- ctx->pc = ctx->callstack->body.sctx_call.pc;
-
- // pc was left pointing just after GOSUB so we can parse for the
- // return value. Just eat everything until the above TODO is resolved
- for (;;) {
- ls_uchar uch = ls_fetch_rel(ctx, 0);
-
- if (uch == 0 || uch == '\n')
- return;
-
- ctx->pc++;
- }
-}
-
void ls_kw_fun_RIGHT(ls_context_t * ctx) { _no_impl(ctx); }
void ls_kw_fun_RND(ls_context_t * ctx) { _no_impl(ctx); }
void ls_kw_fun_SIN(ls_context_t * ctx) { _no_impl(ctx); }
diff --git a/src/ls_kw_impl_GOSUB_RETURN.c b/src/ls_kw_impl_GOSUB_RETURN.c
new file mode 100644
index 0000000..1d3d45d
--- /dev/null
+++ b/src/ls_kw_impl_GOSUB_RETURN.c
@@ -0,0 +1,146 @@
+// This software disclaims copyright. Do what you want with it. Be gay, do
+// crime. Originally written by Alexis Lockwood in 2021. Ⓐ
+
+// --- DEPENDENCIES ------------------------------------------------------------
+
+// Supporting modules
+#include "ls_internal.h"
+#include "ls_kws.h"
+#include "ls_expr.h"
+#include "ls_lex.h"
+#include "ls_goto.h"
+
+// Standard headers
+#include <stdbool.h>
+#include <stddef.h>
+#include <inttypes.h>
+#include <string.h>
+
+// --- PRIVATE MACROS ----------------------------------------------------------
+// --- PRIVATE DATATYPES -------------------------------------------------------
+// --- PRIVATE CONSTANTS -------------------------------------------------------
+// --- PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
+// --- PUBLIC VARIABLES --------------------------------------------------------
+// --- PRIVATE VARIABLES -------------------------------------------------------
+// --- PUBLIC FUNCTIONS --------------------------------------------------------
+
+void ls_kw_fun_GOSUB(ls_context_t * ctx)
+{
+ ctx->callstack->body.sctx_call.pc = ctx->pc;
+
+ ls_token_t tok;
+ ls_lex(ctx, &tok);
+
+ if (tok.ty != LS_TOK_WORD)
+ ls_throw_err(ctx, LS_SYNTAX_ERROR);
+
+ char ident[LS_IDENT_LEN];
+ memcpy(ident, tok.body.word_val, LS_IDENT_LEN);
+
+ ls_value_t * frame = ls_alloc(ctx);
+ *frame = (ls_value_t) {
+ .ty = LS_TY_SCTX_CALL,
+ .prev = ctx->callstack,
+ .next = NULL,
+ .body.sctx_call = {
+ .pc = LS_ADDR_NULL,
+ .readptr = LS_ADDR_NULL,
+ },
+ };
+
+ ctx->callstack = frame;
+ ctx->callstack->body.sctx_call.pc = ctx->pc;
+
+ ls_lex(ctx, &tok);
+ if (tok.ty == LS_TOK_OPERATOR && tok.body.oper_val == OPER_LPAREN)
+ {
+ for (;;)
+ {
+ ls_lex(ctx, &tok);
+
+ if (tok.ty == LS_TOK_WORD)
+ {
+ char arg_ident[LS_IDENT_LEN];
+ memcpy(arg_ident, tok.body.word_val,
+ LS_IDENT_LEN);
+
+ ls_lex(ctx, &tok);
+ if (!(tok.ty == LS_TOK_OPERATOR &&
+ tok.body.oper_val == OPER_EQ))
+ ls_throw_err(ctx, LS_SYNTAX_ERROR);
+
+ ls_value_t val;
+ ls_eval_expr(ctx, &val, NULL);
+
+ ls_value_t * var = ls_new_var(ctx, arg_ident);
+ ls_write_var(ctx, var, &val);
+ }
+ else if (tok.ty == LS_TOK_COMMA)
+ continue;
+ else if (tok.ty == LS_TOK_OPERATOR
+ && tok.body.oper_val == OPER_RPAREN)
+ break;
+ else
+ ls_throw_err(ctx, LS_SYNTAX_ERROR);
+ }
+ ls_lex(ctx, &tok);
+ }
+
+ // AS is parsed by RETURN, just check that the line is actually valid
+ if (tok.ty == LS_TOK_KEYWORD && tok.body.keyword_val == LS_KW_AS)
+ {
+ ls_lex(ctx, &tok);
+ if (tok.ty != LS_TOK_WORD)
+ ls_throw_err(ctx, LS_SYNTAX_ERROR);
+
+ ls_lex(ctx, &tok);
+ }
+
+ if (tok.ty != LS_TOK_STATEMENT_SEP && tok.ty != LS_TOK_NONE)
+ ls_throw_err(ctx, LS_SYNTAX_ERROR);
+
+ ls_goto_ident(ctx, ident);
+}
+
+void ls_kw_fun_RETURN(ls_context_t * ctx)
+{
+ ls_token_t tok;
+ ls_value_t val = {.ty = LS_TY_INT, .body.integer.value = 0};
+ ls_lex(ctx, &tok);
+
+ if (!LS_TOK_EOS(tok))
+ ls_eval_expr(ctx, &val, &tok);
+
+ // Free the scope
+ ls_value_t * next;
+ for (ls_value_t * i = ctx->callstack->next; i; i = next)
+ {
+ next = i->next;
+ ls_free(ctx, i);
+ }
+
+ ls_value_t * frame = ctx->callstack->prev;
+ ls_addr_t pc = ctx->callstack->body.sctx_call.pc;
+ ls_free(ctx, ctx->callstack);
+ ctx->callstack = frame;
+ ctx->pc = pc;
+
+ while (!LS_TOK_EOS(tok))
+ {
+ if (tok.ty == LS_TOK_KEYWORD
+ && tok.body.keyword_val == LS_KW_AS)
+ {
+ ls_lex(ctx, &tok);
+ if (tok.ty != LS_TOK_WORD)
+ // This was already checked!
+ ls_throw_err(ctx, LS_INTERNAL_ERROR);
+ ls_value_t * var = ls_find_var(ctx, tok.body.word_val);
+ if (!var)
+ var = ls_new_var(ctx, tok.body.word_val);
+ ls_write_var(ctx, var, &val);
+ }
+ ls_lex(ctx, &tok);
+ }
+}
+
+// --- PRIVATE FUNCTION DEFINITIONS --------------------------------------------
diff --git a/src/ls_types.h b/src/ls_types.h
index 9df404e..63934bd 100644
--- a/src/ls_types.h
+++ b/src/ls_types.h
@@ -51,6 +51,9 @@
#define LS_ADDR_NULL UINT16_MAX
#define LS_ADDR_MAX UINT16_MAX
+/// Check if a token represents the end of a statement
+#define LS_TOK_EOS(tok) ((tok).ty >= LS_TOK_STATEMENT_SEP)
+
// --- ACCESSORY DEFINITIONS ---------------------------------------------------
// Forward declaration of the main Value type.
@@ -350,15 +353,15 @@ typedef enum {
/// Comma
LS_TOK_COMMA,
+ /// All other "tokens". Normally only used internally by the lexer -
+ /// lexer will throw a syntax error on invalid tokens.
+ LS_TOK_INVALID,
+
/// Statement separators: newline and ;
LS_TOK_STATEMENT_SEP,
/// End of stream
LS_TOK_NONE,
-
- /// All other "tokens". Normally only used internally by the lexer -
- /// lexer will throw a syntax error on invalid tokens.
- LS_TOK_INVALID,
} ls_token_ty_t;
/// Decoded token
diff --git a/test/tsupport.c b/test/tsupport.c
index 5308200..fc83a25 100644
--- a/test/tsupport.c
+++ b/test/tsupport.c
@@ -37,7 +37,6 @@ void ls_test_setup(ls_context_t * ctx, char const * text)
ctx->funcs = NULL;
ctx->pool = _pool;
ctx->pc = 0;
- ctx->line = 1;
ctx->fetcher = _fetcher;
ctx->fetcher_ctx = (void *) text;
ctx->line_trace_hook = NULL;