diff --git a/src/index.js b/src/index.js index 5a923c244b..15dd053b5a 100644 --- a/src/index.js +++ b/src/index.js @@ -121,6 +121,7 @@ export { visitInParallel, visitWithTypeInfo, Kind, + TokenKind, BREAK, } from './language'; diff --git a/src/language/__tests__/lexer-test.js b/src/language/__tests__/lexer-test.js index 040e6bdf57..79b1b5e970 100644 --- a/src/language/__tests__/lexer-test.js +++ b/src/language/__tests__/lexer-test.js @@ -10,21 +10,18 @@ import { expect } from 'chai'; import { describe, it } from 'mocha'; import { Source } from '../source'; -import { lex, TokenKind } from '../lexer'; +import { createLexer, TokenKind } from '../lexer'; function lexOne(str) { - return lex(new Source(str))(); -} - -function lexErr(str) { - return lex(new Source(str)); + const lexer = createLexer(new Source(str)); + return lexer.advance(); } describe('Lexer', () => { it('disallows uncommon control characters', () => { - expect(lexErr('\u0007') + expect(() => lexOne('\u0007') ).to.throw( 'Syntax Error GraphQL (1:1) Invalid character "\\u0007"' ); @@ -33,7 +30,7 @@ describe('Lexer', () => { it('accepts BOM header', () => { expect(lexOne('\uFEFF foo') - ).to.deep.equal({ + ).to.containSubset({ kind: TokenKind.NAME, start: 2, end: 5, @@ -41,7 +38,31 @@ describe('Lexer', () => { }); }); - it('skips whitespace', () => { + it('records line and column', () => { + expect(lexOne('\n \r\n \r foo\n')).to.containSubset({ + kind: TokenKind.NAME, + start: 8, + end: 11, + line: 4, + column: 3, + value: 'foo' + }); + }); + + it('can be JSON.stringified or util.inspected', () => { + const token = lexOne('foo'); + expect(JSON.stringify(token)).to.equal( + '{"kind":"Name","value":"foo","line":1,"column":1}' + ); + // NB: util.inspect used to suck + if (parseFloat(process.version.slice(1)) > 0.10) { + expect(require('util').inspect(token)).to.equal( + '{ kind: \'Name\', value: \'foo\', line: 1, column: 1 }' + ); + } + }); + + it('skips whitespace and comments', () => { expect(lexOne(` @@ -49,7 +70,7 @@ describe('Lexer', () => { `) - ).to.deep.equal({ + ).to.containSubset({ kind: TokenKind.NAME, start: 6, end: 9, @@ -60,14 +81,14 @@ describe('Lexer', () => { #comment foo#comment `) - ).to.deep.equal({ + ).to.containSubset({ kind: TokenKind.NAME, start: 18, end: 21, value: 'foo' }); - expect(lexOne(',,,foo,,,')).to.deep.equal({ + expect(lexOne(',,,foo,,,')).to.containSubset({ kind: TokenKind.NAME, start: 3, end: 6, @@ -78,7 +99,7 @@ describe('Lexer', () => { it('errors respect whitespace', () => { - expect(lexErr(` + expect(() => lexOne(` ? @@ -99,7 +120,7 @@ describe('Lexer', () => { expect( lexOne('"simple"') - ).to.deep.equal({ + ).to.containSubset({ kind: TokenKind.STRING, start: 0, end: 8, @@ -108,7 +129,7 @@ describe('Lexer', () => { expect( lexOne('" white space "') - ).to.deep.equal({ + ).to.containSubset({ kind: TokenKind.STRING, start: 0, end: 15, @@ -117,7 +138,7 @@ describe('Lexer', () => { expect( lexOne('"quote \\""') - ).to.deep.equal({ + ).to.containSubset({ kind: TokenKind.STRING, start: 0, end: 10, @@ -126,7 +147,7 @@ describe('Lexer', () => { expect( lexOne('"escaped \\n\\r\\b\\t\\f"') - ).to.deep.equal({ + ).to.containSubset({ kind: TokenKind.STRING, start: 0, end: 20, @@ -135,7 +156,7 @@ describe('Lexer', () => { expect( lexOne('"slashes \\\\ \\/"') - ).to.deep.equal({ + ).to.containSubset({ kind: TokenKind.STRING, start: 0, end: 15, @@ -144,7 +165,7 @@ describe('Lexer', () => { expect( lexOne('"unicode \\u1234\\u5678\\u90AB\\uCDEF"') - ).to.deep.equal({ + ).to.containSubset({ kind: TokenKind.STRING, start: 0, end: 34, @@ -156,71 +177,71 @@ describe('Lexer', () => { it('lex reports useful string errors', () => { expect( - lexErr('"') + () => lexOne('"') ).to.throw('Syntax Error GraphQL (1:2) Unterminated string'); expect( - lexErr('"no end quote') + () => lexOne('"no end quote') ).to.throw('Syntax Error GraphQL (1:14) Unterminated string'); expect( - lexErr('"contains unescaped \u0007 control char"') + () => lexOne('"contains unescaped \u0007 control char"') ).to.throw( 'Syntax Error GraphQL (1:21) Invalid character within String: "\\u0007".' ); expect( - lexErr('"null-byte is not \u0000 end of file"') + () => lexOne('"null-byte is not \u0000 end of file"') ).to.throw( 'Syntax Error GraphQL (1:19) Invalid character within String: "\\u0000".' ); expect( - lexErr('"multi\nline"') + () => lexOne('"multi\nline"') ).to.throw('Syntax Error GraphQL (1:7) Unterminated string'); expect( - lexErr('"multi\rline"') + () => lexOne('"multi\rline"') ).to.throw('Syntax Error GraphQL (1:7) Unterminated string'); expect( - lexErr('"bad \\z esc"') + () => lexOne('"bad \\z esc"') ).to.throw( 'Syntax Error GraphQL (1:7) Invalid character escape sequence: \\z.' ); expect( - lexErr('"bad \\x esc"') + () => lexOne('"bad \\x esc"') ).to.throw( 'Syntax Error GraphQL (1:7) Invalid character escape sequence: \\x.' ); expect( - lexErr('"bad \\u1 esc"') + () => lexOne('"bad \\u1 esc"') ).to.throw( 'Syntax Error GraphQL (1:7) Invalid character escape sequence: \\u1 es.' ); expect( - lexErr('"bad \\u0XX1 esc"') + () => lexOne('"bad \\u0XX1 esc"') ).to.throw( 'Syntax Error GraphQL (1:7) Invalid character escape sequence: \\u0XX1.' ); expect( - lexErr('"bad \\uXXXX esc"') + () => lexOne('"bad \\uXXXX esc"') ).to.throw( 'Syntax Error GraphQL (1:7) Invalid character escape sequence: \\uXXXX.' ); expect( - lexErr('"bad \\uFXXX esc"') + () => lexOne('"bad \\uFXXX esc"') ).to.throw( 'Syntax Error GraphQL (1:7) Invalid character escape sequence: \\uFXXX.' ); expect( - lexErr('"bad \\uXXXF esc"') + () => lexOne('"bad \\uXXXF esc"') ).to.throw( 'Syntax Error GraphQL (1:7) Invalid character escape sequence: \\uXXXF.' ); @@ -230,7 +251,7 @@ describe('Lexer', () => { expect( lexOne('4') - ).to.deep.equal({ + ).to.containSubset({ kind: TokenKind.INT, start: 0, end: 1, @@ -239,7 +260,7 @@ describe('Lexer', () => { expect( lexOne('4.123') - ).to.deep.equal({ + ).to.containSubset({ kind: TokenKind.FLOAT, start: 0, end: 5, @@ -248,7 +269,7 @@ describe('Lexer', () => { expect( lexOne('-4') - ).to.deep.equal({ + ).to.containSubset({ kind: TokenKind.INT, start: 0, end: 2, @@ -257,7 +278,7 @@ describe('Lexer', () => { expect( lexOne('9') - ).to.deep.equal({ + ).to.containSubset({ kind: TokenKind.INT, start: 0, end: 1, @@ -266,7 +287,7 @@ describe('Lexer', () => { expect( lexOne('0') - ).to.deep.equal({ + ).to.containSubset({ kind: TokenKind.INT, start: 0, end: 1, @@ -275,7 +296,7 @@ describe('Lexer', () => { expect( lexOne('-4.123') - ).to.deep.equal({ + ).to.containSubset({ kind: TokenKind.FLOAT, start: 0, end: 6, @@ -284,7 +305,7 @@ describe('Lexer', () => { expect( lexOne('0.123') - ).to.deep.equal({ + ).to.containSubset({ kind: TokenKind.FLOAT, start: 0, end: 5, @@ -293,7 +314,7 @@ describe('Lexer', () => { expect( lexOne('123e4') - ).to.deep.equal({ + ).to.containSubset({ kind: TokenKind.FLOAT, start: 0, end: 5, @@ -302,7 +323,7 @@ describe('Lexer', () => { expect( lexOne('123E4') - ).to.deep.equal({ + ).to.containSubset({ kind: TokenKind.FLOAT, start: 0, end: 5, @@ -311,7 +332,7 @@ describe('Lexer', () => { expect( lexOne('123e-4') - ).to.deep.equal({ + ).to.containSubset({ kind: TokenKind.FLOAT, start: 0, end: 6, @@ -320,7 +341,7 @@ describe('Lexer', () => { expect( lexOne('123e+4') - ).to.deep.equal({ + ).to.containSubset({ kind: TokenKind.FLOAT, start: 0, end: 6, @@ -329,7 +350,7 @@ describe('Lexer', () => { expect( lexOne('-1.123e4') - ).to.deep.equal({ + ).to.containSubset({ kind: TokenKind.FLOAT, start: 0, end: 8, @@ -338,7 +359,7 @@ describe('Lexer', () => { expect( lexOne('-1.123E4') - ).to.deep.equal({ + ).to.containSubset({ kind: TokenKind.FLOAT, start: 0, end: 8, @@ -347,7 +368,7 @@ describe('Lexer', () => { expect( lexOne('-1.123e-4') - ).to.deep.equal({ + ).to.containSubset({ kind: TokenKind.FLOAT, start: 0, end: 9, @@ -356,7 +377,7 @@ describe('Lexer', () => { expect( lexOne('-1.123e+4') - ).to.deep.equal({ + ).to.containSubset({ kind: TokenKind.FLOAT, start: 0, end: 9, @@ -365,7 +386,7 @@ describe('Lexer', () => { expect( lexOne('-1.123e4567') - ).to.deep.equal({ + ).to.containSubset({ kind: TokenKind.FLOAT, start: 0, end: 11, @@ -377,49 +398,49 @@ describe('Lexer', () => { it('lex reports useful number errors', () => { expect( - lexErr('00') + () => lexOne('00') ).to.throw( 'Syntax Error GraphQL (1:2) Invalid number, ' + 'unexpected digit after 0: "0".' ); expect( - lexErr('+1') + () => lexOne('+1') ).to.throw('Syntax Error GraphQL (1:1) Unexpected character "+"'); expect( - lexErr('1.') + () => lexOne('1.') ).to.throw( 'Syntax Error GraphQL (1:3) Invalid number, ' + 'expected digit but got: .' ); expect( - lexErr('.123') + () => lexOne('.123') ).to.throw('Syntax Error GraphQL (1:1) Unexpected character "."'); expect( - lexErr('1.A') + () => lexOne('1.A') ).to.throw( 'Syntax Error GraphQL (1:3) Invalid number, ' + 'expected digit but got: "A".' ); expect( - lexErr('-A') + () => lexOne('-A') ).to.throw( 'Syntax Error GraphQL (1:2) Invalid number, ' + 'expected digit but got: "A".' ); expect( - lexErr('1.0e') + () => lexOne('1.0e') ).to.throw( 'Syntax Error GraphQL (1:5) Invalid number, ' + 'expected digit but got: .'); expect( - lexErr('1.0eA') + () => lexOne('1.0eA') ).to.throw( 'Syntax Error GraphQL (1:5) Invalid number, ' + 'expected digit but got: "A".' @@ -430,7 +451,7 @@ describe('Lexer', () => { expect( lexOne('!') - ).to.deep.equal({ + ).to.containSubset({ kind: TokenKind.BANG, start: 0, end: 1, @@ -439,7 +460,7 @@ describe('Lexer', () => { expect( lexOne('$') - ).to.deep.equal({ + ).to.containSubset({ kind: TokenKind.DOLLAR, start: 0, end: 1, @@ -448,7 +469,7 @@ describe('Lexer', () => { expect( lexOne('(') - ).to.deep.equal({ + ).to.containSubset({ kind: TokenKind.PAREN_L, start: 0, end: 1, @@ -457,7 +478,7 @@ describe('Lexer', () => { expect( lexOne(')') - ).to.deep.equal({ + ).to.containSubset({ kind: TokenKind.PAREN_R, start: 0, end: 1, @@ -466,7 +487,7 @@ describe('Lexer', () => { expect( lexOne('...') - ).to.deep.equal({ + ).to.containSubset({ kind: TokenKind.SPREAD, start: 0, end: 3, @@ -475,7 +496,7 @@ describe('Lexer', () => { expect( lexOne(':') - ).to.deep.equal({ + ).to.containSubset({ kind: TokenKind.COLON, start: 0, end: 1, @@ -484,7 +505,7 @@ describe('Lexer', () => { expect( lexOne('=') - ).to.deep.equal({ + ).to.containSubset({ kind: TokenKind.EQUALS, start: 0, end: 1, @@ -493,7 +514,7 @@ describe('Lexer', () => { expect( lexOne('@') - ).to.deep.equal({ + ).to.containSubset({ kind: TokenKind.AT, start: 0, end: 1, @@ -502,7 +523,7 @@ describe('Lexer', () => { expect( lexOne('[') - ).to.deep.equal({ + ).to.containSubset({ kind: TokenKind.BRACKET_L, start: 0, end: 1, @@ -511,7 +532,7 @@ describe('Lexer', () => { expect( lexOne(']') - ).to.deep.equal({ + ).to.containSubset({ kind: TokenKind.BRACKET_R, start: 0, end: 1, @@ -520,7 +541,7 @@ describe('Lexer', () => { expect( lexOne('{') - ).to.deep.equal({ + ).to.containSubset({ kind: TokenKind.BRACE_L, start: 0, end: 1, @@ -529,7 +550,7 @@ describe('Lexer', () => { expect( lexOne('|') - ).to.deep.equal({ + ).to.containSubset({ kind: TokenKind.PIPE, start: 0, end: 1, @@ -538,7 +559,7 @@ describe('Lexer', () => { expect( lexOne('}') - ).to.deep.equal({ + ).to.containSubset({ kind: TokenKind.BRACE_R, start: 0, end: 1, @@ -550,36 +571,73 @@ describe('Lexer', () => { it('lex reports useful unknown character error', () => { expect( - lexErr('..') + () => lexOne('..') ).to.throw('Syntax Error GraphQL (1:1) Unexpected character "."'); expect( - lexErr('?') + () => lexOne('?') ).to.throw('Syntax Error GraphQL (1:1) Unexpected character "?"'); expect( - lexErr('\u203B') + () => lexOne('\u203B') ).to.throw('Syntax Error GraphQL (1:1) Unexpected character "\\u203B"'); expect( - lexErr('\u200b') + () => lexOne('\u200b') ).to.throw('Syntax Error GraphQL (1:1) Unexpected character "\\u200B"'); }); it('lex reports useful information for dashes in names', () => { const q = 'a-b'; - const lexer = lex(new Source(q)); - const firstToken = lexer(); - expect(firstToken).to.deep.equal({ + const lexer = createLexer(new Source(q)); + const firstToken = lexer.advance(); + expect(firstToken).to.containSubset({ kind: TokenKind.NAME, start: 0, end: 1, value: 'a' }); expect( - () => lexer() + () => lexer.advance() ).to.throw( 'Syntax Error GraphQL (1:3) Invalid number, expected digit but got: "b".' ); }); + + it('produces double linked list of tokens, including comments', () => { + const lexer = createLexer(new Source(`{ + #comment + field + }`)); + + const startToken = lexer.token; + let endToken; + do { + endToken = lexer.advance(); + // Lexer advances over ignored comment tokens to make writing parsers + // easier, but will include them in the linked list result. + expect(endToken.kind).not.to.equal('Comment'); + } while (endToken.kind !== ''); + + expect(startToken.prev).to.equal(null); + expect(endToken.next).to.equal(null); + + const tokens = []; + for (let tok = startToken; tok; tok = tok.next) { + if (tokens.length) { + // Tokens are double-linked, prev should point to last seen token. + expect(tok.prev).to.equal(tokens[tokens.length - 1]); + } + tokens.push(tok); + } + + expect(tokens.map(tok => tok.kind)).to.deep.equal([ + '', + '{', + 'Comment', + 'Name', + '}', + '' + ]); + }); }); diff --git a/src/language/__tests__/parser-test.js b/src/language/__tests__/parser-test.js index 7d7e968f87..d7d843a02c 100644 --- a/src/language/__tests__/parser-test.js +++ b/src/language/__tests__/parser-test.js @@ -17,34 +17,6 @@ import { join } from 'path'; describe('Parser', () => { - it('accepts option to not include source', () => { - expect(parse('{ field }', { noSource: true })).to.deep.equal({ - kind: 'Document', - loc: { start: 0, end: 9 }, - definitions: - [ { kind: 'OperationDefinition', - loc: { start: 0, end: 9 }, - operation: 'query', - name: null, - variableDefinitions: null, - directives: [], - selectionSet: { - kind: 'SelectionSet', - loc: { start: 0, end: 9 }, - selections: - [ { kind: 'Field', - loc: { start: 2, end: 7 }, - alias: null, - name: - { kind: 'Name', - loc: { start: 2, end: 7 }, - value: 'field' }, - arguments: [], - directives: [], - selectionSet: null } ] } } ] - }); - }); - it('parse provides useful errors', () => { let caughtError; @@ -55,7 +27,7 @@ describe('Parser', () => { } expect(caughtError.message).to.equal( - `Syntax Error GraphQL (1:2) Expected Name, found EOF + `Syntax Error GraphQL (1:2) Expected Name, found 1: { ^ @@ -92,7 +64,7 @@ fragment MissingOn Type it('parse provides useful error when using source', () => { expect( () => parse(new Source('query', 'MyQuery.graphql')) - ).to.throw('Syntax Error MyQuery.graphql (1:6) Expected {, found EOF'); + ).to.throw('Syntax Error MyQuery.graphql (1:6) Expected {, found '); }); it('parses variable inline values', () => { @@ -218,7 +190,7 @@ fragment ${fragmentName} on Type { `)).to.not.throw(); }); - it('parse creates ast', () => { + it('creates ast', () => { const source = new Source(`{ node(id: 4) { @@ -229,63 +201,96 @@ fragment ${fragmentName} on Type { `); const result = parse(source); - expect(result).to.deep.equal( + expect(result).to.containSubset( { kind: Kind.DOCUMENT, - loc: { start: 0, end: 41, source }, + loc: { start: 0, end: 41 }, definitions: [ { kind: Kind.OPERATION_DEFINITION, - loc: { start: 0, end: 40, source }, + loc: { start: 0, end: 40 }, operation: 'query', name: null, variableDefinitions: null, directives: [], selectionSet: { kind: Kind.SELECTION_SET, - loc: { start: 0, end: 40, source }, + loc: { start: 0, end: 40 }, selections: [ { kind: Kind.FIELD, - loc: { start: 4, end: 38, source }, + loc: { start: 4, end: 38 }, alias: null, name: { kind: Kind.NAME, - loc: { start: 4, end: 8, source }, + loc: { start: 4, end: 8 }, value: 'node' }, arguments: [ { kind: Kind.ARGUMENT, name: { kind: Kind.NAME, - loc: { start: 9, end: 11, source }, + loc: { start: 9, end: 11 }, value: 'id' }, value: { kind: Kind.INT, - loc: { start: 13, end: 14, source }, + loc: { start: 13, end: 14 }, value: '4' }, - loc: { start: 9, end: 14, source } } ], + loc: { start: 9, end: 14 } } ], directives: [], selectionSet: { kind: Kind.SELECTION_SET, - loc: { start: 16, end: 38, source }, + loc: { start: 16, end: 38 }, selections: [ { kind: Kind.FIELD, - loc: { start: 22, end: 24, source }, + loc: { start: 22, end: 24 }, alias: null, name: { kind: Kind.NAME, - loc: { start: 22, end: 24, source }, + loc: { start: 22, end: 24 }, value: 'id' }, arguments: [], directives: [], selectionSet: null }, { kind: Kind.FIELD, - loc: { start: 30, end: 34, source }, + loc: { start: 30, end: 34 }, alias: null, name: { kind: Kind.NAME, - loc: { start: 30, end: 34, source }, + loc: { start: 30, end: 34 }, value: 'name' }, arguments: [], directives: [], selectionSet: null } ] } } ] } } ] } ); }); + + it('allows parsing without source location information', () => { + const source = new Source('{ id }'); + const result = parse(source, { noLocation: true }); + expect(result.loc).to.equal(undefined); + }); + + it('contains location information that only stringifys start/end', () => { + const source = new Source('{ id }'); + const result = parse(source); + expect(JSON.stringify(result.loc)).to.equal( + '{"start":0,"end":6}' + ); + // NB: util.inspect used to suck + if (parseFloat(process.version.slice(1)) > 0.10) { + expect(require('util').inspect(result.loc)).to.equal( + '{ start: 0, end: 6 }' + ); + } + }); + + it('contains references to source', () => { + const source = new Source('{ id }'); + const result = parse(source); + expect(result.loc.source).to.equal(source); + }); + + it('contains references to start and end tokens', () => { + const source = new Source('{ id }'); + const result = parse(source); + expect(result.loc.startToken.kind).to.equal(''); + expect(result.loc.endToken.kind).to.equal(''); + }); }); diff --git a/src/language/__tests__/printer-test.js b/src/language/__tests__/printer-test.js index 2f8a416618..51b93ab2aa 100644 --- a/src/language/__tests__/printer-test.js +++ b/src/language/__tests__/printer-test.js @@ -17,9 +17,9 @@ import { join } from 'path'; describe('Printer', () => { it('does not alter ast', () => { const ast = parse(kitchenSink); - const astCopy = JSON.parse(JSON.stringify(ast)); + const astBefore = JSON.stringify(ast); print(ast); - expect(ast).to.deep.equal(astCopy); + expect(JSON.stringify(ast)).to.equal(astBefore); }); it('prints minimal ast', () => { diff --git a/src/language/__tests__/schema-parser-test.js b/src/language/__tests__/schema-parser-test.js index 6168cf42d5..3a15e300e5 100644 --- a/src/language/__tests__/schema-parser-test.js +++ b/src/language/__tests__/schema-parser-test.js @@ -11,17 +11,6 @@ import { expect } from 'chai'; import { describe, it } from 'mocha'; import { parse } from '../parser'; -function createLocFn(body) { - return (start, end) => ({ - start, - end, - source: { - body, - name: 'GraphQL', - }, - }); -} - function printJson(obj) { return JSON.stringify(obj, null, 2); } @@ -84,26 +73,25 @@ type Hello { world: String }`; const doc = parse(body); - const loc = createLocFn(body); const expected = { kind: 'Document', definitions: [ { kind: 'ObjectTypeDefinition', - name: nameNode('Hello', loc(6, 11)), + name: nameNode('Hello', { start: 6, end: 11 }), interfaces: [], directives: [], fields: [ fieldNode( - nameNode('world', loc(16, 21)), - typeNode('String', loc(23, 29)), - loc(16, 29) + nameNode('world', { start: 16, end: 21 }), + typeNode('String', { start: 23, end: 29 }), + { start: 16, end: 29 } ) ], - loc: loc(1, 31), + loc: { start: 1, end: 31 }, } ], - loc: loc(1, 31), + loc: { start: 0, end: 31 }, }; expect(printJson(doc)).to.equal(printJson(expected)); }); @@ -112,9 +100,9 @@ type Hello { const body = ` extend type Hello { world: String -}`; +} +`; const doc = parse(body); - const loc = createLocFn(body); const expected = { kind: 'Document', definitions: [ @@ -122,22 +110,22 @@ extend type Hello { kind: 'TypeExtensionDefinition', definition: { kind: 'ObjectTypeDefinition', - name: nameNode('Hello', loc(13, 18)), + name: nameNode('Hello', { start: 13, end: 18 }), interfaces: [], directives: [], fields: [ fieldNode( - nameNode('world', loc(23, 28)), - typeNode('String', loc(30, 36)), - loc(23, 36) + nameNode('world', { start: 23, end: 28 }), + typeNode('String', { start: 30, end: 36 }), + { start: 23, end: 36 } ) ], - loc: loc(8, 38), + loc: { start: 8, end: 38 }, }, - loc: loc(1, 38), + loc: { start: 1, end: 38 }, } ], - loc: loc(1, 38) + loc: { start: 0, end: 39 } }; expect(printJson(doc)).to.equal(printJson(expected)); }); @@ -147,31 +135,30 @@ extend type Hello { type Hello { world: String! }`; - const loc = createLocFn(body); const doc = parse(body); const expected = { kind: 'Document', definitions: [ { kind: 'ObjectTypeDefinition', - name: nameNode('Hello', loc(6, 11)), + name: nameNode('Hello', { start: 6, end: 11 }), interfaces: [], directives: [], fields: [ fieldNode( - nameNode('world', loc(16, 21)), + nameNode('world', { start: 16, end: 21 }), { kind: 'NonNullType', - type: typeNode('String', loc(23, 29)), - loc: loc(23, 30), + type: typeNode('String', { start: 23, end: 29 }), + loc: { start: 23, end: 30 }, }, - loc(16, 30) + { start: 16, end: 30 } ) ], - loc: loc(1, 32), + loc: { start: 1, end: 32 }, } ], - loc: loc(1, 32), + loc: { start: 0, end: 32 }, }; expect(printJson(doc)).to.equal(printJson(expected)); }); @@ -179,88 +166,84 @@ type Hello { it('Simple type inheriting interface', () => { const body = 'type Hello implements World { }'; - const loc = createLocFn(body); const doc = parse(body); const expected = { kind: 'Document', definitions: [ { kind: 'ObjectTypeDefinition', - name: nameNode('Hello', loc(5, 10)), - interfaces: [ typeNode('World', loc(22, 27)) ], + name: nameNode('Hello', { start: 5, end: 10 }), + interfaces: [ typeNode('World', { start: 22, end: 27 }) ], directives: [], fields: [], - loc: loc(0, 31), + loc: { start: 0, end: 31 }, } ], - loc: loc(0, 31), + loc: { start: 0, end: 31 }, }; expect(printJson(doc)).to.equal(printJson(expected)); }); it('Simple type inheriting multiple interfaces', () => { const body = 'type Hello implements Wo, rld { }'; - const loc = createLocFn(body); const doc = parse(body); const expected = { kind: 'Document', definitions: [ { kind: 'ObjectTypeDefinition', - name: nameNode('Hello', loc(5, 10)), + name: nameNode('Hello', { start: 5, end: 10 }), interfaces: [ - typeNode('Wo', loc(22, 24)), - typeNode('rld', loc(26, 29)) + typeNode('Wo', { start: 22, end: 24 }), + typeNode('rld', { start: 26, end: 29 }) ], directives: [], fields: [], - loc: loc(0, 33), + loc: { start: 0, end: 33 }, } ], - loc: loc(0, 33), + loc: { start: 0, end: 33 }, }; expect(printJson(doc)).to.equal(printJson(expected)); }); it('Single value enum', () => { const body = 'enum Hello { WORLD }'; - const loc = createLocFn(body); const doc = parse(body); const expected = { kind: 'Document', definitions: [ { kind: 'EnumTypeDefinition', - name: nameNode('Hello', loc(5, 10)), + name: nameNode('Hello', { start: 5, end: 10 }), directives: [], - values: [ enumValueNode('WORLD', loc(13, 18)) ], - loc: loc(0, 20), + values: [ enumValueNode('WORLD', { start: 13, end: 18 }) ], + loc: { start: 0, end: 20 }, } ], - loc: loc(0, 20), + loc: { start: 0, end: 20 }, }; expect(printJson(doc)).to.equal(printJson(expected)); }); it('Double value enum', () => { const body = 'enum Hello { WO, RLD }'; - const loc = createLocFn(body); const doc = parse(body); const expected = { kind: 'Document', definitions: [ { kind: 'EnumTypeDefinition', - name: nameNode('Hello', loc(5, 10)), + name: nameNode('Hello', { start: 5, end: 10 }), directives: [], values: [ - enumValueNode('WO', loc(13, 15)), - enumValueNode('RLD', loc(17, 20)), + enumValueNode('WO', { start: 13, end: 15 }), + enumValueNode('RLD', { start: 17, end: 20 }), ], - loc: loc(0, 22), + loc: { start: 0, end: 22 }, } ], - loc: loc(0, 22), + loc: { start: 0, end: 22 }, }; expect(printJson(doc)).to.equal(printJson(expected)); }); @@ -271,25 +254,24 @@ interface Hello { world: String }`; const doc = parse(body); - const loc = createLocFn(body); const expected = { kind: 'Document', definitions: [ { kind: 'InterfaceTypeDefinition', - name: nameNode('Hello', loc(11, 16)), + name: nameNode('Hello', { start: 11, end: 16 }), directives: [], fields: [ fieldNode( - nameNode('world', loc(21, 26)), - typeNode('String', loc(28, 34)), - loc(21, 34) + nameNode('world', { start: 21, end: 26 }), + typeNode('String', { start: 28, end: 34 }), + { start: 21, end: 34 } ) ], - loc: loc(1, 36), + loc: { start: 1, end: 36 }, } ], - loc: loc(1, 36), + loc: { start: 0, end: 36 }, }; expect(printJson(doc)).to.equal(printJson(expected)); }); @@ -300,34 +282,33 @@ type Hello { world(flag: Boolean): String }`; const doc = parse(body); - const loc = createLocFn(body); const expected = { kind: 'Document', definitions: [ { kind: 'ObjectTypeDefinition', - name: nameNode('Hello', loc(6, 11)), + name: nameNode('Hello', { start: 6, end: 11 }), interfaces: [], directives: [], fields: [ fieldNodeWithArgs( - nameNode('world', loc(16, 21)), - typeNode('String', loc(38, 44)), + nameNode('world', { start: 16, end: 21 }), + typeNode('String', { start: 38, end: 44 }), [ inputValueNode( - nameNode('flag', loc(22, 26)), - typeNode('Boolean', loc(28, 35)), + nameNode('flag', { start: 22, end: 26 }), + typeNode('Boolean', { start: 28, end: 35 }), null, - loc(22, 35) + { start: 22, end: 35 } ) ], - loc(16, 44) + { start: 16, end: 44 } ) ], - loc: loc(1, 46), + loc: { start: 1, end: 46 }, } ], - loc: loc(1, 46), + loc: { start: 0, end: 46 }, }; expect(printJson(doc)).to.equal(printJson(expected)); }); @@ -338,38 +319,37 @@ type Hello { world(flag: Boolean = true): String }`; const doc = parse(body); - const loc = createLocFn(body); const expected = { kind: 'Document', definitions: [ { kind: 'ObjectTypeDefinition', - name: nameNode('Hello', loc(6, 11)), + name: nameNode('Hello', { start: 6, end: 11 }), interfaces: [], directives: [], fields: [ fieldNodeWithArgs( - nameNode('world', loc(16, 21)), - typeNode('String', loc(45, 51)), + nameNode('world', { start: 16, end: 21 }), + typeNode('String', { start: 45, end: 51 }), [ inputValueNode( - nameNode('flag', loc(22, 26)), - typeNode('Boolean', loc(28, 35)), + nameNode('flag', { start: 22, end: 26 }), + typeNode('Boolean', { start: 28, end: 35 }), { kind: 'BooleanValue', value: true, - loc: loc(38, 42), + loc: { start: 38, end: 42 }, }, - loc(22, 42) + { start: 22, end: 42 } ) ], - loc(16, 51) + { start: 16, end: 51 } ) ], - loc: loc(1, 53), + loc: { start: 1, end: 53 }, } ], - loc: loc(1, 53), + loc: { start: 0, end: 53 }, }; expect(printJson(doc)).to.equal(printJson(expected)); }); @@ -380,38 +360,37 @@ type Hello { world(things: [String]): String }`; const doc = parse(body); - const loc = createLocFn(body); const expected = { kind: 'Document', definitions: [ { kind: 'ObjectTypeDefinition', - name: nameNode('Hello', loc(6, 11)), + name: nameNode('Hello', { start: 6, end: 11 }), interfaces: [], directives: [], fields: [ fieldNodeWithArgs( - nameNode('world', loc(16, 21)), - typeNode('String', loc(41, 47)), + nameNode('world', { start: 16, end: 21 }), + typeNode('String', { start: 41, end: 47 }), [ inputValueNode( - nameNode('things', loc(22, 28)), + nameNode('things', { start: 22, end: 28 }), { kind: 'ListType', - type: typeNode('String', loc(31, 37)), - loc: loc(30, 38) + type: typeNode('String', { start: 31, end: 37 }), + loc: { start: 30, end: 38 } }, null, - loc(22, 38) + { start: 22, end: 38 } ) ], - loc(16, 47) + { start: 16, end: 47 } ) ], - loc: loc(1, 49), + loc: { start: 1, end: 49 }, } ], - loc: loc(1, 49), + loc: { start: 0, end: 49 }, }; expect(printJson(doc)).to.equal(printJson(expected)); }); @@ -422,40 +401,39 @@ type Hello { world(argOne: Boolean, argTwo: Int): String }`; const doc = parse(body); - const loc = createLocFn(body); const expected = { kind: 'Document', definitions: [ { kind: 'ObjectTypeDefinition', - name: nameNode('Hello', loc(6, 11)), + name: nameNode('Hello', { start: 6, end: 11 }), interfaces: [], directives: [], fields: [ fieldNodeWithArgs( - nameNode('world', loc(16, 21)), - typeNode('String', loc(53, 59)), + nameNode('world', { start: 16, end: 21 }), + typeNode('String', { start: 53, end: 59 }), [ inputValueNode( - nameNode('argOne', loc(22, 28)), - typeNode('Boolean', loc(30, 37)), + nameNode('argOne', { start: 22, end: 28 }), + typeNode('Boolean', { start: 30, end: 37 }), null, - loc(22, 37) + { start: 22, end: 37 } ), inputValueNode( - nameNode('argTwo', loc(39, 45)), - typeNode('Int', loc(47, 50)), + nameNode('argTwo', { start: 39, end: 45 }), + typeNode('Int', { start: 47, end: 50 }), null, - loc(39, 50) + { start: 39, end: 50 } ), ], - loc(16, 59) + { start: 16, end: 59 } ) ], - loc: loc(1, 61), + loc: { start: 1, end: 61 }, } ], - loc: loc(1, 61), + loc: { start: 0, end: 61 }, }; expect(printJson(doc)).to.equal(printJson(expected)); }); @@ -463,19 +441,18 @@ type Hello { it('Simple union', () => { const body = 'union Hello = World'; const doc = parse(body); - const loc = createLocFn(body); const expected = { kind: 'Document', definitions: [ { kind: 'UnionTypeDefinition', - name: nameNode('Hello', loc(6, 11)), + name: nameNode('Hello', { start: 6, end: 11 }), directives: [], - types: [ typeNode('World', loc(14, 19)) ], - loc: loc(0, 19), + types: [ typeNode('World', { start: 14, end: 19 }) ], + loc: { start: 0, end: 19 }, } ], - loc: loc(0, 19), + loc: { start: 0, end: 19 }, }; expect(printJson(doc)).to.equal(printJson(expected)); }); @@ -483,22 +460,21 @@ type Hello { it('Union with two types', () => { const body = 'union Hello = Wo | Rld'; const doc = parse(body); - const loc = createLocFn(body); const expected = { kind: 'Document', definitions: [ { kind: 'UnionTypeDefinition', - name: nameNode('Hello', loc(6, 11)), + name: nameNode('Hello', { start: 6, end: 11 }), directives: [], types: [ - typeNode('Wo', loc(14, 16)), - typeNode('Rld', loc(19, 22)), + typeNode('Wo', { start: 14, end: 16 }), + typeNode('Rld', { start: 19, end: 22 }), ], - loc: loc(0, 22), + loc: { start: 0, end: 22 }, } ], - loc: loc(0, 22), + loc: { start: 0, end: 22 }, }; expect(printJson(doc)).to.equal(printJson(expected)); }); @@ -506,18 +482,17 @@ type Hello { it('Scalar', () => { const body = 'scalar Hello'; const doc = parse(body); - const loc = createLocFn(body); const expected = { kind: 'Document', definitions: [ { kind: 'ScalarTypeDefinition', - name: nameNode('Hello', loc(7, 12)), + name: nameNode('Hello', { start: 7, end: 12 }), directives: [], - loc: loc(0, 12), + loc: { start: 0, end: 12 }, } ], - loc: loc(0, 12), + loc: { start: 0, end: 12 }, }; expect(printJson(doc)).to.equal(printJson(expected)); }); @@ -528,26 +503,25 @@ input Hello { world: String }`; const doc = parse(body); - const loc = createLocFn(body); const expected = { kind: 'Document', definitions: [ { kind: 'InputObjectTypeDefinition', - name: nameNode('Hello', loc(7, 12)), + name: nameNode('Hello', { start: 7, end: 12 }), directives: [], fields: [ inputValueNode( - nameNode('world', loc(17, 22)), - typeNode('String', loc(24, 30)), + nameNode('world', { start: 17, end: 22 }), + typeNode('String', { start: 24, end: 30 }), null, - loc(17, 30) + { start: 17, end: 30 } ) ], - loc: loc(1, 32), + loc: { start: 1, end: 32 }, } ], - loc: loc(1, 32), + loc: { start: 0, end: 32 }, }; expect(printJson(doc)).to.equal(printJson(expected)); }); diff --git a/src/language/__tests__/schema-printer-test.js b/src/language/__tests__/schema-printer-test.js index 0d36f219b8..29376f32c9 100644 --- a/src/language/__tests__/schema-printer-test.js +++ b/src/language/__tests__/schema-printer-test.js @@ -38,9 +38,9 @@ describe('Printer', () => { it('does not alter ast', () => { const ast = parse(kitchenSink); - const astCopy = JSON.parse(JSON.stringify(ast)); + const astBefore = JSON.stringify(ast); print(ast); - expect(ast).to.deep.equal(astCopy); + expect(JSON.stringify(ast)).to.equal(astBefore); }); it('prints kitchen sink', () => { diff --git a/src/language/ast.js b/src/language/ast.js index 6c0056f12a..7477cdee02 100644 --- a/src/language/ast.js +++ b/src/language/ast.js @@ -12,14 +12,100 @@ import type { Source } from './source'; /** - * Contains a range of UTF-8 character offsets that identify - * the region of the source from which the AST derived. + * Contains a range of UTF-8 character offsets and token references that + * identify the region of the source from which the AST derived. */ export type Location = { + + /** + * The character offset at which this Node begins. + */ start: number; + + /** + * The character offset at which this Node ends. + */ end: number; - source?: ?Source -} + + /** + * The Token at which this Node begins. + */ + startToken: Token; + + /** + * The Token at which this Node ends. + */ + endToken: Token; + + /** + * The Source document the AST represents. + */ + source: Source; +}; + +/** + * Represents a range of characters represented by a lexical token + * within a Source. + */ +export type Token = { + + /** + * The kind of Token. + */ + kind: '' + | '' + | '!' + | '$' + | '(' + | ')' + | '...' + | ':' + | '=' + | '@' + | '[' + | ']' + | '{' + | '|' + | '}' + | 'Name' + | 'Int' + | 'Float' + | 'String' + | 'Comment'; + + /** + * The character offset at which this Node begins. + */ + start: number; + + /** + * The character offset at which this Node ends. + */ + end: number; + + /** + * The 1-indexed line number on which this Token appears. + */ + line: number; + + /** + * The 1-indexed column number at which this Token begins. + */ + column: number; + + /** + * For non-punctuation tokens, represents the interpreted value of the token. + */ + value: string | void; + + /** + * Tokens exist as nodes in a double-linked-list amongst all tokens + * including ignored tokens. is always the first node and + * the last. + */ + prev: Token | null; + next: Token | null; +}; /** * The list of all possible AST node types. @@ -65,7 +151,7 @@ export type Node = Name export type Name = { kind: 'Name'; - loc?: ?Location; + loc?: Location; value: string; } @@ -73,7 +159,7 @@ export type Name = { export type Document = { kind: 'Document'; - loc?: ?Location; + loc?: Location; definitions: Array; } @@ -83,7 +169,7 @@ export type Definition = OperationDefinition export type OperationDefinition = { kind: 'OperationDefinition'; - loc?: ?Location; + loc?: Location; operation: OperationType; name?: ?Name; variableDefinitions?: ?Array; @@ -96,7 +182,7 @@ export type OperationType = 'query' | 'mutation' | 'subscription'; export type VariableDefinition = { kind: 'VariableDefinition'; - loc?: ?Location; + loc?: Location; variable: Variable; type: Type; defaultValue?: ?Value; @@ -104,13 +190,13 @@ export type VariableDefinition = { export type Variable = { kind: 'Variable'; - loc?: ?Location; + loc?: Location; name: Name; } export type SelectionSet = { kind: 'SelectionSet'; - loc?: ?Location; + loc?: Location; selections: Array; } @@ -120,7 +206,7 @@ export type Selection = Field export type Field = { kind: 'Field'; - loc?: ?Location; + loc?: Location; alias?: ?Name; name: Name; arguments?: ?Array; @@ -130,7 +216,7 @@ export type Field = { export type Argument = { kind: 'Argument'; - loc?: ?Location; + loc?: Location; name: Name; value: Value; } @@ -140,14 +226,14 @@ export type Argument = { export type FragmentSpread = { kind: 'FragmentSpread'; - loc?: ?Location; + loc?: Location; name: Name; directives?: ?Array; } export type InlineFragment = { kind: 'InlineFragment'; - loc?: ?Location; + loc?: Location; typeCondition?: ?NamedType; directives?: ?Array; selectionSet: SelectionSet; @@ -155,7 +241,7 @@ export type InlineFragment = { export type FragmentDefinition = { kind: 'FragmentDefinition'; - loc?: ?Location; + loc?: Location; name: Name; typeCondition: NamedType; directives?: ?Array; @@ -176,49 +262,49 @@ export type Value = Variable export type IntValue = { kind: 'IntValue'; - loc?: ?Location; + loc?: Location; value: string; } export type FloatValue = { kind: 'FloatValue'; - loc?: ?Location; + loc?: Location; value: string; } export type StringValue = { kind: 'StringValue'; - loc?: ?Location; + loc?: Location; value: string; } export type BooleanValue = { kind: 'BooleanValue'; - loc?: ?Location; + loc?: Location; value: boolean; } export type EnumValue = { kind: 'EnumValue'; - loc?: ?Location; + loc?: Location; value: string; } export type ListValue = { kind: 'ListValue'; - loc?: ?Location; + loc?: Location; values: Array; } export type ObjectValue = { kind: 'ObjectValue'; - loc?: ?Location; + loc?: Location; fields: Array; } export type ObjectField = { kind: 'ObjectField'; - loc?: ?Location; + loc?: Location; name: Name; value: Value; } @@ -228,7 +314,7 @@ export type ObjectField = { export type Directive = { kind: 'Directive'; - loc?: ?Location; + loc?: Location; name: Name; arguments?: ?Array; } @@ -242,19 +328,19 @@ export type Type = NamedType export type NamedType = { kind: 'NamedType'; - loc?: ?Location; + loc?: Location; name: Name; }; export type ListType = { kind: 'ListType'; - loc?: ?Location; + loc?: Location; type: Type; } export type NonNullType = { kind: 'NonNullType'; - loc?: ?Location; + loc?: Location; type: NamedType | ListType; } @@ -267,14 +353,14 @@ export type TypeSystemDefinition = SchemaDefinition export type SchemaDefinition = { kind: 'SchemaDefinition'; - loc?: ?Location; + loc?: Location; directives: Array; operationTypes: Array; } export type OperationTypeDefinition = { kind: 'OperationTypeDefinition'; - loc?: ?Location; + loc?: Location; operation: OperationType; type: NamedType; } @@ -288,14 +374,14 @@ export type TypeDefinition = ScalarTypeDefinition export type ScalarTypeDefinition = { kind: 'ScalarTypeDefinition'; - loc?: ?Location; + loc?: Location; name: Name; directives?: ?Array; } export type ObjectTypeDefinition = { kind: 'ObjectTypeDefinition'; - loc?: ?Location; + loc?: Location; name: Name; interfaces?: ?Array; directives?: ?Array; @@ -304,7 +390,7 @@ export type ObjectTypeDefinition = { export type FieldDefinition = { kind: 'FieldDefinition'; - loc?: ?Location; + loc?: Location; name: Name; arguments: Array; type: Type; @@ -313,7 +399,7 @@ export type FieldDefinition = { export type InputValueDefinition = { kind: 'InputValueDefinition'; - loc?: ?Location; + loc?: Location; name: Name; type: Type; defaultValue?: ?Value; @@ -322,7 +408,7 @@ export type InputValueDefinition = { export type InterfaceTypeDefinition = { kind: 'InterfaceTypeDefinition'; - loc?: ?Location; + loc?: Location; name: Name; directives?: ?Array; fields: Array; @@ -330,7 +416,7 @@ export type InterfaceTypeDefinition = { export type UnionTypeDefinition = { kind: 'UnionTypeDefinition'; - loc?: ?Location; + loc?: Location; name: Name; directives?: ?Array; types: Array; @@ -338,7 +424,7 @@ export type UnionTypeDefinition = { export type EnumTypeDefinition = { kind: 'EnumTypeDefinition'; - loc?: ?Location; + loc?: Location; name: Name; directives?: ?Array; values: Array; @@ -346,14 +432,14 @@ export type EnumTypeDefinition = { export type EnumValueDefinition = { kind: 'EnumValueDefinition'; - loc?: ?Location; + loc?: Location; name: Name; directives?: ?Array; } export type InputObjectTypeDefinition = { kind: 'InputObjectTypeDefinition'; - loc?: ?Location; + loc?: Location; name: Name; directives?: ?Array; fields: Array; @@ -361,13 +447,13 @@ export type InputObjectTypeDefinition = { export type TypeExtensionDefinition = { kind: 'TypeExtensionDefinition'; - loc?: ?Location; + loc?: Location; definition: ObjectTypeDefinition; } export type DirectiveDefinition = { kind: 'DirectiveDefinition'; - loc?: ?Location; + loc?: Location; name: Name; arguments?: ?Array; locations: Array; diff --git a/src/language/index.js b/src/language/index.js index 2cda6ec294..48f0b5bb26 100644 --- a/src/language/index.js +++ b/src/language/index.js @@ -10,7 +10,7 @@ export { getLocation } from './location'; import * as Kind from './kinds'; export { Kind }; -export { lex } from './lexer'; +export { createLexer, TokenKind } from './lexer'; export { parse, parseValue } from './parser'; export { print } from './printer'; export { Source } from './source'; diff --git a/src/language/lexer.js b/src/language/lexer.js index ac70f21713..8fa2f54f58 100644 --- a/src/language/lexer.js +++ b/src/language/lexer.js @@ -8,67 +8,126 @@ * of patent rights can be found in the PATENTS file in the same directory. */ +import type { Token } from './ast'; import type { Source } from './source'; import { syntaxError } from '../error'; -/** - * A representation of a lexed Token. Value only appears for non-punctuation - * tokens: NAME, INT, FLOAT, and STRING. - */ -export type Token = { - kind: number; - start: number; - end: number; - value: ?string; -}; - -type Lexer = (resetPosition?: number) => Token; - /** * Given a Source object, this returns a Lexer for that source. - * A Lexer is a function that acts like a generator in that every time - * it is called, it returns the next token in the Source. Assuming the + * A Lexer is a stateful stream generator in that every time + * it is advanced, it returns the next token in the Source. Assuming the * source lexes, the final Token emitted by the lexer will be of kind - * EOF, after which the lexer will repeatedly return EOF tokens whenever - * called. - * - * The argument to the lexer function is optional, and can be used to - * rewind or fast forward the lexer to a new position in the source. + * EOF, after which the lexer will repeatedly return the same EOF token + * whenever called. */ -export function lex(source: Source): Lexer { - let prevPosition = 0; - return function nextToken(resetPosition) { - const token = readToken( - source, - resetPosition === undefined ? prevPosition : resetPosition - ); - prevPosition = token.end; - return token; +export function createLexer( + source: Source, + options: TOptions +): Lexer { + const startOfFileToken = new Tok(SOF, 0, 0, 0, 0, null); + const lexer: Lexer = { + source, + options, + lastToken: startOfFileToken, + token: startOfFileToken, + line: 1, + lineStart: 0, + advance: advanceLexer }; + return lexer; +} + +function advanceLexer() { + let token = this.lastToken = this.token; + if (token.kind !== EOF) { + do { + token = token.next = readToken(this, token); + } while (token.kind === COMMENT); + this.token = token; + } + return token; } /** - * An enum describing the different kinds of tokens that the lexer emits. + * The return type of createLexer. + */ +export type Lexer = { + source: Source; + options: TOptions; + + /** + * The previously focused non-ignored token. + */ + lastToken: Token; + + /** + * The currently focused non-ignored token. + */ + token: Token; + + /** + * The (1-indexed) line containing the current token. + */ + line: number; + + /** + * The character offset at which the current line begins. + */ + lineStart: number; + + /** + * Advances the token stream to the next non-ignored token. + */ + advance(): Token; +}; + +// Each kind of token. +const SOF = ''; +const EOF = ''; +const BANG = '!'; +const DOLLAR = '$'; +const PAREN_L = '('; +const PAREN_R = ')'; +const SPREAD = '...'; +const COLON = ':'; +const EQUALS = '='; +const AT = '@'; +const BRACKET_L = '['; +const BRACKET_R = ']'; +const BRACE_L = '{'; +const PIPE = '|'; +const BRACE_R = '}'; +const NAME = 'Name'; +const INT = 'Int'; +const FLOAT = 'Float'; +const STRING = 'String'; +const COMMENT = 'Comment'; + +/** + * An exported enum describing the different kinds of tokens that the + * lexer emits. */ export const TokenKind = { - EOF: 1, - BANG: 2, - DOLLAR: 3, - PAREN_L: 4, - PAREN_R: 5, - SPREAD: 6, - COLON: 7, - EQUALS: 8, - AT: 9, - BRACKET_L: 10, - BRACKET_R: 11, - BRACE_L: 12, - PIPE: 13, - BRACE_R: 14, - NAME: 15, - INT: 16, - FLOAT: 17, - STRING: 18, + SOF, + EOF, + BANG, + DOLLAR, + PAREN_L, + PAREN_R, + SPREAD, + COLON, + EQUALS, + AT, + BRACKET_L, + BRACKET_R, + BRACE_L, + PIPE, + BRACE_R, + NAME, + INT, + FLOAT, + STRING, + COMMENT }; /** @@ -76,57 +135,48 @@ export const TokenKind = { */ export function getTokenDesc(token: Token): string { const value = token.value; - return value ? - `${getTokenKindDesc(token.kind)} "${value}"` : - getTokenKindDesc(token.kind); -} - -/** - * A helper function to describe a token kind as a string for debugging - */ -export function getTokenKindDesc(kind: number): string { - return tokenDescription[kind]; + return value ? `${token.kind} "${value}"` : token.kind; } -const tokenDescription = {}; -tokenDescription[TokenKind.EOF] = 'EOF'; -tokenDescription[TokenKind.BANG] = '!'; -tokenDescription[TokenKind.DOLLAR] = '$'; -tokenDescription[TokenKind.PAREN_L] = '('; -tokenDescription[TokenKind.PAREN_R] = ')'; -tokenDescription[TokenKind.SPREAD] = '...'; -tokenDescription[TokenKind.COLON] = ':'; -tokenDescription[TokenKind.EQUALS] = '='; -tokenDescription[TokenKind.AT] = '@'; -tokenDescription[TokenKind.BRACKET_L] = '['; -tokenDescription[TokenKind.BRACKET_R] = ']'; -tokenDescription[TokenKind.BRACE_L] = '{'; -tokenDescription[TokenKind.PIPE] = '|'; -tokenDescription[TokenKind.BRACE_R] = '}'; -tokenDescription[TokenKind.NAME] = 'Name'; -tokenDescription[TokenKind.INT] = 'Int'; -tokenDescription[TokenKind.FLOAT] = 'Float'; -tokenDescription[TokenKind.STRING] = 'String'; - const charCodeAt = String.prototype.charCodeAt; const slice = String.prototype.slice; /** * Helper function for constructing the Token object. */ -function makeToken( - kind: number, +function Tok( + kind, start: number, end: number, + line: number, + column: number, + prev: Token | null, value?: string -): Token { - return { kind, start, end, value }; +) { + this.kind = kind; + this.start = start; + this.end = end; + this.line = line; + this.column = column; + this.value = value; + this.prev = prev; + this.next = null; } +// Print a simplified form when appearing in JSON/util.inspect. +Tok.prototype.toJSON = Tok.prototype.inspect = function toJSON() { + return { + kind: this.kind, + value: this.value, + line: this.line, + column: this.column + }; +}; + function printCharCode(code) { return ( // NaN/undefined represents access beyond the end of the file. - isNaN(code) ? '' : + isNaN(code) ? EOF : // Trust JSON for ASCII. code < 0x007F ? JSON.stringify(String.fromCharCode(code)) : // Otherwise print the escaped form. @@ -141,14 +191,17 @@ function printCharCode(code) { * token, then lexes punctuators immediately or calls the appropriate helper * function for more complicated tokens. */ -function readToken(source: Source, fromPosition: number): Token { +function readToken(lexer: Lexer<*>, prev: Token): Token { + const source = lexer.source; const body = source.body; const bodyLength = body.length; - const position = positionAfterWhitespace(body, fromPosition); + const position = positionAfterWhitespace(body, prev.end, lexer); + const line = lexer.line; + const col = 1 + position - lexer.lineStart; if (position >= bodyLength) { - return makeToken(TokenKind.EOF, position, position); + return new Tok(EOF, bodyLength, bodyLength, line, col, prev); } const code = charCodeAt.call(body, position); @@ -164,36 +217,42 @@ function readToken(source: Source, fromPosition: number): Token { switch (code) { // ! - case 33: return makeToken(TokenKind.BANG, position, position + 1); + case 33: return new Tok(BANG, position, position + 1, line, col, prev); + // # + case 35: return readComment(source, position, line, col, prev); // $ - case 36: return makeToken(TokenKind.DOLLAR, position, position + 1); + case 36: return new Tok(DOLLAR, position, position + 1, line, col, prev); // ( - case 40: return makeToken(TokenKind.PAREN_L, position, position + 1); + case 40: return new Tok(PAREN_L, position, position + 1, line, col, prev); // ) - case 41: return makeToken(TokenKind.PAREN_R, position, position + 1); + case 41: return new Tok(PAREN_R, position, position + 1, line, col, prev); // . case 46: if (charCodeAt.call(body, position + 1) === 46 && charCodeAt.call(body, position + 2) === 46) { - return makeToken(TokenKind.SPREAD, position, position + 3); + return new Tok(SPREAD, position, position + 3, line, col, prev); } break; // : - case 58: return makeToken(TokenKind.COLON, position, position + 1); + case 58: return new Tok(COLON, position, position + 1, line, col, prev); // = - case 61: return makeToken(TokenKind.EQUALS, position, position + 1); + case 61: return new Tok(EQUALS, position, position + 1, line, col, prev); // @ - case 64: return makeToken(TokenKind.AT, position, position + 1); + case 64: return new Tok(AT, position, position + 1, line, col, prev); // [ - case 91: return makeToken(TokenKind.BRACKET_L, position, position + 1); + case 91: + return new Tok(BRACKET_L, position, position + 1, line, col, prev); // ] - case 93: return makeToken(TokenKind.BRACKET_R, position, position + 1); + case 93: + return new Tok(BRACKET_R, position, position + 1, line, col, prev); // { - case 123: return makeToken(TokenKind.BRACE_L, position, position + 1); + case 123: + return new Tok(BRACE_L, position, position + 1, line, col, prev); // | - case 124: return makeToken(TokenKind.PIPE, position, position + 1); + case 124: return new Tok(PIPE, position, position + 1, line, col, prev); // } - case 125: return makeToken(TokenKind.BRACE_R, position, position + 1); + case 125: + return new Tok(BRACE_R, position, position + 1, line, col, prev); // A-Z _ a-z case 65: case 66: case 67: case 68: case 69: case 70: case 71: case 72: case 73: case 74: case 75: case 76: case 77: case 78: case 79: case 80: @@ -204,14 +263,14 @@ function readToken(source: Source, fromPosition: number): Token { case 105: case 106: case 107: case 108: case 109: case 110: case 111: case 112: case 113: case 114: case 115: case 116: case 117: case 118: case 119: case 120: case 121: case 122: - return readName(source, position); + return readName(source, position, line, col, prev); // - 0-9 case 45: case 48: case 49: case 50: case 51: case 52: case 53: case 54: case 55: case 56: case 57: - return readNumber(source, position, code); + return readNumber(source, position, code, line, col, prev); // " - case 34: return readString(source, position); + case 34: return readString(source, position, line, col, prev); } throw syntaxError( @@ -226,36 +285,30 @@ function readToken(source: Source, fromPosition: number): Token { * or commented character, then returns the position of that character for * lexing. */ -function positionAfterWhitespace(body: string, startPosition: number): number { +function positionAfterWhitespace( + body: string, + startPosition: number, + lexer: Lexer<*> +): number { const bodyLength = body.length; let position = startPosition; while (position < bodyLength) { - let code = charCodeAt.call(body, position); - // Skip Ignored - if ( - // BOM - code === 0xFEFF || - // White Space - code === 0x0009 || // tab - code === 0x0020 || // space - // Line Terminator - code === 0x000A || // new line - code === 0x000D || // carriage return - // Comma - code === 0x002C - ) { + const code = charCodeAt.call(body, position); + // tab | space | comma | BOM + if (code === 9 || code === 32 || code === 44 || code === 0xFEFF) { ++position; - // Skip comments - } else if (code === 35) { // # + } else if (code === 10) { // new line ++position; - while ( - position < bodyLength && - (code = charCodeAt.call(body, position)) !== null && - // SourceCharacter but not LineTerminator - (code > 0x001F || code === 0x0009) && code !== 0x000A && code !== 0x000D - ) { + ++lexer.line; + lexer.lineStart = position; + } else if (code === 13) { // carriage return + if (charCodeAt.call(body, position + 1) === 10) { + position += 2; + } else { ++position; } + ++lexer.line; + lexer.lineStart = position; } else { break; } @@ -263,6 +316,35 @@ function positionAfterWhitespace(body: string, startPosition: number): number { return position; } +/** + * Reads a comment token from the source file. + * + * #[\u0009\u0020-\uFFFF]* + */ +function readComment(source, start, line, col, prev): Token { + const body = source.body; + let code; + let position = start; + + do { + code = charCodeAt.call(body, ++position); + } while ( + code !== null && + // SourceCharacter but not LineTerminator + (code > 0x001F || code === 0x0009) + ); + + return new Tok( + COMMENT, + start, + position, + line, + col, + prev, + slice.call(body, start + 1, position) + ); +} + /** * Reads a number token from the source file, either a float * or an int depending on whether a decimal point appears. @@ -270,7 +352,7 @@ function positionAfterWhitespace(body: string, startPosition: number): number { * Int: -?(0|[1-9][0-9]*) * Float: -?(0|[1-9][0-9]*)(\.[0-9]+)?((E|e)(+|-)?[0-9]+)? */ -function readNumber(source, start, firstCode) { +function readNumber(source, start, firstCode, line, col, prev): Token { const body = source.body; let code = firstCode; let position = start; @@ -312,10 +394,13 @@ function readNumber(source, start, firstCode) { position = readDigits(source, position, code); } - return makeToken( - isFloat ? TokenKind.FLOAT : TokenKind.INT, + return new Tok( + isFloat ? FLOAT : INT, start, position, + line, + col, + prev, slice.call(body, start, position) ); } @@ -345,7 +430,7 @@ function readDigits(source, start, firstCode) { * * "([^"\\\u000A\u000D]|(\\(u[0-9a-fA-F]{4}|["\\/bfnrt])))*" */ -function readString(source, start) { +function readString(source, start, line, col, prev): Token { const body = source.body; let position = start + 1; let chunkStart = position; @@ -417,7 +502,7 @@ function readString(source, start) { } value += slice.call(body, chunkStart, position); - return makeToken(TokenKind.STRING, start, position + 1, value); + return new Tok(STRING, start, position + 1, line, col, prev, value); } /** @@ -456,7 +541,7 @@ function char2hex(a) { * * [_A-Za-z][_0-9A-Za-z]* */ -function readName(source, position) { +function readName(source, position, line, col, prev): Token { const body = source.body; const bodyLength = body.length; let end = position + 1; @@ -473,10 +558,13 @@ function readName(source, position) { ) { ++end; } - return makeToken( - TokenKind.NAME, + return new Tok( + NAME, position, end, + line, + col, + prev, slice.call(body, position, end) ); } diff --git a/src/language/parser.js b/src/language/parser.js index b8d8a137b9..7061a72d7e 100644 --- a/src/language/parser.js +++ b/src/language/parser.js @@ -11,9 +11,16 @@ import { Source } from './source'; import { syntaxError } from '../error'; import type { GraphQLError } from '../error'; -import { lex, TokenKind, getTokenKindDesc, getTokenDesc } from './lexer'; -import type { Token } from './lexer'; +import { + createLexer, + TokenKind, + getTokenDesc +} from './lexer'; +import type { Lexer } from './lexer'; import type { + Location, + Token, + Name, Variable, @@ -121,27 +128,20 @@ export type ParseOptions = { * in the source that they correspond to. This configuration flag * disables that behavior for performance or testing. */ - noLocation?: boolean, - - /** - * By default, the parser creates AST nodes that contain a reference - * to the source that they were created from. This configuration flag - * disables that behavior for performance or testing. - */ - noSource?: boolean, -} + noLocation?: boolean +}; /** * Given a GraphQL source, parses it into a Document. * Throws GraphQLError if a syntax error is encountered. */ export function parse( - source: Source | string, + source: string | Source, options?: ParseOptions ): Document { - const sourceObj = source instanceof Source ? source : new Source(source); - const parser = makeParser(sourceObj, options || {}); - return parseDocument(parser); + const sourceObj = typeof source === 'string' ? new Source(source) : source; + const lexer = createLexer(sourceObj, options || {}); + return parseDocument(lexer); } /** @@ -152,23 +152,26 @@ export function parse( * in isolation of complete GraphQL documents. */ export function parseValue( - source: Source | string, + source: string | Source, options?: ParseOptions ): Value { - const sourceObj = source instanceof Source ? source : new Source(source); - const parser = makeParser(sourceObj, options || {}); - return parseValueLiteral(parser, false); + const sourceObj = typeof source === 'string' ? new Source(source) : source; + const lexer = createLexer(sourceObj, options || {}); + expect(lexer, TokenKind.SOF); + const value = parseValueLiteral(lexer, false); + expect(lexer, TokenKind.EOF); + return value; } /** * Converts a name lex token into a name parse node. */ -function parseName(parser: Parser): Name { - const token = expect(parser, TokenKind.NAME); +function parseName(lexer: Lexer<*>): Name { + const token = expect(lexer, TokenKind.NAME); return { kind: NAME, value: ((token.value: any): string), - loc: loc(parser, token.start) + loc: loc(lexer, token) }; } @@ -177,18 +180,18 @@ function parseName(parser: Parser): Name { /** * Document : Definition+ */ -function parseDocument(parser: Parser): Document { - const start = parser.token.start; - +function parseDocument(lexer: Lexer<*>): Document { + const start = lexer.token; + expect(lexer, TokenKind.SOF); const definitions = []; do { - definitions.push(parseDefinition(parser)); - } while (!skip(parser, TokenKind.EOF)); + definitions.push(parseDefinition(lexer)); + } while (!skip(lexer, TokenKind.EOF)); return { kind: DOCUMENT, definitions, - loc: loc(parser, start) + loc: loc(lexer, start) }; } @@ -198,20 +201,20 @@ function parseDocument(parser: Parser): Document { * - FragmentDefinition * - TypeSystemDefinition */ -function parseDefinition(parser: Parser): Definition { - if (peek(parser, TokenKind.BRACE_L)) { - return parseOperationDefinition(parser); +function parseDefinition(lexer: Lexer<*>): Definition { + if (peek(lexer, TokenKind.BRACE_L)) { + return parseOperationDefinition(lexer); } - if (peek(parser, TokenKind.NAME)) { - switch (parser.token.value) { + if (peek(lexer, TokenKind.NAME)) { + switch (lexer.token.value) { // Note: subscription is an experimental non-spec addition. case 'query': case 'mutation': case 'subscription': - return parseOperationDefinition(parser); + return parseOperationDefinition(lexer); - case 'fragment': return parseFragmentDefinition(parser); + case 'fragment': return parseFragmentDefinition(lexer); // Note: the Type System IDL is an experimental non-spec addition. case 'schema': @@ -222,11 +225,11 @@ function parseDefinition(parser: Parser): Definition { case 'enum': case 'input': case 'extend': - case 'directive': return parseTypeSystemDefinition(parser); + case 'directive': return parseTypeSystemDefinition(lexer); } } - throw unexpected(parser); + throw unexpected(lexer); } @@ -237,40 +240,40 @@ function parseDefinition(parser: Parser): Definition { * - SelectionSet * - OperationType Name? VariableDefinitions? Directives? SelectionSet */ -function parseOperationDefinition(parser: Parser): OperationDefinition { - const start = parser.token.start; - if (peek(parser, TokenKind.BRACE_L)) { +function parseOperationDefinition(lexer: Lexer<*>): OperationDefinition { + const start = lexer.token; + if (peek(lexer, TokenKind.BRACE_L)) { return { kind: OPERATION_DEFINITION, operation: 'query', name: null, variableDefinitions: null, directives: [], - selectionSet: parseSelectionSet(parser), - loc: loc(parser, start) + selectionSet: parseSelectionSet(lexer), + loc: loc(lexer, start) }; } - const operation = parseOperationType(parser); + const operation = parseOperationType(lexer); let name; - if (peek(parser, TokenKind.NAME)) { - name = parseName(parser); + if (peek(lexer, TokenKind.NAME)) { + name = parseName(lexer); } return { kind: OPERATION_DEFINITION, operation, name, - variableDefinitions: parseVariableDefinitions(parser), - directives: parseDirectives(parser), - selectionSet: parseSelectionSet(parser), - loc: loc(parser, start) + variableDefinitions: parseVariableDefinitions(lexer), + directives: parseDirectives(lexer), + selectionSet: parseSelectionSet(lexer), + loc: loc(lexer, start) }; } /** * OperationType : one of query mutation subscription */ -function parseOperationType(parser: Parser): OperationType { - const operationToken = expect(parser, TokenKind.NAME); +function parseOperationType(lexer: Lexer<*>): OperationType { + const operationToken = expect(lexer, TokenKind.NAME); switch (operationToken.value) { case 'query': return 'query'; case 'mutation': return 'mutation'; @@ -278,16 +281,16 @@ function parseOperationType(parser: Parser): OperationType { case 'subscription': return 'subscription'; } - throw unexpected(parser, operationToken); + throw unexpected(lexer, operationToken); } /** * VariableDefinitions : ( VariableDefinition+ ) */ -function parseVariableDefinitions(parser: Parser): Array { - return peek(parser, TokenKind.PAREN_L) ? +function parseVariableDefinitions(lexer: Lexer<*>): Array { + return peek(lexer, TokenKind.PAREN_L) ? many( - parser, + lexer, TokenKind.PAREN_L, parseVariableDefinition, TokenKind.PAREN_R @@ -298,41 +301,41 @@ function parseVariableDefinitions(parser: Parser): Array { /** * VariableDefinition : Variable : Type DefaultValue? */ -function parseVariableDefinition(parser: Parser): VariableDefinition { - const start = parser.token.start; +function parseVariableDefinition(lexer: Lexer<*>): VariableDefinition { + const start = lexer.token; return { kind: VARIABLE_DEFINITION, - variable: parseVariable(parser), - type: (expect(parser, TokenKind.COLON), parseType(parser)), + variable: parseVariable(lexer), + type: (expect(lexer, TokenKind.COLON), parseType(lexer)), defaultValue: - skip(parser, TokenKind.EQUALS) ? parseValueLiteral(parser, true) : null, - loc: loc(parser, start) + skip(lexer, TokenKind.EQUALS) ? parseValueLiteral(lexer, true) : null, + loc: loc(lexer, start) }; } /** * Variable : $ Name */ -function parseVariable(parser: Parser): Variable { - const start = parser.token.start; - expect(parser, TokenKind.DOLLAR); +function parseVariable(lexer: Lexer<*>): Variable { + const start = lexer.token; + expect(lexer, TokenKind.DOLLAR); return { kind: VARIABLE, - name: parseName(parser), - loc: loc(parser, start) + name: parseName(lexer), + loc: loc(lexer, start) }; } /** * SelectionSet : { Selection+ } */ -function parseSelectionSet(parser: Parser): SelectionSet { - const start = parser.token.start; +function parseSelectionSet(lexer: Lexer<*>): SelectionSet { + const start = lexer.token; return { kind: SELECTION_SET, selections: - many(parser, TokenKind.BRACE_L, parseSelection, TokenKind.BRACE_R), - loc: loc(parser, start) + many(lexer, TokenKind.BRACE_L, parseSelection, TokenKind.BRACE_R), + loc: loc(lexer, start) }; } @@ -342,10 +345,10 @@ function parseSelectionSet(parser: Parser): SelectionSet { * - FragmentSpread * - InlineFragment */ -function parseSelection(parser: Parser): Selection { - return peek(parser, TokenKind.SPREAD) ? - parseFragment(parser) : - parseField(parser); +function parseSelection(lexer: Lexer<*>): Selection { + return peek(lexer, TokenKind.SPREAD) ? + parseFragment(lexer) : + parseField(lexer); } /** @@ -353,15 +356,15 @@ function parseSelection(parser: Parser): Selection { * * Alias : Name : */ -function parseField(parser: Parser): Field { - const start = parser.token.start; +function parseField(lexer: Lexer<*>): Field { + const start = lexer.token; - const nameOrAlias = parseName(parser); + const nameOrAlias = parseName(lexer); let alias; let name; - if (skip(parser, TokenKind.COLON)) { + if (skip(lexer, TokenKind.COLON)) { alias = nameOrAlias; - name = parseName(parser); + name = parseName(lexer); } else { alias = null; name = nameOrAlias; @@ -371,33 +374,33 @@ function parseField(parser: Parser): Field { kind: FIELD, alias, name, - arguments: parseArguments(parser), - directives: parseDirectives(parser), + arguments: parseArguments(lexer), + directives: parseDirectives(lexer), selectionSet: - peek(parser, TokenKind.BRACE_L) ? parseSelectionSet(parser) : null, - loc: loc(parser, start) + peek(lexer, TokenKind.BRACE_L) ? parseSelectionSet(lexer) : null, + loc: loc(lexer, start) }; } /** * Arguments : ( Argument+ ) */ -function parseArguments(parser: Parser): Array { - return peek(parser, TokenKind.PAREN_L) ? - many(parser, TokenKind.PAREN_L, parseArgument, TokenKind.PAREN_R) : +function parseArguments(lexer: Lexer<*>): Array { + return peek(lexer, TokenKind.PAREN_L) ? + many(lexer, TokenKind.PAREN_L, parseArgument, TokenKind.PAREN_R) : []; } /** * Argument : Name : Value */ -function parseArgument(parser: Parser): Argument { - const start = parser.token.start; +function parseArgument(lexer: Lexer<*>): Argument { + const start = lexer.token; return { kind: ARGUMENT, - name: parseName(parser), - value: (expect(parser, TokenKind.COLON), parseValueLiteral(parser, false)), - loc: loc(parser, start) + name: parseName(lexer), + value: (expect(lexer, TokenKind.COLON), parseValueLiteral(lexer, false)), + loc: loc(lexer, start) }; } @@ -411,28 +414,28 @@ function parseArgument(parser: Parser): Argument { * * InlineFragment : ... TypeCondition? Directives? SelectionSet */ -function parseFragment(parser: Parser): FragmentSpread | InlineFragment { - const start = parser.token.start; - expect(parser, TokenKind.SPREAD); - if (peek(parser, TokenKind.NAME) && parser.token.value !== 'on') { +function parseFragment(lexer: Lexer<*>): FragmentSpread | InlineFragment { + const start = lexer.token; + expect(lexer, TokenKind.SPREAD); + if (peek(lexer, TokenKind.NAME) && lexer.token.value !== 'on') { return { kind: FRAGMENT_SPREAD, - name: parseFragmentName(parser), - directives: parseDirectives(parser), - loc: loc(parser, start) + name: parseFragmentName(lexer), + directives: parseDirectives(lexer), + loc: loc(lexer, start) }; } let typeCondition = null; - if (parser.token.value === 'on') { - advance(parser); - typeCondition = parseNamedType(parser); + if (lexer.token.value === 'on') { + lexer.advance(); + typeCondition = parseNamedType(lexer); } return { kind: INLINE_FRAGMENT, typeCondition, - directives: parseDirectives(parser), - selectionSet: parseSelectionSet(parser), - loc: loc(parser, start) + directives: parseDirectives(lexer), + selectionSet: parseSelectionSet(lexer), + loc: loc(lexer, start) }; } @@ -442,27 +445,27 @@ function parseFragment(parser: Parser): FragmentSpread | InlineFragment { * * TypeCondition : NamedType */ -function parseFragmentDefinition(parser: Parser): FragmentDefinition { - const start = parser.token.start; - expectKeyword(parser, 'fragment'); +function parseFragmentDefinition(lexer: Lexer<*>): FragmentDefinition { + const start = lexer.token; + expectKeyword(lexer, 'fragment'); return { kind: FRAGMENT_DEFINITION, - name: parseFragmentName(parser), - typeCondition: (expectKeyword(parser, 'on'), parseNamedType(parser)), - directives: parseDirectives(parser), - selectionSet: parseSelectionSet(parser), - loc: loc(parser, start) + name: parseFragmentName(lexer), + typeCondition: (expectKeyword(lexer, 'on'), parseNamedType(lexer)), + directives: parseDirectives(lexer), + selectionSet: parseSelectionSet(lexer), + loc: loc(lexer, start) }; } /** * FragmentName : Name but not `on` */ -function parseFragmentName(parser: Parser): Name { - if (parser.token.value === 'on') { - throw unexpected(parser); +function parseFragmentName(lexer: Lexer<*>): Name { + if (lexer.token.value === 'on') { + throw unexpected(lexer); } - return parseName(parser); + return parseName(lexer); } @@ -483,66 +486,66 @@ function parseFragmentName(parser: Parser): Name { * * EnumValue : Name but not `true`, `false` or `null` */ -function parseValueLiteral(parser: Parser, isConst: boolean): Value { - const token = parser.token; +function parseValueLiteral(lexer: Lexer<*>, isConst: boolean): Value { + const token = lexer.token; switch (token.kind) { case TokenKind.BRACKET_L: - return parseList(parser, isConst); + return parseList(lexer, isConst); case TokenKind.BRACE_L: - return parseObject(parser, isConst); + return parseObject(lexer, isConst); case TokenKind.INT: - advance(parser); + lexer.advance(); return { kind: (INT: 'IntValue'), value: ((token.value: any): string), - loc: loc(parser, token.start) + loc: loc(lexer, token) }; case TokenKind.FLOAT: - advance(parser); + lexer.advance(); return { kind: (FLOAT: 'FloatValue'), value: ((token.value: any): string), - loc: loc(parser, token.start) + loc: loc(lexer, token) }; case TokenKind.STRING: - advance(parser); + lexer.advance(); return { kind: (STRING: 'StringValue'), value: ((token.value: any): string), - loc: loc(parser, token.start) + loc: loc(lexer, token) }; case TokenKind.NAME: if (token.value === 'true' || token.value === 'false') { - advance(parser); + lexer.advance(); return { kind: (BOOLEAN: 'BooleanValue'), value: token.value === 'true', - loc: loc(parser, token.start) + loc: loc(lexer, token) }; } else if (token.value !== 'null') { - advance(parser); + lexer.advance(); return { kind: (ENUM: 'EnumValue'), value: ((token.value: any): string), - loc: loc(parser, token.start) + loc: loc(lexer, token) }; } break; case TokenKind.DOLLAR: if (!isConst) { - return parseVariable(parser); + return parseVariable(lexer); } break; } - throw unexpected(parser); + throw unexpected(lexer); } -export function parseConstValue(parser: Parser): Value { - return parseValueLiteral(parser, true); +export function parseConstValue(lexer: Lexer<*>): Value { + return parseValueLiteral(lexer, true); } -function parseValueValue(parser: Parser): Value { - return parseValueLiteral(parser, false); +function parseValueValue(lexer: Lexer<*>): Value { + return parseValueLiteral(lexer, false); } /** @@ -550,13 +553,13 @@ function parseValueValue(parser: Parser): Value { * - [ ] * - [ Value[?Const]+ ] */ -function parseList(parser: Parser, isConst: boolean): ListValue { - const start = parser.token.start; +function parseList(lexer: Lexer<*>, isConst: boolean): ListValue { + const start = lexer.token; const item = isConst ? parseConstValue : parseValueValue; return { kind: LIST, - values: any(parser, TokenKind.BRACKET_L, item, TokenKind.BRACKET_R), - loc: loc(parser, start) + values: any(lexer, TokenKind.BRACKET_L, item, TokenKind.BRACKET_R), + loc: loc(lexer, start) }; } @@ -565,31 +568,31 @@ function parseList(parser: Parser, isConst: boolean): ListValue { * - { } * - { ObjectField[?Const]+ } */ -function parseObject(parser: Parser, isConst: boolean): ObjectValue { - const start = parser.token.start; - expect(parser, TokenKind.BRACE_L); +function parseObject(lexer: Lexer<*>, isConst: boolean): ObjectValue { + const start = lexer.token; + expect(lexer, TokenKind.BRACE_L); const fields = []; - while (!skip(parser, TokenKind.BRACE_R)) { - fields.push(parseObjectField(parser, isConst)); + while (!skip(lexer, TokenKind.BRACE_R)) { + fields.push(parseObjectField(lexer, isConst)); } return { kind: OBJECT, fields, - loc: loc(parser, start) + loc: loc(lexer, start) }; } /** * ObjectField[Const] : Name : Value[?Const] */ -function parseObjectField(parser: Parser, isConst: boolean): ObjectField { - const start = parser.token.start; +function parseObjectField(lexer: Lexer<*>, isConst: boolean): ObjectField { + const start = lexer.token; return { kind: OBJECT_FIELD, - name: parseName(parser), + name: parseName(lexer), value: - (expect(parser, TokenKind.COLON), parseValueLiteral(parser, isConst)), - loc: loc(parser, start) + (expect(lexer, TokenKind.COLON), parseValueLiteral(lexer, isConst)), + loc: loc(lexer, start) }; } @@ -599,10 +602,10 @@ function parseObjectField(parser: Parser, isConst: boolean): ObjectField { /** * Directives : Directive+ */ -function parseDirectives(parser: Parser): Array { +function parseDirectives(lexer: Lexer<*>): Array { const directives = []; - while (peek(parser, TokenKind.AT)) { - directives.push(parseDirective(parser)); + while (peek(lexer, TokenKind.AT)) { + directives.push(parseDirective(lexer)); } return directives; } @@ -610,14 +613,14 @@ function parseDirectives(parser: Parser): Array { /** * Directive : @ Name Arguments? */ -function parseDirective(parser: Parser): Directive { - const start = parser.token.start; - expect(parser, TokenKind.AT); +function parseDirective(lexer: Lexer<*>): Directive { + const start = lexer.token; + expect(lexer, TokenKind.AT); return { kind: DIRECTIVE, - name: parseName(parser), - arguments: parseArguments(parser), - loc: loc(parser, start) + name: parseName(lexer), + arguments: parseArguments(lexer), + loc: loc(lexer, start) }; } @@ -630,25 +633,25 @@ function parseDirective(parser: Parser): Directive { * - ListType * - NonNullType */ -export function parseType(parser: Parser): Type { - const start = parser.token.start; +export function parseType(lexer: Lexer<*>): Type { + const start = lexer.token; let type; - if (skip(parser, TokenKind.BRACKET_L)) { - type = parseType(parser); - expect(parser, TokenKind.BRACKET_R); + if (skip(lexer, TokenKind.BRACKET_L)) { + type = parseType(lexer); + expect(lexer, TokenKind.BRACKET_R); type = ({ kind: LIST_TYPE, type, - loc: loc(parser, start) + loc: loc(lexer, start) }: ListType); } else { - type = parseNamedType(parser); + type = parseNamedType(lexer); } - if (skip(parser, TokenKind.BANG)) { + if (skip(lexer, TokenKind.BANG)) { return ({ kind: NON_NULL_TYPE, type, - loc: loc(parser, start) + loc: loc(lexer, start) }: NonNullType); } return type; @@ -657,12 +660,12 @@ export function parseType(parser: Parser): Type { /** * NamedType : Name */ -export function parseNamedType(parser: Parser): NamedType { - const start = parser.token.start; +export function parseNamedType(lexer: Lexer<*>): NamedType { + const start = lexer.token; return { kind: NAMED_TYPE, - name: parseName(parser), - loc: loc(parser, start) + name: parseName(lexer), + loc: loc(lexer, start) }; } @@ -684,22 +687,22 @@ export function parseNamedType(parser: Parser): NamedType { * - EnumTypeDefinition * - InputObjectTypeDefinition */ -function parseTypeSystemDefinition(parser: Parser): TypeSystemDefinition { - if (peek(parser, TokenKind.NAME)) { - switch (parser.token.value) { - case 'schema': return parseSchemaDefinition(parser); - case 'scalar': return parseScalarTypeDefinition(parser); - case 'type': return parseObjectTypeDefinition(parser); - case 'interface': return parseInterfaceTypeDefinition(parser); - case 'union': return parseUnionTypeDefinition(parser); - case 'enum': return parseEnumTypeDefinition(parser); - case 'input': return parseInputObjectTypeDefinition(parser); - case 'extend': return parseTypeExtensionDefinition(parser); - case 'directive': return parseDirectiveDefinition(parser); +function parseTypeSystemDefinition(lexer: Lexer<*>): TypeSystemDefinition { + if (peek(lexer, TokenKind.NAME)) { + switch (lexer.token.value) { + case 'schema': return parseSchemaDefinition(lexer); + case 'scalar': return parseScalarTypeDefinition(lexer); + case 'type': return parseObjectTypeDefinition(lexer); + case 'interface': return parseInterfaceTypeDefinition(lexer); + case 'union': return parseUnionTypeDefinition(lexer); + case 'enum': return parseEnumTypeDefinition(lexer); + case 'input': return parseInputObjectTypeDefinition(lexer); + case 'extend': return parseTypeExtensionDefinition(lexer); + case 'directive': return parseDirectiveDefinition(lexer); } } - throw unexpected(parser); + throw unexpected(lexer); } /** @@ -707,12 +710,12 @@ function parseTypeSystemDefinition(parser: Parser): TypeSystemDefinition { * * OperationTypeDefinition : OperationType : NamedType */ -function parseSchemaDefinition(parser: Parser): SchemaDefinition { - const start = parser.token.start; - expectKeyword(parser, 'schema'); - const directives = parseDirectives(parser); +function parseSchemaDefinition(lexer: Lexer<*>): SchemaDefinition { + const start = lexer.token; + expectKeyword(lexer, 'schema'); + const directives = parseDirectives(lexer); const operationTypes = many( - parser, + lexer, TokenKind.BRACE_L, parseOperationTypeDefinition, TokenKind.BRACE_R @@ -721,36 +724,38 @@ function parseSchemaDefinition(parser: Parser): SchemaDefinition { kind: SCHEMA_DEFINITION, directives, operationTypes, - loc: loc(parser, start), + loc: loc(lexer, start), }; } -function parseOperationTypeDefinition(parser: Parser): OperationTypeDefinition { - const start = parser.token.start; - const operation = parseOperationType(parser); - expect(parser, TokenKind.COLON); - const type = parseNamedType(parser); +function parseOperationTypeDefinition( + lexer: Lexer<*> +): OperationTypeDefinition { + const start = lexer.token; + const operation = parseOperationType(lexer); + expect(lexer, TokenKind.COLON); + const type = parseNamedType(lexer); return { kind: OPERATION_TYPE_DEFINITION, operation, type, - loc: loc(parser, start), + loc: loc(lexer, start), }; } /** * ScalarTypeDefinition : scalar Name Directives? */ -function parseScalarTypeDefinition(parser: Parser): ScalarTypeDefinition { - const start = parser.token.start; - expectKeyword(parser, 'scalar'); - const name = parseName(parser); - const directives = parseDirectives(parser); +function parseScalarTypeDefinition(lexer: Lexer<*>): ScalarTypeDefinition { + const start = lexer.token; + expectKeyword(lexer, 'scalar'); + const name = parseName(lexer); + const directives = parseDirectives(lexer); return { kind: SCALAR_TYPE_DEFINITION, name, directives, - loc: loc(parser, start), + loc: loc(lexer, start), }; } @@ -758,14 +763,14 @@ function parseScalarTypeDefinition(parser: Parser): ScalarTypeDefinition { * ObjectTypeDefinition : * - type Name ImplementsInterfaces? Directives? { FieldDefinition+ } */ -function parseObjectTypeDefinition(parser: Parser): ObjectTypeDefinition { - const start = parser.token.start; - expectKeyword(parser, 'type'); - const name = parseName(parser); - const interfaces = parseImplementsInterfaces(parser); - const directives = parseDirectives(parser); +function parseObjectTypeDefinition(lexer: Lexer<*>): ObjectTypeDefinition { + const start = lexer.token; + expectKeyword(lexer, 'type'); + const name = parseName(lexer); + const interfaces = parseImplementsInterfaces(lexer); + const directives = parseDirectives(lexer); const fields = any( - parser, + lexer, TokenKind.BRACE_L, parseFieldDefinition, TokenKind.BRACE_R @@ -776,20 +781,20 @@ function parseObjectTypeDefinition(parser: Parser): ObjectTypeDefinition { interfaces, directives, fields, - loc: loc(parser, start), + loc: loc(lexer, start), }; } /** * ImplementsInterfaces : implements NamedType+ */ -function parseImplementsInterfaces(parser: Parser): Array { +function parseImplementsInterfaces(lexer: Lexer<*>): Array { const types = []; - if (parser.token.value === 'implements') { - advance(parser); + if (lexer.token.value === 'implements') { + lexer.advance(); do { - types.push(parseNamedType(parser)); - } while (peek(parser, TokenKind.NAME)); + types.push(parseNamedType(lexer)); + } while (peek(lexer, TokenKind.NAME)); } return types; } @@ -797,66 +802,68 @@ function parseImplementsInterfaces(parser: Parser): Array { /** * FieldDefinition : Name ArgumentsDefinition? : Type Directives? */ -function parseFieldDefinition(parser: Parser): FieldDefinition { - const start = parser.token.start; - const name = parseName(parser); - const args = parseArgumentDefs(parser); - expect(parser, TokenKind.COLON); - const type = parseType(parser); - const directives = parseDirectives(parser); +function parseFieldDefinition(lexer: Lexer<*>): FieldDefinition { + const start = lexer.token; + const name = parseName(lexer); + const args = parseArgumentDefs(lexer); + expect(lexer, TokenKind.COLON); + const type = parseType(lexer); + const directives = parseDirectives(lexer); return { kind: FIELD_DEFINITION, name, arguments: args, type, directives, - loc: loc(parser, start), + loc: loc(lexer, start), }; } /** * ArgumentsDefinition : ( InputValueDefinition+ ) */ -function parseArgumentDefs(parser: Parser): Array { - if (!peek(parser, TokenKind.PAREN_L)) { +function parseArgumentDefs(lexer: Lexer<*>): Array { + if (!peek(lexer, TokenKind.PAREN_L)) { return []; } - return many(parser, TokenKind.PAREN_L, parseInputValueDef, TokenKind.PAREN_R); + return many(lexer, TokenKind.PAREN_L, parseInputValueDef, TokenKind.PAREN_R); } /** * InputValueDefinition : Name : Type DefaultValue? Directives? */ -function parseInputValueDef(parser: Parser): InputValueDefinition { - const start = parser.token.start; - const name = parseName(parser); - expect(parser, TokenKind.COLON); - const type = parseType(parser); +function parseInputValueDef(lexer: Lexer<*>): InputValueDefinition { + const start = lexer.token; + const name = parseName(lexer); + expect(lexer, TokenKind.COLON); + const type = parseType(lexer); let defaultValue = null; - if (skip(parser, TokenKind.EQUALS)) { - defaultValue = parseConstValue(parser); + if (skip(lexer, TokenKind.EQUALS)) { + defaultValue = parseConstValue(lexer); } - const directives = parseDirectives(parser); + const directives = parseDirectives(lexer); return { kind: INPUT_VALUE_DEFINITION, name, type, defaultValue, directives, - loc: loc(parser, start), + loc: loc(lexer, start), }; } /** * InterfaceTypeDefinition : interface Name Directives? { FieldDefinition+ } */ -function parseInterfaceTypeDefinition(parser: Parser): InterfaceTypeDefinition { - const start = parser.token.start; - expectKeyword(parser, 'interface'); - const name = parseName(parser); - const directives = parseDirectives(parser); +function parseInterfaceTypeDefinition( + lexer: Lexer<*> +): InterfaceTypeDefinition { + const start = lexer.token; + expectKeyword(lexer, 'interface'); + const name = parseName(lexer); + const directives = parseDirectives(lexer); const fields = any( - parser, + lexer, TokenKind.BRACE_L, parseFieldDefinition, TokenKind.BRACE_R @@ -866,26 +873,26 @@ function parseInterfaceTypeDefinition(parser: Parser): InterfaceTypeDefinition { name, directives, fields, - loc: loc(parser, start), + loc: loc(lexer, start), }; } /** * UnionTypeDefinition : union Name Directives? = UnionMembers */ -function parseUnionTypeDefinition(parser: Parser): UnionTypeDefinition { - const start = parser.token.start; - expectKeyword(parser, 'union'); - const name = parseName(parser); - const directives = parseDirectives(parser); - expect(parser, TokenKind.EQUALS); - const types = parseUnionMembers(parser); +function parseUnionTypeDefinition(lexer: Lexer<*>): UnionTypeDefinition { + const start = lexer.token; + expectKeyword(lexer, 'union'); + const name = parseName(lexer); + const directives = parseDirectives(lexer); + expect(lexer, TokenKind.EQUALS); + const types = parseUnionMembers(lexer); return { kind: UNION_TYPE_DEFINITION, name, directives, types, - loc: loc(parser, start), + loc: loc(lexer, start), }; } @@ -894,24 +901,24 @@ function parseUnionTypeDefinition(parser: Parser): UnionTypeDefinition { * - NamedType * - UnionMembers | NamedType */ -function parseUnionMembers(parser: Parser): Array { +function parseUnionMembers(lexer: Lexer<*>): Array { const members = []; do { - members.push(parseNamedType(parser)); - } while (skip(parser, TokenKind.PIPE)); + members.push(parseNamedType(lexer)); + } while (skip(lexer, TokenKind.PIPE)); return members; } /** * EnumTypeDefinition : enum Name Directives? { EnumValueDefinition+ } */ -function parseEnumTypeDefinition(parser: Parser): EnumTypeDefinition { - const start = parser.token.start; - expectKeyword(parser, 'enum'); - const name = parseName(parser); - const directives = parseDirectives(parser); +function parseEnumTypeDefinition(lexer: Lexer<*>): EnumTypeDefinition { + const start = lexer.token; + expectKeyword(lexer, 'enum'); + const name = parseName(lexer); + const directives = parseDirectives(lexer); const values = many( - parser, + lexer, TokenKind.BRACE_L, parseEnumValueDefinition, TokenKind.BRACE_R @@ -921,7 +928,7 @@ function parseEnumTypeDefinition(parser: Parser): EnumTypeDefinition { name, directives, values, - loc: loc(parser, start), + loc: loc(lexer, start), }; } @@ -930,15 +937,15 @@ function parseEnumTypeDefinition(parser: Parser): EnumTypeDefinition { * * EnumValue : Name */ -function parseEnumValueDefinition(parser: Parser) : EnumValueDefinition { - const start = parser.token.start; - const name = parseName(parser); - const directives = parseDirectives(parser); +function parseEnumValueDefinition(lexer: Lexer<*>) : EnumValueDefinition { + const start = lexer.token; + const name = parseName(lexer); + const directives = parseDirectives(lexer); return { kind: ENUM_VALUE_DEFINITION, name, directives, - loc: loc(parser, start), + loc: loc(lexer, start), }; } @@ -946,14 +953,14 @@ function parseEnumValueDefinition(parser: Parser) : EnumValueDefinition { * InputObjectTypeDefinition : input Name Directives? { InputValueDefinition+ } */ function parseInputObjectTypeDefinition( - parser: Parser + lexer: Lexer<*> ): InputObjectTypeDefinition { - const start = parser.token.start; - expectKeyword(parser, 'input'); - const name = parseName(parser); - const directives = parseDirectives(parser); + const start = lexer.token; + expectKeyword(lexer, 'input'); + const name = parseName(lexer); + const directives = parseDirectives(lexer); const fields = any( - parser, + lexer, TokenKind.BRACE_L, parseInputValueDef, TokenKind.BRACE_R @@ -963,21 +970,23 @@ function parseInputObjectTypeDefinition( name, directives, fields, - loc: loc(parser, start), + loc: loc(lexer, start), }; } /** * TypeExtensionDefinition : extend ObjectTypeDefinition */ -function parseTypeExtensionDefinition(parser: Parser): TypeExtensionDefinition { - const start = parser.token.start; - expectKeyword(parser, 'extend'); - const definition = parseObjectTypeDefinition(parser); +function parseTypeExtensionDefinition( + lexer: Lexer<*> +): TypeExtensionDefinition { + const start = lexer.token; + expectKeyword(lexer, 'extend'); + const definition = parseObjectTypeDefinition(lexer); return { kind: TYPE_EXTENSION_DEFINITION, definition, - loc: loc(parser, start), + loc: loc(lexer, start), }; } @@ -985,20 +994,20 @@ function parseTypeExtensionDefinition(parser: Parser): TypeExtensionDefinition { * DirectiveDefinition : * - directive @ Name ArgumentsDefinition? on DirectiveLocations */ -function parseDirectiveDefinition(parser: Parser): DirectiveDefinition { - const start = parser.token.start; - expectKeyword(parser, 'directive'); - expect(parser, TokenKind.AT); - const name = parseName(parser); - const args = parseArgumentDefs(parser); - expectKeyword(parser, 'on'); - const locations = parseDirectiveLocations(parser); +function parseDirectiveDefinition(lexer: Lexer<*>): DirectiveDefinition { + const start = lexer.token; + expectKeyword(lexer, 'directive'); + expect(lexer, TokenKind.AT); + const name = parseName(lexer); + const args = parseArgumentDefs(lexer); + expectKeyword(lexer, 'on'); + const locations = parseDirectiveLocations(lexer); return { kind: DIRECTIVE_DEFINITION, name, arguments: args, locations, - loc: loc(parser, start) + loc: loc(lexer, start) }; } @@ -1007,111 +1016,88 @@ function parseDirectiveDefinition(parser: Parser): DirectiveDefinition { * - Name * - DirectiveLocations | Name */ -function parseDirectiveLocations(parser: Parser): Array { +function parseDirectiveLocations(lexer: Lexer<*>): Array { const locations = []; do { - locations.push(parseName(parser)); - } while (skip(parser, TokenKind.PIPE)); + locations.push(parseName(lexer)); + } while (skip(lexer, TokenKind.PIPE)); return locations; } // Core parsing utility functions -type Parser = { - source: Source, - options: ParseOptions, - prevEnd: number, - token: Token, - _lexToken: () => Token, -}; - -/** - * Returns the parser object that is used to store state throughout the - * process of parsing. - */ -function makeParser(source: Source, options: ParseOptions): Parser { - const _lexToken = lex(source); - return { - _lexToken, - source, - options, - prevEnd: 0, - token: _lexToken(), - }; -} - /** * Returns a location object, used to identify the place in * the source that created a given parsed object. */ -function loc(parser: Parser, start: number) { - if (parser.options.noLocation) { - return null; +function loc(lexer: Lexer<*>, startToken: Token): Location | void { + if (!lexer.options.noLocation) { + return new Loc(startToken, lexer.lastToken, lexer.source); } - if (parser.options.noSource) { - return { start, end: parser.prevEnd }; - } - return { start, end: parser.prevEnd, source: parser.source }; } -/** - * Moves the internal parser object to the next lexed token. - */ -function advance(parser: Parser): void { - const prevEnd = parser.token.end; - parser.prevEnd = prevEnd; - parser.token = parser._lexToken(prevEnd); +function Loc(startToken: Token, endToken: Token, source: Source) { + this.start = startToken.start; + this.end = endToken.end; + this.startToken = startToken; + this.endToken = endToken; + this.source = source; } +// Print a simplified form when appearing in JSON/util.inspect. +Loc.prototype.toJSON = Loc.prototype.inspect = function toJSON() { + return { start: this.start, end: this.end }; +}; + /** * Determines if the next token is of a given kind */ -function peek(parser: Parser, kind: number): boolean { - return parser.token.kind === kind; +function peek(lexer: Lexer<*>, kind: string): boolean { + return lexer.token.kind === kind; } /** * If the next token is of the given kind, return true after advancing - * the parser. Otherwise, do not change the parser state and return false. + * the lexer. Otherwise, do not change the parser state and return false. */ -function skip(parser: Parser, kind: number): boolean { - const match = parser.token.kind === kind; +function skip(lexer: Lexer<*>, kind: string): boolean { + const match = lexer.token.kind === kind; if (match) { - advance(parser); + lexer.advance(); } return match; } /** * If the next token is of the given kind, return that token after advancing - * the parser. Otherwise, do not change the parser state and throw an error. + * the lexer. Otherwise, do not change the parser state and throw an error. */ -function expect(parser: Parser, kind: number): Token { - const token = parser.token; +function expect(lexer: Lexer<*>, kind: string): Token { + const token = lexer.token; if (token.kind === kind) { - advance(parser); + lexer.advance(); return token; } throw syntaxError( - parser.source, + lexer.source, token.start, - `Expected ${getTokenKindDesc(kind)}, found ${getTokenDesc(token)}` + `Expected ${kind}, found ${getTokenDesc(token)}` ); } /** * If the next token is a keyword with the given value, return that token after - * advancing the parser. Otherwise, do not change the parser state and return + * advancing the lexer. Otherwise, do not change the parser state and return * false. */ -function expectKeyword(parser: Parser, value: string): Token { - const token = parser.token; +function expectKeyword(lexer: Lexer<*>, value: string): Token { + const token = lexer.token; if (token.kind === TokenKind.NAME && token.value === value) { - advance(parser); + lexer.advance(); return token; } throw syntaxError( - parser.source, + lexer.source, token.start, `Expected "${value}", found ${getTokenDesc(token)}` ); @@ -1121,10 +1107,10 @@ function expectKeyword(parser: Parser, value: string): Token { * Helper function for creating an error when an unexpected lexed token * is encountered. */ -function unexpected(parser: Parser, atToken?: ?Token): GraphQLError { - const token = atToken || parser.token; +function unexpected(lexer: Lexer<*>, atToken?: ?Token): GraphQLError { + const token = atToken || lexer.token; return syntaxError( - parser.source, + lexer.source, token.start, `Unexpected ${getTokenDesc(token)}` ); @@ -1137,15 +1123,15 @@ function unexpected(parser: Parser, atToken?: ?Token): GraphQLError { * to the next lex token after the closing token. */ function any( - parser: Parser, - openKind: number, - parseFn: (parser: Parser) => T, - closeKind: number + lexer: Lexer<*>, + openKind: string, + parseFn: (lexer: Lexer<*>) => T, + closeKind: string ): Array { - expect(parser, openKind); + expect(lexer, openKind); const nodes = []; - while (!skip(parser, closeKind)) { - nodes.push(parseFn(parser)); + while (!skip(lexer, closeKind)) { + nodes.push(parseFn(lexer)); } return nodes; } @@ -1157,15 +1143,15 @@ function any( * to the next lex token after the closing token. */ function many( - parser: Parser, - openKind: number, - parseFn: (parser: Parser) => T, - closeKind: number + lexer: Lexer<*>, + openKind: string, + parseFn: (lexer: Lexer<*>) => T, + closeKind: string ): Array { - expect(parser, openKind); - const nodes = [ parseFn(parser) ]; - while (!skip(parser, closeKind)) { - nodes.push(parseFn(parser)); + expect(lexer, openKind); + const nodes = [ parseFn(lexer) ]; + while (!skip(lexer, closeKind)) { + nodes.push(parseFn(lexer)); } return nodes; }