1529 lines
45 KiB
C
1529 lines
45 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_debug.h"
|
|
#include "be_exec.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
|
|
|
|
/* 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 */
|
|
}
|
|
|
|
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));
|
|
}
|
|
}
|
|
|
|
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));
|
|
}
|
|
}
|
|
|
|
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->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
|
|
|
|
static void begin_block(bfuncinfo *finfo, bblockinfo *binfo, int type)
|
|
{
|
|
binfo->prev = finfo->binfo;
|
|
finfo->binfo = binfo;
|
|
binfo->type = (bbyte)type;
|
|
binfo->hasupval = 0;
|
|
binfo->beginpc = finfo->pc;
|
|
binfo->nactlocals = (bbyte)be_list_count(finfo->local);
|
|
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);
|
|
finfo->freereg = binfo->nactlocals;
|
|
finfo->binfo = binfo->prev;
|
|
}
|
|
|
|
static void end_block(bparser *parser)
|
|
{
|
|
end_block_ex(parser, -1);
|
|
}
|
|
|
|
static bstring* parser_source(bparser *parser)
|
|
{
|
|
if (parser->finfo) {
|
|
return parser->finfo->proto->source;
|
|
}
|
|
return be_newstr(parser->vm, parser->lexer.fname);
|
|
}
|
|
|
|
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));
|
|
proto->code = be_vector_data(&finfo->code);
|
|
proto->codesize = be_vector_capacity(&finfo->code);
|
|
be_vector_init(vm, &finfo->kvec, sizeof(bvalue));
|
|
proto->ktab = be_vector_data(&finfo->kvec);
|
|
proto->nconst = be_vector_capacity(&finfo->kvec);
|
|
be_vector_init(vm, &finfo->pvec, sizeof(bproto*));
|
|
proto->ptab = be_vector_data(&finfo->pvec);
|
|
proto->nproto = be_vector_capacity(&finfo->pvec);
|
|
proto->source = parser_source(parser);
|
|
finfo->local = be_list_new(vm);
|
|
var_setlist(vm->top, finfo->local);
|
|
be_stackpush(vm);
|
|
finfo->upval = be_map_new(vm);
|
|
var_setmap(vm->top, finfo->upval);
|
|
be_stackpush(vm);
|
|
finfo->prev = parser->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);
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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);
|
|
setupvals(finfo);
|
|
proto->code = be_vector_release(vm, &finfo->code);
|
|
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_DEBUG_RUNTIME_INFO
|
|
proto->lineinfo = be_vector_release(vm, &finfo->linevec);
|
|
proto->nlineinfo = be_vector_count(&finfo->linevec);
|
|
#endif
|
|
#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;
|
|
be_stackpop(vm, 2); /* pop upval and local */
|
|
}
|
|
|
|
static btokentype get_binop(bparser *parser)
|
|
{
|
|
btokentype op = next_type(parser);
|
|
if (op >= OptAdd && op <= OptOr) {
|
|
return op;
|
|
}
|
|
return OP_NOT_BINARY;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static btokentype get_assign_op(bparser *parser)
|
|
{
|
|
btokentype op = next_type(parser);
|
|
if (op >= OptAssign && op <= OptRsfAssign) {
|
|
return op;
|
|
}
|
|
return OP_NOT_ASSIGN;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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 */
|
|
}
|
|
|
|
static int new_localvar(bparser *parser, bstring *name)
|
|
{
|
|
bfuncinfo *finfo = parser->finfo;
|
|
int reg = find_localvar(finfo, name, finfo->binfo->nactlocals);
|
|
if (reg == -1) {
|
|
bvalue *var;
|
|
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);
|
|
}
|
|
return reg;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static void new_var(bparser *parser, bstring *name, bexpdesc *var)
|
|
{
|
|
bfuncinfo *finfo = parser->finfo;
|
|
if (finfo->prev || finfo->binfo->prev || parser->islocal) {
|
|
init_exp(var, ETLOCAL, 0);
|
|
var->v.idx = new_localvar(parser, name);
|
|
} 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));
|
|
}
|
|
}
|
|
}
|
|
|
|
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 if (be_global_find(vm, s) >= 0) {
|
|
return ETGLOBAL; /* global variable */
|
|
} else {
|
|
return ETVOID; /* unknow (new variable or error) */
|
|
}
|
|
}
|
|
init_exp(var, ETUPVAL, idx);
|
|
return ETUPVAL;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void singlevar(bparser *parser, bexpdesc *var)
|
|
{
|
|
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;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void func_varlist(bparser *parser)
|
|
{
|
|
bexpdesc v;
|
|
bstring *str;
|
|
/* '(' [ID {',' ID}] ')' */
|
|
match_token(parser, OptLBK); /* skip '(' */
|
|
if (match_id(parser, str) != NULL) {
|
|
new_var(parser, str, &v); /* new variable */
|
|
while (match_skip(parser, OptComma)) { /* ',' */
|
|
str = next_token(parser).u.s;
|
|
match_token(parser, TokenId); /* match and skip ID */
|
|
/* new local variable */
|
|
if (find_localvar(parser->finfo, str, 0) == -1) {
|
|
new_var(parser, str, &v);
|
|
} else {
|
|
push_error(parser, "redefinition of '%s'", str(str));
|
|
}
|
|
}
|
|
}
|
|
match_token(parser, OptRBK); /* skip ')' */
|
|
parser->finfo->proto->argc = parser->finfo->freereg;
|
|
}
|
|
|
|
static bproto* funcbody(bparser *parser, bstring *name, int type)
|
|
{
|
|
bfuncinfo finfo;
|
|
bblockinfo binfo;
|
|
|
|
/* '(' varlist ')' block 'end' */
|
|
begin_func(parser, &finfo, &binfo);
|
|
finfo.proto->name = name;
|
|
if (type & FUNC_METHOD) {
|
|
new_localvar(parser, parser_newstr(parser, "self"));
|
|
}
|
|
func_varlist(parser);
|
|
stmtlist(parser);
|
|
end_func(parser);
|
|
match_token(parser, KeyEnd); /* skip 'end' */
|
|
return finfo.proto;
|
|
}
|
|
|
|
/* anonymous function */
|
|
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, 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 */
|
|
if (find_localvar(parser->finfo, str, 0) == -1) {
|
|
new_var(parser, str, &v);
|
|
} else {
|
|
push_error(parser, "redefinition of '%s'", str(str));
|
|
}
|
|
}
|
|
}
|
|
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);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static void list_nextmember(bparser *parser, bexpdesc *l)
|
|
{
|
|
bexpdesc e, v = *l;
|
|
bfuncinfo *finfo = parser->finfo;
|
|
expr(parser, &e); /* value */
|
|
check_var(parser, &e);
|
|
be_code_binop(finfo, OptConnect, &v, &e);
|
|
be_code_freeregs(finfo, 1);
|
|
}
|
|
|
|
static void map_nextmember(bparser *parser, bexpdesc *l)
|
|
{
|
|
bexpdesc e, v = *l;
|
|
bfuncinfo *finfo = parser->finfo;
|
|
expr(parser, &e); /* key */
|
|
check_var(parser, &e);
|
|
be_code_index(finfo, &v, &e);
|
|
match_token(parser, OptColon); /* ':' */
|
|
expr(parser, &e); /* value */
|
|
check_var(parser, &e);
|
|
be_code_setvar(finfo, &v, &e);
|
|
}
|
|
|
|
static void list_expr(bparser *parser, bexpdesc *e)
|
|
{
|
|
/* '[' {expr ','} [expr] ']' */
|
|
new_primtype(parser, "list", e); /* new list */
|
|
while (next_type(parser) != OptRSB) {
|
|
list_nextmember(parser, e);
|
|
if (!match_skip(parser, OptComma)) { /* ',' */
|
|
break;
|
|
}
|
|
}
|
|
e->type = ETREG;
|
|
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 '}' */
|
|
}
|
|
|
|
static int exprlist(bparser *parser, bexpdesc *e)
|
|
{
|
|
bfuncinfo *finfo = parser->finfo;
|
|
int n = 1;
|
|
/* expr { ',' expr } */
|
|
expr(parser, e);
|
|
check_var(parser, e);
|
|
be_code_nextreg(finfo, e);
|
|
while (match_skip(parser, OptComma)) { /* ',' */
|
|
expr(parser, e);
|
|
check_var(parser, e);
|
|
be_code_nextreg(finfo, e);
|
|
++n;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
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);
|
|
}
|
|
scan_next_token(parser); /* skip '(' */
|
|
if (next_type(parser) != OptRBK) {
|
|
argc = exprlist(parser, &args);
|
|
}
|
|
match_token(parser, OptRBK); /* skip ')' */
|
|
argc += ismember;
|
|
be_code_call(finfo, base, argc);
|
|
if (e->type != ETREG) {
|
|
e->type = ETREG;
|
|
e->v.idx = base;
|
|
}
|
|
}
|
|
|
|
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 {
|
|
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;
|
|
}
|
|
}
|
|
|
|
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;
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void suffix_alloc_reg(bparser *parser, bexpdesc *l)
|
|
{
|
|
bfuncinfo *finfo = parser->finfo;
|
|
bbool suffix = l->type == ETINDEX || l->type == ETMEMBER;
|
|
/* in the suffix expression, if the object is a temporary
|
|
* variable (l->v.ss.tt == ETREG), it needs to be cached. */
|
|
if (suffix && l->v.ss.tt == ETREG) {
|
|
be_code_allocregs(finfo, 1);
|
|
}
|
|
}
|
|
|
|
/* compound assignment */
|
|
static void compound_assign(bparser *parser, int op, bexpdesc *l, bexpdesc *r)
|
|
{
|
|
if (op != OptAssign) { /* check left variable */
|
|
check_var(parser, l);
|
|
/* cache the register of the object when continuously assigning */
|
|
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); /* coding operation */
|
|
*r = e;
|
|
}
|
|
}
|
|
|
|
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);
|
|
return btrue;
|
|
}
|
|
return bfalse;
|
|
}
|
|
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;
|
|
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);
|
|
if (op != OP_NOT_UNARY) {
|
|
int line, res;
|
|
scan_next_token(parser);
|
|
line = parser->lexer.linenumber;
|
|
sub_expr(parser, e, UNARY_OP_PRIO);
|
|
check_var(parser, e);
|
|
res = be_code_unop(finfo, op, e);
|
|
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);
|
|
}
|
|
op = get_binop(parser);
|
|
while (op != OP_NOT_BINARY && prio > binary_op_prio(op)) {
|
|
bexpdesc e2;
|
|
check_var(parser, e);
|
|
scan_next_token(parser);
|
|
be_code_prebinop(finfo, op, e); /* and or */
|
|
init_exp(&e2, ETVOID, 0);
|
|
sub_expr(parser, &e2, binary_op_prio(op));
|
|
check_var(parser, &e2);
|
|
be_code_binop(finfo, op, e, &e2); /* encode binary op */
|
|
op = get_binop(parser);
|
|
}
|
|
if (prio == ASSIGN_OP_PRIO) {
|
|
cond_expr(parser, e);
|
|
}
|
|
}
|
|
|
|
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), 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_NIL) {
|
|
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_member_bind(parser->vm, c, name);
|
|
while (match_skip(parser, OptComma)) { /* ',' */
|
|
if (match_id(parser, name) != NULL) {
|
|
check_class_attr(parser, c, name);
|
|
be_member_bind(parser->vm, c, name);
|
|
} else {
|
|
parser_error(parser, "class var error");
|
|
}
|
|
}
|
|
} else {
|
|
parser_error(parser, "class var error");
|
|
}
|
|
}
|
|
|
|
static void classdef_stmt(bparser *parser, bclass *c)
|
|
{
|
|
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, FUNC_METHOD);
|
|
be_method_bind(parser->vm, c, proto->name, proto);
|
|
be_stackpop(parser->vm, 1);
|
|
}
|
|
|
|
static void class_inherit(bparser *parser, bexpdesc *e)
|
|
{
|
|
if (next_type(parser) == OptColon) { /* ':' */
|
|
bexpdesc e1;
|
|
scan_next_token(parser); /* skip ':' */
|
|
expr(parser, &e1);
|
|
check_var(parser, &e1);
|
|
be_code_setsuper(parser->finfo, e, &e1);
|
|
}
|
|
}
|
|
|
|
static void class_block(bparser *parser, bclass *c)
|
|
{
|
|
/* { [;] } */
|
|
while (block_follow(parser)) {
|
|
switch (next_type(parser)) {
|
|
case KeyVar: classvar_stmt(parser, c); break;
|
|
case KeyDef: classdef_stmt(parser, c); 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);
|
|
be_class_compress(parser->vm, c); /* compress class size */
|
|
match_token(parser, KeyEnd); /* skip 'end' */
|
|
} 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, 1);
|
|
scan_next_token(&parser); /* clear lexer */
|
|
return cl;
|
|
}
|
|
|
|
#endif
|