Commit fbacb9fa authored by Leon Arnott's avatar Leon Arnott
Browse files

Fixed a long-standing bug where a string containing \ followed by a newline...

Fixed a long-standing bug where a string containing \ followed by a newline would result in the newline transforming into the letter "n".

Also:

 * Added "newline" as an alias for the "linebreak" datatype. (Some parts of the documentation erroneously use the former instead of the latter.)
 * Fixed another documentation line.
parent c349f8307c2f
......@@ -14,6 +14,7 @@ Documentation is at http://twine2.neocities.org/. See below for compilation inst
* Fixed a bug where Harlowe attempting to generate certain error messages would instead cause an unrelated Javascript error to occur.
* Fixed a Javascript bug that resulted when using `(load-game:)` to load a saved game that had previously used `(mock-visits:)` in Debug Mode.
* Fixed a bug where a Javascript error would be produced when 0 was used in a substring or subarray data name, such as `$a's (range:0,2)`. Now, a proper error message will be given.
* Fixed a long-standing bug where a string containing \ followed by a newline would result in the newline transforming into the letter "n".
* Debug Mode: fixed a bug where values in the Variables pane would sometimes be truncated incorrectly, causing partial HTML entities like "&am…" to appear.
#### Alterations
......@@ -21,6 +22,7 @@ Documentation is at http://twine2.neocities.org/. See below for compilation inst
* Certain kinds of intense substring or subarray data names, such as `$a's (range:1,65535)`, have had their runtime performance improved.
* Made the syntax colour of the lambda keywords (`where`, `when`, `via`, `each` and `making`) slightly brighter.
* `(mock-visits:)` effects are now saved using `(save-game:)` in Debug Mode.
* Added `newline` as an alias for the `linebreak` datatype. (Some parts of the documentation previously erroneously used the former instead of the latter.)
* Editor: disabled the "overwrite" input mode that was toggled by pressing Insert, because I've decided it's too confusing and easy to activate accidentally.
### 3.2.2 changes (May 12, 2021):
......
......@@ -57,7 +57,7 @@ define([
| `anycase` | This matches any character which is case-sensitive - that is, where its (lowercase:) form doesn't match its (uppercase:) form.
| `alphanumeric`, `alnum` | Only matches a single alphanumeric character (letters and numbers).
| `digit` | Only matches a string consisting of exactly one of the characters '0', '1', '2', '3', '4', '5', '6', '7', '8', and '9'.
| `linebreak` | Only matches a line break character.
| `linebreak`, `newline` | Only matches a line break character (also known as a "newline" character).
| `const` | Matches nothing; Use this only with (set:) to make constants.
| `any` | Matches anything; Use this with (macro:) to make variables that accept any storable type, or with (set:) inside data structure patterns.
......@@ -192,6 +192,7 @@ define([
name === "boolean" ? "bool" :
name === "alphanumeric" ? "alnum" :
name === "integer" ? "int" :
name === "newline" ? "linebreak" :
name
);
const ret = Object.create(this);
......
......@@ -386,7 +386,7 @@ define(['jquery', 'macros', 'utils', 'utils/operationutils', 'datatypes/datatype
This is part of a suite of string pattern macros. Consult the (p:) article to learn more about string patterns, special user-created datatypes
that can match very precise kinds of strings.
When you use this in (unpack:), such as `(unpack: "Connie" into (p-opt:'Lord')-type _isLord`, and the optional pattern doesn't match,
When you use this in (unpack:), such as `(unpack: "Connie" into (p:(p-opt:"Lord")-type _isLord, str-type _name))`, and the optional pattern doesn't match,
the variable will be set to the empty string "".
Note that while you can use this as the datatype of a TypedVar (as shown previously), you can't nest TypedVars inside this, because it is an optional match - `(set: (p:"A",(p-opt:digit-type _d)) to "A")`
......
......@@ -14,14 +14,15 @@ define(['macros', 'utils', 'utils/operationutils', 'datatypes/colour', 'datatype
/*d:
String data
A string is just a run of text - a sequence of text characters strung together.
A string is just a run of text - a sequence of text characters strung together. To indicate that a
sequence of characters is a string, place a matching pair of either " or ' characters around them.
When making a story, you'll mostly work with strings that you intend to insert into
the passage source. If a string contains markup, then the markup will be processed when it's
inserted. For instance, `"The ''biiiiig'' bellyblob"` will print as "The <b>biiiiig</b> bellyblob".
Even macro calls inside strings will be processed: printing `"The (print:2*3) bears"` will print "The 6 bears".
If you wish to avoid this, simply include the verbatim markup inside the string:``"`It's (exactly: as planned)`"`` will
print "It's (exactly: as planned)".
If you wish to avoid this, you can include the verbatim markup inside the string:``"`It's (exactly: as planned)`"`` will
print "It's (exactly: as planned)". Alternatively, you can use (verbatim-print:) to prevent the markup from being processed.
You can add strings together to join them: `"The" + ' former ' + "Prime Minister's"`
pushes the strings together, and evaluates to "The former Prime Minister's". Notice
......
......@@ -1014,7 +1014,7 @@
datatype: either(
"alnum", "alphanumeric", "any(?:case)?", "array", "bool(?:ean)?", "changer", "colou?r", "const",
"command", "dm", "data" + either("map","type","set"), "ds", "digit",
"gradient", "empty", "even", "int" + notBefore('o') + "(?:eger)?", "lambda", "lowercase", "macro", "linebreak",
"gradient", "empty", "even", "int" + notBefore('o') + "(?:eger)?", "lambda", "lowercase", "macro", "linebreak", "newline",
"num(?:ber)?", "odd", "str(?:ing)?", "uppercase", "whitespace"
) + wb,
......
......@@ -307,11 +307,15 @@ define(['utils'], ({impossible}) => {
}
else if (type === "string") {
/*
Note that this is entirely reliant on the fact that TwineScript string
Note that this is reliant on the fact that TwineScript string
literals are currently exactly equal to JS string literals (minus template
strings and newlines).
*/
return token.text.replace(/\n/g, "\\n");
return token.text.replace(/(.?)\n/g, (_,a) =>
/*
If a literal newline has a \ before it (such as when invoking the escaped line ending syntax), DON'T escape the newline without escaping the \ first.
*/
(a === "\\" ? "\\\\" : a === "\n" ? "\\n" : a) + "\\n");
}
else if (type === "hook") {
/*
......
......@@ -50,23 +50,4 @@ describe("miscellaneous hook changer macros", function() {
expect(runPassage("(v6m:)[A\n\n\nB]").find('tw-consecutive-br').length).toBe(2);
});
});
describe("the (verbatim-print:) macro", function() {
it("takes any one value", function() {
expect("(verbatim-print:)").markupToError();
expect("(verbatim-print:'A')").not.markupToError();
expect("(verbatim-print:(css:''))").not.markupToError();
expect("(verbatim-print:red)").not.markupToError();
});
it("is aliased as (v6m-print:)", function() {
expect("(v6m-print:'$foo')").markupToPrint('$foo');
});
it("prints the given value verbatim", function() {
expect("(verbatim-print:'$foo')").markupToPrint('$foo');
expect("(set:$foo to '**bar**')(verbatim-print:$foo)").markupToPrint('**bar**');
});
it("preserves newlines, and creates <tw-consecutive-br>s appropriately", function() {
expect(runPassage("(v6m-print:'A \n B \n C')").find('br').length).toBe(2);
expect(runPassage("(v6m-print:'A\n\n\nB')").find('tw-consecutive-br').length).toBe(2);
});
});
});
......@@ -12,12 +12,15 @@ describe("basic command macros", function() {
it("prints the text equivalent of string expressions", function() {
expect("(print: 'gar' + 'ply')").markupToPrint("garply");
});
it("prints twinemarkup in strings", function() {
it("prints markup in strings", function() {
var expr = runPassage("(print: '//gar' + 'ply//')").find('tw-expression');
expect(expr.text()).toBe("garply");
expect(expr.children().is('i')).toBe(true);
});
it("prints strings with escaped line breaks correctly", function() {
expect("(set:$a to '\\" + '\n' + "A')(print:$a)").markupToPrint("A");
});
it("prints the text equivalent of boolean expressions", function() {
expect("(print: true)").markupToPrint("true");
});
......@@ -66,6 +69,30 @@ describe("basic command macros", function() {
});
});
});
describe("the (verbatim-print:) macro", function() {
it("takes any one value", function() {
expect("(verbatim-print:)").markupToError();
expect("(verbatim-print:'A')").not.markupToError();
expect("(verbatim-print:(css:''))").not.markupToError();
expect("(verbatim-print:red)").not.markupToError();
});
it("is aliased as (v6m-print:)", function() {
expect("(v6m-print:'$foo')").markupToPrint('$foo');
});
it("prints the given value verbatim", function() {
expect("(verbatim-print:'$foo')").markupToPrint('$foo');
expect("(set:$foo to '**bar**')(verbatim-print:$foo)").markupToPrint('**bar**');
});
it("preserves newlines, and creates <tw-consecutive-br>s appropriately", function() {
expect(runPassage("(v6m-print:'A \n B \n C')").find('br').length).toBe(2);
expect(runPassage("(v6m-print:'A\n\n\nB')").find('tw-consecutive-br').length).toBe(2);
});
it("prints strings with escaped line breaks correctly", function() {
expect("(set:$a to '\\" + '\n' + "A')(v6m-print:$a)").markupToPrint("\\\nA");
});
});
describe("the (display:) macro", function() {
it("requires exactly 1 string argument", function() {
expect("(display:)").markupToError();
......
......@@ -232,12 +232,15 @@ describe("patterns", function() {
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() {
expect("(set: $a to '\\r\\n' is a linebreak)(print:$a)").markupToPrint("true");
expect("(set: $a to '\\n' is a linebreak)(print:$a)").markupToPrint("true");
expect("(set: $a to '\\r' is a linebreak)(print:$a)").markupToPrint("true");
expect("(set: $a to last of 'Red\n' is a linebreak)(print:$a)").markupToPrint("true");
expect("(set: $a to '' is a linebreak)(print:$a)").markupToPrint("false");
it("'linebreak' or 'newline' matches line breaks", function() {
['linebreak','newline'].forEach(e=>{
expect("(set: $a to '\\r\\n' is a "+e+")(print:$a)").markupToPrint("true");
expect("(set: $a to '\\n' is a "+e+")(print:$a)").markupToPrint("true");
expect("(set: $a to '\\r' is a "+e+")(print:$a)").markupToPrint("true");
expect("(set: $a to last of 'Red\n' is a "+e+")(print:$a)").markupToPrint("true");
expect("(set: $a to '' is a "+e+")(print:$a)").markupToPrint("false");
});
expect("(print:linebreak is newline)").markupToPrint("true");
});
it("'empty' matches only empty structures", function() {
expect("(set: $a to (a:) is a empty)(print:$a)").markupToPrint("true");
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment