halunke 0.8.0 → 0.9.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: 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