Skip to content

Commit 81ce3c3

Browse files
committed
feat(compiler, builtins): add corresponding builtins for the operators, so that we can compile operators to their corresponding builtins and use them as functions in list:map and others
1 parent 9ddb592 commit 81ce3c3

5 files changed

Lines changed: 316 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## [Unreleased changes] - 2026-XX-YY
44
### Added
55
- `apply` function: `(apply func [args...])`, to call a function with a set of arguments stored in a list. Works with functions, closures and builtins
6+
- `+`, `-`, `*`, `/` and many other operators can now be passed around, like builtins. This now works: `(list:reduce [1 2 3] +)`, where before we would get a compile time error about a "freestanding operator '+'"
67

78
## [4.2.0] - 2026-02-04
89
### Breaking changes

include/Ark/Builtins/Builtins.hpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,33 @@ namespace Ark::internal::Builtins
136136
{
137137
ARK_BUILTIN(disassemble);
138138
}
139+
140+
namespace Operators
141+
{
142+
ARK_BUILTIN(add);
143+
ARK_BUILTIN(sub);
144+
ARK_BUILTIN(mul);
145+
ARK_BUILTIN(div);
146+
ARK_BUILTIN(mod);
147+
ARK_BUILTIN(toNumber);
148+
ARK_BUILTIN(toString);
149+
ARK_BUILTIN(lessThan);
150+
ARK_BUILTIN(lessOrEq);
151+
ARK_BUILTIN(greaterThan);
152+
ARK_BUILTIN(greaterOrEq);
153+
ARK_BUILTIN(eq);
154+
ARK_BUILTIN(notEq);
155+
ARK_BUILTIN(not_);
156+
ARK_BUILTIN(len);
157+
ARK_BUILTIN(isEmpty);
158+
ARK_BUILTIN(isNil);
159+
ARK_BUILTIN(tail);
160+
ARK_BUILTIN(head);
161+
ARK_BUILTIN(at);
162+
ARK_BUILTIN(atAt);
163+
ARK_BUILTIN(type);
164+
ARK_BUILTIN(hasField);
165+
}
139166
}
140167

141168
#undef ARK_BUILTIN

src/arkreactor/Builtins/Builtins.cpp

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,31 @@ namespace Ark::internal::Builtins
108108
{ "builtin__dict:size", Value(Dict::size) },
109109

