rpl 0.5.0 → 0.6.0

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