class Keisan::Parser
Constants
- KEYWORDS
- OPERATOR_TO_PARSING_CLASS
Attributes
components[R]
tokens[R]
Public Class Methods
new(string: nil, tokens: nil)
click to toggle source
# File lib/keisan/parser.rb, line 7 def initialize(string: nil, tokens: nil) if string.nil? && tokens.nil? raise Exceptions::InternalError.new("Invalid arguments") end if !string.nil? @tokens = Tokenizer.new(string).tokens else raise Exceptions::InternalError.new("Invalid argument: tokens = #{tokens}") if tokens.nil? || !tokens.is_a?(Array) @tokens = tokens end @components = [] if multi_line? parse_multi_line! elsif @tokens.first&.is_a?(Tokens::Word) && KEYWORDS.include?(@tokens.first.string) parse_keyword! else parse_components! remove_unary_identity! end end
Public Instance Methods
ast()
click to toggle source
# File lib/keisan/parser.rb, line 31 def ast @ast ||= AST::Builder.new(parser: self).ast end
Private Instance Methods
add_assignment_to_components!(token)
click to toggle source
# File lib/keisan/parser.rb, line 264 def add_assignment_to_components!(token) if compound_operator = token.compound_operator @components << Parsing::CompoundAssignment.new(compound_operator) else @components << Parsing::Assignment.new end end
add_element_to_components!(token)
click to toggle source
# File lib/keisan/parser.rb, line 188 def add_element_to_components!(token) case token when Tokens::Number @components << Parsing::Number.new(token.value) when Tokens::String @components << Parsing::String.new(token.value) when Tokens::Null @components << Parsing::Null.new when Tokens::Word @components << Parsing::Variable.new(token.string) when Tokens::Boolean @components << Parsing::Boolean.new(token.value) when Tokens::Group add_group_element_components!(token) else raise Exceptions::ParseError.new("Unhandled operator type #{token.operator_type}") end end
add_function_to_components!(token)
click to toggle source
# File lib/keisan/parser.rb, line 272 def add_function_to_components!(token) @components[-1] = Parsing::Function.new(@components[-1].name, arguments_from_group(token)) end
add_group_element_components!(token)
click to toggle source
# File lib/keisan/parser.rb, line 207 def add_group_element_components!(token) case token.group_type when :round @components << Parsing::RoundGroup.new(token.sub_tokens) when :square @components << Parsing::List.new(arguments_from_group(token)) when :curly if token.sub_tokens.any? {|token| token.is_a?(Tokens::Colon)} @components << Parsing::Hash.new(Util.array_split(token.sub_tokens) {|token| token.is_a?(Tokens::Comma)}) else @components << Parsing::CurlyGroup.new(token.sub_tokens) end else raise Exceptions::ParseError.new("Unhandled group type #{token.group_type}") end end
add_indexing_to_components!(token)
click to toggle source
# File lib/keisan/parser.rb, line 276 def add_indexing_to_components!(token) # Have an indexing @components << Parsing::Indexing.new(arguments_from_group(token)) end
add_operator_to_components!(token)
click to toggle source
# File lib/keisan/parser.rb, line 224 def add_operator_to_components!(token) case token.operator_type # Assignment when :"=" add_assignment_to_components!(token) else @components << operator_to_component(token.operator_type) end end
add_token_after_dot_word!(token)
click to toggle source
# File lib/keisan/parser.rb, line 144 def add_token_after_dot_word!(token) case token.type when :group case token.group_type when :round # Here it is a method call name = @components[-1].name @components[-1] = Parsing::DotOperator.new(name, arguments_from_group(token)) when :square # Here we are indexing after method call add_indexing_to_components!(token) else raise Exceptions::ParseError.new("Cannot take curly braces after function call") end when :dot # Chaining method calls @components << Parsing::Dot.new when :operator # End of method call, move on to operator add_operator_to_components!(token) else raise Exceptions::ParseError.new("Expected arguments to dot operator, received #{token.string}") end end
add_token_to_components!(token)
click to toggle source
Elements are groups of tokens separated by (non-unary) operators The following are basic elements: number variable function (word + round group) list (square group)
Additionally these can be modified by having any number of unary operators in front, and any number of indexing groups (square groups) at the back
# File lib/keisan/parser.rb, line 97 def add_token_to_components!(token) if token.is_a?(Tokens::LineSeparator) @components << Parsing::LineSeparator.new elsif is_start_of_line? || @components[-1].is_a?(Parsing::Operator) # Expect an element or a unary operator if token.type == :operator # Here it must be a unary operator add_unary_operator_to_components!(token) else # Here it must be an element add_element_to_components!(token) end elsif @components[-1].is_a?(Parsing::Element) # A word followed by a "round group" is actually a function: e.g. sin(x) if @components[-1].is_a?(Parsing::Variable) && token.type == :group && token.group_type == :round add_function_to_components!(token) # Here it is a postfix Indexing (access elements by index) elsif token.type == :group && token.group_type == :square add_indexing_to_components!(token) elsif token.type == :dot @components << Parsing::Dot.new elsif token.type == :operator add_operator_to_components!(token) else # Concatenation is multiplication @components << Parsing::Times.new add_token_to_components!(token) end elsif @components[-1].is_a?(Parsing::Dot) # Expect a word case token.type when :word @components[-1] = Parsing::DotWord.new(token.string) else raise Exceptions::ParseError.new("A word must follow a dot, received #{token.string}") end elsif @components[-1].is_a?(Parsing::DotWord) add_token_after_dot_word!(token) else raise Exceptions::ParseError.new("Token cannot be parsed, #{token.string}") end end
add_unary_operator_to_components!(token)
click to toggle source
# File lib/keisan/parser.rb, line 169 def add_unary_operator_to_components!(token) case token.operator_type when :+ @components << Parsing::UnaryPlus.new when :- @components << Parsing::UnaryMinus.new when :"~" @components << Parsing::BitwiseNot.new when :"~~" @components << Parsing::BitwiseNotNot.new when :"!" @components << Parsing::LogicalNot.new when :"!!" @components << Parsing::LogicalNotNot.new else raise Exceptions::ParseError.new("Unhandled unary operator type #{token.operator_type}") end end
arguments_from_group(token)
click to toggle source
# File lib/keisan/parser.rb, line 281 def arguments_from_group(token) if token.sub_tokens.empty? [] else Util.array_split(token.sub_tokens) {|sub_token| sub_token.is_a?(Tokens::Comma)}.map do |sub_tokens| Parsing::Argument.new(sub_tokens) end end end
is_start_of_line?()
click to toggle source
# File lib/keisan/parser.rb, line 83 def is_start_of_line? @components.empty? || @components.last.is_a?(Parsing::LineSeparator) end
multi_line?()
click to toggle source
# File lib/keisan/parser.rb, line 37 def multi_line? @tokens.any? {|token| token.is_a?(Tokens::LineSeparator)} end
operator_to_component(operator)
click to toggle source
# File lib/keisan/parser.rb, line 256 def operator_to_component(operator) if klass = OPERATOR_TO_PARSING_CLASS[operator] klass.new else raise Exceptions::ParseError.new("Unhandled operator type #{operator}") end end
parse_components!()
click to toggle source
# File lib/keisan/parser.rb, line 66 def parse_components! @unparsed_tokens = tokens.dup # Components will store the elements (numbers, variables, bracket components, function calls) # and the operators in between while @unparsed_tokens.count > 0 token = @unparsed_tokens.shift add_token_to_components!(token) end end
parse_keyword!()
click to toggle source
# File lib/keisan/parser.rb, line 52 def parse_keyword! keyword = tokens.first.string arguments = if tokens[1].is_a?(Tokens::Group) Util.array_split(tokens[1].sub_tokens) {|token| token.is_a?(Tokens::Comma)}.map {|argument_tokens| Parsing::Argument.new(argument_tokens) } else Parsing::Argument.new(tokens[1..-1]) end @components = [ Parsing::Function.new(keyword, arguments) ] end
parse_multi_line!()
click to toggle source
# File lib/keisan/parser.rb, line 41 def parse_multi_line! line_parsers = Util.array_split(@tokens) {|token| token.is_a?(Tokens::LineSeparator)}.map {|tokens| self.class.new(tokens: tokens)} @components = [] line_parsers.each.with_index do |line_parser, i| @components += line_parser.components if i < line_parsers.count - 1 @components << Parsing::LineSeparator.new end end end
remove_unary_identity!()
click to toggle source
# File lib/keisan/parser.rb, line 77 def remove_unary_identity! @components = @components.select do |component| !component.is_a?(Parsing::Operator) || !(component.node_class <= AST::UnaryIdentity) end end