rpl 0.5.0 → 0.6.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: db2185e41b8f4562c26b03d1b71ae420661697cb7dfbe3fe27763bc13146427b
4
- data.tar.gz: 7130c10e23d621b08d2a71840b48afc5adbd49d8ed2e8f4fc80e507f07cc5dd3
3
+ metadata.gz: 473327f669ed4e0484a32f534efa52bcbfbb8521ebcad7fa4d38de35f6576972
4
+ data.tar.gz: cd6d921d8f40edcd95a32d4af529d7435962c65f621fa7903cbee4a1fabcf07f
5
5
  SHA512:
6
- metadata.gz: 9a45f40c4fd25cf9eb5471d02cbcd74e2512744e81495aeaeee2f90e278298f09e6f8168caf10f97f93da56c2beadb992c0d927c5bf27a337a21f5ef07fcc6d8
7
- data.tar.gz: 44a18620e90a806ebc24488e133e9250dab50165ec27508d5f0d867b7b32e9fee6bb6aa311cfacfe2ccb796e3a8ac408caf1b5e5f4938a81b2ff9aaba21a1603
6
+ metadata.gz: 4db7ebbce26e2eb92cc4baaa0a35954f3de5e8129b28c348bd78d420109300ec988dc4d8ed27464e7455ba8103c6141ed084d6c63526651b982c712a52cd9413
7
+ data.tar.gz: fbc27ef9008a261401d02bb69a86c4eff00861df9018d2af4a029aa69fbf21475436a67aa8066d23c4eb6ac01455944e2a567c4f18b36f121059b06c351ef903
data/bin/rpl CHANGED
@@ -10,6 +10,7 @@ require 'rpl'
10
10
  class RplRepl
11
11
  def initialize( interpreter )
12
12
  interpreter ||= Rpl.new
13
+
13
14
  @interpreter = interpreter
14
15
  end
15
16
 
@@ -33,28 +34,22 @@ class RplRepl
33
34
  begin
34
35
  @interpreter.run( input )
35
36
  rescue ArgumentError => e
36
- p e
37
+ pp e
37
38
  end
38
39
 
39
40
  print_stack
40
41
  end
41
42
  end
42
43
 
43
- def format_element( elt )
44
- @interpreter.stringify( elt )
45
- end
46
-
47
44
  def print_stack
48
45
  stack_size = @interpreter.stack.size
49
46
 
50
47
  @interpreter.stack.each_with_index do |elt, i|
51
- puts "#{stack_size - i}: #{format_element( elt )}"
48
+ puts "#{stack_size - i}: #{elt}"
52
49
  end
53
50
  end
54
51
  end
55
52
 
56
- interpreter = Rpl.new
57
-
58
53
  options = { run_REPL: ARGV.empty?,
59
54
  files: [],
60
55
  programs: [] }
@@ -73,14 +68,21 @@ OptionParser.new do |opts|
73
68
  end
74
69
  end.parse!
75
70
 
71
+ # Instantiate interpreter
72
+ interpreter = Rpl.new
73
+
74
+ # first run provided files if any
76
75
  options[:files].each do |filename|
77
76
  interpreter.run "\"#{filename}\" feval"
78
77
  end
79
78
 
79
+ # second run provided code if any
80
80
  options[:programs].each do |program|
81
81
  interpreter.run program
82
82
  end
83
83
 
84
+ # third launch REPL if (explicitely or implicitely) asked
84
85
  RplRepl.new( interpreter ).run if options[:run_REPL]
85
86
 
86
- pp interpreter.stack.map { |elt| interpreter.stringify( elt ) }.join(' ')
87
+ # last print resulting stack on exit (formatted so that it can be fed back later to interpreter)
88
+ pp interpreter.export_stack
@@ -5,9 +5,11 @@ require 'bigdecimal/math'
5
5
  require 'bigdecimal/util'
6
6
 
7
7
  require 'rpl/dictionary'
8
+ require 'rpl/types'
8
9
 
9
10
  class Interpreter
10
11
  include BigMath
12
+ include Types
11
13
 
12
14
  attr_reader :stack,
13
15
  :dictionary,
@@ -16,173 +18,37 @@ class Interpreter
16
18
  attr_accessor :precision
17
19
 
18
20
  def initialize( stack = [], dictionary = Rpl::Lang::Dictionary.new )
19
- @version = 0.1
20
-
21
- @precision = default_precision
21
+ @version = 0.6
22
22
 
23
23
  @dictionary = dictionary
24
24
  @stack = stack
25
-
26
- populate_dictionary if @dictionary.words.empty?
27
- end
28
-
29
- def default_precision
30
- 12
31
- end
32
-
33
- def parse( input )
34
- is_numeric = lambda do |elt|
35
- begin
36
- !Float(elt).nil?
37
- rescue ArgumentError
38
- begin
39
- !Integer(elt).nil?
40
- rescue ArgumentError
41
- false
42
- end
43
- end
44
- end
45
-
46
- unless input.index("\n").nil?
47
- input = input
48
- .split("\n")
49
- .map do |line|
50
- comment_begin_index = line.index('#')
51
-
52
- if comment_begin_index.nil?
53
- line
54
- elsif comment_begin_index == 0
55
- ''
56
- else
57
- line[0..(comment_begin_index - 1)]
58
- end
59
- end
60
- .join(' ')
61
- end
62
-
63
- splitted_input = input.split(' ')
64
-
65
- # 2-passes:
66
- # 1. regroup strings and programs
67
- opened_programs = 0
68
- closed_programs = 0
69
- opened_lists = 0
70
- closed_lists = 0
71
- string_delimiters = 0
72
- name_delimiters = 0
73
- regrouping = false
74
-
75
- regrouped_input = []
76
- splitted_input.each do |elt|
77
- if elt[0] == '«'
78
- opened_programs += 1
79
- elt.gsub!( '«', '« ') if elt.length > 1 && elt[1] != ' '
80
- elsif elt[0] == '{'
81
- opened_lists += 1
82
- elt.gsub!( '{', '{ ') if elt.length > 1 && elt[1] != ' '
83
- elsif elt[0] == '"' && elt.length > 1
84
- string_delimiters += 1
85
- elsif elt[0] == "'" && elt.length > 1
86
- name_delimiters += 1
87
- end
88
-
89
- elt = "#{regrouped_input.pop} #{elt}".strip if regrouping
90
-
91
- regrouped_input << elt
92
-
93
- case elt[-1]
94
- when '»'
95
- closed_programs += 1
96
- elt.gsub!( '»', ' »') if elt.length > 1 && elt[-2] != ' '
97
- when '}'
98
- closed_lists += 1
99
- elt.gsub!( '}', ' }') if elt.length > 1 && elt[-2] != ' '
100
- when '"'
101
- string_delimiters += 1
102
- when "'"
103
- name_delimiters += 1
104
- end
105
-
106
- regrouping = string_delimiters.odd? || name_delimiters.odd? || (opened_programs > closed_programs ) || (opened_lists > closed_lists )
107
- end
108
-
109
- # 2. parse
110
- # TODO: parse ∞, <NaN> as numerics
111
- parsed_tree = []
112
- regrouped_input.each do |elt|
113
- parsed_entry = { value: elt }
114
-
115
- parsed_entry[:type] = case elt[0]
116
- when '«'
117
- :program
118
- when '{'
119
- :list
120
- when '"'
121
- :string
122
- when "'"
123
- :name # TODO: check for forbidden space
124
- else
125
- if is_numeric.call( elt )
126
- :numeric
127
- else
128
- :word
129
- end
130
- end
131
-
132
- if %I[string name].include?( parsed_entry[:type] )
133
- parsed_entry[:value] = parsed_entry[:value][1..-2]
134
- elsif %I[program].include?( parsed_entry[:type] )
135
- parsed_entry[:value] = parsed_entry[:value][2..-3]
136
- elsif %I[list].include?( parsed_entry[:type] )
137
- parsed_entry[:value] = parse( parsed_entry[:value][2..-3] )
138
- elsif %I[numeric].include?( parsed_entry[:type] )
139
- underscore_position = parsed_entry[:value].index('_')
140
-
141
- if parsed_entry[:value][0] == '0' && ( ['b', 'o', 'x'].include?( parsed_entry[:value][1] ) || !underscore_position.nil? )
142
- if parsed_entry[:value][1] == 'x'
143
- parsed_entry[:base] = 16
144
- elsif parsed_entry[:value][1] == 'b'
145
- parsed_entry[:base] = 2
146
- elsif parsed_entry[:value][1] == 'o'
147
- parsed_entry[:base] = 8
148
- parsed_entry[:value] = parsed_entry[:value][2..]
149
- elsif !underscore_position.nil?
150
- parsed_entry[:base] = parsed_entry[:value][1..(underscore_position - 1)].to_i
151
- parsed_entry[:value] = parsed_entry[:value][(underscore_position + 1)..]
152
- end
153
- else
154
- parsed_entry[:base] = 10
155
- end
156
-
157
- parsed_entry[:value] = parsed_entry[:value].to_i( parsed_entry[:base] ) unless parsed_entry[:base] == 10
158
- parsed_entry[:value] = BigDecimal( parsed_entry[:value], @precision )
159
- end
160
-
161
- parsed_tree << parsed_entry
162
- end
163
-
164
- parsed_tree
165
25
  end
166
26
 
167
27
  def run( input )
168
28
  @dictionary.add_local_vars_layer
169
29
 
170
- parse( input.to_s ).each do |elt|
171
- case elt[:type]
172
- when :word
173
- break if %w[break quit exit].include?( elt[:value] )
174
-
175
- command = @dictionary.lookup( elt[:value] )
176
-
177
- if command.nil?
178
- # if there isn't a command by that name then it's a name
179
- elt[:type] = :name
30
+ Parser.parse( input.to_s ).each do |elt|
31
+ if elt.instance_of?( RplName )
32
+ break if %w[break quit exit].include?( elt.value )
180
33
 
34
+ if elt.not_to_evaluate
181
35
  @stack << elt
182
- elsif command.is_a?( Proc )
183
- command.call
184
36
  else
185
- run( command[:value] )
37
+ command = @dictionary.lookup( elt.value )
38
+ if command.nil?
39
+ # if there isn't a command by that name then it's a name
40
+ # elt[:type] = :name
41
+
42
+ @stack << elt
43
+ elsif command.is_a?( Proc )
44
+ command.call
45
+ else
46
+ if command.instance_of?( RplProgram )
47
+ run( command.value )
48
+ else
49
+ @stack << command
50
+ end
51
+ end
186
52
  end
187
53
  else
188
54
  @stack << elt
@@ -201,7 +67,7 @@ class Interpreter
201
67
  needs.each_with_index do |need, index|
202
68
  stack_index = (index + 1) * -1
203
69
 
204
- raise ArgumentError, "Type Error, needed #{need} got #{@stack[stack_index]}" unless need == :any || need.include?( @stack[stack_index][:type] )
70
+ raise ArgumentError, "Type Error, needed #{need} got #{@stack[stack_index]}" unless need == :any || need.include?( @stack[stack_index].class )
205
71
  end
206
72
 
207
73
  args = []
@@ -212,52 +78,7 @@ class Interpreter
212
78
  args
213
79
  end
214
80
 
