diff --git a/lib/console1984/command_validator/.command_parser.rb b/lib/console1984/command_validator/.command_parser.rb new file mode 100644 index 0000000..1088e15 --- /dev/null +++ b/lib/console1984/command_validator/.command_parser.rb @@ -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 diff --git a/lib/console1984/command_validator/parsed_command.rb b/lib/console1984/command_validator/parsed_command.rb index 9573f1b..29d80df 100644 --- a/lib/console1984/command_validator/parsed_command.rb +++ b/lib/console1984/command_validator/parsed_command.rb @@ -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 diff --git a/lib/console1984/refrigerator.rb b/lib/console1984/refrigerator.rb index 14e2052..bac1407 100644 --- a/lib/console1984/refrigerator.rb +++ b/lib/console1984/refrigerator.rb @@ -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 diff --git a/lib/console1984/supervisor.rb b/lib/console1984/supervisor.rb index 52755e3..86c37b9 100644 --- a/lib/console1984/supervisor.rb +++ b/lib/console1984/supervisor.rb @@ -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