110110
// Bytecode
111-
{ "disassemble", Value(Bytecode::disassemble) }
111+
{ "disassemble", Value(Bytecode::disassemble) },
112+
113+
// Operators that can also be used as builtins
114+
{ "+", Value(Operators::add) },
115+
{ "-", Value(Operators::sub) },
116+
{ "*", Value(Operators::mul) },
117+
{ "/", Value(Operators::div) },
118+
{ "mod", Value(Operators::mod) },
119+
{ "toNumber", Value(Operators::toNumber) },
120+
{ "toString", Value(Operators::toString) },
121+
{ "<", Value(Operators::lessThan) },
122+
{ "<=", Value(Operators::lessOrEq) },
123+
{ ">", Value(Operators::greaterThan) },
124+
{ ">=", Value(Operators::greaterOrEq) },
125+
{ "=", Value(Operators::eq) },
126+
{ "!=", Value(Operators::notEq) },
127+
{ "not", Value(Operators::not_) },
128+
{ "len", Value(Operators::len) },
129+
{ "empty?", Value(Operators::isEmpty) },
130+
{ "nil?", Value(Operators::isNil) },
131+
{ "tail", Value(Operators::tail) },
132+
{ "head", Value(Operators::head) },
133+
{ "@", Value(Operators::at) },
134+
{ "@@", Value(Operators::atAt) },
135+
{ "type", Value(Operators::type) },
136+
{ "hasField", Value(Operators::hasField) }
112137
};
113138
}
Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
#include <Ark/Builtins/Builtins.hpp>
2+
3+
#include <Ark/Constants.hpp>
4+
#include <Ark/TypeChecker.hpp>
5+
#include <Ark/VM/DefaultValues.hpp>
6+
#include <Ark/VM/VM.hpp>
7+
#include <Ark/VM/Value/Dict.hpp>
8+
#include <Ark/Utils/Utils.hpp>
9+
10+
namespace Ark::internal::Builtins::Operators
11+
{
12+
// cppcheck-suppress constParameterReference
13+
Value add(std::vector<Value>& n, VM* vm [[maybe_unused]])
14+
{
15+
if (n.empty())
16+
throw types::TypeCheckingError(
17+
"+",
18+
{ { types::Contract { { types::Typedef("a", ValueType::Number, /* is_variadic= */ true) } } } }, n);
19+
20+
double output = 0.0;
21+
for (const Value& num : n)
22+
{
23+
if (num.valueType() != ValueType::Number)
24+
throw types::TypeCheckingError(
25+
"+",
26+
{ { types::Contract { { types::Typedef("a", ValueType::Number, /* is_variadic= */ true) } } } }, n);
27+
output += num.number();
28+
}
29+
30+
return Value(output);
31+
}
32+
33+
// cppcheck-suppress constParameterReference
34+
Value sub(std::vector<Value>& n, VM* vm [[maybe_unused]])
35+
{
36+
if (n.empty() || n[0].valueType() != ValueType::Number)
37+
throw types::TypeCheckingError(
38+
"-",
39+
{ { types::Contract { { types::Typedef("a", ValueType::Number, /* is_variadic= */ true) } } } }, n);
40+
41+
double output = n[0].number();
42+
for (const Value& num : n | std::ranges::views::drop(1))
43+
{
44+
if (num.valueType() != ValueType::Number)
45+
throw types::TypeCheckingError(
46+
"-",
47+
{ { types::Contract { { types::Typedef("a", ValueType::Number, /* is_variadic= */ true) } } } }, n);
48+
output -= num.number();
49+
}
50+
51+
return Value(output);
52+
}
53+
54+
// cppcheck-suppress constParameterReference
55+
Value mul(std::vector<Value>& n, VM* vm [[maybe_unused]])
56+
{
57+
if (n.size() < 2 || n[0].valueType() != ValueType::Number || n[1].valueType() != ValueType::Number)
58+
throw types::TypeCheckingError(
59+
"*",
60+
{ { types::Contract { { types::Typedef("a", ValueType::Number, /* is_variadic= */ true) } } } }, n);
61+
62+
double output = n[0].number();
63+
for (const Value& num : n | std::ranges::views::drop(1))
64+
{
65+
if (num.valueType() != ValueType::Number)
66+
throw types::TypeCheckingError(
67+
"*",
68+
{ { types::Contract { { types::Typedef("a", ValueType::Number, /* is_variadic= */ true) } } } }, n);
69+
output *= num.number();
70+
}
71+
72+
return Value(output);
73+
}
74+
75+
// cppcheck-suppress constParameterReference
76+
Value div(std::vector<Value>& n, VM* vm [[maybe_unused]])
77+
{
78+
if (n.size() < 2 || n[0].valueType() != ValueType::Number || n[1].valueType() != ValueType::Number)
79+
throw types::TypeCheckingError(
80+
"*",
81+
{ { types::Contract { { types::Typedef("a", ValueType::Number, /* is_variadic= */ true) } } } }, n);
82+
83+
double output = n[0].number();
84+
for (const Value& num : n | std::ranges::views::drop(1))
85+
{
86+
if (num.valueType() != ValueType::Number)
87+
throw types::TypeCheckingError(
88+
"*",
89+
{ { types::Contract { { types::Typedef("a", ValueType::Number, /* is_variadic= */ true) } } } }, n);
90+
output *= num.number();
91+
}
92+
93+
return Value(output);
94+
}
95+
96+
// cppcheck-suppress constParameterReference
97+
Value mod(std::vector<Value>& n, VM* vm [[maybe_unused]])
98+
{
99+
if (!types::check(n, ValueType::Number, ValueType::Number))
100+
throw types::TypeCheckingError(
101+
"mod",
102+
{ { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
103+
n);
104+
105+
return Value(std::fmod(n[0].number(), n[1].number()));
106+
}
107+
108+
// cppcheck-suppress constParameterReference
109+
Value toNumber(std::vector<Value>& n, VM* vm [[maybe_unused]])
110+
{
111+
if (!types::check(n, ValueType::String))
112+
throw types::TypeCheckingError(
113+
"toNumber",
114+
{ { types::Contract { { types::Typedef("val", ValueType::String) } } } },
115+
n);
116+
117+
double val;
118+
if (Utils::isDouble(n[0].string(), &val))
119+
return Value(val);
120+
return Nil;
121+
}
122+
123+
// cppcheck-suppress constParameterReference
124+
Value toString(std::vector<Value>& n, VM* vm)
125+
{
126+
if (n.size() != 1)
127+
throw types::TypeCheckingError(
128+
"toString",
129+
{ { types::Contract { { types::Typedef("val", ValueType::Any) } } } },
130+
n);
131+
132+
return Value(n[0].toString(*vm));
133+
}
134+
135+
// cppcheck-suppress constParameterReference
136+
Value lessThan(std::vector<Value>& n [[maybe_unused]], VM* vm [[maybe_unused]])
137+
{
138+
return Nil;
139+
}
140+
141+
// cppcheck-suppress constParameterReference
142+
Value lessOrEq(std::vector<Value>& n [[maybe_unused]], VM* vm [[maybe_unused]])
143+
{
144+
return Nil;
145+
}
146+
147+
// cppcheck-suppress constParameterReference
148+
Value greaterThan(std::vector<Value>& n [[maybe_unused]], VM* vm [[maybe_unused]])
149+
{
150+
return Nil;
151+
}
152+
153+
// cppcheck-suppress constParameterReference
154+
Value greaterOrEq(std::vector<Value>& n [[maybe_unused]], VM* vm [[maybe_unused]])
155+
{
156+
return Nil;
157+
}
158+
159+
// cppcheck-suppress constParameterReference
160+
Value eq(std::vector<Value>& n [[maybe_unused]], VM* vm [[maybe_unused]])
161+
{
162+
return Nil;
163+
}
164+
165+
// cppcheck-suppress constParameterReference
166+
Value notEq(std::vector<Value>& n [[maybe_unused]], VM* vm [[maybe_unused]])
167+
{
168+
return Nil;
169+
}
170+
171+
// cppcheck-suppress constParameterReference
172+
Value not_(std::vector<Value>& n [[maybe_unused]], VM* vm [[maybe_unused]])
173+
{
174+
return Nil;
175+
}
176+
177+
// cppcheck-suppress constParameterReference
178+
Value len(std::vector<Value>& n [[maybe_unused]], VM* vm [[maybe_unused]])
179+
{
180+
return Nil;
181+
}
182+
183+
// cppcheck-suppress constParameterReference
184+
Value isEmpty(std::vector<Value>& n, VM* vm [[maybe_unused]])
185+
{
186+
if (n.size() != 1)
187+
throw types::TypeCheckingError(
188+
"empty?",
189+
{ { types::Contract { { types::Typedef("value", ValueType::List) } },
190+
types::Contract { { types::Typedef("value", ValueType::Nil) } },
191+
types::Contract { { types::Typedef("value", ValueType::String) } },
192+
types::Contract { { types::Typedef("value", ValueType::Dict) } } } },
193+
n);
194+
195+
if (n[0].valueType() == ValueType::List)
196+
return n[0].constList().empty() ? True : False;
197+
if (n[0].valueType() == ValueType::String)
198+
return n[0].string().empty() ? True : False;
199+
if (n[0].valueType() == ValueType::Dict)
200+
return std::cmp_equal(n[0].dict().size(), 0) ? True : False;
201+
if (n[0].valueType() == ValueType::Nil)
202+
return True;
203+
204+
throw types::TypeCheckingError(
205+
"empty?",
206+
{ { types::Contract { { types::Typedef("value", ValueType::List) } },
207+
types::Contract { { types::Typedef("value", ValueType::Nil) } },
208+
types::Contract { { types::Typedef("value", ValueType::String) } },
209+
types::Contract { { types::Typedef("value", ValueType::Dict) } } } },
210+
n);
211+
}
212+
213+
// cppcheck-suppress constParameterReference
214+
Value isNil(std::vector<Value>& n, VM* vm [[maybe_unused]])
215+
{
216+
if (n.size() != 1)
217+
throw types::TypeCheckingError(
218+
"nil?",
219+
{ { types::Contract { { types::Typedef("value", ValueType::Any) } } } },
220+
n);
221+
222+
if (n[0] == Nil)
223+
return True;
224+
return False;
225+
}
226+
227+
// cppcheck-suppress constParameterReference
228+
Value tail(std::vector<Value>& n [[maybe_unused]], VM* vm [[maybe_unused]])
229+
{
230+
return Nil;
231+
}
232+
233+
// cppcheck-suppress constParameterReference
234+
Value head(std::vector<Value>& n [[maybe_unused]], VM* vm [[maybe_unused]])
235+
{
236+
return Nil;
237+
}
238+
239+
// cppcheck-suppress constParameterReference
240+
Value at(std::vector<Value>& n [[maybe_unused]], VM* vm [[maybe_unused]])
241+
{
242+
return Nil;
243+
}
244+
245+
// cppcheck-suppress constParameterReference
246+
Value atAt(std::vector<Value>& n [[maybe_unused]], VM* vm [[maybe_unused]])
247+
{
248+
return Nil;
249+
}
250+
251+
// cppcheck-suppress constParameterReference
252+
Value type(std::vector<Value>& n [[maybe_unused]], VM* vm [[maybe_unused]])
253+
{
254+
return Nil;
255+
}
256+
257+
// cppcheck-suppress constParameterReference
258+
Value hasField(std::vector<Value>& n [[maybe_unused]], VM* vm [[maybe_unused]])
259+
{
260+
return Nil;
261+
}
262+
}

src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -267,8 +267,6 @@ namespace Ark::internal
267267

268268
if (const auto it_builtin = getBuiltin(name))
269269
page(p).emplace_back(Instruction::BUILTIN, it_builtin.value());
270-
else if (getOperator(name).has_value())
271-
buildAndThrowError(fmt::format("Found a free standing operator: `{}`", name), x);
272270
else
273271
{
274272
if (can_use_ref)
@@ -768,7 +766,6 @@ namespace Ark::internal
768766
page(p).emplace_back(op);
769767
}
770768

771-
// todo: allow using operators with 0 or 1 argument, but push their builtin counterpart (todo as well)
772769
if (isBreakpoint(x))
773770
{
774771
if (exp_count > 1)

0 commit comments

Comments
 (0)