Skip to content

Commit

Permalink
Merge pull request #4718 from rmosolgo/recursive-descent-parser
Browse files Browse the repository at this point in the history
Use a recursive descent parser
  • Loading branch information
rmosolgo authored Dec 11, 2023
2 parents c30b5e8 + 1771eb9 commit 007a1ea
Show file tree
Hide file tree
Showing 23 changed files with 1,142 additions and 2,857 deletions.
448 changes: 271 additions & 177 deletions lib/graphql/language/lexer.rb

Large diffs are not rendered by default.

126 changes: 72 additions & 54 deletions lib/graphql/language/nodes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,30 +16,30 @@ module DefinitionNode
# @return [Integer] The first line of the definition (not the description)
attr_reader :definition_line

def initialize(options = {})
@definition_line = options.delete(:definition_line)
super(options)
def initialize(definition_line: nil, **_rest)
@definition_line = definition_line
super(**_rest)
end
end

attr_reader :line, :col, :filename
attr_reader :filename

# Initialize a node by extracting its position,
# then calling the class's `initialize_node` method.
# @param options [Hash] Initial attributes for this node
def initialize(options = {})
if options.key?(:position_source)
position_source = options.delete(:position_source)
@line = position_source[1]
@col = position_source[2]
else
@line = options.delete(:line)
@col = options.delete(:col)
end
def line
@line ||= (@source_string && @pos) ? @source_string[0..@pos].count("\n") + 1 : nil
end

@filename = options.delete(:filename)
def col
@col ||= if @source_string && @pos
if @pos == 0
1
else
@source_string[0..@pos].split("\n").last.length
end
end
end

initialize_node(**options)
def definition_line
@definition_line ||= (@source_string && @definition_pos) ? @source_string[0..@definition_pos].count("\n") + 1 : nil
end

