mirror of
https://github.com/arendst/Tasmota.git
synced 2025-04-26 15:57:16 +00:00
1806 lines
60 KiB
C
1806 lines
60 KiB
C
/********************************************************************
|
||
** Copyright (c) 2018-2020 Guan Wenliang
|
||
** This file is part of the Berry default interpreter.
|
||
** skiars@qq.com, https://github.com/Skiars/berry
|
||
** See Copyright Notice in the LICENSE file or at
|
||
** https://github.com/Skiars/berry/blob/master/LICENSE
|
||
********************************************************************/
|
||
#include "be_parser.h"
|
||
#include "be_lexer.h"
|
||
#include "be_vector.h"
|
||
#include "be_mem.h"
|
||
#include "be_vm.h"
|
||
#include "be_map.h"
|
||
#include "be_list.h"
|
||
#include "be_var.h"
|
||
#include "be_code.h"
|
||
#include "be_string.h"
|
||
#include "be_func.h"
|
||
#include "be_class.h"
|
||
#include "be_decoder.h"
|
||
#include "be_exec.h"
|
||
#include <limits.h>
|
||
|
||
#define OP_NOT_BINARY TokenNone
|
||
#define OP_NOT_UNARY TokenNone
|
||
#define OP_NOT_ASSIGN TokenNone
|
||
#define UNARY_OP_PRIO 3
|
||
#define ASSIGN_OP_PRIO 16
|
||
|
||
#define FUNC_METHOD 1
|
||
#define FUNC_ANONYMOUS 2
|
||
#define FUNC_STATIC 4
|
||
|
||
#if BE_INTGER_TYPE == 0 /* int */
|
||
#define M_IMAX INT_MAX
|
||
#define M_IMIN INT_MIN
|
||
#elif BE_INTGER_TYPE == 1 /* long */
|
||
#define M_IMAX LONG_MAX
|
||
#define M_IMIN LONG_MIN
|
||
#else /* int64_t (long long) */
|
||
#define M_IMAX LLONG_MAX
|
||
#define M_IMIN LLONG_MIN
|
||
#endif
|
||
|
||
/* get binary operator priority */
|
||
#define binary_op_prio(op) (binary_op_prio_tab[cast_int(op) - OptAdd])
|
||
|
||
#define scan_next_token(parser) (be_lexer_scan_next(&(parser)->lexer))
|
||
#define next_token(parser) ((parser)->lexer.token)
|
||
#define next_type(parser) (next_token(parser).type)
|
||
#define max(a, b) ((a) > (b) ? (a) : (b))
|
||
#define token2str(parser) be_token2str((parser)->vm, &next_token(parser))
|
||
#define funcname(parser) ((parser)->islocal ? "loader" : "main")
|
||
|
||
#define upval_index(v) ((v) & 0xFF)
|
||
#define upval_target(v) ((bbyte)(((v) >> 8) & 0xFF))
|
||
#define upval_instack(v) ((bbyte)(((v) >> 16) != 0))
|
||
#define upval_desc(i, t, s) (((i) & 0xFF) | (((t) & 0xFF) << 8) \
|
||
| (((s) != 0) << 16))
|
||
|
||
#define match_id(parser, s) ((s) = _match_id(parser))
|
||
|
||
#define parser_newstr(p, str) be_lexer_newstr(&(p)->lexer, (str))
|
||
|
||
#define parser_error(p, msg) be_lexerror(&(p)->lexer, msg)
|
||
|
||
#define push_error(parser, ...) \
|
||
parser_error(parser, be_pushfstring(parser->vm, __VA_ARGS__))
|
||
|
||
typedef struct {
|
||
blexer lexer;
|
||
bvm *vm;
|
||
bfuncinfo *finfo;
|
||
bclosure *cl;
|
||
bbyte islocal;
|
||
} bparser;
|
||
|
||
#if BE_USE_SCRIPT_COMPILER
|
||
|
||
static void stmtlist(bparser *parser);
|
||
static void block(bparser *parser, int type);
|
||
static void expr(bparser *parser, bexpdesc *e);
|
||
static void sub_expr(bparser *parser, bexpdesc *e, int prio);
|
||
|
||
static const bbyte binary_op_prio_tab[] = {
|
||
5, 5, 4, 4, 4, /* + - * / % */
|
||
11, 11, 12, 12, 11, 11, /* < <= == != > >= */
|
||
7, 9, 8, 6, 6, 10, 13, 14 /* & | ^ << >> .. && || */
|
||
};
|
||
|
||
static void match_token(bparser *parser, btokentype type)
|
||
{
|
||
if (next_type(parser) != type) { /* error when token is no match */
|
||
btoken *token = &next_token(parser);
|
||
const char *s1 = be_tokentype2str(type);
|
||
const char *s2 = be_token2str(parser->vm, token);
|
||
push_error(parser, "expected '%s' before '%s'", s1, s2);
|
||
}
|
||
scan_next_token(parser); /* skip this token */
|
||
}
|
||
|
||
/* Check that the next token is not of type `type` */
|
||
/* or raise an exception */
|
||
static void match_notoken(bparser *parser, btokentype type)
|
||
{
|
||
if (next_type(parser) == type) { /* error when token is match */
|
||
push_error(parser,
|
||
"expected statement before '%s'", token2str(parser));
|
||
}
|
||
}
|
||
|
||
/* check that if the expdesc is a symbol, it is a valid one or raise an exception */
|
||
static void check_symbol(bparser *parser, bexpdesc *e)
|
||
{
|
||
if (e->type == ETVOID && e->v.s == NULL) { /* error when token is not a symbol */
|
||
push_error(parser,
|
||
"unexpected symbol near '%s'", token2str(parser));
|
||
}
|
||
}
|
||
|
||
/* check that the value in `e` is valid for a variable, i.e. contains a value or a valid symbol */
|
||
static void check_var(bparser *parser, bexpdesc *e)
|
||
{
|
||
check_symbol(parser, e); /* check the token is a symbol */
|
||
if (e->type == ETVOID) { /* error when symbol is undefined */
|
||
int line = parser->lexer.linenumber;
|
||
parser->lexer.linenumber = parser->lexer.lastline;
|
||
push_error(parser,
|
||
"'%s' undeclared (first use in this function)", str(e->v.s));
|
||
parser->lexer.linenumber = line;
|
||
}
|
||
}
|
||
|
||
static int match_skip(bparser *parser, btokentype type)
|
||
{
|
||
if (next_type(parser) == type) { /* match */
|
||
scan_next_token(parser); /* skip this token */
|
||
return btrue;
|
||
}
|
||
return bfalse;
|
||
}
|
||
|
||
static bstring* _match_id(bparser *parser)
|
||
{
|
||
if (next_type(parser) == TokenId) {
|
||
bstring *str = next_token(parser).u.s;
|
||
scan_next_token(parser); /* skip ID */
|
||
return str;
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
#if BE_DEBUG_VAR_INFO
|
||
|
||
void begin_varinfo(bparser *parser, bstring *name)
|
||
{
|
||
bvarinfo *var;
|
||
bfuncinfo *finfo = parser->finfo;
|
||
be_vector_push_c(parser->vm, &finfo->varvec, NULL);
|
||
var = be_vector_end(&finfo->varvec);
|
||
var->name = name;
|
||
var->beginpc = finfo->pc;
|
||
var->endpc = 0; /* */
|
||
finfo->proto->varinfo = be_vector_data(&finfo->varvec);
|
||
finfo->proto->nvarinfo = be_vector_capacity(&finfo->varvec);
|
||
}
|
||
|
||
void end_varinfo(bparser *parser, int beginpc)
|
||
{
|
||
bfuncinfo *finfo = parser->finfo;
|
||
bblockinfo *binfo = finfo->binfo;
|
||
bvarinfo *it = be_vector_data(&finfo->varvec);
|
||
bvarinfo *end = be_vector_end(&finfo->varvec);
|
||
if (beginpc == -1) /* use block->beginpc by default */
|
||
beginpc = binfo->beginpc;
|
||
/* skip the variable of the previous blocks */
|
||
for (; (it <= end) && (it->beginpc < beginpc); ++it);
|
||
for (; it <= end; ++it) {
|
||
if (!it->endpc) /* write to endpc only once */
|
||
it->endpc = finfo->pc;
|
||
}
|
||
}
|
||
|
||
#else
|
||
|
||
#define begin_varinfo(parser, name)
|
||
#define end_varinfo(parser, beginpc) (void)(beginpc)
|
||
|
||
#endif
|
||
|
||
/* Initialize bblockinfo structure */
|
||
static void begin_block(bfuncinfo *finfo, bblockinfo *binfo, int type)
|
||
{
|
||
binfo->prev = finfo->binfo; /* save previous block */
|
||
finfo->binfo = binfo; /* tell parser this is the current block */
|
||
binfo->type = (bbyte)type;
|
||
binfo->hasupval = 0;
|
||
binfo->beginpc = finfo->pc; /* set starting pc for this block */
|
||
binfo->nactlocals = (bbyte)be_list_count(finfo->local); /* count number of local variables in previous block */
|
||
if (type & BLOCK_LOOP) {
|
||
binfo->breaklist = NO_JUMP;
|
||
binfo->continuelist = NO_JUMP;
|
||
}
|
||
}
|
||
|
||
static void end_block_ex(bparser *parser, int beginpc)
|
||
{
|
||
bfuncinfo *finfo = parser->finfo;
|
||
bblockinfo *binfo = finfo->binfo;
|
||
be_code_close(finfo, 0); /* close upvalues */
|
||
if (binfo->type & BLOCK_LOOP) {
|
||
be_code_jumpto(finfo, binfo->beginpc);
|
||
be_code_patchjump(finfo, binfo->breaklist);
|
||
be_code_patchlist(finfo, binfo->continuelist, binfo->beginpc);
|
||
}
|
||
end_varinfo(parser, beginpc);
|
||
be_list_resize(parser->vm, finfo->local, binfo->nactlocals); /* remove local variables from this block, they are now out of scope */
|
||
finfo->freereg = binfo->nactlocals; /* adjust first free register accordingly */
|
||
finfo->binfo = binfo->prev; /* restore previous block */
|
||
}
|
||
|
||
static void end_block(bparser *parser)
|
||
{
|
||
end_block_ex(parser, -1);
|
||
}
|
||
|
||
/* Return the name of the source for this parser, could be `string`,
|
||
`stdin` or the name of the current function */
|
||
static bstring* parser_source(bparser *parser)
|
||
{
|
||
if (parser->finfo) {
|
||
return parser->finfo->proto->source;
|
||
}
|
||
return be_newstr(parser->vm, parser->lexer.fname);
|
||
}
|
||
|
||
/* Initialize a function block and create a new `bprotoˋ */
|
||
static void begin_func(bparser *parser, bfuncinfo *finfo, bblockinfo *binfo)
|
||
{
|
||
bvm *vm = parser->vm;
|
||
bproto *proto = be_newproto(vm);
|
||
var_setproto(vm->top, proto);
|
||
be_stackpush(vm);
|
||
be_vector_init(vm, &finfo->code, sizeof(binstruction)); /* vector for code, vectors are not gced */
|
||
proto->code = be_vector_data(&finfo->code);
|
||
proto->codesize = be_vector_capacity(&finfo->code);
|
||
be_vector_init(vm, &finfo->kvec, sizeof(bvalue)); /* vector for constants */
|
||
proto->ktab = be_vector_data(&finfo->kvec);
|
||
proto->nconst = be_vector_capacity(&finfo->kvec);
|
||
be_vector_init(vm, &finfo->pvec, sizeof(bproto*)); /* vector for subprotos */
|
||
proto->ptab = be_vector_data(&finfo->pvec);
|
||
proto->nproto = be_vector_capacity(&finfo->pvec);
|
||
proto->source = parser_source(parser); /* keep a copy of source for function */
|
||
finfo->local = be_list_new(vm); /* list for local variables */
|
||
var_setlist(vm->top, finfo->local); /* push list of local variables on the stack (avoid gc) */
|
||
be_stackpush(vm);
|
||
finfo->upval = be_map_new(vm); /* push a map for upvals on stack */
|
||
var_setmap(vm->top, finfo->upval);
|
||
be_stackpush(vm);
|
||
finfo->prev = parser->finfo; /* init finfo */
|
||
finfo->lexer = &parser->lexer;
|
||
finfo->proto = proto;
|
||
finfo->freereg = 0;
|
||
finfo->binfo = NULL;
|
||
finfo->pc = 0;
|
||
finfo->flags = 0;
|
||
parser->finfo = finfo;
|
||
#if BE_DEBUG_RUNTIME_INFO
|
||
be_vector_init(vm, &finfo->linevec, sizeof(blineinfo));
|
||
proto->lineinfo = be_vector_data(&finfo->linevec);
|
||
proto->nlineinfo = be_vector_capacity(&finfo->linevec);
|
||
#endif
|
||
#if BE_DEBUG_VAR_INFO
|
||
be_vector_init(vm, &finfo->varvec, sizeof(bvarinfo));
|
||
proto->varinfo = be_vector_data(&finfo->varvec);
|
||
proto->nvarinfo = be_vector_capacity(&finfo->varvec);
|
||
#endif
|
||
begin_block(finfo, binfo, 0);
|
||
}
|
||
|
||
/* compute the upval structure */
|
||
static void setupvals(bfuncinfo *finfo)
|
||
{
|
||
bproto *proto = finfo->proto;
|
||
int nupvals = be_map_count(finfo->upval);
|
||
if (nupvals) {
|
||
bmapnode *node;
|
||
bmap *map = finfo->upval;
|
||
bmapiter iter = be_map_iter();
|
||
bupvaldesc *upvals = be_malloc(
|
||
finfo->lexer->vm, sizeof(bupvaldesc) * nupvals);
|
||
while ((node = be_map_next(map, &iter)) != NULL) {
|
||
uint32_t v = (uint32_t)var_toint(&node->value);
|
||
bupvaldesc *uv = upvals + upval_index(v);
|
||
uv->idx = upval_target(v);
|
||
uv->instack = upval_instack(v);
|
||
#if BE_DEBUG_VAR_INFO
|
||
uv->name = var_tostr(&node->key);
|
||
#endif
|
||
}
|
||
proto->upvals = upvals;
|
||
proto->nupvals = (bbyte)nupvals;
|
||
}
|
||
}
|
||
|
||
/* Function is complete, finalize bproto */
|
||
static void end_func(bparser *parser)
|
||
{
|
||
bvm *vm = parser->vm;
|
||
bfuncinfo *finfo = parser->finfo;
|
||
bproto *proto = finfo->proto;
|
||
|
||
be_code_ret(finfo, NULL); /* append a return to last code */
|
||
end_block(parser); /* close block */
|
||
setupvals(finfo); /* close upvals */
|
||
proto->code = be_vector_release(vm, &finfo->code); /* compact all vectors and return NULL if empty */
|
||
proto->codesize = finfo->pc;
|
||
proto->ktab = be_vector_release(vm, &finfo->kvec);
|
||
proto->nconst = be_vector_count(&finfo->kvec);
|
||
proto->ptab = be_vector_release(vm, &finfo->pvec);
|
||
proto->nproto = be_vector_count(&finfo->pvec);
|
||
#if BE_USE_MEM_ALIGNED
|
||
proto->code = be_move_to_aligned(vm, proto->code, proto->codesize * sizeof(binstruction)); /* move `code` to 4-bytes aligned memory region */
|
||
proto->ktab = be_move_to_aligned(vm, proto->ktab, proto->nconst * sizeof(bvalue)); /* move `ktab` to 4-bytes aligned memory region */
|
||
#endif /* BE_USE_MEM_ALIGNED */
|
||
#if BE_DEBUG_RUNTIME_INFO
|
||
proto->lineinfo = be_vector_release(vm, &finfo->linevec); /* move `lineinfo` to 4-bytes aligned memory region */
|
||
proto->nlineinfo = be_vector_count(&finfo->linevec);
|
||
#if BE_USE_MEM_ALIGNED
|
||
proto->lineinfo = be_move_to_aligned(vm, proto->lineinfo, proto->nlineinfo * sizeof(blineinfo));
|
||
#endif /* BE_USE_MEM_ALIGNED */
|
||
#endif /* BE_DEBUG_RUNTIME_INFO */
|
||
#if BE_DEBUG_VAR_INFO
|
||
proto->varinfo = be_vector_release(vm, &finfo->varvec);
|
||
proto->nvarinfo = be_vector_count(&finfo->varvec);
|
||
#endif
|
||
parser->finfo = parser->finfo->prev; /* restore previous `finfo` */
|
||
be_stackpop(vm, 2); /* pop upval and local */
|
||
}
|
||
|
||
/* is the next token a binary operator? If yes return the operator or `OP_NOT_BINARY` */
|
||
static btokentype get_binop(bparser *parser)
|
||
{
|
||
btokentype op = next_type(parser);
|
||
if (op >= OptAdd && op <= OptOr) {
|
||
return op;
|
||
}
|
||
return OP_NOT_BINARY;
|
||
}
|
||
|
||
/* is the next token a unary operator? If yes return the operator or `OP_NOT_BINARY` */
|
||
/* operator 'negative' 'not' and 'flip' */
|
||
static btokentype get_unary_op(bparser *parser)
|
||
{
|
||
btokentype op = next_type(parser);
|
||
if (op == OptSub || op == OptNot || op == OptFlip) {
|
||
return op; /* operator 'negative' 'not' and 'flip' */
|
||
}
|
||
return OP_NOT_UNARY;
|
||
}
|
||
|
||
/* is the next token an assignment operator? If yes return the operator or `OP_NOT_BINARY` */
|
||
/* `=`, `+=`, ... `>>=` */
|
||
static btokentype get_assign_op(bparser *parser)
|
||
{
|
||
btokentype op = next_type(parser);
|
||
if (op >= OptAssign && op <= OptRsfAssign) {
|
||
return op;
|
||
}
|
||
return OP_NOT_ASSIGN;
|
||
}
|
||
|
||
/* Initialize bexpdesc structure with specific type and value as int */
|
||
static void init_exp(bexpdesc *e, exptype_t type, bint i)
|
||
{
|
||
e->type = (bbyte)type;
|
||
e->t = NO_JUMP;
|
||
e->f = NO_JUMP;
|
||
e->not = 0;
|
||
e->v.s = NULL;
|
||
e->v.i = i;
|
||
}
|
||
|
||
/* find local variable by name, starting at index `begin` */
|
||
/* linear search, returns -1 if not found */
|
||
static int find_localvar(bfuncinfo *finfo, bstring *s, int begin)
|
||
{
|
||
int i, count = be_list_count(finfo->local);
|
||
bvalue *var = be_list_data(finfo->local);
|
||
for (i = count - 1; i >= begin; --i) {
|
||
if (be_eqstr(var[i].v.s, s)) {
|
||
return i;
|
||
}
|
||
}
|
||
return -1; /* not found */
|
||
}
|
||
|
||
/* create a new local variable by name, or return the current register if already exists */
|
||
/* returns the Reg number for the variable */
|
||
/* If STRICT, we don't allow a var to hide a var from outer scope */
|
||
/* We don't allow the same var to be defined twice in same scope */
|
||
static int new_localvar(bparser *parser, bstring *name)
|
||
{
|
||
bfuncinfo *finfo = parser->finfo;
|
||
int reg = find_localvar(finfo, name, finfo->binfo->nactlocals); /* look if already exists skipping the local variables from upper blocks */
|
||
/* 'strict': raise an exception if the local variable shadows another local variable */
|
||
if (reg == -1) {
|
||
bvalue *var;
|
||
if (comp_is_strict(parser->vm)) {
|
||
if (find_localvar(finfo, name, 0) >= 0 && str(name)[0] != '.') { /* we do accept nested redifinition of internal variables starting with '.' */
|
||
push_error(parser, "strict: redefinition of '%s' from outer scope", str(name));
|
||
}
|
||
}
|
||
reg = be_list_count(finfo->local); /* new local index */
|
||
var = be_list_push(parser->vm, finfo->local, NULL);
|
||
var_setstr(var, name);
|
||
if (reg >= finfo->freereg) {
|
||
be_code_allocregs(finfo, 1); /* use a register */
|
||
}
|
||
begin_varinfo(parser, name);
|
||
} else {
|
||
push_error(parser, "redefinition of '%s'", str(name));
|
||
}
|
||
return reg;
|
||
}
|
||
|
||
/* Find upval by name, if found return its index number, or -1 */
|
||
static int find_upval(bfuncinfo *finfo, bstring *s)
|
||
{
|
||
bvm *vm = finfo->lexer->vm;
|
||
bvalue *desc = be_map_findstr(vm, finfo->upval, s);
|
||
if (desc) {
|
||
return upval_index(desc->v.i);
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
/* Recursively search for upper blocks that are referenced in upvals */
|
||
/* and mark them with `hasupval` */
|
||
static void mark_upval(bfuncinfo *finfo, int level)
|
||
{
|
||
bblockinfo *binfo = finfo->prev->binfo;
|
||
while (binfo->nactlocals > level) {
|
||
binfo = binfo->prev;
|
||
}
|
||
binfo->hasupval = 1;
|
||
}
|
||
|
||
static int new_upval(bvm *vm, bfuncinfo *finfo, bstring *name, bexpdesc *var)
|
||
{
|
||
int index;
|
||
bvalue *desc;
|
||
int target = var->v.idx;
|
||
int instack = var->type == ETLOCAL;
|
||
if (instack) { /* mark upvalue's block */
|
||
mark_upval(finfo, target);
|
||
}
|
||
index = be_map_count(finfo->upval);
|
||
if (index >= 255) {
|
||
be_lexerror(finfo->lexer, be_pushfstring(vm,
|
||
"too many upvalues (in '%s')", str(name)));
|
||
}
|
||
desc = be_map_insertstr(vm, finfo->upval, name, NULL);
|
||
var_setint(desc, upval_desc(index, target, instack));
|
||
return index;
|
||
}
|
||
|
||
/* create a new variable in currenr context as name, and create expdesc for it */
|
||
/* If within a block, create as local, otherwise as global */
|
||
static void new_var(bparser *parser, bstring *name, bexpdesc *var)
|
||
{
|
||
bfuncinfo *finfo = parser->finfo;
|
||
if (comp_is_strict(parser->vm)) {
|
||
/* check if we are masking a builtin */
|
||
if (be_builtin_find(parser->vm, name) >= 0) {
|
||
push_error(parser, "strict: redefinition of builtin '%s'", str(name));
|
||
}
|
||
}
|
||
if (finfo->prev || finfo->binfo->prev || parser->islocal) {
|
||
init_exp(var, ETLOCAL, 0);
|
||
var->v.idx = new_localvar(parser, name); /* if local, contains the index in current local var list */
|
||
} else {
|
||
init_exp(var, ETGLOBAL, 0);
|
||
var->v.idx = be_global_new(parser->vm, name);
|
||
if (var->v.idx > (int)IBx_MASK) {
|
||
push_error(parser,
|
||
"too many global variables (in '%s')", str(name));
|
||
}
|
||
if (comp_is_named_gbl(parser->vm)) {
|
||
/* change to ETNGLBAL */
|
||
bexpdesc key;
|
||
init_exp(&key, ETSTRING, 0);
|
||
key.v.s = name;
|
||
init_exp(var, ETNGLOBAL, 0);
|
||
var->v.idx = be_code_nglobal(parser->finfo, &key);
|
||
}
|
||
}
|
||
}
|
||
|
||
static int singlevaraux(bvm *vm, bfuncinfo *finfo, bstring *s, bexpdesc *var)
|
||
{
|
||
if (finfo == NULL) {
|
||
return ETVOID;
|
||
} else {
|
||
int idx = find_localvar(finfo, s, 0);
|
||
if (idx >= 0) { /* local variable */
|
||
init_exp(var, ETLOCAL, 0);
|
||
var->v.idx = idx;
|
||
return ETLOCAL;
|
||
} else {
|
||
idx = find_upval(finfo, s);
|
||
if (idx < 0) {
|
||
/* find the previous scope */
|
||
int res = singlevaraux(vm, finfo->prev, s, var);
|
||
if (res == ETUPVAL || res == ETLOCAL) {
|
||
idx = new_upval(vm, finfo, s, var); /* new upvalue */
|
||
} else {
|
||
idx = be_global_find(vm, s);
|
||
if (idx >= 0) {
|
||
if (idx < be_builtin_count(vm)) {
|
||
return ETGLOBAL; /* global variable */
|
||
} else {
|
||
return comp_is_named_gbl(vm) ? ETNGLOBAL : ETGLOBAL; /* global variable */
|
||
}
|
||
} else {
|
||
return ETVOID; /* unknow (new variable or error) */
|
||
}
|
||
}
|
||
}
|
||
init_exp(var, ETUPVAL, idx);
|
||
return ETUPVAL;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* get variable from next toden as name */
|
||
/* and create an expdesc from it */
|
||
/* can be new, global, named global, upval */
|
||
static void singlevar(bparser *parser, bexpdesc *var)
|
||
{
|
||
bexpdesc key;
|
||
bstring *varname = next_token(parser).u.s;
|
||
int type = singlevaraux(parser->vm, parser->finfo, varname, var);
|
||
switch (type) {
|
||
case ETVOID:
|
||
init_exp(var, ETVOID, 0);
|
||
var->v.s = varname;
|
||
break;
|
||
case ETGLOBAL:
|
||
init_exp(var, ETGLOBAL, 0);
|
||
var->v.idx = be_global_find(parser->vm, varname);
|
||
break;
|
||
case ETNGLOBAL:
|
||
init_exp(&key, ETSTRING, 0);
|
||
key.v.s = varname;
|
||
init_exp(var, ETNGLOBAL, 0);
|
||
var->v.idx = be_code_nglobal(parser->finfo, &key);
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* parse a vararg argument in the form `def f(a, *b) end` */
|
||
/* Munch the '*', read the token, create variable and declare the function as vararg */
|
||
static void func_vararg(bparser *parser) {
|
||
bexpdesc v;
|
||
bstring *str;
|
||
match_token(parser, OptMul); /* skip '*' */
|
||
str = next_token(parser).u.s;
|
||
match_token(parser, TokenId); /* match and skip ID */
|
||
new_var(parser, str, &v); /* new variable */
|
||
parser->finfo->proto->varg |= BE_VA_VARARG; /* set varg flag */
|
||
}
|
||
|
||
/* Parse function or method definition variable list */
|
||
/* Create an implicit local variable for each argument starting at R0 */
|
||
/* Update function proto argc to the expected number or arguments */
|
||
/* Raise an exception if multiple arguments have the same name */
|
||
/* New: vararg support */
|
||
static void func_varlist(bparser *parser)
|
||
{
|
||
bexpdesc v;
|
||
bstring *str;
|
||
/* '(' [ ID {',' ID}] ')' or */
|
||
/* '(' '*' ID ')' or */
|
||
/* '(' [ ID {',' ID}] ',' '*' ID ')' */
|
||
match_token(parser, OptLBK); /* skip '(' */
|
||
if (next_type(parser) == OptMul) {
|
||
func_vararg(parser);
|
||
} else if (match_id(parser, str) != NULL) {
|
||
new_var(parser, str, &v); /* new variable */
|
||
while (match_skip(parser, OptComma)) { /* ',' */
|
||
if (next_type(parser) == OptMul) {
|
||
func_vararg(parser);
|
||
break;
|
||
} else {
|
||
str = next_token(parser).u.s;
|
||
match_token(parser, TokenId); /* match and skip ID */
|
||
/* new local variable */
|
||
new_var(parser, str, &v);
|
||
}
|
||
}
|
||
}
|
||
match_token(parser, OptRBK); /* skip ')' */
|
||
parser->finfo->proto->argc = parser->finfo->freereg;
|
||
}
|
||
|
||
/* Parse a function includind arg list and body */
|
||
/* Given name and type (function or method) */
|
||
/* Returns `bproto` object */
|
||
static bproto* funcbody(bparser *parser, bstring *name, bclass *c, int type)
|
||
{
|
||
bfuncinfo finfo;
|
||
bblockinfo binfo;
|
||
|
||
/* '(' varlist ')' block 'end' */
|
||
begin_func(parser, &finfo, &binfo); /* init new function context */
|
||
finfo.proto->name = name;
|
||
if (type & FUNC_METHOD) { /* If method, add an implicit first argument `self` */
|
||
new_localvar(parser, parser_newstr(parser, "self"));
|
||
finfo.proto->varg |= BE_VA_METHOD;
|
||
}
|
||
func_varlist(parser); /* parse arg list */
|
||
if ((type & FUNC_STATIC) && (c != NULL)) { /* If static method, add an implicit local variable `_class` */
|
||
bexpdesc e1, e2;
|
||
new_var(parser, parser_newstr(parser, "_class"), &e1); /* new implicit variable '_class' */
|
||
init_exp(&e2, ETCONST, 0);
|
||
be_code_implicit_class(parser->finfo, &e2, c);
|
||
be_code_setvar(parser->finfo, &e1, &e2);
|
||
finfo.proto->varg |= BE_VA_STATICMETHOD;
|
||
}
|
||
stmtlist(parser); /* parse statement without final `end` */
|
||
end_func(parser); /* close function context */
|
||
match_token(parser, KeyEnd); /* skip 'end' */
|
||
return finfo.proto; /* return fully constructed `bproto` */
|
||
}
|
||
|
||
/* anonymous function, build `bproto` object with name `_anonymous_` */
|
||
/* and build a expdesc for the bproto */
|
||
static void anon_func(bparser *parser, bexpdesc *e)
|
||
{
|
||
bproto *proto;
|
||
bstring *name = parser_newstr(parser, "_anonymous_");
|
||
/* 'def' ID '(' varlist ')' block 'end' */
|
||
scan_next_token(parser); /* skip 'def' */
|
||
proto = funcbody(parser, name, NULL, FUNC_ANONYMOUS);
|
||
init_exp(e, ETPROTO, be_code_proto(parser->finfo, proto));
|
||
be_stackpop(parser->vm, 1);
|
||
}
|
||
|
||
static void lambda_varlist(bparser *parser)
|
||
{
|
||
bexpdesc v;
|
||
bstring *str;
|
||
/* [ID {',' ID}] | {ID}] */
|
||
if (match_id(parser, str) != NULL) {
|
||
bbool comma;
|
||
new_var(parser, str, &v); /* new variable */
|
||
comma = next_type(parser) == OptComma;
|
||
while (next_type(parser) != OptArrow) {
|
||
if (comma) {
|
||
match_token(parser, OptComma); /* match and skip ',' */
|
||
}
|
||
str = next_token(parser).u.s;
|
||
match_token(parser, TokenId); /* match and skip ID */
|
||
/* new local variable */
|
||
new_var(parser, str, &v);
|
||
}
|
||
}
|
||
match_token(parser, OptArrow); /* skip '->' */
|
||
parser->finfo->proto->argc = parser->finfo->freereg;
|
||
}
|
||
|
||
/* lambda expression */
|
||
static void lambda_expr(bparser *parser, bexpdesc *e)
|
||
{
|
||
bexpdesc e1;
|
||
bfuncinfo finfo;
|
||
bblockinfo binfo;
|
||
bstring *name = parser_newstr(parser, "<lambda>");
|
||
/* '/' ID {[',' ID]} '->' expr */
|
||
scan_next_token(parser); /* skip '/' */
|
||
begin_func(parser, &finfo, &binfo);
|
||
finfo.proto->name = name;
|
||
lambda_varlist(parser);
|
||
expr(parser, &e1);
|
||
check_var(parser, &e1);
|
||
be_code_ret(parser->finfo, &e1);
|
||
end_func(parser);
|
||
init_exp(e, ETPROTO, be_code_proto(parser->finfo, finfo.proto));
|
||
be_stackpop(parser->vm, 1);
|
||
}
|
||
|
||
/* Instanciate a builtin type by name */
|
||
/* Allocates a new register for the value, and call empty constructor */
|
||
/* Is allocated as LOCAL and must be changed to REG when completed */
|
||
static void new_primtype(bparser *parser, const char *type, bexpdesc *e)
|
||
{
|
||
int idx;
|
||
bvm *vm = parser->vm;
|
||
bfuncinfo *finfo = parser->finfo;
|
||
|
||
scan_next_token(parser);
|
||
idx = be_builtin_find(vm, parser_newstr(parser, type));
|
||
init_exp(e, ETGLOBAL, idx);
|
||
idx = be_code_nextreg(finfo, e);
|
||
be_code_call(finfo, idx, 0);
|
||
e->type = ETLOCAL; /* declare as local, will be changed to ETREG when completely initialized */
|
||
}
|
||
|
||
/* Parse next member within a list */
|
||
/* `l` contains the current list. The expr is evaluated and added to the list */
|
||
static void list_nextmember(bparser *parser, bexpdesc *l)
|
||
{
|
||
bexpdesc e, v = *l;
|
||
bfuncinfo *finfo = parser->finfo;
|
||
expr(parser, &e); /* value */
|
||
check_var(parser, &e); /* check that we don´t have an unknown symbol */
|
||
be_code_binop(finfo, OptConnect, &v, &e, -1); /* add it to list with CONNECT */
|
||
be_code_freeregs(finfo, 1); /* since left arg is LOCAL, an ETREG was allocated. Free it */
|
||
}
|
||
|
||
static void map_nextmember(bparser *parser, bexpdesc *l)
|
||
{
|
||
bexpdesc e, v = *l;
|
||
bfuncinfo *finfo = parser->finfo;
|
||
expr(parser, &e); /* key */
|
||
check_var(parser, &e); /* check if value is valid */
|
||
be_code_index(finfo, &v, &e); /* package as `v` as INDEX suffix for value `e` */
|
||
match_token(parser, OptColon); /* ':' */
|
||
expr(parser, &e); /* value in `e` */
|
||
check_var(parser, &e); /* check if value is correct */
|
||
be_code_setvar(finfo, &v, &e); /* set suffi INDEX value to e */
|
||
}
|
||
|
||
static void list_expr(bparser *parser, bexpdesc *e)
|
||
{
|
||
/* '[' {expr ','} [expr] ']' */
|
||
new_primtype(parser, "list", e); /* new list, created as LOCAL first */
|
||
while (next_type(parser) != OptRSB) {
|
||
list_nextmember(parser, e);
|
||
if (!match_skip(parser, OptComma)) { /* ',' */
|
||
break;
|
||
}
|
||
}
|
||
e->type = ETREG; /* then turned to REG */
|
||
match_token(parser, OptRSB); /* skip ']' */
|
||
}
|
||
|
||
static void map_expr(bparser *parser, bexpdesc *e)
|
||
{
|
||
/* '{' {expr ':' expr ','} [expr ':' expr] '}' */
|
||
new_primtype(parser, "map", e); /* new map */
|
||
while (next_type(parser) != OptRBR) {
|
||
map_nextmember(parser, e);
|
||
if (!match_skip(parser, OptComma)) { /* ',' */
|
||
break;
|
||
}
|
||
}
|
||
e->type = ETREG;
|
||
match_token(parser, OptRBR); /* skip '}' */
|
||
}
|
||
|
||
/* push each argument as new reg and return number of args */
|
||
/* TODO `e` is ignored by caller */
|
||
static int exprlist(bparser *parser, bexpdesc *e)
|
||
{
|
||
bfuncinfo *finfo = parser->finfo;
|
||
int n = 1;
|
||
/* expr { ',' expr } */
|
||
expr(parser, e); /* parse expr */
|
||
check_var(parser, e); /* check if valid */
|
||
be_code_nextreg(finfo, e); /* move result to next reg */
|
||
while (match_skip(parser, OptComma)) { /* ',' */
|
||
expr(parser, e);
|
||
check_var(parser, e);
|
||
be_code_nextreg(finfo, e);
|
||
++n;
|
||
}
|
||
return n;
|
||
}
|
||
|
||
/* parse call to method or function */
|
||
/* `e` can be a member (method) or a register */
|
||
/* On return, `e` is ETREG to the result of the call */
|
||
static void call_expr(bparser *parser, bexpdesc *e)
|
||
{
|
||
bexpdesc args;
|
||
bfuncinfo *finfo = parser->finfo;
|
||
int argc = 0, base;
|
||
int ismember = e->type == ETMEMBER;
|
||
|
||
/* func '(' [exprlist] ')' */
|
||
check_var(parser, e);
|
||
/* code function index to next register */
|
||
if (ismember) {
|
||
base = be_code_getmethod(finfo, e);
|
||
} else {
|
||
base = be_code_nextreg(finfo, e); /* allocate a new base reg if not at top already */
|
||
}
|
||
/* base is always taken at top of freereg and allocates 1 reg for function and 2 regs for method */
|
||
scan_next_token(parser); /* skip '(' */
|
||
if (next_type(parser) != OptRBK) { /* if arg list is not empty */
|
||
argc = exprlist(parser, &args); /* push each argument as new reg and return number of args */
|
||
}
|
||
match_token(parser, OptRBK); /* skip ')' */
|
||
argc += ismember; /* if method there is an additional implicit arg */
|
||
be_code_call(finfo, base, argc);
|
||
if (e->type != ETREG) {
|
||
e->type = ETREG;
|
||
e->v.idx = base;
|
||
}
|
||
}
|
||
|
||
/* Parse member expression */
|
||
/* Generates an ETMEMBER object that is materialized later into GETMBR, GETMET or SETMBR */
|
||
static void member_expr(bparser *parser, bexpdesc *e)
|
||
{
|
||
bstring *str;
|
||
/* . ID */
|
||
check_var(parser, e);
|
||
scan_next_token(parser); /* skip '.' */
|
||
if (match_id(parser, str) != NULL) {
|
||
bexpdesc key;
|
||
init_exp(&key, ETSTRING, 0);
|
||
key.v.s = str;
|
||
be_code_member(parser->finfo, e, &key);
|
||
} else if (next_type(parser) == OptLBK) {
|
||
scan_next_token(parser); /* skip '(' */
|
||
bexpdesc key;
|
||
expr(parser, &key);
|
||
check_var(parser, &key);
|
||
match_token(parser, OptRBK); /* skip ')' */
|
||
be_code_member(parser->finfo, e, &key);
|
||
} else {
|
||
push_error(parser, "invalid syntax near '%s'",
|
||
be_token2str(parser->vm, &next_token(parser)));
|
||
}
|
||
}
|
||
|
||
static void index_expr(bparser *parser, bexpdesc *e)
|
||
{
|
||
bexpdesc e1;
|
||
/* [expr] */
|
||
check_var(parser, e);
|
||
scan_next_token(parser); /* skip '[' */
|
||
expr(parser, &e1);
|
||
check_var(parser, &e1);
|
||
be_code_index(parser->finfo, e, &e1);
|
||
match_token(parser, OptRSB); /* skip ']' */
|
||
}
|
||
|
||
static void simple_expr(bparser *parser, bexpdesc *e)
|
||
{
|
||
switch (next_type(parser)) {
|
||
case TokenInteger:
|
||
init_exp(e, ETINT, next_token(parser).u.i);
|
||
break;
|
||
case TokenReal:
|
||
init_exp(e, ETREAL, 0);
|
||
e->v.r = next_token(parser).u.r;
|
||
break;
|
||
case TokenString:
|
||
init_exp(e, ETSTRING, 0);
|
||
e->v.s = next_token(parser).u.s;
|
||
break;
|
||
case TokenId:
|
||
singlevar(parser, e);
|
||
break;
|
||
case KeyTrue:
|
||
init_exp(e, ETBOOL, 1);
|
||
break;
|
||
case KeyFalse:
|
||
init_exp(e, ETBOOL, 0);
|
||
break;
|
||
case KeyNil:
|
||
init_exp(e, ETNIL, 0);
|
||
break;
|
||
default: /* unknow expr */
|
||
return;
|
||
}
|
||
scan_next_token(parser);
|
||
}
|
||
|
||
static void primary_expr(bparser *parser, bexpdesc *e)
|
||
{
|
||
switch (next_type(parser)) {
|
||
case OptLBK: /* '(' expr ')' */
|
||
scan_next_token(parser); /* skip '(' */
|
||
/* sub_expr() is more efficient because there is no need to initialize e. */
|
||
sub_expr(parser, e, ASSIGN_OP_PRIO);
|
||
check_var(parser, e);
|
||
match_token(parser, OptRBK); /* skip ')' */
|
||
break;
|
||
case OptLSB: /* list */
|
||
list_expr(parser, e);
|
||
break;
|
||
case OptLBR: /* map */
|
||
map_expr(parser, e);
|
||
break;
|
||
case KeyDef: /* anonymous function */
|
||
anon_func(parser, e);
|
||
break;
|
||
case OptDiv: /* lambda expression */
|
||
lambda_expr(parser, e);
|
||
break;
|
||
default: /* simple expr */
|
||
simple_expr(parser, e);
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* parse a single string literal as parameter */
|
||
static void call_single_string_expr(bparser *parser, bexpdesc *e)
|
||
{
|
||
bexpdesc arg;
|
||
bfuncinfo *finfo = parser->finfo;
|
||
int base;
|
||
|
||
/* func 'string_literal' */
|
||
check_var(parser, e);
|
||
if (e->type == ETMEMBER) {
|
||
push_error(parser, "method not allowed for string prefix");
|
||
}
|
||
|
||
base = be_code_nextreg(finfo, e); /* allocate a new base reg if not at top already */
|
||
simple_expr(parser, &arg);
|
||
be_code_nextreg(finfo, &arg); /* move result to next reg */
|
||
|
||
be_code_call(finfo, base, 1); /* only one arg */
|
||
if (e->type != ETREG) {
|
||
e->type = ETREG;
|
||
e->v.idx = base;
|
||
}
|
||
}
|
||
|
||
static void suffix_expr(bparser *parser, bexpdesc *e)
|
||
{
|
||
primary_expr(parser, e);
|
||
for (;;) {
|
||
switch (next_type(parser)) {
|
||
case OptLBK: /* '(' function call */
|
||
call_expr(parser, e);
|
||
break;
|
||
case OptDot: /* '.' member */
|
||
member_expr(parser, e);
|
||
break;
|
||
case OptLSB: /* '[' index */
|
||
index_expr(parser, e);
|
||
break;
|
||
case TokenString:
|
||
call_single_string_expr(parser, e); /* " string literal */
|
||
break;
|
||
default:
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
static void suffix_alloc_reg(bparser *parser, bexpdesc *l)
|
||
{
|
||
bfuncinfo *finfo = parser->finfo;
|
||
bbool is_suffix = l->type == ETINDEX || l->type == ETMEMBER; /* is suffix */
|
||
bbool is_suffix_reg = l->v.ss.tt == ETREG || l->v.ss.tt == ETLOCAL || l->v.ss.tt == ETGLOBAL || l->v.ss.tt == ETNGLOBAL; /* if suffix, does it need a register */
|
||
bbool is_global = l->type == ETGLOBAL || l->type == ETNGLOBAL;
|
||
bbool is_upval = l->type == ETUPVAL;
|
||
/* in the suffix expression, if the object is a temporary
|
||
* variable (l->v.ss.tt == ETREG), it needs to be cached. */
|
||
if (is_global || is_upval || (is_suffix && is_suffix_reg)) {
|
||
be_code_allocregs(finfo, 1);
|
||
}
|
||
}
|
||
|
||
/* compound assignment */
|
||
static void compound_assign(bparser *parser, int op, bexpdesc *l, bexpdesc *r)
|
||
{
|
||
int dst = -1; /* destination register in case of compound assignment */
|
||
if (op != OptAssign) { /* check left variable */
|
||
check_var(parser, l);
|
||
/* cache the register of the object when continuously assigning */
|
||
dst = parser->finfo->freereg;
|
||
suffix_alloc_reg(parser, l);
|
||
}
|
||
expr(parser, r); /* right expression */
|
||
check_var(parser, r);
|
||
if (op != OptAssign) { /* compound assignment */
|
||
bexpdesc e = *l;
|
||
op = op < OptAndAssign ? op - OptAddAssign + OptAdd
|
||
: op - OptAndAssign + OptBitAnd;
|
||
be_code_binop(parser->finfo, op, &e, r, dst); /* coding operation */
|
||
*r = e;
|
||
}
|
||
}
|
||
|
||
/* check if we need to create a new local variable with this name to be assigned to */
|
||
/* Returns true if it´s a new local variable */
|
||
/* A new implicit local variable is created if no global has the same name (excluding builtins) */
|
||
/* This means that you can override a builtin silently */
|
||
/* This also means that a function cannot create a global, they must preexist or create with `global` module */
|
||
static int check_newvar(bparser *parser, bexpdesc *e)
|
||
{
|
||
if (e->type == ETGLOBAL) {
|
||
if (e->v.idx < be_builtin_count(parser->vm)) {
|
||
e->v.s = be_builtin_name(parser->vm, e->v.idx);
|
||
if (comp_is_strict(parser->vm)) {
|
||
push_error(parser, "strict: redefinition of builtin '%s'",
|
||
str(e->v.s));
|
||
}
|
||
return btrue;
|
||
}
|
||
return bfalse;
|
||
}
|
||
if (comp_is_strict(parser->vm)) {
|
||
bfuncinfo *finfo = parser->finfo;
|
||
if ((e->type == ETVOID) && (finfo->prev || finfo->binfo->prev || parser->islocal)) {
|
||
push_error(parser, "strict: no global '%s', did you mean 'var %s'?",
|
||
str(e->v.s), str(e->v.s));
|
||
}
|
||
}
|
||
return e->type == ETVOID;
|
||
}
|
||
|
||
static void assign_expr(bparser *parser)
|
||
{
|
||
bexpdesc e;
|
||
btokentype op;
|
||
int line = parser->lexer.linenumber;
|
||
expr(parser, &e); /* left expression */
|
||
check_symbol(parser, &e);
|
||
op = get_assign_op(parser);
|
||
if (op != OP_NOT_ASSIGN) { /* assign operator */
|
||
bexpdesc e1;
|
||
scan_next_token(parser);
|
||
compound_assign(parser, op, &e, &e1);
|
||
if (check_newvar(parser, &e)) { /* new variable */
|
||
new_var(parser, e.v.s, &e);
|
||
}
|
||
if (be_code_setvar(parser->finfo, &e, &e1)) {
|
||
parser->lexer.linenumber = line;
|
||
parser_error(parser,
|
||
"try to assign constant expressions.");
|
||
}
|
||
} else if (e.type >= ETMEMBER) {
|
||
bfuncinfo *finfo = parser->finfo;
|
||
/* these expressions occupy a register and need to be freed */
|
||
finfo->freereg = (bbyte)be_list_count(finfo->local);
|
||
} else if (e.type == ETVOID) { /* not assign expression */
|
||
/* undeclared symbol */
|
||
parser->lexer.linenumber = line;
|
||
check_var(parser, &e);
|
||
}
|
||
}
|
||
|
||
/* conditional expression */
|
||
static void cond_expr(bparser *parser, bexpdesc *e)
|
||
{
|
||
/* expr '?' expr ':' expr */
|
||
if (next_type(parser) == OptQuestion) {
|
||
int jf, jl = NO_JUMP; /* jump list */
|
||
bfuncinfo *finfo = parser->finfo;
|
||
check_var(parser, e); /* check if valid */
|
||
scan_next_token(parser); /* skip '?' */
|
||
be_code_jumpbool(finfo, e, bfalse); /* go if true */
|
||
jf = e->f;
|
||
expr(parser, e);
|
||
check_var(parser, e);
|
||
be_code_nextreg(finfo, e);
|
||
be_code_freeregs(finfo, 1);
|
||
be_code_conjump(finfo, &jl, be_code_jump(finfo)); /* connect jump */
|
||
be_code_patchjump(finfo, jf);
|
||
match_token(parser, OptColon); /* match and skip ':' */
|
||
expr(parser, e);
|
||
check_var(parser, e);
|
||
e->v.idx = be_code_nextreg(finfo, e);
|
||
be_code_patchjump(finfo, jl);
|
||
e->type = ETREG;
|
||
}
|
||
}
|
||
|
||
/* binary operator: + - * / % && || < <= == != > >=
|
||
* unary operator: + - !
|
||
*/
|
||
static void sub_expr(bparser *parser, bexpdesc *e, int prio)
|
||
{
|
||
bfuncinfo *finfo = parser->finfo;
|
||
btokentype op = get_unary_op(parser); /* check if first token in unary op */
|
||
if (op != OP_NOT_UNARY) { /* unary op found */
|
||
int line, res;
|
||
scan_next_token(parser); /* move to next token */
|
||
line = parser->lexer.linenumber; /* remember line number for error reporting */
|
||
sub_expr(parser, e, UNARY_OP_PRIO); /* parse subexpr with new prio */
|
||
check_var(parser, e); /* check that the value is ok */
|
||
res = be_code_unop(finfo, op, e); /* apply unary op with optimizations if the token is a value */
|
||
if (res) { /* encode unary op */
|
||
parser->lexer.linenumber = line;
|
||
push_error(parser, "wrong type argument to unary '%s'",
|
||
res == 1 ? "negative" : "bit-flip");
|
||
}
|
||
} else {
|
||
suffix_expr(parser, e); /* parse left part of binop */
|
||
}
|
||
op = get_binop(parser); /* check if binop */
|
||
while (op != OP_NOT_BINARY && prio > binary_op_prio(op)) { /* is binop applicable */
|
||
bexpdesc e2;
|
||
check_var(parser, e); /* check that left part is valid */
|
||
scan_next_token(parser); /* move to next token */
|
||
be_code_prebinop(finfo, op, e); /* and or */
|
||
init_exp(&e2, ETVOID, 0);
|
||
sub_expr(parser, &e2, binary_op_prio(op)); /* parse right side */
|
||
if ((e2.type == ETVOID) && (op == OptConnect)) {
|
||
init_exp(&e2, ETINT, M_IMAX);
|
||
} else {
|
||
check_var(parser, &e2); /* check if valid */
|
||
}
|
||
be_code_binop(finfo, op, e, &e2, -1); /* encode binary op */
|
||
op = get_binop(parser); /* is there a following binop? */
|
||
}
|
||
if (prio == ASSIGN_OP_PRIO) {
|
||
cond_expr(parser, e);
|
||
}
|
||
}
|
||
|
||
/* Parse new expression and return value in `e` (overwritten) */
|
||
/* Initializes an empty expdes and parse subexpr */
|
||
/* Always allocates a new temp register at top of freereg */
|
||
static void expr(bparser *parser, bexpdesc *e)
|
||
{
|
||
init_exp(e, ETVOID, 0);
|
||
sub_expr(parser, e, ASSIGN_OP_PRIO);
|
||
}
|
||
|
||
static void expr_stmt(bparser *parser)
|
||
{
|
||
assign_expr(parser);
|
||
}
|
||
|
||
static int block_follow(bparser *parser)
|
||
{
|
||
switch (next_type(parser)) {
|
||
case KeyElse: case KeyElif:
|
||
case KeyEnd: case KeyExcept:
|
||
case TokenEOS:
|
||
return 0;
|
||
default:
|
||
return 1;
|
||
}
|
||
}
|
||
|
||
static int cond_stmt(bparser *parser)
|
||
{
|
||
bexpdesc e;
|
||
/* expr */
|
||
match_notoken(parser, OptRBK);
|
||
expr(parser, &e);
|
||
check_var(parser, &e);
|
||
be_code_jumpbool(parser->finfo, &e, bfalse); /* go if true */
|
||
return e.f;
|
||
}
|
||
|
||
static void condition_block(bparser *parser, int *jmp)
|
||
{
|
||
bfuncinfo *finfo = parser->finfo;
|
||
int br = cond_stmt(parser);
|
||
block(parser, 0);
|
||
if (next_type(parser) == KeyElif
|
||
|| next_type(parser) == KeyElse) {
|
||
be_code_conjump(finfo, jmp, be_code_jump(finfo)); /* connect jump */
|
||
}
|
||
be_code_patchjump(finfo, br);
|
||
}
|
||
|
||
static void if_stmt(bparser *parser)
|
||
{
|
||
int jl = NO_JUMP; /* jump list */
|
||
/* IF expr block {ELSEIF expr block}, [ELSE block], end */
|
||
scan_next_token(parser); /* skip 'if' */
|
||
condition_block(parser, &jl);
|
||
while (match_skip(parser, KeyElif)) { /* 'elif' */
|
||
condition_block(parser, &jl);
|
||
}
|
||
if (match_skip(parser, KeyElse)) { /* 'else' */
|
||
block(parser, 0);
|
||
}
|
||
match_token(parser, KeyEnd); /* skip end */
|
||
be_code_patchjump(parser->finfo, jl);
|
||
}
|
||
|
||
static void do_stmt(bparser *parser)
|
||
{
|
||
/* DO block END */
|
||
scan_next_token(parser); /* skip 'do' */
|
||
block(parser, 0);
|
||
match_token(parser, KeyEnd); /* skip 'end' */
|
||
}
|
||
|
||
static void while_stmt(bparser *parser)
|
||
{
|
||
int brk;
|
||
bblockinfo binfo;
|
||
bfuncinfo *finfo = parser->finfo;
|
||
/* WHILE expr block END */
|
||
scan_next_token(parser); /* skip 'while' */
|
||
begin_block(parser->finfo, &binfo, BLOCK_LOOP);
|
||
brk = cond_stmt(parser);
|
||
stmtlist(parser);
|
||
end_block(parser);
|
||
be_code_patchjump(finfo, brk);
|
||
match_token(parser, KeyEnd); /* skip 'end' */
|
||
}
|
||
|
||
static bstring* for_itvar(bparser *parser)
|
||
{
|
||
bstring *str;
|
||
if (match_id(parser, str) == NULL) {
|
||
push_error(parser,
|
||
"missing iteration variable before '%s'",
|
||
token2str(parser));
|
||
}
|
||
return str;
|
||
}
|
||
|
||
static void for_init(bparser *parser, bexpdesc *v)
|
||
{
|
||
bexpdesc e;
|
||
bstring *s;
|
||
bfuncinfo *finfo = parser->finfo;
|
||
/* .it = __iterator__(expr) */
|
||
s = parser_newstr(parser, "__iterator__");
|
||
init_exp(&e, ETGLOBAL, be_builtin_find(parser->vm, s));
|
||
be_code_nextreg(finfo, &e); /* code function '__iterator__' */
|
||
expr(parser, v);
|
||
check_var(parser, v);
|
||
be_code_nextreg(finfo, v);
|
||
be_code_call(finfo, e.v.idx, 1); /* call __iterator__(expr) */
|
||
be_code_freeregs(finfo, 1); /* free register of __iterator__ */
|
||
s = parser_newstr(parser, ".it");
|
||
init_exp(v, ETLOCAL, new_localvar(parser, s));
|
||
}
|
||
|
||
static void for_iter(bparser *parser, bstring *var, bexpdesc *it)
|
||
{
|
||
bexpdesc e;
|
||
bfuncinfo *finfo = parser->finfo;
|
||
/* reset the jump head PC of the for loop body */
|
||
finfo->binfo->beginpc = finfo->pc;
|
||
/* itvar = .it() */
|
||
init_exp(&e, ETLOCAL, new_localvar(parser, var)); /* new itvar */
|
||
be_code_setvar(finfo, &e, it); /* code function to variable '.it' */
|
||
be_code_call(finfo, e.v.idx, 0); /* itvar <- call .it() */
|
||
stmtlist(parser);
|
||
}
|
||
|
||
static void for_leave(bparser *parser, int jcatch, int beginpc)
|
||
{
|
||
bexpdesc e;
|
||
bfuncinfo *finfo = parser->finfo;
|
||
int jbrk = finfo->binfo->breaklist;
|
||
init_exp(&e, ETSTRING, 0);
|
||
e.v.s = parser_newstr(parser, "stop_iteration");
|
||
end_block_ex(parser, beginpc); /* leave except & loop block */
|
||
if (jbrk != NO_JUMP) { /* has `break` statement in iteration block */
|
||
be_code_exblk(finfo, 1);
|
||
jbrk = be_code_jump(finfo);
|
||
}
|
||
be_code_conjump(finfo, &jcatch, finfo->pc);
|
||
be_code_catch(finfo, be_code_nextreg(finfo, &e), 1, 0, NULL);
|
||
be_code_raise(finfo, NULL, NULL);
|
||
be_code_conjump(finfo, &jbrk, finfo->pc);
|
||
be_code_freeregs(finfo, 1);
|
||
}
|
||
|
||
/* approximate equivalent script code:
|
||
* .it = __iter__(expr)
|
||
* try
|
||
* while (1)
|
||
* itvar = .it()
|
||
* stmtlist
|
||
* end
|
||
* except ('stop_iteration')
|
||
* end
|
||
* */
|
||
static void for_stmt(bparser *parser)
|
||
{
|
||
bstring *var;
|
||
bexpdesc iter;
|
||
bblockinfo binfo;
|
||
int jcatch, beginpc = parser->finfo->pc;
|
||
/* FOR ID : expr block END */
|
||
scan_next_token(parser); /* skip 'for' */
|
||
begin_block(parser->finfo, &binfo, BLOCK_EXCEPT | BLOCK_LOOP);
|
||
var = for_itvar(parser);
|
||
match_token(parser, OptColon); /* skip ':' */
|
||
for_init(parser, &iter);
|
||
jcatch = be_code_exblk(parser->finfo, 0);
|
||
for_iter(parser, var, &iter);
|
||
for_leave(parser, jcatch, beginpc);
|
||
match_token(parser, KeyEnd); /* skip 'end' */
|
||
}
|
||
|
||
static bblockinfo* break_block(bparser *parser)
|
||
{
|
||
int try_depth = 0; /* count of exception catch blocks */
|
||
bblockinfo *binfo = parser->finfo->binfo;
|
||
/* BREAK | CONTINUE */
|
||
scan_next_token(parser); /* skip 'break' or 'continue' */
|
||
while (binfo && !(binfo->type & BLOCK_LOOP)) {
|
||
if (binfo->type & BLOCK_EXCEPT) {
|
||
++try_depth; /* leave the exception catch block */
|
||
}
|
||
binfo = binfo->prev;
|
||
}
|
||
if (binfo && try_depth) { /* exception catch blocks that needs to leave */
|
||
be_code_exblk(parser->finfo, try_depth);
|
||
}
|
||
return binfo;
|
||
}
|
||
|
||
static void break_stmt(bparser *parser)
|
||
{
|
||
bfuncinfo *f = parser->finfo;
|
||
bblockinfo *binfo = break_block(parser);
|
||
if (binfo != NULL) { /* connect jump */
|
||
be_code_conjump(f, &binfo->breaklist, be_code_jump(f));
|
||
} else {
|
||
parser_error(parser, "break not loop");
|
||
}
|
||
}
|
||
|
||
static void continue_stmt(bparser *parser)
|
||
{
|
||
bfuncinfo *f = parser->finfo;
|
||
bblockinfo *b = break_block(parser);
|
||
if (b != NULL) { /* connect jump */
|
||
be_code_conjump(f, &b->continuelist, be_code_jump(f));
|
||
} else {
|
||
parser_error(parser, "continue not loop");
|
||
}
|
||
}
|
||
|
||
static bbool isoverloadable(btokentype type)
|
||
{
|
||
return (type >= OptAdd && type <= OptConnect) /* overloaded binary operator */
|
||
|| type == OptFlip || type == OptLBK; /* '~' and '()' operator */
|
||
}
|
||
|
||
static bstring* func_name(bparser* parser, bexpdesc* e, int ismethod)
|
||
{
|
||
btokentype type = next_type(parser);
|
||
if (type == TokenId) {
|
||
bstring *name = next_token(parser).u.s;
|
||
if (!ismethod) {
|
||
new_var(parser, name, e); /* new variable */
|
||
}
|
||
scan_next_token(parser); /* skip name */
|
||
return name;
|
||
} else if (ismethod && isoverloadable(type)) {
|
||
scan_next_token(parser); /* skip token */
|
||
/* '-*' negative operator */
|
||
if (type == OptSub && next_type(parser) == OptMul) {
|
||
scan_next_token(parser); /* skip '*' */
|
||
return parser_newstr(parser, "-*");
|
||
}
|
||
/* '()' call operator */
|
||
if (type == OptLBK && next_type(parser) == OptRBK) {
|
||
scan_next_token(parser); /* skip ')' */
|
||
return parser_newstr(parser, "()");
|
||
}
|
||
return parser_newstr(parser, be_tokentype2str(type));
|
||
}
|
||
push_error(parser,
|
||
"the token '%s' is not a valid function name.",
|
||
token2str(parser));
|
||
return NULL;
|
||
}
|
||
|
||
static void def_stmt(bparser *parser)
|
||
{
|
||
bexpdesc e;
|
||
bproto *proto;
|
||
bfuncinfo *finfo = parser->finfo;
|
||
/* 'def' ID '(' varlist ')' block 'end' */
|
||
scan_next_token(parser); /* skip 'def' */
|
||
proto = funcbody(parser, func_name(parser, &e, 0), NULL, 0);
|
||
be_code_closure(finfo, &e, be_code_proto(finfo, proto));
|
||
be_stackpop(parser->vm, 1);
|
||
}
|
||
|
||
static void return_stmt(bparser *parser)
|
||
{
|
||
bexpdesc e;
|
||
/* 'return' expr */
|
||
scan_next_token(parser); /* skip 'return' */
|
||
expr(parser, &e);
|
||
if (e.v.s) { /* expression is not empty */
|
||
check_var(parser, &e);
|
||
}
|
||
be_code_ret(parser->finfo, &e);
|
||
}
|
||
|
||
static void check_class_attr(bparser *parser, bclass *c, bstring *attr)
|
||
{
|
||
if (be_class_attribute(parser->vm, c, attr) != BE_NONE) {
|
||
push_error(parser,
|
||
"redefinition of the attribute '%s'", str(attr));
|
||
}
|
||
}
|
||
|
||
static void classvar_stmt(bparser *parser, bclass *c)
|
||
{
|
||
bstring *name;
|
||
/* 'var' ID {',' ID} */
|
||
scan_next_token(parser); /* skip 'var' */
|
||
if (match_id(parser, name) != NULL) {
|
||
check_class_attr(parser, c, name);
|
||
be_class_member_bind(parser->vm, c, name, btrue);
|
||
while (match_skip(parser, OptComma)) { /* ',' */
|
||
if (match_id(parser, name) != NULL) {
|
||
check_class_attr(parser, c, name);
|
||
be_class_member_bind(parser->vm, c, name, btrue);
|
||
} else {
|
||
parser_error(parser, "class var error");
|
||
}
|
||
}
|
||
} else {
|
||
parser_error(parser, "class var error");
|
||
}
|
||
}
|
||
|
||
static void class_static_assignment_expr(bparser *parser, bexpdesc *e, bstring *name)
|
||
{
|
||
if (match_skip(parser, OptAssign)) { /* '=' */
|
||
bexpdesc e1, e2;
|
||
/* parse the right expression */
|
||
expr(parser, &e2);
|
||
|
||
e1 = *e; /* copy the class description */
|
||
bexpdesc key; /* build the member key */
|
||
init_exp(&key, ETSTRING, 0);
|
||
key.v.s = name;
|
||
|
||
be_code_member(parser->finfo, &e1, &key); /* compute member accessor */
|
||
be_code_setvar(parser->finfo, &e1, &e2); /* set member */
|
||
}
|
||
}
|
||
|
||
static void classdef_stmt(bparser *parser, bclass *c, bbool is_static)
|
||
{
|
||
bexpdesc e;
|
||
bstring *name;
|
||
bproto *proto;
|
||
/* 'def' ID '(' varlist ')' block 'end' */
|
||
scan_next_token(parser); /* skip 'def' */
|
||
name = func_name(parser, &e, 1);
|
||
check_class_attr(parser, c, name);
|
||
proto = funcbody(parser, name, c, is_static ? FUNC_STATIC : FUNC_METHOD);
|
||
be_class_method_bind(parser->vm, c, proto->name, proto, is_static);
|
||
be_stackpop(parser->vm, 1);
|
||
}
|
||
|
||
static void classstaticclass_stmt(bparser *parser, bclass *c_out, bexpdesc *e_out);
|
||
|
||
static void classstatic_stmt(bparser *parser, bclass *c, bexpdesc *e)
|
||
{
|
||
bstring *name;
|
||
/* 'static' ['var'] ID ['=' expr] {',' ID ['=' expr] } */
|
||
/* 'static' 'def' ID '(' varlist ')' block 'end' */
|
||
scan_next_token(parser); /* skip 'static' */
|
||
if (next_type(parser) == KeyDef) { /* 'static' 'def' ... */
|
||
classdef_stmt(parser, c, btrue);
|
||
} else if (next_type(parser) == KeyClass) { /* 'static' 'class' ... */
|
||
classstaticclass_stmt(parser, c, e);
|
||
} else {
|
||
if (next_type(parser) == KeyVar) {
|
||
scan_next_token(parser); /* skip 'var' if any */
|
||
}
|
||
if (match_id(parser, name) != NULL) {
|
||
check_class_attr(parser, c, name);
|
||
be_class_member_bind(parser->vm, c, name, bfalse);
|
||
class_static_assignment_expr(parser, e, name);
|
||
|
||
while (match_skip(parser, OptComma)) { /* ',' */
|
||
if (match_id(parser, name) != NULL) {
|
||
check_class_attr(parser, c, name);
|
||
be_class_member_bind(parser->vm, c, name, bfalse);
|
||
class_static_assignment_expr(parser, e, name);
|
||
} else {
|
||
parser_error(parser, "class static error");
|
||
}
|
||
}
|
||
} else {
|
||
parser_error(parser, "class static error");
|
||
}
|
||
}
|
||
}
|
||
|
||
static void class_inherit(bparser *parser, bexpdesc *e)
|
||
{
|
||
if (next_type(parser) == OptColon) { /* ':' */
|
||
bexpdesc ec = *e; /* work on a copy because we preserve original class */
|
||
bexpdesc e1;
|
||
scan_next_token(parser); /* skip ':' */
|
||
expr(parser, &e1);
|
||
check_var(parser, &e1);
|
||
be_code_setsuper(parser->finfo, &ec, &e1);
|
||
}
|
||
}
|
||
|
||
static void class_block(bparser *parser, bclass *c, bexpdesc *e)
|
||
{
|
||
/* { [;] } */
|
||
while (block_follow(parser)) {
|
||
switch (next_type(parser)) {
|
||
case KeyVar: classvar_stmt(parser, c); break;
|
||
case KeyStatic: classstatic_stmt(parser, c, e); break;
|
||
case KeyDef: classdef_stmt(parser, c, bfalse); break;
|
||
case OptSemic: scan_next_token(parser); break;
|
||
default: push_error(parser,
|
||
"unexpected token '%s'", token2str(parser));
|
||
}
|
||
}
|
||
}
|
||
|
||
static void class_stmt(bparser *parser)
|
||
{
|
||
bstring *name;
|
||
/* 'class' ID [':' ID] class_block 'end' */
|
||
scan_next_token(parser); /* skip 'class' */
|
||
if (match_id(parser, name) != NULL) {
|
||
bexpdesc e;
|
||
bclass *c = be_newclass(parser->vm, name, NULL);
|
||
new_var(parser, name, &e);
|
||
be_code_class(parser->finfo, &e, c);
|
||
class_inherit(parser, &e);
|
||
class_block(parser, c, &e);
|
||
be_class_compress(parser->vm, c); /* compress class size */
|
||
match_token(parser, KeyEnd); /* skip 'end' */
|
||
} else {
|
||
parser_error(parser, "class name error");
|
||
}
|
||
}
|
||
|
||
static void classstaticclass_stmt(bparser *parser, bclass *c_out, bexpdesc *e_out)
|
||
{
|
||
bstring *name;
|
||
/* [preceding 'static'] 'class' ID [':' ID] class_block 'end' */
|
||
scan_next_token(parser); /* skip 'class' */
|
||
if (match_id(parser, name) != NULL) {
|
||
bexpdesc e_class; /* new class object */
|
||
check_class_attr(parser, c_out, name); /* check that the class names does not collide with another member */
|
||
be_class_member_bind(parser->vm, c_out, name, bfalse); /* add the member slot as static */
|
||
/* create the class object */
|
||
bclass *c = be_newclass(parser->vm, name, NULL);
|
||
new_var(parser, name, &e_class); /* add a local var to the static initialization code for static members */
|
||
be_code_class(parser->finfo, &e_class, c);
|
||
class_inherit(parser, &e_class);
|
||
class_block(parser, c, &e_class);
|
||
be_class_compress(parser->vm, c); /* compress class size */
|
||
match_token(parser, KeyEnd); /* skip 'end' */
|
||
/* add the code to copy the class object to the static member */
|
||
bexpdesc e1 = *e_out; /* copy the class description */
|
||
bexpdesc key; /* build the member key */
|
||
init_exp(&key, ETSTRING, 0);
|
||
key.v.s = name;
|
||
/* assign the class to the static member */
|
||
be_code_member(parser->finfo, &e1, &key); /* compute member accessor */
|
||
be_code_setvar(parser->finfo, &e1, &e_class); /* set member */
|
||
} else {
|
||
parser_error(parser, "class name error");
|
||
}
|
||
}
|
||
|
||
static void import_stmt(bparser *parser)
|
||
{
|
||
bstring *name; /* variable name */
|
||
bexpdesc m, v;
|
||
/* 'import' (ID (['as' ID] | {',' ID}) | STRING 'as' ID ) */
|
||
scan_next_token(parser); /* skip 'import' */
|
||
init_exp(&m, ETSTRING, 0);
|
||
m.v.s = name = next_token(parser).u.s;
|
||
if (next_type(parser) == TokenString) { /* STRING 'as' ID */
|
||
scan_next_token(parser); /* skip the module path */
|
||
match_token(parser, KeyAs); /* match and skip 'as' */
|
||
name = next_token(parser).u.s;
|
||
match_token(parser, TokenId); /* match and skip ID */
|
||
} else { /* ID (['as' ID] | {',' ID}) */
|
||
match_token(parser, TokenId); /* match and skip ID */
|
||
if (match_skip(parser, KeyAs)) { /* 'as' */
|
||
name = next_token(parser).u.s;
|
||
match_token(parser, TokenId); /* match and skip ID */
|
||
} else { /* {',' ID} */
|
||
while (match_skip(parser, OptComma)) { /* ',' */
|
||
new_var(parser, name, &v);
|
||
be_code_import(parser->finfo, &m, &v); /* code import */
|
||
init_exp(&m, ETSTRING, 0); /* scanning for next node */
|
||
m.v.s = name = next_token(parser).u.s;
|
||
match_token(parser, TokenId); /* match and skip ID */
|
||
}
|
||
}
|
||
}
|
||
new_var(parser, name, &v);
|
||
be_code_import(parser->finfo, &m, &v);
|
||
}
|
||
|
||
static void var_field(bparser *parser)
|
||
{
|
||
/* ID ['=' expr] */
|
||
bexpdesc e1, e2;
|
||
bstring *name;
|
||
name = next_token(parser).u.s;
|
||
match_token(parser, TokenId); /* match and skip ID */
|
||
if (match_skip(parser, OptAssign)) { /* '=' */
|
||
expr(parser, &e2);
|
||
check_var(parser, &e2);
|
||
} else {
|
||
init_exp(&e2, ETNIL, 0);
|
||
}
|
||
new_var(parser, name, &e1); /* new variable */
|
||
be_code_setvar(parser->finfo, &e1, &e2);
|
||
}
|
||
|
||
static void var_stmt(bparser *parser)
|
||
{
|
||
/* 'var' ID ['=' expr] {',' ID ['=' expr]} */
|
||
scan_next_token(parser); /* skip 'var' */
|
||
var_field(parser);
|
||
while (match_skip(parser, OptComma)) { /* ',' */
|
||
var_field(parser);
|
||
}
|
||
}
|
||
|
||
static int except_case_list(bparser *parser, int *base)
|
||
{
|
||
int idx;
|
||
bexpdesc e;
|
||
bfuncinfo *finfo = parser->finfo;
|
||
/* expr {',' expr} | '..' */
|
||
if (match_skip(parser, OptConnect)) { /* '..' */
|
||
*base = finfo->freereg;
|
||
return 0;
|
||
}
|
||
expr(parser, &e); /* first exception expression */
|
||
check_var(parser, &e);
|
||
*base = idx = be_code_nextreg(finfo, &e);
|
||
while (match_skip(parser, OptComma)) { /* ',' */
|
||
expr(parser, &e);
|
||
check_var(parser, &e);
|
||
idx = be_code_nextreg(finfo, &e);
|
||
}
|
||
idx = idx - *base + 1; /* count of exception expression */
|
||
be_code_freeregs(finfo, idx);
|
||
return idx;
|
||
}
|
||
|
||
static int except_var_list(bparser *parser, int base)
|
||
{
|
||
bexpdesc v;
|
||
(void)base; /* unused variable (no debugging) */
|
||
/* [as ID [, ID]] */
|
||
if (match_skip(parser, KeyAs)) { /* 'as' */
|
||
bstring *name = next_token(parser).u.s;
|
||
match_token(parser, TokenId); /* match and skip ID */
|
||
new_var(parser, name, &v); /* new local variable */
|
||
be_assert(v.type == ETLOCAL && v.v.idx == base);
|
||
if (match_skip(parser, OptComma)) { /* match and skip ',' */
|
||
name = next_token(parser).u.s;
|
||
match_token(parser, TokenId); /* match and skip ID */
|
||
new_var(parser, name, &v); /* new local variable */
|
||
be_assert(v.type == ETLOCAL && v.v.idx == base + 1);
|
||
return 2;
|
||
}
|
||
return 1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static void except_block(bparser *parser, int *jmp, int *jbrk)
|
||
{
|
||
int base = 0; /* the first register of the catch opcode */
|
||
int ecnt = 0; /* exception cases count */
|
||
int vcnt = 0; /* exception variable count */
|
||
bblockinfo binfo;
|
||
bfuncinfo *finfo = parser->finfo;
|
||
/* 'except' (expr {',' expr} | '..') ['as' ID [',' ID]] */
|
||
match_token(parser, KeyExcept); /* skip 'except' */
|
||
begin_block(finfo, &binfo, 0); /* begin catch block */
|
||
/* link from the previous except failure point */
|
||
be_code_conjump(finfo, jmp, finfo->pc);
|
||
/* (expr {',' expr} | '..') ['as' ID [',' ID]] */
|
||
ecnt = except_case_list(parser, &base);
|
||
vcnt = except_var_list(parser, base);
|
||
be_code_catch(finfo, base, ecnt, vcnt, jmp);
|
||
stmtlist(parser);
|
||
be_code_conjump(finfo, jbrk, be_code_jump(finfo));
|
||
end_block(parser); /* leave catch block */
|
||
}
|
||
|
||
static void try_stmt(bparser *parser)
|
||
{
|
||
int jcatch, jbrk;
|
||
/* 'try' block 'except' except_stmt block 'end' */
|
||
scan_next_token(parser); /* skip 'try' */
|
||
jcatch = be_code_exblk(parser->finfo, 0);
|
||
block(parser, BLOCK_EXCEPT);
|
||
be_code_exblk(parser->finfo, 1);
|
||
jbrk = be_code_jump(parser->finfo);
|
||
except_block(parser, &jcatch, &jbrk);
|
||
while (next_type(parser) == KeyExcept) {
|
||
except_block(parser, &jcatch, &jbrk);
|
||
}
|
||
be_code_patchjump(parser->finfo, jcatch);
|
||
be_code_raise(parser->finfo, NULL, NULL);
|
||
be_code_patchjump(parser->finfo, jbrk);
|
||
match_token(parser, KeyEnd); /* skip 'end' */
|
||
}
|
||
|
||
static void throw_stmt(bparser *parser)
|
||
{
|
||
bexpdesc e1, e2;
|
||
/* 'raise' expr */
|
||
scan_next_token(parser); /* skip 'raise' */
|
||
expr(parser, &e1);
|
||
check_var(parser, &e1);
|
||
if (match_skip(parser, OptComma)) {
|
||
expr(parser, &e2);
|
||
check_var(parser, &e2);
|
||
be_code_raise(parser->finfo, &e1, &e2);
|
||
} else {
|
||
be_code_raise(parser->finfo, &e1, NULL);
|
||
}
|
||
}
|
||
|
||
static void statement(bparser *parser)
|
||
{
|
||
switch (next_type(parser)) {
|
||
case KeyIf: if_stmt(parser); break;
|
||
case KeyWhile: while_stmt(parser); break;
|
||
case KeyFor: for_stmt(parser); break;
|
||
case KeyDo: do_stmt(parser); break;
|
||
case KeyBreak: break_stmt(parser); break;
|
||
case KeyContinue: continue_stmt(parser); break;
|
||
case KeyDef: def_stmt(parser); break;
|
||
case KeyClass: class_stmt(parser); break;
|
||
case KeyReturn: return_stmt(parser); break;
|
||
case KeyImport: import_stmt(parser); break;
|
||
case KeyVar: var_stmt(parser); break;
|
||
case KeyTry: try_stmt(parser); break;
|
||
case KeyRaise: throw_stmt(parser); break;
|
||
case OptSemic: scan_next_token(parser); break; /* empty statement */
|
||
default: expr_stmt(parser); break;
|
||
}
|
||
be_assert(parser->finfo->freereg >= be_list_count(parser->finfo->local));
|
||
}
|
||
|
||
static void stmtlist(bparser *parser)
|
||
{
|
||
while (block_follow(parser)) {
|
||
statement(parser);
|
||
}
|
||
}
|
||
|
||
static void block(bparser *parser, int type)
|
||
{
|
||
bblockinfo binfo;
|
||
begin_block(parser->finfo, &binfo, type);
|
||
stmtlist(parser);
|
||
end_block(parser);
|
||
}
|
||
|
||
static void mainfunc(bparser *parser, bclosure *cl)
|
||
{
|
||
bblockinfo binfo;
|
||
bfuncinfo finfo;
|
||
begin_func(parser, &finfo, &binfo);
|
||
finfo.proto->argc = 0; /* args */
|
||
finfo.proto->name = be_newstr(parser->vm, funcname(parser));
|
||
cl->proto = finfo.proto;
|
||
be_remove(parser->vm, -3); /* pop proto from stack */
|
||
stmtlist(parser);
|
||
end_func(parser);
|
||
match_token(parser, TokenEOS); /* skip EOS */
|
||
}
|
||
|
||
bclosure* be_parser_source(bvm *vm,
|
||
const char *fname, breader reader, void *data, bbool islocal)
|
||
{
|
||
bparser parser;
|
||
bclosure *cl = be_newclosure(vm, 0);
|
||
parser.vm = vm;
|
||
parser.finfo = NULL;
|
||
parser.cl = cl;
|
||
parser.islocal = (bbyte)islocal;
|
||
var_setclosure(vm->top, cl);
|
||
be_stackpush(vm);
|
||
be_lexer_init(&parser.lexer, vm, fname, reader, data);
|
||
scan_next_token(&parser); /* scan first token */
|
||
mainfunc(&parser, cl);
|
||
be_lexer_deinit(&parser.lexer);
|
||
be_global_release_space(vm); /* clear global space */
|
||
be_stackpop(vm, 2); /* pop strtab */
|
||
scan_next_token(&parser); /* clear lexer */
|
||
return cl;
|
||
}
|
||
|
||
#endif
|