halunke 0.9.0 → 0.10.1
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 +4 -4
- data/.ruby-version +1 -0
- data/.woodpecker/test.yml +9 -0
- data/Dockerfile +6 -6
- data/Gemfile +11 -1
- data/Gemfile.lock +74 -13
- data/README.md +1 -1
- data/Rakefile +6 -1
- data/docs/Gemfile +1 -0
- data/docs/Gemfile.lock +176 -164
- data/docs/_layouts/default.html +1 -19
- data/docs/index.md +4 -4
- data/docs/stdio.md +1 -1
- data/docs/web.md +1 -1
- data/exe/halunke +8 -3
- data/halunke.gemspec +3 -7
- data/lib/halunke/grammar.y +7 -3
- data/lib/halunke/herror.rb +9 -0
- data/lib/halunke/nodes.rb +75 -31
- data/lib/halunke/parser.rb +720 -72
- data/lib/halunke/runtime/hclass.rb +11 -2
- data/lib/halunke/runtime/hregexp.rb +1 -1
- data/lib/halunke/runtime/hweb.rb +6 -6
- data/lib/halunke/tokenizer.rb +8 -9
- data/lib/halunke/tokenizer.rl +1 -2
- data/lib/halunke/version.rb +1 -1
- metadata +20 -64
- data/.travis.yml +0 -5
data/docs/index.md
CHANGED
|
@@ -70,10 +70,10 @@ the value is `["Ha" "Spe"]`.
|
|
|
70
70
|
If you want to find out more about the types of objects in Halunke
|
|
71
71
|
and the messages you can send to them, explore the navigation bar
|
|
72
72
|
at the top. If you want to learn more about conditionals, check out
|
|
73
|
-
the section about [True & False](/
|
|
73
|
+
the section about [True & False](/true-false). If you want
|
|
74
74
|
to write your own functions, check out
|
|
75
|
-
[Function](/
|
|
76
|
-
classes, check out [Class](/
|
|
75
|
+
[Function](/function) and if you want to define your own
|
|
76
|
+
classes, check out [Class](/class).
|
|
77
77
|
|
|
78
78
|
If you want to store an object in a variable, you can do it like this:
|
|
79
79
|
|
|
@@ -87,7 +87,7 @@ with a value, and it will assign it. Be aware that you can't reassign. So if
|
|
|
87
87
|
you assign something to a once, it will stay like this forever (within that
|
|
88
88
|
scope). Reassigning will result in an error. *There will be reassignable
|
|
89
89
|
references in the future, see [the issue for
|
|
90
|
-
details](https://
|
|
90
|
+
details](https://codeberg.org/moonglum/halunke/issues/3)*
|
|
91
91
|
|
|
92
92
|
The values are also immutable. If you send a message to an object,
|
|
93
93
|
it will not change the object, but it will return an answer to you.
|
data/docs/stdio.md
CHANGED
|
@@ -4,7 +4,7 @@ title: Stdio
|
|
|
4
4
|
|
|
5
5
|
The object `stdio` is available from everywhere. *This will change in the
|
|
6
6
|
future. [Interacting with STDIO is IO and will be
|
|
7
|
-
isolated.](https://
|
|
7
|
+
isolated.](https://codeberg.org/moonglum/halunke/issues/4)*
|
|
8
8
|
|
|
9
9
|
## `puts`
|
|
10
10
|
|
data/docs/web.md
CHANGED
|
@@ -4,7 +4,7 @@ title: Web
|
|
|
4
4
|
|
|
5
5
|
The object `web` is available from everywhere. *This will change in the future.
|
|
6
6
|
[Interacting with STDIO is IO and will be
|
|
7
|
-
isolated.](https://
|
|
7
|
+
isolated.](https://codeberg.org/moonglum/halunke/issues/4)*
|
|
8
8
|
|
|
9
9
|
You can send the `run on` message to `web`. It expects the first object to be
|
|
10
10
|
your application (more on that later) and the second one the bind address and
|
data/exe/halunke
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
|
|
3
|
+
if File.exist?(File.expand_path("../.git", __dir__))
|
|
4
|
+
lib = File.expand_path("../lib", __dir__)
|
|
5
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
6
|
+
end
|
|
7
|
+
|
|
3
8
|
require "halunke/version"
|
|
4
9
|
require "halunke/interpreter"
|
|
5
|
-
require "
|
|
10
|
+
require "reline"
|
|
6
11
|
|
|
7
12
|
interpreter = Halunke::Interpreter.new
|
|
8
13
|
|
|
9
|
-
|
|
14
|
+
Reline.completion_proc = ->(*) {[]}
|
|
10
15
|
|
|
11
16
|
if ARGV.length == 0
|
|
12
17
|
puts "Halunke #{Halunke::VERSION} REPL. Ctrl+d to quit"
|
|
13
|
-
while (line =
|
|
18
|
+
while (line = Reline.readline(">> ", true))
|
|
14
19
|
begin
|
|
15
20
|
value = interpreter.eval(line, error_mode: :repl)
|
|
16
21
|
puts "=> #{value}" if value
|
data/halunke.gemspec
CHANGED
|
@@ -21,11 +21,7 @@ Gem::Specification.new do |spec|
|
|
|
21
21
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
22
22
|
spec.require_paths = ["lib"]
|
|
23
23
|
|
|
24
|
-
spec.add_dependency "
|
|
25
|
-
|
|
26
|
-
spec.
|
|
27
|
-
spec.add_development_dependency "bundler", "~> 1.17.2"
|
|
28
|
-
spec.add_development_dependency "rake", "~> 12.3.2"
|
|
29
|
-
spec.add_development_dependency "minitest", "~> 5.11.3"
|
|
30
|
-
spec.add_development_dependency "racc", "~> 1.4.15"
|
|
24
|
+
spec.add_dependency "rackup", "~> 2.3"
|
|
25
|
+
spec.add_dependency "webrick"
|
|
26
|
+
spec.add_dependency "reline"
|
|
31
27
|
end
|
data/lib/halunke/grammar.y
CHANGED
|
@@ -27,15 +27,19 @@ rule
|
|
|
27
27
|
| Expression Expressions { Nodes.new([val[0]].concat(val[1].nodes)) }
|
|
28
28
|
;
|
|
29
29
|
|
|
30
|
+
Arguments:
|
|
31
|
+
/* empty */ { ArrayNode.new([]) }
|
|
32
|
+
| BAR Expressions BAR { ArrayNode.new(val[1].nodes, val[0][1], val[2][2]) }
|
|
33
|
+
;
|
|
34
|
+
|
|
30
35
|
Expression:
|
|
31
36
|
NUMBER { NumberNode.new(*val[0]) }
|
|
32
37
|
| STRING { StringNode.new(*val[0]) }
|
|
33
38
|
| BAREWORD { BarewordNode.new(*val[0]) }
|
|
34
39
|
| UNASSIGNED_BAREWORD { UnassignedNode.new(BarewordNode.new(*val[0]), val[0][1], val[0][2]) }
|
|
35
40
|
| START_COMMENT Expressions END_COMMENT { Nodes.new([]) }
|
|
36
|
-
| OPEN_CURLY Expressions CLOSE_CURLY
|
|
37
|
-
|
|
|
38
|
-
| OPEN_PAREN Expression Expressions CLOSE_PAREN { MessageSendNode.new(val[1], MessageNode.new(val[2].nodes), val[0][1], val[3][2]) }
|
|
41
|
+
| OPEN_CURLY Arguments Expressions CLOSE_CURLY { FunctionNode.new(val[1], val[2], val[0][1], val[3][2]) }
|
|
42
|
+
| OPEN_PAREN Expressions CLOSE_PAREN { MessageSendNode.new(val[1].nodes, val[0][1], val[2][2]) }
|
|
39
43
|
| OPEN_BRACKET Expressions CLOSE_BRACKET { ArrayNode.new(val[1].nodes, val[0][1], val[2][2]) }
|
|
40
44
|
| OPEN_DICT_BRACKET Expressions CLOSE_BRACKET { DictionaryNode.new(val[1].nodes, val[0][1], val[2][2]) }
|
|
41
45
|
;
|
data/lib/halunke/herror.rb
CHANGED
data/lib/halunke/nodes.rb
CHANGED
|
@@ -11,31 +11,40 @@ module Halunke
|
|
|
11
11
|
|
|
12
12
|
NumberNode = Struct.new(:value, :ts, :te) do
|
|
13
13
|
def eval(context)
|
|
14
|
-
context["Number"].create_instance(value
|
|
14
|
+
context["Number"].create_instance(value,
|
|
15
|
+
source_code_position: source_code_position)
|
|
15
16
|
end
|
|
16
17
|
|
|
17
18
|
def ==(other)
|
|
18
19
|
other.is_a?(NumberNode) &&
|
|
19
20
|
value == other.value
|
|
20
21
|
end
|
|
22
|
+
|
|
23
|
+
def source_code_position
|
|
24
|
+
SourceCodePosition.new(ts, te)
|
|
25
|
+
end
|
|
21
26
|
end
|
|
22
27
|
|
|
23
28
|
StringNode = Struct.new(:value, :ts, :te) do
|
|
24
29
|
def eval(context)
|
|
25
|
-
context["String"].create_instance(value
|
|
30
|
+
context["String"].create_instance(value,
|
|
31
|
+
source_code_position: source_code_position)
|
|
26
32
|
end
|
|
27
33
|
|
|
28
34
|
def ==(other)
|
|
29
35
|
other.is_a?(StringNode) &&
|
|
30
36
|
value == other.value
|
|
31
37
|
end
|
|
38
|
+
|
|
39
|
+
def source_code_position
|
|
40
|
+
SourceCodePosition.new(ts, te)
|
|
41
|
+
end
|
|
32
42
|
end
|
|
33
43
|
|
|
34
44
|
BarewordNode = Struct.new(:value, :ts, :te) do
|
|
35
45
|
def eval(context)
|
|
36
46
|
context[value]
|
|
37
47
|
rescue KeyError
|
|
38
|
-
source_code_position = SourceCodePosition.new(ts, te)
|
|
39
48
|
raise HUnassignedBareword.new("Bareword '#{value} is unassigned", source_code_position)
|
|
40
49
|
end
|
|
41
50
|
|
|
@@ -43,22 +52,30 @@ module Halunke
|
|
|
43
52
|
other.is_a?(BarewordNode) &&
|
|
44
53
|
value == other.value
|
|
45
54
|
end
|
|
55
|
+
|
|
56
|
+
def source_code_position
|
|
57
|
+
SourceCodePosition.new(ts, te)
|
|
58
|
+
end
|
|
46
59
|
end
|
|
47
60
|
|
|
48
61
|
UnassignedNode = Struct.new(:node, :ts, :te) do
|
|
49
62
|
def eval(context)
|
|
50
|
-
context["UnassignedBareword"].create_instance(node.value, source_code_position:
|
|
63
|
+
context["UnassignedBareword"].create_instance(node.value, source_code_position: source_code_position)
|
|
51
64
|
end
|
|
52
65
|
|
|
53
66
|
def ==(other)
|
|
54
67
|
other.is_a?(UnassignedNode) &&
|
|
55
68
|
node == other.node
|
|
56
69
|
end
|
|
70
|
+
|
|
71
|
+
def source_code_position
|
|
72
|
+
SourceCodePosition.new(ts, te)
|
|
73
|
+
end
|
|
57
74
|
end
|
|
58
75
|
|
|
59
76
|
FunctionNode = Struct.new(:params, :body, :ts, :te) do
|
|
60
77
|
def eval(context)
|
|
61
|
-
raise HEmptyFunction.new("This function would not return anything, in Halunke every function needs to return something.",
|
|
78
|
+
raise HEmptyFunction.new("This function would not return anything, in Halunke every function needs to return something.", source_code_position) if body.empty?
|
|
62
79
|
signature = params.nodes.map(&:node).map(&:value)
|
|
63
80
|
|
|
64
81
|
context["Function"].new(signature, lambda { |call_context|
|
|
@@ -71,6 +88,10 @@ module Halunke
|
|
|
71
88
|
params == other.params &&
|
|
72
89
|
body == other.body
|
|
73
90
|
end
|
|
91
|
+
|
|
92
|
+
def source_code_position
|
|
93
|
+
SourceCodePosition.new(ts, te)
|
|
94
|
+
end
|
|
74
95
|
end
|
|
75
96
|
|
|
76
97
|
ArrayNode = Struct.new(:nodes, :ts, :te) do
|
|
@@ -82,56 +103,79 @@ module Halunke
|
|
|
82
103
|
other.is_a?(ArrayNode) &&
|
|
83
104
|
nodes == other.nodes
|
|
84
105
|
end
|
|
106
|
+
|
|
107
|
+
def source_code_position
|
|
108
|
+
SourceCodePosition.new(ts, te)
|
|
109
|
+
end
|
|
85
110
|
end
|
|
86
111
|
|
|
87
112
|
DictionaryNode = Struct.new(:nodes, :ts, :te) do
|
|
88
113
|
def eval(context)
|
|
89
114
|
hash = {}
|
|
90
115
|
nodes.each_slice(2) do |key, value|
|
|
116
|
+
raise HInvalidDictionary.new("This key has no value", key.source_code_position) if value.nil?
|
|
91
117
|
hash[key.eval(context)] = value.eval(context)
|
|
92
118
|
end
|
|
93
|
-
context["Dictionary"].create_instance(hash
|
|
119
|
+
context["Dictionary"].create_instance(hash,
|
|
120
|
+
source_code_position: source_code_position)
|
|
94
121
|
end
|
|
95
122
|
|
|
96
123
|
def ==(other)
|
|
97
124
|
other.is_a?(DictionaryNode) &&
|
|
98
125
|
nodes == other.nodes
|
|
99
126
|
end
|
|
127
|
+
|
|
128
|
+
def source_code_position
|
|
129
|
+
SourceCodePosition.new(ts, te)
|
|
130
|
+
end
|
|
100
131
|
end
|
|
101
132
|
|
|
102
|
-
MessageSendNode = Struct.new(:
|
|
133
|
+
MessageSendNode = Struct.new(:nodes, :ts, :te) do
|
|
103
134
|
def eval(context)
|
|
104
|
-
|
|
105
|
-
|
|
135
|
+
message_name, message_value = parse_message(context)
|
|
136
|
+
receiver.eval(context).receive_message(context, message_name, message_value,
|
|
137
|
+
source_code_position: source_code_position)
|
|
106
138
|
end
|
|
107
139
|
|
|
108
140
|
def ==(other)
|
|
109
141
|
other.is_a?(MessageSendNode) &&
|
|
110
|
-
|
|
111
|
-
message == other.message
|
|
142
|
+
nodes == other.nodes
|
|
112
143
|
end
|
|
113
|
-
end
|
|
114
144
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
145
|
+
def source_code_position
|
|
146
|
+
SourceCodePosition.new(ts, te)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
private
|
|
150
|
+
|
|
151
|
+
def receiver
|
|
152
|
+
raise HInvalidMessage.new("This message has no receiver", source_code_position) if nodes.length == 0
|
|
153
|
+
@receiver ||= nodes[0]
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def parse_message(context)
|
|
157
|
+
if message_nodes.length == 1
|
|
158
|
+
# hack to allow expressions like (1+5)
|
|
159
|
+
return ["+", [message_nodes[0].eval(context)]] if message_nodes[0].is_a? NumberNode
|
|
160
|
+
return [message_nodes[0].value, []] if message_nodes[0].is_a? BarewordNode
|
|
161
|
+
|
|
162
|
+
raise HInvalidMessage.new("This is not a bareword", message_nodes[0].source_code_position)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
name = []
|
|
166
|
+
message = []
|
|
167
|
+
message_nodes.each_slice(2) do |name_part, value|
|
|
168
|
+
raise HInvalidMessage.new("This is not a bareword", name_part.source_code_position) unless name_part.is_a? BarewordNode
|
|
169
|
+
raise HInvalidMessage.new("This bareword has no according argument", name_part.source_code_position) if value.nil?
|
|
170
|
+
name << name_part
|
|
171
|
+
message << value
|
|
134
172
|
end
|
|
173
|
+
[name.map(&:value).join(" "), message.map { |node| node.eval(context) }]
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def message_nodes
|
|
177
|
+
raise HInvalidMessage.new("You are sending an empty message", source_code_position) if nodes.length == 1
|
|
178
|
+
@message_nodes ||= nodes.drop(1)
|
|
135
179
|
end
|
|
136
180
|
end
|
|
137
181
|
end
|