ecs: field, ref type checking, start cmd

This commit is contained in:
Steven Hugg 2022-02-20 07:37:51 -06:00
parent 7767608595
commit a444de693b
6 changed files with 143 additions and 91 deletions

14
.gitignore vendored
View File

@ -1,13 +1,13 @@
*~ *~
node_modules node_modules
./local/ /local/
./tests_output/ /tests_output/
./test/output/ /test/output/
.DS_Store .DS_Store
./tmp/ /tmp/
./web/ /web/
./release/ /release/
./gen/ /gen/
config.js config.js
chromedriver.log chromedriver.log
nightwatch.conf.js nightwatch.conf.js

View File

@ -143,3 +143,11 @@ banks need to duplicate code and/or rodata
- don't split critical code across banks - don't split critical code across banks
need bank trampoline macro need bank trampoline macro
nested scopes for game modes? (title / demo / play) nested scopes for game modes? (title / demo / play)
critical data fields
if accessed in critical section, make critical
ignore arrays that aren't referenced
use DASM for multipass?
default values for component fields

View File

@ -25,8 +25,7 @@ export class ECSCompiler extends Tokenizer {
debuginfo = false; debuginfo = false;
constructor( constructor(
public readonly em: EntityManager) public readonly em: EntityManager) {
{
super(); super();
//this.includeEOL = true; //this.includeEOL = true;
this.setTokenRules([ this.setTokenRules([
@ -78,6 +77,7 @@ export class ECSCompiler extends Tokenizer {
comp.parseFile(text, path); comp.parseFile(text, path);
} catch (e) { } catch (e) {
for (var err of comp.errors) this.errors.push(err); for (var err of comp.errors) this.errors.push(err);
throw e;
} }
} }
} }
@ -105,6 +105,8 @@ export class ECSCompiler extends Tokenizer {
if (tok.str == 'demo') { if (tok.str == 'demo') {
let scope = this.parseScope(); let scope = this.parseScope();
scope.isDemo = true; scope.isDemo = true;
// TODO: make required
if (this.peekToken().str == 'demo') this.expectToken('demo');
return scope; return scope;
} }
if (tok.str == 'comment') { if (tok.str == 'comment') {
@ -398,25 +400,37 @@ export class ECSCompiler extends Tokenizer {
let cmd = cmd2; // put in scope let cmd = cmd2; // put in scope
if (cmd == 'var') cmd = 'init'; // TODO: remove? if (cmd == 'var') cmd = 'init'; // TODO: remove?
if (cmd == 'init' || cmd == 'const') { if (cmd == 'init' || cmd == 'const') {
this.parseInitConst(cmd, scope, entity);
} else if (cmd == 'decode') {
this.parseDecode(scope, entity);
}
}
return entity;
}
parseInitConst(cmd: string, scope: EntityScope, entity: Entity) {
// TODO: check data types // TODO: check data types
let name = this.expectIdent().str; let name = this.expectIdent().str;
let { c, f } = this.getEntityField(entity, name); let { c, f } = this.getEntityField(entity, name);
let symtype = this.currentScope.isConstOrInit(c, name); let symtype = scope.isConstOrInit(c, name);
if (symtype && symtype != cmd) if (symtype && symtype != cmd)
this.compileError(`I can't mix const and init values for a given field in a scope.`); this.compileError(`I can't mix const and init values for a given field in a scope.`);
this.expectToken('='); this.expectToken('=');
let valueOrRef = this.parseDataValue(f); let valueOrRef = this.parseDataValue(f);
if ((valueOrRef as ForwardRef).token != null) { if ((valueOrRef as ForwardRef).token != null) {
this.deferred.push(() => { this.deferred.push(() => {
this.lasttoken = (valueOrRef as ForwardRef).token; // for errors
let refvalue = this.resolveEntityRef(scope, valueOrRef as ForwardRef); let refvalue = this.resolveEntityRef(scope, valueOrRef as ForwardRef);
if (cmd == 'const') scope.setConstValue(entity, c, name, refvalue); if (cmd == 'const') scope.setConstValue(entity, c, f, refvalue);
if (cmd == 'init') scope.setInitValue(entity, c, name, refvalue); if (cmd == 'init') scope.setInitValue(entity, c, f, refvalue);
}); });
} else { } else {
if (cmd == 'const') scope.setConstValue(entity, c, name, valueOrRef as DataValue); if (cmd == 'const') scope.setConstValue(entity, c, f, valueOrRef as DataValue);
if (cmd == 'init') scope.setInitValue(entity, c, name, valueOrRef as DataValue); if (cmd == 'init') scope.setInitValue(entity, c, f, valueOrRef as DataValue);
} }
} else if (cmd == 'decode') { }
parseDecode(scope: EntityScope, entity: Entity) {
let decoderid = this.expectIdent().str; let decoderid = this.expectIdent().str;
let codetok = this.expectTokenTypes([ECSTokenType.CodeFragment]); let codetok = this.expectTokenTypes([ECSTokenType.CodeFragment]);
let code = codetok.str; let code = codetok.str;
@ -431,12 +445,9 @@ export class ECSCompiler extends Tokenizer {
} }
for (let entry of Object.entries(result.properties)) { for (let entry of Object.entries(result.properties)) {
let { c, f } = this.getEntityField(entity, entry[0]); let { c, f } = this.getEntityField(entity, entry[0]);
scope.setConstValue(entity, c, f.name, entry[1] as DataValue); scope.setConstValue(entity, c, f, entry[1] as DataValue);
} }
} }
}
return entity;
}
getEntityField(e: Entity, name: string): ComponentFieldPair { getEntityField(e: Entity, name: string): ComponentFieldPair {
if (!this.currentScope) { this.internalError(); throw new Error(); } if (!this.currentScope) { this.internalError(); throw new Error(); }
@ -526,8 +537,7 @@ export class ECSCompiler extends Tokenizer {
export class ECSActionCompiler extends Tokenizer { export class ECSActionCompiler extends Tokenizer {
constructor( constructor(
public readonly context: ActionContext) public readonly context: ActionContext) {
{
super(); super();
this.setTokenRules([ this.setTokenRules([
{ type: ECSTokenType.Placeholder, regex: /\{\{.*?\}\}/ }, { type: ECSTokenType.Placeholder, regex: /\{\{.*?\}\}/ },

View File

@ -309,9 +309,12 @@ export class Dialect_CA65 {
return `.scope ${name}` return `.scope ${name}`
} }
endScope(name: string) { endScope(name: string) {
return `.endscope\n${name}__Start = ${name}::__Start` return `.endscope\n${this.scopeSymbol(name)} = ${name}::__Start`
// TODO: scope__start = scope::start // TODO: scope__start = scope::start
} }
scopeSymbol(name: string) {
return `${name}__Start`;
}
align(value: number) { align(value: number) {
return `.align ${value}`; return `.align ${value}`;
} }
@ -365,6 +368,9 @@ export class Dialect_CA65 {
call(symbol: string) { call(symbol: string) {
return ` jsr ${symbol}`; return ` jsr ${symbol}`;
} }
jump(symbol: string) {
return ` jmp ${symbol}`;
}
return() { return() {
return ' rts'; return ' rts';
} }
@ -662,6 +668,8 @@ class ActionEval {
else state.xreg = new IndexRegister(this.scope, this.qr); else state.xreg = new IndexRegister(this.scope, this.qr);
break; break;
case 'join': case 'join':
// TODO: Joins don't work in superman (arrays offset?)
// ignore the join query, use the ref
if (state.xreg || state.yreg) throw new ECSError('no free index registers for join', this.action); if (state.xreg || state.yreg) throw new ECSError('no free index registers for join', this.action);
this.jr = new EntitySet(this.scope, (this.action as ActionWithJoin).join); this.jr = new EntitySet(this.scope, (this.action as ActionWithJoin).join);
state.xreg = new IndexRegister(this.scope, this.jr); state.xreg = new IndexRegister(this.scope, this.jr);
@ -882,6 +890,10 @@ class ActionEval {
//this.used.add(`arg_${argindex}_${argvalue}`); //this.used.add(`arg_${argindex}_${argvalue}`);
return argvalue; return argvalue;
} }
__start(args: string[]) {
let startSymbol = this.dialect.scopeSymbol(args[0]);
return this.dialect.jump(startSymbol);
}
wrapCodeInLoop(code: string, action: ActionWithQuery, ents: Entity[], joinfield?: ComponentFieldPair): string { wrapCodeInLoop(code: string, action: ActionWithQuery, ents: Entity[], joinfield?: ComponentFieldPair): string {
// TODO: check ents // TODO: check ents
// TODO: check segment bounds // TODO: check segment bounds
@ -976,9 +988,8 @@ class ActionEval {
if (baseLookup) { if (baseLookup) {
return this.dialect.absolute(ident); return this.dialect.absolute(ident);
} else if (entities.length == 1) { } else if (entities.length == 1) {
let eidofs = qr.entities.length && qr.entities[0].id - range.elo; // TODO: negative? // TODO: qr or this.entites?
if (entityLookup) let eidofs = entities[0].id - range.elo; // TODO: negative?
eidofs = entities[0].id - range.elo;
return this.dialect.absolute(ident, eidofs); return this.dialect.absolute(ident, eidofs);
} else { } else {
let ir; let ir;
@ -1298,15 +1309,17 @@ export class EntityScope implements SourceLocated {
return this.bss.getFieldRange(c, fn) || this.rodata.getFieldRange(c, fn); return this.bss.getFieldRange(c, fn) || this.rodata.getFieldRange(c, fn);
} }
// TODO: check type/range of value // TODO: check type/range of value
setConstValue(e: Entity, component: ComponentType, fieldName: string, value: DataValue) { setConstValue(e: Entity, component: ComponentType, field: DataField, value: DataValue) {
this.setConstInitValue(e, component, fieldName, value, 'const'); this.setConstInitValue(e, component, field, value, 'const');
} }
setInitValue(e: Entity, component: ComponentType, fieldName: string, value: DataValue) { setInitValue(e: Entity, component: ComponentType, field: DataField, value: DataValue) {
this.setConstInitValue(e, component, fieldName, value, 'init'); this.setConstInitValue(e, component, field, value, 'init');
} }
setConstInitValue(e: Entity, component: ComponentType, fieldName: string, value: DataValue, setConstInitValue(e: Entity, component: ComponentType, field: DataField, value: DataValue,
type: 'const'|'init') { type: 'const'|'init')
this.em.singleComponentWithFieldName([e.etype], fieldName, e); {
this.checkValueType(field, value);
let fieldName = field.name;
let cfname = mksymbol(component, fieldName); let cfname = mksymbol(component, fieldName);
let ecfname = mkscopesymbol(this, component, fieldName); let ecfname = mkscopesymbol(this, component, fieldName);
if (e.consts[cfname] !== undefined) throw new ECSError(`"${fieldName}" is already defined as a constant`, e); if (e.consts[cfname] !== undefined) throw new ECSError(`"${fieldName}" is already defined as a constant`, e);
@ -1318,6 +1331,24 @@ export class EntityScope implements SourceLocated {
isConstOrInit(component: ComponentType, fieldName: string) : 'const' | 'init' { isConstOrInit(component: ComponentType, fieldName: string) : 'const' | 'init' {
return this.fieldtypes[mksymbol(component, fieldName)]; return this.fieldtypes[mksymbol(component, fieldName)];
} }
checkValueType(field: DataField, value: DataValue) {
if (field.dtype == 'array') {
if (!(value instanceof Uint8Array))
throw new ECSError(`This "${field.name}" value should be an array.`);
} else if (typeof value !== 'number') {
throw new ECSError(`This "${field.name}" ${field.dtype} value should be an number.`);
} else {
if (field.dtype == 'int') {
if (value < field.lo || value > field.hi)
throw new ECSError(`This "${field.name}" value is out of range, should be between ${field.lo} and ${field.hi}.`);
} else if (field.dtype == 'ref') {
// TODO: allow override if number
let eset = new EntitySet(this, field.query);
if (value < 0 || value >= eset.entities.length)
throw new ECSError(`This "${field.name}" value is out of range for this ref type.`);
}
}
}
generateCodeForEvent(event: string, args?: string[], codelabel?: string): string { generateCodeForEvent(event: string, args?: string[], codelabel?: string): string {
// find systems that respond to event // find systems that respond to event
// and have entities in this scope // and have entities in this scope

View File

@ -159,6 +159,7 @@ end
entity NullShape [Bitmap,Colormap] entity NullShape [Bitmap,Colormap]
decode vcs_sprite decode vcs_sprite
--- ---
........ 00
--- ---
end end
@ -504,7 +505,7 @@ x.x.x.x.x.x.x.x.x.x. .. 06 ..
var sprite = #Superdude var sprite = #Superdude
end end
entity Slot1 [SpriteSlot] entity Slot1 [SpriteSlot]
var sprite = $ff var sprite = 0 // $ff
end end
using VersatilePlayfield with #Superdude.room using VersatilePlayfield with #Superdude.room

View File

@ -60,8 +60,9 @@ Bitmap_bitmapdata_b8:
.byte >(Bitmap_bitmapdata_e2_b0+31) .byte >(Bitmap_bitmapdata_e2_b0+31)
.byte >(Bitmap_bitmapdata_e3_b0+31) .byte >(Bitmap_bitmapdata_e3_b0+31)
Bitmap_bitmapdata_e1_b0: Bitmap_bitmapdata_e1_b0:
.byte 0
Bitmap_height_b0: Bitmap_height_b0:
.byte 255 .byte 0
.byte 17 .byte 17
.byte 27 .byte 27
Colormap_colormapdata_b0: Colormap_colormapdata_b0:
@ -73,6 +74,7 @@ Colormap_colormapdata_b8:
.byte >(Colormap_colormapdata_e2_b0+31) .byte >(Colormap_colormapdata_e2_b0+31)
.byte >(Colormap_colormapdata_e3_b0+31) .byte >(Colormap_colormapdata_e3_b0+31)
Colormap_colormapdata_e1_b0: Colormap_colormapdata_e1_b0:
.byte 0
Bitmap_bitmapdata_e2_b0: Bitmap_bitmapdata_e2_b0:
.byte 128 .byte 128
.byte 192 .byte 192
@ -622,7 +624,7 @@ Main__INITDATA:
.byte 60 .byte 60
.byte 90 .byte 90
.byte 0 .byte 0
.byte 255 .byte 0
.byte 0 .byte 0
.byte 0 .byte 0
.byte 0 .byte 0