halunke 0.8.0 → 0.9.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: b88fa1971391744a766f367a96f4be7c602b93ff01d0f97efc20075faa0bb715
4
- data.tar.gz: 6f407c6b082186d4231095b71169e3f2c588bf50b87a8e368a4ffc10e2241566
3
+ metadata.gz: 3b782ee6ddb661b1ab07e7beadc086c692572ed3e2aa3a80a33b9c598ed70a93
4
+ data.tar.gz: f6aa3c8e8984a15ae7593f388354b69b8d9915a544acce6f1911aeebb171c40f
5
5
  SHA512:
6
- metadata.gz: 48775bdb30c4f3d9372e6365f68bf434f51e3591e39de8764bd7c3671e4690acc91c337592444b9f05d360adeb51274c5a0696dc7a2d646978b4039637d19bdd
7
- data.tar.gz: ea26967bfaa91237a248ca4f345884bfef4c1c49137ab70bd0895d2c436d455d7cc0dbb2bb18108ea0977cc227fc14ebf1329e0aca9e05361004afd3c98b4921
6
+ metadata.gz: 61e527ced02be4c0a7b182d9838539148be73683c9fe7421c4e55753a882052551f9e5c16a3ee5e7a75b2224ebcaaeb87e19c1eb51a041dd75dffdf93b331791
7
+ data.tar.gz: 05dc2c4b8f27b6c4a7bae7cf0bd18fbaac19eee8b85bb1c90d0db107ffcc6169eed686c7c3e74e66c3f2f7a0ebd1059200f8740b6e0cadd2ee82e88d77b8687b
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- halunke (0.8.0)
4
+ halunke (0.9.0)
5
5
  rack (~> 2.0.4)
6
6
 
7
7
  GEM
@@ -11,6 +11,7 @@ GEM
11
11
  racc (1.4.15)
12
12
  rack (2.0.6)
13
13
  rake (12.3.2)
14
+ warning (0.10.1)
14
15
 
15
16
  PLATFORMS
16
17
  ruby
@@ -21,6 +22,7 @@ DEPENDENCIES
21
22
  minitest (~> 5.11.3)
22
23
  racc (~> 1.4.15)
23
24
  rake (~> 12.3.2)
25
+ warning (~> 0.10.1)
24
26
 
25
27
  BUNDLED WITH
26
28
  1.17.2
data/README.md CHANGED
@@ -15,6 +15,7 @@ object-oriented language:
15
15
  It also has the following characteristics:
16
16
 
17
17
  * There is no null/nil value in the language
18
+ * Playing close attention to error message design
18
19
 
