529 lines
12 KiB
C
529 lines
12 KiB
C
/*
|
|
|
|
ugl_file.c
|
|
|
|
microcontroller game language
|
|
|
|
bytecode
|
|
return from procedure... all procedure leave a 16 bit value on the stack
|
|
jump probably only for if/loop
|
|
pop stack and jump on zero
|
|
pop stack and jump on not zero
|
|
call buildin procedure
|
|
call bytecode procedure
|
|
call toplevel buildin procedure
|
|
call toplevel bytecode procedure
|
|
push constant 16 bit value on stack
|
|
pop constant value from stack
|
|
|
|
|
|
|
|
proc <name> Starts a new procedure. Procedures must be terminated with "endproc"
|
|
endproc End a procedure
|
|
locals <num> Define the specified number of local variables, acces with arg(n), where n is between no_of_args+1 and no_of_args+<num>
|
|
*/
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include "ugl.h"
|
|
#include "ugl_bc.h"
|
|
|
|
|
|
long ugl_current_local_variables = 0;
|
|
long ugl_current_args = 0;
|
|
|
|
|
|
#define UGL_MAX_INDENT 64
|
|
|
|
#define UGL_INDENT_TYPE_PROC 0
|
|
#define UGL_INDENT_TYPE_IF 1
|
|
#define UGL_INDENT_TYPE_LOOP 2
|
|
|
|
int ugl_indent_level;
|
|
int ugl_indent_type[UGL_MAX_INDENT];
|
|
char ugl_indent_identifier[UGL_MAX_INDENT][UGL_MAX_IDENTIFIER_LEN+1];
|
|
|
|
struct ugl_buildin_cmd_struct
|
|
{
|
|
int code;
|
|
const char *name;
|
|
int args;
|
|
};
|
|
|
|
struct ugl_buildin_cmd_struct ugl_buildin_cmd_list[] = {
|
|
{ /* code=*/ 0, /* name=*/ "nop", /* args=*/ 0},
|
|
{ /* code=*/ 1, /* name=*/ "return", /* args=*/ 1 }, /* assign the return value of a user defined function */
|
|
{ /* code=*/ 2, /* name=*/ "a", /* args=*/ 1 }, /* return the value of the n-th argument of a user defined function */
|
|
{ /* code=*/ 3, /* name=*/ "a", /* args=*/ 2 }, /* reassign the value of the n-th argument of a user defined function */
|
|
{ /* code=*/ 4, /* name=*/ "add", /* args=*/ 2 },
|
|
{ /* code=*/ 5, /* name=*/ "print", /* args=*/ 1 },
|
|
{ /* code=*/ 6, /* name=*/ "setPos", /* args=*/ 2 },
|
|
{ /* code=*/ 7, /* name=*/ "setItemPos", /* args=*/ 1 },
|
|
};
|
|
|
|
|
|
|
|
void ugl_IncIndent(int type)
|
|
{
|
|
if ( ugl_indent_level > UGL_MAX_INDENT )
|
|
ugl_err("max indent level reached");
|
|
ugl_indent_type[ugl_indent_level] = type;
|
|
ugl_indent_level++;
|
|
}
|
|
|
|
void ugl_DecIndent(int type)
|
|
{
|
|
if ( ugl_indent_level == 0 )
|
|
ugl_err("can not decrement indent level, requested type = %d", type);
|
|
ugl_indent_level--;
|
|
if (ugl_indent_type[ugl_indent_level] != type )
|
|
ugl_err("block end mismatch, %d != %d", ugl_indent_type[ugl_indent_level] , type );
|
|
}
|
|
|
|
static void skip_space(const char **s)
|
|
{
|
|
for(;;)
|
|
{
|
|
if ( **s == '#' )
|
|
{
|
|
while( **s != '\0' )
|
|
(*s)++;
|
|
break;
|
|
}
|
|
if ( **s == '\0' )
|
|
break;
|
|
if ( **s > ' ' )
|
|
break;
|
|
(*s)++;
|
|
}
|
|
}
|
|
|
|
static long get_dec(const char **s)
|
|
{
|
|
long v = 0;
|
|
for(;;)
|
|
{
|
|
if ( (**s) >= '0' && (**s) <= '9' )
|
|
{
|
|
v*=10;
|
|
v+= (**s)-'0';
|
|
(*s)++;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
skip_space(s);
|
|
return v;
|
|
}
|
|
|
|
static long get_hex(const char **s)
|
|
{
|
|
long v = 0;
|
|
for(;;)
|
|
{
|
|
if ( (**s) >= '0' && (**s) <= '9' )
|
|
{
|
|
v*=16;
|
|
v+= (**s)-'0';
|
|
(*s)++;
|
|
}
|
|
else if ( (**s) >= 'a' && (**s) <= 'f' )
|
|
{
|
|
v*=16;
|
|
v+= (**s)-'a'+10;
|
|
(*s)++;
|
|
}
|
|
else if ( (**s) >= 'A' && (**s) <= 'F' )
|
|
{
|
|
v*=16;
|
|
v+= (**s)-'A'+10;
|
|
(*s)++;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
skip_space(s);
|
|
return v;
|
|
}
|
|
|
|
static long get_ascii(const char **s)
|
|
{
|
|
long v = 0;
|
|
v = **s;
|
|
(*s)++;
|
|
skip_space(s);
|
|
return v;
|
|
}
|
|
|
|
|
|
static long get_num(const char **s)
|
|
{
|
|
if ( (**s) == '$' )
|
|
{
|
|
(*s)++;
|
|
return get_hex(s);
|
|
}
|
|
if ( (**s) == '\'' )
|
|
{
|
|
(*s)++;
|
|
return get_ascii(s);
|
|
}
|
|
|
|
return get_dec(s);
|
|
}
|
|
|
|
static const char *get_identifier(const char **s)
|
|
{
|
|
static char buf[UGL_MAX_IDENTIFIER_LEN+1];
|
|
int c;
|
|
int i = 0;
|
|
buf[0] = '\0';
|
|
for(;;)
|
|
{
|
|
c = **s;
|
|
if ( c < 'A' )
|
|
break;
|
|
if ( i > UGL_MAX_IDENTIFIER_LEN )
|
|
break;
|
|
buf[i++] = c;
|
|
buf[i] = '\0';
|
|
(*s)++;
|
|
}
|
|
skip_space(s);
|
|
return buf;
|
|
}
|
|
|
|
/*======================================================*/
|
|
/* code generator sub procedures */
|
|
|
|
void ugl_bytecode_buildin_procedure(const char *name, int idx, int is_toplevel)
|
|
{
|
|
ugl_plog("BC %scall buildin '%s'", is_toplevel?"toplevel ":"", name);
|
|
|
|
if ( is_toplevel == 0 )
|
|
{
|
|
ugl_AddBytecode(BC_CMD_CALL_BUILDIN | ((idx&0x0f00)>>4));
|
|
ugl_AddBytecode(idx&0xff);
|
|
}
|
|
else
|
|
{
|
|
ugl_AddBytecode(BC_CMD_CALL_BUILDIN_POP_STACK | ((idx&0x0f00)>>4));
|
|
ugl_AddBytecode(idx&0xff);
|
|
}
|
|
}
|
|
|
|
void ugl_bytecode_call_procedure(const char *name, int is_toplevel, int arg_cnt)
|
|
{
|
|
uint16_t idx;
|
|
|
|
ugl_plog("BC call %sbytecode '%s' with %d arg(s)", is_toplevel?"toplevel ":"", name, arg_cnt);
|
|
idx = ugl_GetLabel(name);
|
|
|
|
ugl_AddBytecode(BC_CMD_CALL_PROCEDURE | (arg_cnt<<4));
|
|
|
|
ugl_AddBytecode(idx>>8);
|
|
ugl_AddBytecode(idx&0x0ff);
|
|
|
|
if ( is_toplevel != 0 )
|
|
{
|
|
ugl_AddBytecode(BC_CMD_POP_ARG_STACK ); // remove the return value from the stack
|
|
}
|
|
|
|
#ifdef NOTUSED
|
|
if ( is_toplevel != 0 )
|
|
{
|
|
arg_cnt++; // one more element on the stack (return value) has to be removed, if this is called from top level
|
|
}
|
|
|
|
while( arg_cnt > 16 )
|
|
{
|
|
ugl_AddBytecode(BC_CMD_POP_ARG_STACK | 0x0f0);
|
|
arg_cnt -= 16;
|
|
}
|
|
if ( arg_cnt > 0 )
|
|
{
|
|
arg_cnt--;
|
|
ugl_AddBytecode(BC_CMD_POP_ARG_STACK | (arg_cnt<<4));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void ugl_bytecode_reserve_arg_stack(int arg_cnt)
|
|
{
|
|
while( arg_cnt > 16 )
|
|
{
|
|
ugl_AddBytecode(BC_CMD_PUSH_ARG_STACK | 0x0f0);
|
|
arg_cnt -= 16;
|
|
}
|
|
if ( arg_cnt > 0 )
|
|
{
|
|
arg_cnt--;
|
|
ugl_AddBytecode(BC_CMD_PUSH_ARG_STACK | (arg_cnt<<4));
|
|
}
|
|
}
|
|
|
|
void ugl_bytecode_remove_arg_stack(int arg_cnt)
|
|
{
|
|
while( arg_cnt > 16 )
|
|
{
|
|
ugl_AddBytecode(BC_CMD_POP_ARG_STACK | 0x0f0);
|
|
arg_cnt -= 16;
|
|
}
|
|
if ( arg_cnt > 0 )
|
|
{
|
|
arg_cnt--;
|
|
ugl_AddBytecode(BC_CMD_POP_ARG_STACK | (arg_cnt<<4));
|
|
}
|
|
}
|
|
|
|
void ugl_bytecode_constant_value(long num)
|
|
{
|
|
ugl_plog("BC constant value %ld", num);
|
|
|
|
if ( num == 0 )
|
|
{
|
|
ugl_AddBytecode(BC_CMD_LOAD_0);
|
|
}
|
|
else if ( num == 1 )
|
|
{
|
|
ugl_AddBytecode(BC_CMD_LOAD_1);
|
|
}
|
|
else if ( num <= 0x0fff )
|
|
{
|
|
ugl_AddBytecode(BC_CMD_LOAD_12BIT | ((num&0x0f00)>>4));
|
|
ugl_AddBytecode(num&0xff);
|
|
}
|
|
else
|
|
{
|
|
ugl_AddBytecode(BC_CMD_LOAD_16BIT );
|
|
ugl_AddBytecode(num>>8);
|
|
ugl_AddBytecode(num&0x0ff);
|
|
}
|
|
}
|
|
|
|
void ugl_bytecode_return_from_procedure(void)
|
|
{
|
|
ugl_plog("BC return from procedure");
|
|
ugl_AddBytecode(BC_CMD_RETURN_FROM_PROCEDURE );
|
|
}
|
|
|
|
void ugl_bytecode_branch(const char *s)
|
|
{
|
|
int idx;
|
|
idx = ugl_GetLabel(s);
|
|
ugl_plog("BC branch, label %s, idx=%d", s, idx);
|
|
|
|
ugl_AddBytecode(BC_CMD_BRANCH | ((idx&0x0f00)>>4));
|
|
ugl_AddBytecode(idx&0xff);
|
|
|
|
|
|
}
|
|
|
|
void ugl_bytecode_jmp_no_zero(const char *s)
|
|
{
|
|
int idx;
|
|
idx = ugl_GetLabel(s);
|
|
ugl_plog("BC jump on not zero, label idx=%d", idx);
|
|
|
|
ugl_AddBytecode(BC_CMD_JUMP_NOT_ZERO );
|
|
ugl_AddBytecode(idx>>8);
|
|
ugl_AddBytecode(idx&0x0ff);
|
|
}
|
|
|
|
void ugl_bytecode_jmp_zero(const char *s)
|
|
{
|
|
int idx;
|
|
idx = ugl_GetLabel(s);
|
|
ugl_plog("BC jump on zero, label idx=%d", idx);
|
|
|
|
ugl_AddBytecode(BC_CMD_JUMP_ZERO );
|
|
ugl_AddBytecode(idx>>8);
|
|
ugl_AddBytecode(idx&0x0ff);
|
|
}
|
|
|
|
|
|
/*======================================================*/
|
|
|
|
int ugl_is_buildin_cmd(const char *name)
|
|
{
|
|
int i, cnt;
|
|
cnt = sizeof(ugl_buildin_cmd_list)/sizeof(*ugl_buildin_cmd_list);
|
|
for( i = 0; i < cnt; i++ )
|
|
{
|
|
if ( strcmp(ugl_buildin_cmd_list[i].name, name) == 0 )
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void ugl_call_proc(const char *name, int is_toplevel, int arg_cnt)
|
|
{
|
|
int i, cnt;
|
|
int ii;
|
|
cnt = sizeof(ugl_buildin_cmd_list)/sizeof(*ugl_buildin_cmd_list);
|
|
ii = cnt;
|
|
for( i = 0; i < cnt; i++ )
|
|
{
|
|
if ( strcmp(ugl_buildin_cmd_list[i].name, name) == 0 )
|
|
ii = i;
|
|
if ( strcmp(ugl_buildin_cmd_list[i].name, name) == 0 && ugl_buildin_cmd_list[i].args == arg_cnt)
|
|
break;
|
|
}
|
|
if ( i < cnt )
|
|
{
|
|
ugl_bytecode_buildin_procedure(name, i, is_toplevel);
|
|
}
|
|
else
|
|
{
|
|
if ( ii != cnt )
|
|
{
|
|
ugl_err("Buildin procedure '%s' expects differnt number of args", name);
|
|
}
|
|
else
|
|
{
|
|
ugl_bytecode_call_procedure(name, is_toplevel, arg_cnt);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ugl_parse_proc(const char **s, const char *id, int is_toplevel)
|
|
{
|
|
char procname[UGL_MAX_IDENTIFIER_LEN];
|
|
int arg_cnt = 0;
|
|
ugl_plog("parse procedure '%s'", id);
|
|
strcpy(procname, id);
|
|
if ( ugl_is_buildin_cmd(id) == 0 )
|
|
{
|
|
/* if this is a buildin cmd, then do nothing: buildin code takes care on the return value */
|
|
/* for custom procedures push a value on the arg stack for the return value, but do this only if this is not a toplevel procedure */
|
|
ugl_bytecode_constant_value(0); /* return value will be 0 by default */
|
|
}
|
|
|
|
if ( **s == '(' )
|
|
{
|
|
const char *name;
|
|
for(;;)
|
|
{
|
|
(*s)++;
|
|
skip_space(s);
|
|
|
|
if ( **s == ')' )
|
|
break;
|
|
|
|
if ( (**s >= '0' && **s <= '9') || **s == '$' || **s == '\'' )
|
|
{
|
|
ugl_bytecode_constant_value(get_num(s));
|
|
}
|
|
else
|
|
{
|
|
name = get_identifier(s);
|
|
ugl_parse_proc(s, name, 0);
|
|
}
|
|
arg_cnt++;
|
|
if ( **s != ',' )
|
|
break;
|
|
}
|
|
if ( **s != ')' )
|
|
ugl_err("missing ')'");
|
|
(*s)++;
|
|
skip_space(s);
|
|
ugl_call_proc(procname, is_toplevel, arg_cnt);
|
|
}
|
|
else
|
|
{
|
|
ugl_call_proc(procname, is_toplevel, 0);
|
|
}
|
|
}
|
|
|
|
uint16_t uglStartNamelessProc(int args)
|
|
{
|
|
ugl_current_local_variables = 0;
|
|
ugl_current_args = args;
|
|
if ( ugl_indent_level != 0 )
|
|
ugl_err("nested procedures not allowed");
|
|
ugl_IncIndent(UGL_INDENT_TYPE_PROC);
|
|
return ugl_bytecode_len;
|
|
}
|
|
|
|
int ugl_read_line(const char **s)
|
|
{
|
|
const char *id;
|
|
|
|
skip_space(s);
|
|
if ( **s == '\0' )
|
|
return 1;
|
|
|
|
|
|
id = get_identifier(s);
|
|
|
|
if ( strcmp(id, "proc") == 0 )
|
|
{
|
|
const char *name = get_identifier(s);
|
|
long args = get_num(s);
|
|
|
|
ugl_plog("start procedure '%s' (args=%ld)", name, args);
|
|
|
|
ugl_GetLabel(name); /* create a label for the procedure name */
|
|
ugl_SetLabelBytecodePos(name, uglStartNamelessProc(args)); /* and set the label for it */
|
|
|
|
|
|
}
|
|
else if ( strcmp(id, "endproc") == 0 )
|
|
{
|
|
//ugl_bytecode_remove_arg_stack(ugl_current_local_variables);
|
|
ugl_DecIndent(UGL_INDENT_TYPE_PROC);
|
|
ugl_bytecode_return_from_procedure();
|
|
ugl_current_local_variables = 0;
|
|
ugl_current_args = 0;
|
|
}
|
|
else if ( strcmp(id, "locals") == 0 )
|
|
{
|
|
long n = get_num(s);
|
|
if ( ugl_current_local_variables != 0 )
|
|
ugl_err("only one 'locals' command allowed");
|
|
if ( ugl_indent_level != 1 )
|
|
ugl_err("'locals': only toplevel call allowed"); /* it must not be inside loops or ifs */
|
|
ugl_current_local_variables = n;
|
|
ugl_bytecode_reserve_arg_stack(ugl_current_local_variables);
|
|
}
|
|
else if ( strcmp(id, "if" ) == 0 )
|
|
{
|
|
ugl_parse_proc(s, get_identifier(s), 0);
|
|
sprintf(ugl_indent_identifier[ugl_indent_level], ".if%d", ugl_bytecode_len); /* use the current bytecode position as unique identifier */
|
|
ugl_bytecode_jmp_zero(ugl_indent_identifier[ugl_indent_level]);
|
|
ugl_IncIndent(UGL_INDENT_TYPE_IF);
|
|
|
|
}
|
|
else if ( strcmp(id, "else" ) == 0 )
|
|
{
|
|
char s[UGL_MAX_IDENTIFIER_LEN+1];
|
|
sprintf(s, ".else%d", ugl_bytecode_len); /* use the current bytecode position as unique identifier */
|
|
ugl_bytecode_branch(s);
|
|
|
|
ugl_SetLabelBytecodePos(ugl_indent_identifier[ugl_indent_level-1], ugl_bytecode_len);
|
|
strcpy(ugl_indent_identifier[ugl_indent_level-1], s);
|
|
}
|
|
else if ( strcmp(id, "endif" ) == 0 )
|
|
{
|
|
ugl_DecIndent(UGL_INDENT_TYPE_IF);
|
|
ugl_SetLabelBytecodePos(ugl_indent_identifier[ugl_indent_level], ugl_bytecode_len);
|
|
}
|
|
else
|
|
{
|
|
ugl_parse_proc(s, id, 1);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* returns 0 if "endproc" is found */
|
|
int uglReadLine(const char **s)
|
|
{
|
|
ugl_read_line(s);
|
|
if ( ugl_indent_level == 0 )
|
|
return 0;
|
|
return 1;
|
|
}
|