Commit 4b55e5c1 authored by Leon Arnott's avatar Leon Arnott
Browse files

Normalised the meaning of spread datatypes inside arrays and string patterns,...

Normalised the meaning of spread datatypes inside arrays and string patterns, fixing an ambiguity where spread string datatypes (like ...whitespace) referred to both strings of multiple whitespace and arrays of multiple strings of whitespace.

Also:
 * Updated the logo, and left it unoptimised due to the previous version not rendering correctly in Chrome.
 * Fixed a bug where the 'anycase' datatype wasn't permitted in (p:) macros.
 * Fixed a bug in the previous commit where (icon-restart:)'s name wasn't recognised.
 * A few panels in The Harlowe Toolbar now support generating (enchant:?passage) calls.
parent 621ee1263152
<svg xmlns="http://www.w3.org/2000/svg" width="266" height="266" class="undefined"><style>.a{fill:#ccc;}.b{fill:#ccc;font-family:sans-serif;font-size:40px;letter-spacing:0px;line-height:1.25;word-spacing:0px;}.c{-inkscape-font-specification:monospace;fill:#ccc;font-family:monospace;font-size:256px;}.d{fill:#ccc;font-family:sans-serif;font-size:192px;letter-spacing:0px;line-height:1.25;word-spacing:0px;}.e{-inkscape-font-specification:monospace;fill:#ccc;font-family:monospace;font-size:192px;}.f{font-family:sans-serif;letter-spacing:0px;line-height:0%;word-spacing:0px;}.g{-inkscape-font-specification:Consolas;fill:#800080;font-family:Consolas;font-size:300px;line-height:0;}.h{fill:#f0f;}.i{font-family:sans-serif;font-size:40px;letter-spacing:0px;line-height:1.25;word-spacing:0px;}.j{fill:#a84186;font-family:sans-serif;font-size:40px;letter-spacing:0px;line-height:1.25;word-spacing:0px;}.k{-inkscape-font-specification:monospace;fill:#a84186;font-family:monospace;font-size:256px;}.l{fill:#a84186;font-family:sans-serif;font-size:192px;letter-spacing:0px;line-height:1.25;word-spacing:0px;}.m{-inkscape-font-specification:monospace;fill:#a84186;font-family:monospace;font-size:192px;}.n{fill-opacity:0.53333336;fill:#a84186;}</style><g transform="translate(-71.971426 -713.1875)" class="undefined"><g transform="matrix(1.1088107 -0.00961036 0.70536778 0.85557564 -675.58683 134.54896)" class="a"><text transform="matrix(0.9368318 -0.17385867 0.70820721 0.93599731 0 0)" y="898.6" x="-548.2" class="b"><tspan y="898.6" x="-548.2" class="undefined"><tspan y="898.6" x="-548.2" class="c"> H</tspan></tspan></text><text transform="matrix(0.9368318 -0.17385867 0.70820721 0.93599731 0 0)" y="864.2" x="-447.3" class="d"><tspan y="864.2" x="-447.3" class="undefined"><tspan y="864.2" x="-447.3" class="e"> :</tspan></tspan></text><text transform="matrix(0.9368318 -0.17385867 0.70820721 0.93599731 0 0)" y="872.8" x="-395.8" class="d"><tspan y="872.8" x="-395.8" class="undefined"><tspan y="872.8" x="-395.8" class="e"> )</tspan></tspan></text><text transform="matrix(0.9368318 -0.17385867 0.70820721 0.93599731 0 0)" y="871.8" x="-629.4" class="d"><tspan y="871.8" x="-629.4" class="undefined"><tspan y="871.8" x="-629.4" class="e"> (</tspan></tspan></text></g><text x="66.6" y="947.5" class="f"/><text x="76.3" y="1094.7" class="i"><tspan x="76.3" y="1131.1" class="undefined"/></text><text transform="skewY(-10.19254)" y="958" x="112" class="j"><tspan y="958" x="112" class="undefined"><tspan y="958" x="112" class="k"> H</tspan></tspan></text><text transform="skewY(-10.19254)" y="923.6" x="212.9" class="l"><tspan y="923.6" x="212.9" class="undefined"><tspan y="923.6" x="212.9" class="m"> :</tspan></tspan></text><text transform="skewY(-10.19254)" y="932.2" x="264.3" class="l"><tspan y="932.2" x="264.3" class="undefined"><tspan y="932.2" x="264.3" class="m"> )</tspan></tspan></text><text transform="skewY(-10.19254)" y="931.2" x="30.8" class="l"><tspan y="931.2" x="30.8" class="undefined"><tspan y="931.2" x="30.8" class="m"> (</tspan></tspan></text><path d="m71.8 714.1v59L339 722.7v-8.6z" class="n"/><path d="M339.8 979.6V920.6L72.7 971v8.6z" class="n"/></g></svg>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
sodipodi:docname="icon.svg"
id="svg146"
version="1.1"
class="undefined"
height="266"
width="266">
<metadata
id="metadata152">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs150" />
<sodipodi:namedview
inkscape:current-layer="svg146"
inkscape:window-maximized="0"
inkscape:window-y="195"
inkscape:window-x="193"
inkscape:cy="134.98464"
inkscape:cx="66.041863"
inkscape:zoom="2.7255639"
showgrid="false"
id="namedview148"
inkscape:window-height="1090"
inkscape:window-width="2153"
inkscape:pageshadow="2"
inkscape:pageopacity="0"
guidetolerance="10"
gridtolerance="10"
objecttolerance="10"
borderopacity="1"
bordercolor="#666666"
pagecolor="#ffffff" />
<style
id="style82">.a{fill:#ccc;}.b{fill:#ccc;font-family:sans-serif;font-size:40px;letter-spacing:0px;line-height:1.25;word-spacing:0px;}.c{-inkscape-font-specification:monospace;fill:#ccc;font-family:monospace;font-size:256px;}.d{fill:#ccc;font-family:sans-serif;font-size:192px;letter-spacing:0px;line-height:1.25;word-spacing:0px;}.e{-inkscape-font-specification:monospace;fill:#ccc;font-family:monospace;font-size:192px;}.f{font-family:sans-serif;letter-spacing:0px;line-height:0%;word-spacing:0px;}.g{-inkscape-font-specification:monospace;fill:#800080;font-family:monospace;font-size:300px;line-height:0;}.h{fill:#f0f;}.i{font-family:sans-serif;font-size:40px;letter-spacing:0px;line-height:1.25;word-spacing:0px;}.j{fill:#a84186;font-family:sans-serif;font-size:40px;letter-spacing:0px;line-height:1.25;word-spacing:0px;}.k{-inkscape-font-specification:monospace;fill:#a84186;font-family:monospace;font-size:256px;}.l{fill:#a84186;font-family:sans-serif;font-size:192px;letter-spacing:0px;line-height:1.25;word-spacing:0px;}.m{-inkscape-font-specification:monospace;fill:#a84186;font-family:monospace;font-size:192px;}.n{fill-opacity:0.53333336;fill:#a84186;}</style>
<g
transform="matrix(1.0258547,-0.1271479,-0.2588808,0.36831902,154.72162,-104.40968)"
class="a"
id="g108">
<text
style="-inkscape-font-specification:'Courier New, Normal';font-family:'Courier New';font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:249.729px;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;line-height:1.25;letter-spacing:0px;word-spacing:0px;fill:#cccccc;"
transform="matrix(0.9368318,-0.17385867,0.70820721,0.93599731,0,0)"
y="898.59998"
x="-548.20001"
class="b"
id="text88"><tspan
style="-inkscape-font-specification:'Courier New, Normal';font-family:'Courier New';font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:249.72882612px;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;"
y="898.59998"
x="-548.20001"
class="undefined"
id="tspan86"><tspan
style="-inkscape-font-specification:'Courier New, Normal';font-family:'Courier New';font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:249.72882612px;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;"
y="898.59998"
x="-548.20001"
class="c"
id="tspan84">H</tspan></tspan></text>
<text
style="-inkscape-font-specification:'Courier New, Normal';font-family:'Courier New';font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:192px;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;line-height:1.25;letter-spacing:0px;word-spacing:0px;fill:#cccccc;"
transform="matrix(0.9368318,-0.17385867,0.70820721,0.93599731,0,0)"
y="864.20001"
x="-447.29999"
class="d"
id="text94"><tspan
style="-inkscape-font-specification:'Courier New, Normal';font-family:'Courier New';font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:192.00000181px;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;"
y="864.20001"
x="-447.29999"
class="undefined"
id="tspan92"><tspan
style="-inkscape-font-specification:'Courier New, Normal';font-family:'Courier New';font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:192.00000181px;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;"
y="864.20001"
x="-447.29999"
class="e"
id="tspan90">:</tspan></tspan></text>
<text
style="-inkscape-font-specification:'Courier New, Normal';font-family:'Courier New';font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:192px;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;line-height:1.25;letter-spacing:0px;word-spacing:0px;fill:#cccccc;"
transform="matrix(0.9368318,-0.17385867,0.70820721,0.93599731,0,0)"
y="872.79999"
x="-395.79999"
class="d"
id="text100"><tspan
style="-inkscape-font-specification:'Courier New, Normal';font-family:'Courier New';font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:192.00000181px;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;"
y="872.79999"
x="-395.79999"
class="undefined"
id="tspan98"><tspan
style="-inkscape-font-specification:'Courier New, Normal';font-family:'Courier New';font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:192.00000181px;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;"
y="872.79999"
x="-395.79999"
class="e"
id="tspan96">)</tspan></tspan></text>
<text
style="-inkscape-font-specification:'Courier New, Normal';font-family:'Courier New';font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:192px;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;line-height:1.25;letter-spacing:0px;word-spacing:0px;fill:#cccccc;"
transform="matrix(0.9368318,-0.17385867,0.70820721,0.93599731,0,0)"
y="871.79999"
x="-629.40002"
class="d"
id="text106"><tspan
style="-inkscape-font-specification:'Courier New, Normal';font-family:'Courier New';font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:192.00000181px;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;"
y="871.79999"
x="-629.40002"
class="undefined"
id="tspan104"><tspan
style="-inkscape-font-specification:'Courier New, Normal';font-family:'Courier New';font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:192.00000181px;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;"
y="871.79999"
x="-629.40002"
class="e"
id="tspan102">(</tspan></tspan></text>
</g>
<text
x="-5.3714275"
y="234.3125"
class="f"
id="text110" />
<text
style="-inkscape-font-specification:'Courier New, Normal';font-family:'Courier New';font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:40px;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;line-height:1.25;letter-spacing:0px;word-spacing:0px;"
x="4.328577"
y="381.51245"
class="i"
id="text114"><tspan
id="tspan347"
sodipodi:role="line"></tspan></text>
<text
style="-inkscape-font-specification:'Courier New, Normal';font-family:'Courier New';font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:253.333px;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;line-height:1.25;letter-spacing:0px;word-spacing:0px;fill:#a84186;"
transform="skewY(-10.19254)"
y="231.87247"
x="40.028576"
class="j"
id="text120"><tspan
style="-inkscape-font-specification:'Courier New, Normal';font-family:'Courier New';font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:253.33333333px;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;"
y="231.87247"
x="40.028576"
class="undefined"
id="tspan118"><tspan
style="-inkscape-font-specification:'Courier New, Normal';font-family:'Courier New';font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:253.33333333px;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;"
y="231.87247"
x="40.028576"
class="k"
id="tspan116">H</tspan></tspan></text>
<text
style="-inkscape-font-specification:'Courier New, Normal';font-family:'Courier New';font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:192px;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;line-height:1.25;letter-spacing:0px;word-spacing:0px;fill:#a84186;"
transform="skewY(-10.19254)"
y="197.47244"
x="140.92857"
class="l"
id="text126"><tspan
style="-inkscape-font-specification:'Courier New, Normal';font-family:'Courier New';font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:192px;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;"
y="197.47244"
x="140.92857"
class="undefined"
id="tspan124"><tspan
style="-inkscape-font-specification:'Courier New, Normal';font-family:'Courier New';font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:192px;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;"
y="197.47244"
x="140.92857"
class="m"
id="tspan122">:</tspan></tspan></text>
<text
style="-inkscape-font-specification:'Courier New, Normal';font-family:'Courier New';font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:192px;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;line-height:1.25;letter-spacing:0px;word-spacing:0px;fill:#a84186;"
transform="skewY(-10.19254)"
y="206.07248"
x="192.32857"
class="l"
id="text132"><tspan
style="-inkscape-font-specification:'Courier New, Normal';font-family:'Courier New';font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:192px;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;"
y="206.07248"
x="192.32857"
class="undefined"
id="tspan130"><tspan
style="-inkscape-font-specification:'Courier New, Normal';font-family:'Courier New';font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:192px;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;"
y="206.07248"
x="192.32857"
class="m"
id="tspan128">)</tspan></tspan></text>
<text
style="-inkscape-font-specification:'Courier New, Normal';font-family:'Courier New';font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:192px;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;line-height:1.25;letter-spacing:0px;word-spacing:0px;fill:#a84186;"
transform="skewY(-10.19254)"
y="205.07248"
x="-41.171425"
class="l"
id="text138"><tspan
style="-inkscape-font-specification:'Courier New, Normal';font-family:'Courier New';font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:192px;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;"
y="205.07248"
x="-41.171425"
class="undefined"
id="tspan136"><tspan
style="-inkscape-font-specification:'Courier New, Normal';font-family:'Courier New';font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:192px;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;"
y="205.07248"
x="-41.171425"
class="m"
id="tspan134">(</tspan></tspan></text>
<path
d="m -0.171426,0.9125 v 59 l 267.199996,-50.4 v -8.6 z"
class="n"
id="path140" />
<path
d="m 267.82857,266.4125 v -59 l -267.099996,50.4 v 8.6 z"
class="n"
id="path142" />
</svg>
......@@ -75,12 +75,21 @@ define([
array, but that may not be precise enough for you. `$pos matches (a: number, number)` checks to see if $pos is an array containing only two numbers in a row. A data structure with datatype
names in various positions inside it is called a **pattern**, and `matches` is used to compare data values and patterns.
A modified datatype called a **spread datatype** can be created using the `...` syntax. `...str` can match any number of string values, including zero. You can think of this as a counterpart to
the spread `...` syntax used inside macro calls - just as one array is turned into many values, so too is `...str` considered equivalent to enough `str` datatypes to match the values on the other side.
Spread datatypes:
A modified datatype can be created using the `...` syntax inside an (a:) macro call or a pattern macro call. `...str` can match any number of string values, including zero.
You can think of this as a counterpart to the normal use of the spread `...` syntax - just as one array is turned into many values, so too is `...str` considered equivalent to enough `str` datatypes
to match the values on the other side.
Inside a typed variable in a custom macro call defined using (macro:)) a spread datatype refers to zero or more matching values being given at and after that variable's position in the macro call.
The typed variable in `(macro: ...num-type _a, [(output:_a)])` refers to any number of numbers being given to the macro. The variable inside the code hook is an array containing all of those number values.
See the (macro:) macro's article for more details.
Inside a string pattern, like those created by (p:), spread datatypes have a slightly different meaning. They refer to zero or more sequences of the given datatype. `...whitespace` inside (p:) matches
an entire string of whitespace, which can be one or more characters, as well as the empty string. `...digit` matches zero or more digit characters.
Note: outside of macro calls and typed variables, spread datatypes are considered the same as normal datatypes. So, `(a:2,3) matches ...num` produces `false` - in this case, you should write `(a:2,3) matches (a:...num)` instead.
Some more examples.
* `(datamap:'a',2,'b',4) matches (datamap:'b',num,'a',num))` is true.
......@@ -213,13 +222,17 @@ define([
false
),
int: obj => typeof obj === "number" && obj === (obj|0),
uppercase: (obj, rest) => typeof obj === "string" && (rest ? obj.length !== 0 : [...obj].length === 1) && [...obj].every(char => char !== char.toLowerCase()),
lowercase: (obj, rest) => typeof obj === "string" && (rest ? obj.length !== 0 : [...obj].length === 1) && [...obj].every(char => char !== char.toUpperCase()),
whitespace: (obj, rest) => typeof obj === "string" && !!obj.match("^" + realWhitespace + (rest ? '*' : '') + "$"),
digit: (obj, rest) => typeof obj === "string" && !!obj.match("^\\d" + (rest ? '*' : '') + "$"),
alnum: (obj, rest) => typeof obj === "string" && !!obj.match("^" + anyRealLetter + (rest ? '*' : '') + "$"),
anycase: (obj, rest) => typeof obj === "string" && !!obj.match("^" + anyCasedLetter + (rest ? '*' : '') + "$"),
linebreak: (obj, rest) => typeof obj === "string" && !!obj.match("^" + anyNewline + (rest ? '*' : '') + "$"),
/*
The following string types only match single characters, even if a rest-type, Only inside (p:), which
compiles its own RegExps for these types, is it possible for them to match multiple characters using rest.
*/
uppercase: obj => typeof obj === "string" && [...obj].length === 1 && [...obj].every(char => char !== char.toLowerCase()),
lowercase: obj => typeof obj === "string" && [...obj].length === 1 && [...obj].every(char => char !== char.toUpperCase()),
whitespace: obj => typeof obj === "string" && !!obj.match("^" + realWhitespace + "$"),
digit: obj => typeof obj === "string" && !!obj.match("^\\d" + "$"),
alnum: obj => typeof obj === "string" && !!obj.match("^" + anyRealLetter + "$"),
anycase: obj => typeof obj === "string" && !!obj.match("^" + anyCasedLetter + "$"),
linebreak: obj => typeof obj === "string" && !!obj.match("^" + anyNewline + "$"),
any: () => true,
/*
"const" is handled almost entirely as a special case inside VarRef. This
......
......@@ -697,7 +697,7 @@ define(['jquery', 'macros', 'utils', 'state', 'passages', 'renderer', 'engine',
["Undo", "&#8630;", () => State.pastLength > 0],
["Redo", "&#8631;", () => State.futureLength > 0],
["Fullscreen", "&#9974;", () => document.fullscreenEnabled || document.msFullscreenEnabled],
["Reload", "&#10226;", Object],
["Restart", "&#10226;", Object],
].forEach(([name, defaultIcon, visibilityTest]) => {
/*d:
(icon-undo: [String], [String]) -> Command
......
......@@ -46,11 +46,13 @@ define(['jquery', 'macros', 'utils', 'utils/operationutils', 'datatypes/datatype
"(" + name + ":) can't have typed variables inside its pattern.");
}
if (pattern.regExp) {
/*
If this is a recompilation of a sensitive pattern into an insensitive one, then convert this argument
into an insensitive one, too. (Note that the insensitive() call won't recompile an already-insensitive pattern.)
*/
return insensitive ? pattern.insensitive().regExp : pattern.regExp;
return (pattern.rest ? "(?:" : "") + (
/*
If this is a recompilation of a sensitive pattern into an insensitive one, then convert this argument
into an insensitive one, too. (Note that the insensitive() call won't recompile an already-insensitive pattern.)
*/
insensitive ? pattern.insensitive().regExp : pattern.regExp
) + (pattern.rest ? ")*" : "");
}
const pName = pattern.name;
/*
......@@ -58,7 +60,7 @@ define(['jquery', 'macros', 'utils', 'utils/operationutils', 'datatypes/datatype
*/
const rest = pattern.rest ? "*" : "";
/*
All of these, unfortunately, need to be manually aligned with their implementation in datatype.js.
All of these need to be manually aligned with their implementation in datatype.js.
Fortunately, both implementations rely on the same RegExp strings in Utils.
*/
if (pName === "alnum") {
......@@ -76,6 +78,9 @@ define(['jquery', 'macros', 'utils', 'utils/operationutils', 'datatypes/datatype
if (pName === "lowercase") {
return (insensitive ? anyCasedLetter : anyLowercase) + rest;
}
if (pName === "anycase") {
return anyCasedLetter + rest;
}
if (pName === "digit") {
return "\\d" + rest;
}
......@@ -208,7 +213,20 @@ define(['jquery', 'macros', 'utils', 'utils/operationutils', 'datatypes/datatype
Note that in the case of a 0-character match, like "(p-opt:'A')-type _a", we must default the value to the empty string
ourselves rather than leaving it as undefined.
*/
return results.map((r,i) => typedVars[i] && ({ dest: typedVars[i], value: r || '', src: undefined, })).filter(Boolean);
return results.map((r,i) => {
let dest = typedVars[i];
if (dest) {
/*
As with spread datatypes inside destructuring array patterns (like (a: 1, ...num-type _a)) which need to be wrapped in (a:)
(becoming (a:...num)-type _a), the datatype of the resulting variable inside a string pattern needs to be wrapped in (p:).
*/
if (dest.datatype.rest && !dest.datatype.regExp) {
dest = dest.TwineScript_Clone();
dest.datatype = createPattern({ name:"p", fullArgs: [dest.datatype], });
}
return ({ dest, value: r || '', src: undefined, });
}
}).filter(Boolean);
},
TwineScript_IsTypeOf(value) {
if (!canBeUsedAlone) {
......@@ -226,7 +244,9 @@ define(['jquery', 'macros', 'utils', 'utils/operationutils', 'datatypes/datatype
/*
The fullArgs are given unto this function entirely to allow ToSource to access them.
*/
TwineScript_ToSource: () => "(" + name + ":" + fullArgs.map(toSource) + ")",
TwineScript_ToSource () {
return (this.rest ? "..." : '') + "(" + name + ":" + fullArgs.map(toSource) + ")";
},
});
/*
Replacing the TwineScript_ObjectName getter on Datatype requires the following finesse.
......
......@@ -1276,19 +1276,37 @@
type: 'preview',
text: 'Example border preview',
update(m, panel) {
const changersObj = m.suppressedChangers || m.changers;
panel.firstChild.setAttribute('style', `border-style:${
m.changers.b4r ? m.changers.b4r.map(parse).join(' ') + ";" : ''
changersObj.b4r ? changersObj.b4r.map(parse).join(' ') + ";" : ''
}${
/*
Border-size is a multiplier on the default Harlowe border (2px).
*/
m.changers['b4r-size'] ? 'border-width:' + m.changers['b4r-size'].reduce((a,e) => `${a} ${e*2}px`,'') + ";" : ''
changersObj['b4r-size'] ? 'border-width:' + changersObj['b4r-size'].reduce((a,e) => `${a} ${e*2}px`,'') + ";" : ''
}${
m.changers['b4r-colour'] ? 'border-color:' + m.borderColours.join(' ') + ";" : ''
changersObj['b4r-colour'] ? 'border-color:' + m.borderColours.join(' ') + ";" : ''
}`);
},
},
...dropdownControls("Top", 0), ...dropdownControls("Right", 1), ...dropdownControls("Bottom", 2), ...dropdownControls("Left", 3),
{
type: 'radios',
name: 'Affect:',
options: ["The attached hook", "The remainder of the passage or hook.", "The entire passage."],
model(m, el) {
const v = el[$]('input:checked');
const index = Array.from(el[$$]('label')).indexOf(v.parentNode);
if (index >= 2 && Object.keys(m.changers).length) {
m.suppressedChangers = m.changers;
m.changers = {};
} else if (index === 1) {
m.wrapStart = "[=\n";
m.wrapEnd = "";
}
},
},
{
type: 'confirm',
model(m) {
......@@ -1297,15 +1315,20 @@
Quickly check that each of the to-be-constructed changers' values differ from the default,
and don't create them if not.
*/
const changersObj = m.suppressedChangers || m.changers;
[['b4r', e => parse(e) === "none"], ['b4r-size', e => +e === 1], ['b4r-colour', e => e === "transparent"]].forEach(([name, test]) => {
m.changers[name] = reduce4ValueProp(m.changers[name]);
if (m.changers[name].every(test)) {
delete m.changers[name];
changersObj[name] = reduce4ValueProp(changersObj[name]);
if (changersObj[name].every(test)) {
delete changersObj[name];
if (name === 'b4r') {
m.valid = false;
}
}
});
if (m.valid && m.suppressedChangers) {
m.wrapStart = "(enchant:?passage," + Object.entries(changersObj).map(([k,v]) => `(${k}:${v.join(',')})`).join('+') + ")";
m.wrapEnd = "";
}
},
}
);
......
......@@ -134,7 +134,7 @@
doesNotMatch: producesBooleanTrueIf('does not match') + "one side does NOT describe the other." + otherwiseFalse,
bind: `The <b>bind</b> or <b>2bind</b> keyword specifies that the variable to the right should be "bound" to an interactable element. This is used only by certain command macros, like \`(dialog:)\` or \`(cycling-link:)\`.`,
comma: `Use <b>commas</b> to separate the values that you give to macro calls.`,
spread: `This is a <b>spreader</b> These spread out the values in the array, string or dataset to the right of it, as if each value was individually placed in the call and separated with commas.`
spread: `This is a <b>spreader</b>. These spread out the values in the array, string or dataset to the right of it, as if each value was individually placed in the call and separated with commas.`
+ `<br><br>Alternatively, if a datatype is to the right of it, that datatype becomes a <b>spread datatype</b> that matches zero or more of itself.`,
typeSignature: `The <b>-type</b> suffix is used to restrict the variable on the right to only holding data that matches the data pattern on the left. Variables restricted in this way are called <b>typed variables</b>.`,
addition: `Use the <b>addition operator</b> to add two numbers together, as well as join two strings or two arrays, and combine two datamaps, two datasets, two changers, or two colours.`
......
......@@ -462,7 +462,7 @@ describe("interface macros", function(){
}
});
});
[["Undo",""],["Redo",""],["Fullscreen",""],["Reload",""]].forEach(function(a) {
[["Undo",""],["Redo",""],["Fullscreen",""],["Restart",""]].forEach(function(a) {
var name = a[0], symbol = a[1];
describe("the (icon-" + name.toLowerCase() + ":) macro", function() {
it("accepts an optional one-character-long string, and an optional 2+ character-long string", function() {
......
......@@ -54,7 +54,7 @@ describe("list markup", function() {
"<ul><li>A</li></ul>B"
);
});
xit("work inside hooks", function() {
it("work inside hooks", function() {
expect(
" |x>[\n* A\n* B]\n"
).markupToBecome(
......@@ -148,7 +148,7 @@ describe("list markup", function() {
"<ol><li>A</li></ol>B"
);
});
xit("work inside hooks", function() {
it("work inside hooks", function() {
expect(
" |x>[\n0. A\n0. B]\n"
).markupToBecome(
......
......@@ -13,6 +13,7 @@ describe("patterns", function() {
['(dm:)',"datamap","dm"],
['(ds:)','dataset',"ds"],
['red','colour','color'],
['" "','whitespace',undefined],
['whitespace','datatype',undefined],
['(_a where _a is 2)','lambda',undefined,'no structural equality'],
['(macro:[(output:1)])','macro',undefined,'no structural equality'],
......@@ -47,7 +48,7 @@ describe("patterns", function() {
function isATest(op) {
typesAndValues.forEach(function(e) {
datatypes.forEach(function(name) {
if (name === "datatype" && op.includes("match")) {
if ((name === "datatype" && op.includes("match")) || (name.startsWith("str") && e[0] === '" "')) {
return;
}
// e[1] and e[2] are datatypes that e[0] is.
......@@ -130,9 +131,14 @@ describe("patterns", function() {
expect("(print: (a:" + type1 + ") "+name+" (a:(a:" + value + ")))").markupToPrint(((type1 === "array") !== Boolean(negative))+'');
expect("(print: (a:" + type1 + ",3," + type1 + ") "+name+" (a:3,2,4))").markupToPrint(pFalse);
});
it("matches spread " + type1 + " datatype inside arrays to any number of " + type1 + "s in the same position", function() {
it("matches spread " + type1 + " datatypes inside arrays to any number of " + type1 + "s in the same position", function() {
// Spread -> Value
expect("(print: (a: ..." + type1 + ") "+name+" (a:" + value + "))").markupToPrint(pTrue);
if (type1 === "whitespace") {
expect("(print: (a: ..." + type1 + ") "+name+" (a:' \n '))").markupToPrint(pFalse);
expect("(print: (a: ..." + type1 + ") "+name+" (a:' ',' \n '))").markupToPrint(pFalse);
}
expect("(print: (a: ..." + type1 + ") "+name+" (a:" + value + "," + value + "))").markupToPrint(pTrue);
expect("(print: (a: ..." + type1 + ") "+name+" (a:" + Array(7).fill(value) + "))").markupToPrint(pTrue);
expect("(print: (a: ..." + type1 + ") "+name+" (a:(size:1)))").markupToPrint(pFalse);
expect("(print: (a: ..." + type1 + ") "+name+" (a:(size:1)," + value + "))").markupToPrint(pFalse);
......@@ -176,11 +182,12 @@ describe("patterns", function() {
expect("(set: $a to '' is a whitespace)(print:$a)").markupToPrint("false");
expect("(set: $a to ' ' is a whitespace)(print:$a)").markupToPrint("true");
expect("(set: $a to ' ' is a whitespace)(print:$a)").markupToPrint("false");
expect("(set: $a to ' ' is a ...whitespace)(print:$a)").markupToPrint("true");
expect("(set: $a to \" .\" is a ...whitespace)(print:$a)").markupToPrint("false");
expect("(set: $a to ' ' is a ...whitespace)(print:$a)").markupToPrint("false");
expect("(set: $a to ' ' is a (p:...whitespace))(print:$a)").markupToPrint("true");
expect("(set: $a to \" .\" is a (p:...whitespace))(print:$a)").markupToPrint("false");
expect("(set: $a to 0 is a whitespace)(print:$a)").markupToPrint("false");
expect("(set: $a to '\t\t\n' is a ...whitespace)(print:$a)").markupToPrint("true");
expect("(set: $a to (str-repeated:5,' ') is a ...whitespace)(print:$a)").markupToPrint("true");
expect("(set: $a to '\t\t\n' is a (p:...whitespace))(print:$a)").markupToPrint("true");
expect("(set: $a to (str-repeated:5,' ') is a (p:...whitespace))(print:$a)").markupToPrint("true");
});
it("'integer' or 'int' matches integers", function() {
expect("(set: $a to 2.1 is a integer)(print:$a)").markupToPrint("false");
......@@ -193,17 +200,19 @@ describe("patterns", function() {
it("'alphanumeric' or 'alnum' matches single alphanumeric characters", function() {
expect("(set: $a to '' is a alnum)(print:$a)").markupToPrint("false");
expect("(set: $a to 'EűG2' is a alnum)(print:$a)").markupToPrint("false");
expect("(set: $a to 'EűG2' is a ...alnum)(print:$a)").markupToPrint("true");
expect("(set: $a to \"E-G\" is a ...alnum)(print:$a)").markupToPrint("false");
expect("(set: $a to 'EűG2' is a ...alphanumeric)(print:$a)").markupToPrint("true");
expect("(set: $a to 'EűG2\n' is a ...alphanumeric)(print:$a)").markupToPrint("false");
expect("(set: $a to 'EűG2' is a ...alnum)(print:$a)").markupToPrint("false");
expect("(set: $a to 'EűG2' is a (p:...alnum))(print:$a)").markupToPrint("true");
expect("(set: $a to \"E-G\" is a (p:...alnum))(print:$a)").markupToPrint("false");
expect("(set: $a to 'EűG2' is a (p:...alphanumeric))(print:$a)").markupToPrint("true");
expect("(set: $a to 'EűG2\n' is a (p:...alphanumeric))(print:$a)").markupToPrint("false");
});
it("'digit' matches digit characters", function() {
expect("(set: $a to '' is a digit)(print:$a)").markupToPrint("false");
expect("(set: $a to \"EGG\" is a digit)(print:$a)").markupToPrint("false");
expect("(set: $a to '2' is a digit)(print:$a)").markupToPrint("true");
expect("(set: $a to '1234567890' is a digit)(print:$a)").markupToPrint("false");
expect("(set: $a to '1234567890' is a ...digit)(print:$a)").markupToPrint("true");
expect("(set: $a to '1234567890' is a ...digit)(print:$a)").markupToPrint("false");
expect("(set: $a to '1234567890' is a (p:...digit))(print:$a)").markupToPrint("true");
expect("(set: $a to '2\n' is a digit)(print:$a)").markupToPrint("false");
});
it("'uppercase', 'lowercase' or 'anycase' matches single cased characters", function() {
......@@ -212,13 +221,15 @@ describe("patterns", function() {
expect("(set: $a to 'ӝ' is a lowercase)(print:$a)").markupToPrint("true");
expect("(set: $a to 'ӜEAR' is a uppercase)(print:$a)").markupToPrint("false");
expect("(set: $a to 'ӝear' is a lowercase)(print:$a)").markupToPrint("false");
expect("(set: $a to 'ӜEAR' is a ...uppercase)(print:$a)").markupToPrint("true");
expect("(set: $a to 'ӝear' is a ...lowercase)(print:$a)").markupToPrint("true");
expect("(set: $a to 'ӜEAr' is a ...uppercase)(print:$a)").markupToPrint("false");
expect("(set: $a to 'ӝeaR' is a ...lowercase)(print:$a)").markupToPrint("false");
expect("(set: $a to 'ӜEAR' is a ...uppercase)(print:$a)").markupToPrint("false");
expect("(set: $a to 'ӝear' is a ...lowercase)(print:$a)").markupToPrint("false");
expect("(set: $a to 'ӜEAR' is a (p:...uppercase))(print:$a)").markupToPrint("true");
expect("(set: $a to 'ӝear' is a (p:...lowercase))(print:$a)").markupToPrint("true");
expect("(set: $a to 'ӜEAr' is a (p:...uppercase))(print:$a)").markupToPrint("false");
expect("(set: $a to 'ӝeaR' is a (p:...lowercase))(print:$a)").markupToPrint("false");
expect("(set: $a to 'ӝ' is a anycase)(print:$a)").markupToPrint("true");
expect("(set: $a to 'Ӝ' is a anycase)(print:$a)").markupToPrint("true");
expect("(set: $a to 'Ӝӝӝ' is a ...anycase)(print:$a)").markupToPrint("true");
expect("(set: $a to 'Ӝӝӝ' is a (p:...anycase))(print:$a)").markupToPrint("true");
expect("(set: $a to '2' is a anycase)(print:$a)").markupToPrint("false");
});
it("'linebreak' matches line breaks", function() {
......@@ -241,6 +252,8 @@ describe("patterns", function() {
var stringTypes = [
['uppercase','A'],
['lowercase','a'],
['anycase','a'],
['anycase','A'],
['digit','3'],
['whitespace',' '],
['linebreak','\n']
......@@ -371,7 +384,9 @@ describe("patterns", function() {
});
describe("(datatype:)", function() {
it("takes most kinds of data, and produces a datatype that matches it", function() {
typesAndValues.forEach(function(e) {
typesAndValues.filter(function(e) {
return e[1] !== "whitespace";
}).forEach(function(e) {
expect("(print:(datatype:" + e[0] + ") is " + e[1] + ")").markupToPrint("true");
});
});
......@@ -383,7 +398,7 @@ describe("patterns", function() {
describe("the (datapattern:) macro", function() {
it("takes most kinds of data, and produces a datatype that matches it", function() {
typesAndValues.filter(function(e) {
return e[1] !== "array" && e[1] !== "datamap";
return e[1] !== "array" && e[1] !== "datamap" && e[1] !== "whitespace";
}).forEach(function(e) {
expect("(print:(datapattern:" + e[0] + ") is " + e[1] + ")").markupToPrint("true");
});
......
......@@ -140,11 +140,11 @@ describe("variables", function() {
});
it("works with spread typed variables", function() {
expect("(set: ...num-type $a to 2)$a").markupToPrint('2');
expect("(set: ...whitespace-type $b to ' ')$b").markupToPrint(' ');
expect("(set: (p:...whitespace)-type $b to ' ')$b").markupToPrint(' ');
expect("(set: (a:...num)-type $z to (a:0,1,2))$z").markupToPrint('0,1,2');
expect("(set: (a:...odd, even-type $y)-type $e to (a:1,3,5,6))$e $y").markupToPrint('1,3,5,6 6');
expect("(set: ...digit-type $h to '0041')$h").markupToPrint('0041');