Skip to content

Commit

Permalink
Improved explanation when there's a space between an evaluation's nam…
Browse files Browse the repository at this point in the history
…e and inputs.
  • Loading branch information
amyjko committed Jun 29, 2024
1 parent 56081d4 commit 1de29ab
Show file tree
Hide file tree
Showing 13 changed files with 117 additions and 18 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,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.
- Ensured type errors when a structure definition is given instead of a structure value.
- [#500](https://github.com/wordplaydev/wordplay/issues/500). Improved explanation when there's a space between an evaluation's name and inputs.

## 0.10.1 2024-06-22

Expand Down
38 changes: 38 additions & 0 deletions src/conflicts/SeparatedEvaluate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import type Context from '@nodes/Context';
import NodeRef from '@locale/NodeRef';
import Conflict from './Conflict';
import concretize from '../locale/concretize';
import type Locales from '../locale/Locales';
import type Block from '@nodes/Block';
import type Reference from '@nodes/Reference';

export default class SeparatedEvaluate extends Conflict {
readonly name: Reference;
readonly inputs: Block;
readonly structure: boolean;

constructor(name: Reference, inputs: Block, structure: boolean) {
super(false);

this.name = name;
this.inputs = inputs;
this.structure = structure;
}

getConflictingNodes() {
return {
primary: {
node: this.name,
explanation: (locales: Locales, context: Context) =>
concretize(
locales,
locales.get(
(l) => l.node.Evaluate.conflict.SeparatedEvaluate,
),
new NodeRef(this.name, locales, context),
this.structure,
),
},
};
}
}
4 changes: 4 additions & 0 deletions src/locale/NodeTexts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,10 @@ type NodeTexts = {
* When a list of inputs is given but isn't last.
*/
InputListMustBeLast: InternalConflictText;
/**
* When something looks like an Evaluate with space
*/
SeparatedEvaluate: InternalConflictText;
}> &
Exceptions<{
/**
Expand Down
3 changes: 2 additions & 1 deletion src/locale/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -624,7 +624,8 @@
"primary": "I don't know of an input by this name",
"secondary": "I don't think I belong here"
},
"InputListMustBeLast": "list of inputs must be last"
"InputListMustBeLast": "list of inputs must be last",
"SeparatedEvaluate": "Is $1 the name of a $2[$structure|$function] you're trying to evaluate? Try removing the space after me, so I know it's an @Evaluate and not a separate @Block."
},
"exception": {
"FunctionException": {
Expand Down
52 changes: 51 additions & 1 deletion src/nodes/Evaluate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ import type Locales from '../locale/Locales';
import UnionType from './UnionType';
import NoExpressionType from './NoExpressionType';
import StructureDefinitionType from './StructureDefinitionType';
import Block from './Block';
import Reference from './Reference';
import SeparatedEvaluate from '@conflicts/SeparatedEvaluate';

type Mapping = {
expected: Bind;
Expand Down Expand Up @@ -422,7 +425,7 @@ export default class Evaluate extends Expression {
}

computeConflicts(context: Context): Conflict[] {
const conflicts = [];
const conflicts: Conflict[] = [];

if (this.close === undefined)
conflicts.push(
Expand Down Expand Up @@ -595,6 +598,53 @@ export default class Evaluate extends Expression {
}
}

// If there are two consectutive IncompatibleInput conflicts, and the first is a StructureDefinitionType and the next is a block,
// offer to remove the space separating them.

const possibleEvaluates: [
Reference,
boolean,
Block,
Conflict,
Conflict,
][] = [];
conflicts.find((conflict, index, conflicts) => {
const next = conflicts[index + 1];
if (
conflict instanceof IncompatibleInput &&
(conflict.givenType instanceof StructureDefinitionType ||
conflict.givenType instanceof FunctionType) &&
next instanceof IncompatibleInput &&
next.givenNode instanceof Block
) {
const ref = conflict.givenNode
.nodes()
.findLast((n): n is Reference => n instanceof Reference);
if (ref instanceof Reference)
possibleEvaluates.push([
ref,
conflict.givenType instanceof StructureDefinitionType,
next.givenNode,
conflict,
next,
]);
}
});

for (const [
ref,
structure,
block,
first,
second,
] of possibleEvaluates) {
// Remove the two conflicts from the list.
conflicts.splice(conflicts.indexOf(first), 1);
conflicts.splice(conflicts.indexOf(second), 1);
// Add a new one.
conflicts.push(new SeparatedEvaluate(ref, block, structure));
}

return conflicts;
}

Expand Down
2 changes: 1 addition & 1 deletion src/parser/Tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export default class Tokens {
return types.find((type) => this.nextIs(type)) !== undefined;
}

/** Returns true if and only if the next token is the specified type. */
/** Returns true if and only if the next token has no preceding space. */
nextLacksPrecedingSpace(): boolean {
return this.hasNext() && !this.#spaces.hasSpace(this.#unread[0]);
}
Expand Down
13 changes: 4 additions & 9 deletions src/parser/parseExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -515,8 +515,7 @@ function parseListAccess(left: Expression, tokens: Tokens): Expression {
left = new ListAccess(left, open, index, close);

// But wait, is it a function evaluation?
if (nextIsEvaluate(tokens) && tokens.nextLacksPrecedingSpace())
left = parseEvaluate(left, tokens);
if (nextIsEvaluate(tokens)) left = parseEvaluate(left, tokens);
},
() => tokens.nextIs(Sym.ListOpen),
);
Expand Down Expand Up @@ -573,8 +572,7 @@ function parseSetOrMapAccess(left: Expression, tokens: Tokens): Expression {
left = new SetOrMapAccess(left, open, key, close);

// But wait, is it a function evaluation?
if (nextIsEvaluate(tokens) && tokens.nextLacksPrecedingSpace())
left = parseEvaluate(left, tokens);
if (nextIsEvaluate(tokens)) left = parseEvaluate(left, tokens);
},
() => tokens.hasNext() && tokens.nextIs(Sym.SetOpen),
);
Expand Down Expand Up @@ -798,6 +796,7 @@ export function parseStructure(tokens: Tokens): StructureDefinition {
}

