From fc79cf9b0f50adc92fe38f33fb272029e50b099c Mon Sep 17 00:00:00 2001 From: Steven Hugg Date: Fri, 25 Feb 2022 11:45:13 -0600 Subject: [PATCH] ecs: block statement expr --- src/common/ecs/compiler.ts | 45 +++++++++++++++++---------- src/common/ecs/ecs.ts | 64 +++++++++++++++++++++++++++----------- 2 files changed, 75 insertions(+), 34 deletions(-) diff --git a/src/common/ecs/compiler.ts b/src/common/ecs/compiler.ts index a0de4bfc..d3dd6809 100644 --- a/src/common/ecs/compiler.ts +++ b/src/common/ecs/compiler.ts @@ -2,7 +2,7 @@ import { mergeLocs, Token, Tokenizer, TokenType } from "../tokenizer"; import { SourceLocated, SourceLocation } from "../workertypes"; import { newDecoder } from "./decoder"; -import { Action, ActionContext, ActionNode, ActionWithJoin, ArrayType, CodeLiteralNode, CodePlaceholderNode, ComponentType, DataField, DataType, DataValue, ECSError, Entity, EntityArchetype, EntityManager, EntityScope, IntType, Query, RefType, SelectType, SELECT_TYPE, SourceFileExport, System, SystemInstance, SystemInstanceParameters, ComponentFieldPair, Expr, ExprBase, ForwardRef, isLiteral, EntitySetField, LExpr } from "./ecs"; +import { Action, ActionContext, ActionNode, ActionWithJoin, ArrayType, CodeLiteralNode, CodePlaceholderNode, ComponentType, DataField, DataType, DataValue, ECSError, Entity, EntityArchetype, EntityManager, EntityScope, IntType, Query, RefType, SelectType, SELECT_TYPE, SourceFileExport, System, SystemInstance, SystemInstanceParameters, ComponentFieldPair, Expr, ExprBase, ForwardRef, isLiteral, EntityFieldOp, LExpr, Statement } from "./ecs"; export enum ECSTokenType { Ellipsis = 'ellipsis', @@ -349,10 +349,9 @@ export class ECSCompiler extends Tokenizer { tempbytes = this.parseIntegerConstant(); } let system: System = { name, tempbytes, actions: [] }; - let context: ActionContext = { scope: null, system }; - let text = this.parseCode(context); let select: SelectType = 'once'; - let action: Action = { text, event: name, select }; + let expr = this.parseBlockStatement(); + let action: Action = { expr, event: name, select }; system.actions.push(action); return system; } @@ -384,13 +383,11 @@ export class ECSCompiler extends Tokenizer { if (this.ifToken('fit')) { fitbytes = this.parseIntegerConstant(); } - let context: ActionContext = { scope: null, system }; - // parse --- code --- - let text = this.parseCode(context); let direction = undefined; if (modifiers['asc']) direction = 'asc'; else if (modifiers['desc']) direction = 'desc'; - let action = { text, event, query, join, select, direction, fitbytes }; + let expr = this.parseBlockStatement(); + let action = { expr, event, query, join, select, direction, fitbytes }; if (modifiers['critical']) (action as ActionWithJoin).critical = true; return action as ActionWithJoin; } @@ -441,7 +438,7 @@ export class ECSCompiler extends Tokenizer { return this.parseList(this.parseEventName, ","); } - parseCode(context: ActionContext): string { // TODOActionNode[] { + parseCode(): string { // TODOActionNode[] { // TODO: add $loc let tok = this.expectTokenTypes([ECSTokenType.CodeFragment]); let code = tok.str.substring(3, tok.str.length - 3); @@ -450,8 +447,8 @@ export class ECSCompiler extends Tokenizer { if (this.includeDebugInfo) this.addDebugInfo(lines, tok.$loc.line); code = lines.join('\n'); - let acomp = new ECSActionCompiler(context); - let nodes = acomp.parseFile(code, this.path); + //let acomp = new ECSActionCompiler(context); + //let nodes = acomp.parseFile(code, this.path); // TODO: return nodes return code; } @@ -694,8 +691,8 @@ export class ECSCompiler extends Tokenizer { } var opfn = getOperator(op.str).f; // use logical operators instead of bitwise? - if (op.str == 'AND') opfn = 'land'; - if (op.str == 'OR') opfn = 'lor'; + if (op.str == 'and') opfn = 'land'; + if (op.str == 'or') opfn = 'lor'; var valtype = this.exprTypeForOp(opfn, left, right, op); left = { valtype:valtype, op:opfn, left: left, right: right }; } @@ -710,7 +707,7 @@ export class ECSCompiler extends Tokenizer { let valtype : IntType = { dtype: 'int', lo: value, hi: value }; return { valtype, value }; case TokenType.Ident: - if (tok.str == 'NOT') { + if (tok.str == 'not') { let expr = this.parsePrimary(); let valtype : IntType = { dtype: 'int', lo: 0, hi: 1 }; return { valtype, op: 'lnot', expr: expr }; @@ -752,7 +749,7 @@ export class ECSCompiler extends Tokenizer { if (!this.currentScope) throw this.compileError(`This operation only works inside of a scope.`); let atypes = this.em.archetypesMatching({ include: [component] }) let entities = this.currentScope.entitiesMatching(atypes); - return { entities, field } as EntitySetField; + return { entities, field } as EntityFieldOp; } // entity.field if (this.ifToken('.')) { @@ -764,7 +761,7 @@ export class ECSCompiler extends Tokenizer { let field = component.fields.find(f => f.name == ftok.str); if (!field) throw this.compileError(`There is no "${ftok.str}" field in this entity.`); let entities = [entity]; - return { entities, field } as EntitySetField; + return { entities, field } as EntityFieldOp; } let args : Expr[] = []; if (this.ifToken('(')) { @@ -796,6 +793,22 @@ export class ECSCompiler extends Tokenizer { parseExprList(): Expr[] { return this.parseList(this.parseExpr, ','); } + parseBlockStatement(): Statement { + let valtype : IntType = { dtype:'int', lo:0, hi: 0 } + if (this.peekToken().type == ECSTokenType.CodeFragment) { + return { valtype, code: this.parseCode() }; + } + let cmd = this.expectTokens(['end','begin']); + if (cmd.str == 'begin') { + let stmts = []; + while (this.peekToken().str != 'end') { + stmts.push(this.parseBlockStatement()); + } + this.expectToken('end'); + return { valtype, stmts }; + } + throw this.internalError(); + } } /// diff --git a/src/common/ecs/ecs.ts b/src/common/ecs/ecs.ts index 8a91864e..7f3c2a21 100644 --- a/src/common/ecs/ecs.ts +++ b/src/common/ecs/ecs.ts @@ -78,6 +78,7 @@ export const SELECT_TYPE = ['once', 'foreach', 'join', 'with', 'if', 'select', ' export type SelectType = typeof SELECT_TYPE[number]; +// TODO? export interface ActionContext { system: System scope: EntityScope | null @@ -112,20 +113,10 @@ export class CodePlaceholderNode extends ActionNode { } } -class QueryNode extends ActionNode { -} - -class WrapperNode extends ActionNode { -} - -class LoopNode extends ActionNode { -} - - export interface ActionBase extends SourceLocated { select: SelectType; event: string; - text: string; + expr: Statement; critical?: boolean; fitbytes?: number; } @@ -204,18 +195,17 @@ export interface ForwardRef extends SourceLocated { token: Token } -export type LExpr = IndOp | EntitySetField; -export type ExprTypes = BinOp | UnOp | Literal | ForwardRef | LExpr; -export type Expr = ExprTypes; // & SourceLocated; +export type LExpr = IndOp | EntityFieldOp; +export type Statement = InlineCode | BlockExpr; +export type Expr = BinOp | UnOp | Literal | ForwardRef | LExpr | Statement; export type Opcode = string; -export type Value = DataValue; export interface ExprBase extends SourceLocated { valtype: DataType; } export interface Literal extends ExprBase { - value: Value; + value: DataValue; } export interface LiteralInt extends Literal { @@ -239,11 +229,34 @@ export interface IndOp extends ExprBase { args: Expr[]; } -export interface EntitySetField extends ExprBase { +export interface CondOp extends ExprBase { + cond: Expr; + iftrue?: Expr; + iffalse?: Expr; +} + +export interface BlockExpr extends ExprBase { + loop?: boolean; + stmts: Expr[]; +} + +export interface BranchOp extends ExprBase { + branch: BlockExpr; +} + +export interface EntityFieldOp extends ExprBase { entities: Entity[]; field: DataField; } +export interface EntityContextOp extends ExprBase { + entities: Entity[]; +} + +export interface InlineCode extends ExprBase { + code: string; +} + export function isLiteral(arg: Expr): arg is Literal { return (arg as any).value != null; } @@ -259,6 +272,12 @@ export function isBinOp(arg: Expr): arg is BinOp { export function isUnOp(arg: Expr): arg is UnOp { return (arg as any).op != null && (arg as any).expr != null; } +export function isBlockStmt(arg: Expr): arg is BlockExpr { + return (arg as any).stmts != null; +} +export function isInlineCode(arg: Expr): arg is InlineCode { + return (arg as any).code != null; +} /// DIALECT @@ -796,7 +815,7 @@ class ActionEval { return code; } private getCodeAndProps(action: Action) { - let code = action.text; + let code = this.exprToCode(action.expr); let props: { [name: string]: string } = {}; if (action.select != 'once') { // TODO: detect cycles @@ -1125,6 +1144,15 @@ class ActionEval { if (code.split('\n ').length >= 4) return true; // TODO: :^/ return false; } + exprToCode(expr: Expr) : string { + if (isBlockStmt(expr)) { + return expr.stmts.map(node => this.exprToCode(node)).join('\n'); + } + if (isInlineCode(expr)) { + return expr.code; + } + throw new ECSError(`cannot convert expression to code`, expr); + } } class EventCodeStats {