# Value equality
Expand Down Expand Up @@ -196,8 +196,8 @@ def children_methods(children_of_type)
module_eval <<-RUBY, __FILE__, __LINE__
# Singular method: create a node with these options
# and return a new `self` which includes that node in this list.
def merge_#{method_name.to_s.sub(/s$/, "")}(node_opts)
merge(#{method_name}: #{method_name} + [#{node_type.name}.new(node_opts)])
def merge_#{method_name.to_s.sub(/s$/, "")}(**node_opts)
merge(#{method_name}: #{method_name} + [#{node_type.name}.new(**node_opts)])
end
RUBY
end
Expand Down Expand Up @@ -226,13 +226,14 @@ def children
end

if defined?(@scalar_methods)
if !method_defined?(:initialize_node)
generate_initialize_node
if !@initialize_was_generated
@initialize_was_generated = true
generate_initialize
else
# This method was defined manually
end
else
raise "Can't generate_initialize_node because scalar_methods wasn't called; call it before children_methods"
raise "Can't generate_initialize because scalar_methods wasn't called; call it before children_methods"
end
end

Expand Down Expand Up @@ -261,7 +262,15 @@ def scalars
end
end

def generate_initialize_node
DEFAULT_INITIALIZE_OPTIONS = [
"line: nil",
"col: nil",
"pos: nil",
"filename: nil",
"source_string: nil",
]

def generate_initialize
scalar_method_names = @scalar_methods
# TODO: These probably should be scalar methods, but `types` returns an array
[:types, :description].each do |extra_method|
Expand All @@ -277,16 +286,27 @@ def generate_initialize_node
return
else
arguments = scalar_method_names.map { |m| "#{m}: nil"} +
@children_methods.keys.map { |m| "#{m}: NO_CHILDREN" }
@children_methods.keys.map { |m| "#{m}: NO_CHILDREN" } +
DEFAULT_INITIALIZE_OPTIONS

assignments = scalar_method_names.map { |m| "@#{m} = #{m}"} +
@children_methods.keys.map { |m| "@#{m} = #{m}.freeze" }

if name.end_with?("Definition") && name != "FragmentDefinition"
arguments << "definition_pos: nil"
assignments << "@definition_pos = definition_pos"
end

keywords = scalar_method_names.map { |m| "#{m}: #{m}"} +
@children_methods.keys.map { |m| "#{m}: #{m}" }

module_eval <<-RUBY, __FILE__, __LINE__
def initialize_node #{arguments.join(", ")}
def initialize(#{arguments.join(", ")})
@line = line
@col = col
@pos = pos
@filename = filename
@source_string = source_string
#{assignments.join("\n")}
end
Expand Down Expand Up @@ -336,7 +356,6 @@ class DirectiveLocation < NameOnlyNode
end

class DirectiveDefinition < AbstractNode
include DefinitionNode
attr_reader :description
scalar_methods :name, :repeatable
children_methods(
Expand Down Expand Up @@ -365,17 +384,22 @@ class Field < AbstractNode
# @!attribute selections
# @return [Array<Nodes::Field>] Selections on this object (or empty array if this is a scalar field)

def initialize_node(attributes)
@name = attributes[:name]
@arguments = attributes[:arguments] || NONE
@directives = attributes[:directives] || NONE
@selections = attributes[:selections] || NONE
def initialize(name: nil, arguments: NONE, directives: NONE, selections: NONE, field_alias: nil, line: nil, col: nil, pos: nil, filename: nil, source_string: nil)
@name = name
@arguments = arguments || NONE
@directives = directives || NONE
@selections = selections || NONE
# oops, alias is a keyword:
@alias = attributes[:alias]
@alias = field_alias
@line = line
@col = col
@pos = pos
@filename = filename
@source_string = source_string
end

def self.from_a(filename, line, col, graphql_alias, name, arguments, directives, selections) # rubocop:disable Metrics/ParameterLists
self.new(filename: filename, line: line, col: col, alias: graphql_alias, name: name, arguments: arguments, directives: directives, selections: selections)
def self.from_a(filename, line, col, field_alias, name, arguments, directives, selections) # rubocop:disable Metrics/ParameterLists
self.new(filename: filename, line: line, col: col, field_alias: field_alias, name: name, arguments: arguments, directives: directives, selections: selections)
end

# Override this because default is `:fields`
Expand All @@ -384,29 +408,33 @@ def self.from_a(filename, line, col, graphql_alias, name, arguments, directives,

# A reusable fragment, defined at document-level.
class FragmentDefinition < AbstractNode
scalar_methods :name, :type
children_methods({
selections: GraphQL::Language::Nodes::Field,
directives: GraphQL::Language::Nodes::Directive,
})

self.children_method_name = :definitions
# @!attribute name
# @return [String] the identifier for this fragment, which may be applied with `...#{name}`

# @!attribute type
# @return [String] the type condition for this fragment (name of type which it may apply to)
def initialize_node(name: nil, type: nil, directives: [], selections: [])
def initialize(name: nil, type: nil, directives: NONE, selections: NONE, filename: nil, pos: nil, source_string: nil, line: nil, col: nil)
@name = name
@type = type
@directives = directives
@selections = selections
@filename = filename
@pos = pos
@source_string = source_string
@line = line
@col = col
end

def self.from_a(filename, line, col, name, type, directives, selections)
self.new(filename: filename, line: line, col: col, name: name, type: type, directives: directives, selections: selections)
end

scalar_methods :name, :type
children_methods({
selections: GraphQL::Language::Nodes::Field,
directives: GraphQL::Language::Nodes::Directive,
})

self.children_method_name = :definitions
end

# Application of a named fragment in a selection
Expand Down Expand Up @@ -562,7 +590,6 @@ class VariableIdentifier < NameOnlyNode
end

class SchemaDefinition < AbstractNode
include DefinitionNode
scalar_methods :query, :mutation, :subscription
children_methods({
directives: GraphQL::Language::Nodes::Directive,
Expand All @@ -579,7 +606,6 @@ class SchemaExtension < AbstractNode
end

class ScalarTypeDefinition < AbstractNode
include DefinitionNode
attr_reader :description
scalar_methods :name
children_methods({
Expand All @@ -597,7 +623,6 @@ class ScalarTypeExtension < AbstractNode
end

class InputValueDefinition < AbstractNode
include DefinitionNode
attr_reader :description
scalar_methods :name, :type, :default_value
children_methods({
Expand All @@ -607,7 +632,6 @@ class InputValueDefinition < AbstractNode
end

class FieldDefinition < AbstractNode
include DefinitionNode
attr_reader :description
scalar_methods :name, :type
children_methods({
Expand All @@ -628,7 +652,6 @@ def merge(new_options)
end

class ObjectTypeDefinition < AbstractNode
include DefinitionNode
attr_reader :description
scalar_methods :name, :interfaces
children_methods({
Expand All @@ -648,7 +671,6 @@ class ObjectTypeExtension < AbstractNode
end

class InterfaceTypeDefinition < AbstractNode
include DefinitionNode
attr_reader :description
scalar_methods :name
children_methods({
Expand All @@ -670,7 +692,6 @@ class InterfaceTypeExtension < AbstractNode
end

class UnionTypeDefinition < AbstractNode
include DefinitionNode
attr_reader :description, :types
scalar_methods :name
children_methods({
Expand All @@ -689,7 +710,6 @@ class UnionTypeExtension < AbstractNode
end

class EnumValueDefinition < AbstractNode
include DefinitionNode
attr_reader :description
scalar_methods :name
children_methods({
Expand All @@ -699,7 +719,6 @@ class EnumValueDefinition < AbstractNode
end

class EnumTypeDefinition < AbstractNode
include DefinitionNode
attr_reader :description
scalar_methods :name
children_methods({
Expand All @@ -719,7 +738,6 @@ class EnumTypeExtension < AbstractNode
end

class InputObjectTypeDefinition < AbstractNode
include DefinitionNode
attr_reader :description
scalar_methods :name
children_methods({
Expand Down
Loading

0 comments on commit 007a1ea

Please sign in to comment.