jaina 0.0.0 → 0.1.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.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +19 -0
  4. data/README.md +160 -1
  5. data/jaina.gemspec +2 -1
  6. data/lib/jaina/parser/ast/context.rb +98 -0
  7. data/lib/jaina/parser/ast/evaluator.rb +17 -0
  8. data/lib/jaina/parser/ast/tree.rb +35 -0
  9. data/lib/jaina/parser/ast/tree_builder.rb +129 -0
  10. data/lib/jaina/parser/ast.rb +62 -0
  11. data/lib/jaina/parser/code_converter/to_postfix_form.rb +167 -0
  12. data/lib/jaina/parser/code_converter/to_prefix_form.rb +122 -0
  13. data/lib/jaina/parser/code_converter.rb +28 -0
  14. data/lib/jaina/parser/expression/operator/abstract/dsl.rb +256 -0
  15. data/lib/jaina/parser/expression/operator/abstract.rb +67 -0
  16. data/lib/jaina/parser/expression/operator/and.rb +12 -0
  17. data/lib/jaina/parser/expression/operator/grouping/dsl.rb +100 -0
  18. data/lib/jaina/parser/expression/operator/grouping.rb +12 -0
  19. data/lib/jaina/parser/expression/operator/left_corner.rb +10 -0
  20. data/lib/jaina/parser/expression/operator/non_terminal.rb +25 -0
  21. data/lib/jaina/parser/expression/operator/not.rb +12 -0
  22. data/lib/jaina/parser/expression/operator/or.rb +12 -0
  23. data/lib/jaina/parser/expression/operator/right_corner.rb +10 -0
  24. data/lib/jaina/parser/expression/operator/terminal.rb +25 -0
  25. data/lib/jaina/parser/expression/operator.rb +15 -0
  26. data/lib/jaina/parser/expression/registry/access_interface_mixin.rb +49 -0
  27. data/lib/jaina/parser/expression/registry.rb +127 -0
  28. data/lib/jaina/parser/expression.rb +87 -0
  29. data/lib/jaina/parser/tokenizer.rb +37 -0
  30. data/lib/jaina/parser.rb +44 -0
  31. data/lib/jaina/version.rb +5 -1
  32. data/lib/jaina.rb +58 -4
  33. metadata +29 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 48fe47bcb5284c5750a92c39457d8554d18c4b4a68751495b2b4e980d0f6ab79
4
- data.tar.gz: '08f0fca3c2d988a9468da5f8abad49c78915dd7192f189b28deb372a017fbaa7'
3
+ metadata.gz: 5b071350b636b4d475d51836639fa20c3b62f5518eddd4d0e71aa70cf9a9a1f9
4
+ data.tar.gz: eec63665a26dc9de484fd162795332e79762e2b4da24a637753468b06c5cfd8b
5
5
  SHA512:
6
- metadata.gz: e0754eca5ffe832e586de80a9f50ba8dcd09feb4264be5da05da551ec5cd89a1bc79ccdec9c8920fcf927cbee32cb3ba64382fd8230fce5101a75e6abbd0f8e9
7
- data.tar.gz: 55c67e22dcc1c857e61215843be9335538a993b477e2f4d6c2ee089a8ee11335865aaaaebadeb211f3aa9cc81eabde766d117f46034ffb55b16deb29e95e228d
6
+ metadata.gz: 56422133b95ab2bbfdcb720aea41a320d0c147e26ffca1de3fcf1a7cc013af2bb2cc9462634eee9dc4f1296584a6488b9fc63555aafc6bd37814e01d000c6f35
7
+ data.tar.gz: e5127b38022694809fd58644d86b9434bd4e368d1467b2a13d36ebe22103b5117a29bc2aaa11b6298dae0109f1ae6dea9f0d11983db52e45c3f1b447000ee54b
data/.gitignore CHANGED
@@ -11,3 +11,4 @@ Gemfile.lock
11
11
  .ruby-version
12
12
  /.idea
13
13
  /.vscode
