Skip to content

Commit 4e7c299

Browse files
committed
Merge commit 'refs/pullreqs/211'
2 parents eba0941 + 3428899 commit 4e7c299

File tree

1 file changed

+88
-22
lines changed

1 file changed

+88
-22
lines changed

lib/folk.js

Lines changed: 88 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
// const WHITESPACE = /^[ \t]+/;
22
// const NEWLINE = /^[;\n]+/;
3-
const WORDSEP = /^[ \t;\n]+/;
3+
const WORDSEP = /^[ \t\n\r\f\v]+/;
44
const QUOTED = /^".*?[^\\]"/;
5-
const WORD = /^([^ \t;\n{}[\]"\\]|\\[ \t;\n{}[\]"\\])+/;
6-
const ESCAPED = /\\(.)/g;
7-
const ESCAPABLE = /([ \t;\n{}[\]"])/g;
5+
const WORD = /^([^ \t\n\r\f\v{}\\]|\\.)+/;
86

97
const eat = (regex, str) => {
108
const match = str.match(regex);
@@ -34,26 +32,84 @@ const eatBrace = (str) => {
3432
};
3533

3634
const checkBrace = (str) => {
37-
let bc = 0;
38-
let bk = 0;
35+
let curlies = 0;
36+
let brackets = 0;
3937
const l = str.length;
4038
for (let i = 0; i < l; i++) {
4139
switch (str[i]) {
42-
case '{': bc++; break;
43-
case '}': bc--; break;
44-
case '[': bk++; break;
45-
case ']': bk--; break;
40+
case '{': curlies++; break;
41+
case '}': curlies--; break;
42+
case '[': brackets++; break;
43+
case ']': brackets--; break;
44+
}
45+
46+
if (curlies < 0) return false;
47+
}
48+
49+
return curlies === 0 && brackets >= 0;
50+
};
51+
52+
// ported from JimEscape in jim.c
53+
const UNESCAPE_TABLE = {
54+
'a': '\a',
55+
'b': '\b',
56+
'n': '\n',
57+
'r': '\r',
58+
't': '\t',
59+
'f': '\f',
60+
'v': '\v',
61+
'0': '\x00',
62+
};
63+
64+
const unescape = (str) => {
65+
let output = "";
66+
67+
for (let i = 0; i < str.length; i++) {
68+
// used to prevent out-of-bounds issues with `str[i + 1]`
69+
if (i === str.length - 1) {
70+
output += str[i];
71+
break;
4672
}
4773

48-
if (bc < 0) return false;
49-
if (bk < 0) return false;
74+
if (str[i] === '\\') {
75+
// is the next character escapable?
76+
const unescaped = UNESCAPE_TABLE[str[i + 1]];
77+
if (unescaped) {
78+
output += unescaped;
79+
} else {
80+
// else just append it, sans the backslash
81+
output += str[i + 1];
82+
}
83+
84+
i++;
85+
// TODO: octal, \x \u \U \u{NNN}
86+
} else {
87+
output += str[i];
88+
}
5089
}
5190

52-
return bc === 0 && bk === 0;
91+
return output;
92+
};
93+
94+
// ported from BackslashQuoteString in jim.c
95+
const ESCAPE_TABLE = {
96+
' ': '\\ ',
97+
'$': '\\$',
98+
'"': '\\"',
99+
'[': '\\[',
100+
']': '\\]',
101+
'{': '\\{',
102+
'}': '\\}',
103+
';': '\\;',
104+
'\\': '\\\\',
105+
'\n': '\\n',
106+
'\r': '\\r',
107+
'\t': '\\t',
108+
'\f': '\\f',
109+
'\v': '\\v',
53110
};
54111

55-
const unescape = (str) => str.replaceAll(ESCAPED, '$1');
56-
const escape = (str) => str.replaceAll(ESCAPABLE, '\\$1');
112+
const escape = (str) => str.split("").map(char => char in ESCAPE_TABLE ? ESCAPE_TABLE[char] : char).join("");
57113

58114
// deserializtion
59115
const nextToken = (str) => {
@@ -69,14 +125,19 @@ const nextToken = (str) => {
69125
case ' ':
70126
case '\t':
71127
case '\n':
72-
case ';':
128+
case '\r':
129+
case '\f':
130+
case '\v':
73131
return [token, str];
74132

75-
case '[':
76-
throw new Error("this aint no interpreter");
77133
case '{':
78134
[word, str] = eatBrace(str);
79-
token += word.slice(1, -1);
135+
136+
if (token === '') {
137+
token = word.slice(1, -1);
138+
} else {
139+
token += word;
140+
}
80141
break;
81142
case '"':
82143
[word, str] = eat(QUOTED, str);
@@ -127,8 +188,13 @@ const loadDict = (str) => {
127188
const dumpString = (word) => {
128189
if (!word.length) return '{}';
129190
const match = word.match(WORD);
130-
if (match && match[0] === word) return word;
131-
if (checkBrace(word)) return `{${word}}`;
191+
if (word[0] !== "#" && match && match[0] === word) return word;
192+
if (checkBrace(word)) {
193+
// the one case that we can't handle with braces is a linefeed escape
194+
if (!(/\\\n/.test(word))) {
195+
return `{${word}}`;
196+
}
197+
}
132198
return escape(word);
133199
}
134200

@@ -280,7 +346,7 @@ class FolkWS {
280346
const channel = this.createChannel((message) => {
281347
const [action, match, matchId] = loadList(message);
282348
const callback = callbacks[action];
283-
callback && callback(loadDict(match), matchId);
349+
if (callback) callback(loadDict(match), matchId);
284350
});
285351

286352
const retractKey = await this.send(tcl`

0 commit comments

Comments
 (0)