diff --git a/ecsroot/vcs/kernel.ecs b/ecsroot/vcs/kernel.ecs deleted file mode 100644 index 67674fd6..00000000 --- a/ecsroot/vcs/kernel.ecs +++ /dev/null @@ -1,279 +0,0 @@ - -component Kernel - lines: 0..255 - bgcolor: 0..255 -end - -component Bitmap - bitmapdata: array of 0..255 -end - -component HasBitmap - bitmap: [Bitmap] -end - -component Colormap - colormapdata: array of 0..255 -end - -component HasColormap - colormap: [Colormap] -end - -component Sprite - height: 0..255 - plyrindex: 0..1 -end - -component Player end - -component PlayerFlags - plyrflags: 0..63 -end - -component HasXpos - xpos: 0..255 -end - -component HasYpos - ypos: 0..255 -end - -component HasXYVel - xyvel: 0..255 -end - -system FrameLoop - on start do once [Kernel] emit (preframe, kernel, postframe) ---- -{{@NextFrame}}: - FRAME_START - {{!preframe}} - KERNEL_START - {{!kernel}} - KERNEL_END - {{!postframe}} - FRAME_END - lsr SWCHB ; test Game Reset switch - bcs {{@NoStart}} - jmp Start -{{@NoStart}}: - jmp {{@NextFrame}} ---- -end - -system SimpleKernel - locals 12 - on preframe do each [Sprite,HasBitmap,HasColormap,HasYpos] ---- - lda #192 ; TODO: numlines - sec - sbc {{Bitmap.bitmapdata}},y - sbc #0 - sta {{$2}},x - - ldy {{Colormap.colormapdata}},y - sbc #0 - sta {{$6}},x - - lda {{ L0 H0 L1 H1 - lda {{$1}} - ldy {{$2}} - sty {{$1}} - sta {{$2}} - lda {{$5}} - ldy {{$6}} - sty {{$5}} - sta {{$6}} ---- - on kernel do once [Kernel] ---- - lda {{ + diff --git a/src/codemirror/ecs.js b/src/codemirror/ecs.js new file mode 100644 index 00000000..eb149db1 --- /dev/null +++ b/src/codemirror/ecs.js @@ -0,0 +1,103 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function (mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function (CodeMirror) { + "use strict"; + + // 6502 DASM syntax + + CodeMirror.defineMode('ecs', function (_config, parserConfig) { + var keywords1, keywords2; + + var directives_list = [ + 'component', 'system', 'entity', 'scope', 'end', + 'const', 'init', 'locals', + 'on', 'do', 'emit', + 'once', 'foreach', 'source', + ]; + var keywords_list = [ + 'processor', + 'byte', 'word', 'long', + 'include', 'seg', 'dc', 'ds', 'dv', 'hex', 'err', 'org', 'rorg', 'echo', 'rend', + 'align', 'subroutine', 'equ', 'eqm', 'set', 'mac', 'endm', 'mexit', 'ifconst', + 'ifnconst', 'if', 'else', 'endif', 'eif', 'repeat', 'repend' + ]; + + var directives = new Map(); + directives_list.forEach(function (s) { directives.set(s, 'def'); }); + keywords_list.forEach(function (s) { directives.set(s, 'keyword'); }); + + var opcodes = /^[a-z][a-z][a-z]\b/i; + var numbers = /^(0x[\da-f]+|[\da-f]+h|[0-7]+o|[01]+b|\d+d?)\b/i; + var tags = /^\{\{.*\}\}/; + var mlcomment = /^---.+?---\b/i; + + return { + startState: function () { + return { + context: 0 + }; + }, + token: function (stream, state) { + if (stream.eatSpace()) + return null; + + if (stream.match(tags)) { + return 'meta'; + } + + var w; + if (stream.eatWhile(/\w/)) { + w = stream.current(); + var cur = w.toLowerCase(); + var style = directives.get(cur); + if (style) + return style; + + if (opcodes.test(w)) { + return 'keyword'; + } else if (numbers.test(w)) { + return 'number'; + } else if (w == 'comment') { + stream.match(mlcomment); + return 'comment'; + } + } else if (stream.eat(';')) { + stream.skipToEnd(); + return 'comment'; + } else if (stream.eat('"')) { + while (w = stream.next()) { + if (w == '"') + break; + + if (w == '\\') + stream.next(); + } + return 'string'; + } else if (stream.eat('\'')) { + if (stream.match(/\\?.'/)) + return 'number'; + } else if (stream.eat('$') || stream.eat('#')) { + if (stream.eatWhile(/[^;]/i)) + return 'number'; + } else if (stream.eat('%')) { + if (stream.eatWhile(/[01]/)) + return 'number'; + } else { + stream.next(); + } + return null; + } + }; + }); + + CodeMirror.defineMIME("text/x-ecs", "ecs"); + +}); diff --git a/src/common/ecs/compiler.ts b/src/common/ecs/compiler.ts index 49761fc9..ba3677c7 100644 --- a/src/common/ecs/compiler.ts +++ b/src/common/ecs/compiler.ts @@ -152,7 +152,7 @@ export class ECSCompiler extends Tokenizer { parseAction(): Action { let event = this.expectIdent().str; this.expectToken('do'); - let select = this.expectTokens(['once', 'each', 'source']).str as SelectType; // TODO: type check? + let select = this.expectTokens(['once', 'foreach', 'source']).str as SelectType; // TODO: type check? let query = this.parseQuery(); let emits; if (this.peekToken().str == 'emit') { @@ -183,6 +183,7 @@ export class ECSCompiler extends Tokenizer { } parseCode(): string { + // TODO: add $loc let tok = this.expectTokenTypes([TokenType.CodeFragment]); let code = tok.str; let lines = code.split('\n'); diff --git a/src/common/ecs/ecs.ts b/src/common/ecs/ecs.ts index 5bfb3fd3..019452c2 100644 --- a/src/common/ecs/ecs.ts +++ b/src/common/ecs/ecs.ts @@ -97,7 +97,7 @@ export interface Action { emits?: string[]; } -export type SelectType = 'once' | 'each' | 'source'; +export type SelectType = 'once' | 'foreach' | 'source'; export type DataValue = number | boolean | Uint8Array | Uint16Array; @@ -154,11 +154,11 @@ interface ComponentFieldPair { export class Dialect_CA65 { readonly ASM_ITERATE_EACH = ` ldx #0 -{{@__each}}: +@__each: {{code}} inx cpx #{{ecount}} - bne {{@__each}} + bne @__each `; readonly INIT_FROM_ARRAY = ` ldy #{{nbytes}} @@ -254,13 +254,15 @@ class Segment { this.codefrags.push(code); } allocateBytes(name: string, bytes: number) { - if (this.symbols[name]) return this.symbols[name]; // TODO: check size - let ofs = this.size; - this.symbols[name] = ofs; - if (!this.ofs2sym.has(ofs)) - this.ofs2sym.set(ofs, []); - this.ofs2sym.get(ofs)?.push(name); - this.size += bytes; + let ofs = this.symbols[name]; + if (ofs == null) { + ofs = this.size; + this.symbols[name] = ofs; + if (!this.ofs2sym.has(ofs)) + this.ofs2sym.set(ofs, []); + this.ofs2sym.get(ofs)?.push(name); + this.size += bytes; + } return ofs; } // TODO: optimize shared data @@ -388,7 +390,7 @@ export class EntityScope { // variable size? make it a pointer if (bits == 0) bits = 16; // TODO? let rangelen = (f.ehi - f.elo + 1); - let bytesperelem = Math.ceil(bits / 8) * rangelen; + let bytesperelem = Math.ceil(bits / 8); // TODO: packing bits // TODO: split arrays f.access = []; @@ -419,7 +421,7 @@ export class EntityScope { let hiofs = segment.allocateBytes(ptrhisym, entcount); segment.initdata[loofs + e.id - fieldrange.elo] = { symbol: datasym, bitofs: 0 }; segment.initdata[hiofs + e.id - fieldrange.elo] = { symbol: datasym, bitofs: 8 }; - // TODO: } else if (v instanceof Uint16Array) { + // TODO: } else if (v instanceof Uint16Array) { } else if (typeof v === 'number') { // more than 1 entity, add an array // TODO: what if > 8 bits? @@ -428,6 +430,7 @@ export class EntityScope { let datasym = this.dialect.fieldsymbol(c, f, 0); let base = segment.allocateBytes(datasym, entcount); segment.initdata[base + e.id - fieldrange.elo] = v; + //console.error(cfname, datasym, base, e.id, fieldrange.elo, entcount, v); } } else { throw new ECSError(`unhandled constant ${e.id}:${cfname}`); @@ -524,17 +527,20 @@ export class EntityScope { } replaceCode(code: string, sys: System, action: Action): string { const re = /\{\{(.+?)\}\}/g; - let label = sys.name + '_' + action.event; + let label = `${sys.name}__${action.event}`; let atypes = this.em.archetypesMatching(action.query); let entities = this.entitiesMatching(atypes); // TODO: detect cycles // TODO: "source"? // TODO: what if only 1 item? - if (action.select == 'each') { + if (action.select == 'foreach') { code = this.wrapCodeInLoop(code, sys, action, entities); //console.log(sys.name, action.event, ents); //frag = this.iterateCode(frag); } + // replace @labels + code = code.replace(/@(\w+)\b/g, (s: string, a: string) => `${label}__${a}`); + // replace {{...}} tags return code.replace(re, (entire, group: string) => { let cmd = group.charAt(0); let rest = group.substring(1); diff --git a/src/ide/ui.ts b/src/ide/ui.ts index 2c958ba3..c64edde0 100644 --- a/src/ide/ui.ts +++ b/src/ide/ui.ts @@ -114,7 +114,8 @@ const TOOL_TO_SOURCE_STYLE = { 'silice': 'verilog', 'wiz': 'text/x-wiz', 'vasmarm': 'vasm', - 'armips': 'vasm' + 'armips': 'vasm', + 'ecs': 'ecs', } const TOOL_TO_HELPURL = { diff --git a/src/ide/views/editors.ts b/src/ide/views/editors.ts index d5a81d28..d594ac10 100644 --- a/src/ide/views/editors.ts +++ b/src/ide/views/editors.ts @@ -36,6 +36,7 @@ const MODEDEFS = { markdown: { lineWrap: true }, fastbasic: { noGutters: true }, basic: { noLineNumbers: true, noGutters: true }, // TODO: not used? + ecs: { theme: 'mbo', isAsm: false }, } export var textMapFunctions = { diff --git a/src/test/testecs.ts b/src/test/testecs.ts index 492a76a3..3c3b4438 100644 --- a/src/test/testecs.ts +++ b/src/test/testecs.ts @@ -3,7 +3,7 @@ import { ECSCompiler } from "../common/ecs/compiler"; import { Dialect_CA65, EntityManager, SourceFileExport } from "../common/ecs/ecs"; const TEMPLATE1 = ` -{{@NextFrame}}: +@NextFrame: FRAME_START {{!preframe}} KERNEL_START @@ -15,74 +15,12 @@ const TEMPLATE1 = ` bcs @NoStart jmp Start @NoStart: - jmp {{@NextFrame}} + jmp @NextFrame `; -// TODO: two sticks? -const TEMPLATE2_a = ` - lda SWCHA - sta {{$0}} -` - -const TEMPLATE2_b = ` - asl {{$0}} - bcs {{@SkipMoveRight}} - {{!joyright}} -{{@SkipMoveRight}}: - asl {{$0}} - bcs {{@SkipMoveLeft}} - {{!joyleft}} -{{@SkipMoveLeft}}: - asl {{$0}} - bcs {{@SkipMoveDown}} - {{!joydown}} -{{@SkipMoveDown}}: - asl {{$0}} - bcs {{@SkipMoveUp}} - {{!joyup}} -{{@SkipMoveUp}}: -`; - -const TEMPLATE3_L = ` - lda {{ [start] -> frameloop // frameloop -> [preframe] [kernel] [postframe] @@ -299,7 +231,7 @@ function testECS() { name: 'set_xpos', actions: [ { - text: SET_XPOS, event: 'preframe', select: 'each', query: { + text: SET_XPOS, event: 'preframe', select: 'foreach', query: { include: ['sprite', 'xpos'] }, }, @@ -314,29 +246,6 @@ function testECS() { emits: ['preframe', 'kernel', 'postframe'] } ] }) - em.defineSystem({ - name: 'joyread', - tempbytes: 1, - actions: [ - { text: TEMPLATE2_a, event: 'postframe', select: 'once', query: { include: ['player'] } }, - { text: TEMPLATE2_b, event: 'postframe', select: 'each', query: { include: ['player'] }, - emits: ['joyup', 'joydown', 'joyleft', 'joyright', 'joybutton'] } - ] - }); - em.defineSystem({ - name: 'move_x', - actions: [ - { text: TEMPLATE3_L, event: 'joyleft', select: 'source', query: { include: ['player', 'xpos'] }, }, - { text: TEMPLATE3_R, event: 'joyright', select: 'source', query: { include: ['player', 'xpos'] }, }, - ] - }); - em.defineSystem({ - name: 'move_y', - actions: [ - { text: TEMPLATE3_U, event: 'joyup', select: 'source', query: { include: ['player', 'ypos'] } }, - { text: TEMPLATE3_D, event: 'joydown', select: 'source', query: { include: ['player', 'ypos'] } }, - ] - }); em.defineSystem({ name: 'SetHorizPos', actions: [