Skip to content

Commit

Permalink
option to check for integer overflow; round arguments for integer div…
Browse files Browse the repository at this point in the history
…; RIGHT$ with length 0 fixed; detect disk images with 2 heads
  • Loading branch information
benchmarko committed Jan 22, 2025
1 parent aa8d877 commit af46b71
Show file tree
Hide file tree
Showing 14 changed files with 74 additions and 21 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ These URL parameters are also put in the URL when you press the *Reload* button
- exampleIndex=0index.js (example index file in every entry of exampleDirs)
- implicitLines=false (allow implicit line numbers)
- input= (keyboard input when starting the app, use %0D as return charcter)
- integerOverflow=false (check for integer overflow in integer DIV, MOD, AND, OR, XOR, NOT)
- kbdLayout=alphanum (virtual keyboard layout: alphanum, alpha, num)
- canvasType="graphics" ("graphics", "text", "none")
- palette="color" ("color", "green", "grey")
Expand Down
11 changes: 10 additions & 1 deletion examples/test/testpage.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@ cpcBasic.addItem("", function () { /*
750 a%=(3+2)*(3-7):IF a%<>-20 THEN ERROR 33
760 a=-(10-7)-(-6-2):IF a<>5 THEN ERROR 33
770 a=20/2.5:IF a<>8 THEN ERROR 33
772 a=102.5/2.5:IF a<>41 THEN ERROR 33
780 a=20\3:IF a<>6 THEN ERROR 33
782 a=102.5\2.5:IF a<>34 THEN ERROR 33
790 a=3^2:IF a<>9 THEN ERROR 33
800 a=&X1001 AND &X1110:IF a<>&X1000 THEN ERROR 33
810 a=&X1001 OR &X110:IF a<>&X1111 THEN ERROR 33
Expand Down Expand Up @@ -629,7 +631,14 @@ cpcBasic.addItem("", function () { /*
6170 NEXT
6180 MOVE 348,0
6190 FILL 1
6200 '
6191 GOSUB 9040
6192 '
6193 a=1
6194 ON ERROR GOTO 6197
6196 a=1 AND 32768
6197 RESUME 6198
6198 ON ERROR GOTO 0
6199 PRINT "AND integer overflow detected: ";: IF a=1 THEN ?"yes" ELSE ?"no"
6205 GOSUB 9040
6210 PRINT "testpage finished: ok"
6220 END
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cpcbasicts",
"version": "0.2.13",
"version": "0.2.14",
"description": "# CPCBasicTS - Run CPC BASIC in a Browser",
"main": "src/cpcbasic.ts",
"directories": {
Expand Down
23 changes: 12 additions & 11 deletions src/CodeGeneratorJs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ interface CodeGeneratorJsOptions {
parser: BasicParser
implicitLines?: boolean // generate missing line numbers
noCodeFrame?: boolean // suppress generation of a code frame
integerOverflow?: boolean // check for integer overflow
quiet?: boolean // quiet mode: suppress most warnings
trace?: boolean
}
Expand Down Expand Up @@ -341,11 +342,11 @@ export class CodeGeneratorJs {
return reIntConst.test(a);
}

private static fnGetRoundString(node: CodeNode) {
private fnGetRoundString(node: CodeNode, err: string) {
if (node.pt !== "I") { // no rounding needed for integer, hex, binary constants, integer variables, functions returning integer (optimization)
node.pv = "o.vmRound(" + node.pv + ")";
}
return node.pv;
return this.options.integerOverflow ? "o.vmInRange16(" + node.pv + ', "' + err + '")' : node.pv;
}

private static fnIsInString(string: string, find: string) {
Expand Down Expand Up @@ -421,7 +422,7 @@ export class CodeGeneratorJs {
}

private intDiv(node: CodeNode, left: CodeNode, right: CodeNode) { // "\\"
node.pv = "(" + left.pv + " / " + right.pv + ") | 0"; // integer division
node.pv = "(" + this.fnGetRoundString(left, "IDIV") + " / " + this.fnGetRoundString(right, "IDIV") + ") | 0"; // integer division
this.fnPropagateStaticTypes(node, left, right, "II RR IR RI");
node.pt = "I";
}
Expand All @@ -432,30 +433,30 @@ export class CodeGeneratorJs {
}

private and(node: CodeNode, left: CodeNode, right: CodeNode) {
node.pv = CodeGeneratorJs.fnGetRoundString(left) + " & " + CodeGeneratorJs.fnGetRoundString(right);
node.pv = this.fnGetRoundString(left, "AND") + " & " + this.fnGetRoundString(right, "AND");
this.fnPropagateStaticTypes(node, left, right, "II RR IR RI");
node.pt = "I";
}

private or(node: CodeNode, left: CodeNode, right: CodeNode) {
node.pv = CodeGeneratorJs.fnGetRoundString(left) + " | " + CodeGeneratorJs.fnGetRoundString(right);
node.pv = this.fnGetRoundString(left, "OR") + " | " + this.fnGetRoundString(right, "OR");
this.fnPropagateStaticTypes(node, left, right, "II RR IR RI");
node.pt = "I";
}

private xor(node: CodeNode, left: CodeNode, right: CodeNode) {
node.pv = CodeGeneratorJs.fnGetRoundString(left) + " ^ " + CodeGeneratorJs.fnGetRoundString(right);
node.pv = this.fnGetRoundString(left, "XOR") + " ^ " + this.fnGetRoundString(right, "XOR");
this.fnPropagateStaticTypes(node, left, right, "II RR IR RI");
node.pt = "I";
}

private static not(node: CodeNode, _oLeft: CodeNode | undefined, right: CodeNode) { // (unary operator)
node.pv = "~(" + CodeGeneratorJs.fnGetRoundString(right) + ")"; // a can be an expression
private not(node: CodeNode, _oLeft: CodeNode | undefined, right: CodeNode) { // (unary operator)
node.pv = "~(" + this.fnGetRoundString(right, "NOT") + ")"; // a can be an expression
node.pt = "I";
}

private mod(node: CodeNode, left: CodeNode, right: CodeNode) {
node.pv = CodeGeneratorJs.fnGetRoundString(left) + " % " + CodeGeneratorJs.fnGetRoundString(right);
node.pv = this.fnGetRoundString(left, "MOD") + " % " + this.fnGetRoundString(right, "MOD");
this.fnPropagateStaticTypes(node, left, right, "II RR IR RI");
node.pt = "I";
}
Expand Down Expand Up @@ -525,7 +526,7 @@ export class CodeGeneratorJs {
and: this.and,
or: this.or,
xor: this.xor,
not: CodeGeneratorJs.not,
not: this.not,
mod: this.mod,
">": this.greater,
"<": this.less,
Expand All @@ -540,7 +541,7 @@ export class CodeGeneratorJs {
private unaryOperators: Record<string, (node: CodeNode, left: undefined, right: CodeNode) => void> = { // to call methods, use unaryOperators[].call(this,...)
"+": this.plus,
"-": this.minus,
not: CodeGeneratorJs.not,
not: this.not,
"@": this.addressOf,
"#": CodeGeneratorJs.stream
};
Expand Down
7 changes: 7 additions & 0 deletions src/CommonEventHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,13 @@ export class CommonEventHandler implements EventListenerObject {
func: this.onCheckedChange,
controllerFunc: this.controller.fnImplicitLines
},
{
id: ViewID.integerOverflowInput,
viewType: "checked",
property: ModelPropID.integerOverflow,
func: this.onCheckedChange,
controllerFunc: this.controller.fnIntegerOverflow
},
{
id: ViewID.kbdLayoutSelect,
viewType: "select",
Expand Down
2 changes: 2 additions & 0 deletions src/Constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const enum ModelPropID { // eslint-disable-line no-shadow
exampleIndex = "exampleIndex",
implicitLines = "implicitLines",
input = "input",
integerOverflow = "integerOverflow",
kbdLayout = "kbdLayout",
dragElements = "dragElements",
palette = "palette",
Expand Down Expand Up @@ -85,6 +86,7 @@ export const enum ViewID { // eslint-disable-line no-shadow
inp2Text = "inp2Text",
inputArea = "inputArea",
inputText = "inputText",
integerOverflowInput = "integerOverflowInput",
kbdAlpha = "kbdAlpha",
kbdArea = "kbdArea",
kbdAreaInner = "kbdAreaInner",
Expand Down
11 changes: 10 additions & 1 deletion src/Controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,8 @@ export class Controller implements IController {
lexer: this.basicLexer,
parser: this.basicParser,
trace: model.getProperty<boolean>(ModelPropID.trace),
implicitLines: model.getProperty<boolean>(ModelPropID.implicitLines)
implicitLines: model.getProperty<boolean>(ModelPropID.implicitLines),
integerOverflow: model.getProperty<boolean>(ModelPropID.integerOverflow)
});

if (model.getProperty<boolean>(ModelPropID.sound)) { // activate sound needs user action
Expand Down Expand Up @@ -3112,6 +3113,14 @@ export class Controller implements IController {
}
}

fnIntegerOverflow(): void {
const integerOverflow = this.model.getProperty<boolean>(ModelPropID.integerOverflow);

this.codeGeneratorJs.setOptions({
integerOverflow: integerOverflow
});
}

fnTrace(): void {
const trace = this.model.getProperty<boolean>(ModelPropID.trace);

Expand Down
19 changes: 18 additions & 1 deletion src/CpcVm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -845,6 +845,19 @@ export class CpcVm implements ICpcVm {
return n;
}

vmInRange16(n: number, err?: string): number {
const min = -32768,
max = 32767;

if (n < min || n > max) {
if (!this.quiet) {
Utils.console.warn("vmInRange16: number not in range:", min + "<=" + n + "<=" + max);
}
throw this.vmComposeError(Error(), n < min || n > max ? 6 : 5, err + " " + n); // 6=Overflow, 5=Improper argument
}
return n;
}

private vmLineInRange(n: number | undefined, err: string) {
const min = 1,
max = 65535,
Expand Down Expand Up @@ -1295,6 +1308,8 @@ export class CpcVm implements ICpcVm {
return code;
}

// and

asc(s: string): number {
this.vmAssertString(s, "ASC");
if (!s.length) {
Expand Down Expand Up @@ -3640,7 +3655,7 @@ export class CpcVm implements ICpcVm {
right$(s: string, len: number): string {
this.vmAssertString(s, "RIGHT$");
len = this.vmInRangeRound(len, 0, 255, "RIGHT$");
return s.slice(-len);
return s.substring(s.length - len);
}

rnd(n?: number): number {
Expand Down Expand Up @@ -4267,6 +4282,8 @@ export class CpcVm implements ICpcVm {
}
}

// xor

xpos(): number {
return this.canvas.getXpos();
}
Expand Down
6 changes: 4 additions & 2 deletions src/DiskImage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ export class DiskImage {
}
}

private static readonly twoHeads = "2h";

private static readonly formatDescriptors: PartialFormatDescriptorMap = {
data: {
tracks: 40, // number of tracks (1-85)
Expand Down Expand Up @@ -557,8 +559,8 @@ export class DiskImage {
throw this.composeError(Error(), "Unknown format with sector", String(firstSector));
}

if (diskInfo.heads > 1) { // maybe 2
format += String(diskInfo.heads); // e.g. "data": "data2"
if (diskInfo.heads > 1) { // maybe 2 heads
format += DiskImage.twoHeads; // e.g. "data": "data2h"
}

if (Utils.debug > 1) {
Expand Down
1 change: 1 addition & 0 deletions src/Interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ export interface IController {
redoStackElement: () => string

fnImplicitLines: () => void
fnIntegerOverflow: () => void
fnArrayBounds: () => void
fnTrace: () => void
fnSpeed: () => void
Expand Down
1 change: 1 addition & 0 deletions src/cpcbasic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class cpcBasic {
exampleIndex: "0index.js", // example index for every databaseDir
implicitLines: false, // allow implicit line numbers
input: "", // keyboard input when starting the app
integerOverflow: false, // check for integer overflow
kbdLayout: "alphanum", // alphanum, alpha, num
dragElements: false,
palette: "color", // "color", "green", "grey"
Expand Down
4 changes: 2 additions & 2 deletions src/cpcconfig.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/* cpcconfig.ts - configuration file for CPCBasicTS */

export const cpcconfig = { // eslint-disable-line no-unused-vars
databaseDirs: "./examples,https://benchmarko.github.io/CPCBasicApps/apps,storage",
//databaseDirs: "./examples,../../CPCBasicApps/apps,storage", // local test
//databaseDirs: "./examples,https://benchmarko.github.io/CPCBasicApps/apps,storage",
databaseDirs: "./examples,../../CPCBasicApps/apps,storage", // local test

// just an example, not the full list of moved examples...
redirectExamples: {
Expand Down
5 changes: 4 additions & 1 deletion src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="cpcbasic.css">
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
<title id="title">CPC BasicTS v0.2.13</title>
<title id="title">CPC BasicTS v0.2.14</title>
</head>

<body id="pageBody">
Expand Down Expand Up @@ -34,6 +34,9 @@
<input id="implicitLinesInput" type="checkbox" title="Allow implicit line numbers">
<label for="implicitLinesInput" title="Allow implicit line numbers">Implicit Lines</label>
<br>
<input id="integerOverflowInput" type="checkbox" title="Check for integer overflow">
<label for="integerOverflowInput" title="Check for integer overflow">Integer Overflow</label>
<br>
<input id="traceInput" type="checkbox" title="Trace Mode">
<label for="traceInput" title="Trace Mode">Trace Mode</label>
<br>
Expand Down
2 changes: 1 addition & 1 deletion src/test/CpcVm.qunit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1445,7 +1445,7 @@ const allTests: AllTestsType = {
'""': 'CpcVm: Unexpected RETURN in 0: -- {"_key":"stop","reason":"error","priority":50,"paras":{}}'
},
right$: {
'"abc",0': "abc",
'"abc",0': "",
'"abc",1': "c",
'"abc",2': "bc",
'"abc",4': "abc",
Expand Down

0 comments on commit af46b71

Please sign in to comment.