function nextIsEvaluate(tokens: Tokens): boolean {
// If the next token is a line break, then it's not an evaluate.
if (!tokens.nextLacksPrecedingSpace()) return false;

const rollbackToken = tokens.peek();
Expand Down Expand Up @@ -924,11 +923,7 @@ function parsePropertyReference(left: Expression, tokens: Tokens): Expression {
}

// But wait, is it a function evaluation?
if (
tokens.nextIsOneOf(Sym.EvalOpen, Sym.TypeOpen) &&
tokens.nextLacksPrecedingSpace()
)
left = parseEvaluate(left, tokens);
if (nextIsEvaluate(tokens)) left = parseEvaluate(left, tokens);
},
() => tokens.nextIs(Sym.Access),
);
Expand Down
3 changes: 2 additions & 1 deletion static/locales/es-MX/es-MX.json
Original file line number Diff line number Diff line change
Expand Up @@ -604,7 +604,8 @@
"primary": "No conozco un input con este nombre",
"secondary": "No creo que pertenezca aquí"
},
"InputListMustBeLast": "la lista de inputs debe ir al final"
"InputListMustBeLast": "la lista de inputs debe ir al final",
"SeparatedEvaluate": "$?"
},
"exception": {
"FunctionException": {
Expand Down
3 changes: 2 additions & 1 deletion static/locales/example/example.json
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,8 @@
"NotInstantiable": "$?",
"UnexpectedInput": { "primary": "$?", "secondary": "$?" },
"UnknownInput": { "primary": "$?", "secondary": "$?" },
"InputListMustBeLast": "$?"
"InputListMustBeLast": "$?",
"SeparatedEvaluate": "$?"
},
"exception": {
"FunctionException": {
Expand Down
3 changes: 2 additions & 1 deletion static/locales/ko-KR/ko-KR.json
Original file line number Diff line number Diff line change
Expand Up @@ -617,7 +617,8 @@
"primary": "이런 이름의 입력수는 모르는걸",
"secondary": "나는 여기에 속하지 않는것 같아"
},
"InputListMustBeLast": "list of inputs must be last"
"InputListMustBeLast": "$?",
"SeparatedEvaluate": "$?"
},
"exception": {
"FunctionException": {
Expand Down
3 changes: 2 additions & 1 deletion static/locales/zh-CN/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -625,7 +625,8 @@
"primary": "我不知道这个名字的输入",
"secondary": "我觉得我不应该在这里"
},
"InputListMustBeLast": "输入列表必须放在最后"
"InputListMustBeLast": "输入列表必须放在最后",
"SeparatedEvaluate": "$?"
},
"exception": {
"FunctionException": {
Expand Down
3 changes: 2 additions & 1 deletion static/locales/zh-TW/zh-TW.json
Original file line number Diff line number Diff line change
Expand Up @@ -625,7 +625,8 @@
"primary": "我不知道這個名字的輸入",
"secondary": "我覺得我不應該在這裡"
},
"InputListMustBeLast": "輸入清單必須放在最後"
"InputListMustBeLast": "輸入清單必須放在最後",
"SeparatedEvaluate": "$?"
},
"exception": {
"FunctionException": {
Expand Down
7 changes: 6 additions & 1 deletion static/schemas/Locale.json
Original file line number Diff line number Diff line change
Expand Up @@ -3495,6 +3495,10 @@
"$ref": "#/definitions/InternalConflictText",
"description": "When the structure definition given is an interface, and can't be created"
},
"SeparatedEvaluate": {
"$ref": "#/definitions/InternalConflictText",
"description": "When something looks like an Evaluate with space"
},
"UnexpectedInput": {
"$ref": "#/definitions/ConflictText",
"description": "When an input value is given but not expected Description inputs: $1 = evaluate with unexected input, $2: unexpected input"
Expand All @@ -3516,7 +3520,8 @@
"NotInstantiable",
"UnexpectedInput",
"UnknownInput",
"InputListMustBeLast"
"InputListMustBeLast",
"SeparatedEvaluate"
],
"type": "object"
},
Expand Down

0 comments on commit 1de29ab

Please sign in to comment.