pycplus 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 (9) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +93 -0
  3. data/bin/pycplus +26 -0
  4. data/lib/STL.rb +12 -0
  5. data/lib/lang.rb +265 -0
  6. data/lib/nodes.rb +313 -0
  7. data/lib/rdparse.rb +231 -0
  8. data/lib/test.rb +161 -0
  9. metadata +71 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: dec5bac15d4c28625dd8140e84ce9a33792b8ae7eca11615ef535c356482ddab
4
+ data.tar.gz: 5f2ea920f42d8215211c584b9b173a3f5e5243d939fca2d70e3381116ca427d1
5
+ SHA512:
6
+ metadata.gz: 53428ab27dfd59f9c5dd9e9654bef5edbeda3bd3832943b351d459e073f309f048a60ba7a0a473d8ead2af2780818a09951e58b5e27f7626090744a05e86132c
7
+ data.tar.gz: '07692f12111a97f02724ae0fc3ec5c3ba535fc97306e29eaf2021a3ed0bc070a76d4180ebbce2785928324b03412bb1d521a6c3e403a86290331735ed5f7aa2f'
data/README.md ADDED
@@ -0,0 +1,93 @@
1
+ # TDP019
2
+
3
+
4
+
5
+ ## Getting started
6
+
7
+ To make it easy for you to get started with GitLab, here's a list of recommended next steps.
8
+
9
+ Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
10
+
11
+ ## Add your files
12
+
13
+ - [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
14
+ - [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
15
+
16
+ ```
17
+ cd existing_repo
18
+ git remote add origin https://gitlab.liu.se/andjo858-ip/tdp019.git
19
+ git branch -M main
20
+ git push -uf origin main
21
+ ```
22
+
23
+ ## Integrate with your tools
24
+
25
+ - [ ] [Set up project integrations](https://gitlab.liu.se/andjo858-ip/tdp019/-/settings/integrations)
26
+
27
+ ## Collaborate with your team
28
+
29
+ - [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
30
+ - [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
31
+ - [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
32
+ - [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
33
+ - [ ] [Set auto-merge](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
34
+
35
+ ## Test and Deploy
36
+
37
+ Use the built-in continuous integration in GitLab.
38
+
39
+ - [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
40
+ - [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing (SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
41
+ - [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
42
+ - [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
43
+ - [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
44
+
45
+ ***
46
+
47
+ # Editing this README
48
+
49
+ When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thanks to [makeareadme.com](https://www.makeareadme.com/) for this template.
50
+
51
+ ## Suggestions for a good README
52
+
53
+ Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
54
+
55
+ ## Name
56
+ Choose a self-explaining name for your project.
57
+
58
+ ## Description
59
+ Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
60
+
61
+ ## Badges
62
+ On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
63
+
64
+ ## Visuals
65
+ Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
66
+
67
+ ## Installation
68
+ Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
69
+
70
+ ## Usage
71
+ Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
72
+
73
+ ## Support
74
+ Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
75
+
76
+ ## Roadmap
77
+ If you have ideas for releases in the future, it is a good idea to list them in the README.
78
+
79
+ ## Contributing
80
+ State if you are open to contributions and what your requirements are for accepting them.
81
+
82
+ For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
83
+
84
+ You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
85
+
86
+ ## Authors and acknowledgment
87
+ Show your appreciation to those who have contributed to the project.
88
+
89
+ ## License
90
+ For open source projects, say how it is licensed.
91
+
92
+ ## Project status
93
+ If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
data/bin/pycplus ADDED
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../lib/lang.rb'
5
+
6
+
7
+ if ARGV.empty?
8
+ puts "No arguments provided."
9
+ else
10
+ arg = ARGV[0]
11
+
12
+ @lang_parser = MyLang.new
13
+
14
+ if File.file?(arg)
15
+ extension = File.extname(arg)
16
+
17
+ if extension == ".pcp"
18
+ @lang_parser.log(false)
19
+ @lang_parser.parse_file(arg)
20
+ else
21
+ puts "File is not a .pcp file, make sure to use the correct extension."
22
+ end
23
+ else
24
+ puts "Unable to find file #{arg}"
25
+ end
26
+ end
data/lib/STL.rb ADDED
@@ -0,0 +1,12 @@
1
+ def print(x)
2
+ pp x
3
+ end
4
+
5
+ module ArrayFunctions
6
+ def self.pop()
7
+ pop
8
+ end
9
+
10
+ def self.push()
11
+ end
12
+ end
data/lib/lang.rb ADDED
@@ -0,0 +1,265 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative 'rdparse.rb'
4
+ require_relative 'nodes.rb'
5
+
6
+ class MyLang
7
+
8
+ def initialize
9
+
10
+ @lang_parser = Parser.new("lang parser") do
11
+ token(/\n/)
12
+ # token(/\\/)
13
+ # token(/"/) {|m| m}
14
+ token(/"[^"]"/) {|m| m.chars}
15
+ token(/\s+/)
16
+ token(/if/) {|m| m}
17
+ token(/>=/) {|m| m}
18
+ token(/<=/) {|m| m}
19
+ token(/==/) {|m| m}
20
+ token(/!=/) {|m| m}
21
+ token(/\+=/) {|m| m}
22
+ token(/\-=/) {|m| m}
23
+ token(/&&/) {|m| m}
24
+ token(/\|\|/) {|m| m}
25
+ token(/true/) {|m| m}
26
+ token(/false/) {|m| m}
27
+ token(/return/) {|m| m}
28
+ token(/def/) {|m| m}
29
+ token(/while/) {|m| m}
30
+ token(/\d+\.\d+/) {|m| m.to_f}
31
+ token(/\d+/) {|m| m.to_i}
32
+ token(/\w+/) {|m| m}
33
+ token(/./) {|m| m}
34
+
35
+ start :program do
36
+ match(:statements) {|m| ProgramNode.new(m)}
37
+ end
38
+
39
+ rule :statements do
40
+ match(:statements, :statement) {|a,b| a << b}
41
+ match(:statement) {|a| [a]}
42
+ end
43
+
44
+ rule :statement do
45
+ match(:assignment, ';')
46
+ match(:function_def)
47
+ match(:function_call, ';')
48
+ match(:return, ';')
49
+ match(:if_statement)
50
+ match(:while_statement)
51
+ end
52
+
53
+ rule :assignment do
54
+ match(:identifier, :assignment_OP, :container) {|a, b, c| AssignmentNode.new(a,b,c)}
55
+ match(:identifier, :assignment_OP, :logical_expr) {|a, b, c| AssignmentNode.new(a,b,c)}
56
+ match(:identifier, :assignment_OP, :function_call) {|a, b, c| AssignmentNode.new(a,b,c)}
57
+ end
58
+
59
+ rule :assignment_OP do
60
+ match('=') {|a| a}
61
+ match('+=') {|a| a}
62
+ match('-=') {|a| a}
63
+ end
64
+
65
+ rule :function_def do
66
+ match('def', :identifier, '(', :parameters , ')', :block) {|_, a, _, b, _, c| FunctiondefNode.new(a,c,b)}
67
+ match('def', :identifier, '(', ')', :block) {|_, a, _, _, b| FunctiondefNode.new(a,b)}
68
+ end
69
+
70
+ rule :parameters do
71
+ match(:parameters,',' ,:parameter) {|a ,_ , b| a << b}
72
+ match(:parameter) {|a| [a]}
73
+ end
74
+
75
+ rule :parameter do
76
+ match(:identifier)
77
+ end
78
+
79
+ rule :block do
80
+ match('{', :statements, '}') {|_,a,_| BlockNode.new(a)}
81
+ match('{', '}') {|_,_| BlockNode.new()}
82
+ end
83
+
84
+ rule :function_call do
85
+ match(:identifier,'(', :arguments, ')') {|a, _, b, _| FunctioncallNode.new(a,b)}
86
+ match(:identifier,'(', ')') {|a, _, _| FunctioncallNode.new(a)}
87
+ # match(:identifier, '.', :identifier,'(', :arguments, ')') {|a, _, b, _| FunctioncallNode.new(a,b)}
88
+ # match(:identifier, '.', :identifier,'(',')')
89
+ end
90
+
91
+ rule :arguments do
92
+ match(:arguments, ',', :argument) {|a ,_ , b| a << b}
93
+ match(:argument) {|a| [a]}
94
+ end
95
+
96
+ rule :argument do
97
+ match(:logical_expr)
98
+ match(:function_call)
99
+ end
100
+
101
+ rule :return do
102
+ match('return', :logical_expr) {|_, a| ReturnNode.new(a)}
103
+ match('return', :function_call) {|_, a| ReturnNode.new(a)}
104
+ end
105
+
106
+ rule :while_statement do
107
+ match('while', '(', :logical_expr, ')', :block) {|_,_,a,_,b| WhileNode.new(a,b)}
108
+ end
109
+
110
+ rule :if_statement do
111
+ match('if', '(', :logical_expr, ')', :block) {|_,_,a,_,b| IfNode.new(a,b)}
112
+ end
113
+
114
+ rule :logical_expr do
115
+ match(:logical_expr, :logical_OP, :comparsion_expr) {|a,b,c| LogicalNode.new(a,b,c)}
116
+ match(:comparsion_expr)
117
+ end
118
+
119
+ rule :logical_OP do
120
+ match('&&') {|a| a}
121
+ match('||') {|a| a}
122
+ end
123
+
124
+ rule :comparsion_expr do
125
+ match(:comparsion_expr, :comparsion_OP, :addition_expr) {|a,b,c| ComparisonNode.new(a,b,c)}
126
+ match(:addition_expr)
127
+ end
128
+
129
+ rule :comparsion_OP do
130
+ match('<') {|a| a}
131
+ match('>') {|a| a}
132
+ match('<=') {|a| a}
133
+ match('>=') {|a| a}
134
+ match('==') {|a| a}
135
+ match('!=') {|a| a}
136
+ end
137
+
138
+ rule :addition_expr do
139
+ match(:addition_expr, :addition_OP, :multiplication_expr) {|a, b, c| ArithmeticNode.new(a,b,c)}
140
+ match(:multiplication_expr)
141
+ end
142
+
143
+ rule :addition_OP do
144
+ match('+') {|a| a}
145
+ match('-') {|a| a}
146
+ end
147
+
148
+ rule :multiplication_expr do
149
+ match(:multiplication_expr, :multiplication_OP, :power_expr) {|a, b, c| ArithmeticNode.new(a,b,c)}
150
+ match(:power_expr)
151
+ end
152
+
153
+ rule :multiplication_OP do
154
+ match('%') {|a| a}
155
+ match('*') {|a| a}
156
+ match('/') {|a| a}
157
+ end
158
+
159
+ rule :power_expr do
160
+ match(:unary_expr, :power_OP, :power_expr) {|a, b, c| ArithmeticNode.new(a,b,c)}
161
+ match(:unary_expr)
162
+ end
163
+
164
+ rule :power_OP do
165
+ match('^') {|_| '**'}
166
+ end
167
+
168
+ rule :unary_expr do
169
+ match(:unary_OP, :unary_expr) {|a, b| UnaryNode.new(a,b)}
170
+ match(:binary_operand)
171
+ end
172
+
173
+ rule :unary_OP do
174
+ match('!') {|a| a}
175
+ match('-') {|a| a}
176
+ end
177
+
178
+ rule :binary_operand do
179
+ match(:bool)
180
+ match(:digit)
181
+ match(:function_call)
182
+ match(:identifier)
183
+ match('(', :logical_expr, ')') {|_, a, _| a}
184
+ end
185
+
186
+ rule :container do
187
+ match(:string)
188
+ match(:array)
189
+ end
190
+
191
+ rule :array do
192
+ match('[', :elements , ']') {|_, a, _| ArrayNode.new(a)}
193
+ match('[', ']') {|_,_| ArrayNode.new()}
194
+ end
195
+
196
+ rule :elements do
197
+ match(:elements, ',', :element) {|a ,_ , b| a << b}
198
+ match(:element) {|a| [a]}
199
+ end
200
+
201
+ rule :element do
202
+ match(:logical_expr)
203
+ match(:container)
204
+ end
205
+
206
+ rule :string do
207
+ match('"', :chars, '"') {|_, a, _| StringNode.new(a)}
208
+ match('"', '"') {|_, _| StringNode.new()}
209
+ end
210
+
211
+ rule :chars do
212
+ match(:chars, :char) {|a , b| a << b}
213
+ match(:char) {|a| [a]}
214
+ end
215
+
216
+ rule :atom do
217
+ match(:bool)
218
+ match(:digit)
219
+ match(:char)
220
+ match(:identifier)
221
+ end
222
+
223
+ rule :char do
224
+ match(/./) {|a| CharNode.new(a)}
225
+ end
226
+
227
+ rule :bool do
228
+ match('true') {|_| BoolNode.new(true)}
229
+ match('false') {|_| BoolNode.new(false)}
230
+ end
231
+
232
+ rule :digit do
233
+ match(Float) {|a| DigitNode.new(a)}
234
+ match(Integer) {|a| DigitNode.new(a)}
235
+ end
236
+
237
+ rule :identifier do
238
+ match(/_?[a-zA-Z*]\w*/) {|a| IdentifierNode.new(a.to_sym)}
239
+ end
240
+ end
241
+ end
242
+
243
+ def parse_file(filename)
244
+ file = File.open(filename)
245
+ file_data = file.read
246
+ result = @lang_parser.parse(file_data)
247
+ return result.evaluate
248
+ end
249
+
250
+ def parse_string(str, display_output = false)
251
+ output = @lang_parser.parse(str)
252
+ if display_output
253
+ puts "=> #{output}"
254
+ end
255
+ return output.evaluate
256
+ end
257
+
258
+ def log(state = true)
259
+ if state
260
+ @lang_parser.logger.level = Logger::DEBUG
261
+ else
262
+ @lang_parser.logger.level = Logger::WARN
263
+ end
264
+ end
265
+ end
data/lib/nodes.rb ADDED
@@ -0,0 +1,313 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative 'STL.rb'
4
+
5
+ class Scope
6
+ attr_reader :variables, :functions
7
+ def initialize(parent_scope = nil)
8
+ @variables = {}
9
+ @functions = {}
10
+ @parent_scope = parent_scope
11
+ end
12
+
13
+ def set_variable(name, op, expression)
14
+ case op
15
+ when '='
16
+ @variables[name] = expression
17
+ when '+='
18
+ @variables[name] += expression
19
+ when '-='
20
+ @variables[name] -= expression
21
+ end
22
+
23
+ return unless @parent_scope
24
+
25
+ variable_scope = @parent_scope.get_scope(name, :variables)
26
+
27
+ if variable_scope
28
+ variable_scope.set_variable(name, op, expression)
29
+ end
30
+ end
31
+
32
+ def get_identifier(name, id_type)
33
+ scope = get_scope(name, id_type)
34
+ if scope
35
+ return scope.instance_variable_get("@#{id_type}")[name]
36
+ else
37
+ raise "[#{id_type}] #{name} not found."
38
+ end
39
+ end
40
+
41
+ def get_scope(name, id_type)
42
+ if instance_variable_get("@#{id_type}").has_key?(name)
43
+ return self
44
+ elsif @parent_scope
45
+ return @parent_scope.get_scope(name, id_type)
46
+ end
47
+ end
48
+
49
+ def set_function(name, block, parameters)
50
+ @functions[name] = {:parameters => parameters, :block => block}
51
+ if @parent_scope
52
+ function_scope = @parent_scope.get_scope(name, :functions)
53
+ end
54
+ if function_scope
55
+ function_scope.set_function(name, block, parameters)
56
+ end
57
+ end
58
+
59
+ # def initialize_STL
60
+ # return unless defined?(STL)
61
+ # # STL.methods(false).each do |name|
62
+ # # method_object = STL.method(name)
63
+ # # @functions[name] = {:parameters => method_object.parameters.flatten[1..-1], :block => method_object}
64
+ # # end
65
+ # STL.singleton_methods(false).each do |name|
66
+ # method_object = STL.method(name)
67
+ # parameters = method_object.parameters.map { |type, name| name }.compact
68
+ # @functions[name] = { parameters: parameters, block: method_object }
69
+ # end
70
+
71
+ # STL.instance_methods(false).each do |name|
72
+ # method_object = STL.instance_method(name)
73
+ # parameters = method_object.parameters.map { |type, name| name }.compact
74
+ # @functions[name] = { parameters: parameters, block: method_object }
75
+ # end
76
+ # end
77
+ end
78
+
79
+
80
+ class BlockNode
81
+ def initialize(statements = nil)
82
+ @block = statements
83
+ end
84
+
85
+ def evaluate(scope)
86
+ return unless @block
87
+ @block.each do |statement|
88
+ result = statement.evaluate(scope)
89
+ return result if result.is_a?(ReturnValue)
90
+ end
91
+ end
92
+ end
93
+
94
+ class FunctiondefNode
95
+ def initialize(identifier, block, parameters = [])
96
+ @identifier = identifier.value
97
+ @parameters = parameters
98
+ @block = block
99
+ end
100
+
101
+ def evaluate(scope)
102
+ params = @parameters.map {|parameter| parameter.value}
103
+ scope.set_function(@identifier, @block, params)
104
+ end
105
+ end
106
+
107
+ class FunctioncallNode
108
+ def initialize(identifier, arguments = [])
109
+ @identifier = identifier.value
110
+ @arguments = arguments
111
+ end
112
+
113
+ def validate_arguments(parameters)
114
+ if @arguments.size != parameters.size
115
+ raise "Wrong number of arguments (given: #{@arguments.size} expected: #{parameters.size})."
116
+ end
117
+ end
118
+
119
+ def evaluate_method(method, scope)
120
+ args = @arguments.map {|arg| arg.evaluate(scope)}
121
+ method.call(*args)
122
+ end
123
+
124
+ def evaluate_block(parameters, block, scope)
125
+ new_scope = Scope.new(scope)
126
+
127
+ parameters.zip(@arguments).each do |parameter, argument|
128
+ new_scope.set_variable(parameter, '=', argument.evaluate(scope))
129
+ end
130
+
131
+ result = block.evaluate(new_scope)
132
+ return result.value if result.is_a?(ReturnValue)
133
+ end
134
+
135
+ def evaluate(scope)
136
+ function_scope = scope.get_scope(@identifier, :functions)
137
+ if !function_scope
138
+ if self.respond_to?(@identifier, true)
139
+ method_object = method(@identifier)
140
+ parameters = method_object.parameters.map {|type, name| name}.compact
141
+ validate_arguments(parameters)
142
+ evaluate_method(method_object, scope)
143
+ else
144
+ raise "Function #{@identifier} not found."
145
+ end
146
+ else
147
+ function = function_scope.get_identifier(@identifier, :functions)
148
+ parameters = function[:parameters]
149
+ block = function[:block]
150
+ validate_arguments(parameters)
151
+ evaluate_block(parameters, block, scope)
152
+ end
153
+ end
154
+ end
155
+
156
+ class ProgramNode
157
+ def initialize(statements)
158
+ @statements = statements
159
+ end
160
+
161
+ def evaluate
162
+ global_scope = Scope.new
163
+ # global_scope.initialize_STL
164
+
165
+ @statements.each do |statement|
166
+ result = statement.evaluate(global_scope)
167
+ return result.value if statement.is_a?(ReturnNode)
168
+ end
169
+ return
170
+ end
171
+ end
172
+
173
+ class ReturnValue
174
+ attr_accessor :value
175
+ def initialize(value)
176
+ @value = value
177
+ end
178
+ end
179
+
180
+ class ReturnNode
181
+ def initialize(expression)
182
+ @expression = expression
183
+ end
184
+
185
+ def evaluate(scope)
186
+ return ReturnValue.new(@expression.evaluate(scope))
187
+ end
188
+ end
189
+
190
+
191
+ class BinaryexpressionNode
192
+ def initialize(lhs, op, rhs)
193
+ @lhs = lhs
194
+ @op = op
195
+ @rhs = rhs
196
+ end
197
+
198
+ def evaluate(scope)
199
+ return @lhs.evaluate(scope).send(@op, @rhs.evaluate(scope))
200
+ end
201
+ end
202
+
203
+ class LogicalNode < BinaryexpressionNode
204
+ def evaluate(scope)
205
+ return eval("#{@lhs.evaluate(scope)}#{@op}#{@rhs.evaluate(scope)}") # Not possible to use send-method since ruby TrueClass (Ruby bools) has no method for && or ||.
206
+ end
207
+ end
208
+
209
+ class ComparisonNode < BinaryexpressionNode
210
+ end
211
+
212
+ class ArithmeticNode < BinaryexpressionNode
213
+ end
214
+
215
+
216
+ class UnaryNode
217
+ def initialize(op, operand)
218
+ @op = op
219
+ @operand = operand
220
+ end
221
+ def evaluate(scope)
222
+ return eval("#{@op}#{@operand.evaluate(scope)}") # Not possible to use send-method.
223
+ end
224
+ end
225
+
226
+ class AssignmentNode
227
+ def initialize(identifier, op, expression)
228
+ @identifier = identifier.value
229
+ @op = op
230
+ @expression = expression
231
+ end
232
+
233
+ def evaluate(scope)
234
+ scope.set_variable(@identifier, @op, @expression.evaluate(scope))
235
+ end
236
+ end
237
+
238
+
239
+ class ControlflowNode
240
+ def initialize(expression, block)
241
+ @expression = expression
242
+ @block = block
243
+ end
244
+ end
245
+
246
+ class IfNode < ControlflowNode
247
+ def evaluate(scope)
248
+ if @expression.evaluate(scope)
249
+ @block.evaluate(scope)
250
+ end
251
+ end
252
+ end
253
+
254
+ class WhileNode < ControlflowNode
255
+ def evaluate(scope)
256
+ while @expression.evaluate(scope)
257
+ result = @block.evaluate(scope)
258
+ end
259
+ end
260
+ end
261
+
262
+
263
+ # Unsure about implementation, will check with project assistant
264
+ class ArrayNode
265
+ def initialize(elements = [])
266
+ @elements = elements
267
+ @data_container = {}
268
+ end
269
+
270
+ def evaluate(scope)
271
+ @elements.each_with_index do |element, index|
272
+ @data_container[index] = element.evaluate(scope)
273
+ end
274
+ end
275
+ end
276
+
277
+
278
+ # Not implemented yet
279
+ class StringNode
280
+ def initialize(chars = [])
281
+ @chars = chars
282
+ end
283
+
284
+ def evaluate(scope)
285
+ end
286
+ end
287
+
288
+
289
+ class PrimitiveNode
290
+ attr_reader :value
291
+ def initialize(value)
292
+ @value = value
293
+ end
294
+
295
+ def evaluate(scope)
296
+ return @value
297
+ end
298
+ end
299
+
300
+ class IdentifierNode < PrimitiveNode
301
+ def evaluate(scope)
302
+ return scope.get_identifier(@value, :variables)
303
+ end
304
+ end
305
+
306
+ class CharNode < PrimitiveNode
307
+ end
308
+
309
+ class DigitNode < PrimitiveNode
310
+ end
311
+
312
+ class BoolNode < PrimitiveNode
313
+ end
data/lib/rdparse.rb ADDED
@@ -0,0 +1,231 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # This file is called rdparse.rb because it implements a Recursive
4
+ # Descent Parser. Read more about the theory on e.g.
5
+ # http://en.wikipedia.org/wiki/Recursive_descent_parser
6
+
7
+ # 2010-02-11 New version of this file for the 2010 instance of TDP007
8
+ # which handles false return values during parsing, and has an easy way
9
+ # of turning on and off debug messages.
10
+ # 2014-02-16 New version that handles { false } blocks and :empty tokens.
11
+
12
+ require 'logger'
13
+
14
+ class Rule
15
+
16
+ # A rule is created through the rule method of the Parser class, like this:
17
+ # rule :term do
18
+ # match(:term, '*', :dice) {|a, _, b| a * b }
19
+ # match(:term, '/', :dice) {|a, _, b| a / b }
20
+ # match(:dice)
21
+ # end
22
+
23
+ Match = Struct.new :pattern, :block
24
+
25
+ def initialize(name, parser)
26
+ @logger = parser.logger
27
+ # The name of the expressions this rule matches
28
+ @name = name
29
+ # We need the parser to recursively parse sub-expressions occurring
30
+ # within the pattern of the match objects associated with this rule
31
+ @parser = parser
32
+ @matches = []
33
+ # Left-recursive matches
34
+ @lrmatches = []
35
+ end
36
+
37
+ # Add a matching expression to this rule, as in this example:
38
+ # match(:term, '*', :dice) {|a, _, b| a * b }
39
+ # The arguments to 'match' describe the constituents of this expression.
40
+ def match(*pattern, &block)
41
+ match = Match.new(pattern, block)
42
+ # If the pattern is left-recursive, then add it to the left-recursive set
43
+ if pattern[0] == @name
44
+ pattern.shift
45
+ @lrmatches << match
46
+ else
47
+ @matches << match
48
+ end
49
+ end
50
+
51
+ def parse
52
+ # Try non-left-recursive matches first, to avoid infinite recursion
53
+ match_result = try_matches(@matches)
54
+ return nil if match_result.nil?
55
+ loop do
56
+ result = try_matches(@lrmatches, match_result)
57
+ return match_result if result.nil?
58
+ match_result = result
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ # Try out all matching patterns of this rule
65
+ def try_matches(matches, pre_result = nil)
66
+ match_result = nil
67
+ # Begin at the current position in the input string of the parser
68
+ start = @parser.pos
69
+ matches.each do |match|
70
+ # pre_result is a previously available result from evaluating expressions
71
+ result = pre_result.nil? ? [] : [pre_result]
72
+
73
+ # We iterate through the parts of the pattern, which may be e.g.
74
+ # [:expr,'*',:term]
75
+ match.pattern.each_with_index do |token,index|
76
+
77
+ # If this "token" is a compound term, add the result of
78
+ # parsing it to the "result" array
79
+ if @parser.rules[token]
80
+ result << @parser.rules[token].parse
81
+ if result.last.nil?
82
+ result = nil
83
+ break
84
+ end
85
+ @logger.debug("Matched '#{@name} = #{match.pattern[index..-1].inspect}'")
86
+ else
87
+ # Otherwise, we consume the token as part of applying this rule
88
+ nt = @parser.expect(token)
89
+ if nt
90
+ result << nt
91
+ if @lrmatches.include?(match.pattern) then
92
+ pattern = [@name]+match.pattern
93
+ else
94
+ pattern = match.pattern
95
+ end
96
+ @logger.debug("Matched token '#{nt}' as part of rule '#{@name} <= #{pattern.inspect}'")
97
+ else
98
+ result = nil
99
+ break
100
+ end
101
+ end # pattern.each
102
+ end # matches.each
103
+ if result
104
+ if match.block
105
+ match_result = match.block.call(*result)
106
+ else
107
+ match_result = result[0]
108
+ end
109
+ @logger.debug("'#{@parser.string[start..@parser.pos-1]}' matched '#{@name}' and generated '#{match_result.inspect}'") unless match_result.nil?
110
+ break
111
+ else
112
+ # If this rule did not match the current token list, move
113
+ # back to the scan position of the last match
114
+ @parser.pos = start
115
+ end
116
+ end
117
+
118
+ return match_result
119
+ end
120
+ end
121
+
122
+ class Parser
123
+
124
+ attr_accessor :pos
125
+ attr_reader :rules, :string, :logger
126
+
127
+ class ParseError < RuntimeError
128
+ end
129
+
130
+ def initialize(language_name, &block)
131
+ @logger = Logger.new(STDOUT)
132
+ @lex_tokens = []
133
+ @rules = {}
134
+ @start = nil
135
+ @language_name = language_name
136
+ instance_eval(&block)
137
+ end
138
+
139
+ # Tokenize the string into small pieces
140
+ def tokenize(string)
141
+ @tokens = []
142
+ @string = string.clone
143
+ until string.empty?
144
+ # Unless any of the valid tokens of our language are the prefix of
145
+ # 'string', we fail with an exception
146
+ raise ParseError, "unable to lex '#{string}" unless @lex_tokens.any? do |tok|
147
+ match = tok.pattern.match(string)
148
+ # The regular expression of a token has matched the beginning of 'string'
149
+ if match
150
+ @logger.debug("Token #{match[0]} consumed")
151
+ # Also, evaluate this expression by using the block
152
+ # associated with the token
153
+ @tokens << tok.block.call(match.to_s) if tok.block
154
+ # consume the match and proceed with the rest of the string
155
+ string = match.post_match
156
+ true
157
+ else
158
+ # this token pattern did not match, try the next
159
+ false
160
+ end # if
161
+ end # raise
162
+ end # until
163
+ end
164
+
165
+ def parse(string)
166
+ # First, split the string according to the "token" instructions given.
167
+ # Afterwards @tokens contains all tokens that are to be parsed.
168
+ tokenize(string)
169
+
170
+ # These variables are used to match if the total number of tokens
171
+ # are consumed by the parser
172
+ @pos = 0
173
+ @max_pos = 0
174
+ @expected = []
175
+ # Parse (and evaluate) the tokens received
176
+ result = @start.parse
177
+ # If there are unparsed extra tokens, signal error
178
+ if @pos != @tokens.size
179
+ raise ParseError, "Parse error. expected: '#{@expected.join(', ')}', found '#{@tokens[@max_pos]}'"
180
+ end
181
+ return result
182
+ end
183
+
184
+ def next_token
185
+ @pos += 1
186
+ return @tokens[@pos - 1]
187
+ end
188
+
189
+ # Return the next token in the queue
190
+ def expect(tok)
191
+ return tok if tok == :empty
192
+ t = next_token
193
+ if @pos - 1 > @max_pos
194
+ @max_pos = @pos - 1
195
+ @expected = []
196
+ end
197
+ return t if tok === t
198
+ @expected << tok if @max_pos == @pos - 1 && !@expected.include?(tok)
199
+ return nil
200
+ end
201
+
202
+ def to_s
203
+ "Parser for #{@language_name}"
204
+ end
205
+
206
+ private
207
+
208
+ LexToken = Struct.new(:pattern, :block)
209
+
210
+ def token(pattern, &block)
211
+ @lex_tokens << LexToken.new(Regexp.new('\\A' + pattern.source), block)
212
+ end
213
+
214
+ def start(name, &block)
215
+ rule(name, &block)
216
+ @start = @rules[name]
217
+ end
218
+
219
+ def rule(name,&block)
220
+ @current_rule = Rule.new(name, self)
221
+ @rules[name] = @current_rule
222
+ instance_eval &block # In practise, calls match 1..N times
223
+ @current_rule = nil
224
+ end
225
+
226
+ def match(*pattern, &block)
227
+ # Basically calls memberfunction "match(*pattern, &block)
228
+ @current_rule.send(:match, *pattern, &block)
229
+ end
230
+
231
+ end
data/lib/test.rb ADDED
@@ -0,0 +1,161 @@
1
+ require_relative '../lib/lang.rb'
2
+ require 'test/unit'
3
+
4
+ class TestFaculty < Test::Unit::TestCase
5
+ def setup
6
+ @lang_parser = MyLang.new
7
+ @lang_parser.log(false)
8
+ end
9
+
10
+ # # Test for simple addition
11
+ # def test_addition_expr
12
+ # assert_equal(1, @lang_parser.parse_string("return 0+1;"))
13
+ # assert_equal(0, @lang_parser.parse_string("return -1+1;"))
14
+ # assert_equal(-1, @lang_parser.parse_string("return -2+1;"))
15
+ # end
16
+
17
+ # # Test for simple subtraction
18
+ # def test_subtraction_expr
19
+ # assert_equal(1, @lang_parser.parse_string("return 2-1;"))
20
+ # assert_equal(0, @lang_parser.parse_string("return 1-1;"))
21
+ # assert_equal(-1, @lang_parser.parse_string("return 1-2;"))
22
+ # assert_equal(2, @lang_parser.parse_string("return 1--1;"))
23
+ # end
24
+
25
+ # # Test for simple multiplication
26
+ # def test_multiplication_expr
27
+ # assert_equal(1, @lang_parser.parse_string("return 1*1;"))
28
+ # assert_equal(0, @lang_parser.parse_string("return 0*1;"))
29
+ # assert_equal(-1, @lang_parser.parse_string("return -1*1;"))
30
+ # end
31
+
32
+ # # Test for simple division
33
+ # def test_division_expr
34
+ # assert_equal(1, @lang_parser.parse_string("return 1/1;"))
35
+ # assert_equal(0, @lang_parser.parse_string("return 0/1;"))
36
+ # assert_equal(-1, @lang_parser.parse_string("return -1/1;"))
37
+ # end
38
+
39
+ # # Test for simple modulo
40
+ # def test_modulo_expr
41
+ # assert_equal(0, @lang_parser.parse_string("return 9%3;"))
42
+ # assert_equal(1, @lang_parser.parse_string("return 5%2;"))
43
+ # end
44
+
45
+ # # Test for parentheses priority
46
+ # def test_parentheses_priority
47
+ # assert_equal(3, @lang_parser.parse_string("return (3-2)*3;"))
48
+ # assert_equal(7, @lang_parser.parse_string("return (3+4)*(6-2)/(2+2);"))
49
+ # assert_equal(1, @lang_parser.parse_string("return (5-3)/(1*2);"))
50
+ # end
51
+
52
+ # # test for power expressions
53
+ # def test_power_expr
54
+ # assert_equal(4, @lang_parser.parse_string("return 2^2;"))
55
+ # assert_equal(8, @lang_parser.parse_string("return 2^2*2;"))
56
+ # assert_equal(256, @lang_parser.parse_string("return 2^2^3;"))
57
+ # end
58
+
59
+ # # Test for variable names
60
+ # def test_variable_names
61
+ # assert_equal(1, @lang_parser.parse_string("a = 1; return a;"))
62
+ # assert_equal(1, @lang_parser.parse_string("_a = 1; return _a;"))
63
+ # assert_equal(1, @lang_parser.parse_string("A131 = 1; return A131;"))
64
+
65
+ # # Forbidden variable names throws parsing error
66
+ # assert_raises Parser::ParseError do
67
+ # @lang_parser.parse_string("1a = 1;")
68
+ # end
69
+ # assert_raises Parser::ParseError do
70
+ # @lang_parser.parse_string("_1 = 1;")
71
+ # end
72
+ # assert_raises Parser::ParseError do
73
+ # @lang_parser.parse_string("1 = 1;")
74
+ # end
75
+ # assert_raises Parser::ParseError do
76
+ # @lang_parser.parse_string("1 = 1;")
77
+ # end
78
+ # assert_raises Parser::ParseError do
79
+ # @lang_parser.parse_string("_a? = 1;")
80
+ # end
81
+ # end
82
+
83
+ # # Test for comparsion expressions
84
+ # def test_comparison_expr
85
+ # assert_equal(true, @lang_parser.parse_string("return 1 < 2;"))
86
+ # assert_equal(false, @lang_parser.parse_string("return 1 > 2;"))
87
+
88
+ # assert_equal(false, @lang_parser.parse_string("return 1 >= 2;"))
89
+ # assert_equal(true, @lang_parser.parse_string("return 2 >= 2;"))
90
+ # assert_equal(true, @lang_parser.parse_string("return 1 <= 2;"))
91
+ # assert_equal(false, @lang_parser.parse_string("return 3 <= 2;"))
92
+
93
+ # assert_equal(false, @lang_parser.parse_string("return 1 == 2;"))
94
+ # assert_equal(true, @lang_parser.parse_string("return 2 == 2;"))
95
+ # assert_equal(true, @lang_parser.parse_string("return false == false;"))
96
+ # assert_equal(true, @lang_parser.parse_string("a=2; return a == 2;"))
97
+ # assert_equal(false, @lang_parser.parse_string("a=2; return a == 3;"))
98
+
99
+ # assert_equal(true, @lang_parser.parse_string("return 1 != 2;"))
100
+ # assert_equal(false, @lang_parser.parse_string("return 2 != 2;"))
101
+ # end
102
+
103
+ # # Test for bool assignment
104
+ # def test_assignmen_bool
105
+ # assert_equal(false, @lang_parser.parse_string("a = false; return a;"))
106
+ # assert_equal(true, @lang_parser.parse_string("a = true; return a;"))
107
+ # end
108
+
109
+ # # Test for logical expressions
110
+ # def test_logical_expr
111
+ # assert_equal(false, @lang_parser.parse_string("a = true && false; return a;"))
112
+ # assert_equal(true, @lang_parser.parse_string("a = false || true; return a;"))
113
+ # assert_equal(true, @lang_parser.parse_string("a = true && false || true; return a;"))
114
+ # end
115
+
116
+ # # Test for if statements
117
+ # def test_if_statement
118
+ # assert_equal(11, @lang_parser.parse_string("a=1; if(10>1){b=10; a=a+b;} return a;"))
119
+ # assert_equal(1, @lang_parser.parse_string("a=1; if(a<1){b=10; a=a+b;} return a;"))
120
+
121
+ # # Raises error when variable has not been defined in if-statement
122
+ # assert_raises RuntimeError do
123
+ # @lang_parser.parse_string("if(10<1){a=33;} return a;")
124
+ # end
125
+ # end
126
+
127
+ # # Test for define function and call
128
+ # def test_function_def_and_call
129
+ # assert_equal(4, @lang_parser.parse_string("def test(z,x){a = z+x; return a+1;} b=test(1,2); return b;"))
130
+ # assert_equal(11, @lang_parser.parse_string("def test(){a = 10; return a+1;} return test();"))
131
+ # assert_equal(true, @lang_parser.parse_string("def test(x){return x && true;} return test(true);"))
132
+ # assert_equal(100, @lang_parser.parse_string("a=1; b=99; def test(x){c=x+b; return c;} return test(a);"))
133
+ # assert_equal(100, @lang_parser.parse_string("a=1; b=99; def test(x){a=x+b;} test(a); return a;"))
134
+
135
+ # # Raises error when functioncall with wrong number of arguments
136
+ # assert_raises RuntimeError do
137
+ # @lang_parser.parse_string("def test(){a = 10; return a+1;} return test(1);")
138
+ # end
139
+
140
+ # # Raises error when functioncall with wrong name
141
+ # assert_raises RuntimeError do
142
+ # @lang_parser.parse_string("def test(){a = 10; return a+1;} a(1);")
143
+ # end
144
+ # end
145
+
146
+ # # Test for while-statement
147
+ # def test_while_statement
148
+ # assert_equal(10, @lang_parser.parse_string("a=1; while(a<10){a=a+1;} return a;"))
149
+ # assert_equal(18, @lang_parser.parse_string("a=1; while(a<10){b=a*2; a=a+1;} return b;"))
150
+
151
+ # assert_raises RuntimeError do
152
+ # assert_equal(10, @lang_parser.parse_string("a=1; while(a>10){b=a+1;} return b;"))
153
+ # end
154
+ # end
155
+
156
+ # Test for recursion
157
+ def test_recursion
158
+ # assert_equal(720, @lang_parser.parse_string("def factorial(n){if(n==0 || n==1){return 1;} return n*factorial(n-1);} print(factorial(6);)"))
159
+ assert_equal(720, @lang_parser.parse_string("x=1; def hahatest(){ print(x); return x;} x=99; def tt(){x=22;} print(hahatest()); tt(); return x;"))
160
+ end
161
+ end
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pycplus
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Johannes
8
+ - Andreas
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2024-05-01 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: test-unit
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '3.6'
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 3.6.0
24
+ type: :runtime
25
+ prerelease: false
26
+ version_requirements: !ruby/object:Gem::Requirement
27
+ requirements:
28
+ - - "~>"
29
+ - !ruby/object:Gem::Version
30
+ version: '3.6'
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 3.6.0
34
+ description: This Ruby Gem provides a framework for creating and utilizing a domain-specific
35
+ language (DSL) developed as part of a school project at LiU (Linköping University).
36
+ email: test@test.com
37
+ executables:
38
+ - pycplus
39
+ extensions: []
40
+ extra_rdoc_files: []
41
+ files:
42
+ - README.md
43
+ - bin/pycplus
44
+ - lib/STL.rb
45
+ - lib/lang.rb
46
+ - lib/nodes.rb
47
+ - lib/rdparse.rb
48
+ - lib/test.rb
49
+ homepage: https://rubygems.org/gems/mytestdsl
50
+ licenses: []
51
+ metadata: {}
52
+ post_install_message:
53
+ rdoc_options: []
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ requirements: []
67
+ rubygems_version: 3.3.5
68
+ signing_key:
69
+ specification_version: 4
70
+ summary: A Ruby Gem for creating and using a domain-specific language (DSL)
71
+ test_files: []