From f7099b8e8f0a2187b22f2af9596487d320162619 Mon Sep 17 00:00:00 2001 From: Steven Hugg Date: Tue, 22 Feb 2022 12:33:57 -0600 Subject: [PATCH] ecs: enum keyword, entity.field constants --- src/codemirror/ecs.js | 1 + src/common/ecs/compiler.ts | 71 ++++++++++++++++++++++++++++++++++++-- src/common/ecs/ecs.ts | 12 ++++++- 3 files changed, 81 insertions(+), 3 deletions(-) diff --git a/src/codemirror/ecs.js b/src/codemirror/ecs.js index 1a893b4c..9d8bcad2 100644 --- a/src/codemirror/ecs.js +++ b/src/codemirror/ecs.js @@ -19,6 +19,7 @@ var directives_list = [ 'end', 'component', 'system', 'entity', 'scope', 'using', 'demo', 'decode', 'resource', 'const', 'locals', 'var', + 'enum', 'default', 'array', 'baseoffset', 'critical', 'on', 'do', 'emit', 'limit', 'once', 'foreach', 'with', 'join', 'if', ]; diff --git a/src/common/ecs/compiler.ts b/src/common/ecs/compiler.ts index 45957533..f7bbba57 100644 --- a/src/common/ecs/compiler.ts +++ b/src/common/ecs/compiler.ts @@ -139,11 +139,16 @@ export class ECSCompiler extends Tokenizer { let lo = this.expectInteger(); this.expectToken('..'); let hi = this.expectInteger(); + this.checkLowerLimit(lo, -0x80000000, "lower int range"); + this.checkUpperLimit(hi, 0x7fffffff, "upper int range"); + this.checkUpperLimit(hi-lo, 0xffffffff, "int range"); + this.checkLowerLimit(hi, lo, "int range"); // TODO: use default value? let defvalue; if (this.ifToken('default')) { defvalue = this.expectInteger(); } + // TODO: check types return { dtype: 'int', lo, hi, defvalue } as IntType; } if (this.peekToken().str == '[') { @@ -159,17 +164,67 @@ export class ECSCompiler extends Tokenizer { let baseoffset; if (this.ifToken('baseoffset')) { baseoffset = this.expectInteger(); + this.checkLowerLimit(baseoffset, -32768, "base offset"); + this.checkUpperLimit(baseoffset, 32767, "base offset"); } return { dtype: 'array', index, elem, baseoffset } as ArrayType; } + if (this.ifToken('enum')) { + this.expectToken('['); + let enumtoks = this.parseList(this.parseEnumIdent, ','); + this.expectToken(']'); + if (enumtoks.length == 0) this.compileError(`must define at least one enum`); + let lo = 0; + let hi = enumtoks.length-1; + this.checkLowerLimit(hi, 0, "enum count"); + this.checkUpperLimit(hi, 255, "enum count"); + let enums : {[name:string]:number} = {}; + for (let i=0; i<=hi; i++) + enums[enumtoks[i].str] = i; + // TODO: use default value? + let defvalue; + if (this.ifToken('default')) { + defvalue = this.expectInteger(); + } + return { dtype: 'int', lo, hi, defvalue, enums } as IntType; + } this.compileError(`I expected a data type here.`); throw new Error(); } + parseEnumIdent() { + let tok = this.expectTokenTypes([TokenType.Ident]); + return tok; + } + parseEnumValue(tok: Token, field: IntType) { + if (!field.enums) throw new ECSError(`field is not an enum`); + let value = field.enums[tok.str]; + if (value == null) throw new ECSError(`unknown enum "${tok.str}"`); + return value; + } + parseDataValue(field: DataField): DataValue | ForwardRef { let tok = this.peekToken(); - if (tok.type == 'integer') { + if (tok.type == ECSTokenType.Integer) { return this.expectInteger(); } + if (tok.type == TokenType.Ident && field.dtype == 'int') { + return this.parseEnumValue(this.consumeToken(), field); + } + if (tok.type == TokenType.Ident) { + let entity = this.currentScope?.getEntityByName(tok.str); + if (!entity) + this.compileError('no entity named "${tok.str}"'); + else { + this.consumeToken(); + this.expectToken('.'); + let fieldName = this.expectIdent().str; + let constValue = this.currentScope?.getConstValue(entity, fieldName); + if (constValue == null) + throw new ECSError(`"${fieldName}" is not defined as a constant`, entity); + else + return constValue; + } + } if (tok.str == '[') { // TODO: 16-bit? return new Uint8Array(this.parseDataArray()); @@ -303,7 +358,10 @@ export class ECSCompiler extends Tokenizer { q.exclude.push(cref); } else if (prefix.str == '#') { const scope = this.currentScope; - if (scope == null) { this.internalError(); throw new Error(); } + if (scope == null) { + this.compileError('You can only reference specific entities inside of a scope.'); + throw new Error(); + } let eref = this.parseEntityForwardRef(); this.deferred.push(() => { let refvalue = this.resolveEntityRef(scope, eref); @@ -537,8 +595,17 @@ export class ECSCompiler extends Tokenizer { this.exportToFile(src); return src.toString(); } + + checkUpperLimit(value: number, upper: number, what: string) { + if (value > upper) this.compileError(`This ${what} is too high; must be ${upper} or less`); + } + checkLowerLimit(value: number, lower: number, what: string) { + if (value < lower) this.compileError(`This ${what} is too low; must be ${lower} or more`); + } } +/// + export class ECSActionCompiler extends Tokenizer { constructor( public readonly context: ActionContext) { diff --git a/src/common/ecs/ecs.ts b/src/common/ecs/ecs.ts index 530d04e3..bf92ebba 100644 --- a/src/common/ecs/ecs.ts +++ b/src/common/ecs/ecs.ts @@ -157,6 +157,7 @@ export interface IntType { lo: number hi: number defvalue?: number + enums?: { [name: string] : number } } export interface ArrayType { @@ -415,6 +416,7 @@ class DataSegment { this.ofs2sym.set(ofs, []); this.ofs2sym.get(ofs)?.push(name); } + // TODO: ordering should not matter, but it does findExistingInitData(bytes: Uint8Array) { for (let i=0; i atypes.find(a => a.components.includes(cf.c))); if (filtered.length == 0) { - throw new ECSError(`cannot find component with field "${fieldName}"`, where); + throw new ECSError(`cannot find component with field "${fieldName}" in this context`, where); } if (filtered.length > 1) { throw new ECSError(`ambiguous field name "${fieldName}"`, where);