1
0
mirror of https://github.com/sehugg/8bitworkshop.git synced 2024-06-08 23:29:42 +00:00
8bitworkshop/src/common/ecs/compiler.ts

392 lines
14 KiB
TypeScript
Raw Normal View History

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-03 13:44:47 +00:00
import { Action, ArrayType, ComponentType, DataField, DataType, DataValue, Entity, EntityArchetype, EntityManager, EntityScope, IntType, Query, RefType, SelectType, SourceFileExport, System } from "./ecs";
export enum ECSTokenType {
Ellipsis = 'ellipsis',
Operator = 'delimiter',
QuotedString = 'quoted-string',
Integer = 'integer',
2022-02-01 15:13:37 +00:00
CodeFragment = 'code-fragment',
}
export class ECSCompiler extends Tokenizer {
currentScope: EntityScope | null = null;
2022-02-03 02:06:44 +00:00
constructor(
public readonly em: EntityManager)
{
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]+/ },
{ 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: /\/\*.*?\*\// },
{ 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;
}
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-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}".`);
new ECSCompiler(this.em).parseFile(path, text);
this.em.imported[path] = true;
}
2022-02-03 02:06:44 +00:00
}
parseTopLevel() {
//this.skipBlankLines();
2022-02-03 02:06:44 +00:00
let tok = this.expectTokens(['component', 'system', 'scope', 'resource', 'import', 'demo', 'comment']);
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;
}
if (tok.str == 'comment') {
2022-02-01 15:13:37 +00:00
this.expectTokenTypes([ECSTokenType.CodeFragment]);
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
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;
}
if (this.peekToken().str == 'array') {
2022-02-01 15:13:37 +00:00
let index : IntType | undefined = undefined;
this.expectToken('array');
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
}
this.expectToken('of');
2022-01-31 19:28:55 +00:00
return { dtype: 'array', index, elem: this.parseDataType() } as ArrayType;
}
2022-02-01 15:13:37 +00:00
this.internalError(); throw new Error();
}
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') {
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-01 15:13:37 +00:00
this.internalError(); throw new Error();
}
2022-01-29 03:13:33 +00:00
parseDataArray() {
this.expectToken('[');
let arr = this.parseList(this.expectInteger, ',');
this.expectToken(']');
return arr;
}
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);
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') {
if (cmd == 'on') {
2022-01-30 16:48:56 +00:00
let action = this.annotate(() => this.parseAction());
actions.push(action);
} 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] };
}
parseAction(): Action {
2022-01-30 16:48:56 +00:00
// TODO: unused events?
let event = this.expectIdent().str;
this.expectToken('do');
2022-02-03 13:44:47 +00:00
let select = this.expectTokens(
2022-02-03 15:24:00 +00:00
['once', 'foreach', 'join', 'with', 'if', 'select']).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-03 13:44:47 +00:00
if (select != 'once') {
query = this.parseQuery();
}
if (select == 'join') {
this.expectToken('with');
join = this.parseQuery();
}
2022-01-29 03:13:33 +00:00
let emits;
let limit;
if (this.peekToken().str == 'limit') {
this.consumeToken();
if (!query) { this.compileError(`A "${select}" query can't include a limit.`); }
else query.limit = this.expectInteger();
}
2022-01-29 03:13:33 +00:00
if (this.peekToken().str == 'emit') {
this.consumeToken();
this.expectToken('(');
emits = this.parseEventList();
this.expectToken(')');
}
let text = this.parseCode();
let action = { text, event, query, join, select };
2022-02-03 13:44:47 +00:00
return action as Action;
}
2022-01-29 03:13:33 +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(']');
// TODO: other params
2022-02-02 17:34:55 +00:00
q.$loc = mergeLocs(start.$loc, this.lasttoken.$loc);
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 '-'.`);
}
}
parseEvent() {
return this.expectIdent().str;
}
2022-01-29 03:13:33 +00:00
parseEventList() {
return this.parseList(this.parseEvent, ",");
}
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');
for (let i=0; i<lines.length; i++) {
lines[i] = ` .dbg line, "${this.path}", ${tok.$loc.line+i}\n` + lines[i];
}
return lines.join('\n');
}
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;
this.currentScope = scope;
let cmd;
while ((cmd = this.expectTokens(['using', 'entity', 'scope', 'comment', 'end']).str) != 'end') {
if (cmd == 'using') {
this.parseScopeUsing();
}
if (cmd == 'entity') {
2022-01-30 16:48:56 +00:00
this.annotate(() => this.parseEntity());
}
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-02-01 15:13:37 +00:00
this.currentScope = scope.parent || null;
return scope;
}
parseScopeUsing() {
let syslist = this.parseList(this.parseSystemRef, ',');
for (let sys of syslist) {
this.currentScope?.systems.push(sys);
}
}
parseEntity() : Entity {
2022-02-01 15:13:37 +00:00
if (!this.currentScope) { this.internalError(); throw new Error(); }
let name = '';
if (this.peekToken().type == TokenType.Ident) {
name = this.expectIdent().str;
}
let etype = this.parseEntityArchetype();
let e = this.currentScope.newEntity(etype);
e.name = name;
let cmd;
2022-01-30 16:48:56 +00:00
while ((cmd = this.expectTokens(['const', 'init', 'end']).str) != 'end') {
// TODO: check data types
2022-01-30 16:48:56 +00:00
let name = this.expectIdent().str;
let comps = this.em.componentsWithFieldName([{etype: e.etype, cmatch:e.etype.components}], name);
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);
2022-02-01 15:13:37 +00:00
if (!field) { this.internalError(); throw new Error(); }
2022-01-30 16:48:56 +00:00
this.expectToken('=');
let value = this.parseDataValue(field);
if (cmd == 'const') this.currentScope.setConstValue(e, comps[0], name, value);
if (cmd == 'init') this.currentScope.setInitValue(e, comps[0], name, value);
}
return e;
}
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;
}
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;
}
exportToFile(src: SourceFileExport) {
this.em.exportToFile(src);
}
2022-01-29 17:34:26 +00:00
export() {
let src = new SourceFileExport();
2022-01-29 18:24:38 +00:00
src.debug_file(this.path);
2022-01-29 17:34:26 +00:00
this.exportToFile(src);
return src.toString();
}
}