122 lines
4.8 KiB
C
122 lines
4.8 KiB
C
|
|
/**
|
||
|
|
* @file runtime_format.c
|
||
|
|
* @brief The group D verbs: PRINT USING, PUDEF, WIDTH and CHAR.
|
||
|
|
*
|
||
|
|
* Formatting and text placement. The field rendering itself lives in
|
||
|
|
* src/format.c, which is a pure function of a format string and a value and is
|
||
|
|
* tested as one; what is here is the verbs that reach it.
|
||
|
|
*/
|
||
|
|
|
||
|
|
#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 )
|
||
|
|
|
||
|
|
/* ------------------------------------------------------------------ PUDEF -- */
|
||
|
|
|
||
|
|
akerr_ErrorContext *akbasic_cmd_pudef(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
akbasic_Value *value = NULL;
|
||
|
|
|
||
|
|
(void)lval; (void)rval;
|
||
|
|
FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER,
|
||
|
|
"NULL argument in PUDEF");
|
||
|
|
FAIL_ZERO_RETURN(errctx, (expr != NULL && expr->right != NULL), AKBASIC_ERR_SYNTAX,
|
||
|
|
"Expected PUDEF \"characters\"");
|
||
|
|
PASS(errctx, akbasic_runtime_evaluate(obj, expr->right, &value));
|
||
|
|
FAIL_NONZERO_RETURN(errctx, (value->valuetype != AKBASIC_TYPE_STRING), AKBASIC_ERR_TYPE,
|
||
|
|
"PUDEF expected a string");
|
||
|
|
/*
|
||
|
|
* No device needed. The fill characters are the program's state, the same
|
||
|
|
* way COLOR's source bindings are, and a runtime with no output device is
|
||
|
|
* still entitled to remember what it was told.
|
||
|
|
*/
|
||
|
|
PASS(errctx, akbasic_format_pudef(&obj->format_state, value->stringval));
|
||
|
|
SUCCEED_TRUE(obj, dest);
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ------------------------------------------------------------------ WIDTH -- */
|
||
|
|
|
||
|
|
akerr_ErrorContext *akbasic_cmd_width(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
double args[1];
|
||
|
|
int count = 0;
|
||
|
|
|
||
|
|
(void)lval; (void)rval;
|
||
|
|
FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER,
|
||
|
|
"NULL argument in WIDTH");
|
||
|
|
PASS(errctx, akbasic_args_numbers(obj, expr, "WIDTH", args, 1, &count));
|
||
|
|
FAIL_ZERO_RETURN(errctx, (count == 1), AKBASIC_ERR_SYNTAX, "Expected WIDTH (1 or 2)");
|
||
|
|
/*
|
||
|
|
* BASIC 7.0's WIDTH sets the thickness of the lines the *graphics* verbs
|
||
|
|
* draw, not a text column count -- a common confusion, and the reason this
|
||
|
|
* verb lives with the drawing state rather than with PRINT.
|
||
|
|
*/
|
||
|
|
FAIL_ZERO_RETURN(errctx, (args[0] == 1.0 || args[0] == 2.0), AKBASIC_ERR_BOUNDS,
|
||
|
|
"WIDTH is 1 or 2, not %d", (int)args[0]);
|
||
|
|
obj->gfx.linewidth = (int)args[0];
|
||
|
|
SUCCEED_TRUE(obj, dest);
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ------------------------------------------------------------------- CHAR -- */
|
||
|
|
|
||
|
|
akerr_ErrorContext *akbasic_cmd_char(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
akbasic_ASTLeaf *arg = NULL;
|
||
|
|
akbasic_Value *value = NULL;
|
||
|
|
char text[AKBASIC_MAX_STRING_LENGTH];
|
||
|
|
double coords[3];
|
||
|
|
int count = 0;
|
||
|
|
|
||
|
|
(void)lval; (void)rval;
|
||
|
|
FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER,
|
||
|
|
"NULL argument in CHAR");
|
||
|
|
FAIL_ZERO_RETURN(errctx, (obj->sink != NULL), AKBASIC_ERR_DEVICE,
|
||
|
|
"CHAR needs a text device and this runtime has none");
|
||
|
|
FAIL_ZERO_RETURN(errctx, (obj->sink->moveto != NULL), AKBASIC_ERR_DEVICE,
|
||
|
|
"CHAR needs a text device that can position its cursor, and this one cannot");
|
||
|
|
|
||
|
|
/*
|
||
|
|
* CHAR colour, column, row, "text". The colour argument is accepted and
|
||
|
|
* ignored: this interpreter's text sink draws in one colour, chosen by the
|
||
|
|
* host when it built the sink, and a per-call colour would have to become
|
||
|
|
* part of the sink interface. Recorded in TODO.md section 5.
|
||
|
|
*/
|
||
|
|
arg = akbasic_leaf_first_argument(expr);
|
||
|
|
for ( count = 0; count < 3 && arg != NULL; count++, arg = arg->next ) {
|
||
|
|
PASS(errctx, akbasic_runtime_evaluate(obj, arg, &value));
|
||
|
|
FAIL_NONZERO_RETURN(errctx, (value->valuetype == AKBASIC_TYPE_STRING), AKBASIC_ERR_TYPE,
|
||
|
|
"CHAR expected a number in argument %d", count + 1);
|
||
|
|
coords[count] = (value->valuetype == AKBASIC_TYPE_FLOAT)
|
||
|
|
? value->floatval : (double)value->intval;
|
||
|
|
}
|
||
|
|
FAIL_ZERO_RETURN(errctx, (count == 3 && arg != NULL), AKBASIC_ERR_SYNTAX,
|
||
|
|
"Expected CHAR (colour), (column), (row), (string)");
|
||
|
|
|
||
|
|
PASS(errctx, akbasic_runtime_evaluate(obj, arg, &value));
|
||
|
|
PASS(errctx, akbasic_value_to_string(value, text, sizeof(text)));
|
||
|
|
|
||
|
|
PASS(errctx, obj->sink->moveto(obj->sink, (int)coords[1], (int)coords[2]));
|
||
|
|
PASS(errctx, obj->sink->write(obj->sink, text));
|
||
|
|
SUCCEED_TRUE(obj, dest);
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|