@hyperfixi/testing-framework 4.2.0 → 4.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,68 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [4.3.0] - 2026-10-07
|
|
11
|
+
|
|
12
|
+
More of what upstream reads now translates, and what does not is refused instead of lost. On the
|
|
13
|
+
command-shape gate, round trips that came back silently different fell from 174 in 4.2.0 to 31,
|
|
14
|
+
none of them in English; 24,386 of 27,240 now read as their source does. English-to-English refuses
|
|
15
|
+
61 of the 1,135 scripts, down from 82, and each of the 61 now belongs to a named family with the
|
|
16
|
+
reason it is not carried yet.
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **`@lokascript/semantic` reads `on every <event>` and `init immediately`.** Both lost their head
|
|
21
|
+
word, so the handler or the block was refused. `EventHandlerSemanticNode.eventModifiers.every` and
|
|
22
|
+
`FeatureSemanticNode.immediately` carry them. English writes them as upstream does; every other
|
|
23
|
+
language writes the English word first (`every al clic …`), as it already did for `on first`.
|
|
24
|
+
- **`@hyperfixi/testing-framework`: two new value-matrix axes.** A `branch` position is an `if` with
|
|
25
|
+
no `then` before a command that names no target (`if 2 is in arr add .a end`). The matrix's `if`
|
|
26
|
+
cells always wrote `then` and a `put` with its target, so an SOV reader always saw where the
|
|
27
|
+
condition ended, and none of them could fail the way Turkish did (see Fixed). The 610 pairs that
|
|
28
|
+
`branch` refuses are listed in the baseline. A twelfth operand kind is a CSS length (`100px`,
|
|
29
|
+
`50%`). The matrix now has 5,396 cells.
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- **`@lokascript/semantic` refuses core's swap strategies that upstream has no spelling for.**
|
|
34
|
+
`translate()` gains a loss kind, `'core-only'` (`TranslationLossKind`). Upstream and the engine
|
|
35
|
+
read `swap morph of #t with it` as an exchange with a property named `morph`, so core's morph
|
|
36
|
+
strategies, `none`, and a strategy with no content are now refused in every language. Before,
|
|
37
|
+
English wrote them as they were read. Core's `swap into #t with x` and `swap over #t with x` are its
|
|
38
|
+
innerHTML and outerHTML, and they are now written as `put x into #t` and `put x into #t's
|
|
39
|
+
outerHTML`. Before, English wrote `swap into of #t with x`, which runs as an exchange.
|
|
40
|
+
- **`@lokascript/semantic` writes a word in the target language only where that language's reader
|
|
41
|
+
brings it back.** A property name is localized only through the reader's property table or as a
|
|
42
|
+
keyword. So `add .foo to my children` keeps `children` in Spanish (`mi children`), where it wrote
|
|
43
|
+
`mi hijos` and read back a property named `hijos`. A call's arguments are written as written
|
|
44
|
+
(`call sprayInto(me)` wrote `sprayInto(yo)`). So is every bracket group inside an expression:
|
|
45
|
+
`2 is in [true, n]` wrote `[verdadero, n]` in Spanish, a variable no reader de-localizes. And so is a
|
|
46
|
+
type name after `am a`. These spots now show English words in foreign renders where they used to
|
|
47
|
+
show words that did not come back.
|
|
48
|
+
- **`@lokascript/semantic`: `innerHTML` and `outerHTML` are no longer English keywords.** A swap's
|
|
49
|
+
strategy slot still reads them, as words.
|
|
50
|
+
|
|
51
|
+
### Fixed
|
|
52
|
+
|
|
53
|
+
- **`@lokascript/semantic`: Turkish keeps an `if` condition whole before a command with no target.**
|
|
54
|
+
`if 2 is in arr add .a end` read back as `if 2 add .a to is in arr end`, with any operand, and so
|
|
55
|
+
did `is a Number` and `<` after a possessive. Turkish case markers may be dropped, and the marker
|
|
56
|
+
test counted a marker word anywhere in the value: `in` (also the operator), `a` (also the article),
|
|
57
|
+
`nin` (also the possessive). The marker must now be the last token of its group.
|
|
58
|
+
- **`@lokascript/semantic`: a number with a CSS unit is one value.** Upstream reads `100px`,
|
|
59
|
+
`1.5rem` and `50%` as one string. Every tokenizer split them, so `set my *width to 100px` and
|
|
60
|
+
`transition height to 100px` were refused in all 24 languages. A `%` that an operand touches
|
|
61
|
+
(`5%2`) or that is spaced still reads as the operator.
|
|
62
|
+
- **`@lokascript/semantic`: more English forms that were refused in every language now read.**
|
|
63
|
+
`set innerHTML of #d1 to …` and `increment innerHTML of #d1`; a single-quoted string that holds a
|
|
64
|
+
`"` (`set #t to '<div _="…">'`), which was written back in double quotes and so closed early;
|
|
65
|
+
`fetch … don't throw`; `put … at the start of` / `at the end of`, where the `end` closed the
|
|
66
|
+
handler; `remove 3 from :arr` and `remove {color} from me`; `otherwise` as `else`. In a translation
|
|
67
|
+
of an empty then-branch's `otherwise`, 13 languages wrote their own word for it inside the
|
|
68
|
+
condition and read it back there, silently.
|
|
69
|
+
- **`@lokascript/semantic`: a handler's `catch` and `finally` bodies are written in upstream's
|
|
70
|
+
spelling.** `catch e prepend …` kept core's `prepend`, which the engine rejects.
|
|
71
|
+
|
|
10
72
|
## [4.2.0] - 2026-10-07
|
|
11
73
|
|
|
12
74
|
A translation that would lose part of a script is now refused instead of returned short.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperfixi/testing-framework",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.3.0",
|
|
4
4
|
"description": "Cross-platform behavior testing suite for LokaScript applications",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -57,10 +57,10 @@
|
|
|
57
57
|
"author": "LokaScript Contributors",
|
|
58
58
|
"license": "MIT",
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@hyperfixi/patterns-reference": "^4.
|
|
61
|
-
"@lokascript/compilation-service": "^4.
|
|
62
|
-
"@lokascript/i18n": "^4.
|
|
63
|
-
"@lokascript/semantic": "^4.
|
|
60
|
+
"@hyperfixi/patterns-reference": "^4.3.0",
|
|
61
|
+
"@lokascript/compilation-service": "^4.3.0",
|
|
62
|
+
"@lokascript/i18n": "^4.3.0",
|
|
63
|
+
"@lokascript/semantic": "^4.3.0",
|
|
64
64
|
"diff": "^8.0.3",
|
|
65
65
|
"esbuild": "^0.25.12",
|
|
66
66
|
"happy-dom": "^20.14.5",
|
|
@@ -72,8 +72,8 @@
|
|
|
72
72
|
"vite": "^8.3.1"
|
|
73
73
|
},
|
|
74
74
|
"devDependencies": {
|
|
75
|
-
"@hyperfixi/engine": "^4.
|
|
76
|
-
"@lokascript/hyperscript-adapter": "^4.
|
|
75
|
+
"@hyperfixi/engine": "^4.3.0",
|
|
76
|
+
"@lokascript/hyperscript-adapter": "^4.3.0",
|
|
77
77
|
"@types/diff": "^5.0.0",
|
|
78
78
|
"@types/node": "^26.6.3",
|
|
79
79
|
"@vitest/coverage-v8": "^5.0.0",
|
|
@@ -589,6 +589,115 @@ export const LOUD: readonly LoudFamily[] = [
|
|
|
589
589
|
"a possessive with nothing after it (`halt the event's then …`), which the engine " +
|
|
590
590
|
'reads as the bare reference; one upstream test source',
|
|
591
591
|
matches: source => /'s(?=\s+(then|end)\b|\s*$)/.test(source),
|
|
592
|
+
}, // M1 step 3 (2026-10-07): the English refusals left after the value and
|
|
593
|
+
// command-form fixes, each family with why it is not carried.
|
|
594
|
+
{
|
|
595
|
+
name: 'scroll-by',
|
|
596
|
+
reason:
|
|
597
|
+
"scroll's relative form (`scroll #box down by 200px`) names a direction, which no " +
|
|
598
|
+
"language's dictionary has: adding them is an owner vocabulary decision (the M2 sheet)",
|
|
599
|
+
matches: (_source, _types, plain) =>
|
|
600
|
+
someNode(plain, node => node.type === 'scrollCommand' && 'by' in node),
|
|
601
|
+
},
|
|
602
|
+
{
|
|
603
|
+
name: 'beep',
|
|
604
|
+
reason:
|
|
605
|
+
"`beep!`, upstream's debugging command, has no schema: its form in each language is an " +
|
|
606
|
+
'owner vocabulary decision (the M2 sheet)',
|
|
607
|
+
matches: (_source, types) => types.has('beepCommand'),
|
|
608
|
+
},
|
|
609
|
+
{
|
|
610
|
+
name: 'halt-modes',
|
|
611
|
+
reason:
|
|
612
|
+
"halt's `default` (also a command word) and `the event's bubbling|default`: the slot " +
|
|
613
|
+
'that keeps `call` out of `halt call f()` cannot tell `default` from a command without ' +
|
|
614
|
+
'a halt-only reading (OPEN_ITEMS P56)',
|
|
615
|
+
matches: source => /\bhalt\s+(default\b|the event's\s+(bubbling|default)\b)/.test(source),
|
|
616
|
+
},
|
|
617
|
+
{
|
|
618
|
+
name: 'collection-expressions',
|
|
619
|
+
reason:
|
|
620
|
+
"upstream 0.9.93's collection expressions (`… where it matches .a`, `mapped to`) are an " +
|
|
621
|
+
"expression form semantic's reader does not have: a parser-track item (OPEN_ITEMS P57)",
|
|
622
|
+
// Not `if no <q/> in … where …`, which English reads (its foreign lanes are
|
|
623
|
+
// the value-words family).
|
|
624
|
+
matches: (source, types) => types.has('collectionExpression') && !/\bif\s+no\s+</.test(source),
|
|
625
|
+
},
|
|
626
|
+
{
|
|
627
|
+
name: 'pseudo-class-query',
|
|
628
|
+
reason:
|
|
629
|
+
'a query of a pseudo-class (`<:checked/>`): the tokenizers read `:checked` as a local ' +
|
|
630
|
+
'variable; a tokenizer item, rare in pages',
|
|
631
|
+
matches: source => /<:[\w-]+\/>/.test(source),
|
|
632
|
+
},
|
|
633
|
+
{
|
|
634
|
+
name: 'closest-forms',
|
|
635
|
+
reason:
|
|
636
|
+
"`closest @attr` (an ancestor's attribute) and `closest parent <q/>`: the forms of " +
|
|
637
|
+
'`closest` besides a query, rare in pages',
|
|
638
|
+
matches: (_source, _types, plain) =>
|
|
639
|
+
someNode(
|
|
640
|
+
plain,
|
|
641
|
+
node =>
|
|
642
|
+
node.type === 'closestExpr' &&
|
|
643
|
+
// Not `closest @foo to #d1`, which English reads (value-words).
|
|
644
|
+
(node.parentSearch === true ||
|
|
645
|
+
(String(node.css ?? '').startsWith('[') &&
|
|
646
|
+
(node.to as { type?: unknown } | undefined)?.type === 'implicitMeTarget'))
|
|
647
|
+
),
|
|
648
|
+
},
|
|
649
|
+
{
|
|
650
|
+
name: 'possessive-after-query',
|
|
651
|
+
reason:
|
|
652
|
+
"a possessive after a positional query (`the next <div/>'s textContent`), rare in pages; " +
|
|
653
|
+
'the `of` form (`the textContent of the next <div/>`) is carried',
|
|
654
|
+
matches: (source, types) => types.has('relativePositionalExpression') && /\/>'s\b/.test(source),
|
|
655
|
+
},
|
|
656
|
+
{
|
|
657
|
+
name: 'colon-event-arguments',
|
|
658
|
+
reason: 'a colon-qualified event name with arguments (`send foo:bar(x:42)`), rare in pages',
|
|
659
|
+
matches: (source, types) => types.has('namedArgumentList') && /\w:\w+\(/.test(source),
|
|
660
|
+
},
|
|
661
|
+
{
|
|
662
|
+
name: 'call-chain-command',
|
|
663
|
+
reason: 'a call chain as a command (`bar().foo()`), rare in pages',
|
|
664
|
+
matches: (source, types) => types.has('pseudoCommand') && /\)\.\w+\(/.test(source),
|
|
665
|
+
},
|
|
666
|
+
{
|
|
667
|
+
name: 'rare-heads',
|
|
668
|
+
reason:
|
|
669
|
+
'handler heads rare in pages: a quoted event name (`on "a-b"`), a filter after ' +
|
|
670
|
+
'parameters (`on foo(bar)[bar]`), `and on`, a source on each `or` leg, a ' +
|
|
671
|
+
'parenthesized feature (`(on click …)`)',
|
|
672
|
+
matches: source =>
|
|
673
|
+
/^\s*on\s+"|^\s*on\s+[\w:.-]+\([^)]*\)\[|\bon\s+\w+\s+\d+\s+and\s+on\b|\bfrom\s+\S+\s+or\s+\w+\s+from\b|^\s*\(on\s/.test(
|
|
674
|
+
source
|
|
675
|
+
),
|
|
676
|
+
},
|
|
677
|
+
{
|
|
678
|
+
name: 'rare-values',
|
|
679
|
+
reason:
|
|
680
|
+
'value spellings rare in pages: an escaped class name (`.group-\\[…\\]`), a comma ' +
|
|
681
|
+
'list in a query (`<#d1, #d2/>`), an attribute after a bare or bracketed reference ' +
|
|
682
|
+
'(`it @a`, `my [@a]`), a dotted path before `of`, a unit after parentheses ' +
|
|
683
|
+
'(`(w)px`), `set {…} on <obj>`',
|
|
684
|
+
matches: source =>
|
|
685
|
+
/\\\[|<[^<>]*,[^<>]*\/>|\b(my|its|your)\s+\[@|\bset\s+(it|me|you)\s+@|\b[a-z]\w*\.[a-z]\w*\s+of\b|\)(px|em|rem|%)|\bset\s+\{[^}]*\}\s+on\b/i.test(
|
|
686
|
+
source
|
|
687
|
+
),
|
|
688
|
+
},
|
|
689
|
+
{
|
|
690
|
+
name: 'rare-commands',
|
|
691
|
+
reason:
|
|
692
|
+
'commands rare in pages: `measure` of a property list, `pick characters 0 to 3 from …`, ' +
|
|
693
|
+
'an `if` whose body is empty (`if false end`), a behavior holding a bare command, two ' +
|
|
694
|
+
'behaviors with no `end` between',
|
|
695
|
+
matches: (source, types, plain) =>
|
|
696
|
+
(types.has('measureCommand') && /\bmeasure\s+\w+\s*,/.test(source)) ||
|
|
697
|
+
someNode(plain, node => node.type === 'pickCommand' && node.variant === 'range') ||
|
|
698
|
+
/\bif\s+\S+\s+end\b/.test(source) ||
|
|
699
|
+
(types.has('behaviorFeature') && types.has('setFeature')) ||
|
|
700
|
+
(source.match(/\bbehavior\b/g)?.length ?? 0) > 1,
|
|
592
701
|
},
|
|
593
702
|
];
|
|
594
703
|
|
|
@@ -606,28 +715,6 @@ export interface SilentFamily {
|
|
|
606
715
|
}
|
|
607
716
|
|
|
608
717
|
export const SILENT_FAMILIES: readonly SilentFamily[] = [
|
|
609
|
-
{
|
|
610
|
-
name: 'localized-call-arguments',
|
|
611
|
-
reason:
|
|
612
|
-
"a call's arguments are written in the target language (`sprayInto(أنا)`, " +
|
|
613
|
-
'`writeText(قيمة لـ #input)`) and read back raw, so English holds the foreign words',
|
|
614
|
-
ids: ['2d6fa6cf27', '2218bab0ca'],
|
|
615
|
-
},
|
|
616
|
-
{
|
|
617
|
-
name: 'localized-dom-names',
|
|
618
|
-
reason:
|
|
619
|
-
'a DOM property or type name is translated as a word (`my children` → de `my kinder`, ' +
|
|
620
|
-
'`my style[…]` → `my stil […]`, `a Element` → ar `a عنصر`) and not read back',
|
|
621
|
-
ids: [
|
|
622
|
-
'76bf595d21',
|
|
623
|
-
'a1ca7a74a0',
|
|
624
|
-
'e172bd73af',
|
|
625
|
-
'ddb498424a',
|
|
626
|
-
'dc9ce23e31',
|
|
627
|
-
'bf3ceac332',
|
|
628
|
-
'8ecf869842',
|
|
629
|
-
],
|
|
630
|
-
},
|
|
631
718
|
{
|
|
632
719
|
name: 'value-words',
|
|
633
720
|
reason:
|
|
@@ -645,13 +732,6 @@ export const SILENT_FAMILIES: readonly SilentFamily[] = [
|
|
|
645
732
|
'54a65f4602',
|
|
646
733
|
],
|
|
647
734
|
},
|
|
648
|
-
{
|
|
649
|
-
name: 'else-word',
|
|
650
|
-
reason:
|
|
651
|
-
"an empty then-branch's `otherwise` (de `andernfalls`, …) is not read as `else` and " +
|
|
652
|
-
'stays a word in the condition',
|
|
653
|
-
ids: ['e60afa928a'],
|
|
654
|
-
},
|
|
655
735
|
{
|
|
656
736
|
name: 'it-locative',
|
|
657
737
|
reason: 'it `in me` (a query scope) reads back as `on me`',
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Value matrix gate — the `branch` position's operator-phrase cells (`is equal
|
|
3
|
+
* to`, `includes`, `is an Element`, …). See value-matrix.ts for the matrix
|
|
4
|
+
* and value-matrix-gate.ts for the assertions.
|
|
5
|
+
*
|
|
6
|
+
* @vitest-environment node
|
|
7
|
+
* Required, not a preference: under happy-dom the engines bind happy-dom's
|
|
8
|
+
* DOM constructors, not the jsdom window's (see
|
|
9
|
+
* shipped-examples-execution.test.ts).
|
|
10
|
+
*/
|
|
11
|
+
import { describeValueMatrixShard } from './value-matrix-gate';
|
|
12
|
+
|
|
13
|
+
describeValueMatrixShard('branch', 'phrases');
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Value matrix gate — the `branch` cells. See value-matrix.ts for the matrix and
|
|
3
|
+
* value-matrix-gate.ts for the assertions; one position per file so the
|
|
4
|
+
* shards run in parallel.
|
|
5
|
+
*
|
|
6
|
+
* @vitest-environment node
|
|
7
|
+
* Required, not a preference: under happy-dom the engines bind happy-dom's
|
|
8
|
+
* DOM constructors, not the jsdom window's (see
|
|
9
|
+
* shipped-examples-execution.test.ts).
|
|
10
|
+
*/
|
|
11
|
+
import { describeValueMatrixShard } from './value-matrix-gate';
|
|
12
|
+
|
|
13
|
+
describeValueMatrixShard('branch');
|
|
@@ -9,16 +9,17 @@
|
|
|
9
9
|
*
|
|
10
10
|
* This does. A CELL is one value expression in one position:
|
|
11
11
|
*
|
|
12
|
-
* - OPERANDS:
|
|
12
|
+
* - OPERANDS: twelve kinds (literal, variable, selector, possessive, `of`,
|
|
13
13
|
* dotted, call, array, parens, a reference's property: `event's type`,
|
|
14
|
-
* `the id of target`,
|
|
15
|
-
* of the value types it produces;
|
|
14
|
+
* `the id of target`, a sigil variable: `$n`, and a CSS length: `100px`),
|
|
15
|
+
* each with instances of the value types it produces;
|
|
16
16
|
* - OPERATORS: arithmetic, comparison, equality, logic, membership, and the
|
|
17
17
|
* prefix and postfix forms (`not`, `-`, `no`, `is empty`, `is null`,
|
|
18
18
|
* `exists`, `as`), and core's operator PHRASES (`is greater than or equal
|
|
19
19
|
* to`, `does not include`, `is an Element`, …);
|
|
20
|
-
* - POSITIONS: a `put` value, a `set` value, an `if` condition,
|
|
21
|
-
*
|
|
20
|
+
* - POSITIONS: a `put` value, a `set` value, an `if` condition, an `if`
|
|
21
|
+
* condition with no `then` before a command that names no target
|
|
22
|
+
* (`branch`), a `repeat while` condition, a loop's count (`repeat … times`), an `increment …
|
|
22
23
|
* by` amount, a `set` value in the second command of a chain (`chain`),
|
|
23
24
|
* what a `get` reads (`get`), and two written targets: what a `set`
|
|
24
25
|
* writes (`assign`) and what an `increment` counts (`count`).
|
|
@@ -112,15 +113,27 @@ export type OperandKind =
|
|
|
112
113
|
| 'array'
|
|
113
114
|
| 'parens'
|
|
114
115
|
| 'reference'
|
|
115
|
-
| 'sigil'
|
|
116
|
+
| 'sigil'
|
|
117
|
+
| 'length';
|
|
116
118
|
|
|
117
119
|
export type Position =
|
|
118
|
-
|
|
120
|
+
| 'put'
|
|
121
|
+
| 'set'
|
|
122
|
+
| 'if'
|
|
123
|
+
| 'branch'
|
|
124
|
+
| 'while'
|
|
125
|
+
| 'times'
|
|
126
|
+
| 'increment'
|
|
127
|
+
| 'chain'
|
|
128
|
+
| 'get'
|
|
129
|
+
| 'assign'
|
|
130
|
+
| 'count';
|
|
119
131
|
|
|
120
132
|
export const POSITIONS: readonly Position[] = [
|
|
121
133
|
'put',
|
|
122
134
|
'set',
|
|
123
135
|
'if',
|
|
136
|
+
'branch',
|
|
124
137
|
'while',
|
|
125
138
|
'times',
|
|
126
139
|
'increment',
|
|
@@ -164,7 +177,7 @@ export interface MatrixCell {
|
|
|
164
177
|
*/
|
|
165
178
|
export const FIXTURE =
|
|
166
179
|
'<div id="out">∅</div>' +
|
|
167
|
-
'<p id="a" class="x" title="t1">6</p>' +
|
|
180
|
+
'<p id="a" class="x" title="t1" aria-pressed="true">6</p>' +
|
|
168
181
|
'<div id="w"><p class="w">w</p><p class="w">v</p></div>' +
|
|
169
182
|
'<button id="b">b</button>';
|
|
170
183
|
|
|
@@ -197,12 +210,16 @@ export const OPERANDS: readonly Operand[] = [
|
|
|
197
210
|
{ kind: 'possessive', type: 'num', text: "obj's v" },
|
|
198
211
|
{ kind: 'possessive', type: 'num', text: "arr's length" },
|
|
199
212
|
{ kind: 'possessive', type: 'num', text: "#a's textContent's length" },
|
|
213
|
+
// A property name a lexicon translates and no reader brings back: es wrote
|
|
214
|
+
// `hijos`, which read back as a property named `hijos`.
|
|
215
|
+
{ kind: 'possessive', type: 'num', text: "#w's children's length" },
|
|
200
216
|
{ kind: 'of', type: 'nstr', text: 'textContent of #a' },
|
|
201
217
|
{ kind: 'of', type: 'nstr', text: 'the textContent of #a' },
|
|
202
218
|
{ kind: 'of', type: 'num', text: 'v of obj' },
|
|
203
219
|
{ kind: 'of', type: 'num', text: 'length of arr' },
|
|
204
220
|
{ kind: 'of', type: 'str', text: '@title of #a' },
|
|
205
221
|
{ kind: 'of', type: 'num', text: 'v of w of obj' },
|
|
222
|
+
{ kind: 'of', type: 'num', text: 'the length of the children of #w' },
|
|
206
223
|
{ kind: 'of', type: 'arr', text: 'textContent of .w' },
|
|
207
224
|
{ kind: 'dotted', type: 'nstr', text: '#a.textContent' },
|
|
208
225
|
{ kind: 'dotted', type: 'str', text: 'me.id' },
|
|
@@ -213,8 +230,15 @@ export const OPERANDS: readonly Operand[] = [
|
|
|
213
230
|
{ kind: 'call', type: 'nstr', text: 'String(n)' },
|
|
214
231
|
{ kind: 'call', type: 'str', text: 's.toUpperCase()' },
|
|
215
232
|
{ kind: 'call', type: 'bool', text: 'Array.isArray(arr)' },
|
|
233
|
+
// A word a lexicon translates, inside a call's arguments, which every reader
|
|
234
|
+
// takes as written: es `Math.max(n, verdadero)`.
|
|
235
|
+
{ kind: 'call', type: 'num', text: 'Math.max(n, true)' },
|
|
216
236
|
{ kind: 'array', type: 'arr', text: '[1, 2]' },
|
|
217
237
|
{ kind: 'array', type: 'arr', text: '[n, 2]' },
|
|
238
|
+
// A word some language's lexicon translates, inside an array in a larger
|
|
239
|
+
// expression: every reader takes a bracket group whole, so a renderer that
|
|
240
|
+
// localized `true` there (es `2 is in [verdadero, n]`) lost it in all 23.
|
|
241
|
+
{ kind: 'array', type: 'arr', text: '[true, n]' },
|
|
218
242
|
{ kind: 'parens', type: 'num', text: '(n + 1)' },
|
|
219
243
|
{ kind: 'parens', type: 'str', text: '(s + "c")' },
|
|
220
244
|
{ kind: 'parens', type: 'bool', text: '(n > 1)' },
|
|
@@ -235,6 +259,10 @@ export const OPERANDS: readonly Operand[] = [
|
|
|
235
259
|
// a variable, but no cell had one: semantic's English dropped a whole loop
|
|
236
260
|
// counted by one (`repeat $n times`), and every translation with it.
|
|
237
261
|
{ kind: 'sigil', type: 'num', text: '$n' },
|
|
262
|
+
// A number with a CSS unit is one string upstream (`100px`): every reader
|
|
263
|
+
// split it, so `set my *width to 100px` was refused in all 24 languages.
|
|
264
|
+
{ kind: 'length', type: 'str', text: '100px' },
|
|
265
|
+
{ kind: 'length', type: 'str', text: '50%' },
|
|
238
266
|
];
|
|
239
267
|
|
|
240
268
|
/** An operator slot, and the value types that fill it. */
|
|
@@ -330,7 +358,16 @@ export const BINARY_OPERATORS: readonly BinaryOperator[] = [
|
|
|
330
358
|
representative: true,
|
|
331
359
|
signatures: [sig('num', 'arr', '2', '[1, 2, 6]', 'bool')],
|
|
332
360
|
},
|
|
333
|
-
{
|
|
361
|
+
{
|
|
362
|
+
op: 'matches',
|
|
363
|
+
representative: true,
|
|
364
|
+
// An attribute selector whose value a lexicon translates: es wrote
|
|
365
|
+
// `<[aria-pressed=verdadero]/>`, another selector, in all 23 languages.
|
|
366
|
+
signatures: [
|
|
367
|
+
sig('one', 'none', '#a', '.x', 'bool'),
|
|
368
|
+
sig('one', 'none', '#a', '<[aria-pressed=true]/>', 'bool'),
|
|
369
|
+
],
|
|
370
|
+
},
|
|
334
371
|
// Operator PHRASES (the after-85 handoff, part 2): the words each language
|
|
335
372
|
// renders for them are read back by the join and the sense table, which no
|
|
336
373
|
// single-word operator exercises. One of each class is representative, so the
|
|
@@ -623,6 +660,7 @@ const TEMPLATES: Record<Position, (expression: string) => string> = {
|
|
|
623
660
|
put: e => `on click put ${e} into #out`,
|
|
624
661
|
set: e => `on click set x to ${e} then put x into #out`,
|
|
625
662
|
if: e => `on click if ${e} then put "Y" into #out else put "N" into #out end`,
|
|
663
|
+
branch: e => `on click if ${e} add .y else add .n end then put my className into #out`,
|
|
626
664
|
while: e => `on click set i to 0 then repeat while i < ${e} increment i end then put i into #out`,
|
|
627
665
|
times: e => `on click set i to 0 then repeat ${e} times increment i end then put i into #out`,
|
|
628
666
|
increment: e => `on click set i to 1 then increment i by ${e} then put i into #out`,
|
|
@@ -649,10 +687,18 @@ const TEMPLATES: Record<Position, (expression: string) => string> = {
|
|
|
649
687
|
* `it`, but its role took no literal, so English dropped `get "hello"`, `get
|
|
650
688
|
* 3` and `get true` whole (and every translation with them) while `get n` and
|
|
651
689
|
* `get #a's textContent` read.
|
|
690
|
+
*
|
|
691
|
+
* A `branch` condition is every `if` condition but the names (none failed
|
|
692
|
+
* there that `if` did not). The `if` cells write `then` and a `put` with its
|
|
693
|
+
* target, so a reader always saw where the condition ended. Without `then`, an
|
|
694
|
+
* SOV language writes the branch's first role right after the condition, and
|
|
695
|
+
* its reader has to find the seam: tr read `if 2 is in arr add .a end` as
|
|
696
|
+
* `if 2 add .a to is in arr end` (its destination marker is optional, and `in`
|
|
697
|
+
* is also one), with every operand, and the `if` cells all passed.
|
|
652
698
|
*/
|
|
653
699
|
function positionsFor(type: ValueType, bare: boolean, group: MatrixCell['group']): Position[] {
|
|
654
700
|
const out: Position[] = ['put', 'set'];
|
|
655
|
-
if (type === 'bool' || bare) out.push('if');
|
|
701
|
+
if (type === 'bool' || bare) out.push('if', 'branch');
|
|
656
702
|
if (type === 'num' || type === 'nstr') out.push('while', 'times', 'increment');
|
|
657
703
|
if (group !== 'operand-operator') out.push('chain', 'get');
|
|
658
704
|
return out;
|