jaina 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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