Add line numbers to errors [#15]
This commit is contained in:
parent
2cb34c273c
commit
e6064b72de
|
@ -153,12 +153,15 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
}},
|
}},
|
||||||
'define': {type: 'keyword', suggest: true, then: {
|
'define': {type: 'keyword', suggest: true, then: {
|
||||||
'': aliasListToEnd,
|
'': aliasListToEnd,
|
||||||
|
'as': CM_ERROR,
|
||||||
}},
|
}},
|
||||||
'begin': {type: 'keyword', suggest: true, then: {
|
'begin': {type: 'keyword', suggest: true, then: {
|
||||||
'': aliasListToEnd,
|
'': aliasListToEnd,
|
||||||
|
'as': CM_ERROR,
|
||||||
}},
|
}},
|
||||||
'end': {type: 'keyword', suggest: true, then: {
|
'end': {type: 'keyword', suggest: true, then: {
|
||||||
'': aliasListToEnd,
|
'': aliasListToEnd,
|
||||||
|
'as': CM_ERROR,
|
||||||
'\n': end,
|
'\n': end,
|
||||||
}},
|
}},
|
||||||
'if': {type: 'keyword', suggest: true, then: {
|
'if': {type: 'keyword', suggest: true, then: {
|
||||||
|
|
|
@ -323,7 +323,7 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
beginNested(mode, label, name) {
|
beginNested(mode, label, name, ln) {
|
||||||
const leftAgent = makeAgent(name + '[', {anchorRight: true});
|
const leftAgent = makeAgent(name + '[', {anchorRight: true});
|
||||||
const rightAgent = makeAgent(name + ']');
|
const rightAgent = makeAgent(name + ']');
|
||||||
const agents = [leftAgent, rightAgent];
|
const agents = [leftAgent, rightAgent];
|
||||||
|
@ -332,6 +332,7 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
mode,
|
mode,
|
||||||
label,
|
label,
|
||||||
stages,
|
stages,
|
||||||
|
ln,
|
||||||
};
|
};
|
||||||
this.currentNest = {
|
this.currentNest = {
|
||||||
agents,
|
agents,
|
||||||
|
@ -457,13 +458,13 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleBlockBegin({mode, label}) {
|
handleBlockBegin({ln, mode, label}) {
|
||||||
const name = '__BLOCK' + this.blockCount;
|
const name = '__BLOCK' + this.blockCount;
|
||||||
this.beginNested(mode, label, name);
|
this.beginNested(mode, label, name, ln);
|
||||||
++ this.blockCount;
|
++ this.blockCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleBlockSplit({mode, label}) {
|
handleBlockSplit({ln, mode, label}) {
|
||||||
const containerMode = this.currentNest.stage.sections[0].mode;
|
const containerMode = this.currentNest.stage.sections[0].mode;
|
||||||
if(containerMode !== 'if') {
|
if(containerMode !== 'if') {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
@ -476,6 +477,7 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
mode,
|
mode,
|
||||||
label,
|
label,
|
||||||
stages: [],
|
stages: [],
|
||||||
|
ln,
|
||||||
};
|
};
|
||||||
this.currentNest.stage.sections.push(this.currentSection);
|
this.currentNest.stage.sections.push(this.currentSection);
|
||||||
}
|
}
|
||||||
|
@ -501,7 +503,13 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleStage(stage) {
|
handleStage(stage) {
|
||||||
this.stageHandlers[stage.type](stage);
|
try {
|
||||||
|
this.stageHandlers[stage.type](stage);
|
||||||
|
} catch(e) {
|
||||||
|
if(typeof e === 'object' && e.message) {
|
||||||
|
throw new Error(e.message + ' at line ' + (stage.ln + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
generate({stages, meta = {}}) {
|
generate({stages, meta = {}}) {
|
||||||
|
@ -511,14 +519,14 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
this.agents.length = 0;
|
this.agents.length = 0;
|
||||||
this.blockCount = 0;
|
this.blockCount = 0;
|
||||||
this.nesting.length = 0;
|
this.nesting.length = 0;
|
||||||
const globals = this.beginNested('global', '', '');
|
const globals = this.beginNested('global', '', '', 0);
|
||||||
|
|
||||||
stages.forEach(this.handleStage);
|
stages.forEach(this.handleStage);
|
||||||
|
|
||||||
if(this.nesting.length !== 1) {
|
if(this.nesting.length !== 1) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Invalid block nesting (' +
|
'Unterminated section at line ' +
|
||||||
(this.nesting.length - 1) + ' unclosed)'
|
(this.currentSection.ln + 1)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,12 +14,12 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const PARSED = {
|
const PARSED = {
|
||||||
blockBegin: (mode, label) => {
|
blockBegin: (mode, label, {ln = 0} = {}) => {
|
||||||
return {type: 'block begin', mode, label};
|
return {type: 'block begin', mode, label, ln};
|
||||||
},
|
},
|
||||||
|
|
||||||
blockSplit: (mode, label) => {
|
blockSplit: (mode, label, {ln = 0} = {}) => {
|
||||||
return {type: 'block split', mode, label};
|
return {type: 'block split', mode, label, ln};
|
||||||
},
|
},
|
||||||
|
|
||||||
blockEnd: () => {
|
blockEnd: () => {
|
||||||
|
@ -662,20 +662,20 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
||||||
|
|
||||||
it('records all sections within blocks', () => {
|
it('records all sections within blocks', () => {
|
||||||
const sequence = generator.generate({stages: [
|
const sequence = generator.generate({stages: [
|
||||||
PARSED.blockBegin('if', 'abc'),
|
PARSED.blockBegin('if', 'abc', {ln: 10}),
|
||||||
PARSED.connect(['A', 'B']),
|
PARSED.connect(['A', 'B']),
|
||||||
PARSED.blockSplit('else', 'xyz'),
|
PARSED.blockSplit('else', 'xyz', {ln: 20}),
|
||||||
PARSED.connect(['A', 'C']),
|
PARSED.connect(['A', 'C']),
|
||||||
PARSED.blockEnd(),
|
PARSED.blockEnd(),
|
||||||
]});
|
]});
|
||||||
|
|
||||||
const block0 = sequence.stages[0];
|
const block0 = sequence.stages[0];
|
||||||
expect(block0.sections).toEqual([
|
expect(block0.sections).toEqual([
|
||||||
{mode: 'if', label: 'abc', stages: [
|
{mode: 'if', label: 'abc', ln: 10, stages: [
|
||||||
GENERATED.beginAgents(['A', 'B']),
|
GENERATED.beginAgents(['A', 'B']),
|
||||||
GENERATED.connect(['A', 'B']),
|
GENERATED.connect(['A', 'B']),
|
||||||
]},
|
]},
|
||||||
{mode: 'else', label: 'xyz', stages: [
|
{mode: 'else', label: 'xyz', ln: 20, stages: [
|
||||||
GENERATED.beginAgents(['C']),
|
GENERATED.beginAgents(['C']),
|
||||||
GENERATED.connect(['A', 'C']),
|
GENERATED.connect(['A', 'C']),
|
||||||
]},
|
]},
|
||||||
|
@ -756,11 +756,11 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
||||||
|
|
||||||
const block0 = sequence.stages[0];
|
const block0 = sequence.stages[0];
|
||||||
expect(block0.sections).toEqual([
|
expect(block0.sections).toEqual([
|
||||||
{mode: 'if', label: 'abc', stages: [
|
{mode: 'if', label: 'abc', ln: 0, stages: [
|
||||||
jasmine.anything(),
|
jasmine.anything(),
|
||||||
jasmine.anything(),
|
jasmine.anything(),
|
||||||
]},
|
]},
|
||||||
{mode: 'else', label: 'xyz', stages: []},
|
{mode: 'else', label: 'xyz', ln: 0, stages: []},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -774,8 +774,8 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
||||||
|
|
||||||
const block0 = sequence.stages[0];
|
const block0 = sequence.stages[0];
|
||||||
expect(block0.sections).toEqual([
|
expect(block0.sections).toEqual([
|
||||||
{mode: 'if', label: 'abc', stages: []},
|
{mode: 'if', label: 'abc', ln: 0, stages: []},
|
||||||
{mode: 'else', label: 'xyz', stages: [
|
{mode: 'else', label: 'xyz', ln: 0, stages: [
|
||||||
jasmine.anything(),
|
jasmine.anything(),
|
||||||
jasmine.anything(),
|
jasmine.anything(),
|
||||||
]},
|
]},
|
||||||
|
@ -832,11 +832,11 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
||||||
|
|
||||||
const block0 = sequence.stages[0];
|
const block0 = sequence.stages[0];
|
||||||
expect(block0.sections).toEqual([
|
expect(block0.sections).toEqual([
|
||||||
{mode: 'if', label: 'abc', stages: [
|
{mode: 'if', label: 'abc', ln: 0, stages: [
|
||||||
jasmine.anything(),
|
jasmine.anything(),
|
||||||
jasmine.anything(),
|
jasmine.anything(),
|
||||||
]},
|
]},
|
||||||
{mode: 'else', label: 'xyz', stages: []},
|
{mode: 'else', label: 'xyz', ln: 0, stages: []},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -71,6 +71,28 @@ define([
|
||||||
'end': {type: 'agent end', mode: 'cross'},
|
'end': {type: 'agent end', mode: 'cross'},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function makeError(message, token = null) {
|
||||||
|
let suffix = '';
|
||||||
|
if(token) {
|
||||||
|
suffix = (
|
||||||
|
' at line ' + (token.b.ln + 1) +
|
||||||
|
', character ' + token.b.ch
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return new Error(message + suffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
function errToken(line, pos) {
|
||||||
|
if(pos < line.length) {
|
||||||
|
return line[pos];
|
||||||
|
}
|
||||||
|
const last = array.last(line);
|
||||||
|
if(!last) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {b: last.e};
|
||||||
|
}
|
||||||
|
|
||||||
function joinLabel(line, begin = 0, end = null) {
|
function joinLabel(line, begin = 0, end = null) {
|
||||||
if(end === null) {
|
if(end === null) {
|
||||||
end = line.length;
|
end = line.length;
|
||||||
|
@ -93,14 +115,18 @@ define([
|
||||||
}
|
}
|
||||||
|
|
||||||
function skipOver(line, start, skip, error = null) {
|
function skipOver(line, start, skip, error = null) {
|
||||||
const pass = skip.every((expected, i) => (
|
for(let i = 0; i < skip.length; ++ i) {
|
||||||
tokenKeyword(line[start + i]) === expected
|
const expected = skip[i];
|
||||||
));
|
const token = line[start + i];
|
||||||
if(!pass) {
|
if(tokenKeyword(token) !== expected) {
|
||||||
if(error) {
|
if(error) {
|
||||||
throw new Error(error + ': ' + joinLabel(line));
|
throw makeError(
|
||||||
} else {
|
error + '; expected "' + expected + '"',
|
||||||
return start;
|
token
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return start;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return start + skip.length;
|
return start + skip.length;
|
||||||
|
@ -124,7 +150,7 @@ define([
|
||||||
aliasSep = end;
|
aliasSep = end;
|
||||||
}
|
}
|
||||||
if(start >= aliasSep) {
|
if(start >= aliasSep) {
|
||||||
throw new Error('Missing agent name');
|
throw makeError('Missing agent name', errToken(line, start));
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
name: joinLabel(line, start, aliasSep),
|
name: joinLabel(line, start, aliasSep),
|
||||||
|
@ -139,11 +165,12 @@ define([
|
||||||
const flags = [];
|
const flags = [];
|
||||||
let p = start;
|
let p = start;
|
||||||
for(; p < end; ++ p) {
|
for(; p < end; ++ p) {
|
||||||
const rawFlag = tokenKeyword(line[p]);
|
const token = line[p];
|
||||||
|
const rawFlag = tokenKeyword(token);
|
||||||
const flag = flagTypes[rawFlag];
|
const flag = flagTypes[rawFlag];
|
||||||
if(flag) {
|
if(flag) {
|
||||||
if(flags.includes(flag)) {
|
if(flags.includes(flag)) {
|
||||||
throw new Error('Duplicate agent flag: ' + rawFlag);
|
throw makeError('Duplicate agent flag: ' + rawFlag, token);
|
||||||
}
|
}
|
||||||
flags.push(flag);
|
flags.push(flag);
|
||||||
} else {
|
} else {
|
||||||
|
@ -204,7 +231,7 @@ define([
|
||||||
|
|
||||||
const type = tokenKeyword(line[1]);
|
const type = tokenKeyword(line[1]);
|
||||||
if(TERMINATOR_TYPES.indexOf(type) === -1) {
|
if(TERMINATOR_TYPES.indexOf(type) === -1) {
|
||||||
throw new Error('Unknown termination: ' + joinLabel(line));
|
throw makeError('Unknown termination "' + type + '"', line[1]);
|
||||||
}
|
}
|
||||||
meta.terminators = type;
|
meta.terminators = type;
|
||||||
return true;
|
return true;
|
||||||
|
@ -274,14 +301,11 @@ define([
|
||||||
let skip = 2;
|
let skip = 2;
|
||||||
skip = skipOver(line, skip, type.skip);
|
skip = skipOver(line, skip, type.skip);
|
||||||
const agents = readAgentList(line, skip, labelSep);
|
const agents = readAgentList(line, skip, labelSep);
|
||||||
if(
|
if(agents.length < type.min) {
|
||||||
agents.length < type.min ||
|
throw makeError('Too few agents for ' + mode.mode, line[0]);
|
||||||
(type.max !== null && agents.length > type.max)
|
}
|
||||||
) {
|
if(type.max !== null && agents.length > type.max) {
|
||||||
throw new Error(
|
throw makeError('Too many agents for ' + mode.mode, line[0]);
|
||||||
'Invalid ' + mode.mode +
|
|
||||||
': ' + joinLabel(line)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
type: type.type,
|
type: type.type,
|
||||||
|
@ -343,9 +367,13 @@ define([
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(!stage) {
|
if(!stage) {
|
||||||
throw new Error('Unrecognised command: ' + joinLabel(line));
|
throw makeError(
|
||||||
|
'Unrecognised command: ' + joinLabel(line),
|
||||||
|
line[0]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if(typeof stage === 'object') {
|
if(typeof stage === 'object') {
|
||||||
|
stage.ln = line[0].b.ln;
|
||||||
stages.push(stage);
|
stages.push(stage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,43 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
|
||||||
const parser = new Parser();
|
const parser = new Parser();
|
||||||
|
|
||||||
const PARSED = {
|
const PARSED = {
|
||||||
|
blockBegin: ({
|
||||||
|
ln = jasmine.anything(),
|
||||||
|
mode = jasmine.anything(),
|
||||||
|
label = jasmine.anything(),
|
||||||
|
} = {}) => {
|
||||||
|
return {
|
||||||
|
type: 'block begin',
|
||||||
|
ln,
|
||||||
|
mode,
|
||||||
|
label,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
blockSplit: ({
|
||||||
|
ln = jasmine.anything(),
|
||||||
|
mode = jasmine.anything(),
|
||||||
|
label = jasmine.anything(),
|
||||||
|
} = {}) => {
|
||||||
|
return {
|
||||||
|
type: 'block split',
|
||||||
|
ln,
|
||||||
|
mode,
|
||||||
|
label,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
blockEnd: ({
|
||||||
|
ln = jasmine.anything(),
|
||||||
|
} = {}) => {
|
||||||
|
return {
|
||||||
|
type: 'block end',
|
||||||
|
ln,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
connect: (agentNames, {
|
connect: (agentNames, {
|
||||||
|
ln = jasmine.anything(),
|
||||||
line = jasmine.anything(),
|
line = jasmine.anything(),
|
||||||
left = jasmine.anything(),
|
left = jasmine.anything(),
|
||||||
right = jasmine.anything(),
|
right = jasmine.anything(),
|
||||||
|
@ -12,6 +48,7 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
|
||||||
} = {}) => {
|
} = {}) => {
|
||||||
return {
|
return {
|
||||||
type: 'connect',
|
type: 'connect',
|
||||||
|
ln,
|
||||||
agents: agentNames.map((name) => ({
|
agents: agentNames.map((name) => ({
|
||||||
name,
|
name,
|
||||||
alias: '',
|
alias: '',
|
||||||
|
@ -77,7 +114,7 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
|
||||||
it('propagates aliases', () => {
|
it('propagates aliases', () => {
|
||||||
const parsed = parser.parse('define Foo Bar as A B');
|
const parsed = parser.parse('define Foo Bar as A B');
|
||||||
expect(parsed.stages).toEqual([
|
expect(parsed.stages).toEqual([
|
||||||
{type: 'agent define', agents: [
|
{type: 'agent define', ln: jasmine.anything(), agents: [
|
||||||
{name: 'Foo Bar', alias: 'A B', flags: []},
|
{name: 'Foo Bar', alias: 'A B', flags: []},
|
||||||
]},
|
]},
|
||||||
]);
|
]);
|
||||||
|
@ -102,6 +139,7 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
|
||||||
expect(parsed.stages).toEqual([
|
expect(parsed.stages).toEqual([
|
||||||
{
|
{
|
||||||
type: 'connect',
|
type: 'connect',
|
||||||
|
ln: jasmine.anything(),
|
||||||
agents: [
|
agents: [
|
||||||
{name: 'A', alias: '', flags: ['start']},
|
{name: 'A', alias: '', flags: ['start']},
|
||||||
{name: 'B', alias: '', flags: [
|
{name: 'B', alias: '', flags: [
|
||||||
|
@ -117,12 +155,18 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('rejects duplicate flags', () => {
|
it('rejects duplicate flags', () => {
|
||||||
expect(() => parser.parse('A -> +*+B')).toThrow();
|
expect(() => parser.parse('A -> +*+B')).toThrow(new Error(
|
||||||
expect(() => parser.parse('A -> **B')).toThrow();
|
'Duplicate agent flag: + at line 1, character 7'
|
||||||
|
));
|
||||||
|
expect(() => parser.parse('A -> **B')).toThrow(new Error(
|
||||||
|
'Duplicate agent flag: * at line 1, character 6'
|
||||||
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('rejects missing agent names', () => {
|
it('rejects missing agent names', () => {
|
||||||
expect(() => parser.parse('A -> +')).toThrow();
|
expect(() => parser.parse('A -> +')).toThrow(new Error(
|
||||||
|
'Missing agent name at line 1, character 6'
|
||||||
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('converts multiple entries', () => {
|
it('converts multiple entries', () => {
|
||||||
|
@ -141,6 +185,14 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('stores line numbers', () => {
|
||||||
|
const parsed = parser.parse('A -> B\nB -> A');
|
||||||
|
expect(parsed.stages).toEqual([
|
||||||
|
PARSED.connect(['A', 'B'], {ln: 0}),
|
||||||
|
PARSED.connect(['B', 'A'], {ln: 1}),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
it('recognises all types of connection', () => {
|
it('recognises all types of connection', () => {
|
||||||
const parsed = parser.parse(
|
const parsed = parser.parse(
|
||||||
'A -> B\n' +
|
'A -> B\n' +
|
||||||
|
@ -215,6 +267,7 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
|
||||||
const parsed = parser.parse('note over A: hello there');
|
const parsed = parser.parse('note over A: hello there');
|
||||||
expect(parsed.stages).toEqual([{
|
expect(parsed.stages).toEqual([{
|
||||||
type: 'note over',
|
type: 'note over',
|
||||||
|
ln: jasmine.anything(),
|
||||||
agents: [{name: 'A', alias: '', flags: []}],
|
agents: [{name: 'A', alias: '', flags: []}],
|
||||||
mode: 'note',
|
mode: 'note',
|
||||||
label: 'hello there',
|
label: 'hello there',
|
||||||
|
@ -232,30 +285,35 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
|
||||||
expect(parsed.stages).toEqual([
|
expect(parsed.stages).toEqual([
|
||||||
{
|
{
|
||||||
type: 'note left',
|
type: 'note left',
|
||||||
|
ln: jasmine.anything(),
|
||||||
agents: [{name: 'A', alias: '', flags: []}],
|
agents: [{name: 'A', alias: '', flags: []}],
|
||||||
mode: 'note',
|
mode: 'note',
|
||||||
label: 'hello there',
|
label: 'hello there',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'note left',
|
type: 'note left',
|
||||||
|
ln: jasmine.anything(),
|
||||||
agents: [{name: 'A', alias: '', flags: []}],
|
agents: [{name: 'A', alias: '', flags: []}],
|
||||||
mode: 'note',
|
mode: 'note',
|
||||||
label: 'hello there',
|
label: 'hello there',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'note right',
|
type: 'note right',
|
||||||
|
ln: jasmine.anything(),
|
||||||
agents: [{name: 'A', alias: '', flags: []}],
|
agents: [{name: 'A', alias: '', flags: []}],
|
||||||
mode: 'note',
|
mode: 'note',
|
||||||
label: 'hello there',
|
label: 'hello there',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'note right',
|
type: 'note right',
|
||||||
|
ln: jasmine.anything(),
|
||||||
agents: [{name: 'A', alias: '', flags: []}],
|
agents: [{name: 'A', alias: '', flags: []}],
|
||||||
mode: 'note',
|
mode: 'note',
|
||||||
label: 'hello there',
|
label: 'hello there',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'note between',
|
type: 'note between',
|
||||||
|
ln: jasmine.anything(),
|
||||||
agents: [
|
agents: [
|
||||||
{name: 'A', alias: '', flags: []},
|
{name: 'A', alias: '', flags: []},
|
||||||
{name: 'B', alias: '', flags: []},
|
{name: 'B', alias: '', flags: []},
|
||||||
|
@ -270,6 +328,7 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
|
||||||
const parsed = parser.parse('note over A B, C D: hi');
|
const parsed = parser.parse('note over A B, C D: hi');
|
||||||
expect(parsed.stages).toEqual([{
|
expect(parsed.stages).toEqual([{
|
||||||
type: 'note over',
|
type: 'note over',
|
||||||
|
ln: jasmine.anything(),
|
||||||
agents: [
|
agents: [
|
||||||
{name: 'A B', alias: '', flags: []},
|
{name: 'A B', alias: '', flags: []},
|
||||||
{name: 'C D', alias: '', flags: []},
|
{name: 'C D', alias: '', flags: []},
|
||||||
|
@ -280,13 +339,16 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('rejects note between for a single agent', () => {
|
it('rejects note between for a single agent', () => {
|
||||||
expect(() => parser.parse('note between A: hi')).toThrow();
|
expect(() => parser.parse('note between A: hi')).toThrow(new Error(
|
||||||
|
'Too few agents for note at line 1, character 0'
|
||||||
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('converts state', () => {
|
it('converts state', () => {
|
||||||
const parsed = parser.parse('state over A: doing stuff');
|
const parsed = parser.parse('state over A: doing stuff');
|
||||||
expect(parsed.stages).toEqual([{
|
expect(parsed.stages).toEqual([{
|
||||||
type: 'note over',
|
type: 'note over',
|
||||||
|
ln: jasmine.anything(),
|
||||||
agents: [{name: 'A', alias: '', flags: []}],
|
agents: [{name: 'A', alias: '', flags: []}],
|
||||||
mode: 'state',
|
mode: 'state',
|
||||||
label: 'doing stuff',
|
label: 'doing stuff',
|
||||||
|
@ -294,13 +356,16 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('rejects multiple agents for state', () => {
|
it('rejects multiple agents for state', () => {
|
||||||
expect(() => parser.parse('state over A, B: hi')).toThrow();
|
expect(() => parser.parse('state over A, B: hi')).toThrow(new Error(
|
||||||
|
'Too many agents for state at line 1, character 0'
|
||||||
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('converts text blocks', () => {
|
it('converts text blocks', () => {
|
||||||
const parsed = parser.parse('text right of A: doing stuff');
|
const parsed = parser.parse('text right of A: doing stuff');
|
||||||
expect(parsed.stages).toEqual([{
|
expect(parsed.stages).toEqual([{
|
||||||
type: 'note right',
|
type: 'note right',
|
||||||
|
ln: jasmine.anything(),
|
||||||
agents: [{name: 'A', alias: '', flags: []}],
|
agents: [{name: 'A', alias: '', flags: []}],
|
||||||
mode: 'text',
|
mode: 'text',
|
||||||
label: 'doing stuff',
|
label: 'doing stuff',
|
||||||
|
@ -316,6 +381,7 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
|
||||||
expect(parsed.stages).toEqual([
|
expect(parsed.stages).toEqual([
|
||||||
{
|
{
|
||||||
type: 'agent define',
|
type: 'agent define',
|
||||||
|
ln: jasmine.anything(),
|
||||||
agents: [
|
agents: [
|
||||||
{name: 'A', alias: '', flags: []},
|
{name: 'A', alias: '', flags: []},
|
||||||
{name: 'B', alias: '', flags: []},
|
{name: 'B', alias: '', flags: []},
|
||||||
|
@ -323,6 +389,7 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'agent begin',
|
type: 'agent begin',
|
||||||
|
ln: jasmine.anything(),
|
||||||
agents: [
|
agents: [
|
||||||
{name: 'A', alias: '', flags: []},
|
{name: 'A', alias: '', flags: []},
|
||||||
{name: 'B', alias: '', flags: []},
|
{name: 'B', alias: '', flags: []},
|
||||||
|
@ -331,6 +398,7 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'agent end',
|
type: 'agent end',
|
||||||
|
ln: jasmine.anything(),
|
||||||
agents: [
|
agents: [
|
||||||
{name: 'A', alias: '', flags: []},
|
{name: 'A', alias: '', flags: []},
|
||||||
{name: 'B', alias: '', flags: []},
|
{name: 'B', alias: '', flags: []},
|
||||||
|
@ -344,6 +412,7 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
|
||||||
const parsed = parser.parse('abc:');
|
const parsed = parser.parse('abc:');
|
||||||
expect(parsed.stages).toEqual([{
|
expect(parsed.stages).toEqual([{
|
||||||
type: 'mark',
|
type: 'mark',
|
||||||
|
ln: jasmine.anything(),
|
||||||
name: 'abc',
|
name: 'abc',
|
||||||
}]);
|
}]);
|
||||||
});
|
});
|
||||||
|
@ -352,6 +421,7 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
|
||||||
const parsed = parser.parse('simultaneously:');
|
const parsed = parser.parse('simultaneously:');
|
||||||
expect(parsed.stages).toEqual([{
|
expect(parsed.stages).toEqual([{
|
||||||
type: 'async',
|
type: 'async',
|
||||||
|
ln: jasmine.anything(),
|
||||||
target: '',
|
target: '',
|
||||||
}]);
|
}]);
|
||||||
});
|
});
|
||||||
|
@ -360,6 +430,7 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
|
||||||
const parsed = parser.parse('simultaneously with abc:');
|
const parsed = parser.parse('simultaneously with abc:');
|
||||||
expect(parsed.stages).toEqual([{
|
expect(parsed.stages).toEqual([{
|
||||||
type: 'async',
|
type: 'async',
|
||||||
|
ln: jasmine.anything(),
|
||||||
target: 'abc',
|
target: 'abc',
|
||||||
}]);
|
}]);
|
||||||
});
|
});
|
||||||
|
@ -376,21 +447,21 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
|
||||||
'end\n'
|
'end\n'
|
||||||
);
|
);
|
||||||
expect(parsed.stages).toEqual([
|
expect(parsed.stages).toEqual([
|
||||||
{type: 'block begin', mode: 'if', label: 'something happens'},
|
PARSED.blockBegin({mode: 'if', label: 'something happens'}),
|
||||||
PARSED.connect(['A', 'B']),
|
PARSED.connect(['A', 'B']),
|
||||||
{type: 'block split', mode: 'else', label: 'something else'},
|
PARSED.blockSplit({mode: 'else', label: 'something else'}),
|
||||||
PARSED.connect(['A', 'C']),
|
PARSED.connect(['A', 'C']),
|
||||||
PARSED.connect(['C', 'B']),
|
PARSED.connect(['C', 'B']),
|
||||||
{type: 'block split', mode: 'else', label: ''},
|
PARSED.blockSplit({mode: 'else', label: ''}),
|
||||||
PARSED.connect(['A', 'D']),
|
PARSED.connect(['A', 'D']),
|
||||||
{type: 'block end'},
|
PARSED.blockEnd(),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('converts loop blocks', () => {
|
it('converts loop blocks', () => {
|
||||||
const parsed = parser.parse('repeat until something');
|
const parsed = parser.parse('repeat until something');
|
||||||
expect(parsed.stages).toEqual([
|
expect(parsed.stages).toEqual([
|
||||||
{type: 'block begin', mode: 'repeat', label: 'until something'},
|
PARSED.blockBegin({mode: 'repeat', label: 'until something'}),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -399,7 +470,9 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('rejects invalid inputs', () => {
|
it('rejects invalid inputs', () => {
|
||||||
expect(() => parser.parse('huh')).toThrow();
|
expect(() => parser.parse('huh')).toThrow(new Error(
|
||||||
|
'Unrecognised command: huh at line 1, character 0'
|
||||||
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('rejects partial links', () => {
|
it('rejects partial links', () => {
|
||||||
|
@ -409,7 +482,9 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('rejects invalid terminators', () => {
|
it('rejects invalid terminators', () => {
|
||||||
expect(() => parser.parse('terminators foo')).toThrow();
|
expect(() => parser.parse('terminators foo')).toThrow(new Error(
|
||||||
|
'Unknown termination "foo" at line 1, character 12'
|
||||||
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('rejects malformed notes', () => {
|
it('rejects malformed notes', () => {
|
||||||
|
@ -417,7 +492,9 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('rejects malformed block commands', () => {
|
it('rejects malformed block commands', () => {
|
||||||
expect(() => parser.parse('else nope foo')).toThrow();
|
expect(() => parser.parse('else nope foo')).toThrow(new Error(
|
||||||
|
'Invalid block command; expected "if" at line 1, character 5'
|
||||||
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('rejects invalid notes', () => {
|
it('rejects invalid notes', () => {
|
||||||
|
|
|
@ -104,30 +104,93 @@ define(['./CodeMirrorMode'], (CMMode) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function copyPos(pos) {
|
||||||
|
return {i: pos.i, ln: pos.ln, ch: pos.ch};
|
||||||
|
}
|
||||||
|
|
||||||
|
function advancePos(pos, src, steps) {
|
||||||
|
for(let i = 0; i < steps; ++ i) {
|
||||||
|
++ pos.ch;
|
||||||
|
if(src[pos.i + i] === '\n') {
|
||||||
|
++ pos.ln;
|
||||||
|
pos.ch = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pos.i += steps;
|
||||||
|
}
|
||||||
|
|
||||||
|
class TokenState {
|
||||||
|
constructor(src) {
|
||||||
|
this.src = src;
|
||||||
|
this.block = null;
|
||||||
|
this.token = null;
|
||||||
|
this.pos = {i: 0, ln: 0, ch: 0};
|
||||||
|
this.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
isOver() {
|
||||||
|
return this.pos.i > this.src.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.token = {s: '', v: '', q: false, b: null, e: null};
|
||||||
|
this.block = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
beginToken(advance) {
|
||||||
|
this.block = advance.newBlock;
|
||||||
|
Object.assign(this.token, this.block.baseToken);
|
||||||
|
this.token.b = copyPos(this.pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
endToken() {
|
||||||
|
let token = null;
|
||||||
|
if(!this.block.omit) {
|
||||||
|
this.token.e = copyPos(this.pos);
|
||||||
|
token = this.token;
|
||||||
|
}
|
||||||
|
this.reset();
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
advance() {
|
||||||
|
const advance = tokAdvance(this.src, this.pos.i, this.block);
|
||||||
|
|
||||||
|
if(advance.newBlock) {
|
||||||
|
this.beginToken(advance);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.token.s += advance.appendSpace;
|
||||||
|
this.token.v += advance.appendValue;
|
||||||
|
advancePos(this.pos, this.src, advance.skip);
|
||||||
|
|
||||||
|
if(advance.end) {
|
||||||
|
return this.endToken();
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function posStr(pos) {
|
||||||
|
return 'line ' + (pos.ln + 1) + ', character ' + pos.ch;
|
||||||
|
}
|
||||||
|
|
||||||
return class Tokeniser {
|
return class Tokeniser {
|
||||||
tokenise(src) {
|
tokenise(src) {
|
||||||
const tokens = [];
|
const tokens = [];
|
||||||
let block = null;
|
const state = new TokenState(src);
|
||||||
let current = {s: '', v: '', q: false};
|
while(!state.isOver()) {
|
||||||
for(let i = 0; i <= src.length;) {
|
const token = state.advance();
|
||||||
const advance = tokAdvance(src, i, block);
|
if(token) {
|
||||||
if(advance.newBlock) {
|
tokens.push(token);
|
||||||
block = advance.newBlock;
|
|
||||||
Object.assign(current, block.baseToken);
|
|
||||||
}
|
|
||||||
current.s += advance.appendSpace;
|
|
||||||
current.v += advance.appendValue;
|
|
||||||
i += advance.skip;
|
|
||||||
if(advance.end) {
|
|
||||||
if(!block.omit) {
|
|
||||||
tokens.push(current);
|
|
||||||
}
|
|
||||||
current = {s: '', v: '', q: false};
|
|
||||||
block = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(block) {
|
if(state.block) {
|
||||||
throw new Error('Unterminated block');
|
throw new Error(
|
||||||
|
'Unterminated literal (began at ' +
|
||||||
|
posStr(state.token.b) + ')'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return tokens;
|
return tokens;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,14 +4,24 @@ defineDescribe('Sequence Tokeniser', ['./Tokeniser'], (Tokeniser) => {
|
||||||
const tokeniser = new Tokeniser();
|
const tokeniser = new Tokeniser();
|
||||||
|
|
||||||
describe('.tokenise', () => {
|
describe('.tokenise', () => {
|
||||||
|
function token({
|
||||||
|
s = jasmine.anything(), // spacing
|
||||||
|
v = jasmine.anything(), // value
|
||||||
|
q = jasmine.anything(), // isQuote?
|
||||||
|
b = jasmine.anything(), // begin position
|
||||||
|
e = jasmine.anything(), // end position
|
||||||
|
}) {
|
||||||
|
return {s, v, q, b, e};
|
||||||
|
}
|
||||||
|
|
||||||
it('converts the source into atomic tokens', () => {
|
it('converts the source into atomic tokens', () => {
|
||||||
const input = 'foo bar -> baz';
|
const input = 'foo bar -> baz';
|
||||||
const tokens = tokeniser.tokenise(input);
|
const tokens = tokeniser.tokenise(input);
|
||||||
expect(tokens).toEqual([
|
expect(tokens).toEqual([
|
||||||
{s: '', v: 'foo', q: false},
|
token({s: '', v: 'foo'}),
|
||||||
{s: ' ', v: 'bar', q: false},
|
token({s: ' ', v: 'bar'}),
|
||||||
{s: ' ', v: '->', q: false},
|
token({s: ' ', v: '->'}),
|
||||||
{s: ' ', v: 'baz', q: false},
|
token({s: ' ', v: 'baz'}),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -19,10 +29,21 @@ defineDescribe('Sequence Tokeniser', ['./Tokeniser'], (Tokeniser) => {
|
||||||
const input = 'foo bar->baz';
|
const input = 'foo bar->baz';
|
||||||
const tokens = tokeniser.tokenise(input);
|
const tokens = tokeniser.tokenise(input);
|
||||||
expect(tokens).toEqual([
|
expect(tokens).toEqual([
|
||||||
{s: '', v: 'foo', q: false},
|
token({s: '', v: 'foo'}),
|
||||||
{s: ' ', v: 'bar', q: false},
|
token({s: ' ', v: 'bar'}),
|
||||||
{s: '', v: '->', q: false},
|
token({s: '', v: '->'}),
|
||||||
{s: '', v: 'baz', q: false},
|
token({s: '', v: 'baz'}),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('stores character numbers', () => {
|
||||||
|
const input = 'foo bar -> baz';
|
||||||
|
const tokens = tokeniser.tokenise(input);
|
||||||
|
expect(tokens).toEqual([
|
||||||
|
token({b: {i: 0, ln: 0, ch: 0}, e: {i: 3, ln: 0, ch: 3}}),
|
||||||
|
token({b: {i: 4, ln: 0, ch: 4}, e: {i: 7, ln: 0, ch: 7}}),
|
||||||
|
token({b: {i: 8, ln: 0, ch: 8}, e: {i: 10, ln: 0, ch: 10}}),
|
||||||
|
token({b: {i: 11, ln: 0, ch: 11}, e: {i: 14, ln: 0, ch: 14}}),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -30,10 +51,21 @@ defineDescribe('Sequence Tokeniser', ['./Tokeniser'], (Tokeniser) => {
|
||||||
const input = 'foo bar\nbaz';
|
const input = 'foo bar\nbaz';
|
||||||
const tokens = tokeniser.tokenise(input);
|
const tokens = tokeniser.tokenise(input);
|
||||||
expect(tokens).toEqual([
|
expect(tokens).toEqual([
|
||||||
{s: '', v: 'foo', q: false},
|
token({s: '', v: 'foo'}),
|
||||||
{s: ' ', v: 'bar', q: false},
|
token({s: ' ', v: 'bar'}),
|
||||||
{s: '', v: '\n', q: false},
|
token({s: '', v: '\n'}),
|
||||||
{s: '', v: 'baz', q: false},
|
token({s: '', v: 'baz'}),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('stores line numbers', () => {
|
||||||
|
const input = 'foo bar\nbaz';
|
||||||
|
const tokens = tokeniser.tokenise(input);
|
||||||
|
expect(tokens).toEqual([
|
||||||
|
token({b: {i: 0, ln: 0, ch: 0}, e: {i: 3, ln: 0, ch: 3}}),
|
||||||
|
token({b: {i: 4, ln: 0, ch: 4}, e: {i: 7, ln: 0, ch: 7}}),
|
||||||
|
token({b: {i: 7, ln: 0, ch: 7}, e: {i: 8, ln: 1, ch: 0}}),
|
||||||
|
token({b: {i: 8, ln: 1, ch: 0}, e: {i: 11, ln: 1, ch: 3}}),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -41,9 +73,9 @@ defineDescribe('Sequence Tokeniser', ['./Tokeniser'], (Tokeniser) => {
|
||||||
const input = 'foo "\n" baz';
|
const input = 'foo "\n" baz';
|
||||||
const tokens = tokeniser.tokenise(input);
|
const tokens = tokeniser.tokenise(input);
|
||||||
expect(tokens).toEqual([
|
expect(tokens).toEqual([
|
||||||
{s: '', v: 'foo', q: false},
|
token({s: '', v: 'foo', q: false}),
|
||||||
{s: ' ', v: '\n', q: true},
|
token({s: ' ', v: '\n', q: true}),
|
||||||
{s: ' ', v: 'baz', q: false},
|
token({s: ' ', v: 'baz', q: false}),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -51,10 +83,10 @@ defineDescribe('Sequence Tokeniser', ['./Tokeniser'], (Tokeniser) => {
|
||||||
const input = ' foo \t bar\t\n baz';
|
const input = ' foo \t bar\t\n baz';
|
||||||
const tokens = tokeniser.tokenise(input);
|
const tokens = tokeniser.tokenise(input);
|
||||||
expect(tokens).toEqual([
|
expect(tokens).toEqual([
|
||||||
{s: ' ', v: 'foo', q: false},
|
token({s: ' ', v: 'foo'}),
|
||||||
{s: ' \t ', v: 'bar', q: false},
|
token({s: ' \t ', v: 'bar'}),
|
||||||
{s: '\t', v: '\n', q: false},
|
token({s: '\t', v: '\n'}),
|
||||||
{s: ' ', v: 'baz', q: false},
|
token({s: ' ', v: 'baz'}),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -62,9 +94,17 @@ defineDescribe('Sequence Tokeniser', ['./Tokeniser'], (Tokeniser) => {
|
||||||
const input = 'foo "zig zag" \'abc def\'';
|
const input = 'foo "zig zag" \'abc def\'';
|
||||||
const tokens = tokeniser.tokenise(input);
|
const tokens = tokeniser.tokenise(input);
|
||||||
expect(tokens).toEqual([
|
expect(tokens).toEqual([
|
||||||
{s: '', v: 'foo', q: false},
|
token({s: '', v: 'foo', q: false}),
|
||||||
{s: ' ', v: 'zig zag', q: true},
|
token({s: ' ', v: 'zig zag', q: true}),
|
||||||
{s: ' ', v: 'abc def', q: true},
|
token({s: ' ', v: 'abc def', q: true}),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('stores character positions around quoted strings', () => {
|
||||||
|
const input = '"foo bar"';
|
||||||
|
const tokens = tokeniser.tokenise(input);
|
||||||
|
expect(tokens).toEqual([
|
||||||
|
token({b: {i: 0, ln: 0, ch: 0}, e: {i: 9, ln: 0, ch: 9}}),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -72,9 +112,9 @@ defineDescribe('Sequence Tokeniser', ['./Tokeniser'], (Tokeniser) => {
|
||||||
const input = 'foo # bar baz\nzig';
|
const input = 'foo # bar baz\nzig';
|
||||||
const tokens = tokeniser.tokenise(input);
|
const tokens = tokeniser.tokenise(input);
|
||||||
expect(tokens).toEqual([
|
expect(tokens).toEqual([
|
||||||
{s: '', v: 'foo', q: false},
|
token({s: '', v: 'foo'}),
|
||||||
{s: '', v: '\n', q: false},
|
token({s: '', v: '\n'}),
|
||||||
{s: '', v: 'zig', q: false},
|
token({s: '', v: 'zig'}),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -82,9 +122,9 @@ defineDescribe('Sequence Tokeniser', ['./Tokeniser'], (Tokeniser) => {
|
||||||
const input = 'foo # bar "\'baz\nzig';
|
const input = 'foo # bar "\'baz\nzig';
|
||||||
const tokens = tokeniser.tokenise(input);
|
const tokens = tokeniser.tokenise(input);
|
||||||
expect(tokens).toEqual([
|
expect(tokens).toEqual([
|
||||||
{s: '', v: 'foo', q: false},
|
token({s: '', v: 'foo'}),
|
||||||
{s: '', v: '\n', q: false},
|
token({s: '', v: '\n'}),
|
||||||
{s: '', v: 'zig', q: false},
|
token({s: '', v: 'zig'}),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -92,8 +132,8 @@ defineDescribe('Sequence Tokeniser', ['./Tokeniser'], (Tokeniser) => {
|
||||||
const input = 'foo "zig\\" zag\\n"';
|
const input = 'foo "zig\\" zag\\n"';
|
||||||
const tokens = tokeniser.tokenise(input);
|
const tokens = tokeniser.tokenise(input);
|
||||||
expect(tokens).toEqual([
|
expect(tokens).toEqual([
|
||||||
{s: '', v: 'foo', q: false},
|
token({s: '', v: 'foo', q: false}),
|
||||||
{s: ' ', v: 'zig" zag\n', q: true},
|
token({s: ' ', v: 'zig" zag\n', q: true}),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -101,13 +141,27 @@ defineDescribe('Sequence Tokeniser', ['./Tokeniser'], (Tokeniser) => {
|
||||||
const input = 'foo " zig\n zag "';
|
const input = 'foo " zig\n zag "';
|
||||||
const tokens = tokeniser.tokenise(input);
|
const tokens = tokeniser.tokenise(input);
|
||||||
expect(tokens).toEqual([
|
expect(tokens).toEqual([
|
||||||
{s: '', v: 'foo', q: false},
|
token({s: '', v: 'foo', q: false}),
|
||||||
{s: ' ', v: ' zig\n zag ', q: true},
|
token({s: ' ', v: ' zig\n zag ', q: true}),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calculates line numbers consistently within quotes', () => {
|
||||||
|
const input = 'foo\nbar "zig\nzag\na" b';
|
||||||
|
const tokens = tokeniser.tokenise(input);
|
||||||
|
expect(tokens).toEqual([
|
||||||
|
token({b: {i: 0, ln: 0, ch: 0}, e: {i: 3, ln: 0, ch: 3}}),
|
||||||
|
token({b: {i: 3, ln: 0, ch: 3}, e: {i: 4, ln: 1, ch: 0}}),
|
||||||
|
token({b: {i: 4, ln: 1, ch: 0}, e: {i: 7, ln: 1, ch: 3}}),
|
||||||
|
token({b: {i: 8, ln: 1, ch: 4}, e: {i: 19, ln: 3, ch: 2}}),
|
||||||
|
token({b: {i: 20, ln: 3, ch: 3}, e: {i: 21, ln: 3, ch: 4}}),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('rejects unterminated quoted values', () => {
|
it('rejects unterminated quoted values', () => {
|
||||||
expect(() => tokeniser.tokenise('"nope')).toThrow();
|
expect(() => tokeniser.tokenise('"nope')).toThrow(new Error(
|
||||||
|
'Unterminated literal (began at line 1, character 0)'
|
||||||
|
));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue