Skip to content

Commit

Permalink
Remove +Parser+ dependencies as class loading time
Browse files Browse the repository at this point in the history
We deliberately only load `parser` when there a console session starts,
so we need to remove references from class declarations.
  • Loading branch information
jorgemanrubia committed Sep 9, 2021
1 parent 96373f9 commit 2addc58
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 72 deletions.
69 changes: 69 additions & 0 deletions lib/console1984/command_validator/.command_parser.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Naming class with dot so that it doesn't get loaded eagerly by Zeitwork. We want to load
# only when a console session is started, when +parser+ is loaded.
#
# See +Console1984::Supervisor#require_dependencies+
class Console1984::CommandValidator::CommandParser < ::Parser::AST::Processor
include AST::Processor::Mixin
include Console1984::Freezeable

def initialize
@constants = []
@declared_classes_or_modules = []
@constant_assignments = []
end

# We define accessors to define lists without duplicates. We are not using a +SortedSet+ because we want
# to mutate strings in the list while the processing is happening. And we don't use metapgroamming to define the
# accessors to prevent having problems with freezable and its instance_variable* protection.

def constants
@constants.uniq
end

def declared_classes_or_modules
@declared_classes_or_modules.uniq
end

def constant_assignments
@constant_assignments.uniq
end

def on_class(node)
super
const_declaration, _, _ = *node
constant = extract_constants(const_declaration).first
@declared_classes_or_modules << constant if constant.present?
end

alias_method :on_module, :on_class

def on_const(node)
super
name, const_name = *node
const_name = const_name.to_s
last_constant = @constants.last

if name.nil? || (name && name.type == :cbase) # cbase = leading ::
if last_constant&.end_with?("::")
last_constant << const_name
else
@constants << const_name
end
elsif last_constant
last_constant << "::#{const_name}"
end
end

def on_casgn(node)
super
scope_node, name, value_node = *node
@constant_assignments.push(*extract_constants(value_node))
end

private
def extract_constants(node)
self.class.new.tap do |processor|
processor.process(node)
end.constants
end
end
72 changes: 3 additions & 69 deletions lib/console1984/command_validator/parsed_command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,85 +6,19 @@ class Console1984::CommandValidator::ParsedCommand

attr_reader :raw_command

delegate :declared_classes_or_modules, :constants, :constant_assignments, to: :processed_ast
delegate :declared_classes_or_modules, :constants, :constant_assignments, to: :command_parser

def initialize(raw_command)
@raw_command = Array(raw_command).join("\n")
end

private
def processed_ast
@processed_ast ||= CommandProcessor.new.tap do |processor|
def command_parser
@command_parser ||= Console1984::CommandValidator::CommandParser.new.tap do |processor|
ast = Parser::CurrentRuby.parse(raw_command)
processor.process(ast)
rescue Parser::SyntaxError
# Fail open with syntax errors
end
end

class CommandProcessor < ::Parser::AST::Processor
include AST::Processor::Mixin
include Console1984::Freezeable

def initialize
@constants = []
@declared_classes_or_modules = []
@constant_assignments = []
end

# We define accessors to define lists without duplicates. We are not using a +SortedSet+ because we want
# to mutate strings in the list while the processing is happening. And we don't use metapgroamming to define the
# accessors to prevent having problems with freezable and its instance_variable* protection.

def constants
@constants.uniq
end

def declared_classes_or_modules
@declared_classes_or_modules.uniq
end

def constant_assignments
@constant_assignments.uniq
end

def on_class(node)
super
const_declaration, _, _ = *node
constant = extract_constants(const_declaration).first
@declared_classes_or_modules << constant if constant.present?
end

alias_method :on_module, :on_class

def on_const(node)
super
name, const_name = *node
const_name = const_name.to_s
last_constant = @constants.last

if name.nil? || (name && name.type == :cbase) # cbase = leading ::
if last_constant&.end_with?("::")
last_constant << const_name
else
@constants << const_name
end
elsif last_constant
last_constant << "::#{const_name}"
end
end

def on_casgn(node)
super
scope_node, name, value_node = *node
@constant_assignments.push(*extract_constants(value_node))
end

private
def extract_constants(node)
self.class.new.tap do |processor|
processor.process(node)
end.constants
end
end
end
9 changes: 6 additions & 3 deletions lib/console1984/refrigerator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,17 @@ def freeze_all
end

private
EXTERNAL_MODULES_AND_CLASSES_TO_FREEZE = [Parser::CurrentRuby]

def freeze_internal_instances
Console1984.config.freeze unless Console1984.config.test_mode
end

def freeze_external_modules_and_classes
EXTERNAL_MODULES_AND_CLASSES_TO_FREEZE.each { |klass| klass.include(Console1984::Freezeable) }
external_modules_and_classes_to_freeze.each { |klass| klass.include(Console1984::Freezeable) }
end

def external_modules_and_classes_to_freeze
# Not using a constant because we want this to run lazily (console-dependant dependencies might not be loaded).
[Parser::CurrentRuby]
end

def eager_load_all_classes
Expand Down
4 changes: 4 additions & 0 deletions lib/console1984/supervisor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ def require_dependencies
require 'parser/current'
end
require 'colorized_string'

# Explicit lazy loading because it depends on +parser+, which we want to only load
# in console sessions.
require_relative "./command_validator/.command_parser"
end

def start_session
Expand Down

0 comments on commit 2addc58

Please sign in to comment.