pycplus 0.4.1 → 1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5f3bc5bfe8923bf561c4a2b3167a862151f1487d62b84bb1587f9f07b0433c63
4
- data.tar.gz: 4872ec376c3501b869ed773b0a535b894abe35d9af05af81784cf081a34696dc
3
+ metadata.gz: b5ad55e5bb630968fc874f2b4864d1157f613387a386963d80ae70a059f7fe82
4
+ data.tar.gz: 3f9cd2e36bb0307ad340f1e0279904761d0bf0b6d1f8f14a61cb86230aacc0b6
5
5
  SHA512:
6
- metadata.gz: 84dd3244332bfe07a86e8448276f0bdb7ebd5318696ba882ab707fbb8e82aaeba63e40c6cc531a166012461df549a47bede2e9a07bab81cf58255f1d6faeb784
7
- data.tar.gz: 3002a3bc7ce8f595fd1f84371796cd4ad0ce9de8ad4f0d08c5dbe7278685c9278f8be3fbc7f7d8618b923ef03af483c0e0f4d65cda9b934918f3bc3ff0c918b6
6
+ metadata.gz: fd8d3e774ffa9e0008799e40a612a4f0855736f4ffb7a80c7bd47b0215586e054b8843f3eb9c01b212bd11b651a593510531ad439a0a8ec5a5b3abd8d16c6587
7
+ data.tar.gz: a425b2c00467899bedaa54b2ba471b731f10f3013daf726295b0105e5258ac8253e6e33dc16a551d0cd52a2f5a35048bb7c5578b7b9c36664534cc019c901988
data/README.md CHANGED
@@ -1,3 +1,48 @@
1
- # TDP019
1
+ # PyCPlus
2
2
 
3
- TODO
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
+ ## Flags
17
+
18
+ Several flags can be used with the gem.
19
+
20
+ Type the following command to see the available flags:
21
+
22
+ ```bash
23
+ pycplus -h
24
+ ```
25
+
26
+ ## Usage
27
+
28
+ The programming language is to be used with files of type .pcp.
29
+
30
+ To run a file, navigate to the directory where the file is located and type the following command:
31
+
32
+ ```bash
33
+ pycplus filename.pcp
34
+ ```
35
+
36
+ ## Unit testing
37
+
38
+ Unit tests can be ran from the projects test directory.
39
+
40
+ Navigate to the projectfolder and type the following command:
41
+
42
+ ```bash
43
+ rake
44
+ ```
45
+
46
+ ## Additional documentation
47
+
48
+ Additional documentation about the language and system can be found in the 'doc' directory. This documentation is in Swedish.
data/bin/pycplus CHANGED
@@ -12,7 +12,8 @@ def print_version
12
12
  puts "#{spec.name} version #{spec.version}"
13
13
  rescue
14
14
  begin
15
- spec = Gem::Specification.load('../pycplus.gemspec')
15
+ path = File.expand_path('../pycplus.gemspec', __dir__)
16
+ spec = Gem::Specification.load(path)
16
17
  puts "#{spec.name} version #{spec.version}"
17
18
  rescue
18
19
  puts "Error: Unable to find version for pycplus gem"
@@ -27,6 +28,11 @@ OptionParser.new do |opts|
27
28
 
28
29
  opts.banner = "Usage: #{File.basename($PROGRAM_NAME)} [options] filename"
29
30
 
31
+ opts.on("-h", "--help", "Display this help message") do
32
+ puts opts
33
+ exit
34
+ end
35
+
30
36
  # version is handled as a special case as it doesn't make sense to use it alongside the other options
31
37
  opts.on("-v", "--version", "Display version information") do
32
38
  print_version
@@ -35,7 +41,6 @@ OptionParser.new do |opts|
35
41
 
36
42
  opts.on("-d", "--debug", "Enable debug mode") do
37
43
  options[:debug] = true
38
- print("dhsdhshdshds")
39
44
  end
40
45
  end.parse!
41
46
 
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(array)
9
- unless array.is_a?(Array)
10
- raise TypeError, "Function #{__method__} must be used on an array."
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 array.count
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, "local identifier #{name} referenced before assignment"
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, "local identifier #{name} referenced before assignment"
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] -= expression
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) && is_function(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
- id_value = scope.get_identifier(name) if scope
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
- include STL
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
- def evaluate_method(scope)
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
- def evaluate_block(parameters, block, current_scope, id_scope)
122
- new_scope = Scope.new(id_scope)
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
- if @object
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
- evaluate_method(scope)
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
- return result
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
- 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 ||.
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)}") # Not possible to use send-method.
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 CharNode < PrimitiveNode
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,17 +227,26 @@ 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(/[_a-zA-Z]+\w*/) {|a| IdentifierNode.new(a.to_sym)}
235
+ match(/^(?!.*")[_a-zA-Z]+\w*(?<!")$/) {|a| IdentifierNode.new(a.to_sym)}
231
236
  end
232
237
  end
233
238
  end
234
239
 
235
240
  def parse_file(filename)
236
- file = File.open(filename)
237
- file_data = file.read
238
- result = @pycplus_parser.parse(file_data)
239
- return result.evaluate if result
241
+ file_data = File.read(filename)
242
+ result = nil
243
+ begin
244
+ ast = @pycplus_parser.parse(file_data)
245
+ result = ast.evaluate if ast
246
+ rescue StandardError => e
247
+ puts "Something went wrong! #{e.class}: #{e.message}"
248
+ end
249
+ return result
240
250
  end
241
251
 
242
252
  def parse_string(str, return_tree = false, display_output = false)
@@ -258,7 +268,4 @@ class Pycplus
258
268
  @pycplus_parser.logger.level = Logger::WARN
259
269
  end
260
270
  end
261
- end
262
-
263
-
264
- # _?[a-zA-Z*]\w*
271
+ 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.1
4
+ version: 1.1.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-05 00:00:00.000000000 Z
12
+ date: 2024-05-24 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: test-unit
@@ -31,8 +31,9 @@ dependencies:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: 3.6.0
34
- description: This Ruby Gem provides a framework for creating and utilizing a programming
35
- language developed as part of a school project at LiU (Linköping University).
34
+ description: This Ruby Gem provides the tools to parse, interpret, and execute programs
35
+ written in a custom programming language, developed as part of a school project
36
+ at LiU (Linköping University).
36
37
  email: test@test.com
37
38
  executables:
38
39
  - pycplus
@@ -66,5 +67,5 @@ requirements: []
66
67
  rubygems_version: 3.3.5
67
68
  signing_key:
68
69
  specification_version: 4
69
- summary: A Ruby Gem for creating and using a programming language.
70
+ summary: A Ruby Gem for creating and running programs in a custom language.
70
71
  test_files: []