215
- def stringify( elt )
216
- case elt[:type]
217
- when :numeric
218
- prefix = case elt[:base]
219
- when 2
220
- '0b'
221
- when 8
222
- '0o'
223
- when 10
224
- ''
225
- when 16
226
- '0x'
227
- else
228
- "0#{elt[:base]}_"
229
- end
230
-
231
- if elt[:value].infinite?
232
- suffix = elt[:value].infinite?.positive? ? '∞' : '-∞'
233
- elsif elt[:value].nan?
234
- suffix = '<NaN>'
235
- else
236
- suffix = if elt[:value].to_i == elt[:value]
237
- elt[:value].to_i
238
- else
239
- elt[:value].to_s('F')
240
- end
241
- suffix = elt[:value].to_s( elt[:base] ) unless elt[:base] == 10
242
- end
243
-
244
- "#{prefix}#{suffix}"
245
- when :list
246
- "{ #{elt[:value].map { |e| stringify( e ) }.join(' ')} }"
247
- when :program
248
- "« #{elt[:value]} »"
249
- when :string
250
- "\"#{elt[:value]}\""
251
- when :name
252
- "'#{elt[:value]}'"
253
- else
254
- elt[:value]
255
- end
256
- end
257
-
258
- def infer_resulting_base( numerics )
259
- 10 if numerics.length.zero?
260
-
261
- numerics.last[:base]
81
+ def export_stack
82
+ @stack.map(&:to_s).join(' ')
262
83
  end
263
84
  end
