test('should allow replacing the dispatch character', t => { const prevTable = getCurrentReadtable(); const newTable = prevTable.extend({ key: '@', mode: 'terminating', action: prevTable.getMapping('#').action },{ key: '#', mode: 'terminating', action: prevTable.getMapping('@').action }); const result = read('#``'); const error = t.throws(()=>read('@``')); setCurrentReadtable(newTable); const result2 = read('@``'); const error2 = t.throws(()=>read('#``')); t.is(error.message, error2.message); t.deepEqual(result, result2); setCurrentReadtable(prevTable); });
export default function readIdentifier(stream: CharStream) { terminates = isTerminating(getCurrentReadtable()); let char = stream.peek(); let code = char.charCodeAt(0); let check = isIdentifierStart; // If the first char is invalid if (!check(code) && !startsEscape(code)) { throw this.createError('Invalid or unexpected token'); } let idx = 0; while (!terminates(char) && !isEOS(char)) { if (startsEscape(code)) { return new IdentifierToken({ value: getEscapedIdentifier.call(this, stream), }); } if (!check(code)) { return new IdentifierToken({ value: stream.readString(idx), }); } char = stream.peek(++idx); code = char.charCodeAt(0); check = isIdentifierPart; } return new IdentifierToken({ value: stream.readString(idx), }); }
test('should create a dispatch macro', t => { const prevTable = getCurrentReadtable(); const newTable = prevTable.extend({ key: ':', mode: 'dispatch', action: function readColon(stream, prefix, allowExpr) { stream.readString(); setCurrentReadtable(defaultTable); return new IdentifierToken({ value: 'Keyword' }); } }); function readDefault(stream, prefix, allowExpr) { const { token: parens } = read('()').first(); let result = List.of(parens.first()); setCurrentReadtable(prevTable); const stx = this.readToken(stream, List(), false); result = result.push(stx.fromString(stx.token.value).value); setCurrentReadtable(newTable); return result.push(parens.last()); } const kwLetters = Array.from(new Set(Object.keys(keywordTable).map(w => w[0]))); const keywordEntries = kwLetters.map(key => ({ key, mode: 'non-terminating', action: readDefault })); const defaultTable = newTable.extend(...keywordEntries,{ mode: 'non-terminating', action: readDefault }); setCurrentReadtable(newTable); const result = read('#:for if #:else'); setCurrentReadtable(prevTable); t.is(result.toString(), 'List [ Keyword, ( \'for ), if, Keyword, ( \'else ) ]'); });
test('should create a dispatch macro', t => { const prevTable = getCurrentReadtable(); const newTable = prevTable.extend({ key: ':', mode: 'dispatch', action: function readColon(stream, prefix, allowExpr) { stream.readString(); setCurrentReadtable(defaultTable); return new IdentifierToken({ value: 'Keyword', }); }, }); function readDefault(stream, prefix, allowExpr) { const [[openParen, closeParen]] = read('()'); setCurrentReadtable(prevTable); const stx = this.readToken(stream, List(), false); let result = List.of(openParen).push(stx); setCurrentReadtable(newTable); return result.push(closeParen); } const kwLetters = Array.from( new Set(Object.keys(keywordTable).map(w => w[0])), ); const keywordEntries = kwLetters.map(key => ({ key, mode: 'non-terminating', action: readDefault, })); const defaultTable = newTable.extend(...keywordEntries, { mode: 'non-terminating', action: readDefault, }); setCurrentReadtable(newTable); // eslint-disable-next-line no-unused-vars const [one, [open, kw, close], iff, els, [open2, elkw]] = read( '#:for if #:else', ); t.true(isIdentifier(one, 'Keyword')); t.true(isKeyword(kw, 'for')); t.true(isKeyword(iff, 'if')); t.true(isIdentifier(els, 'Keyword')); t.true(isKeyword(elkw, 'else')); setCurrentReadtable(prevTable); });
test('terminating macros should delimit identifiers and numbers', t => { const prevTable = getCurrentReadtable(); const newTable = prevTable.extend( { key: 'z', mode: 'terminating', action: function readZ(stream) { stream.readString(); return EmptyToken; }, }, { key: '0', mode: 'terminating', action: function readZero(stream) { stream.readString(); return EmptyToken; }, }, ); // reading with 'z' and '0' as 'non-terminating' let [result] = read('abczefgzhij\u{102A7}ba '); t.is(prevTable.getMapping('z').mode, 'non-terminating'); t.is(result.value, 'abczefgzhijš§ba'); [result] = read('12304560789'); t.is(prevTable.getMapping('0').mode, 'non-terminating'); t.is(result.value, 12304560789); setCurrentReadtable(newTable); // reading with 'z' and '0' as 'terminating' let [x, y, z] = read('abczefgzhij\u{102A7}ba ').map(s => s.value); t.is(x, 'abc'); t.is(y, 'efg'); t.is(z, 'hijš§ba'); [x, y, z] = read('12304560789').map(s => s.value); t.is(x, 123); t.is(y, 456); t.is(z, 789); setCurrentReadtable(prevTable); });