rpl 0.3.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: 8f26f2f32e0e42c05a0ed59c2f21bb31433a5c2be29454cc17e7c5e1ee9bc2b1
4
- data.tar.gz: ddb9ad1384954af38402126f5425654e1534b684f57271736bd64eace4cc987a
3
+ metadata.gz: 473327f669ed4e0484a32f534efa52bcbfbb8521ebcad7fa4d38de35f6576972
4
+ data.tar.gz: cd6d921d8f40edcd95a32d4af529d7435962c65f621fa7903cbee4a1fabcf07f
5
5
  SHA512:
6
- metadata.gz: 1d3ebfc6baa238fc2ad342f71c871399cbb37d4e8a87bbbbaf0173573947592ebeb62a2686a4a99ba07bf8860e97abb0615abce86450ffa328915cace9523658
7
- data.tar.gz: ee1d985c2f136ef8eedd496b3c5a1c051f19f46aef118c2e308f2b5dbbe2cc5191555e714fc398c0c518c13e388e48d30db4d81751160bf0e8f6e1374dc17eae
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
@@ -1,11 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'bigdecimal'
3
4
  require 'bigdecimal/math'
5
+ require 'bigdecimal/util'
4
6
 
5
7
  require 'rpl/dictionary'
8
+ require 'rpl/types'
6
9
 
7
10
  class Interpreter
8
11
  include BigMath
12
+ include Types
9
13
 
10
14
  attr_reader :stack,
11
15
  :dictionary,
@@ -14,146 +18,37 @@ class Interpreter
14
18
  attr_accessor :precision
15
19
 
16
20
  def initialize( stack = [], dictionary = Rpl::Lang::Dictionary.new )
17
- @version = 0.1
18
-
19
- @precision = default_precision
21
+ @version = 0.6
20
22
 
21
23
  @dictionary = dictionary
22
24
  @stack = stack
23
-
24
- populate_dictionary if @dictionary.words.empty?
25
- end
26
-
27
- def default_precision
28
- 12
29
- end
30
-
31
- def parse( input )
32
- is_numeric = lambda do |elt|
33
- begin
34
- !Float(elt).nil?
35
- rescue ArgumentError
36
- begin
37
- !Integer(elt).nil?
38
- rescue ArgumentError
39
- false
40
- end
41
- end
42
- end
43
-
44
- splitted_input = input.split(' ')
45
-
46
- # 2-passes:
47
- # 1. regroup strings and programs
48
- opened_programs = 0
49
- closed_programs = 0
50
- opened_lists = 0
51
- closed_lists = 0
52
- string_delimiters = 0
53
- name_delimiters = 0
54
- regrouping = false
55
-
56
- regrouped_input = []
57
- splitted_input.each do |elt|
58
- if elt[0] == '«'
59
- opened_programs += 1
60
- elt.gsub!( '«', '« ') if elt.length > 1 && elt[1] != ' '
61
- elsif elt[0] == '{'
62
- opened_lists += 1
63
- elt.gsub!( '{', '{ ') if elt.length > 1 && elt[1] != ' '
64
- elsif elt[0] == '"' && elt.length > 1
65
- string_delimiters += 1
66
- elsif elt[0] == "'" && elt.length > 1
67
- name_delimiters += 1
68
- end
69
-
70
- elt = "#{regrouped_input.pop} #{elt}".strip if regrouping
71
-
72
- regrouped_input << elt
73
-
74
- case elt[-1]
75
- when '»'
76
- closed_programs += 1
77
- elt.gsub!( '»', ' »') if elt.length > 1 && elt[-2] != ' '
78
- when '}'
79
- closed_lists += 1
80
- elt.gsub!( '}', ' }') if elt.length > 1 && elt[-2] != ' '
81
- when '"'
82
- string_delimiters += 1
83
- when "'"
84
- name_delimiters += 1
85
- end
86
-
87
- regrouping = string_delimiters.odd? || name_delimiters.odd? || (opened_programs > closed_programs ) || (opened_lists > closed_lists )
88
- end
89
-
90
- # 2. parse
91
- # TODO: parse ∞, <NaN> as numerics
92
- parsed_tree = []
93
- regrouped_input.each do |elt|
94
- parsed_entry = { value: elt }
95
-
96
- parsed_entry[:type] = case elt[0]
97
- when '«'
98
- :program
99
- when '{'
100
- :list
101
- when '"'
102
- :string
103
- when "'"
104
- :name # TODO: check for forbidden space
105
- else
106
- if is_numeric.call( elt )
107
- :numeric
108
- else
109
- :word
110
- end
111
- end
112
-
113
- if %I[string name].include?( parsed_entry[:type] )
114
- parsed_entry[:value] = parsed_entry[:value][1..-2]
115
- elsif parsed_entry[:type] == :program
116
- parsed_entry[:value] = parsed_entry[:value][2..-3]
117
- elsif parsed_entry[:type] == :list
118
- parsed_entry[:value] = parse( parsed_entry[:value][2..-3] )
119
- elsif parsed_entry[:type] == :numeric
120
- parsed_entry[:base] = 10 # TODO: parse others possible bases 0x...
121
-
122
- begin
123
- parsed_entry[:value] = Float( parsed_entry[:value] )
124
- parsed_entry[:value] = parsed_entry[:value].to_i if (parsed_entry[:value] % 1).zero? && elt.index('.').nil?
125
- rescue ArgumentError
126
- parsed_entry[:value] = Integer( parsed_entry[:value] )
127
- end
128
-
129
- parsed_entry[:value] = BigDecimal( parsed_entry[:value], @precision )
130
- end
131
-
132
- parsed_tree << parsed_entry
133
- end
134
-
135
- parsed_tree
136
25
  end
