quecto_calc 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3f56437ed3d312aa5c0508c4e787d2b33cb0ea8f6943fa6198ff201d8fc6f579
4
- data.tar.gz: c089f9abbc586cada86442f88c6fdbd9c063609613643388bca93ed6e5eb87a3
3
+ metadata.gz: 44bae8aee9c0431cfd4ea0b75aefe914bdc55f3e0ea4565c889ad608c7f67b64
4
+ data.tar.gz: 8a2a5b6e5e59304c184e2aa0eea67b36fdb9a90ccfa23fd19f71065a241fb135
5
5
  SHA512:
6
- metadata.gz: 3ec5be8c5bcbbba76dcea6a2b13cd75ce58a4f96d67d1cc2861cc909f9194982b25b15762a22200a7cab3ccb312fbecf7b55c6fee219369fcd14898832b2ae51
7
- data.tar.gz: 41348a16b6f58ecadc075f9e06e74e3f96dbdd1f61b5e1d8b8c10b9bccc8a3b06eb170bfb5e36e61905fe7e580bc25dfb1f6dadd01f24aaba22668870614dcd7
6
+ metadata.gz: 5decd805cf29de3744f18b161ea4cd0557df6ee6886f6a7f68527ce791083640ce8a29f92e4f5a3e0985bf19c21cf2dd4a4297c58f06ce9da1d8bfecbe6d1cb1
7
+ data.tar.gz: 78025741efc21e5957ccbab21ca3fab3963ddb47eb50b6e08f9c116d289b8d20d995f7357e9113141af9829fed8bd9419d871b787c688eae3d2567dbe5e5e178
data/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ### 0.2.0 / 2023-02-20
2
+
3
+ * Lexer/parser separated in a it's own gem 'quecto_parser';
4
+ * Bug fixes.
5
+
6
+ ### 0.1.1 / 2023-02-19
7
+
8
+ * Added constants interface.
9
+
1
10
  ### 0.1.0 / 2023-02-17
2
11
 
3
12
  * Initial release.
data/Gemfile CHANGED
@@ -10,3 +10,5 @@ gem "rake", "~> 13.0"
10
10
  gem "minitest", "~> 5.0"
11
11
 
12
12
  gem "rubocop", "~> 0.80"
