pycplus 0.4.0 → 1.0.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 +4 -4
- data/README.md +22 -2
- data/bin/pycplus +50 -42
- data/lib/STL.rb +6 -4
- data/lib/nodes.rb +109 -21
- data/lib/pcpparse.rb +14 -8
- data/lib/rdparse.rb +19 -18
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 573f59a30af939f137fba7684ca507a4c0726598b9b98a9596761be93ced4906
|
4
|
+
data.tar.gz: 4158857a428507fa891adfb036516ff7cdd242532d69f68b8203e5131de34ed3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a43a5652ee52fe8bf6fe771ee78ca88ee4a695f8cee399994fc86d722437ccef6e09ccb66367066da7823b506d8a9ec96597b765a8288385e11c8f48a526b824
|
7
|
+
data.tar.gz: cbbd8b500dc0fc67bc3bc55936c67acdf2e324436ddcdde6951ff95ff6c479a2275afd193f4fba3139ad291e13ec2d2fcaa12abcdfafedd075e44ed8905aac41
|
data/README.md
CHANGED
@@ -1,3 +1,23 @@
|
|
1
|
-
#
|
1
|
+
# PyCPlus
|
2
2
|
|
3
|
-
|
3
|
+
PyCPlus is a custom programming language constructed on Ruby for a first-year school project at Linköping University,
|
4
|
+
part of the IP-program.
|
5
|
+
|
6
|
+
It includes common functionalities found in programming languages, featuring a clear and concise syntax.
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
To install PyCPlus, you can simply use RubyGems:
|
11
|
+
|
12
|
+
```bash
|
13
|
+
gem install pycplus
|
14
|
+
```
|
15
|
+
|
16
|
+
|
17
|
+
## Unit testing
|
18
|
+
|
19
|
+
Unit tests can be ran from the projects test directory.
|
20
|
+
|
21
|
+
```bash
|
22
|
+
rake
|
23
|
+
```
|
data/bin/pycplus
CHANGED
@@ -7,59 +7,67 @@ require_relative '../lib/pcpparse'
|
|
7
7
|
|
8
8
|
# Method to print gem version
|
9
9
|
def print_version
|
10
|
-
|
11
|
-
|
10
|
+
begin
|
11
|
+
spec = Gem.loaded_specs['pycplus']
|
12
|
+
puts "#{spec.name} version #{spec.version}"
|
13
|
+
rescue
|
14
|
+
begin
|
15
|
+
path = File.expand_path('../pycplus.gemspec', __dir__)
|
16
|
+
spec = Gem::Specification.load(path)
|
17
|
+
puts "#{spec.name} version #{spec.version}"
|
18
|
+
rescue
|
19
|
+
puts "Error: Unable to find version for pycplus gem"
|
20
|
+
end
|
21
|
+
end
|
12
22
|
end
|
13
23
|
|
14
|
-
def main
|
15
|
-
# Parse other command-line options using OptionParser
|
16
|
-
options = {}
|
17
|
-
OptionParser.new do |opts|
|
18
24
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
opts.on("-v", "--version", "Display version information") do
|
23
|
-
print_version
|
24
|
-
exit
|
25
|
-
end
|
25
|
+
# Parse other command-line options using OptionParser
|
26
|
+
options = {}
|
27
|
+
OptionParser.new do |opts|
|
26
28
|
|
27
|
-
|
28
|
-
options[:debug] = true
|
29
|
-
print("dhsdhshdshds")
|
30
|
-
end
|
31
|
-
end.parse!
|
29
|
+
opts.banner = "Usage: #{File.basename($PROGRAM_NAME)} [options] filename"
|
32
30
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
exit 1
|
31
|
+
opts.on("-h", "--help", "Display this help message") do
|
32
|
+
puts opts
|
33
|
+
exit
|
37
34
|
end
|
38
35
|
|
39
|
-
|
40
|
-
|
36
|
+
# version is handled as a special case as it doesn't make sense to use it alongside the other options
|
37
|
+
opts.on("-v", "--version", "Display version information") do
|
38
|
+
print_version
|
39
|
+
exit
|
40
|
+
end
|
41
41
|
|
42
|
-
|
43
|
-
|
44
|
-
@lang_parser.log(true)
|
45
|
-
else
|
46
|
-
@lang_parser.log(false)
|
42
|
+
opts.on("-d", "--debug", "Enable debug mode") do
|
43
|
+
options[:debug] = true
|
47
44
|
end
|
45
|
+
end.parse!
|
48
46
|
|
49
|
-
|
50
|
-
|
47
|
+
# Check if a filename argument is provided
|
48
|
+
if ARGV.empty?
|
49
|
+
puts "Error: No filename or arguments provided."
|
50
|
+
exit 1
|
51
|
+
end
|
51
52
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
53
|
+
filename = ARGV.shift
|
54
|
+
@lang_parser = Pycplus.new
|
55
|
+
|
56
|
+
# Enable debug mode if specified
|
57
|
+
if options[:debug]
|
58
|
+
@lang_parser.log(true)
|
59
|
+
else
|
60
|
+
@lang_parser.log(false)
|
60
61
|
end
|
61
62
|
|
63
|
+
if File.file?(filename)
|
64
|
+
extension = File.extname(filename)
|
62
65
|
|
63
|
-
if
|
64
|
-
|
65
|
-
|
66
|
+
if extension == ".pcp"
|
67
|
+
@lang_parser.parse_file(filename)
|
68
|
+
else
|
69
|
+
puts "Error: File is not a .pcp file. Please provide a file with the correct extension."
|
70
|
+
end
|
71
|
+
else
|
72
|
+
puts "Error: Unable to find file #{filename}"
|
73
|
+
end
|
data/lib/STL.rb
CHANGED
@@ -1,16 +1,18 @@
|
|
1
1
|
|
2
|
+
|
3
|
+
# Module for built-in functions.
|
2
4
|
module STL
|
3
5
|
def self.print(x)
|
4
6
|
pp x
|
5
7
|
return nil
|
6
8
|
end
|
7
9
|
|
8
|
-
def self.size(
|
9
|
-
unless
|
10
|
-
raise TypeError, "Function #{__method__} must be used on
|
10
|
+
def self.size(x)
|
11
|
+
unless x.is_a?(Array) || x.is_a?(String)
|
12
|
+
raise TypeError, "Function #{__method__} must be used on a string or array."
|
11
13
|
end
|
12
14
|
|
13
|
-
return
|
15
|
+
return x.length
|
14
16
|
end
|
15
17
|
|
16
18
|
def self.pop(array)
|
data/lib/nodes.rb
CHANGED
@@ -13,25 +13,36 @@ class Scope
|
|
13
13
|
|
14
14
|
# Set variable in current scope
|
15
15
|
def set_variable(name, op, expression)
|
16
|
+
|
17
|
+
# Checks if the variable has been used as a non-local variable, if true: raise NameError
|
16
18
|
if @non_local_variables.include?(name)
|
17
|
-
raise NameError, "
|
19
|
+
raise NameError, "Local identifier #{name} referenced before assignment"
|
18
20
|
end
|
21
|
+
|
19
22
|
if op == '='
|
20
23
|
@identifiers[name] = expression
|
21
24
|
else
|
25
|
+
|
26
|
+
# Checks if the variable is local, raise NameError if otherwise
|
22
27
|
unless @identifiers.has_key?(name)
|
23
|
-
raise NameError, "
|
28
|
+
raise NameError, "Local identifier #{name} referenced before assignment"
|
24
29
|
end
|
30
|
+
|
25
31
|
if op == '+='
|
26
32
|
@identifiers[name] += expression
|
27
33
|
else
|
28
|
-
@identifiers[name]
|
34
|
+
if @identifiers[name].is_a?(String) || expression.is_a?(String)
|
35
|
+
raise TypeError, "Operation #{op} is not allowed for identifier #{name} or the expression as they must not be strings"
|
36
|
+
else
|
37
|
+
@identifiers[name] -= expression
|
38
|
+
end
|
29
39
|
end
|
30
40
|
end
|
31
41
|
end
|
32
42
|
|
43
|
+
# Add non local variable used in scope
|
33
44
|
def set_non_local_variable(name)
|
34
|
-
unless @non_local_variables.include?(name)
|
45
|
+
unless @non_local_variables.include?(name) || is_function?(name)
|
35
46
|
@non_local_variables.append(name)
|
36
47
|
end
|
37
48
|
end
|
@@ -58,7 +69,9 @@ class Scope
|
|
58
69
|
# Checks if a identifier is a function.
|
59
70
|
def is_function?(name)
|
60
71
|
scope = get_scope(name)
|
61
|
-
|
72
|
+
return false unless scope
|
73
|
+
|
74
|
+
id_value = scope.get_identifier(name)
|
62
75
|
return id_value.is_a?(Hash)
|
63
76
|
end
|
64
77
|
|
@@ -68,23 +81,29 @@ class Scope
|
|
68
81
|
end
|
69
82
|
end
|
70
83
|
|
84
|
+
# **********************************************************************************
|
71
85
|
|
86
|
+
# Class for statements to be evaluated as a code-block.
|
72
87
|
class BlockNode
|
73
88
|
def initialize(statements = nil)
|
74
89
|
@block = statements
|
75
90
|
end
|
76
91
|
|
92
|
+
# Evaluate block based on scope.
|
77
93
|
def evaluate(scope)
|
78
|
-
return unless @block
|
94
|
+
return nil unless @block
|
79
95
|
result = nil
|
80
96
|
@block.each do |statement|
|
81
97
|
result = statement.evaluate(scope)
|
98
|
+
|
99
|
+
# Returns the result if the value is a return value. Needed for returns in nestled blocks.
|
82
100
|
return result if result.is_a?(ReturnValue)
|
83
101
|
end
|
84
102
|
return result
|
85
103
|
end
|
86
104
|
end
|
87
105
|
|
106
|
+
# Class for defining functions.
|
88
107
|
class FunctiondefNode
|
89
108
|
def initialize(identifier, block, parameters = [])
|
90
109
|
@identifier = identifier.value
|
@@ -92,34 +111,46 @@ class FunctiondefNode
|
|
92
111
|
@block = block
|
93
112
|
end
|
94
113
|
|
114
|
+
# Saves identifier with code-block and parameters in current scope
|
95
115
|
def evaluate(scope)
|
96
116
|
params = @parameters.map {|parameter| parameter.value}
|
97
117
|
scope.set_function(@identifier, @block, params)
|
98
118
|
end
|
99
119
|
end
|
100
120
|
|
121
|
+
|
122
|
+
# Class for calling functions.
|
101
123
|
class FunctioncallNode
|
102
|
-
|
124
|
+
|
125
|
+
include STL # Standard template library for the language (builtin-functions).
|
126
|
+
|
103
127
|
def initialize(identifier, object = nil, arguments = [])
|
104
128
|
@identifier = identifier.value
|
105
129
|
@arguments = arguments
|
106
130
|
@object = object
|
107
131
|
end
|
108
132
|
|
133
|
+
# Checks if the arguments given is the same number of expected parameters.
|
109
134
|
def validate_arguments(parameters)
|
110
135
|
if @arguments.size != parameters.size
|
111
136
|
raise ArgumentError, "Wrong number of arguments (given: #{@arguments.size} expected: #{parameters.size})."
|
112
137
|
end
|
113
138
|
end
|
114
139
|
|
115
|
-
|
140
|
+
# Calls builtin-function.
|
141
|
+
def evaluate_STL_function(scope)
|
116
142
|
args = @arguments.map {|arg| arg.evaluate(scope)}
|
117
143
|
result = STL.send(@identifier, *args)
|
118
144
|
return result
|
119
145
|
end
|
120
146
|
|
121
|
-
|
122
|
-
|
147
|
+
# Evaluate code-block of user defined function.
|
148
|
+
def evaluate_block(parameters, block, current_scope, declared_scope)
|
149
|
+
|
150
|
+
# Create new scope for function with the scope where the function was declared as its parent scope.
|
151
|
+
new_scope = Scope.new(declared_scope)
|
152
|
+
|
153
|
+
# Set local variables for function with values from arguments
|
123
154
|
parameters.zip(@arguments).each do |parameter, argument|
|
124
155
|
new_scope.set_variable(parameter, '=', argument.evaluate(current_scope))
|
125
156
|
end
|
@@ -130,22 +161,33 @@ class FunctioncallNode
|
|
130
161
|
|
131
162
|
def evaluate(scope)
|
132
163
|
|
164
|
+
# Check if scope of identifier. If it exist but is not defined as a function: raise TypeError
|
133
165
|
id_scope = scope.get_scope(@identifier)
|
134
166
|
if id_scope && !id_scope.is_function?(@identifier)
|
135
167
|
raise TypeError, "Identifier #{@identifier} is not defined as a function."
|
136
168
|
end
|
137
|
-
|
169
|
+
|
170
|
+
|
171
|
+
# Check if the function has been called as a method on a object (Ex. a.print();). Add object as a argument if true.
|
172
|
+
|
173
|
+
if @object && !@arguments.include?(@object)
|
138
174
|
@arguments.unshift(@object)
|
139
175
|
end
|
176
|
+
|
177
|
+
# Check if no identifier has been found in scope.
|
140
178
|
if !id_scope
|
179
|
+
|
180
|
+
# Check if function part of STL. if true: call function, else raise NameError.
|
141
181
|
if STL.respond_to?(@identifier)
|
142
182
|
method = STL.method(@identifier)
|
143
183
|
parameters = method.parameters
|
144
184
|
validate_arguments(parameters)
|
145
|
-
|
185
|
+
evaluate_STL_function(scope)
|
146
186
|
else
|
147
187
|
raise NameError, "Function #{@identifier} not defined."
|
148
188
|
end
|
189
|
+
|
190
|
+
# If identifier is user defined function, call it.
|
149
191
|
else
|
150
192
|
function = id_scope.get_identifier(@identifier)
|
151
193
|
parameters = function[:parameters]
|
@@ -156,6 +198,8 @@ class FunctioncallNode
|
|
156
198
|
end
|
157
199
|
end
|
158
200
|
|
201
|
+
|
202
|
+
# Root node for program. Stores statements and evaluates them.
|
159
203
|
class ProgramNode
|
160
204
|
attr_reader :statements
|
161
205
|
def initialize(statements)
|
@@ -169,10 +213,15 @@ class ProgramNode
|
|
169
213
|
result = statement.evaluate(global_scope)
|
170
214
|
return result.value if statement.is_a?(ReturnNode)
|
171
215
|
end
|
172
|
-
|
216
|
+
if result.is_a?(ReturnValue)
|
217
|
+
return result.value
|
218
|
+
else
|
219
|
+
return result
|
220
|
+
end
|
173
221
|
end
|
174
222
|
end
|
175
223
|
|
224
|
+
# Wrapper class for specifying that a value is a return value.
|
176
225
|
class ReturnValue
|
177
226
|
attr_accessor :value
|
178
227
|
def initialize(value)
|
@@ -180,6 +229,8 @@ class ReturnValue
|
|
180
229
|
end
|
181
230
|
end
|
182
231
|
|
232
|
+
|
233
|
+
# Class for return statements.
|
183
234
|
class ReturnNode
|
184
235
|
def initialize(expression)
|
185
236
|
@expression = expression
|
@@ -190,6 +241,8 @@ class ReturnNode
|
|
190
241
|
end
|
191
242
|
end
|
192
243
|
|
244
|
+
|
245
|
+
# Super class for arithmetic and logical expressions.
|
193
246
|
class BinaryexpressionNode
|
194
247
|
def initialize(lhs, op, rhs)
|
195
248
|
@lhs = lhs
|
@@ -199,6 +252,8 @@ class BinaryexpressionNode
|
|
199
252
|
|
200
253
|
def evaluate(scope)
|
201
254
|
rhs_value = @rhs.evaluate(scope)
|
255
|
+
|
256
|
+
# Dividing by 0 using using fdiv returns Infinity, raise ZeroDivisionError instead.
|
202
257
|
if @op == :fdiv && rhs_value == 0
|
203
258
|
raise ZeroDivisionError,"Division by 0 not possible."
|
204
259
|
end
|
@@ -206,29 +261,35 @@ class BinaryexpressionNode
|
|
206
261
|
end
|
207
262
|
end
|
208
263
|
|
264
|
+
# Class for logical expressions.
|
209
265
|
class LogicalNode < BinaryexpressionNode
|
210
266
|
def evaluate(scope)
|
211
|
-
|
267
|
+
|
268
|
+
# Not possible to use send-method since ruby TrueClass/FalseClass has no method for && or ||.
|
269
|
+
return eval("#{@lhs.evaluate(scope)}#{@op}#{@rhs.evaluate(scope)}")
|
212
270
|
end
|
213
271
|
end
|
214
272
|
|
273
|
+
# Wrapper class for comparison expressions
|
215
274
|
class ComparisonNode < BinaryexpressionNode
|
216
275
|
end
|
217
276
|
|
277
|
+
# Wrapper class for arithmetic expressions.
|
218
278
|
class ArithmeticNode < BinaryexpressionNode
|
219
279
|
end
|
220
280
|
|
221
|
-
|
281
|
+
# Class for unary expressions.
|
222
282
|
class UnaryNode
|
223
283
|
def initialize(op, operand)
|
224
284
|
@op = op
|
225
285
|
@operand = operand
|
226
286
|
end
|
227
287
|
def evaluate(scope)
|
228
|
-
return eval("#{@op}#{@operand.evaluate(scope)}")
|
288
|
+
return eval("#{@op}#{@operand.evaluate(scope)}")
|
229
289
|
end
|
230
290
|
end
|
231
291
|
|
292
|
+
# Class for assignment statements.
|
232
293
|
class AssignmentNode
|
233
294
|
def initialize(identifier, op, expression)
|
234
295
|
@identifier = identifier.value
|
@@ -236,12 +297,13 @@ class AssignmentNode
|
|
236
297
|
@expression = expression
|
237
298
|
end
|
238
299
|
|
300
|
+
# Set identifier based on operator.
|
239
301
|
def evaluate(scope)
|
240
302
|
scope.set_variable(@identifier, @op, @expression.evaluate(scope))
|
241
303
|
end
|
242
304
|
end
|
243
305
|
|
244
|
-
|
306
|
+
# Superclass for control flow statements (if/while).
|
245
307
|
class ControlflowNode
|
246
308
|
def initialize(expression, block)
|
247
309
|
@expression = expression
|
@@ -249,7 +311,10 @@ class ControlflowNode
|
|
249
311
|
end
|
250
312
|
end
|
251
313
|
|
314
|
+
# Class for if-statements.
|
252
315
|
class IfNode < ControlflowNode
|
316
|
+
|
317
|
+
# Evaluate if expression is true, if so evaluate block.
|
253
318
|
def evaluate(scope)
|
254
319
|
if @expression.evaluate(scope)
|
255
320
|
@block.evaluate(scope)
|
@@ -257,24 +322,31 @@ class IfNode < ControlflowNode
|
|
257
322
|
end
|
258
323
|
end
|
259
324
|
|
325
|
+
# Class for while-statements.
|
260
326
|
class WhileNode < ControlflowNode
|
327
|
+
|
328
|
+
# Evaluates the expression as a condition in a while-loop. If true -> continue iterations.
|
261
329
|
def evaluate(scope)
|
262
330
|
while @expression.evaluate(scope)
|
263
|
-
@block.evaluate(scope)
|
331
|
+
result = @block.evaluate(scope)
|
332
|
+
return result if result.is_a?(ReturnValue)
|
264
333
|
end
|
265
334
|
end
|
266
335
|
end
|
267
336
|
|
337
|
+
# Class for arrays.
|
268
338
|
class ArrayNode
|
269
339
|
def initialize(elements = [])
|
270
340
|
@elements = elements
|
271
341
|
end
|
272
342
|
|
343
|
+
# Evaluate each object in elements and return array.
|
273
344
|
def evaluate(scope)
|
274
345
|
return @elements.map {|element| element.evaluate(scope)}
|
275
346
|
end
|
276
347
|
end
|
277
348
|
|
349
|
+
# Superclass for primitive data-types.
|
278
350
|
class PrimitiveNode
|
279
351
|
attr_reader :value
|
280
352
|
def initialize(value)
|
@@ -286,25 +358,41 @@ class PrimitiveNode
|
|
286
358
|
end
|
287
359
|
end
|
288
360
|
|
361
|
+
# Class for identifiers (functions and variables).
|
289
362
|
class IdentifierNode < PrimitiveNode
|
290
363
|
def evaluate(scope)
|
364
|
+
|
365
|
+
# Checks if the identifier is a function. If true raise SyntaxError.
|
291
366
|
if scope.is_function?(@value)
|
292
367
|
raise SyntaxError, "Identifier #{@value} is assigned to a function. Please use correct syntax for function call."
|
293
368
|
end
|
369
|
+
|
370
|
+
# Checks if the identifier is a local variable. If false, add it to non-local list.
|
294
371
|
id_value = scope.get_identifier(@value)
|
295
372
|
unless scope.identifiers.has_key?(@value)
|
296
373
|
scope.set_non_local_variable(@value)
|
297
374
|
end
|
375
|
+
|
298
376
|
return id_value
|
299
377
|
end
|
300
378
|
end
|
301
379
|
|
302
|
-
class
|
303
|
-
end
|
304
|
-
|
380
|
+
# Wrapper class for primitive data-type Integer/Float.
|
305
381
|
class DigitNode < PrimitiveNode
|
306
382
|
end
|
307
383
|
|
384
|
+
# Wrapper class for primitive data-type Bool.
|
308
385
|
class BoolNode < PrimitiveNode
|
309
386
|
end
|
310
387
|
|
388
|
+
# Class for primitive data-type String.
|
389
|
+
class StringNode < PrimitiveNode
|
390
|
+
attr_reader :value
|
391
|
+
def initialize(value)
|
392
|
+
@value = value.gsub(/^"|"$/, '')
|
393
|
+
end
|
394
|
+
|
395
|
+
def evaluate(scope)
|
396
|
+
return @value
|
397
|
+
end
|
398
|
+
end
|
data/lib/pcpparse.rb
CHANGED
@@ -26,6 +26,7 @@ class Pycplus
|
|
26
26
|
token(/&&/) {|m| m}
|
27
27
|
token(/\|\|/) {|m| m}
|
28
28
|
token(/\d+\.\d+/) {|m| m.to_f}
|
29
|
+
token(/"[^"]*"/) {|m| m}
|
29
30
|
token(/\d+/) {|m| m.to_i}
|
30
31
|
token(/\w+/) {|m| m}
|
31
32
|
token(/./) {|m| m}
|
@@ -50,7 +51,6 @@ class Pycplus
|
|
50
51
|
end
|
51
52
|
|
52
53
|
rule :assignment do
|
53
|
-
# match(:identifier, :assignment_OP, :function_call) {|a, b, c| AssignmentNode.new(a,b,c)}
|
54
54
|
match(:identifier, :assignment_OP, :logical_expr) {|a, b, c| AssignmentNode.new(a,b,c)}
|
55
55
|
match(:identifier, :assignment_OP, :array) {|a, b, c| AssignmentNode.new(a,b,c)}
|
56
56
|
end
|
@@ -107,7 +107,6 @@ class Pycplus
|
|
107
107
|
rule :return_statement do
|
108
108
|
match(:return, :logical_expr) {|_, a| ReturnNode.new(a)}
|
109
109
|
match(:return, :array) {|_, a| ReturnNode.new(a)}
|
110
|
-
# match(:return, :function_call) {|_, a| ReturnNode.new(a)}
|
111
110
|
end
|
112
111
|
|
113
112
|
rule :while_statement do
|
@@ -192,6 +191,7 @@ class Pycplus
|
|
192
191
|
match(:bool)
|
193
192
|
match(:digit)
|
194
193
|
match(:identifier)
|
194
|
+
match(:string)
|
195
195
|
match('(', :logical_expr, ')') {|_, a, _| a}
|
196
196
|
end
|
197
197
|
|
@@ -213,6 +213,7 @@ class Pycplus
|
|
213
213
|
rule :atom do
|
214
214
|
match(:bool)
|
215
215
|
match(:digit)
|
216
|
+
match(:string)
|
216
217
|
match(:identifier)
|
217
218
|
end
|
218
219
|
|
@@ -226,8 +227,12 @@ class Pycplus
|
|
226
227
|
match(Integer) {|a| DigitNode.new(a)}
|
227
228
|
end
|
228
229
|
|
230
|
+
rule :string do
|
231
|
+
match(/".*?"/) {|a| StringNode.new(a)}
|
232
|
+
end
|
233
|
+
|
229
234
|
rule :identifier do
|
230
|
-
match(
|
235
|
+
match(/^(?!.*")[_a-zA-Z]+\w*(?<!")$/) {|a| IdentifierNode.new(a.to_sym)}
|
231
236
|
end
|
232
237
|
end
|
233
238
|
end
|
@@ -235,7 +240,11 @@ class Pycplus
|
|
235
240
|
def parse_file(filename)
|
236
241
|
file = File.open(filename)
|
237
242
|
file_data = file.read
|
238
|
-
|
243
|
+
begin
|
244
|
+
result = @pycplus_parser.parse(file_data)
|
245
|
+
rescue StandardError => e
|
246
|
+
puts "Something went wrong! Error #{e.message}"
|
247
|
+
end
|
239
248
|
return result.evaluate if result
|
240
249
|
end
|
241
250
|
|
@@ -258,7 +267,4 @@ class Pycplus
|
|
258
267
|
@pycplus_parser.logger.level = Logger::WARN
|
259
268
|
end
|
260
269
|
end
|
261
|
-
end
|
262
|
-
|
263
|
-
|
264
|
-
# _?[a-zA-Z*]\w*
|
270
|
+
end
|
data/lib/rdparse.rb
CHANGED
@@ -19,21 +19,21 @@ class Rule
|
|
19
19
|
# match(:term, '/', :dice) {|a, _, b| a / b }
|
20
20
|
# match(:dice)
|
21
21
|
# end
|
22
|
-
|
22
|
+
|
23
23
|
Match = Struct.new :pattern, :block
|
24
|
-
|
24
|
+
|
25
25
|
def initialize(name, parser)
|
26
26
|
@logger = parser.logger
|
27
27
|
# The name of the expressions this rule matches
|
28
28
|
@name = name
|
29
|
-
# We need the parser to recursively parse sub-expressions occurring
|
29
|
+
# We need the parser to recursively parse sub-expressions occurring
|
30
30
|
# within the pattern of the match objects associated with this rule
|
31
31
|
@parser = parser
|
32
32
|
@matches = []
|
33
33
|
# Left-recursive matches
|
34
34
|
@lrmatches = []
|
35
35
|
end
|
36
|
-
|
36
|
+
|
37
37
|
# Add a matching expression to this rule, as in this example:
|
38
38
|
# match(:term, '*', :dice) {|a, _, b| a * b }
|
39
39
|
# The arguments to 'match' describe the constituents of this expression.
|
@@ -47,7 +47,7 @@ class Rule
|
|
47
47
|
@matches << match
|
48
48
|
end
|
49
49
|
end
|
50
|
-
|
50
|
+
|
51
51
|
def parse
|
52
52
|
# Try non-left-recursive matches first, to avoid infinite recursion
|
53
53
|
match_result = try_matches(@matches)
|
@@ -60,7 +60,7 @@ class Rule
|
|
60
60
|
end
|
61
61
|
|
62
62
|
private
|
63
|
-
|
63
|
+
|
64
64
|
# Try out all matching patterns of this rule
|
65
65
|
def try_matches(matches, pre_result = nil)
|
66
66
|
match_result = nil
|
@@ -73,7 +73,7 @@ class Rule
|
|
73
73
|
# We iterate through the parts of the pattern, which may be e.g.
|
74
74
|
# [:expr,'*',:term]
|
75
75
|
match.pattern.each_with_index do |token,index|
|
76
|
-
|
76
|
+
|
77
77
|
# If this "token" is a compound term, add the result of
|
78
78
|
# parsing it to the "result" array
|
79
79
|
if @parser.rules[token]
|
@@ -114,7 +114,7 @@ class Rule
|
|
114
114
|
@parser.pos = start
|
115
115
|
end
|
116
116
|
end
|
117
|
-
|
117
|
+
|
118
118
|
return match_result
|
119
119
|
end
|
120
120
|
end
|
@@ -135,7 +135,7 @@ class Parser
|
|
135
135
|
@language_name = language_name
|
136
136
|
instance_eval(&block)
|
137
137
|
end
|
138
|
-
|
138
|
+
|
139
139
|
# Tokenize the string into small pieces
|
140
140
|
def tokenize(string)
|
141
141
|
@tokens = []
|
@@ -161,10 +161,10 @@ class Parser
|
|
161
161
|
end # raise
|
162
162
|
end # until
|
163
163
|
end
|
164
|
-
|
164
|
+
|
165
165
|
def parse(string)
|
166
166
|
# First, split the string according to the "token" instructions given.
|
167
|
-
# Afterwards @tokens contains all tokens that are to be parsed.
|
167
|
+
# Afterwards @tokens contains all tokens that are to be parsed.
|
168
168
|
tokenize(string)
|
169
169
|
|
170
170
|
# These variables are used to match if the total number of tokens
|
@@ -175,12 +175,13 @@ class Parser
|
|
175
175
|
# Parse (and evaluate) the tokens received
|
176
176
|
result = @start.parse
|
177
177
|
# If there are unparsed extra tokens, signal error
|
178
|
+
#
|
178
179
|
if @pos != @tokens.size
|
179
180
|
raise ParseError, "Parse error. expected: '#{@expected.join(', ')}', found '#{@tokens[@max_pos]}'"
|
180
181
|
end
|
181
182
|
return result
|
182
183
|
end
|
183
|
-
|
184
|
+
|
184
185
|
def next_token
|
185
186
|
@pos += 1
|
186
187
|
return @tokens[@pos - 1]
|
@@ -198,31 +199,31 @@ class Parser
|
|
198
199
|
@expected << tok if @max_pos == @pos - 1 && !@expected.include?(tok)
|
199
200
|
return nil
|
200
201
|
end
|
201
|
-
|
202
|
+
|
202
203
|
def to_s
|
203
204
|
"Parser for #{@language_name}"
|
204
205
|
end
|
205
206
|
|
206
207
|
private
|
207
|
-
|
208
|
+
|
208
209
|
LexToken = Struct.new(:pattern, :block)
|
209
|
-
|
210
|
+
|
210
211
|
def token(pattern, &block)
|
211
212
|
@lex_tokens << LexToken.new(Regexp.new('\\A' + pattern.source), block)
|
212
213
|
end
|
213
|
-
|
214
|
+
|
214
215
|
def start(name, &block)
|
215
216
|
rule(name, &block)
|
216
217
|
@start = @rules[name]
|
217
218
|
end
|
218
|
-
|
219
|
+
|
219
220
|
def rule(name,&block)
|
220
221
|
@current_rule = Rule.new(name, self)
|
221
222
|
@rules[name] = @current_rule
|
222
223
|
instance_eval(&block) # In practise, calls match 1..N times
|
223
224
|
@current_rule = nil
|
224
225
|
end
|
225
|
-
|
226
|
+
|
226
227
|
def match(*pattern, &block)
|
227
228
|
# Basically calls memberfunction "match(*pattern, &block)
|
228
229
|
@current_rule.send(:match, *pattern, &block)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pycplus
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Johannes
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2024-05-
|
12
|
+
date: 2024-05-07 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: test-unit
|