From 00106608155a478a7b6a69858464c03dbb499ab7 Mon Sep 17 00:00:00 2001 From: "Amy J. Ko" Date: Sat, 29 Jun 2024 22:31:24 -0700 Subject: [PATCH] Replaced `Bind` with `Input` in table parsing and evaluation. --- CHANGELOG.md | 2 +- src/components/editor/RowView.svelte | 1 - src/conflicts/InvalidRow.ts | 2 +- src/conflicts/MissingCell.ts | 10 ++--- src/nodes/Input.ts | 5 +++ src/nodes/Insert.ts | 7 ++-- src/nodes/Row.ts | 26 +++++++------ src/nodes/TableLiteral.ts | 8 ++-- src/nodes/Update.ts | 57 ++++++++++------------------ src/parser/parseBind.ts | 7 ++++ src/parser/parseExpression.ts | 11 +++--- 11 files changed, 68 insertions(+), 68 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 310c8d6e8..d688db97d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ Dates are in `YYYY-MM-DD` format and versions are in [semantic versioning](http: - [#504](https://github.com/wordplaydev/wordplay/issues/504). Account for non-fixed-width characters in caret positioning. - [#488](https://github.com/wordplaydev/wordplay/issues/488). Added animations off indicator on stage. - [#500](https://github.com/wordplaydev/wordplay/issues/500). Improved explanation when there's a space between an evaluation's name and inputs. -- [#455](https://github.com/wordplaydev/wordplay/issues/455). Replaced `Bind`s with `Input`'s in `Evaluate` to prevent invalid bind metadata in evaluations. +- [#455](https://github.com/wordplaydev/wordplay/issues/455). Replaced `Bind`s with `Input`'s in `Evaluate` and table operations to prevent invalid bind metadata in evaluations. ### Maintenance diff --git a/src/components/editor/RowView.svelte b/src/components/editor/RowView.svelte index 7abeba2e1..07da84e03 100644 --- a/src/components/editor/RowView.svelte +++ b/src/components/editor/RowView.svelte @@ -4,7 +4,6 @@ import type Row from '@nodes/Row'; import NodeSequenceView from './NodeSequenceView.svelte'; import NodeView from './NodeView.svelte'; - export let node: Row; diff --git a/src/conflicts/InvalidRow.ts b/src/conflicts/InvalidRow.ts index 002222032..c4680be6a 100644 --- a/src/conflicts/InvalidRow.ts +++ b/src/conflicts/InvalidRow.ts @@ -18,7 +18,7 @@ export default class InvalidRow extends Conflict { explanation: (locales: Locales) => concretize( locales, - locales.get((l) => l.node.Row.conflict.InvalidRow) + locales.get((l) => l.node.Row.conflict.InvalidRow), ), }, }; diff --git a/src/conflicts/MissingCell.ts b/src/conflicts/MissingCell.ts index 4072a9608..7bbe7fa16 100644 --- a/src/conflicts/MissingCell.ts +++ b/src/conflicts/MissingCell.ts @@ -28,14 +28,14 @@ export default class MissingCell extends Conflict { concretize( locales, locales.get( - (l) => l.node.Row.conflict.MissingCell.primary + (l) => l.node.Row.conflict.MissingCell.primary, ), new NodeRef( this.column, locales, context, - locales.getName(this.column.names) - ) + locales.getName(this.column.names), + ), ), }, secondary: { @@ -44,9 +44,9 @@ export default class MissingCell extends Conflict { concretize( locales, locales.get( - (l) => l.node.Row.conflict.MissingCell.secondary + (l) => l.node.Row.conflict.MissingCell.secondary, ), - new NodeRef(this.row, locales, context) + new NodeRef(this.row, locales, context), ), }, }; diff --git a/src/nodes/Input.ts b/src/nodes/Input.ts index 78fdd7246..4da91b9ba 100644 --- a/src/nodes/Input.ts +++ b/src/nodes/Input.ts @@ -109,6 +109,11 @@ export default class Input extends SimpleExpression { return this.value.getType(context); } + /** Never constant, as we always reevaluate functions. */ + isConstant(): boolean { + return false; + } + getDependencies(): Expression[] { return [this.value]; } diff --git a/src/nodes/Insert.ts b/src/nodes/Insert.ts index 2e35449dc..087fba06b 100644 --- a/src/nodes/Insert.ts +++ b/src/nodes/Insert.ts @@ -34,6 +34,7 @@ import { INSERT_SYMBOL, TABLE_CLOSE_SYMBOL } from '../parser/Symbols'; import Sym from './Sym'; import ExpressionPlaceholder from './ExpressionPlaceholder'; import type Locales from '../locale/Locales'; +import Input from './Input'; export default class Insert extends Expression { readonly table: Expression; @@ -132,12 +133,12 @@ export default class Insert extends Expression { // Rows can either be all unnamed and provide values for every column or they can be selectively named, // but must provide a value for all non-default columns. No other format is allowed. // Additionally, all values must match their column's types. - if (this.row.allBinds()) { + if (this.row.cells.every((c) => c instanceof Input)) { // Ensure every bind is a valid column. const matchedColumns = []; for (const cell of this.row.cells) { - if (cell instanceof Bind) { - const column = tableType.getColumnNamed(cell.getNames()[0]); + if (cell instanceof Input) { + const column = tableType.getColumnNamed(cell.getName()); if (column === undefined) conflicts.push(new UnknownColumn(tableType, cell)); else { diff --git a/src/nodes/Row.ts b/src/nodes/Row.ts index bf12d92ce..093975eae 100644 --- a/src/nodes/Row.ts +++ b/src/nodes/Row.ts @@ -1,6 +1,5 @@ import type { Grammar, Replacement } from './Node'; import Token from './Token'; -import Bind from './Bind'; import Expression from './Expression'; import Glyphs from '../lore/Glyphs'; import Purpose from '../concepts/Purpose'; @@ -15,13 +14,18 @@ import ValueException from '../values/ValueException'; import StructureValue from '../values/StructureValue'; import { TABLE_CLOSE_SYMBOL, TABLE_OPEN_SYMBOL } from '../parser/Symbols'; import type Locales from '../locale/Locales'; +import Input from './Input'; export default class Row extends Node { readonly open: Token; - readonly cells: Expression[]; + readonly cells: (Input | Expression)[]; readonly close: Token | undefined; - constructor(open: Token, cells: Expression[], close: Token | undefined) { + constructor( + open: Token, + cells: (Input | Expression)[], + close: Token | undefined, + ) { super(); this.open = open; @@ -35,7 +39,7 @@ export default class Row extends Node { return new Row( new Token(TABLE_OPEN_SYMBOL, Sym.TableOpen), cells, - new Token(TABLE_CLOSE_SYMBOL, Sym.TableClose) + new Token(TABLE_CLOSE_SYMBOL, Sym.TableClose), ); } @@ -52,7 +56,7 @@ export default class Row extends Node { node(Sym.Select), node(Sym.Insert), node(Sym.Delete), - node(Sym.Update) + node(Sym.Update), ), }, { name: 'cells', kind: list(true, node(Expression)), space: true }, @@ -64,7 +68,7 @@ export default class Row extends Node { return new Row( this.replaceChild('open', this.open, replace), this.replaceChild('cells', this.cells, replace), - this.replaceChild('close', this.close, replace) + this.replaceChild('close', this.close, replace), ) as this; } @@ -72,12 +76,12 @@ export default class Row extends Node { return Purpose.Bind; } - allBinds() { - return this.cells.every((cell) => cell instanceof Bind); + getDependencies() { + return [...this.cells]; } allExpressions() { - return this.cells.every((cell) => !(cell instanceof Bind)); + return this.cells.every((cell) => !(cell instanceof Input)); } computeConflicts() { @@ -98,13 +102,13 @@ export function getRowFromValues( evaluator: Evaluator, creator: EvaluationNode, table: TableType, - values: Value[] + values: Value[], ) { const evaluation = new Evaluation( evaluator, creator, table.definition, - evaluator.getCurrentEvaluation() + evaluator.getCurrentEvaluation(), ); for (let c = 0; c < table.columns.length; c++) { diff --git a/src/nodes/TableLiteral.ts b/src/nodes/TableLiteral.ts index 5d1a4edac..3aa9d1cdf 100644 --- a/src/nodes/TableLiteral.ts +++ b/src/nodes/TableLiteral.ts @@ -285,9 +285,11 @@ export default class TableLiteral extends Expression { * Is a binding enclosure of its columns and rows, because it defines columns. * */ getScopeOfChild(child: Node, context: Context): Node | undefined { - return this.rows.includes(child as Row) - ? this.type - : this.getParent(context); + return child instanceof Row + ? this.rows.includes(child) + ? this.type + : this.getParent(context) + : undefined; } evaluateTypeGuards(current: TypeSet, guard: GuardContext) { diff --git a/src/nodes/Update.ts b/src/nodes/Update.ts index 39482597b..6f3b2efb2 100644 --- a/src/nodes/Update.ts +++ b/src/nodes/Update.ts @@ -39,6 +39,7 @@ import { TABLE_CLOSE_SYMBOL, UPDATE_SYMBOL } from '../parser/Symbols'; import Sym from './Sym'; import ExpressionPlaceholder from './ExpressionPlaceholder'; import type Locales from '../locale/Locales'; +import Input from './Input'; type UpdateState = { table: TableValue; index: number; rows: StructureValue[] }; @@ -147,25 +148,12 @@ export default class Update extends Expression { } this.row.cells.forEach((cell) => { - // The columns in an update must be binds with expressions. - if ( - !( - cell instanceof Bind && - cell.value !== undefined && - cell.names.names.length === 1 - ) - ) + // The columns in an update must be inputs + if (!(cell instanceof Input)) conflicts.push(new ExpectedColumnBind(this, cell)); else if (tableType instanceof TableType) { - const alias = - cell instanceof Bind && cell.names.names.length > 0 - ? cell.names.names[0] - : undefined; - const name = alias === undefined ? undefined : alias.getName(); - const columnType = - name === undefined - ? undefined - : tableType.getColumnNamed(name); + const name = cell.getName(); + const columnType = tableType.getColumnNamed(name); // The named table column must exist. if (columnType === undefined) conflicts.push(new UnknownColumn(tableType, cell)); @@ -208,13 +196,12 @@ export default class Update extends Expression { return this.table.getType(context); } - getDefinitions(node: Node, context: Context): Definition[] { - node; + getDefinitions(_: Node, context: Context): Definition[] { const type = this.table.getType(context); if (type instanceof TableType) return type.columns .filter((col) => col instanceof Bind) - .map((col) => col) as Bind[]; + .map((col) => col); else return []; } @@ -231,15 +218,13 @@ export default class Update extends Expression { : new AnyType(); // Get the binds - const binds = this.row.cells; + const inputs = this.row.cells.filter( + (c): c is Input => c instanceof Input, + ); // Get the update steps - const updates = binds - .map((bind) => - bind instanceof Bind && bind.value - ? bind.value.compile(evaluator, context) - : [], - ) + const updates = inputs + .map((input) => input.value.compile(evaluator, context)) .flat(); /** A derived function based on the query, used to evaluate each row of the table. */ @@ -267,12 +252,10 @@ export default class Update extends Expression { ); // Get the values computed const values: Value[] = []; - for (const bind of binds) { - if (bind instanceof Bind && bind.value) { - const value = evaluation.popValue(this, undefined); - if (value instanceof ExceptionValue) return value; - values.unshift(value); - } + for (let count = 0; count < inputs.length; count++) { + const value = evaluation.popValue(this, undefined); + if (value instanceof ExceptionValue) return value; + values.unshift(value); } // Get the query result const match = evaluation.popValue( @@ -282,17 +265,17 @@ export default class Update extends Expression { if (!(match instanceof BoolValue)) return match; // Not a query match? Don't modify the row. if (match.bool === false) return row; - // Otherwise, refine the row with the updtes + // Otherwise, refine the row with the updates else { let newRow: StructureValue = row; - for (const bind of binds) { - if (bind instanceof Bind && bind.value) { + for (const input of inputs) { + if (input instanceof Input) { const value = values.shift(); const revised: StructureValue | undefined = value ? newRow.withValue( this, - bind.names.getNames()[0], + input.getName(), value, ) : undefined; diff --git a/src/parser/parseBind.ts b/src/parser/parseBind.ts index 8d8fb9f25..d758e0ded 100644 --- a/src/parser/parseBind.ts +++ b/src/parser/parseBind.ts @@ -88,3 +88,10 @@ export function nextIsBind(tokens: Tokens, expectValue: boolean): boolean { bind.names.hasALanguageTag()) ); } + +export function nextIsInput(tokens: Tokens): boolean { + return ( + tokens.nextIsOneOf(Sym.Name, Sym.Operator) && + tokens.afterNextIs(Sym.Bind) + ); +} diff --git a/src/parser/parseExpression.ts b/src/parser/parseExpression.ts index 3522f772c..a6c1a838a 100644 --- a/src/parser/parseExpression.ts +++ b/src/parser/parseExpression.ts @@ -9,7 +9,7 @@ import Sym from '../nodes/Sym'; import This from '../nodes/This'; import UnaryEvaluate from '../nodes/UnaryEvaluate'; import UnparsableExpression from '../nodes/UnparsableExpression'; -import parseBind, { nextIsBind, parseNames } from './parseBind'; +import parseBind, { nextIsBind, nextIsInput, parseNames } from './parseBind'; import type Tokens from './Tokens'; import ListLiteral from '@nodes/ListLiteral'; import type Bind from '@nodes/Bind'; @@ -612,7 +612,7 @@ function parseRow(tokens: Tokens, expected: Sym = Sym.TableOpen): Row { // Don't allow reactions on row values. tokens.pushReactionAllowed(false); - const cells: (Bind | Expression)[] = []; + const cells: (Input | Expression)[] = []; // Read the cells. tokens.untilDo( () => @@ -622,8 +622,8 @@ function parseRow(tokens: Tokens, expected: Sym = Sym.TableOpen): Row { !tokens.nextHasPrecedingLineBreak(), () => cells.push( - nextIsBind(tokens, true) - ? parseBind(tokens) + nextIsInput(tokens) + ? parseInput(tokens) : parseExpression(tokens), ), ); @@ -828,8 +828,7 @@ function parseEvaluate(left: Expression, tokens: Tokens): Evaluate { tokens.nextIsnt(Sym.EvalClose), () => inputs.push( - tokens.nextIsOneOf(Sym.Name, Sym.Operator) && - tokens.afterNextIs(Sym.Bind) + nextIsInput(tokens) ? parseInput(tokens) : parseExpression(tokens), ),