diff --git a/src/common/ecs/compiler.ts b/src/common/ecs/compiler.ts index 122cce35..3e452930 100644 --- a/src/common/ecs/compiler.ts +++ b/src/common/ecs/compiler.ts @@ -2,7 +2,7 @@ import { mergeLocs, Tokenizer, TokenType } from "../tokenizer"; import { SourceLocated } from "../workertypes"; import { newDecoder } from "./decoder"; -import { Action, ArrayType, ComponentType, DataField, DataType, DataValue, ECSError, Entity, EntityArchetype, EntityManager, EntityScope, IntType, Query, RefType, SelectType, SourceFileExport, System } from "./ecs"; +import { Action, ArrayType, ComponentType, DataField, DataType, DataValue, ECSError, Entity, EntityArchetype, EntityManager, EntityScope, IntType, Query, RefType, SelectType, SELECT_TYPE, SourceFileExport, System } from "./ecs"; export enum ECSTokenType { Ellipsis = 'ellipsis', @@ -15,6 +15,7 @@ export enum ECSTokenType { export class ECSCompiler extends Tokenizer { currentScope: EntityScope | null = null; + debuginfo = false; constructor( public readonly em: EntityManager) @@ -63,6 +64,7 @@ export class ECSCompiler extends Tokenizer { if (!text) this.compileError(`I can't find the import file "${path}".`); this.em.imported[path] = true; let comp = new ECSCompiler(this.em); + comp.debuginfo = this.debuginfo; // TODO: clone compiler try { comp.parseFile(text, path); } catch (e) { @@ -224,11 +226,12 @@ export class ECSCompiler extends Tokenizer { // TODO: unused events? let event = this.expectIdent().str; this.expectToken('do'); - let select = this.expectTokens( - ['once', 'foreach', 'join', 'with', 'if', 'select']).str as SelectType; // TODO: type check? + let select = this.expectTokens(SELECT_TYPE).str as SelectType; // TODO: type check? let query = undefined; let join = undefined; - if (select != 'once') { + if (select == 'once') { + if (this.peekToken().str == '[') this.compileError(`A "${select}" query can't include a query.`) + } else { query = this.parseQuery(); } if (select == 'join') { @@ -273,12 +276,12 @@ export class ECSCompiler extends Tokenizer { } } - parseEvent() { + parseEventName() { return this.expectIdent().str; } parseEventList() { - return this.parseList(this.parseEvent, ","); + return this.parseList(this.parseEventName, ","); } parseCode(): string { @@ -286,12 +289,16 @@ export class ECSCompiler extends Tokenizer { let tok = this.expectTokenTypes([ECSTokenType.CodeFragment]); let code = tok.str.substring(3, tok.str.length-3); let lines = code.split('\n'); - let re = /^\s*(;|\/\/|$)/; // ignore comments and blank lines + if (this.debuginfo) this.addDebugInfo(lines, tok.$loc.line); + return lines.join('\n'); + } + + addDebugInfo(lines: string[], startline: number) { + const re = /^\s*(;|\/\/|$)/; // ignore comments and blank lines for (let i=0; i`); // TODO + s += this.dialect.comment(`start action ${sys.name} ${event}`); // TODO s += codeeval.codeToString(); - s += this.dialect.comment(``); + s += this.dialect.comment(`end action ${sys.name} ${event}`); // TODO: check that this happens once? codeeval.end(); numActions++; diff --git a/src/common/tokenizer.ts b/src/common/tokenizer.ts index 1dd1c5ba..1b7c3fc9 100644 --- a/src/common/tokenizer.ts +++ b/src/common/tokenizer.ts @@ -171,7 +171,7 @@ export class Tokenizer { } return tok; } - expectTokens(strlist: string[], msg?: string): Token { + expectTokens(strlist: readonly string[], msg?: string): Token { let tok = this.consumeToken(); let tokstr = tok.str; if (!strlist.includes(tokstr)) { diff --git a/src/test/testecs.ts b/src/test/testecs.ts index 59d98f5e..2498062d 100644 --- a/src/test/testecs.ts +++ b/src/test/testecs.ts @@ -1,4 +1,6 @@ -import { readdirSync, readFileSync } from "fs"; +import assert from "assert"; +import { execFileSync } from "child_process"; +import { readdirSync, readFileSync, writeFileSync } from "fs"; import { describe } from "mocha"; import { ECSCompiler } from "../common/ecs/compiler"; import { Dialect_CA65, EntityManager, SourceFileExport } from "../common/ecs/ecs"; @@ -338,7 +340,7 @@ scope Root end `, 'foo.txt'); - console.log('json', c.em.toJSON()); + //console.log('json', c.em.toJSON()); let src = new SourceFileExport(); c.exportToFile(src); // TODO: test? @@ -368,14 +370,27 @@ describe('Tokenizer', function() { describe('Compiler', function() { let testdir = './test/ecs/'; let files = readdirSync(testdir).filter(f => f.endsWith('.ecs')); - files.forEach((ecspath) => { + files.forEach((ecsfn) => { + let goodfn = ecsfn.replace('.ecs','.txt') + let srcpath = testdir + ecsfn; + let destpath = testdir + goodfn; let dialect = new Dialect_CA65(); let em = new EntityManager(dialect); + em.mainPath = srcpath; let compiler = new ECSCompiler(em); - let code = readFileSync(testdir + ecspath, 'utf-8'); - compiler.parseFile(code, ecspath); + compiler.getImportFile = (path: string) => { + return readFileSync(testdir + path, 'utf-8'); + } + let code = readFileSync(srcpath, 'utf-8'); + compiler.parseFile(code, srcpath); let out = new SourceFileExport(); em.exportToFile(out); - console.log(out.toString()); + let outtxt = out.toString(); + let goodtxt = readFileSync(destpath, 'utf-8'); + if (outtxt.trim() != goodtxt.trim()) { + writeFileSync('/tmp/' + goodfn, outtxt, 'utf-8'); + execFileSync('/usr/bin/diff', [srcpath, destpath]); + throw new Error(ecsfn + ' did not match test file'); + } }); }); diff --git a/src/worker/tools/ecs.ts b/src/worker/tools/ecs.ts index 4d2de328..f537cc05 100644 --- a/src/worker/tools/ecs.ts +++ b/src/worker/tools/ecs.ts @@ -16,6 +16,7 @@ export function assembleECS(step: BuildStep): BuildStepResult { if (staleFiles(step, [destpath])) { let code = getWorkFileAsString(step.path); try { + compiler.debuginfo = true; compiler.parseFile(code, step.path); let outtext = compiler.export().toString(); putWorkFile(destpath, outtext); diff --git a/test/ecs/basic1.txt b/test/ecs/basic1.txt new file mode 100644 index 00000000..e69de29b diff --git a/test/ecs/vcs1.ecs b/test/ecs/vcs1.ecs new file mode 100644 index 00000000..0044cad0 --- /dev/null +++ b/test/ecs/vcs1.ecs @@ -0,0 +1,235 @@ + +//#resource "vcs-ca65.h" + +system Init + on main_init do once +--- +.include "vcs-ca65.h" +.define PAL 0 +__NMI: +__Reset: +__BRK: + CLEAN_START +{{bss_init}} ; initialize data segment +{{!start}} ; start main routine +.segment "VECTORS" +Return: .word $6060 +VecNMI: +VecReset: .word Main::__Reset +VecBRK: .word Main::__BRK +--- +end + +component Player +end + +component KernelSection + lines: 1..255 +end + +component BGColor + bgcolor: 0..255 +end + +component PFColor + pfcolor: 0..255 +end + +component Playfield + pf: 0..0xffffff +end + +component AsymPlayfield + pfleft: 0..0xffffff + pfright: 0..0xffffff +end + +component VersatilePlayfield + data: array of 0..255 baseoffset -1 +end + +system FrameLoop + on start do once +--- +@NextFrame: + FRAME_START + {{emit preframe}} + KERNEL_START + {{emit kernel}} + KERNEL_END + {{emit postframe}} + FRAME_END + {{emit nextframe}} + jmp @NextFrame ; loop to next frame +--- +end + +system ResetSwitch + on nextframe do once +--- + lsr SWCHB ; test Game Reset switch + bcs @NoStart + {{!resetswitch}} +@NoStart: +--- +end + +system ResetConsole + on resetswitch do once +--- + jmp Main::__Reset ; jump to Reset handler +--- +end + +system JoyButton + on postframe do foreach [Player] +--- + lda {{index INPT4}} ;read button input + bmi @NotPressed + {{emit joybutton}} +@NotPressed: +--- +end + +system Joystick + locals 1 + on postframe do once +--- +; 2 control inputs share a single byte, 4 bits each + lda SWCHA + sta {{$0}} +--- + on postframe do foreach [Player] +--- + asl {{$0}} + bcs @SkipMoveRight + {{!joyright}} +@SkipMoveRight: + asl {{$0}} + bcs @SkipMoveLeft + {{!joyleft}} +@SkipMoveLeft: + asl {{$0}} + bcs @SkipMoveDown + {{!joydown}} +@SkipMoveDown: + asl {{$0}} + bcs @SkipMoveUp + {{!joyup}} +@SkipMoveUp: +--- +end + +system SetHorizPos + on SetHorizPos do once +--- +; SetHorizPos routine +; A = X coordinate +; Y = player number (0 or 1) + sta WSYNC ; start a new line + sec ; set carry flag + nop +@DivideLoop: + sbc #15 ; subtract 15 + bcs @DivideLoop ; branch until negative + eor #7 ; calculate fine offset + asl + asl + asl + asl + sta RESP0,y ; fix coarse position + sta HMP0,y ; set fine offset +--- +end + + +system StaticKernel + on preframe do foreach [KernelSection] limit 1 +--- + {{!kernelsetup}} +--- + on kernel do foreach [KernelSection] +--- + sta WSYNC + {{!kernelsetup}} + ldy {{