133 lines
5.2 KiB
C
133 lines
5.2 KiB
C
|
|
/**
|
||
|
|
* @file runtime_machine.c
|
||
|
|
* @brief The group J verbs: FETCH, STASH and SYS.
|
||
|
|
*
|
||
|
|
* Machine-level verbs on a machine that is not a Commodore 128. `POKE`, `PEEK`
|
||
|
|
* and `POINTER` already treat a BASIC integer as a real address in this
|
||
|
|
* process's memory -- that decision was made when they were ported -- and these
|
||
|
|
* follow it, because a `FETCH` that meant something different from the `PEEK`
|
||
|
|
* beside it would be worse than either answer on its own.
|
||
|
|
*
|
||
|
|
* `SYS` does not follow it, and the difference is the point: reading and writing
|
||
|
|
* a byte is something this process can do, and jumping to one is not.
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include <inttypes.h>
|
||
|
|
#include <stdio.h>
|
||
|
|
#include <string.h>
|
||
|
|
|
||
|
|
#include <akerror.h>
|
||
|
|
|
||
|
|
#include <akbasic/args.h>
|
||
|
|
#include <akbasic/error.h>
|
||
|
|
#include <akbasic/runtime.h>
|
||
|
|
|
||
|
|
#include "verbs.h"
|
||
|
|
|
||
|
|
/* Most verbs answer "did something happen"; this is that answer. */
|
||
|
|
#define SUCCEED_TRUE(__obj, __dest) \
|
||
|
|
do { \
|
||
|
|
*(__dest) = &(__obj)->staticTrueValue; \
|
||
|
|
} while ( 0 )
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @brief The copy behind both FETCH and STASH.
|
||
|
|
*
|
||
|
|
* On a C128 the two differ by which side is expansion RAM: `STASH` writes out to
|
||
|
|
* the RAM Expansion Unit and `FETCH` reads back from it. There is no expansion
|
||
|
|
* unit here and no banks, so both are the same byte copy and the only honest
|
||
|
|
* thing to do is say so rather than invent a distinction.
|
||
|
|
*
|
||
|
|
* @param obj Runtime to evaluate against.
|
||
|
|
* @param expr The command leaf.
|
||
|
|
* @param verb Name to use in a diagnostic.
|
||
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||
|
|
*/
|
||
|
|
static akerr_ErrorContext *copy_bytes(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, const char *verb)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
double args[4];
|
||
|
|
int count = 0;
|
||
|
|
int64_t length = 0;
|
||
|
|
|
||
|
|
PASS(errctx, akbasic_args_numbers(obj, expr, verb, args, 4, &count));
|
||
|
|
FAIL_ZERO_RETURN(errctx, (count >= 3), AKBASIC_ERR_SYNTAX,
|
||
|
|
"Expected %s (count), (source address), (destination address)", verb);
|
||
|
|
length = (int64_t)args[0];
|
||
|
|
FAIL_ZERO_RETURN(errctx, (length >= 0), AKBASIC_ERR_VALUE,
|
||
|
|
"%s cannot copy a negative number of bytes", verb);
|
||
|
|
FAIL_ZERO_RETURN(errctx, (args[1] != 0.0 && args[2] != 0.0), AKBASIC_ERR_VALUE,
|
||
|
|
"%s will not touch address zero", verb);
|
||
|
|
if ( length == 0 ) {
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
/*
|
||
|
|
* memmove rather than memcpy: a program shifting a buffer along by a few
|
||
|
|
* bytes overlaps its own source, and that is a normal thing to ask for.
|
||
|
|
*
|
||
|
|
* There is no bounds check, and there cannot be one -- the same is true of
|
||
|
|
* POKE and PEEK. A BASIC integer here is a real address, so a wrong one is a
|
||
|
|
* segmentation fault rather than an error message. See TODO.md section 5.
|
||
|
|
*/
|
||
|
|
memmove((void *)(uintptr_t)args[2], (const void *)(uintptr_t)args[1], (size_t)length);
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext *akbasic_cmd_fetch(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
(void)lval; (void)rval;
|
||
|
|
FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER,
|
||
|
|
"NULL argument in FETCH");
|
||
|
|
PASS(errctx, copy_bytes(obj, expr, "FETCH"));
|
||
|
|
SUCCEED_TRUE(obj, dest);
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext *akbasic_cmd_stash(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
(void)lval; (void)rval;
|
||
|
|
FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER,
|
||
|
|
"NULL argument in STASH");
|
||
|
|
PASS(errctx, copy_bytes(obj, expr, "STASH"));
|
||
|
|
SUCCEED_TRUE(obj, dest);
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext *akbasic_cmd_sys(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
double args[4];
|
||
|
|
int count = 0;
|
||
|
|
|
||
|
|
(void)lval; (void)rval;
|
||
|
|
FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER,
|
||
|
|
"NULL argument in SYS");
|
||
|
|
/*
|
||
|
|
* Parsed, then refused. Parsing first is deliberate: a program is told that
|
||
|
|
* SYS is not implemented rather than that its arguments are wrong, and a
|
||
|
|
* listing containing SYS still LISTs and still RENUMBERs.
|
||
|
|
*/
|
||
|
|
PASS(errctx, akbasic_args_numbers(obj, expr, "SYS", args, 4, &count));
|
||
|
|
FAIL_ZERO_RETURN(errctx, (count >= 1), AKBASIC_ERR_SYNTAX, "Expected SYS (address)");
|
||
|
|
|
||
|
|
/*
|
||
|
|
* There is no 6502. SYS calls machine code at an address, and the machine
|
||
|
|
* code a C128 program would call is ROM that does not exist here -- so there
|
||
|
|
* is nothing to jump to and nothing sensible to emulate. Jumping to a real
|
||
|
|
* address in this process would be a way to crash it on purpose.
|
||
|
|
*
|
||
|
|
* Refused by name rather than ignored, because a program whose SYS silently
|
||
|
|
* did nothing would carry on as though it had worked. This is the same
|
||
|
|
* reasoning BANK, FAST and MONITOR are out of scope on, with one difference:
|
||
|
|
* those are refused at the table and this one has a handler, because SYS is
|
||
|
|
* common enough in published listings to be worth a specific message.
|
||
|
|
*/
|
||
|
|
FAIL_RETURN(errctx, AKBASIC_ERR_DEVICE,
|
||
|
|
"SYS %d: there is no 6502 here, and no ROM to call. Machine code cannot be run by this interpreter",
|
||
|
|
(int)args[0]);
|
||
|
|
}
|