2022-01-28 17:22:59 +00:00
|
|
|
|
2022-01-31 15:17:40 +00:00
|
|
|
import { mergeLocs, Tokenizer, TokenType } from "../tokenizer";
|
2022-01-30 16:48:56 +00:00
|
|
|
import { SourceLocated } from "../workertypes";
|
2022-02-05 05:56:43 +00:00
|
|
|
import { newDecoder } from "./decoder";
|
2022-02-08 12:18:28 +00:00
|
|
|
import { Action, ActionWithJoin, ArrayType, ComponentType, DataField, DataType, DataValue, ECSError, Entity, EntityArchetype, EntityManager, EntityScope, IntType, Query, RefType, SelectType, SELECT_TYPE, SourceFileExport, System } from "./ecs";
|
2022-01-28 17:22:59 +00:00
|
|
|
|
|
|
|
export enum ECSTokenType {
|
|
|
|
Ellipsis = 'ellipsis',
|
|
|
|
Operator = 'delimiter',
|
|
|
|
QuotedString = 'quoted-string',
|
|
|
|
Integer = 'integer',
|
2022-02-01 15:13:37 +00:00
|
|
|
CodeFragment = 'code-fragment',
|
2022-01-28 17:22:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export class ECSCompiler extends Tokenizer {
|
|
|
|
|
|
|
|
currentScope: EntityScope | null = null;
|
2022-02-07 20:58:03 +00:00
|
|
|
debuginfo = false;
|
2022-01-28 17:22:59 +00:00
|
|
|
|
2022-02-03 02:06:44 +00:00
|
|
|
constructor(
|
|
|
|
public readonly em: EntityManager)
|
|
|
|
{
|
2022-01-28 17:22:59 +00:00
|
|
|
super();
|
|
|
|
//this.includeEOL = true;
|
|
|
|
this.setTokenRules([
|
|
|
|
{ type: ECSTokenType.Ellipsis, regex: /\.\./ },
|
|
|
|
{ type: ECSTokenType.QuotedString, regex: /".*?"/ },
|
2022-02-01 15:13:37 +00:00
|
|
|
{ type: ECSTokenType.CodeFragment, regex: /---.*?---/ },
|
2022-01-29 03:13:33 +00:00
|
|
|
{ type: ECSTokenType.Integer, regex: /[-]?0x[A-Fa-f0-9]+/ },
|
|
|
|
{ type: ECSTokenType.Integer, regex: /[-]?\$[A-Fa-f0-9]+/ },
|
2022-01-28 17:22:59 +00:00
|
|
|
{ type: ECSTokenType.Integer, regex: /[-]?\d+/ },
|
2022-02-02 17:34:55 +00:00
|
|
|
{ type: ECSTokenType.Operator, regex: /[#=,:(){}\[\]\-]/ },
|
2022-01-30 16:48:56 +00:00
|
|
|
{ type: TokenType.Ident, regex: /[A-Za-z_][A-Za-z0-9_]*/ },
|
2022-02-01 15:13:37 +00:00
|
|
|
{ type: TokenType.Ignore, regex: /\/\/.*?[\n\r]/ },
|
|
|
|
{ type: TokenType.Ignore, regex: /\/\*.*?\*\// },
|
2022-01-28 17:22:59 +00:00
|
|
|
{ type: TokenType.Ignore, regex: /\s+/ },
|
|
|
|
]);
|
|
|
|
this.errorOnCatchAll = true;
|
|
|
|
}
|
2022-01-30 16:48:56 +00:00
|
|
|
|
|
|
|
annotate<T extends SourceLocated>(fn: () => T) {
|
|
|
|
let tok = this.peekToken();
|
|
|
|
let obj = fn();
|
|
|
|
if (obj) (obj as SourceLocated).$loc = tok.$loc;
|
|
|
|
return obj;
|
|
|
|
}
|
2022-01-28 17:22:59 +00:00
|
|
|
|
|
|
|
parseFile(text: string, path: string) {
|
|
|
|
this.tokenizeFile(text, path);
|
|
|
|
while (!this.isEOF()) {
|
2022-02-01 15:13:37 +00:00
|
|
|
let top = this.parseTopLevel();
|
|
|
|
if (top) {
|
|
|
|
let t = top;
|
|
|
|
this.annotate(() => t); // TODO? typescript bug?
|
|
|
|
}
|
2022-01-28 17:22:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-03 02:06:44 +00:00
|
|
|
getImportFile: (path: string) => string;
|
|
|
|
|
|
|
|
importFile(path: string) {
|
2022-02-03 16:44:29 +00:00
|
|
|
if (!this.em.imported[path]) { // already imported?
|
|
|
|
let text = this.getImportFile && this.getImportFile(path);
|
|
|
|
if (!text) this.compileError(`I can't find the import file "${path}".`);
|
|
|
|
this.em.imported[path] = true;
|
2022-02-04 00:25:36 +00:00
|
|
|
let comp = new ECSCompiler(this.em);
|
2022-02-07 20:58:03 +00:00
|
|
|
comp.debuginfo = this.debuginfo; // TODO: clone compiler
|
2022-02-04 00:25:36 +00:00
|
|
|
try {
|
|
|
|
comp.parseFile(text, path);
|
|
|
|
} catch (e) {
|
2022-02-04 04:38:35 +00:00
|
|
|
for (var err of comp.errors) this.errors.push(err);
|
2022-02-04 00:25:36 +00:00
|
|
|
}
|
2022-02-03 16:44:29 +00:00
|
|
|
}
|
2022-02-03 02:06:44 +00:00
|
|
|
}
|
|
|
|
|
2022-01-28 17:22:59 +00:00
|
|
|
parseTopLevel() {
|
|
|
|
//this.skipBlankLines();
|
2022-02-03 02:06:44 +00:00
|
|
|
let tok = this.expectTokens(['component', 'system', 'scope', 'resource', 'import', 'demo', 'comment']);
|
2022-01-28 17:22:59 +00:00
|
|
|
if (tok.str == 'component') {
|
|
|
|
return this.em.defineComponent(this.parseComponentDefinition());
|
|
|
|
}
|
|
|
|
if (tok.str == 'system') {
|
|
|
|
return this.em.defineSystem(this.parseSystem());
|
|
|
|
}
|
|
|
|
if (tok.str == 'scope') {
|
|
|
|
return this.parseScope();
|
|
|
|
}
|
2022-01-31 18:11:50 +00:00
|
|
|
if (tok.str == 'resource') {
|
|
|
|
return this.em.defineSystem(this.parseResource());
|
|
|
|
}
|
2022-02-03 02:06:44 +00:00
|
|
|
if (tok.str == 'import') {
|
|
|
|
let tok = this.expectTokenTypes([ECSTokenType.QuotedString]);
|
|
|
|
let path = tok.str.substring(1, tok.str.length-1);
|
|
|
|
return this.importFile(path);
|
|
|
|
}
|
|
|
|
if (tok.str == 'demo') {
|
|
|
|
let scope = this.parseScope();
|
|
|
|
scope.isDemo = true;
|
|
|
|
return scope;
|
|
|
|
}
|
2022-01-28 17:22:59 +00:00
|
|
|
if (tok.str == 'comment') {
|
2022-02-01 15:13:37 +00:00
|
|
|
this.expectTokenTypes([ECSTokenType.CodeFragment]);
|
2022-01-28 17:22:59 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.compileError(`Unexpected top-level keyword: ${tok.str}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
parseComponentDefinition(): ComponentType {
|
|
|
|
let name = this.expectIdent().str;
|
|
|
|
let fields = [];
|
|
|
|
while (this.peekToken().str != 'end') {
|
|
|
|
fields.push(this.parseComponentField());
|
|
|
|
}
|
|
|
|
this.expectToken('end');
|
|
|
|
return { name, fields };
|
|
|
|
}
|
|
|
|
|
|
|
|
parseComponentField(): DataField {
|
|
|
|
let name = this.expectIdent();
|
2022-01-29 17:34:26 +00:00
|
|
|
this.expectToken(':', 'I expected either a ":" or "end" here.'); // TODO
|
2022-01-28 17:22:59 +00:00
|
|
|
let type = this.parseDataType();
|
|
|
|
return { name: name.str, ...type };
|
|
|
|
}
|
|
|
|
|
|
|
|
parseDataType(): DataType {
|
|
|
|
if (this.peekToken().type == 'integer') {
|
|
|
|
let lo = this.expectInteger();
|
|
|
|
this.expectToken('..');
|
|
|
|
let hi = this.expectInteger();
|
|
|
|
return { dtype: 'int', lo, hi } as IntType;
|
|
|
|
}
|
|
|
|
if (this.peekToken().str == '[') {
|
|
|
|
return { dtype: 'ref', query: this.parseQuery() } as RefType;
|
|
|
|
}
|
2022-02-05 01:48:08 +00:00
|
|
|
if (this.ifToken('array')) {
|
2022-02-01 15:13:37 +00:00
|
|
|
let index : IntType | undefined = undefined;
|
2022-01-31 18:11:50 +00:00
|
|
|
if (this.peekToken().type == ECSTokenType.Integer) {
|
2022-01-31 19:28:55 +00:00
|
|
|
index = this.parseDataType() as IntType;
|
2022-01-31 18:11:50 +00:00
|
|
|
}
|
2022-01-28 17:22:59 +00:00
|
|
|
this.expectToken('of');
|
2022-02-05 01:48:08 +00:00
|
|
|
let elem = this.parseDataType();
|
|
|
|
let baseoffset;
|
|
|
|
if (this.ifToken('baseoffset')) {
|
|
|
|
baseoffset = this.expectInteger();
|
|
|
|
}
|
|
|
|
return { dtype: 'array', index, elem, baseoffset } as ArrayType;
|
2022-01-28 17:22:59 +00:00
|
|
|
}
|
2022-02-05 05:56:43 +00:00
|
|
|
this.compileError(`I expected a data type here.`); throw new Error();
|
2022-01-28 17:22:59 +00:00
|
|
|
}
|
|
|
|
|
2022-01-29 15:15:44 +00:00
|
|
|
parseDataValue(field: DataField) : DataValue {
|
2022-01-29 03:13:33 +00:00
|
|
|
let tok = this.peekToken();
|
|
|
|
if (tok.type == 'integer') {
|
2022-01-28 17:22:59 +00:00
|
|
|
return this.expectInteger();
|
|
|
|
}
|
2022-01-29 03:13:33 +00:00
|
|
|
if (tok.str == '[') {
|
|
|
|
// TODO: 16-bit?
|
|
|
|
return new Uint8Array(this.parseDataArray());
|
|
|
|
}
|
|
|
|
if (tok.str == '#') {
|
2022-01-29 15:15:44 +00:00
|
|
|
let reftype = field.dtype == 'ref' ? field as RefType : undefined;
|
2022-01-29 03:13:33 +00:00
|
|
|
let e = this.parseEntityRef();
|
2022-01-29 15:15:44 +00:00
|
|
|
let id = e.id;
|
|
|
|
if (reftype) {
|
|
|
|
// TODO: make this a function? elo ehi etc?
|
2022-02-01 15:13:37 +00:00
|
|
|
if (!this.currentScope) {
|
|
|
|
this.compileError("This type can only exist inside of a scope."); throw new Error()
|
|
|
|
};
|
2022-01-29 15:15:44 +00:00
|
|
|
let atypes = this.em.archetypesMatching(reftype.query);
|
|
|
|
let entities = this.currentScope.entitiesMatching(atypes);
|
|
|
|
if (entities.length == 0) this.compileError(`This entitiy doesn't seem to fit the reference type.`);
|
|
|
|
id -= entities[0].id;
|
|
|
|
}
|
|
|
|
return id;
|
2022-01-29 03:13:33 +00:00
|
|
|
}
|
2022-02-05 05:56:43 +00:00
|
|
|
this.compileError(`I expected a ${field.dtype} here.`); throw new Error();
|
2022-01-28 17:22:59 +00:00
|
|
|
}
|
|
|
|
|
2022-01-29 03:13:33 +00:00
|
|
|
parseDataArray() {
|
|
|
|
this.expectToken('[');
|
|
|
|
let arr = this.parseList(this.expectInteger, ',');
|
|
|
|
this.expectToken(']');
|
|
|
|
return arr;
|
|
|
|
}
|
|
|
|
|
2022-01-28 17:22:59 +00:00
|
|
|
expectInteger(): number {
|
2022-01-29 03:13:33 +00:00
|
|
|
let s = this.consumeToken().str;
|
|
|
|
if (s.startsWith('$')) s = '0x' + s.substring(1);
|
|
|
|
let i = parseInt(s);
|
2022-01-28 17:22:59 +00:00
|
|
|
if (isNaN(i)) this.compileError('There should be an integer here.');
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
|
|
|
|
parseSystem(): System {
|
|
|
|
let name = this.expectIdent().str;
|
|
|
|
let actions: Action[] = [];
|
|
|
|
let system: System = { name, actions };
|
|
|
|
let cmd;
|
2022-01-30 16:48:56 +00:00
|
|
|
while ((cmd = this.expectTokens(['on','locals','end']).str) != 'end') {
|
2022-01-28 17:22:59 +00:00
|
|
|
if (cmd == 'on') {
|
2022-01-30 16:48:56 +00:00
|
|
|
let action = this.annotate(() => this.parseAction());
|
|
|
|
actions.push(action);
|
2022-01-28 17:22:59 +00:00
|
|
|
} else if (cmd == 'locals') {
|
|
|
|
system.tempbytes = this.expectInteger();
|
|
|
|
} else {
|
|
|
|
this.compileError(`Unexpected system keyword: ${cmd}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return system;
|
|
|
|
}
|
|
|
|
|
2022-01-31 18:11:50 +00:00
|
|
|
parseResource(): System {
|
|
|
|
let name = this.expectIdent().str;
|
|
|
|
let tempbytes;
|
|
|
|
if (this.peekToken().str == 'locals') {
|
|
|
|
this.consumeToken();
|
|
|
|
tempbytes = this.expectInteger();
|
|
|
|
}
|
|
|
|
let text = this.parseCode();
|
|
|
|
let select : SelectType = 'once';
|
2022-02-03 13:44:47 +00:00
|
|
|
let action : Action = { text, event: name, select };
|
2022-01-31 18:11:50 +00:00
|
|
|
return { name, tempbytes, actions: [action] };
|
|
|
|
}
|
|
|
|
|
2022-01-28 17:22:59 +00:00
|
|
|
parseAction(): Action {
|
2022-01-30 16:48:56 +00:00
|
|
|
// TODO: unused events?
|
2022-02-08 12:18:28 +00:00
|
|
|
const event = this.expectIdent().str;
|
2022-01-28 17:22:59 +00:00
|
|
|
this.expectToken('do');
|
2022-02-08 12:18:28 +00:00
|
|
|
const all_modifiers = ['cyclecritical','asc','desc']; // TODO
|
|
|
|
const modifiers = this.parseModifiers(all_modifiers);
|
|
|
|
// TODO: include modifiers in error msg
|
|
|
|
const select = this.expectTokens(SELECT_TYPE).str as SelectType; // TODO: type check?
|
2022-02-03 13:44:47 +00:00
|
|
|
let query = undefined;
|
2022-02-01 15:13:37 +00:00
|
|
|
let join = undefined;
|
2022-02-07 20:58:03 +00:00
|
|
|
if (select == 'once') {
|
2022-02-08 12:18:28 +00:00
|
|
|
if (this.peekToken().str == '[') this.compileError(`A "${select}" action can't include a query.`)
|
2022-02-07 20:58:03 +00:00
|
|
|
} else {
|
2022-02-03 13:44:47 +00:00
|
|
|
query = this.parseQuery();
|
|
|
|
}
|
|
|
|
if (select == 'join') {
|
|
|
|
this.expectToken('with');
|
|
|
|
join = this.parseQuery();
|
|
|
|
}
|
2022-02-05 05:56:43 +00:00
|
|
|
if (this.ifToken('limit')) {
|
2022-02-03 20:46:10 +00:00
|
|
|
if (!query) { this.compileError(`A "${select}" query can't include a limit.`); }
|
|
|
|
else query.limit = this.expectInteger();
|
2022-01-30 15:01:55 +00:00
|
|
|
}
|
2022-01-28 17:22:59 +00:00
|
|
|
let text = this.parseCode();
|
2022-02-08 12:18:28 +00:00
|
|
|
let direction = undefined;
|
|
|
|
if (modifiers['asc']) direction = 'asc';
|
|
|
|
else if (modifiers['desc']) direction = 'desc';
|
|
|
|
let action = { text, event, query, join, select, direction };
|
|
|
|
return action as ActionWithJoin;
|
2022-01-28 17:22:59 +00:00
|
|
|
}
|
2022-01-29 03:13:33 +00:00
|
|
|
|
2022-01-28 17:22:59 +00:00
|
|
|
parseQuery() {
|
|
|
|
let q: Query = { include: [] };
|
2022-01-31 15:17:40 +00:00
|
|
|
let start = this.expectToken('[');
|
2022-02-03 16:44:29 +00:00
|
|
|
this.parseList(() => this.parseQueryItem(q), ',');
|
2022-02-02 17:34:55 +00:00
|
|
|
this.expectToken(']');
|
2022-01-28 17:22:59 +00:00
|
|
|
// TODO: other params
|
2022-02-02 17:34:55 +00:00
|
|
|
q.$loc = mergeLocs(start.$loc, this.lasttoken.$loc);
|
2022-01-28 17:22:59 +00:00
|
|
|
return q;
|
|
|
|
}
|
|
|
|
|
2022-02-03 16:44:29 +00:00
|
|
|
parseQueryItem(q: Query) {
|
|
|
|
let prefix = this.peekToken();
|
|
|
|
if (prefix.type != TokenType.Ident) {
|
|
|
|
this.consumeToken();
|
|
|
|
}
|
|
|
|
let cref = this.parseComponentRef();
|
|
|
|
if (prefix.type == TokenType.Ident) {
|
|
|
|
q.include.push(cref);
|
|
|
|
} else if (prefix.str == '-') {
|
|
|
|
if (!q.exclude) q.exclude = [];
|
|
|
|
q.exclude.push(cref);
|
|
|
|
} else {
|
|
|
|
this.compileError(`Query components may be preceded only by a '-'.`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-07 20:58:03 +00:00
|
|
|
parseEventName() {
|
2022-01-28 17:22:59 +00:00
|
|
|
return this.expectIdent().str;
|
|
|
|
}
|
|
|
|
|
2022-01-29 03:13:33 +00:00
|
|
|
parseEventList() {
|
2022-02-07 20:58:03 +00:00
|
|
|
return this.parseList(this.parseEventName, ",");
|
2022-01-29 03:13:33 +00:00
|
|
|
}
|
|
|
|
|
2022-01-28 17:22:59 +00:00
|
|
|
parseCode(): string {
|
2022-01-30 00:21:38 +00:00
|
|
|
// TODO: add $loc
|
2022-02-01 15:13:37 +00:00
|
|
|
let tok = this.expectTokenTypes([ECSTokenType.CodeFragment]);
|
|
|
|
let code = tok.str.substring(3, tok.str.length-3);
|
2022-01-29 18:24:38 +00:00
|
|
|
let lines = code.split('\n');
|
2022-02-07 20:58:03 +00:00
|
|
|
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
|
2022-01-29 18:24:38 +00:00
|
|
|
for (let i=0; i<lines.length; i++) {
|
2022-02-04 17:45:14 +00:00
|
|
|
if (!lines[i].match(re))
|
2022-02-07 20:58:03 +00:00
|
|
|
lines[i] = this.em.dialect.debug_line(this.path, startline+i) + '\n' + lines[i];
|
2022-01-29 18:24:38 +00:00
|
|
|
}
|
2022-01-28 17:22:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
parseScope() : EntityScope {
|
|
|
|
let name = this.expectIdent().str;
|
2022-02-01 15:13:37 +00:00
|
|
|
let scope = this.em.newScope(name, this.currentScope || undefined);
|
2022-02-03 02:06:44 +00:00
|
|
|
scope.filePath = this.path;
|
2022-01-28 17:22:59 +00:00
|
|
|
this.currentScope = scope;
|
|
|
|
let cmd;
|
2022-02-03 23:14:03 +00:00
|
|
|
while ((cmd = this.expectTokens(['end', 'using', 'entity', 'scope', 'comment', 'system']).str) != 'end') {
|
2022-02-02 22:32:04 +00:00
|
|
|
if (cmd == 'using') {
|
|
|
|
this.parseScopeUsing();
|
|
|
|
}
|
2022-01-28 17:22:59 +00:00
|
|
|
if (cmd == 'entity') {
|
2022-01-30 16:48:56 +00:00
|
|
|
this.annotate(() => this.parseEntity());
|
|
|
|
}
|
2022-02-02 23:03:22 +00:00
|
|
|
if (cmd == 'scope') {
|
|
|
|
this.annotate(() => this.parseScope());
|
|
|
|
}
|
2022-01-30 16:48:56 +00:00
|
|
|
if (cmd == 'comment') {
|
2022-02-01 15:13:37 +00:00
|
|
|
this.expectTokenTypes([ECSTokenType.CodeFragment]);
|
2022-01-28 17:22:59 +00:00
|
|
|
}
|
2022-02-03 23:14:03 +00:00
|
|
|
if (cmd == 'system') {
|
|
|
|
let sys = this.annotate(() => this.parseSystem());
|
|
|
|
this.em.defineSystem(sys);
|
|
|
|
this.currentScope.addUsingSystem(sys);
|
2022-02-03 22:04:48 +00:00
|
|
|
}
|
2022-01-28 17:22:59 +00:00
|
|
|
}
|
2022-02-01 15:13:37 +00:00
|
|
|
this.currentScope = scope.parent || null;
|
2022-01-28 17:22:59 +00:00
|
|
|
return scope;
|
|
|
|
}
|
|
|
|
|
2022-02-02 22:32:04 +00:00
|
|
|
parseScopeUsing() {
|
|
|
|
let syslist = this.parseList(this.parseSystemRef, ',');
|
|
|
|
for (let sys of syslist) {
|
2022-02-03 22:04:48 +00:00
|
|
|
this.currentScope?.addUsingSystem(sys);
|
2022-02-02 22:32:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-28 17:22:59 +00:00
|
|
|
parseEntity() : Entity {
|
2022-02-01 15:13:37 +00:00
|
|
|
if (!this.currentScope) { this.internalError(); throw new Error(); }
|
2022-02-05 05:56:43 +00:00
|
|
|
let entname = '';
|
2022-01-28 17:22:59 +00:00
|
|
|
if (this.peekToken().type == TokenType.Ident) {
|
2022-02-05 05:56:43 +00:00
|
|
|
entname = this.expectIdent().str;
|
2022-01-28 17:22:59 +00:00
|
|
|
}
|
|
|
|
let etype = this.parseEntityArchetype();
|
2022-02-05 05:56:43 +00:00
|
|
|
let entity = this.currentScope.newEntity(etype);
|
|
|
|
entity.name = entname;
|
2022-01-28 17:22:59 +00:00
|
|
|
let cmd;
|
2022-02-04 17:45:14 +00:00
|
|
|
// TODO: remove init?
|
2022-02-05 05:56:43 +00:00
|
|
|
while ((cmd = this.expectTokens(['const', 'init', 'var', 'decode', 'end']).str) != 'end') {
|
2022-02-04 17:45:14 +00:00
|
|
|
if (cmd == 'var') cmd = 'init';
|
2022-02-05 05:56:43 +00:00
|
|
|
if (cmd == 'init' || cmd == 'const') {
|
|
|
|
// TODO: check data types
|
|
|
|
let name = this.expectIdent().str;
|
|
|
|
this.setEntityProperty(entity, name, cmd, (field) : DataValue => {
|
|
|
|
this.expectToken('=');
|
|
|
|
return this.parseDataValue(field);
|
|
|
|
});
|
|
|
|
} else if (cmd == 'decode') {
|
|
|
|
let decoderid = this.expectIdent().str;
|
|
|
|
let code = this.expectTokenTypes([ECSTokenType.CodeFragment]).str;
|
|
|
|
code = code.substring(3, code.length-3);
|
|
|
|
let decoder = newDecoder(decoderid, code);
|
|
|
|
if (!decoder) { this.compileError(`I can't find a "${decoderid}" decoder.`); throw new Error() }
|
|
|
|
let result = decoder.parse();
|
|
|
|
for (let entry of Object.entries(result.properties)) {
|
|
|
|
this.setEntityProperty(entity, entry[0], 'const', (field) : DataValue => {
|
|
|
|
return entry[1];
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2022-01-28 17:22:59 +00:00
|
|
|
}
|
2022-02-05 05:56:43 +00:00
|
|
|
return entity;
|
|
|
|
}
|
|
|
|
|
|
|
|
setEntityProperty(e: Entity, name: string, cmd: 'init' | 'const', valuefn: (field: DataField) => DataValue) {
|
|
|
|
if (!this.currentScope) { this.internalError(); throw new Error(); }
|
2022-02-06 17:27:08 +00:00
|
|
|
let comps = this.em.componentsWithFieldName([e.etype], name);
|
2022-02-05 05:56:43 +00:00
|
|
|
if (comps.length == 0) this.compileError(`I couldn't find a field named "${name}" for this entity.`)
|
|
|
|
if (comps.length > 1) this.compileError(`I found more than one field named "${name}" for this entity.`)
|
|
|
|
let field = comps[0].fields.find(f => f.name == name);
|
|
|
|
if (!field) { this.internalError(); throw new Error(); }
|
|
|
|
let value = valuefn(field);
|
|
|
|
let symtype = this.currentScope.isConstOrInit(comps[0], name);
|
|
|
|
if (symtype && symtype != cmd)
|
|
|
|
this.compileError(`I can't mix const and init values for a given field in a scope.`);
|
|
|
|
if (cmd == 'const') this.currentScope.setConstValue(e, comps[0], name, value);
|
|
|
|
if (cmd == 'init') this.currentScope.setInitValue(e, comps[0], name, value);
|
2022-01-28 17:22:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
parseEntityArchetype() : EntityArchetype {
|
|
|
|
this.expectToken('[');
|
|
|
|
let components = this.parseList(this.parseComponentRef, ',');
|
|
|
|
this.expectToken(']');
|
|
|
|
return {components};
|
|
|
|
}
|
|
|
|
|
|
|
|
parseComponentRef() : ComponentType {
|
|
|
|
let name = this.expectIdent().str;
|
|
|
|
let cref = this.em.getComponentByName(name);
|
|
|
|
if (!cref) this.compileError(`I couldn't find a component named "${name}".`)
|
|
|
|
return cref;
|
|
|
|
}
|
|
|
|
|
2022-01-29 15:15:44 +00:00
|
|
|
parseEntityRef(reftype?: RefType) : Entity {
|
2022-02-01 15:13:37 +00:00
|
|
|
if (!this.currentScope) { this.internalError(); throw new Error(); }
|
2022-01-29 03:13:33 +00:00
|
|
|
this.expectToken('#');
|
|
|
|
let name = this.expectIdent().str;
|
|
|
|
let eref = this.currentScope.entities.find(e => e.name == name);
|
2022-02-01 15:13:37 +00:00
|
|
|
if (!eref) {
|
|
|
|
this.compileError(`I couldn't find an entity named "${name}" in this scope.`)
|
|
|
|
throw new Error();
|
|
|
|
}
|
2022-01-29 03:13:33 +00:00
|
|
|
return eref;
|
|
|
|
}
|
|
|
|
|
2022-02-02 22:32:04 +00:00
|
|
|
parseSystemRef() : System {
|
|
|
|
let name = this.expectIdent().str;
|
|
|
|
let sys = this.em.getSystemByName(name);
|
|
|
|
if (!sys) this.compileError(`I couldn't find a system named "${name}".`, this.lasttoken.$loc);
|
|
|
|
return sys;
|
|
|
|
}
|
|
|
|
|
2022-01-28 17:22:59 +00:00
|
|
|
exportToFile(src: SourceFileExport) {
|
2022-02-02 22:32:04 +00:00
|
|
|
this.em.exportToFile(src);
|
2022-01-28 17:22:59 +00:00
|
|
|
}
|
2022-01-29 17:34:26 +00:00
|
|
|
|
|
|
|
export() {
|
|
|
|
let src = new SourceFileExport();
|
2022-02-04 17:45:14 +00:00
|
|
|
src.line(this.em.dialect.debug_file(this.path));
|
2022-02-04 00:25:36 +00:00
|
|
|
for (let path of Object.keys(this.em.imported))
|
2022-02-04 17:45:14 +00:00
|
|
|
src.line(this.em.dialect.debug_file(path));
|
2022-01-29 17:34:26 +00:00
|
|
|
this.exportToFile(src);
|
|
|
|
return src.toString();
|
|
|
|
}
|
2022-01-28 17:22:59 +00:00
|
|
|
}
|