-
-
Notifications
You must be signed in to change notification settings - Fork 25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Some small refactorings #26
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,52 +3,52 @@ module Extended | |
class ParseCode | ||
include Mandate | ||
|
||
STOP_AT_FIRST_LINE_OF_CODE = "stop_at_first_loc".freeze | ||
|
||
def initialize(code, syntax_trie, arguments = []) | ||
@code = code | ||
@syntax_trie = syntax_trie | ||
@arguments = arguments | ||
@stop_at_first_loc = arguments.include?(STOP_AT_FIRST_LINE_OF_CODE) | ||
|
||
@scan_index = 0 | ||
|
||
@current_line = "" | ||
@current_line_has_skip = false | ||
@current_action = nil | ||
@queued_multiline = nil | ||
|
||
@saved_lines = [] | ||
generate_new_line! | ||
end | ||
|
||
def call | ||
action, skipped = try_match_first_word | ||
execute_action(action, skipped) unless action.nil? | ||
execute_action(action, skipped) if action | ||
|
||
follow_strategy until is_finished? | ||
|
||
save_last_line | ||
save_last_line! | ||
|
||
self.saved_lines | ||
saved_lines.map(&:content) | ||
end | ||
|
||
protected | ||
attr_accessor :code, :syntax_trie, :scan_index, :current_syntax_node, :current_line, :current_line_skipped, | ||
:current_action, :queued_multiline, :saved_lines, :arguments, :current_line_has_skip | ||
attr_accessor :code, :syntax_trie, :scan_index, :current_syntax_node, :current_line, | ||
:current_action, :queued_multiline, :saved_lines | ||
|
||
def try_match_first_word | ||
try_match(syntax_trie.root.get_match_node(' ')) if syntax_trie.root.has_match?(' ') | ||
return unless syntax_trie.root.has_match?(' ') | ||
|
||
try_match(syntax_trie.root.get_match_node(' ')) | ||
end | ||
|
||
def try_match(from_node = nil) | ||
if self.arguments.include?(STOP_AT_FIRST_LINE_OF_CODE) && !(self.saved_lines.empty? && self.current_line.empty?) | ||
return [nil, 0] | ||
end | ||
return if stop_at_first_loc? && !(saved_lines.empty? && current_line.empty?) | ||
|
||
current_syntax_node = from_node || self.syntax_trie.root | ||
current_syntax_node = from_node || syntax_trie.root | ||
scan_lookahead = 0 | ||
|
||
while !is_finished?(scan_lookahead) && current_syntax_node.has_match?(scan_char(scan_lookahead)) | ||
current_syntax_node = current_syntax_node.get_match_node(scan_char(scan_lookahead)) | ||
until is_finished?(scan_lookahead) | ||
node = scan_char!(scan_lookahead) | ||
break unless current_syntax_node.has_match?(node) | ||
|
||
current_syntax_node = current_syntax_node.get_match_node(node) | ||
scan_lookahead += 1 | ||
end | ||
|
||
|
@@ -58,119 +58,156 @@ def try_match(from_node = nil) | |
def execute_action(action, skipped) | ||
return if action.nil? | ||
|
||
if action.instance_of? Multi | ||
if action.instance_of?(Multi) | ||
self.queued_multiline = action | ||
execute_action(action.start_action, skipped) | ||
return | ||
end | ||
|
||
save_if_newline_reached(skipped) | ||
self.scan_index += skipped | ||
self.current_line_has_skip = true | ||
save_if_newline_reached!(skipped) | ||
current_line.skip! | ||
|
||
if action.instance_of?(Just) || (action.instance_of?(Line) && code[self.scan_index - 1].include?("\n")) | ||
if action.instance_of?(Just) || | ||
(action.instance_of?(Line) && code[scan_index - 1] == "\n") | ||
self.current_action = self.queued_multiline | ||
self.queued_multiline = nil | ||
else | ||
self.current_action = action | ||
end | ||
end | ||
|
||
def save_if_newline_reached(skipped = 1) | ||
return unless code[self.scan_index, skipped].include?("\n") | ||
def save_if_newline_reached!(size = 1) | ||
if code[scan_index, size].include?("\n") | ||
save_current_line! | ||
generate_new_line! | ||
end | ||
|
||
save_current_line | ||
self.current_line = "" | ||
increment_scan_index!(size) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are coupling the line saving with the progressing the scan index here. I would personally leave the Personally I also found it a bit confusing. I guess it should work, but I don't expect the new line storage method to be the one progressing the pointer, but rather the scan/skip methods. (See next comment) I think it would be mostly solved changing the name of the method to |
||
end | ||
|
||
def follow_strategy | ||
case self.current_action | ||
when Line then line_strategy | ||
when Multi then multi_strategy | ||
case current_action | ||
when Line then follow_line_strategy | ||
when Multi then follow_multi_strategy | ||
else | ||
looking_for_match_strategy | ||
end | ||
end | ||
|
||
def looking_for_match_strategy | ||
action, skipped = try_match | ||
if action.nil? | ||
self.current_line = self.current_line + self.code[self.scan_index] | ||
save_if_newline_reached | ||
self.scan_index += 1 | ||
else | ||
execute_action(action, skipped) | ||
end | ||
end | ||
|
||
def line_strategy | ||
if self.code[self.scan_index] == "\n" | ||
def follow_line_strategy | ||
if code[scan_index] == "\n" | ||
self.current_action = self.queued_multiline | ||
self.queued_multiline = nil | ||
end | ||
|
||
# We have to check for matches even though the line was skipped. | ||
# The newline could be the blank space of the next whole word keyword. | ||
if self.current_action.nil? | ||
action, skipped = try_match | ||
if action.nil? | ||
self.current_line = self.current_line + self.code[self.scan_index] | ||
save_if_newline_reached | ||
self.scan_index += 1 | ||
else | ||
execute_action(action, skipped) | ||
end | ||
else | ||
save_if_newline_reached | ||
self.scan_index += 1 | ||
end | ||
|
||
return save_if_newline_reached! if current_action | ||
|
||
looking_for_match_strategy | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks much better like this. Is right, that the special case is just a looking for match strategy (although I think I tried that and didnt woooork...? But I digress, this looks much cleaner if it's working). Now that I'm seeing this save_if_newline_reached!, another way to put that if you want to do both the index progressing and the newline saving in one method, would be to call it |
||
end | ||
|
||
def multi_strategy | ||
action, skipped = try_match(self.current_action.syntax_trie.root) | ||
if action.nil? | ||
save_if_newline_reached | ||
self.scan_index += 1 | ||
else | ||
execute_action(action, skipped) | ||
end | ||
def follow_multi_strategy | ||
action, skipped = try_match(current_action.syntax_trie.root) | ||
|
||
return execute_action(action, skipped) if action | ||
|
||
save_if_newline_reached! | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same. I think the idea of renaming it to |
||
end | ||
|
||
def looking_for_match_strategy | ||
action, skipped = try_match | ||
return execute_action(action, skipped) if action | ||
|
||
current_line.append(code[scan_index]) | ||
save_if_newline_reached! | ||
end | ||
|
||
def start_at_new_word | ||
self.current_syntax_node = self.current_syntax_node.mapping[' '] if self.current_syntax_node.mapping.key? ' ' | ||
self.current_syntax_node = current_syntax_node.mapping[' '] if current_syntax_node.mapping.key?(' ') | ||
end | ||
|
||
def stop_at_first_loc? | ||
!!@stop_at_first_loc | ||
end | ||
|
||
def is_finished?(lookahead = 0) | ||
self.scan_index + lookahead >= self.code.length | ||
scan_index + lookahead >= code.length | ||
end | ||
|
||
def scan_char(lookahead = 0) | ||
character = self.code[self.scan_index + lookahead] | ||
def scan_char!(lookahead = 0) | ||
character = code[scan_index + lookahead] | ||
|
||
character.strip.empty? ? ' ' : character.downcase | ||
end | ||
|
||
def save_current_line | ||
if self.current_line.strip.empty? | ||
unless self.current_line_has_skip || self.saved_lines.empty? || self.saved_lines.last.strip.empty? | ||
self.current_line.strip! | ||
add_newline_and_save_line | ||
end | ||
else | ||
add_newline_and_save_line | ||
end | ||
self.current_line = "" | ||
self.current_line_has_skip = false unless current_action.instance_of?(Multi) && queued_multiline.nil? | ||
def save_current_line! | ||
# If the current line is not empty, then save it and move on | ||
add_newline_and_save_line! and return unless current_line.empty?(strip: true) | ||
|
||
# The current line is empty. Sometimes we want to save it, sometimes we don't. | ||
return if current_line.skip? || saved_lines.empty? || saved_lines.last.empty?(strip: true) | ||
|
||
current_line.strip! | ||
add_newline_and_save_line! | ||
end | ||
|
||
def generate_new_line! | ||
self.current_line = CurrentLine.new(current_action.instance_of?(Multi) && queued_multiline.nil?) | ||
end | ||
|
||
def add_newline_and_save_line! | ||
current_line.append("\n") unless current_line.ends_with_newline? | ||
saved_lines.append(current_line) | ||
end | ||
|
||
def save_last_line! | ||
saved_lines.append(current_line) unless current_line.empty?(strip: true) | ||
end | ||
|
||
def add_newline_and_save_line | ||
self.current_line += "\n" if self.current_line[-1, 1] != "\n" | ||
self.saved_lines.append(self.current_line) | ||
def increment_scan_index!(size = 1) | ||
self.scan_index += size | ||
end | ||
|
||
def save_last_line | ||
self.saved_lines.append(self.current_line) unless self.current_line.strip.empty? | ||
STOP_AT_FIRST_LINE_OF_CODE = "stop_at_first_loc".freeze | ||
private_constant :STOP_AT_FIRST_LINE_OF_CODE | ||
|
||
CurrentLine = Struct.new(:skip) do | ||
def initialize(skip) | ||
@content = "" | ||
@skip = skip | ||
end | ||
|
||
def content | ||
@content.freeze | ||
end | ||
|
||
def skip? | ||
!!@skip | ||
end | ||
|
||
def skip! | ||
@skip = true | ||
end | ||
|
||
def empty?(strip: false) | ||
strip ? @content.strip.empty? : @content.empty? | ||
end | ||
|
||
def strip! | ||
@content.strip! | ||
end | ||
|
||
def append(extra) | ||
@content += extra | ||
end | ||
|
||
def ends_with_newline? | ||
@content[-1, 1] == "\n" | ||
end | ||
end | ||
private_constant :CurrentLine | ||
end | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: Using this syntax, how about calling the method
has_node_for?(node)
or calling thenode
next_node
instead? Is not possible at simple glance to know thatr scan_char brings the mapping to the next node, and not the actual node (which is brought byget_match_node