Skip to content

Commit

Permalink
Schema Definition Language Parser
Browse files Browse the repository at this point in the history
This is a parser for the ad-hoc type definition language included in the
spec. This should also serve as a de-facto spec for the language.
Clearly once this is stable we should include it in the actual spec as
well.

This should end up being useful in a few contexts, including
client-side types and also a DSL for producing the JS type definitions
which can be tedious to write, and a way to specify acceptance tests.
(This would be a clear next step for this project)
  • Loading branch information
schrockn-zz committed Jul 21, 2015
1 parent 33ed9aa commit 9d70ee9
Show file tree
Hide file tree
Showing 6 changed files with 1,109 additions and 215 deletions.
233 changes: 18 additions & 215 deletions src/language/parser.js
Expand Up @@ -9,8 +9,7 @@

import { Source } from './source';
import { syntaxError } from '../error';
import { lex, TokenKind, getTokenKindDesc, getTokenDesc } from './lexer';
import type { Token } from './lexer';
import { TokenKind } from './lexer';
import type {
Name,
Variable,
Expand All @@ -33,13 +32,9 @@ import type {
ObjectField,

Directive,

Type,
NamedType
} from './ast';

import {
NAME,
VARIABLE,

DOCUMENT,
Expand All @@ -63,16 +58,28 @@ import {
OBJECT_FIELD,

DIRECTIVE,

NAMED_TYPE,
LIST_TYPE,
NON_NULL_TYPE,
} from './kinds';

import {
makeParser,
peek,
skip,
loc,
any,
many,
parseName,
expect,
parseType,
parseNamedType,
unexpected,
expectKeyword,
advance,
} from './parserCore';

/**
* Configuration options to control parser behavior
*/
type ParseOptions = {
export type ParseOptions = {
/**
* By default, the parser creates AST nodes that know the location
* in the source that they correspond to. This configuration flag
Expand Down Expand Up @@ -101,171 +108,6 @@ export function parse(
return parseDocument(parser);
}

/**
* Returns the parser object that is used to store state throughout the
* process of parsing.
*/
function makeParser(source: Source, options: ParseOptions) {
var _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, start: number) {
if (parser.options.noLocation) {
return null;
}
if (parser.options.noSource) {
return {
start: start,
end: parser.prevEnd
};
}
return {
start: start,
end: parser.prevEnd,
source: parser.source
};
}

/**
* Moves the internal parser object to the next lexed token.
*/
function advance(parser): void {
var prevEnd = parser.token.end;
parser.prevEnd = prevEnd;
parser.token = parser._lexToken(prevEnd);
}

/**
* Determines if the next token is of a given kind
*/
function peek(parser, kind: string): boolean {
return parser.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.
*/
function skip(parser, kind: string): boolean {
var match = parser.token.kind === kind;
if (match) {
advance(parser);
}
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 return false.
*/
function expect(parser, kind: string): Token {
var token = parser.token;
if (token.kind === kind) {
advance(parser);
return token;
}
throw syntaxError(
parser.source,
token.start,
`Expected ${getTokenKindDesc(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
* false.
*/
function expectKeyword(parser, value: string): Token {
var token = parser.token;
if (token.kind === TokenKind.NAME && token.value === value) {
advance(parser);
return token;
}
throw syntaxError(
parser.source,
token.start,
`Expected "${value}", found ${getTokenDesc(token)}`
);
}

/**
* Helper function for creating an error when an unexpected lexed token
* is encountered.
*/
function unexpected(parser, atToken?: ?Token): Error {
var token = atToken || parser.token;
return syntaxError(
parser.source,
token.start,
`Unexpected ${getTokenDesc(token)}`
);
}

/**
* Returns a possibly empty list of parse nodes, determined by
* the parseFn. This list begins with a lex token of openKind
* and ends with a lex token of closeKind. Advances the parser
* to the next lex token after the closing token.
*/
function any<T>(
parser,
openKind: number,
parseFn: (parser: any) => T,
closeKind: number
): Array<T> {
expect(parser, openKind);
var nodes = [];
while (!skip(parser, closeKind)) {
nodes.push(parseFn(parser));
}
return nodes;
}

/**
* Returns a non-empty list of parse nodes, determined by
* the parseFn. This list begins with a lex token of openKind
* and ends with a lex token of closeKind. Advances the parser
* to the next lex token after the closing token.
*/
function many<T>(
parser,
openKind: number,
parseFn: (parser: any) => T,
closeKind: number
): Array<T> {
expect(parser, openKind);
var nodes = [parseFn(parser)];
while (!skip(parser, closeKind)) {
nodes.push(parseFn(parser));
}
return nodes;
}

/**
* Converts a name lex token into a name parse node.
*/
function parseName(parser): Name {
var token = expect(parser, TokenKind.NAME);
return {
kind: NAME,
value: token.value,
loc: loc(parser, token.start)
};
}


// Implements the parsing rules in the Document section.

function parseDocument(parser): Document {
Expand Down Expand Up @@ -596,42 +438,3 @@ function parseDirective(parser): Directive {
loc: loc(parser, start)
};
}


// Implements the parsing rules in the Types section.

/**
* Handles the Type: NamedType, ListType, and NonNullType parsing rules.
*/
function parseType(parser): Type {
var start = parser.token.start;
var type;
if (skip(parser, TokenKind.BRACKET_L)) {
type = parseType(parser);
expect(parser, TokenKind.BRACKET_R);
type = {
kind: LIST_TYPE,
type,
loc: loc(parser, start)
};
} else {
type = parseNamedType(parser);
}
if (skip(parser, TokenKind.BANG)) {
return {
kind: NON_NULL_TYPE,
type,
loc: loc(parser, start)
};
}
return type;
}

function parseNamedType(parser): NamedType {
var start = parser.token.start;
return {
kind: NAMED_TYPE,
name: parseName(parser),
loc: loc(parser, start)
};
}

0 comments on commit 9d70ee9

Please sign in to comment.