Skip to content

Commit

Permalink
Added Spread to ListLiteral syntax, to simplify list construction.
Browse files Browse the repository at this point in the history
  • Loading branch information
amyjko committed Sep 17, 2023
1 parent 352ac0a commit c6a3c09
Show file tree
Hide file tree
Showing 18 changed files with 217 additions and 35 deletions.
2 changes: 2 additions & 0 deletions src/basis/ListBasis.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { test, expect } from 'vitest';
import evaluateCode from '../runtime/evaluate';

test.each([
['[1 2 3 :[4 5 6]]', '[1 2 3 4 5 6]'],
['[:[1 2 3] :[4 5 6]]', '[1 2 3 4 5 6]'],
['[1 2 3].add(4)', '[1 2 3 4]'],
['[1 2 3].has(4)', '⊥'],
['[1 2 3].has(3)', '⊤'],
Expand Down
10 changes: 10 additions & 0 deletions src/components/editor/SpreadView.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<svelte:options immutable={true} />

<script lang="ts">
import NodeView from './NodeView.svelte';
import type Spread from '../../nodes/Spread';
export let node: Spread;
</script>

<NodeView node={node.dots} /><NodeView node={node.list} />
6 changes: 5 additions & 1 deletion src/components/editor/util/nodeToView.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { ComponentType, SvelteComponent } from 'svelte';

/* eslint-disable @typescript-eslint/ban-types */
import BlockView from '../BlockView.svelte';
import BorrowView from '../BorrowView.svelte';
Expand Down Expand Up @@ -80,6 +82,7 @@ import TranslationView from '../TranslationView.svelte';
import FormattedLiteralView from '../FormattedLiteralView.svelte';
import FormattedTranslationView from '../FormattedTranslationView.svelte';
import IsLocaleView from '../IsLocaleView.svelte';
import SpreadView from '../SpreadView.svelte';

import type Node from '@nodes/Node';
import Program from '@nodes/Program';
Expand Down Expand Up @@ -164,7 +167,7 @@ import Translation from '@nodes/Translation';
import FormattedTranslation from '@nodes/FormattedTranslation';
import FormattedLiteral from '@nodes/FormattedLiteral';
import IsLocale from '@nodes/IsLocale';
import type { ComponentType, SvelteComponent } from 'svelte';
import Spread from '@nodes/Spread';

const nodeToView = new Map<Function, ComponentType<SvelteComponent>>();

Expand Down Expand Up @@ -240,6 +243,7 @@ nodeToView.set(SetType, SetTypeView);
nodeToView.set(MapType, MapTypeView);

nodeToView.set(ListLiteral, ListLiteralView);
nodeToView.set(Spread, SpreadView);
nodeToView.set(ListAccess, ListAccessView);
nodeToView.set(ListType, ListTypeView);

Expand Down
3 changes: 2 additions & 1 deletion src/components/palette/editOutput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
} from '../../parser/Symbols';
import { toExpression } from '../../parser/parseExpression';
import { getPlaceExpression } from '../../output/getOrCreatePlace';
import type Spread from '../../nodes/Spread';

export function getNumber(given: Expression): number | undefined {
const measurement =
Expand Down Expand Up @@ -186,7 +187,7 @@ export function reviseContent(
db: Database,
project: Project,
list: ListLiteral,
newValues: Expression[]
newValues: (Expression | Spread)[]
) {
db.Projects.revise(project, [[list, ListLiteral.make(newValues)]]);
}
Expand Down
7 changes: 5 additions & 2 deletions src/examples/Adventure.wp
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,11 @@ Stage(
[
Group(
Stack()
[Phrase(state.description duration: 0.5s name: state.action entering: Pose(offset: Place(0m 2m)))].append(options)
)
[
Phrase(state.description duration: 0.5s name: state.action entering: Pose(offset: Place(0m 2m)))
:options
]
)
]
background: Color(0 0 0%°)
color: Color(100% 0 0°)
Expand Down
4 changes: 1 addition & 3 deletions src/examples/Hira.wp
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@ letters•[Output]: count → [].translate(


Stage(
letters.append([
Shape(Rectangle(-40m 0m 40m -2m))
])
[ :letters Shape(Rectangle(-40m 0m 40m -2m))]
gravity: gravity
place: Place(0m 5m -20m)
)
7 changes: 5 additions & 2 deletions src/examples/Layers.wp
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@ count: 10→[]
)

Stage(
balls(0m).append(balls(-10m).append(balls(10m))).append([
[
:balls(0m)
:balls(-10m)
:balls(10m)
Shape(Rectangle(-20m 0m 20m -1m 0m))
Shape(Rectangle(-20m 0m 20m -1m 10m) rotation: 25°)
Shape(Rectangle(-20m 0m 20m -1m -10m) rotation: -10°)
])
]
place: Place(0m 10m -30m)
)
25 changes: 14 additions & 11 deletions src/examples/Questions.wp
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,22 @@ Questions

pressed: ∆ Key()
count•#: 0 … pressed … count + 1
questions•[Question]: [] … pressed … questions.add(Question(count→"" Random(30 50) · 1m/s Random(30 50) · 1m/s Random(0 30) · 1°/s))
questions•[Question]: [] … pressed … [:questions Question(count→"" Random(30 50) · 1m/s Random(30 50) · 1m/s Random(0 30) · 1°/s)]

Stage(
[ Phrase('👨‍👨‍👧‍👦' place: Place(0m 0m) face: "Noto Emoji") Shape(Rectangle(-5m 0m 25m -1m))].append(questions.translate(
ƒ(q•Question index•#) (
initialize: pressed & (index = questions.length())
Phrase(
'Q'
name: q.id
place: Motion(velocity: Velocity(q.vx q.vy q.va))
matter: Matter()
resting: Pose(opacity: initialize ? 0% 100%)
[
Phrase('👨‍👨‍👧‍👦' place: Place(0m 0m) face: "Noto Emoji") Shape(Rectangle(-5m 0m 25m -1m))
:questions.translate(
ƒ(q•Question index•#) (
initialize: pressed & (index = questions.length())
Phrase(
'Q'
name: q.id
place: Motion(velocity: Velocity(q.vx q.vy q.va))
matter: Matter()
resting: Pose(opacity: initialize ? 0% 100%)
)
)
)
))
]
)
5 changes: 5 additions & 0 deletions src/locale/NodeTexts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,11 @@ type NodeTexts = {
/** Placeholder label for an item in a list */
item: Template;
};
/**
* A way of spreading a list's values into a list literal, e.g., `[ [ 1 2 3]… 4 5]`
* Description inputs: none
*/
Spread: NodeText;
/**
* A map literal, e.g., `{1:1 2:2 3:3}`
* Finish inputs: $1 = resulting value
Expand Down
14 changes: 12 additions & 2 deletions src/locale/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,14 @@
"finish": "I made a me! $1",
"item": "item"
},
"Spread": {
"name": "list spread",
"emotion": "serious",
"doc": [
"A help you make lists with the values of other lists. Like this:",
"\\list1: [1 2 3]\nlist2: [4 5 6]\nfinal: [list1… list2…]"
]
},
"MapLiteral": {
"name": "map",
"description": "$1 pairing map",
Expand Down Expand Up @@ -1913,8 +1921,10 @@
},
"append": {
"doc": [
"I create a new @List with my values, then all the values of the given @List.",
"\\['apple' 'banana' 'mango'].append(['watermelon' 'starfruit'])\\"
"I create a new @List with my values, then all the values of the given @List after me.",
"\\['apple' 'banana' 'mango'].append(['watermelon' 'starfruit'])\\",
"It's a little bit easier to use @Spread though, like this:",
"\\['apple' 'banana' 'mango' :['watermelon' 'starfruit']]\\"
],
"names": ["append"],
"inputs": [
Expand Down
50 changes: 41 additions & 9 deletions src/nodes/ListLiteral.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,15 @@ import type { BasisTypeName } from '../basis/BasisConstants';
import concretize from '../locale/concretize';
import Sym from './Sym';
import AnyType from './AnyType';
import Spread from './Spread';
import TypeException from '../values/TypeException';

export default class ListLiteral extends Expression {
readonly open: Token;
readonly values: Expression[];
readonly values: (Spread | Expression)[];
readonly close?: Token;

constructor(open: Token, values: Expression[], close?: Token) {
constructor(open: Token, values: (Spread | Expression)[], close?: Token) {
super();

this.open = open;
Expand All @@ -40,7 +42,7 @@ export default class ListLiteral extends Expression {
this.computeChildren();
}

static make(values?: Expression[]) {
static make(values?: (Expression | Spread)[]) {
return new ListLiteral(
new ListOpenToken(),
values ?? [],
Expand All @@ -57,7 +59,7 @@ export default class ListLiteral extends Expression {
{ name: 'open', kind: node(Sym.ListOpen) },
{
name: 'values',
kind: list(true, node(Expression)),
kind: list(true, node(Expression), node(Spread)),
label: (translation: Locale) =>
translation.node.ListLiteral.item,
// Only allow types to be inserted that are of the list's type, if provided.
Expand All @@ -74,7 +76,7 @@ export default class ListLiteral extends Expression {
clone(replace?: Replacement) {
return new ListLiteral(
this.replaceChild('open', this.open, replace),
this.replaceChild<Expression[]>('values', this.values, replace),
this.replaceChild('values', this.values, replace),
this.replaceChild('close', this.close, replace)
) as this;
}
Expand Down Expand Up @@ -117,7 +119,9 @@ export default class ListLiteral extends Expression {
}

getDependencies(): Expression[] {
return [...this.values];
return this.values
.map((val) => (val instanceof Spread ? val.list : val))
.filter((val): val is Expression => val !== undefined);
}

compile(evaluator: Evaluator, context: Context): Step[] {
Expand All @@ -126,7 +130,11 @@ export default class ListLiteral extends Expression {
...this.values.reduce(
(steps: Step[], item) => [
...steps,
...item.compile(evaluator, context),
...(item instanceof Spread
? item.list
? item.list.compile(evaluator, context)
: []
: item.compile(evaluator, context)),
],
[]
),
Expand All @@ -137,10 +145,34 @@ export default class ListLiteral extends Expression {
evaluate(evaluator: Evaluator, prior: Value | undefined): Value {
if (prior) return prior;

// Start with the list of values from the expression to help keep track of the ones that were handled.
const items = this.values.slice();

// Pop all of the values.
const values = [];
for (let i = 0; i < this.values.length; i++)
values.unshift(evaluator.popValue(this));
for (let i = 0; i < this.values.length; i++) {
const value = evaluator.popValue(this);
let item;
do {
item = items.pop();
} while (item instanceof Spread && item.list === undefined);
// Was this a spread value? Add all of its items to this list.
if (item instanceof Spread) {
if (value instanceof ListValue) {
// Add them in reverse order so they end up in the correct order.
for (let j = value.values.length - 1; j >= 0; j--)
values.unshift(value.values[j]);
} else
return new TypeException(
this,
evaluator,
ListType.make(),
value
);
}
// Add the non-spread value.
else values.unshift(value);
}

// Construct the new list.
return new ListValue(this, values);
Expand Down
77 changes: 77 additions & 0 deletions src/nodes/Spread.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import Expression from './Expression';
import type { Grammar, Replacement } from './Node';
import Token from './Token';
import type Locale from '@locale/Locale';
import Glyphs from '../lore/Glyphs';
import Purpose from '../concepts/Purpose';
import type { BasisTypeName } from '../basis/BasisConstants';
import Node, { node, optional } from './Node';
import Sym from './Sym';
import { BIND_SYMBOL } from '../parser/Symbols';
import AnyType from './AnyType';
import ListType from './ListType';

/** Inside a list literal, flattens values of a list value into a new list */
export default class Spread extends Node {
readonly dots: Token;
readonly list: Expression | undefined;

constructor(dots: Token, list: Expression | undefined) {
super();

this.dots = dots;
this.list = list;

this.computeChildren();
}

static make(list: Expression) {
return new Spread(new Token(BIND_SYMBOL, Sym.Bind), list);
}

static getPossibleNodes() {
return [];
}

getGrammar(): Grammar {
return [
{
name: 'dots',
kind: node(Sym.Bind),
},
{
name: 'list',
kind: optional(node(Expression)),
getType: () => ListType.make(new AnyType()),
label: (translation: Locale) => translation.term.list,
},
];
}

clone(replace?: Replacement) {
return new Spread(
this.replaceChild('dots', this.dots, replace),
this.replaceChild('list', this.list, replace)
) as this;
}

getPurpose(): Purpose {
return Purpose.Value;
}

getAffiliatedType(): BasisTypeName | undefined {
return 'list';
}

computeConflicts() {
return;
}

getNodeLocale(locale: Locale) {
return locale.node.Spread;
}

getGlyphs() {
return Glyphs.Stream;
}
}
Loading

0 comments on commit c6a3c09

Please sign in to comment.