19
20
  Find out more on the [documentation page](http://halunke.jetzt).
20
21
 
data/docs/index.md CHANGED
@@ -18,6 +18,7 @@ object-oriented language:
18
18
  It also has the following characteristics:
19
19
 
20
20
  * There is no null/nil value in the language
21
+ * Playing close attention to error message design
21
22
 
22
23
  ## Install & Usage
23
24
 
@@ -0,0 +1,6 @@
1
+ ('a = 12)
2
+ ('my = "str")
3
+
4
+ ("hello" replace (my replac "i" wit "a") with "mumpf")
5
+
6
+ (stdio puts "a")
data/examples/stdio.hal CHANGED
@@ -5,9 +5,9 @@
5
5
  (stdio puts true)
6
6
  (stdio puts false)
7
7
 
8
- (stdio p "Hello World")
9
- (stdio p 5.2)
10
- (stdio p @["a" 2 "b" 3])
11
- (stdio p ["a" "b"])
12
- (stdio p true)
13
- (stdio p false)
8
+ (stdio examine "Hello World")
9
+ (stdio examine 5.2)
10
+ (stdio examine @["a" 2 "b" 3])
11
+ (stdio examine ["a" "b"])
12
+ (stdio examine true)
13
+ (stdio examine false)
data/exe/halunke CHANGED
@@ -12,12 +12,12 @@ if ARGV.length == 0
12
12
  puts "Halunke #{Halunke::VERSION} REPL. Ctrl+d to quit"
13
13
  while (line = Readline.readline(">> ", true))
14
14
  begin
15
- value = interpreter.eval(line)
15
+ value = interpreter.eval(line, error_mode: :repl)
16
16
  puts "=> #{value}" if value
17
17
  rescue Exception => e
18
18
  puts "An Error Occurred: #{e.message}"
19
19
  end
20
20
  end
21
21
  else
22
- interpreter.eval(File.read(ARGV[0]))
22
+ interpreter.eval(File.read(ARGV[0]), error_mode: :file, exit_on_error: true)
23
23
  end
data/halunke.gemspec CHANGED
@@ -23,6 +23,7 @@ Gem::Specification.new do |spec|
23
23
 
24
24
  spec.add_dependency "rack", "~> 2.0.4"
25
25
 
26
+ spec.add_development_dependency "warning", "~> 0.10.1"
26
27
  spec.add_development_dependency "bundler", "~> 1.17.2"
27
28
  spec.add_development_dependency "rake", "~> 12.3.2"
28
29
  spec.add_development_dependency "minitest", "~> 5.11.3"
@@ -28,16 +28,16 @@ rule
28
28
  ;
29
29
 
30
30
  Expression:
31
- NUMBER { NumberNode.new(val[0]) }
32
- | STRING { StringNode.new(val[0]) }
33
- | BAREWORD { BarewordNode.new(val[0]) }
34
- | UNASSIGNED_BAREWORD { UnassignedNode.new(BarewordNode.new(val[0])) }
31
+ NUMBER { NumberNode.new(*val[0]) }
32
+ | STRING { StringNode.new(*val[0]) }
33
+ | BAREWORD { BarewordNode.new(*val[0]) }
34
+ | UNASSIGNED_BAREWORD { UnassignedNode.new(BarewordNode.new(*val[0]), val[0][1], val[0][2]) }
35
35
  | START_COMMENT Expressions END_COMMENT { Nodes.new([]) }
36
- | OPEN_CURLY Expressions CLOSE_CURLY { FunctionNode.new(ArrayNode.new([]), val[1]) }
37
- | OPEN_CURLY BAR Expressions BAR Expressions CLOSE_CURLY { FunctionNode.new(val[2].to_array, val[4]) }
38
- | OPEN_PAREN Expression Expressions CLOSE_PAREN { MessageSendNode.new(val[1], val[2].to_message) }
39
- | OPEN_BRACKET Expressions CLOSE_BRACKET { val[1].to_array }
40
- | OPEN_DICT_BRACKET Expressions CLOSE_BRACKET { val[1].to_dictionary }
36
+ | OPEN_CURLY Expressions CLOSE_CURLY { FunctionNode.new(ArrayNode.new([]), val[1], val[0][1], val[2][2]) }
37
+ | OPEN_CURLY BAR Expressions BAR Expressions CLOSE_CURLY { FunctionNode.new(ArrayNode.new(val[2].nodes), val[4], val[0][1], val[5][2]) }
38
+ | OPEN_PAREN Expression Expressions CLOSE_PAREN { MessageSendNode.new(val[1], MessageNode.new(val[2].nodes), val[0][1], val[3][2]) }
39
+ | OPEN_BRACKET Expressions CLOSE_BRACKET { ArrayNode.new(val[1].nodes, val[0][1], val[2][2]) }
40
+ | OPEN_DICT_BRACKET Expressions CLOSE_BRACKET { DictionaryNode.new(val[1].nodes, val[0][1], val[2][2]) }
41
41
  ;
42
42
  end
43
43
 
@@ -0,0 +1,51 @@
1
+ require "rubygems/text"
2
+
3
+ module Halunke
4
+ class HError < StandardError
5
+ attr_reader :message
6
+ attr_reader :source_code_position
7
+
8
+ def initialize(message, source_code_position)
9
+ @message = message
10
+ @source_code_position = source_code_position
11
+ super(message)
12
+ end
13
+ end
14
+
15
+ class HUnknownMessage < HError
16
+ include Gem::Text
17
+
18
+ def initialize(receiver, message_name, receivable_messages, source_code_position)
19
+ message = [
20
+ "#{receiver} received the message `#{message_name}`. It doesn't know how to handle that.",
21
+ did_you_mean(receivable_messages, message_name)
22
+ ].join("\n")
23
+
24
+ super(message, source_code_position)
25
+ end
26
+
27
+ private
28
+
29
+ def did_you_mean(receivable_messages, message_name)
30
+ guess = receivable_messages.min_by { |m| levenshtein_distance(m, message_name) }
31
+
32
+ if levenshtein_distance(guess, message_name) < 5
33
+ "Did you mean `#{guess}`?"
34
+ else
35
+ "It supports the following messages: #{receivable_messages.join(", ")}"
36
+ end
37
+ end
38
+ end
39
+
40
+ class HBarewordAlreadyAssigned < HError
41
+ end
42
+
43
+ class HUnassignedBareword < HError
44
+ end
45
+
46
+ class HEmptyFunction < HError
47
+ end
48
+
49
+ class HUnknownAttribute < HError
50
+ end
51
+ end
@@ -26,10 +26,18 @@ module Halunke
26
26
  end
27
27
  end
28
28
 
29
- def eval(str)
29
+ def eval(str, error_mode: :raise, exit_on_error: false)
30
30
  nodes = @parser.parse(str)
31
31
  result = nodes.eval(root_context)
32
32
  result.nil? ? nil : result.inspect(root_context)
33
+ rescue HError => err
34
+ raise err if error_mode == :raise
35
+
36
+ puts err.source_code_position.reveal(str, error_mode)
37
+ puts err.message
38
+ exit(1) if exit_on_error
39
+
40
+ nil
33
41
  end
34
42
 
35
43
  def preludes
@@ -48,14 +56,14 @@ module Halunke
48
56
  end
49
57
 
50
58
  def []=(name, value)
51
- raise "Bareword #{name} is already assigned" if key? name
59
+ raise FrozenError if key? name
52
60
  @context[name] = value
53
61
  end
54
62
 
55
63
  def [](name)
56
64
  @context.fetch(name)
57
65
  rescue KeyError
58
- raise "Bareword '#{name} is unassigned'" if @parent.nil?
66
+ raise KeyError if @parent.nil?
59
67
  @parent[name]
60
68
  end
61
69
 
data/lib/halunke/nodes.rb CHANGED
@@ -7,62 +7,84 @@ module Halunke
7
7
  def empty?
8
8
  nodes.empty?
9
9
  end
10
-
11
- def to_message
12
- MessageNode.new(nodes)
13
- end
14
-
15
- def to_array
16
- ArrayNode.new(nodes)
17
- end
18
-
19
- def to_dictionary
20
- DictionaryNode.new(nodes)
21
- end
22
10
  end
23
11
 
24
- NumberNode = Struct.new(:value) do
12
+ NumberNode = Struct.new(:value, :ts, :te) do
25
13
  def eval(context)
26
14
  context["Number"].create_instance(value)
27
15
  end
16
+
17
+ def ==(other)
18
+ other.is_a?(NumberNode) &&
19
+ value == other.value
20
+ end
28
21
  end
29
22
 
30
- StringNode = Struct.new(:value) do
23
+ StringNode = Struct.new(:value, :ts, :te) do
31
24
  def eval(context)
32
25
  context["String"].create_instance(value)
33
26
  end
27
+
28
+ def ==(other)
29
+ other.is_a?(StringNode) &&
30
+ value == other.value
31
+ end
34
32
  end
35
33
 
36
- BarewordNode = Struct.new(:value) do
34
+ BarewordNode = Struct.new(:value, :ts, :te) do
37
35
  def eval(context)
38
36
  context[value]
37
+ rescue KeyError
38
+ source_code_position = SourceCodePosition.new(ts, te)
39
+ raise HUnassignedBareword.new("Bareword '#{value} is unassigned", source_code_position)
40
+ end
41
+
42
+ def ==(other)
43
+ other.is_a?(BarewordNode) &&
44
+ value == other.value
39
45
  end
40
46
  end
41
47
 
42
- UnassignedNode = Struct.new(:node) do
48
+ UnassignedNode = Struct.new(:node, :ts, :te) do
43
49
  def eval(context)
44
- context["UnassignedBareword"].create_instance(node.value)
50
+ context["UnassignedBareword"].create_instance(node.value, source_code_position: SourceCodePosition.new(ts, te))
51
+ end
52
+
53
+ def ==(other)
54
+ other.is_a?(UnassignedNode) &&
55
+ node == other.node
45
56
  end
46
57
  end
47
58
 
48
- FunctionNode = Struct.new(:params, :body) do
59
+ FunctionNode = Struct.new(:params, :body, :ts, :te) do
49
60
  def eval(context)
50
- raise "This function would not return anything. That's forbidden." if body.empty?
61
+ raise HEmptyFunction.new("This function would not return anything, in Halunke every function needs to return something.", SourceCodePosition.new(ts, te)) if body.empty?
51
62
  signature = params.nodes.map(&:node).map(&:value)
52
63
 
53
64
  context["Function"].new(signature, lambda { |call_context|
54
65
  body.eval(call_context)
55
66
  })
56
67
  end
68
+
69
+ def ==(other)
70
+ other.is_a?(FunctionNode) &&
71
+ params == other.params &&
72
+ body == other.body
73
+ end
57
74
  end
58
75
 
59
- ArrayNode = Struct.new(:nodes) do
76
+ ArrayNode = Struct.new(:nodes, :ts, :te) do
60
77
  def eval(context)
61
78
  context["Array"].create_instance(nodes.map { |node| node.eval(context) })
62
79
  end
80
+
81
+ def ==(other)
82
+ other.is_a?(ArrayNode) &&
83
+ nodes == other.nodes
84
+ end
63
85
  end
64
86
 
65
- DictionaryNode = Struct.new(:nodes) do
87
+ DictionaryNode = Struct.new(:nodes, :ts, :te) do
66
88
  def eval(context)
67
89
  hash = {}
68
90
  nodes.each_slice(2) do |key, value|
@@ -70,11 +92,23 @@ module Halunke
70
92
  end
71
93
  context["Dictionary"].create_instance(hash)
72
94
  end
95
+
96
+ def ==(other)
97
+ other.is_a?(DictionaryNode) &&
98
+ nodes == other.nodes
99
+ end
73
100
  end
74
101
 
75
- MessageSendNode = Struct.new(:receiver, :message) do
102
+ MessageSendNode = Struct.new(:receiver, :message, :ts, :te) do
76
103
  def eval(context)
77
- receiver.eval(context).receive_message(context, *message.eval(context))
104
+ receiver.eval(context).receive_message(context, *message.eval(context),
105
+ source_code_position: SourceCodePosition.new(ts, te))
106
+ end
107
+
108
+ def ==(other)
109
+ other.is_a?(MessageSendNode) &&
110
+ receiver == other.receiver &&
111
+ message == other.message
78
112
  end
79
113
  end
80
114
 
@@ -190,25 +190,25 @@ module_eval(<<'.,.,', 'grammar.y', 26)
190
190
 
191
191
  module_eval(<<'.,.,', 'grammar.y', 30)
192
192
  def _reduce_4(val, _values)
193
- NumberNode.new(val[0])
193
+ NumberNode.new(*val[0])
194
194
  end
195
195
  .,.,
196
196
 
197
197
  module_eval(<<'.,.,', 'grammar.y', 31)
198
198
  def _reduce_5(val, _values)
199
- StringNode.new(val[0])
199
+ StringNode.new(*val[0])
200
200
  end
201
201
  .,.,
202
202
 
203
203
  module_eval(<<'.,.,', 'grammar.y', 32)
204
204
  def _reduce_6(val, _values)
205
- BarewordNode.new(val[0])
205
+ BarewordNode.new(*val[0])
206
206
  end
207
207
  .,.,
208
208
 
209
209
  module_eval(<<'.,.,', 'grammar.y', 33)
210
210
  def _reduce_7(val, _values)
211
- UnassignedNode.new(BarewordNode.new(val[0]))
211
+ UnassignedNode.new(BarewordNode.new(*val[0]), val[0][1], val[0][2])
212
212
  end
213
213
  .,.,
214
214
 
@@ -220,31 +220,31 @@ module_eval(<<'.,.,', 'grammar.y', 34)
220
220
 
221
221
  module_eval(<<'.,.,', 'grammar.y', 35)
222
222
  def _reduce_9(val, _values)
223
- FunctionNode.new(ArrayNode.new([]), val[1])
223
+ FunctionNode.new(ArrayNode.new([]), val[1], val[0][1], val[2][2])
224
224
  end
225
225
  .,.,
226
226
 
227
227
  module_eval(<<'.,.,', 'grammar.y', 36)
228
228
  def _reduce_10(val, _values)
229
- FunctionNode.new(val[2].to_array, val[4])
229
+ FunctionNode.new(ArrayNode.new(val[2].nodes), val[4], val[0][1], val[5][2])
230
230
  end
231
231
  .,.,
232
232
 
233
233
  module_eval(<<'.,.,', 'grammar.y', 37)
234
234
  def _reduce_11(val, _values)
235
- MessageSendNode.new(val[1], val[2].to_message)
235
+ MessageSendNode.new(val[1], MessageNode.new(val[2].nodes), val[0][1], val[3][2])
236
236
  end
237
237
  .,.,
238
238
 
239
239
  module_eval(<<'.,.,', 'grammar.y', 38)
240
240
  def _reduce_12(val, _values)
241
- val[1].to_array
241
+ ArrayNode.new(val[1].nodes, val[0][1], val[2][2])
242
242
  end
243
243
  .,.,
244
244
 
245
245
  module_eval(<<'.,.,', 'grammar.y', 39)
246
246
  def _reduce_13(val, _values)
247
- val[1].to_dictionary
247
+ DictionaryNode.new(val[1].nodes, val[0][1], val[2][2])
248
248
  end
249
249
  .,.,
250
250
 
@@ -1,7 +1,11 @@
1
+ require "halunke/source_code_position"
2
+ require "halunke/herror"
3
+
1
4
  module Halunke
2
5
  module Runtime
3
6
  class HClass
4
7
  attr_reader :name
8
+ attr_reader :instance_methods
5
9
 
6
10
  def initialize(name, allowed_attributes, instance_methods, class_methods, native)
7
11
  @name = name
@@ -12,7 +16,7 @@ module Halunke
12
16
  end
13
17
 
14
18
  class << self
15
- def receive_message(context, message_name, message_value)
19
+ def receive_message(context, message_name, message_value, source_code_position: NullSourceCodePosition.new)
16
20
  case message_name
17
21
  when "new attributes methods class_methods"
18
22
  name = determine_name(message_value[0])
@@ -30,7 +34,8 @@ module Halunke
30
34
  instance_methods = determine_methods(message_value[1])
31
35
  class_methods = {}
32
36
  else
33
- raise "Class Class has no method to respond to message '#{message_name}'"
37
+ known_messages = ["new attributes methods class_methods", "new attributes methods", "new methods"]
38
+ raise HUnknownMessage.new("Class", message_name, known_messages, source_code_position)
34
39
  end
35
40
 
36
41
  context[name] = HClass.new(name, allowed_attributes, instance_methods, class_methods, false)
@@ -47,7 +52,17 @@ module Halunke
47
52
  end
48
53
 
49
54
  def determine_methods(hdictionary)
50
- instance_methods = {}
55
+ instance_methods = {
56
+ "inspect" => HFunction.new([:self], lambda { |context|
57
+ hself = context["self"]
58
+
59
+ attributes = hself.dict.map do |key, value|
60
+ %Q{"#{key}" #{value.inspect(context)}}
61
+ end
62
+
63
+ HString.create_instance("#{hself.runtime_class.name} @[#{attributes.join(' ')}]")
64
+ })
65
+ }
51
66
  hdictionary.ruby_value.each_pair do |method_name, fn|
52
67
  instance_methods[method_name.ruby_value] = fn
53
68
  end
@@ -55,14 +70,15 @@ module Halunke
55
70
  end
56
71
  end
57
72
 
58
- def receive_message(context, message_name, message_value)
73
+ def receive_message(context, message_name, message_value, source_code_position: NullSourceCodePosition.new)
59
74
  if message_name == "new" && !native?
60
- create_instance(message_value[0])
61
- elsif @class_methods.keys.include? message_name
75
+ create_instance(message_value[0], source_code_position: source_code_position)
76
+ elsif @class_methods.key? message_name
62
77
  m = @class_methods[message_name]
63
- m.receive_message(context, "call", [HArray.create_instance([self].concat(message_value))])
78
+ m.receive_message(context, "call", [HArray.create_instance([self].concat(message_value))],
79
+ source_code_position: source_code_position)
64
80
  else
65
- raise "Class #{@name} has no method to respond to message '#{message_name}'"
81
+ raise HUnknownMessage.new(self.inspect(context), message_name, @class_methods.keys, source_code_position)
66
82
  end
67
83
  end
68
84
 
@@ -70,22 +86,24 @@ module Halunke
70
86
  @allowed_attributes.include? attribute_name
71
87
  end
72
88
 
73
- def create_instance(value = nil)
89
+ def create_instance(value = nil, source_code_position: NullSourceCodePosition.new)
74
90
  if native?
75
- HNativeObject.new(self, value)
91
+ HNativeObject.new(self, value, source_code_position: source_code_position)
76
92
  else
77
- HObject.new(self, value ? value.ruby_value : {})
93
+ HObject.new(self, value ? value.ruby_value : {}, source_code_position: source_code_position)
78
94
  end
79
95
  end
80
96
 
81
97
  def lookup(message)
82
98
  @instance_methods.fetch(message)
83
- rescue KeyError
84
- raise "Class #{@name} has no method to respond to message '#{message}'"
85
99
  end
86
100
 
87
101
  def inspect(_context)
88
- "#<Class #{@name}>"
102
+ "Class #{@name}"
103
+ end
104
+
105
+ def runtime_class
106
+ self
89
107
  end
90
108
 
91
109
  def native?
@@ -1,3 +1,5 @@
1
+ require "halunke/source_code_position"
2
+
1
3
  module Halunke
2
4
  module Runtime
3
5
  class HFunction
@@ -6,8 +8,8 @@ module Halunke
6
8
  @fn = fn
7
9
  end
8
10
 
9
- def receive_message(parent_context, message_name, message_value)
10
- raise "Class Function has no method to respond to message '#{message_name}'" unless message_name == "call"
11
+ def receive_message(parent_context, message_name, message_value, source_code_position: NullSourceCodePosition.new)
12
+ raise HUnknownMessage.new(self.inspect(parent_context), message_name, ["call"], source_code_position) unless message_name == "call"
11
13
 
12
14
  context = parent_context.create_child
13
15
  args = message_value[0].ruby_value
@@ -20,7 +22,7 @@ module Halunke
20
22
  end
21
23
 
22
24
  def inspect(_context)
23
- "#<Function (#{@signature.length})>"
25
+ "Function (#{@signature.length})"
24
26
  end
25
27
  end
26
28
  end
@@ -1,16 +1,23 @@
1
+ require "halunke/source_code_position"
2
+
1
3
  module Halunke
2
4
  module Runtime
3
5
  class HNativeObject
6
+ attr_reader :runtime_class
4
7
  attr_reader :ruby_value
8
+ attr_reader :source_code_position
5
9
 
6
- def initialize(runtime_class, ruby_value = nil)
10
+ def initialize(runtime_class, ruby_value, source_code_position:)
7
11
  @runtime_class = runtime_class
8
12
  @ruby_value = ruby_value
13
+ @source_code_position = source_code_position
9
14
  end
10
15
 
11
- def receive_message(context, message_name, message_value)
16
+ def receive_message(context, message_name, message_value, source_code_position: NullSourceCodePosition.new)
12
17
  m = @runtime_class.lookup(message_name)
13
18
  m.receive_message(context, "call", [HArray.create_instance([self].concat(message_value))])
19
+ rescue KeyError
20
+ raise HUnknownMessage.new(self.inspect(context), message_name, @runtime_class.instance_methods.keys, source_code_position) if m.nil?
14
21
  end
15
22
 
16
23
  def inspect(context)
@@ -1,25 +1,32 @@
1
+ require "halunke/source_code_position"
2
+
1
3
  module Halunke
2
4
  module Runtime
3
5
  class HObject
4
6
  attr_reader :dict
7
+ attr_reader :runtime_class
8
+ attr_reader :source_code_position
5
9
 
6
- def initialize(runtime_class, dict)
10
+ def initialize(runtime_class, dict, source_code_position:)
7
11
  @runtime_class = runtime_class
8
12
  @dict = {}
13
+ @source_code_position = source_code_position
9
14
  dict.each_pair do |hkey, value|
10
15
  key = hkey.ruby_value
11
- raise "Unknown attribute '#{key}' for #{@runtime_class.name}" unless @runtime_class.allowed_attribute? key
16
+ raise HUnknownAttribute.new("Unknown attribute '#{key}' for #{@runtime_class.name}", source_code_position) unless @runtime_class.allowed_attribute? key
12
17
  @dict[key] = value
13
18
  end
14
19
  end
15
20
 
16
- def receive_message(context, message_name, message_value)
21
+ def receive_message(context, message_name, message_value, source_code_position: NullSourceCodePosition.new)
17
22
  if message_name == "@ else"
18
23
  @dict.fetch(message_value[0].ruby_value, message_value[1])
19
24
  else
20
25
  m = @runtime_class.lookup(message_name)
21
26
  m.receive_message(context, "call", [HArray.create_instance([self].concat(message_value))])
22
27
  end
28
+ rescue KeyError
29
+ raise HUnknownMessage.new(self.inspect(context), message_name, @runtime_class.instance_methods.keys, source_code_position)
23
30
  end
24
31
 
25
32
  def inspect(context)
@@ -25,7 +25,7 @@ module Halunke
25
25
  h[HString.create_instance(key)] = HString.create_instance(value)
26
26
  end
27
27
  end
28
- HDictionary.create_instance(h)
28
+ HDictionary.create_instance(h, source_code_position: NullSourceCodePosition.new)
29
29
  }),
30
30
  "scan" => HFunction.new([:self, :regexp], lambda { |context|
31
31
  result = context["self"].ruby_value.scan(context["regexp"].ruby_value)
@@ -5,7 +5,14 @@ module Halunke
5
5
  [],
6
6
  {
7
7
  "=" => HFunction.new([:self, :other], lambda { |context|
8
- context.parent[context["self"].ruby_value] = context["other"]
8
+ bareword_name = context["self"].ruby_value
9
+
10
+ begin
11
+ context.parent[bareword_name] = context["other"]
12
+ rescue
13
+ assigned_value = context.parent[context["self"].ruby_value].inspect(context)
14
+ raise HBarewordAlreadyAssigned.new("Bareword '#{bareword_name} is already assigned to #{assigned_value}. In Halunke, you can only assign once.", context["self"].source_code_position)
15
+ end
9
16
  context["true"]
10
17
  }),
11
18
  "inspect" => HFunction.new([:self], lambda { |context|
@@ -0,0 +1,40 @@
1
+ module Halunke
2
+ class SourceCodePosition
3
+ def initialize(ts, te)
4
+ @ts = ts
5
+ @te = te
6
+ end
7
+
8
+ def reveal(source, error_mode)
9
+ line, line_number = source.lines.each_with_index do |candidate, candidate_line_number|
10
+ break candidate, candidate_line_number if @ts < candidate.length
11
+
12
+ @ts -= candidate.length
13
+ @te -= candidate.length
14
+ end
15
+
16
+ if @te > line.length
17
+ @te = line.length - 2
18
+ ellipsis = '...'
19
+ end
20
+
21
+ prefix = error_mode == :repl ? ">> " : "#{line_number + 1} | "
22
+
23
+ output = []
24
+ output << [prefix, line.rstrip, ellipsis].join("") if error_mode == :file
25
+ output << " " * (@ts + prefix.length) + "^" * (@te - @ts + 1)
26
+ output << "\n"
27
+
28
+ output
29
+ end
30
+ end
31
+
32
+ class NullSourceCodePosition
33
+ def initialize
34
+ end
35
+
36
+ def reveal(source, error_mode)
37
+ "(Can't find the source code position, sorry)"
38
+ end
39
+ end
40
+ end
@@ -285,73 +285,73 @@ when 5 then
285
285
  # line 23 "lib/halunke/tokenizer.rl"
286
286
  begin
287
287
  te = p+1
288
- begin emit(:STRING, data[ts+1...te-1]) end
288
+ begin emit(:STRING, data[ts+1...te-1], ts, te - 1) end
289
289
  end
290
290
  when 6 then
291
291
  # line 25 "lib/halunke/tokenizer.rl"
292
292
  begin
293
293
  te = p+1
294
- begin emit(:BAREWORD, data[ts...te]) end
294
+ begin emit(:BAREWORD, data[ts...te], ts, te - 1) end
295
295
  end
296
296
  when 7 then
297
297
  # line 26 "lib/halunke/tokenizer.rl"
298
298
  begin
299
299
  te = p+1
300
- begin emit(:OPEN_PAREN, data[ts...te]) end
300
+ begin emit(:OPEN_PAREN, data[ts...te], ts, te - 1) end
301
301
  end
302
302
  when 8 then
303
303
  # line 27 "lib/halunke/tokenizer.rl"
304
304
  begin
305
305
  te = p+1
306
- begin emit(:CLOSE_PAREN, data[ts...te]) end
306
+ begin emit(:CLOSE_PAREN, data[ts...te], ts, te - 1) end
307
307
  end
308
308
  when 9 then
309
309
  # line 28 "lib/halunke/tokenizer.rl"
310
310
  begin
311
311
  te = p+1
312
- begin emit(:OPEN_CURLY, data[ts...te]) end
312
+ begin emit(:OPEN_CURLY, data[ts...te], ts, te - 1) end
313
313
  end
314
314
  when 10 then
315
315
  # line 29 "lib/halunke/tokenizer.rl"
316
316
  begin
317
317
  te = p+1
318
- begin emit(:CLOSE_CURLY, data[ts...te]) end
318
+ begin emit(:CLOSE_CURLY, data[ts...te], ts, te - 1) end
319
319
  end
320
320
  when 11 then
321
321
  # line 30 "lib/halunke/tokenizer.rl"
322
322
  begin
323
323
  te = p+1
324
- begin emit(:OPEN_BRACKET, data[ts...te]) end
324
+ begin emit(:OPEN_BRACKET, data[ts...te], ts, te - 1) end
325
325
  end
326
326
  when 12 then
327
327
  # line 31 "lib/halunke/tokenizer.rl"
328
328
  begin
329
329
  te = p+1
330
- begin emit(:CLOSE_BRACKET, data[ts...te]) end
330
+ begin emit(:CLOSE_BRACKET, data[ts...te], ts, te - 1) end
331
331
  end
332
332
  when 13 then
333
333
  # line 32 "lib/halunke/tokenizer.rl"
334
334
  begin
335
335
  te = p+1
336
- begin emit(:OPEN_DICT_BRACKET, data[ts...te]) end
336
+ begin emit(:OPEN_DICT_BRACKET, data[ts...te], ts, te - 1) end
337
337
  end
338
338
  when 14 then
339
339
  # line 33 "lib/halunke/tokenizer.rl"
340
340
  begin
341
341
  te = p+1
342
- begin emit(:START_COMMENT, data[ts...te]) end
342
+ begin emit(:START_COMMENT, data[ts...te], ts, te - 1) end
343
343
  end
344
344
  when 15 then
345
345
  # line 34 "lib/halunke/tokenizer.rl"
346
346
  begin
347
347
  te = p+1
348
- begin emit(:END_COMMENT, data[ts...te]) end
348
+ begin emit(:END_COMMENT, data[ts...te], ts, te - 1) end
349
349
  end
350
350
  when 16 then
351
351
  # line 35 "lib/halunke/tokenizer.rl"
352
352
  begin
353
353
  te = p+1
354
- begin emit(:BAR, data[ts...te]) end
354
+ begin emit(:BAR, data[ts...te], ts, te - 1) end
355
355
  end
356
356
  when 17 then
357
357
  # line 36 "lib/halunke/tokenizer.rl"
@@ -368,13 +368,13 @@ when 19 then
368
368
  # line 22 "lib/halunke/tokenizer.rl"
369
369
  begin
370
370
  te = p
371
- p = p - 1; begin emit(:NUMBER, data[ts...te].to_r) end
371
+ p = p - 1; begin emit(:NUMBER, data[ts...te].to_r, ts, te - 1) end
372
372
  end
373
373
  when 20 then
374
374
  # line 25 "lib/halunke/tokenizer.rl"
375
375
  begin
376
376
  te = p
377
- p = p - 1; begin emit(:BAREWORD, data[ts...te]) end
377
+ p = p - 1; begin emit(:BAREWORD, data[ts...te], ts, te - 1) end
378
378
  end
379
379
  when 21 then
380
380
  # line 37 "lib/halunke/tokenizer.rl"
@@ -386,7 +386,7 @@ when 22 then
386
386
  # line 22 "lib/halunke/tokenizer.rl"
387
387
  begin
388
388
  begin p = ((te))-1; end
389
- begin emit(:NUMBER, data[ts...te].to_r) end
389
+ begin emit(:NUMBER, data[ts...te].to_r, ts, te - 1) end
390
390
  end
391
391
  when 23 then
392
392
  # line 37 "lib/halunke/tokenizer.rl"
@@ -400,7 +400,7 @@ when 24 then
400
400
  case act
401
401
  when 3 then
402
402
  begin begin p = ((te))-1; end
403
- emit(:UNASSIGNED_BAREWORD, data[ts+1 ...te]) end
403
+ emit(:UNASSIGNED_BAREWORD, data[ts+1 ...te], ts, te - 1) end
404
404
  when 16 then
405
405
  begin begin p = ((te))-1; end
406
406
  raise "Could not lex '#{ data[ts...te] }'" end
@@ -460,8 +460,8 @@ end
460
460
 
461
461
  private
462
462
 
463
- def emit(type, value)
464
- @tokens << [ type, value ]
463
+ def emit(type, value, ts, te)
464
+ @tokens << [ type, [ value, ts, te ] ]
465
465
  end
466
466
  end
467
467
  end
@@ -19,20 +19,20 @@
19
19
 
20
20
  main := |*
21
21
 
22
- number => { emit(:NUMBER, data[ts...te].to_r) };
23
- string => { emit(:STRING, data[ts+1...te-1]) };
24
- unassigned_bareword => { emit(:UNASSIGNED_BAREWORD, data[ts+1 ...te]) };
25
- bareword => { emit(:BAREWORD, data[ts...te]) };
26
- open_paren => { emit(:OPEN_PAREN, data[ts...te]) };
27
- close_paren => { emit(:CLOSE_PAREN, data[ts...te]) };
28
- open_curly => { emit(:OPEN_CURLY, data[ts...te]) };
29
- close_curly => { emit(:CLOSE_CURLY, data[ts...te]) };
30
- open_bracket => { emit(:OPEN_BRACKET, data[ts...te]) };
31
- close_bracket => { emit(:CLOSE_BRACKET, data[ts...te]) };
32
- open_dict_bracket => { emit(:OPEN_DICT_BRACKET, data[ts...te]) };
33
- start_comment => { emit(:START_COMMENT, data[ts...te]) };
34
- end_comment => { emit(:END_COMMENT, data[ts...te]) };
35
- bar => { emit(:BAR, data[ts...te]) };
22
+ number => { emit(:NUMBER, data[ts...te].to_r, ts, te - 1) };
23
+ string => { emit(:STRING, data[ts+1...te-1], ts, te - 1) };
24
+ unassigned_bareword => { emit(:UNASSIGNED_BAREWORD, data[ts+1 ...te], ts, te - 1) };
25
+ bareword => { emit(:BAREWORD, data[ts...te], ts, te - 1) };
26
+ open_paren => { emit(:OPEN_PAREN, data[ts...te], ts, te - 1) };
27
+ close_paren => { emit(:CLOSE_PAREN, data[ts...te], ts, te - 1) };
28
+ open_curly => { emit(:OPEN_CURLY, data[ts...te], ts, te - 1) };
29
+ close_curly => { emit(:CLOSE_CURLY, data[ts...te], ts, te - 1) };
30
+ open_bracket => { emit(:OPEN_BRACKET, data[ts...te], ts, te - 1) };
31
+ close_bracket => { emit(:CLOSE_BRACKET, data[ts...te], ts, te - 1) };
32
+ open_dict_bracket => { emit(:OPEN_DICT_BRACKET, data[ts...te], ts, te - 1) };
33
+ start_comment => { emit(:START_COMMENT, data[ts...te], ts, te - 1) };
34
+ end_comment => { emit(:END_COMMENT, data[ts...te], ts, te - 1) };
35
+ bar => { emit(:BAR, data[ts...te], ts, te - 1) };
36
36
  space;
37
37
  any => { raise "Could not lex '#{ data[ts...te] }'" };
38
38
 
@@ -59,8 +59,8 @@ module Halunke
59
59
 
60
60
  private
61
61
 
62
- def emit(type, value)
63
- @tokens << [ type, value ]
62
+ def emit(type, value, ts, te)
63
+ @tokens << [ type, [ value, ts, te ] ]
64
64
  end
65
65
  end
66
66
  end
@@ -1,3 +1,3 @@
1
1
  module Halunke
2
- VERSION = "0.8.0"
2
+ VERSION = "0.9.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: halunke
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lucas Dohmen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-03-03 00:00:00.000000000 Z
11
+ date: 2019-03-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: 2.0.4
27
+ - !ruby/object:Gem::Dependency
28
+ name: warning
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.10.1
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.10.1
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: bundler
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -119,6 +133,7 @@ files:
119
133
  - docs/true-false.md
120
134
  - docs/web.md
121
135
  - examples/counter.hal
136
+ - examples/error.hal
122
137
  - examples/hello.hal
123
138
  - examples/stdio.hal
124
139
  - examples/web.hal
@@ -126,6 +141,7 @@ files:
126
141
  - halunke.gemspec
127
142
  - lib/halunke.rb
128
143
  - lib/halunke/grammar.y
144
+ - lib/halunke/herror.rb
129
145
  - lib/halunke/interpreter.rb
130
146
  - lib/halunke/nodes.rb
131
147
  - lib/halunke/parser.rb
@@ -144,6 +160,7 @@ files:
144
160
  - lib/halunke/runtime/hunassigned_bareword.rb
145
161
  - lib/halunke/runtime/hweb.rb
146
162
  - lib/halunke/runtime/true.hal
163
+ - lib/halunke/source_code_position.rb
147
164
  - lib/halunke/tokenizer.rb
148
165
  - lib/halunke/tokenizer.rl
149
166
  - lib/halunke/version.rb