blob: b9b5afb66c449cefa6971f5a61cf023efcd3f707 [file] [log] [blame]
/*
* *****************************************************************************
*
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2018-2021 Gavin D. Howard and contributors.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* *****************************************************************************
*
* The parser for dc.
*
*/
#if DC_ENABLED
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <setjmp.h>
#include <dc.h>
#include <program.h>
#include <vm.h>
/**
* Parses a register. The lexer should have already lexed the true name of the
* register, per extended registers and such.
* @param p The parser.
* @param var True if the parser is for a variable, false otherwise.
*/
static void dc_parse_register(BcParse *p, bool var) {
bc_lex_next(&p->l);
if (p->l.t != BC_LEX_NAME) bc_parse_err(p, BC_ERR_PARSE_TOKEN);
bc_parse_pushName(p, p->l.str.v, var);
}
/**
* Parses a dc string.
* @param p The parser.
*/
static inline void dc_parse_string(BcParse *p) {
bc_parse_addString(p);
bc_lex_next(&p->l);
}
/**
* Parses a token that requires a memory operation, like load or store.
* @param p The parser.
* @param inst The instruction to push for the memory operation.
* @param name Whether the load or store is to a variable or array, and not to
* a global.
* @param store True if the operation is a store, false otherwise.
*/
static void dc_parse_mem(BcParse *p, uchar inst, bool name, bool store) {
// Push the instruction.
bc_parse_push(p, inst);
// Parse the register if necessary.
if (name) dc_parse_register(p, inst != BC_INST_ARRAY_ELEM);
// Stores use the bc assign infrastructure, but they need to do a swap
// first.
if (store) {
bc_parse_push(p, BC_INST_SWAP);
bc_parse_push(p, BC_INST_ASSIGN_NO_VAL);
}
bc_lex_next(&p->l);
}
/**
* Parses a conditional execution instruction.
* @param p The parser.
* @param inst The instruction for the condition.
*/
static void dc_parse_cond(BcParse *p, uchar inst) {
// Push the instruction for the condition and the conditional execution.
bc_parse_push(p, inst);
bc_parse_push(p, BC_INST_EXEC_COND);
// Parse the register.
dc_parse_register(p, true);
bc_lex_next(&p->l);
// If the next token is an else, parse the else.
if (p->l.t == BC_LEX_KW_ELSE) {
dc_parse_register(p, true);
bc_lex_next(&p->l);
}
// Otherwise, push a marker for no else.
else bc_parse_pushIndex(p, SIZE_MAX);
}
/**
* Parses a token for dc.
* @param p The parser.
* @param t The token to parse.
* @param flags The flags that say what is allowed or not.
*/
static void dc_parse_token(BcParse *p, BcLexType t, uint8_t flags) {
uchar inst;
bool assign, get_token = false;
switch (t) {
case BC_LEX_OP_REL_EQ:
case BC_LEX_OP_REL_LE:
case BC_LEX_OP_REL_GE:
case BC_LEX_OP_REL_NE:
case BC_LEX_OP_REL_LT:
case BC_LEX_OP_REL_GT:
{
inst = (uchar) (t - BC_LEX_OP_REL_EQ + BC_INST_REL_EQ);
dc_parse_cond(p, inst);
break;
}
case BC_LEX_SCOLON:
case BC_LEX_COLON:
{
dc_parse_mem(p, BC_INST_ARRAY_ELEM, true, t == BC_LEX_COLON);
break;
}
case BC_LEX_STR:
{
dc_parse_string(p);
break;
}
case BC_LEX_NEG:
{
// This tells us whether or not the neg is for a command or at the
// beginning of a number. If it's a command, push it. Otherwise,
// fallthrough and parse the number.
if (dc_lex_negCommand(&p->l)) {
bc_parse_push(p, BC_INST_NEG);
get_token = true;
break;
}
bc_lex_next(&p->l);
}
// Fallthrough.
BC_FALLTHROUGH
case BC_LEX_NUMBER:
{
bc_parse_number(p);
// Push the negative instruction if we fell through from above.
if (t == BC_LEX_NEG) bc_parse_push(p, BC_INST_NEG);
get_token = true;
break;
}
case BC_LEX_KW_READ:
{
// Make sure the read is not recursive.
if (BC_ERR(flags & BC_PARSE_NOREAD))
bc_parse_err(p, BC_ERR_EXEC_REC_READ);
else bc_parse_push(p, BC_INST_READ);
get_token = true;
break;
}
case BC_LEX_OP_ASSIGN:
case BC_LEX_STORE_PUSH:
{
assign = t == BC_LEX_OP_ASSIGN;
inst = assign ? BC_INST_VAR : BC_INST_PUSH_TO_VAR;
dc_parse_mem(p, inst, true, assign);
break;
}
case BC_LEX_LOAD:
case BC_LEX_LOAD_POP:
{
inst = t == BC_LEX_LOAD_POP ? BC_INST_PUSH_VAR : BC_INST_LOAD;
dc_parse_mem(p, inst, true, false);
break;
}
case BC_LEX_REG_STACK_LEVEL:
{
dc_parse_mem(p, BC_INST_REG_STACK_LEN, true, false);
break;
}
case BC_LEX_STORE_IBASE:
case BC_LEX_STORE_OBASE:
case BC_LEX_STORE_SCALE:
#if BC_ENABLE_EXTRA_MATH
case BC_LEX_STORE_SEED:
#endif // BC_ENABLE_EXTRA_MATH
{
inst = (uchar) (t - BC_LEX_STORE_IBASE + BC_INST_IBASE);
dc_parse_mem(p, inst, false, true);
break;
}
case BC_LEX_ARRAY_LENGTH:
{
// Need to push the array first, based on how length is implemented.
bc_parse_push(p, BC_INST_ARRAY);
dc_parse_register(p, false);
bc_parse_push(p, BC_INST_LENGTH);
get_token = true;
break;
}
default:
{
// All other tokens should be taken care of by the caller, or they
// actually *are* invalid.
bc_parse_err(p, BC_ERR_PARSE_TOKEN);
}
}
if (get_token) bc_lex_next(&p->l);
}
void dc_parse_expr(BcParse *p, uint8_t flags) {
BcInst inst;
BcLexType t;
bool need_expr, have_expr = false;
need_expr = ((flags & BC_PARSE_NOREAD) != 0);
// dc can just keep parsing forever basically, unlike bc, which has to have
// a whole bunch of complicated nonsense because its language was horribly
// designed.
// While we don't have EOF...
while ((t = p->l.t) != BC_LEX_EOF) {
// Eat newline.
if (t == BC_LEX_NLINE) {
bc_lex_next(&p->l);
continue;
}
// Get the instruction that corresponds to the token.
inst = dc_parse_insts[t];
// If the instruction is invalid, that means we have to do some harder
// parsing. So if not invalid, just push the instruction; otherwise,
// parse the token.
if (inst != BC_INST_INVALID) {
bc_parse_push(p, inst);
bc_lex_next(&p->l);
}
else dc_parse_token(p, t, flags);
have_expr = true;
}
// If we don't have an expression and need one, barf. Otherwise, just push a
// BC_INST_POP_EXEC if we have EOF and BC_PARSE_NOCALL, which dc uses to
// indicate that it is executing a string.
if (BC_ERR(need_expr && !have_expr)) bc_err(BC_ERR_EXEC_READ_EXPR);
else if (p->l.t == BC_LEX_EOF && (flags & BC_PARSE_NOCALL))
bc_parse_push(p, BC_INST_POP_EXEC);
}
void dc_parse_parse(BcParse *p) {
assert(p != NULL);
BC_SETJMP(exit);
// If we have EOF, someone called this function one too many times.
// Otherwise, parse.
if (BC_ERR(p->l.t == BC_LEX_EOF)) bc_parse_err(p, BC_ERR_PARSE_EOF);
else dc_parse_expr(p, 0);
exit:
BC_SIG_MAYLOCK;
// Need to reset if there was an error.
if (BC_SIG_EXC) bc_parse_reset(p);
BC_LONGJMP_CONT;
}
#endif // DC_ENABLED