13
+
14
+ gem "quecto_parser", "~> 0.1.0"
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- quecto_calc (0.1.0)
4
+ quecto_calc (0.1.1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -1,14 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "quecto_error"
4
- require_relative "token_types"
3
+ require_relative "quecto_calc_error"
5
4
 
6
5
  #
7
6
  # Evaluates expression from the abstract syntax tree.
8
7
  #
9
8
  class Evaluator
10
- include TokenTypes
11
-
12
9
  #
13
10
  # Initialize an evaluator instance.
14
11
  #
@@ -20,10 +17,27 @@ class Evaluator
20
17
  end
21
18
 
22
19
  #
23
- # @param [BinOpNode, NumberNode] node
20
+ # @param [QuectoParser::BinOpNode, QuectoParser::NumberNode] node
24
21
  #
25
22
  def visit(node)
26
- send("_visit_#{node.class}", node)
23
+ method_name = "_visit_#{node.class}"
24
+ send(method_name, node)
25
+ end
26
+
27
+ #
28
+ # @param [String] _method_name
29
+ #
30
+ # @param [Object] node
31
+ #
32
+ # @raise [NoMethodError]
33
+ #
34
+ def method_missing(_method_name, node)
35
+ error_msg = "unsupported node type: #{node.class}. Plase check parser's output for correct node types."
36
+ raise NoMethodError, error_msg
37
+ end
38
+
39
+ def respond_to_missing?(*)
40
+ true
27
41
  end
28
42
 
29
43
  private
@@ -31,7 +45,7 @@ class Evaluator
31
45
  #
32
46
  # Retrieve result of a binary operation node.
33
47
  #
34
- # @param [BinOpNode] node
48
+ # @param [QuectoParser::BinOpNode] node
35
49
  #
36
50
  # @return [Numeric]
37
51
  # Result of the binary operation.
@@ -41,9 +55,9 @@ class Evaluator
41
55
  right = visit(node.right_node)
42
56
 
43
57
  case node.operator.type
44
- when TT_PLUS
58
+ when TokenTypes::TT_PLUS
45
59
  left + right
46
- when TT_MINUS
60
+ when TokenTypes::TT_MINUS
47
61
  left - right
48
62
  end
49
63
  end
@@ -51,15 +65,15 @@ class Evaluator
51
65
  #
52
66
  # Retrieve value of a number node.
53
67
  #
54
- # @param [NumberNode] node
68
+ # @param [QuectoParser::NumberNode] node
55
69
  #
56
70
  # @return [Numeric]
57
71
  #
58
72
  def _visit_NumberNode(node)
59
73
  case node.token.type
60
- when TT_INT
74
+ when TokenTypes::TT_INT
61
75
  node.token.value
62
- when TT_CONST
76
+ when TokenTypes::TT_CONST
63
77
  _init_constant(node)
64
78
  end
65
79
  end
@@ -67,12 +81,12 @@ class Evaluator
67
81
  #
68
82
  # Replace constant with an associated value.
69
83
  #
70
- # @param [NumberNode] node
84
+ # @param [QuectoParser::NumberNode] node
71
85
  # A numeric node with @token of TT_CONST type.
72
86
  #
73
87
  # @return [Numeric]
74
88
  #
75
- # @raise [CalcError]
89
+ # @raise [CalcError::RunTimeError]
76
90
  # Raises if constant is not found in the @consts.
77
91
  #
78
92
  def _init_constant(node)
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Errors rised by the quecto_calc.
5
+ #
6
+ class QuectoCalcError < ::StandardError
7
+ attr_reader :message
8
+
9
+ def initialize(message)
10
+ @message = message
11
+ super()
12
+ end
13
+ end
14
+
15
+ #
16
+ # Raised by the evaluator on an illegal operation.
17
+ #
18
+ class CalcError < QuectoCalcError
19
+ attr_reader :message
20
+
21
+ def initialize(message = "")
22
+ super(message)
23
+ end
24
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class QuectoCalc
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
data/lib/quecto_calc.rb CHANGED
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "quecto_parser"
4
+
3
5
  require_relative "quecto_calc/evaluator"
4
- require_relative "quecto_calc/lexer"
5
- require_relative "quecto_calc/parser"
6
6
  require_relative "quecto_calc/version"
7
7
 
8
8
  #
@@ -24,41 +24,22 @@ class QuectoCalc
24
24
  # Evaluate an expression.
25
25
  #
26
26
  # @param [String] expr
27
- # Expression to parse and evaluate.
27
+ # Expression (in a text form) to parse and evaluate.
28
28
  #
29
29
  # @option [Hash{ String => Numeric }] consts
30
30
  # List on constants and their values to put in the expression.
31
31
  #
32
32
  def evaluate(expr, consts = {})
33
- tokens = build_tokens(expr)
34
- ast = build_ast(tokens)
35
- evaluate_ast(ast, consts)
36
- rescue QuectoError => e
37
- puts "#{e.error_name} #{e.message}"
38
- end
33
+ parser = QuectoParser.new
39
34
 
40
- #
41
- # Get a list of tokens from the string.
42
- #
43
- # @param [String] expr
44
- #
45
- def build_tokens(expr)
46
- Lexer.new(expr).build_tokens
47
- end
48
-
49
- #
50
- # Build an abstract syntax tree from a list of tokens.
51
- #
52
- # @param [Array<Token>] tokens
53
- #
54
- def build_ast(tokens)
55
- Parser.new(tokens).parse
35
+ ast = parser.parse_expr(expr)
36
+ evaluate_ast(ast, consts) if ast
56
37
  end
57
38
 
58
39
  #
59
40
  # Evaluate expression from the AST.
60
41
  #
61
- # @param [NumberNode, BinOpNode] ast
42
+ # @param [QuectoParser::NumberNode, QuectoParser::BinOpNode] ast
62
43
  # Expression (in a form of AST) to evaluate.
63
44
  #
64
45
  # @option [Hash{ String => Numeric }] consts
@@ -67,5 +48,7 @@ class QuectoCalc
67
48
  def evaluate_ast(ast, consts = {})
68
49
  evaluator = Evaluator.new(consts)
69
50
  evaluator.visit(ast)
51
+ rescue QuectoCalcError, NoMethodError => e
52
+ puts "[#{e.class}] #{e.message}"
70
53
  end
71
54
  end
data/quecto_calc.gemspec CHANGED
@@ -30,7 +30,7 @@ Gem::Specification.new do |spec|
30
30
  spec.require_paths = ["lib"]
31
31
 
32
32
  # Uncomment to register a new dependency of your gem
33
- # spec.add_dependency "example-gem", "~> 1.0"
33
+ spec.add_dependency "quecto_parser", "~> 0.1.0"
34
34
 
35
35
  # For more information and examples about making a new gem, checkout our
36
36
  # guide at: https://bundler.io/guides/creating_gem.html
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: quecto_calc
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mate
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-02-17 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2023-02-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: quecto_parser
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.1.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.1.0
13
27
  description: Evaluates primitive arithmetic expressions represented in a text form.
14
28
  email:
15
29
  executables: []
@@ -27,15 +41,8 @@ files:
27
41
  - bin/console
28
42
  - bin/setup
29
43
  - lib/quecto_calc.rb
30
- - lib/quecto_calc/bin_op_node.rb
31
- - lib/quecto_calc/char_rules.rb
32
44
  - lib/quecto_calc/evaluator.rb
33
- - lib/quecto_calc/lexer.rb
34
- - lib/quecto_calc/number_node.rb
35
- - lib/quecto_calc/parser.rb
36
- - lib/quecto_calc/quecto_error.rb
37
- - lib/quecto_calc/token.rb
38
- - lib/quecto_calc/token_types.rb
45
+ - lib/quecto_calc/quecto_calc_error.rb
39
46
  - lib/quecto_calc/version.rb
40
47
  - quecto_calc.gemspec
41
48
  homepage: https://github.com/8bit-mate/quecto_calc.rb/
@@ -1,14 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #
4
- # Stores a node with a binary operation (between two numbers).
5
- #
6
- class BinOpNode
7
- attr_reader :left_node, :operator, :right_node
8
-
9
- def initialize(left_node, operator, right_node)
10
- @left_node = left_node
11
- @operator = operator
12
- @right_node = right_node
13
- end
14
- end
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #
4
- # Provides character rules that are used by the lexer.
5
- #
6
- module CharRules
7
- NUM_CHAR = /[[:digit:]]/.freeze # characters that could form a number: digits only
8
- CONST_FIRST_CHAR = /[[:alpha:]]/.freeze # characters that could be used as a 1st char. in the constant name
9
- CONTS_CHAR = /[[:alpha:][:digit:]_]/.freeze # characters that could form a constant: letters, digits, underscore char.
10
-
11
- ADD_CHAR = "+" # character that marks an addition operator
12
- SUB_CHAR = "-" # character that marks an subtraction operator
13
-
14
- IGNOR_CHAR = /\s/.freeze # characters that are ignored: all whitespace
15
- end
@@ -1,111 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "char_rules"
4
- require_relative "quecto_error"
5
- require_relative "token"
6
- require_relative "token_types"
7
-
8
- #
9
- # Performs lexical analysis of a given string.
10
- #
11
- class Lexer
12
- include TokenTypes
13
- include CharRules
14
-
15
- #
16
- # Initialize a lexer instance.
17
- #
18
- # @param [String] str
19
- # String to parse.
20
- #
21
- def initialize(str)
22
- @str = str
23
- @pos = 0
24
- @cur_char = nil
25
-
26
- _next_char
27
- end
28
-
29
- #
30
- # Create a list of tokens from a string.
31
- #
32
- # @return [Array<Token>] tokens
33
- #
34
- # @raise [IllegalCharError]
35
- # Raises when @str has an unsupported character.
36
- #
37
- def build_tokens
38
- tokens = []
39
-
40
- until @cur_char.nil?
41
- if @cur_char == ADD_CHAR
42
- tokens.append(Token.new(type: TT_PLUS))
43
- elsif @cur_char == SUB_CHAR
44
- tokens.append(Token.new(type: TT_MINUS))
45
- elsif @cur_char.match?(NUM_CHAR)
46
- num = Token.new(type: TT_INT, value: _build_word(NUM_CHAR).to_i)
47
- tokens.append(num)
48
- elsif @cur_char.match?(CONST_FIRST_CHAR)
49
- num = Token.new(type: TT_CONST, value: _build_word(CONTS_CHAR))
50
- tokens.append(num)
51
- elsif @cur_char.match?(IGNOR_CHAR)
52
- # Ignore whitespace.
53
- else
54
- error_msg = "illegal character '#{@cur_char}' at the position: #{@pos - 1}"
55
- raise IllegalCharError, error_msg
56
- end
57
- _next_char
58
- end
59
-
60
- tokens.append(Token.new(type: TT_EOF))
61
-
62
- tokens
63
- end
64
-
65
- private
66
-
67
- #
68
- # Build a 'word' out of a sequence of characters.
69
- #
70
- # A 'word' is a sequence of characters followed one by another without break characters (e.g. a space or a supported
71
- # math operator: '+', '-', etc).
72
- #
73
- # A 'word' could be represented in a form of:
74
- #
75
- # 1. a sequence of alphabetical characters optionally mixed with digits and joined into one string using a connective
76
- # character '_'. Examples:
77
- # - 'ruby' (a sequence of alphabetical characters only);
78
- # - 'foobar9000' (a mix of alphabetical characters and digits);
79
- # - 'this_is_a_3rd_example' (long string joined with a connective character).
80
- #
81
- # 2. an integer number, e.g.: '31337' (a sequence of digits without break characters).
82
- #
83
- # @param [Regexp] regexp
84
- # Defines that kind of character sequence the method should be looking for: NUM_CHAR (search for a digit) or
85
- # CONTS_CHAR (search for a constant).
86
- #
87
- # @return [String] word
88
- #
89
- def _build_word(regexp)
90
- word = ""
91
-
92
- until @cur_char.nil?
93
- break unless @cur_char.match?(regexp)
94
-
95
- word += @cur_char
96
- _next_char
97
- end
98
- _next_char
99
-
100
- @pos -= 1
101
- word
102
- end
103
-
104
- #
105
- # Process next character in the string.
106
- #
107
- def _next_char
108
- @cur_char = @pos < @str.length ? @str[@pos] : nil
109
- @pos += 1
110
- end
111
- end
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #
4
- # Stores a node with a numeric value.
5
- #
6
- class NumberNode
7
- attr_reader :token
8
-
9
- def initialize(token)
10
- @token = token
11
- end
12
- end
@@ -1,87 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "bin_op_node"
4
- require_relative "number_node"
5
- require_relative "quecto_error"
6
- require_relative "token_types"
7
-
8
- #
9
- # Parses list of tokens to build an abstract syntax tree.
10
- #
11
- class Parser
12
- include TokenTypes
13
-
14
- #
15
- # Initialize a parser instance.
16
- #
17
- # @param [Array<Token>] tokens
18
- # List of tokens to parse.
19
- #
20
- def initialize(tokens)
21
- @tokens = tokens
22
- @cur_token = @tokens[0]
23
- @idx = 0
24
- end
25
-
26
- #
27
- # Parse list of tokens.
28
- #
29
- def parse
30
- _expr
31
- end
32
-
33
- private
34
-
35
- #
36
- # Buid a node for the expression.
37
- #
38
- # @return [BinOpNode, NumberNode] left
39
- # Node for an expression. The left.class corresponds to the expression type: a single number or a binary operation
40
- # (an operation between two numbers).
41
- #
42
- def _expr
43
- # retrieve left part of the expression:
44
- left = _term
45
-
46
- while BIN_OPS.include?(@cur_token.type)
47
- # retrieve operator between two number nodes:
48
- op_tok = @cur_token
49
-
50
- # retrieve right part of the expression:
51
- _next_token
52
- right = _term
53
-
54
- left = BinOpNode.new(left, op_tok, right)
55
- end
56
-
57
- left
58
- end
59
-
60
- #
61
- # Search for a term in the expression.
62
- #
63
- # @return [NumberNode] token
64
- # Found term.
65
- #
66
- # @raise [InvalidSyntaxError]
67
- #
68
- def _term
69
- if TERMS.include?(@cur_token.type)
70
- token = NumberNode.new(@cur_token)
71
- else
72
- error_msg = "expected TT_INT or TT_LBL, but got #{@cur_token.type}"
73
- raise InvalidSyntaxError, error_msg
74
- end
75
-
76
- _next_token
77
- token
78
- end
79
-
80
- #
81
- # Process next token.
82
- #
83
- def _next_token
84
- @idx += 1
85
- @cur_token = @tokens[@idx] if @idx < @tokens.length
86
- end
87
- end
@@ -1,46 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #
4
- # Errors rised by the quecto_calc.
5
- #
6
- class QuectoError < ::StandardError
7
- attr_reader :error_name, :message
8
-
9
- def initialize(error_name, message)
10
- @error_name = error_name
11
- @message = message
12
- end
13
- end
14
-
15
- #
16
- # Raised by the lexer if an illegal character is found.
17
- #
18
- class IllegalCharError < QuectoError
19
- attr_reader :message
20
-
21
- def initialize(message = "")
22
- super("Illegal Character:", message)
23
- end
24
- end
25
-
26
- #
27
- # Raised by the parser if an illegal syntax is found.
28
- #
29
- class InvalidSyntaxError < QuectoError
30
- attr_reader :message
31
-
32
- def initialize(message = "")
33
- super("Syntax Error:", message)
34
- end
35
- end
36
-
37
- #
38
- # Raised by the evaluator on an illegal operation.
39
- #
40
- class CalcError < QuectoError
41
- attr_reader :message
42
-
43
- def initialize(message = "")
44
- super("Runtime Error:", message)
45
- end
46
- end
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #
4
- # Stores a token generated by the lexer.
5
- #
6
- class Token
7
- attr_reader :type, :value
8
-
9
- def initialize(type:, value: nil)
10
- @type = type
11
- @value = value
12
- end
13
- end
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #
4
- # Provides supported token types.
5
- #
6
- module TokenTypes
7
- TT_INT = :TT_INT # integer number
8
- TT_CONST = :TT_CONST # constant (placeholder for a numeric value)
9
- TT_PLUS = :TT_PLUS # addition operator
10
- TT_MINUS = :TT_MINUS # subtraction operator
11
- TT_EOF = :TT_EOF # end of input
12
-
13
- TERMS = [TT_INT, TT_CONST].freeze
14
-
15
- BIN_OPS = [TT_PLUS, TT_MINUS].freeze
16
- end