2017-02-05 23:08:03 +00:00
|
|
|
library graphql_parser.language.parser;
|
|
|
|
|
2017-01-22 23:15:53 +00:00
|
|
|
import 'ast/ast.dart';
|
2017-01-25 04:28:09 +00:00
|
|
|
import 'syntax_error.dart';
|
2017-01-22 23:15:53 +00:00
|
|
|
import 'token.dart';
|
2017-01-25 04:28:09 +00:00
|
|
|
import 'token_type.dart';
|
2017-01-22 23:15:53 +00:00
|
|
|
|
2017-07-03 15:37:35 +00:00
|
|
|
class Parser {
|
|
|
|
Token _current;
|
|
|
|
int _index = -1;
|
2017-01-22 23:15:53 +00:00
|
|
|
|
2017-07-03 15:37:35 +00:00
|
|
|
final List<Token> tokens;
|
2018-08-04 19:18:53 +00:00
|
|
|
final List<SyntaxError> errors = <SyntaxError>[];
|
2017-01-25 04:28:09 +00:00
|
|
|
|
2017-07-03 15:37:35 +00:00
|
|
|
Parser(this.tokens);
|
2017-01-25 04:28:09 +00:00
|
|
|
|
2017-07-03 15:37:35 +00:00
|
|
|
Token get current => _current;
|
2017-01-25 04:28:09 +00:00
|
|
|
|
2017-07-03 15:37:35 +00:00
|
|
|
bool next(TokenType type) {
|
|
|
|
if (peek()?.type == type) {
|
|
|
|
_current = tokens[++_index];
|
|
|
|
return true;
|
|
|
|
}
|
2017-01-25 04:28:09 +00:00
|
|
|
|
2017-07-03 15:37:35 +00:00
|
|
|
return false;
|
2017-01-22 23:15:53 +00:00
|
|
|
}
|
|
|
|
|
2019-03-31 20:07:25 +00:00
|
|
|
bool nextName(String name) {
|
|
|
|
var tok = peek();
|
2019-03-31 20:21:11 +00:00
|
|
|
|
2019-03-31 20:07:25 +00:00
|
|
|
if (tok?.type == TokenType.NAME && tok.span.text == name) {
|
|
|
|
return next(TokenType.NAME);
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-07-03 15:37:35 +00:00
|
|
|
Token peek() {
|
|
|
|
if (_index < tokens.length - 1) {
|
|
|
|
return tokens[_index + 1];
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
2017-01-25 04:28:09 +00:00
|
|
|
}
|
|
|
|
|
2017-07-04 15:58:22 +00:00
|
|
|
Token maybe(TokenType type) => next(type) ? current : null;
|
|
|
|
|
2018-08-04 19:18:53 +00:00
|
|
|
void eatCommas() {
|
|
|
|
while (next(TokenType.COMMA)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-04 17:32:52 +00:00
|
|
|
DocumentContext parseDocument() {
|
|
|
|
List<DefinitionContext> defs = [];
|
|
|
|
DefinitionContext def = parseDefinition();
|
|
|
|
|
|
|
|
while (def != null) {
|
|
|
|
defs.add(def);
|
|
|
|
def = parseDefinition();
|
|
|
|
}
|
|
|
|
|
|
|
|
return new DocumentContext()..definitions.addAll(defs);
|
|
|
|
}
|
|
|
|
|
|
|
|
DefinitionContext parseDefinition() =>
|
|
|
|
parseOperationDefinition() ?? parseFragmentDefinition();
|
|
|
|
|
2017-07-04 19:27:47 +00:00
|
|
|
OperationDefinitionContext parseOperationDefinition() {
|
|
|
|
var selectionSet = parseSelectionSet();
|
|
|
|
if (selectionSet != null)
|
|
|
|
return new OperationDefinitionContext(null, null, null, selectionSet);
|
|
|
|
else {
|
2019-03-31 20:07:25 +00:00
|
|
|
if (nextName('mutation') ||
|
|
|
|
nextName('query') ||
|
|
|
|
nextName('subscription')) {
|
2017-07-04 19:27:47 +00:00
|
|
|
var TYPE = current;
|
2018-08-04 01:41:16 +00:00
|
|
|
Token NAME = next(TokenType.NAME) ? current : null;
|
|
|
|
var variables = parseVariableDefinitions();
|
|
|
|
var dirs = parseDirectives();
|
|
|
|
var selectionSet = parseSelectionSet();
|
|
|
|
if (selectionSet != null)
|
|
|
|
return new OperationDefinitionContext(
|
|
|
|
TYPE, NAME, variables, selectionSet)
|
|
|
|
..directives.addAll(dirs);
|
2018-08-04 19:18:53 +00:00
|
|
|
else {
|
|
|
|
errors.add(new SyntaxError(
|
|
|
|
'Missing selection set in fragment definition.',
|
|
|
|
NAME?.span ?? TYPE.span));
|
|
|
|
return null;
|
|
|
|
}
|
2017-07-04 19:27:47 +00:00
|
|
|
} else
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
2017-01-25 04:28:09 +00:00
|
|
|
|
2017-07-04 19:27:47 +00:00
|
|
|
FragmentDefinitionContext parseFragmentDefinition() {
|
2019-03-31 20:07:25 +00:00
|
|
|
if (nextName('fragment')) {
|
2017-07-04 19:27:47 +00:00
|
|
|
var FRAGMENT = current;
|
|
|
|
if (next(TokenType.NAME)) {
|
|
|
|
var NAME = current;
|
2019-03-31 20:07:25 +00:00
|
|
|
if (nextName('on')) {
|
2017-07-04 19:27:47 +00:00
|
|
|
var ON = current;
|
|
|
|
var typeCondition = parseTypeCondition();
|
|
|
|
if (typeCondition != null) {
|
|
|
|
var dirs = parseDirectives();
|
|
|
|
var selectionSet = parseSelectionSet();
|
|
|
|
if (selectionSet != null)
|
|
|
|
return new FragmentDefinitionContext(
|
|
|
|
FRAGMENT, NAME, ON, typeCondition, selectionSet)
|
|
|
|
..directives.addAll(dirs);
|
2018-08-04 19:18:53 +00:00
|
|
|
else {
|
|
|
|
errors.add(new SyntaxError(
|
2017-07-04 19:27:47 +00:00
|
|
|
'Expected selection set in fragment definition.',
|
2018-08-04 19:18:53 +00:00
|
|
|
typeCondition.span));
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
errors.add(new SyntaxError(
|
2017-07-04 19:27:47 +00:00
|
|
|
'Expected type condition after "on" in fragment definition.',
|
2018-08-04 19:18:53 +00:00
|
|
|
ON.span));
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
errors.add(new SyntaxError(
|
2017-07-04 19:27:47 +00:00
|
|
|
'Expected "on" after name "${NAME.text}" in fragment definition.',
|
2018-08-04 19:18:53 +00:00
|
|
|
NAME.span));
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
errors.add(new SyntaxError(
|
2017-07-04 19:27:47 +00:00
|
|
|
'Expected name after "fragment" in fragment definition.',
|
2018-08-04 19:18:53 +00:00
|
|
|
FRAGMENT.span));
|
|
|
|
return null;
|
|
|
|
}
|
2017-07-04 19:27:47 +00:00
|
|
|
} else
|
|
|
|
return null;
|
|
|
|
}
|
2017-01-25 04:28:09 +00:00
|
|
|
|
2017-07-04 16:46:01 +00:00
|
|
|
FragmentSpreadContext parseFragmentSpread() {
|
|
|
|
if (next(TokenType.ELLIPSIS)) {
|
|
|
|
var ELLIPSIS = current;
|
|
|
|
if (next(TokenType.NAME)) {
|
|
|
|
var NAME = current;
|
|
|
|
return new FragmentSpreadContext(ELLIPSIS, NAME)
|
|
|
|
..directives.addAll(parseDirectives());
|
2017-07-04 17:32:52 +00:00
|
|
|
} else {
|
|
|
|
_index--;
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
InlineFragmentContext parseInlineFragment() {
|
|
|
|
if (next(TokenType.ELLIPSIS)) {
|
|
|
|
var ELLIPSIS = current;
|
2019-03-31 20:07:25 +00:00
|
|
|
if (nextName('on')) {
|
2017-07-04 17:32:52 +00:00
|
|
|
var ON = current;
|
|
|
|
var typeCondition = parseTypeCondition();
|
|
|
|
if (typeCondition != null) {
|
|
|
|
var directives = parseDirectives();
|
|
|
|
var selectionSet = parseSelectionSet();
|
|
|
|
if (selectionSet != null) {
|
|
|
|
return new InlineFragmentContext(
|
|
|
|
ELLIPSIS, ON, typeCondition, selectionSet)
|
|
|
|
..directives.addAll(directives);
|
2018-08-04 19:18:53 +00:00
|
|
|
} else {
|
|
|
|
errors.add(new SyntaxError(
|
|
|
|
'Missing selection set in inline fragment.',
|
|
|
|
directives.isEmpty
|
|
|
|
? typeCondition.span
|
|
|
|
: directives.last.span));
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
errors.add(new SyntaxError(
|
|
|
|
'Missing type condition after "on" in inline fragment.',
|
|
|
|
ON.span));
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
errors.add(new SyntaxError(
|
|
|
|
'Missing "on" after "..." in inline fragment.', ELLIPSIS.span));
|
|
|
|
return null;
|
|
|
|
}
|
2017-07-04 16:46:01 +00:00
|
|
|
} else
|
|
|
|
return null;
|
|
|
|
}
|
2017-01-25 04:28:09 +00:00
|
|
|
|
2017-07-04 16:46:01 +00:00
|
|
|
SelectionSetContext parseSelectionSet() {
|
|
|
|
if (next(TokenType.LBRACE)) {
|
|
|
|
var LBRACE = current;
|
|
|
|
List<SelectionContext> selections = [];
|
|
|
|
SelectionContext selection = parseSelection();
|
|
|
|
|
|
|
|
while (selection != null) {
|
|
|
|
selections.add(selection);
|
2018-08-04 19:18:53 +00:00
|
|
|
eatCommas();
|
2017-07-04 16:46:01 +00:00
|
|
|
selection = parseSelection();
|
|
|
|
}
|
|
|
|
|
2018-08-04 19:18:53 +00:00
|
|
|
eatCommas();
|
2017-07-04 16:46:01 +00:00
|
|
|
if (next(TokenType.RBRACE))
|
|
|
|
return new SelectionSetContext(LBRACE, current)
|
|
|
|
..selections.addAll(selections);
|
2018-08-04 19:18:53 +00:00
|
|
|
else {
|
|
|
|
errors.add(new SyntaxError('Missing "}" after selection set.',
|
|
|
|
selections.isEmpty ? LBRACE.span : selections.last.span));
|
|
|
|
return null;
|
|
|
|
}
|
2017-07-04 16:46:01 +00:00
|
|
|
} else
|
|
|
|
return null;
|
|
|
|
}
|
2017-01-25 04:28:09 +00:00
|
|
|
|
2017-07-04 16:46:01 +00:00
|
|
|
SelectionContext parseSelection() {
|
|
|
|
var field = parseField();
|
|
|
|
if (field != null) return new SelectionContext(field);
|
|
|
|
var fragmentSpread = parseFragmentSpread();
|
|
|
|
if (fragmentSpread != null)
|
|
|
|
return new SelectionContext(null, fragmentSpread);
|
|
|
|
var inlineFragment = parseInlineFragment();
|
|
|
|
if (inlineFragment != null)
|
|
|
|
return new SelectionContext(null, null, inlineFragment);
|
|
|
|
else
|
|
|
|
return null;
|
|
|
|
}
|
2017-01-25 04:28:09 +00:00
|
|
|
|
2017-07-04 15:58:22 +00:00
|
|
|
FieldContext parseField() {
|
|
|
|
var fieldName = parseFieldName();
|
|
|
|
if (fieldName != null) {
|
|
|
|
var args = parseArguments();
|
|
|
|
var directives = parseDirectives();
|
|
|
|
var selectionSet = parseSelectionSet();
|
|
|
|
return new FieldContext(fieldName, selectionSet)
|
2018-08-04 19:18:53 +00:00
|
|
|
..arguments.addAll(args ?? <ArgumentContext>[])
|
2017-07-04 15:58:22 +00:00
|
|
|
..directives.addAll(directives);
|
|
|
|
} else
|
|
|
|
return null;
|
|
|
|
}
|
2017-02-05 23:08:03 +00:00
|
|
|
|
2017-07-04 15:58:22 +00:00
|
|
|
FieldNameContext parseFieldName() {
|
|
|
|
if (next(TokenType.NAME)) {
|
|
|
|
var NAME1 = current;
|
|
|
|
if (next(TokenType.COLON)) {
|
|
|
|
var COLON = current;
|
|
|
|
if (next(TokenType.NAME))
|
|
|
|
return new FieldNameContext(
|
|
|
|
null, new AliasContext(NAME1, COLON, current));
|
2018-08-04 19:18:53 +00:00
|
|
|
else {
|
|
|
|
errors.add(new SyntaxError(
|
|
|
|
'Missing name after colon in alias.', COLON.span));
|
|
|
|
return null;
|
|
|
|
}
|
2017-07-04 15:58:22 +00:00
|
|
|
} else
|
|
|
|
return new FieldNameContext(NAME1);
|
|
|
|
} else
|
|
|
|
return null;
|
|
|
|
}
|
2017-02-05 23:08:03 +00:00
|
|
|
|
2017-07-04 19:27:47 +00:00
|
|
|
VariableDefinitionsContext parseVariableDefinitions() {
|
|
|
|
if (next(TokenType.LPAREN)) {
|
|
|
|
var LPAREN = current;
|
|
|
|
List<VariableDefinitionContext> defs = [];
|
|
|
|
VariableDefinitionContext def = parseVariableDefinition();
|
|
|
|
|
|
|
|
while (def != null) {
|
|
|
|
defs.add(def);
|
2018-08-04 19:18:53 +00:00
|
|
|
eatCommas();
|
2017-07-04 19:27:47 +00:00
|
|
|
def = parseVariableDefinition();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (next(TokenType.RPAREN))
|
|
|
|
return new VariableDefinitionsContext(LPAREN, current)
|
|
|
|
..variableDefinitions.addAll(defs);
|
2018-08-04 19:18:53 +00:00
|
|
|
else {
|
|
|
|
errors.add(new SyntaxError(
|
|
|
|
'Missing ")" after variable definitions.', LPAREN.span));
|
|
|
|
return null;
|
|
|
|
}
|
2017-07-04 19:27:47 +00:00
|
|
|
} else
|
|
|
|
return null;
|
|
|
|
}
|
2017-02-05 23:08:03 +00:00
|
|
|
|
2017-07-04 15:58:22 +00:00
|
|
|
VariableDefinitionContext parseVariableDefinition() {
|
|
|
|
var variable = parseVariable();
|
|
|
|
if (variable != null) {
|
|
|
|
if (next(TokenType.COLON)) {
|
|
|
|
var COLON = current;
|
|
|
|
var type = parseType();
|
|
|
|
if (type != null) {
|
|
|
|
var defaultValue = parseDefaultValue();
|
|
|
|
return new VariableDefinitionContext(
|
|
|
|
variable, COLON, type, defaultValue);
|
2018-08-04 19:18:53 +00:00
|
|
|
} else {
|
|
|
|
errors.add(new SyntaxError(
|
|
|
|
'Missing type in variable definition.', COLON.span));
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
errors.add(new SyntaxError(
|
|
|
|
'Missing ":" in variable definition.', variable.span));
|
|
|
|
return null;
|
|
|
|
}
|
2017-07-04 15:58:22 +00:00
|
|
|
} else
|
|
|
|
return null;
|
|
|
|
}
|
2017-02-05 23:08:03 +00:00
|
|
|
|
2017-07-04 15:58:22 +00:00
|
|
|
TypeContext parseType() {
|
|
|
|
var name = parseTypeName();
|
|
|
|
if (name != null) {
|
|
|
|
return new TypeContext(name, null, maybe(TokenType.EXCLAMATION));
|
|
|
|
} else {
|
|
|
|
var listType = parseListType();
|
|
|
|
if (listType != null) {
|
|
|
|
return new TypeContext(null, listType, maybe(TokenType.EXCLAMATION));
|
|
|
|
} else
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ListTypeContext parseListType() {
|
|
|
|
if (next(TokenType.LBRACKET)) {
|
|
|
|
var LBRACKET = current;
|
|
|
|
var type = parseType();
|
|
|
|
if (type != null) {
|
|
|
|
if (next(TokenType.RBRACKET)) {
|
|
|
|
return new ListTypeContext(LBRACKET, type, current);
|
2018-08-04 19:18:53 +00:00
|
|
|
} else {
|
|
|
|
errors.add(new SyntaxError('Missing "]" in list type.', type.span));
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
errors.add(new SyntaxError('Missing type after "[".', LBRACKET.span));
|
|
|
|
return null;
|
|
|
|
}
|
2017-07-04 15:58:22 +00:00
|
|
|
} else
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
List<DirectiveContext> parseDirectives() {
|
|
|
|
List<DirectiveContext> out = [];
|
|
|
|
DirectiveContext d = parseDirective();
|
|
|
|
while (d != null) {
|
|
|
|
out.add(d);
|
|
|
|
d = parseDirective();
|
|
|
|
}
|
|
|
|
|
|
|
|
return out;
|
|
|
|
}
|
2017-02-05 23:08:03 +00:00
|
|
|
|
2017-07-03 15:37:35 +00:00
|
|
|
DirectiveContext parseDirective() {
|
|
|
|
if (next(TokenType.ARROBA)) {
|
|
|
|
var ARROBA = current;
|
|
|
|
if (next(TokenType.NAME)) {
|
|
|
|
var NAME = current;
|
2017-02-05 23:08:03 +00:00
|
|
|
|
2017-07-03 15:37:35 +00:00
|
|
|
if (next(TokenType.COLON)) {
|
|
|
|
var COLON = current;
|
|
|
|
var val = parseValueOrVariable();
|
|
|
|
if (val != null)
|
|
|
|
return new DirectiveContext(
|
|
|
|
ARROBA, NAME, COLON, null, null, null, val);
|
2018-08-04 19:18:53 +00:00
|
|
|
else {
|
|
|
|
errors.add(new SyntaxError(
|
|
|
|
'Missing value or variable in directive after colon.',
|
|
|
|
COLON.span));
|
|
|
|
return null;
|
|
|
|
}
|
2017-07-03 15:37:35 +00:00
|
|
|
} else if (next(TokenType.LPAREN)) {
|
|
|
|
var LPAREN = current;
|
|
|
|
var arg = parseArgument();
|
|
|
|
if (arg != null) {
|
|
|
|
if (next(TokenType.RPAREN)) {
|
|
|
|
return new DirectiveContext(
|
|
|
|
ARROBA, NAME, null, LPAREN, current, arg, null);
|
2018-08-04 19:18:53 +00:00
|
|
|
} else {
|
|
|
|
errors.add(
|
|
|
|
new SyntaxError('Missing \')\'', arg.valueOrVariable.span));
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
errors.add(
|
|
|
|
new SyntaxError('Missing argument in directive.', LPAREN.span));
|
|
|
|
return null;
|
|
|
|
}
|
2017-07-03 15:37:35 +00:00
|
|
|
} else
|
|
|
|
return new DirectiveContext(
|
|
|
|
ARROBA, NAME, null, null, null, null, null);
|
2018-08-04 19:18:53 +00:00
|
|
|
} else {
|
|
|
|
errors.add(new SyntaxError('Missing name for directive.', ARROBA.span));
|
|
|
|
return null;
|
|
|
|
}
|
2017-07-03 15:37:35 +00:00
|
|
|
} else
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2017-07-04 15:58:22 +00:00
|
|
|
List<ArgumentContext> parseArguments() {
|
|
|
|
if (next(TokenType.LPAREN)) {
|
|
|
|
var LPAREN = current;
|
|
|
|
List<ArgumentContext> out = [];
|
|
|
|
ArgumentContext arg = parseArgument();
|
|
|
|
|
|
|
|
while (arg != null) {
|
|
|
|
out.add(arg);
|
2018-08-04 19:18:53 +00:00
|
|
|
eatCommas();
|
|
|
|
arg = parseArgument();
|
2017-07-04 15:58:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (next(TokenType.RPAREN))
|
|
|
|
return out;
|
2018-08-04 19:18:53 +00:00
|
|
|
else {
|
|
|
|
errors
|
|
|
|
.add(new SyntaxError('Missing ")" in argument list.', LPAREN.span));
|
|
|
|
return null;
|
|
|
|
}
|
2017-07-04 15:58:22 +00:00
|
|
|
} else
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
2017-07-03 15:37:35 +00:00
|
|
|
ArgumentContext parseArgument() {
|
|
|
|
if (next(TokenType.NAME)) {
|
|
|
|
var NAME = current;
|
|
|
|
if (next(TokenType.COLON)) {
|
|
|
|
var COLON = current;
|
|
|
|
var val = parseValueOrVariable();
|
|
|
|
if (val != null)
|
|
|
|
return new ArgumentContext(NAME, COLON, val);
|
2018-08-04 19:18:53 +00:00
|
|
|
else {
|
|
|
|
errors.add(new SyntaxError(
|
|
|
|
'Missing value or variable in argument.', COLON.span));
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
errors.add(new SyntaxError(
|
|
|
|
'Missing colon after name in argument.', NAME.span));
|
|
|
|
return null;
|
|
|
|
}
|
2017-07-03 15:37:35 +00:00
|
|
|
} else
|
|
|
|
return null;
|
2017-01-25 04:28:09 +00:00
|
|
|
}
|
|
|
|
|
2017-07-03 15:37:35 +00:00
|
|
|
ValueOrVariableContext parseValueOrVariable() {
|
|
|
|
var value = parseValue();
|
|
|
|
if (value != null)
|
|
|
|
return new ValueOrVariableContext(value, null);
|
|
|
|
else {
|
|
|
|
var variable = parseVariable();
|
|
|
|
if (variable != null)
|
|
|
|
return new ValueOrVariableContext(null, variable);
|
|
|
|
else
|
|
|
|
return null;
|
2017-02-05 23:08:03 +00:00
|
|
|
}
|
2017-07-03 15:37:35 +00:00
|
|
|
}
|
2017-02-05 23:08:03 +00:00
|
|
|
|
2017-07-03 15:37:35 +00:00
|
|
|
VariableContext parseVariable() {
|
|
|
|
if (next(TokenType.DOLLAR)) {
|
|
|
|
var DOLLAR = current;
|
|
|
|
if (next(TokenType.NAME))
|
|
|
|
return new VariableContext(DOLLAR, current);
|
2018-08-04 19:18:53 +00:00
|
|
|
else {
|
|
|
|
errors.add(new SyntaxError(
|
|
|
|
'Missing name for variable; found a lone "\$" instead.',
|
|
|
|
DOLLAR.span));
|
|
|
|
return null;
|
|
|
|
}
|
2017-07-03 15:37:35 +00:00
|
|
|
} else
|
|
|
|
return null;
|
2017-02-05 23:08:03 +00:00
|
|
|
}
|
|
|
|
|
2017-07-04 15:58:22 +00:00
|
|
|
DefaultValueContext parseDefaultValue() {
|
|
|
|
if (next(TokenType.EQUALS)) {
|
|
|
|
var EQUALS = current;
|
|
|
|
var value = parseValue();
|
|
|
|
if (value != null) {
|
|
|
|
return new DefaultValueContext(EQUALS, value);
|
2018-08-04 19:18:53 +00:00
|
|
|
} else {
|
|
|
|
errors
|
|
|
|
.add(new SyntaxError('Missing value after "=" sign.', EQUALS.span));
|
|
|
|
return null;
|
|
|
|
}
|
2017-07-04 15:58:22 +00:00
|
|
|
} else
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
TypeConditionContext parseTypeCondition() {
|
|
|
|
var name = parseTypeName();
|
|
|
|
if (name != null)
|
|
|
|
return new TypeConditionContext(name);
|
|
|
|
else
|
|
|
|
return null;
|
|
|
|
}
|
2017-02-05 23:08:03 +00:00
|
|
|
|
2017-07-04 15:58:22 +00:00
|
|
|
TypeNameContext parseTypeName() {
|
|
|
|
if (next(TokenType.NAME)) {
|
|
|
|
return new TypeNameContext(current);
|
|
|
|
} else
|
|
|
|
return null;
|
|
|
|
}
|
2017-02-05 23:08:03 +00:00
|
|
|
|
2017-07-03 15:37:35 +00:00
|
|
|
ValueContext parseValue() {
|
2018-08-04 19:18:53 +00:00
|
|
|
return (parseNumberValue() ??
|
|
|
|
parseStringValue() ??
|
2017-07-03 15:37:35 +00:00
|
|
|
parseBooleanValue() ??
|
2018-08-04 19:18:53 +00:00
|
|
|
parseNullValue() ??
|
|
|
|
parseEnumValue() ??
|
|
|
|
parseListValue() ??
|
|
|
|
parseObjectValue()) as ValueContext;
|
2017-07-03 15:37:35 +00:00
|
|
|
}
|
2017-02-05 23:08:03 +00:00
|
|
|
|
2018-08-04 19:18:53 +00:00
|
|
|
StringValueContext parseStringValue() => next(TokenType.STRING)
|
|
|
|
? new StringValueContext(current)
|
|
|
|
: (next(TokenType.BLOCK_STRING)
|
|
|
|
? new StringValueContext(current, isBlockString: true)
|
|
|
|
: null);
|
2017-02-05 23:08:03 +00:00
|
|
|
|
2017-07-03 15:37:35 +00:00
|
|
|
NumberValueContext parseNumberValue() =>
|
|
|
|
next(TokenType.NUMBER) ? new NumberValueContext(current) : null;
|
2017-02-05 23:08:03 +00:00
|
|
|
|
2017-07-03 15:37:35 +00:00
|
|
|
BooleanValueContext parseBooleanValue() =>
|
2019-03-31 20:07:25 +00:00
|
|
|
(nextName('true') || nextName('false'))
|
|
|
|
? new BooleanValueContext(current)
|
|
|
|
: null;
|
2017-02-05 23:08:03 +00:00
|
|
|
|
2018-08-04 19:18:53 +00:00
|
|
|
EnumValueContext parseEnumValue() =>
|
|
|
|
next(TokenType.NAME) ? new EnumValueContext(current) : null;
|
|
|
|
|
|
|
|
NullValueContext parseNullValue() =>
|
2019-03-31 20:07:25 +00:00
|
|
|
nextName('null') ? new NullValueContext(current) : null;
|
2018-08-04 19:18:53 +00:00
|
|
|
|
|
|
|
ListValueContext parseListValue() {
|
2017-07-03 15:37:35 +00:00
|
|
|
if (next(TokenType.LBRACKET)) {
|
|
|
|
var LBRACKET = current;
|
2018-08-04 19:18:53 +00:00
|
|
|
var lastSpan = LBRACKET.span;
|
2017-07-03 15:37:35 +00:00
|
|
|
List<ValueContext> values = [];
|
|
|
|
ValueContext value = parseValue();
|
2017-02-05 23:08:03 +00:00
|
|
|
|
2017-07-03 15:37:35 +00:00
|
|
|
while (value != null) {
|
2018-08-04 19:18:53 +00:00
|
|
|
lastSpan = value.span;
|
2017-07-03 15:37:35 +00:00
|
|
|
values.add(value);
|
2018-08-04 19:18:53 +00:00
|
|
|
eatCommas();
|
|
|
|
value = parseValue();
|
2017-07-03 15:37:35 +00:00
|
|
|
}
|
2017-01-22 23:15:53 +00:00
|
|
|
|
2018-08-04 19:18:53 +00:00
|
|
|
eatCommas();
|
2017-07-03 15:37:35 +00:00
|
|
|
if (next(TokenType.RBRACKET)) {
|
2018-08-04 19:18:53 +00:00
|
|
|
return new ListValueContext(LBRACKET, current)..values.addAll(values);
|
|
|
|
} else {
|
|
|
|
errors.add(new SyntaxError('Unterminated list literal.', lastSpan));
|
|
|
|
return null;
|
|
|
|
}
|
2017-07-03 15:37:35 +00:00
|
|
|
} else
|
|
|
|
return null;
|
2017-01-22 23:15:53 +00:00
|
|
|
}
|
2018-08-04 19:18:53 +00:00
|
|
|
|
|
|
|
ObjectValueContext parseObjectValue() {
|
|
|
|
if (next(TokenType.LBRACE)) {
|
|
|
|
var LBRACE = current;
|
|
|
|
var lastSpan = LBRACE.span;
|
|
|
|
var fields = <ObjectFieldContext>[];
|
|
|
|
var field = parseObjectField();
|
|
|
|
|
|
|
|
while (field != null) {
|
2018-08-04 20:14:26 +00:00
|
|
|
fields.add(field);
|
2018-08-04 19:18:53 +00:00
|
|
|
lastSpan = field.span;
|
|
|
|
eatCommas();
|
|
|
|
field = parseObjectField();
|
|
|
|
}
|
|
|
|
|
|
|
|
eatCommas();
|
|
|
|
|
|
|
|
if (next(TokenType.RBRACE)) {
|
|
|
|
return new ObjectValueContext(LBRACE, fields, current);
|
|
|
|
} else {
|
|
|
|
errors.add(new SyntaxError('Unterminated object literal.', lastSpan));
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ObjectFieldContext parseObjectField() {
|
|
|
|
if (next(TokenType.NAME)) {
|
|
|
|
var NAME = current;
|
|
|
|
|
|
|
|
if (next(TokenType.COLON)) {
|
|
|
|
var COLON = current;
|
|
|
|
var value = parseValue();
|
|
|
|
|
|
|
|
if (value != null) {
|
|
|
|
return new ObjectFieldContext(NAME, COLON, value);
|
|
|
|
} else {
|
|
|
|
errors.add(new SyntaxError('Missing value after ":".', COLON.span));
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
errors.add(new SyntaxError(
|
|
|
|
'Missing ":" after name "${NAME.span.text}".', NAME.span));
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
2017-01-22 23:15:53 +00:00
|
|
|
}
|