data/lib/rpl/parser.rb ADDED
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Parser
4
+ include Types
5
+
6
+ def self.parse( input )
7
+ unless input.index("\n").nil?
8
+ input = input.split("\n")
9
+ .map do |line|
10
+ comment_begin_index = line.index('#')
11
+
12
+ case comment_begin_index
13
+ when nil
14
+ line
15
+ when 0
16
+ ''
17
+ else
18
+ line[0..(comment_begin_index - 1)]
19
+ end
20
+ end
21
+ .join(' ')
22
+ end
23
+
24
+ splitted_input = input.split(' ')
25
+
26
+ # 2-passes:
27
+ # 1. regroup strings and programs
28
+ opened_programs = 0
29
+ closed_programs = 0
30
+ opened_lists = 0
31
+ closed_lists = 0
32
+ string_delimiters = 0
33
+ name_delimiters = 0
34
+ regrouping = false
35
+
36
+ regrouped_input = []
37
+ splitted_input.each do |elt|
38
+ if elt[0] == '«'
39
+ opened_programs += 1
40
+ elt.gsub!( '«', '« ') if elt.length > 1 && elt[1] != ' '
41
+ elsif elt[0] == '{'
42
+ opened_lists += 1
43
+ elt.gsub!( '{', '{ ') if elt.length > 1 && elt[1] != ' '
44
+ elsif elt[0] == '"' && elt.length > 1
45
+ string_delimiters += 1
46
+ elsif elt[0] == "'" && elt.length > 1
47
+ name_delimiters += 1
48
+ end
49
+
50
+ elt = "#{regrouped_input.pop} #{elt}".strip if regrouping
51
+
52
+ regrouped_input << elt
53
+
54
+ case elt[-1]
55
+ when '»'
56
+ closed_programs += 1
57
+ elt.gsub!( '»', ' »') if elt.length > 1 && elt[-2] != ' '
58
+ when '}'
59
+ closed_lists += 1
60
+ elt.gsub!( '}', ' }') if elt.length > 1 && elt[-2] != ' '
61
+ when '"'
62
+ string_delimiters += 1
63
+ when "'"
64
+ name_delimiters += 1
65
+ end
66
+
67
+ regrouping = string_delimiters.odd? || name_delimiters.odd? || (opened_programs > closed_programs ) || (opened_lists > closed_lists )
68
+ end
69
+
70
+ # 2. parse
71
+ regrouped_input.map do |element|
72
+ if RplBoolean.can_parse?( element )
73
+ RplBoolean.new( element )
74
+ elsif RplNumeric.can_parse?( element )
75
+ RplNumeric.new( element )
76
+ elsif RplList.can_parse?( element )
77
+ RplList.new( element )
78
+ elsif RplString.can_parse?( element )
79
+ RplString.new( element )
80
+ elsif RplProgram.can_parse?( element )
81
+ RplProgram.new( element )
82
+ elsif RplName.can_parse?( element )
83
+ RplName.new( element )
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Types
4
+ class RplBoolean
5
+ attr_accessor :value
6
+
7
+ def initialize( value )
8
+ raise RplTypeError unless self.class.can_parse?( value )
9
+
10
+ @value = if value.is_a?( String )
11
+ value.downcase == 'true'
12
+ else
13
+ value
14
+ end
15
+ end
16
+
17
+ def to_s
18
+ @value.to_s
19
+ end
20
+
21
+ def self.can_parse?( value )
22
+ return %w[true false].include?( value.downcase ) if value.is_a?( String )
23
+
24
+ %w[TrueClass FalseClass].include?( value.class.to_s )
25
+ end
26
+
27
+ def ==( other )
28
+ other.class == RplBoolean and
29
+ other.value == value
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rpl/parser'
4
+
5
+ module Types
6
+ class RplList
7
+ attr_accessor :value
8
+
9
+ def initialize( value )
10
+ raise RplTypeError unless self.class.can_parse?( value )
11
+
12
+ @value = if value.instance_of?( Array )
13
+ value
14
+ else
15
+ # we systematicalyl trim enclosing { }
16
+ Parser.parse( value[2..-3] )
17
+ end
18
+ end
19
+
20
+ def to_s
21
+ "{ #{@value.map(&:to_s).join(' ')} }"
22
+ end
23
+
24
+ def self.can_parse?( value )
25
+ value.instance_of?( Array ) or
26
+ value[0..1] == '{ ' && value[-2..-1] == ' }'
27
+ end
28
+
29
+ def ==( other )
30
+ other.class == RplList and
31
+ other.value == value
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Types
4
+ class RplName
5
+ attr_accessor :value,
6
+ :not_to_evaluate
7
+
8
+ def initialize( value )
9
+ raise RplTypeError unless self.class.can_parse?( value )
10
+
11
+ # we systematicalyl trim enclosing '
12
+ @not_to_evaluate = value[0] == "'"
13
+ @value = if value[0] == "'" && value[-1] == "'"
14
+ value[1..-2]
15
+ else
16
+ value
17
+ end
18
+ end
19
+
20
+ def to_s
21
+ "'#{@value}'"
22
+ end
23
+
24
+ def self.can_parse?( value )
25
+ ( value.length > 2 and value[0] == "'" and value[-1] == "'" ) or
26
+ ( value != "''" and !value.match?(/^[0-9']+$/) and
27
+ # it's not any other type
28
+ [RplBoolean, RplList, RplProgram, RplString, RplNumeric].reduce( true ) { |memo, type_class| memo && !type_class.can_parse?( value ) } )
29
+ end
30
+
31
+ def ==( other )
32
+ other.class == RplName and
33
+ other.value == value
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bigdecimal'
4
+
5
+ module Types
6
+ class RplNumeric
7
+ attr_accessor :value,
8
+ :base
9
+
10
+ @@precision = 12 # rubocop:disable Style/ClassVars
11
+
12
+ def self.default_precision
13
+ @@precision = 12 # rubocop:disable Style/ClassVars
14
+ end
15
+
16
+ def self.precision
17
+ @@precision
18
+ end
19
+
20
+ def self.precision=( precision )
21
+ @@precision = precision.to_i # rubocop:disable Style/ClassVars
22
+ end
23
+
24
+ def initialize( value, base = 10 )
25
+ raise RplTypeError unless self.class.can_parse?( value )
26
+
27
+ @base = base
28
+
29
+ case value
30
+ when RplNumeric
31
+ @value = value.value
32
+ @base = value.base
33
+ when BigDecimal
34
+ @value = value
35
+ when Integer
36
+ @value = BigDecimal( value, @@precision )
37
+ when Float
38
+ @value = BigDecimal( value, @@precision )
39
+ when String
40
+ case value
41
+ when '∞'
42
+ @value = BigDecimal('+Infinity')
43
+ when '-∞'
44
+ @value = BigDecimal('-Infinity')
45
+ else
46
+ underscore_position = value.index('_')
47
+
48
+ if value[0] == '0' && ( %w[b o x].include?( value[1] ) || !underscore_position.nil? )
49
+ if value[1] == 'x'
50
+ @base = 16
51
+ elsif value[1] == 'b'
52
+ @base = 2
53
+ elsif value[1] == 'o'
54
+ @base = 8
55
+ value = value[2..-1]
56
+ elsif !underscore_position.nil?
57
+ @base = value[1..(underscore_position - 1)].to_i
58
+ value = value[(underscore_position + 1)..-1]
59
+ end
60
+ end
61
+
62
+ value = value.to_i( @base ) unless @base == 10
63
+ @value = BigDecimal( value, @@precision )
64
+ end
65
+ end
66
+ end
67
+
68
+ def to_s
69
+ prefix = case @base
70
+ when 2
71
+ '0b'
72
+ when 8
73
+ '0o'
74
+ when 10
75
+ ''
76
+ when 16
77
+ '0x'
78
+ else
79
+ "0#{@base}_"
80
+ end
81
+
82
+ if @value.infinite?
83
+ suffix = @value.infinite?.positive? ? '∞' : '-∞'
84
+ elsif @value.nan?
85
+ suffix = '<NaN>'
86
+ else
87
+ suffix = if @value.to_i == @value
88
+ @value.to_i
89
+ else
90
+ @value.to_s('F')
91
+ end
92
+ suffix = @value.to_s( @base ) unless @base == 10
93
+ end
94
+
95
+ "#{prefix}#{suffix}"
96
+ end
97
+
98
+ def self.can_parse?( value )
99
+ [RplNumeric, BigDecimal, Integer, Float].include?( value.class ) or
100
+ ( value.is_a?( String ) and ( value.match?(/^-?[0-9]*\.?[0-9]+$/) or
101
+ value.match?(/^-?∞$/) or
102
+ value.match?(/^0b[0-1]+$/) or
103
+ value.match?(/^0o[0-7]+$/) or
104
+ value.match?(/^0x[0-9a-f]+$/) or
105
+ ( value.match?(/^0[0-9]+_[0-9a-z]+$/) and
106
+ value.split('_').first.to_i <= 36 ) ) )
107
+ end
108
+
109
+ def ==( other )
110
+ other.class == RplNumeric and
111
+ other.base == base and
112
+ other.value == value
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Types
4
+ class RplProgram
5
+ attr_accessor :value
6
+
7
+ def initialize( value )
8
+ raise RplTypeError unless self.class.can_parse?( value )
9
+
10
+ # we systematicalyl trim enclosing « »
11
+ @value = value[2..-3] # TODO: parse each element ?
12
+ end
13
+
14
+ def to_s
15
+ "« #{@value} »"
16
+ end
17
+
18
+ def self.can_parse?( value )
19
+ value.length > 4 && value[0..1] == '« ' && value[-2..-1] == ' »' && !value[2..-3].strip.empty?
20
+ end
21
+
22
+ def ==( other )
23
+ other.class == RplProgram and
24
+ other.value == value
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Types
4
+ class RplString
5
+ attr_accessor :value
6
+
7
+ def initialize( value )
8
+ raise RplTypeError unless self.class.can_parse?( value )
9
+
10
+ # we systematicalyl trim enclosing "
11
+ @value = value[1..-2]
12
+ end
13
+
14
+ def to_s
15
+ "\"#{@value}\""
16
+ end
17
+
18
+ def self.can_parse?( value )
19
+ value[0] == '"' && value[-1] == '"'
20
+ end
21
+
22
+ def ==( other )
23
+ other.class == RplString and
24
+ other.value == value
25
+ end
26
+ end
27
+ end