137
26
 
138
27
  def run( input )
139
28
  @dictionary.add_local_vars_layer
140
29
 
141
- parse( input.to_s ).each do |elt|
142
- case elt[:type]
143
- when :word
144
- break if %w[break quit exit].include?( elt[:value] )
145
-
146
- command = @dictionary.lookup( elt[:value] )
147
-
148
- if command.nil?
149
- # if there isn't a command by that name then it's a name
150
- 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 )
151
33
 
34
+ if elt.not_to_evaluate
152
35
  @stack << elt
153
- elsif command.is_a?( Proc )
154
- command.call
155
36
  else
156
- 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
157
52
  end
158
53
  else
159
54
  @stack << elt
@@ -172,7 +67,7 @@ class Interpreter
172
67
  needs.each_with_index do |need, index|
173
68
  stack_index = (index + 1) * -1
174
69
 
175
- 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 )
176
71
  end
177
72
 
178
73
  args = []
@@ -183,52 +78,7 @@ class Interpreter
183
78
  args
184
79
  end
185
80
 
186
- def stringify( elt )
187
- case elt[:type]
188
- when :numeric
189
- prefix = case elt[:base]
190
- when 2
191
- '0b'
192
- when 8
193
- '0o'
194
- when 10
195
- ''
196
- when 16
197
- '0x'
198
- else
199
- "0#{elt[:base]}_"
200
- end
201
-
202
- if elt[:value].infinite?
203
- suffix = elt[:value].infinite?.positive? ? '∞' : '-∞'
204
- elsif elt[:value].nan?
205
- suffix = '<NaN>'
206
- else
207
- suffix = if elt[:value].to_i == elt[:value]
208
- elt[:value].to_i
209
- else
210
- elt[:value].to_s('F')
211
- end
212
- suffix = elt[:value].to_s( elt[:base] ) unless elt[:base] == 10
213
- end
214
-
215
- "#{prefix}#{suffix}"
216
- when :list
217
- "{ #{elt[:value].map { |e| stringify( e ) }.join(' ')} }"
218
- when :program
219
- "« #{elt[:value]} »"
220
- when :string
221
- "\"#{elt[:value]}\""
222
- when :name
223
- "'#{elt[:value]}'"
224
- else
225
- elt[:value]
226
- end
227
- end
228
-
229
- def infer_resulting_base( numerics )
230
- 10 if numerics.length.zero?
231
-
232
- numerics.last[:base]
81
+ def export_stack
82
+ @stack.map(&:to_s).join(' ')
233
83
  end
234
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
data/lib/rpl/types.rb ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rpl/types/boolean'
4
+ require 'rpl/types/name'
5
+ require 'rpl/types/list'
6
+ require 'rpl/types/string'
7
+ require 'rpl/types/program'
8
+ require 'rpl/types/numeric'