14
+ *.gem
data/.travis.yml ADDED
@@ -0,0 +1,19 @@
1
+ language: ruby
2
+ matrix:
3
+ fast_finish: true
4
+ include:
5
+ - rvm: 2.3
6
+ - rvm: 2.4
7
+ - rvm: 2.5
8
+ - rvm: 2.6
9
+ - rvm: ruby-head
10
+ - rvm: jruby-head
11
+ allow_failures:
12
+ - rvm: ruby-head
13
+ - rvm: jruby-head
14
+ sudo: false
15
+ cache: bundler
16
+ before_install: gem install bundler
17
+ script:
18
+ - bundke exec rubocop
19
+ - bundle exec rspec
data/README.md CHANGED
@@ -1 +1,160 @@
1
- # Jaina
1
+ # Jaina · [![Gem Version](https://badge.fury.io/rb/jaina.svg)](https://badge.fury.io/rb/jaina) [![Build Status](https://travis-ci.org/0exp/jaina.svg?branch=master)](https://travis-ci.org/0exp/jaina) [![Coverage Status](https://coveralls.io/repos/github/0exp/jaina/badge.svg?branch=master)](https://coveralls.io/github/0exp/jaina?branch=master)
2
+
3
+ Simple programming language builder inspired by interpreter pattern.
4
+
5
+ ## Installation
6
+
7
+ ```ruby
8
+ gem 'jaina'
9
+ ```
10
+
11
+ ```shell
12
+ $ bundle install
13
+ # --- or ---
14
+ $ gem install 'jaina'
15
+ ```
16
+
17
+ ```ruby
18
+ require 'jaina'
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ - [Registered operators](#registered-operators)
24
+ - [Register your own operator](#register-your-own-operator)
25
+ - [Register your own operand](#register-your-own-operand)
26
+ - [Context API](#context-api)
27
+ - [Parse your code (build AST)](#parse-your-code-build-ast)
28
+ - [Evaluate your code](#evaluate-your-code)
29
+ - [List registered operands and operators](#list-and-fetch-registered-operands-and-operators)
30
+
31
+ ---
32
+
33
+ ### Registered operators
34
+
35
+ - `AND`
36
+ - `OR`
37
+ - `NOT`
38
+ - `(`, `)` (grouping operators)
39
+
40
+ ---
41
+
42
+ ### Register your own operator
43
+
44
+ ```ruby
45
+ # step 1: define new operator
46
+ class But < Jaina::NonTerminalExpr
47
+ token 'BUT' # use it in your program :)
48
+ associativity_direction :left # associativity (left or right)
49
+ acts_as_binary_term # binar or unary
50
+ precedence_level 4 # for example: AND > OR, NOT > AND, and etc...
51
+ end
52
+
53
+ # step 2: regsiter your operator
54
+ Jaina.register_expression(But)
55
+ ```
56
+
57
+ ---
58
+
59
+ ### Register your own operand
60
+
61
+ ```ruby
62
+ # step 1: define new operand
63
+ class A < Jaina::TerminalExpr
64
+ token 'A'
65
+
66
+ # NOTE: context is a custom data holder that passed from expression to expression
67
+ def evaluate(context)
68
+ # your custom evaluation code
69
+ end
70
+ end
71
+
72
+ # step 2: regsiter your operand
73
+ Jaina.register_expression(A)
74
+ ```
75
+
76
+ ---
77
+
78
+ ### Context API
79
+
80
+ ```ruby
81
+ class A < Jaina::TerminalExpr
82
+ # ... some code
83
+
84
+ def evaluate(context)
85
+ ... your code ...
86
+ # ... context ???
87
+ ... your code ...
88
+ end
89
+ end
90
+
91
+ # NOTE: context api
92
+
93
+ context.keys # => []
94
+ context.set(:a, 1) # => 1
95
+ context.get(:a) # => 1
96
+ context.keys # => [:a]
97
+ context.get(:b) # => Jaina::Parser::AST::Contex::UndefinedContextKeyError
98
+ ```
99
+
100
+ ---
101
+
102
+ ### Parse your code (build AST)
103
+
104
+ ```ruby
105
+ Jaina.parse('A AND B AND (C OR D) OR A AND (C OR E)')
106
+ # => #<Jaina::Parser::AST:0x00007fd6f424a2e8>
107
+ ```
108
+
109
+ ---
110
+
111
+ ### Evaluate your code
112
+
113
+ ```ruby
114
+ ast = Jaina.parse('A AND B AND (C OR D) OR A AND (C OR E)')
115
+ ast.evaluate
116
+
117
+ # --- or ---
118
+ Jaina.evaluate('A AND B AND (C OR D) OR A AND (C OR E)')
119
+ ```
120
+
121
+ ---
122
+
123
+ ### List and fetch registered operands and operators
124
+
125
+ ```ruby
126
+ A = Class.new(Jaina::TerminalExpr) { token 'A' }
127
+ B = Class.new(Jaina::TerminalExpr) { token 'B' }
128
+ C = Class.new(Jaina::TerminalExpr) { token 'C' }
129
+
130
+ Jaina.register_expression(A)
131
+ Jaina.register_expression(B)
132
+ Jaina.register_expression(C)
133
+
134
+ Jaina.expressions
135
+ # => ["AND", "OR", "NOT", "(", ")", "A", "B", "C"]
136
+
137
+ Jaina.fetch_expression("AND") # => Jaina::Parser::Expression::Operator::And
138
+ Jaina.fetch_expression("A") # => A
139
+
140
+ Jaina.fetch_expression("KEK")
141
+ # => raises Jaina::Parser::Expression::Registry::UnregisteredExpressionError
142
+ ```
143
+
144
+ ---
145
+
146
+ ## Contributing
147
+
148
+ - Fork it ( https://github.com/0exp/jaina/fork )
149
+ - Create your feature branch (`git checkout -b feature/my-new-feature`)
150
+ - Commit your changes (`git commit -am 'Add some feature'`)
151
+ - Push to the branch (`git push origin feature/my-new-feature`)
152
+ - Create new Pull Request
153
+
154
+ ## License
155
+
156
+ Released under MIT License.
157
+
158
+ ## Authors
159
+
160
+ [Rustam Ibragimov](https://github.com/0exp)
data/jaina.gemspec CHANGED
@@ -13,7 +13,8 @@ Gem::Specification.new do |spec|
13
13
 
14
14
  spec.homepage = 'https://github.com/0exp/jaina'
15
15
  spec.summary = 'Simple programming language builder inspired by interpreter pattern.'
16
- spec.description = 'Simple programming language builder inspired by interpreter pattern.'
16
+ spec.description = 'Simple programming language builder inspired by interpreter pattern. ' \
17
+ 'You can build your own langs for any project purposes.'
17
18
 
18
19
  spec.bindir = 'bin'
19
20
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api public
4
+ # @since 0.1.0
5
+ class Jaina::Parser::AST::Context
6
+ # @since 0.1.0
7
+ Error = Class.new(StandardError)
8
+ # @since 0.1.0
9
+ UndefinedContextKeyError = Class.new(Error)
10
+
11
+ # @return [void]
12
+ #
13
+ # @api private
14
+ # @since 0.1.0
15
+ def initialize
16
+ @data = {}
17
+ @access_lock = Mutex.new
18
+ end
19
+
20
+ # @param key [Any]
21
+ # @param value [Any]
22
+ # @return [Any]
23
+ #
24
+ # @api public
25
+ # @since 0.1.0
26
+ def set(key, value)
27
+ thread_safe { apply(key, value) }
28
+ end
29
+
30
+ # @param key [Any]
31
+ # @return [Any]
32
+ #
33
+ # @api public
34
+ # @since 0.1.0
35
+ def get(key)
36
+ thread_safe { get(key) }
37
+ end
38
+
39
+ # @return [Array<Any>]
40
+ #
41
+ # @api public
42
+ # @since 0.1.0
43
+ def keys
44
+ thread_safe { registered_data }
45
+ end
46
+
47
+ private
48
+
49
+ # @return [Hash<Any,Any>]
50
+ #
51
+ # @api private
52
+ # @since 0.1.0
53
+ attr_reader :data
54
+
55
+ # @return [Mutex]
56
+ #
57
+ # @api private
58
+ # @since 0.1.0
59
+ attr_reader :access_lock
60
+
61
+ # @param block [Proc]
62
+ # @return [Any]
63
+ #
64
+ # @api private
65
+ # @since 0.1.0
66
+ def thread_safe(&block)
67
+ access_lock.synchronize { yield }
68
+ end
69
+
70
+ # @param key [Any]
71
+ # @param value [Any]
72
+ # @return [Any]
73
+ #
74
+ # @api private
75
+ # @since 0.1.0
76
+ def apply(key, value)
77
+ value.tap { data[key] = value }
78
+ end
79
+
80
+ # @return [Array<Any>]
81
+ #
82
+ # @api private
83
+ # @since 0.1.0
84
+ def registered_data
85
+ data.keys
86
+ end
87
+
88
+ # @param key [String]
89
+ # @return [Any]
90
+ #
91
+ # @api private
92
+ # @since 0.1.0
93
+ def fetch(key)
94
+ data.fetch(key)
95
+ rescue KeyError
96
+ raise UndefinedContextKeyError, "Data with `#{key}` key does not exist"
97
+ end
98
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.1.0
5
+ module Jaina::Parser::AST::Evaluator
6
+ class << self
7
+ # @param ast [Jaina::Parser::AST]
8
+ # @return [Any]
9
+ #
10
+ # @api private
11
+ # @since 0.1.0
12
+ def evaluate(ast)
13
+ context = Jaina::Parser::AST::Context.new
14
+ # TODO: traverse the abstract syntax tree
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.1.0
5
+ class Jaina::Parser::AST::Tree
6
+ # @return [String]
7
+ #
8
+ # @api private
9
+ # @since 0.1.0
10
+ attr_reader :initial_program
11
+
12
+ # @return [String]
13
+ #
14
+ # @api private
15
+ # @since 0.1.0
16
+ attr_reader :ast_oriented_program
17
+
18
+ # @return [Jaina::Parser::Expression::Operator::Abstract]
19
+ #
20
+ # @api private
21
+ # @since 0.1.0
22
+ attr_reader :expression
23
+
24
+ # @param initial_program [String]
25
+ # @param expression [Jaina::Parser::Expression::Operator::Abstract]
26
+ # @return [void]
27
+ #
28
+ # @api private
29
+ # @since 0.1.0
30
+ def initialize(initial_program:, ast_oriented_program:, expression:)
31
+ @initial_program = initial_program
32
+ @ast_oriented_program = ast_oriented_program
33
+ @expression = expression
34
+ end
35
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.1.0
5
+ class Jaina::Parser::AST::TreeBuilder
6
+ # @since 0.1.0
7
+ extend Forwardable
8
+
9
+ class << self
10
+ # @param program [String]
11
+ # @return [Jaina::Parser::AST::Tree]
12
+ #
13
+ # @api private
14
+ # @since 0.1.0
15
+ def build(program)
16
+ new(program).build
17
+ end
18
+ end
19
+
20
+ # @since 0.1.0
21
+ def_delegators Jaina::Parser::Expression,
22
+ :terminal?,
23
+ :non_terminal?,
24
+ :acts_as_unary_term?,
25
+ :acts_as_binary_term?
26
+
27
+ # @param progrm [String]
28
+ # @return [void]
29
+ #
30
+ # @api private
31
+ # @since 0.1.0
32
+ def initialize(program)
33
+ @program = program.dup.tap(&:freeze)
34
+ @prefix_form = Jaina::Parser::CodeConverter.to_prefix_form(@program)
35
+ @tokens = Jaina::Parser::Tokenizer.tokenize(@prefix_form)
36
+ end
37
+
38
+ # @return [Jaina::Parser::AST:Tree]
39
+ #
40
+ # @api private
41
+ # @since 0.1.0
42
+ def build
43
+ token_series = tokens.map(&:dup)
44
+ expression_tree = build_expression_tree(token_series)
45
+
46
+ Jaina::Parser::AST::Tree.new(
47
+ initial_program: program,
48
+ ast_oriented_program: prefix_form,
49
+ expression: expression_tree
50
+ )
51
+ end
52
+
53
+ private
54
+
55
+ # @return [String]
56
+ #
57
+ # @api private
58
+ # @since 0.1.0
59
+ attr_reader :program
60
+
61
+ # @return [String]
62
+ #
63
+ # @api private
64
+ # @since 0.1.0
65
+ attr_reader :prefix_form
66
+
67
+ # @return [Array<String>]
68
+ #
69
+ # @api private
70
+ # @since 0.1.0
71
+ attr_reader :tokens
72
+
73
+ # @param token_series [Array<String>]
74
+ # @return [Array<Jaina::Parser::Expression::Operator::Abstract>]
75
+ #
76
+ # @api private
77
+ # @since 0.1.0
78
+ def build_expression_tree(token_series)
79
+ current_token = extract_second_token(token_series)
80
+ return if current_token.nil?
81
+
82
+ case
83
+ when terminal?(current_token)
84
+ build_terminal_expression(current_token)
85
+ when non_terminal?(current_token)
86
+ build_non_terminal_expression(current_token, token_series)
87
+ end
88
+ end
89
+
90
+ # @param token_series [Array<String>]
91
+ # @return [String, NilClass]
92
+ #
93
+ # @api private
94
+ # @since 0.1.0
95
+ def extract_second_token(token_series)
96
+ token_series.shift
97
+ end
98
+
99
+ # @param current_token [String]
100
+ # @return [Jaina::Parser::Expression::Operator::Abstract]
101
+ #
102
+ # @api private
103
+ # @since 0.1.0
104
+ def build_terminal_expression(current_token)
105
+ Jaina::Parser::Expression.build(current_token)
106
+ end
107
+
108
+ # @param current_token [String]
109
+ # @param token_series [Array<String>]
110
+ # @return [Jaina::Parser::Expression::Operator::Abstract]
111
+ #
112
+ # @api private
113
+ # @since 0.1.0
114
+ def build_non_terminal_expression(current_token, token_series)
115
+ case
116
+ when acts_as_unary_term?(current_token)
117
+ Jaina::Parser::Expression.build(
118
+ current_token,
119
+ build_expression_tree(token_series)
120
+ )
121
+ when acts_as_binary_term?(current_token)
122
+ Jaina::Parser::Expression.build(
123
+ current_token,
124
+ build_expression_tree(token_series),
125
+ build_expression_tree(token_series)
126
+ )
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.1.0
5
+ class Jaina::Parser::AST
6
+ require_relative './ast/tree'
7
+ require_relative './ast/tree_builder'
8
+ require_relative './ast/evaluator'
9
+ require_relative './ast/context'
10
+
11
+ class << self
12
+ # @param program [String] Program string in prefix form
13
+ # @return [Jaina::Parser::AST]
14
+ #
15
+ # @api private
16
+ # @since 0.1.0
17
+ def build(program)
18
+ ast_tree = Jaina::Parser::AST::TreeBuilder.build(program)
19
+ new(program, ast_tree)
20
+ end
21
+
22
+ # @param program [String] Program string in prefix form
23
+ # @return [Any]
24
+ #
25
+ # @api private
26
+ # @since 0.1.0
27
+ def evaluate(program)
28
+ build(program).evaluate
29
+ end
30
+ end
31
+
32
+ # @return [Jaina::Pasrer::AST::Tree]
33
+ #
34
+ # @api private
35
+ # @since 0.1.0
36
+ attr_reader :ast_tree
37
+
38
+ # @return [String]
39
+ #
40
+ # @api private
41
+ # @since 0.1.0
42
+ attr_reader :program
43
+
44
+ # @param program [String]
45
+ # @param ast_tree [Jaina::Parser::AST::Tree]
46
+ # @return [void]
47
+ #
48
+ # @api private
49
+ # @since 0.1.0
50
+ def initialize(program, ast_tree)
51
+ @program = program.dup.tap(&:freeze)
52
+ @ast_tree = ast_tree
53
+ end
54
+
55
+ # @return [Any]
56
+ #
57
+ # @api private
58
+ # @since 0.1.0
59
+ def evaluate
60
+ Jaina::Parser::AST::Evaluator.evaluate(self)
61
+ end
62
+ end