|
This thread is supposed to become a progress report for Nimony. Tonight the following program worked for the first time: |
|
|
|
|
|
import std / syncio |
|
|
|
echo "hi", "abc" |
|
This might not look impressive but it is (IMO): Nimony is a new Nim compiler which does not use much of the old codebase. A good dozen subsystems have been reimplemented from scratch: |
|
|
|
Precompiled modules. |
|
Semantic checking. (Symbol lookup rules, type checking.) |
|
Generic instantiations. Generics are type checked and Nimony already has an implementation of Nim's concept keyword! |
|
Template expansions. |
|
For loop inlining. |
|
ARC based memory management. |
|
Code generation is done via NIFC. |
|
Of course, this is still not ready for anybody. But once ARC has received more bugfixes we have a minimal Nim that offers seqs+objects and modularity. |
|
|
|
For more information about its architecture, read: https://github.com/nim-lang/nimony/blob/master/doc/design.md |
|
|
|
For more information about its planned design, read: https://github.com/nim-lang/nimony/discussions/529 |
|
|
|
Blog post: https://nim-lang.org/araq/nimony.html |
|
|
|
The next milestone is to get our seq implementation to work. I hope to get there within the next 2 weeks, but that might be overly optimistic as the interaction between generics and ARC hooks is hard. |
|
|
|
Once seq works, our table implementation needs to compile. Once that is done, parseopt and then you can write super simple CLI programs with it while we implement ref... |
|
|
|
It's just |
|
|
|
|
|
template foobar(x, y: int): int {.plugin: "foobar.nim".} |
|
And then in foobar.nim you have a program that receives two command line parameters, two filenames <input.nif> and <output.nif> and you use the NIF APIs (or APIs on top of that that emulate macros.nim) for the transformation logic. |
|
|
|
The compiler caches the output and only runs the plugin logic if the input changed. The plugin is compiled to an .exe/binary file and thus runs at native speed. |
|
|
|
Slightly related how does this coincide with previous plans of removing untyped? |
|
|
|
The compiler typechecks the foobar template like any other template. This means that the plugin's output is sem'checked. |
|
|
|
Well this toy example works in Nimony: |
|
|
|
|
|
type |
|
Fibable = concept |
|
proc `<=`(a, b: Self): bool |
|
proc `+`(x, y: Self): Self |
|
proc `-`(x, y: Self): Self |
|
|
|
proc fib[T: Fibable](a: T): T = |
|
if a <= 2: |
|
result = 1 |
|
else: |
|
result = fib(a-1) + fib(a-2) |
|
|
|
discard fib(8) |
|
And it doesn't work without the concept as generics are type-checked. That's good enough for me. The rest is uninteresting details. ;-) |
|
|
|
Another progress report. This code does semcheck: |
|
|
|
|
|
type |
|
openArray*[T] {.view.} = object |
|
a: ptr UncheckedArray[T] |
|
len: int |
|
|
|
proc `[]`*[T](x: openArray[T]; idx: int): var T {.inline, requires: idx >= 0 and idx < x.len.} = x.a[idx] |
|
|
|
proc `[]=`*[T](x: openArray[T]; i: int; elem: sink T) {.inline, requires: i >= 0 and i < x.len.} = |
|
(x[i]) = elem |
|
|
|
converter toOpenArray*[I, T](x {.byref.}: array[I, T]): openArray[T] {.inline.} = |
|
if len(x) == 0: |
|
openArray[T](a: nil, len: 0) |
|
else: |
|
openArray[T](a: cast[ptr UncheckedArray[T]](addr(x)), len: len(x)) |
|
|
|
converter toOpenArray*(s: string): openArray[char] {.inline.} = |
|
openArray[char](a: rawData(s), len: s.len) |
|
|
|
func len*[T](a: openArray[T]): int {.inline.} = a.len |
|
|
|
type |
|
Equatable* = concept |
|
proc `==`(a, b: Self): bool |
|
|
|
func find*[T: Equatable](a: openArray[T]; elem: T): int = |
|
var i = 0 |
|
while i < len(a): |
|
if a[i] == elem: return i |
|
inc i |
|
return -1 |
|
|
|
func contains*[T: Equatable](a: openArray[T]; elem: T): bool {.inline.} = |
|
find(a, elem) >= 0 |
|
|
|
iterator items*[T](a: openArray[T]): var T = |
|
var i = 0 |
|
while i < len(a): |
|
yield a[i] |
|
inc i |
|
|
|
proc `==`*[T: Equatable](a, b: openArray[T]): bool = |
|
if a.len == b.len: |
|
for i in 0..<a.len: |
|
if a[i] != b[i]: return false |
|
return true |
|
return false |
|
Thanks to the new .view pragma openArray does not have to be a magic type anymore, simplifying the compiler. |
|
.requires annotations allow us to do bound check elimination in a principled manner. |
|
The required type annotations for generic code are not overwhelming. |
|
Thanks to polymorphic accessors a single [] proc is now enough. |
|
|
|
Next milestone reached: This program compiles&runs and produces correct output: |
|
|
|
|
|
import std/[parseopt, syncio] |
|
|
|
proc test = |
|
for kind, key, val in getopt(): |
|
echo "##", key, "##", val |
|
|
|
test() |
|
|
|
That's what it means, yes. Something like oomTrap can be used to turn allocation failures into traps with little effort: |
|
|
|
|
|
template oomTrap[T](x: ref T): ref T = |
|
let tmp = x |
|
if tmp == nil: quit "out of memory" |
|
tmp |
|
|
|
proc createObjects() = |
|
let a = oomTrap MyObjectRef() |
|
let b = oomTrap MyObjectRef() |
|
# compiler now understands that a and b are not nil: |
|
use(a, b) |
|
|
|
case of string now works: |
|
|
|
|
|
import std / [syncio] |
|
|
|
proc dispatch(x: string) = |
|
case x |
|
of "foo": |
|
echo "foo" |
|
of "bar": |
|
echo "bar" |
|
else: |
|
echo "unknown" |
|
|
|
dispatch("foo") |
|
dispatch("bar") |
|
dispatch("other") |
|
It should also produce better code than Nim as it produces dispatch trees and doesn't use hashing anymore. Hashing requires 2 complete scans over the input string, our trees only a single scan in the best case. |
|
|
|
Seqs are starting to work: |
|
|
|
|
|
import std / syncio |
|
|
|
proc x(count: int; data: string) = |
|
var s = newSeq[string](count) |
|
for i in 0..<count: |
|
s[i] = data |
|
|
|
echo s.len, " ", s[40] |
|
|
|
x(1000, "hi") |
|
But as predicted the hook generation is not there yet, no destructor for s is synthesized. |
|
Update: This program works now. |
|
|
|
Right now Nimony focuses on a "minimal viable product" (MVP), a Nim variant that combines IC + ARC + typechecked generics. As such many many features will not be available. The focus is on this combination as I consider it the hardest nut to crack. Once that works sufficiently well, we will add features until Nimony can compile most Nim 2.0 code out there and then call it Nim 3.0. |
|
|
|
However, I consider the MVP to be an interesting beautiful language of its own, so I anticipate it to stick around under a flag --mode:aufbruch. If you ask the question "what would a 10 times smaller Nim compressed to its essentials look like?", that is Nimony's "aufbruch" mode. |
|
|
|
Next milestone reached. This program compiles and produces the correct output: |
|
|
|
|
|
import std / syncio |
|
|
|
proc sort(a: var openArray[int]) = |
|
var n = a.len |
|
while true: |
|
var swapped = false |
|
var i = 0 |
|
while i < n-1: |
|
if a[i] > a[i+1]: |
|
swap a[i], a[i+1] |
|
swapped = true |
|
inc i |
|
dec n |
|
if not swapped: break |
|
|
|
var x = [3, 2, 1, 6, 7, 4, 5] |
|
sort x |
|
|
|
var i = 0 |
|
while i < x.len: |
|
echo x[i] |
|
inc i |
|
|
|
import std/[hashes, syncio, tables] |
|
|
|
block: |
|
var t = initTable[int, int]() |
|
assert not t.contains(123) |
|
assert not t.hasKey(123) |
|
assert t.getOrDefault(123) == 0 |
|
assert t.len == 0 |
|
t[123] = 321 |
|
assert t.len == 1 |
|
assert t.contains(123) |
|
assert t.hasKey(123) |
|
assert t.getOrDefault(123) == 321 |
|
assert t[123] == 321 |
|
assert t.mgetOrPut(123, -1) == 321 |
|
inc t[123] |
|
assert t[123] == 322 |
|
|
|
It's a new feature of Nimony, it does type-check generics when they are declared, not only when they are instantiated. And this check requires the usage of concepts, without the concept annotation, the code wouldn't compile. |
|
|
|
(We also have untyped generics for backwards compat, but as I keep saying, the current focus is on a coherent MVP.) |
|
|
|
I don't know what you mean but openArray is not a magic type anymore so Table[openArray[char], T] should cause no trouble. (Well no trouble beyond the massive borrow checking problems...) |
|
|
|
Progress report. This program works: |
|
|
|
|
|
import std / [syncio] |
|
|
|
type |
|
BinaryTree = ref object |
|
le, ri: BinaryTree |
|
data: string |
|
|
|
proc newNode*(data: sink string): BinaryTree = BinaryTree(data: data) |
|
|
|
proc append*(root: var BinaryTree; n: BinaryTree) = |
|
# insert a node into the tree |
|
if root == nil: |
|
root = n |
|
else: |
|
var it = root |
|
while it != nil: |
|
var c = cmp(n.data, it.data) |
|
if c < 0: |
|
if it.le == nil: |
|
it.le = n |
|
return |
|
it = it.le |
|
else: |
|
if it.ri == nil: |
|
it.ri = n |
|
return |
|
it = it.ri |
|
|
|
proc append*(root: var BinaryTree, data: sink string) = |
|
append(root, newNode(data)) |
|
|
|
proc toString(n: BinaryTree; result: var string) = |
|
if n == nil: return |
|
result.add n.data |
|
toString n.le, result |
|
toString n.ri, result |
|
|
|
proc `$`*(n: BinaryTree): string = |
|
result = "" |
|
toString n, result |
|
|
|
proc main = |
|
var x = newNode("abc") |
|
x.append "def" |
|
echo $x |
|
|
|
main() |
|
It does not leak memory either, recursive ref destructors seem to work well. |
|
|
|
Pleasant suprise: This test program works without any compiler bugfix: |
|
|
|
|
|
import std / syncio |
|
|
|
type |
|
X = object |
|
s: seq[string] |
|
|
|
proc use(x: int) = discard |
|
|
|
template copyInto(x: var X; body: untyped) = |
|
let oldLen = x.s.len |
|
body |
|
use oldLen |
|
|
|
type SymId = distinct int |
|
|
|
template `[]`*(syms: seq[string]; s: SymId): string = syms[s.int] |
|
|
|
proc main = |
|
var x = X(s: @["abc", "def"]) |
|
copyInto x: |
|
copyInto x: |
|
echo "yes ", x.s[SymId(1)] |
|
|
|
main() |
|
|
|
Another milestone reached. Our std/intset implementation works: |
|
|
|
|
|
import std / [syncio, intsets] |
|
|
|
proc main = |
|
echo "start" |
|
var s = initIntSet() |
|
for i in 5000..<6000: |
|
s.incl i |
|
assert s.contains i |
|
echo "500..<600" |
|
for i in 500..<600: |
|
s.incl i |
|
assert s.contains i |
|
|
|
echo "50000..<60000" |
|
for i in 50000..<60000: |
|
s.incl i |
|
assert s.contains i |
|
|
|
echo "0..<500" |
|
for i in 0..<500: |
|
assert not s.contains i |
|
|
|
echo "excl 50100" |
|
s.excl 50100 |
|
assert not s.contains 50100 |
|
|
|
assert not containsOrIncl(s, 7) |
|
assert containsOrIncl(s, 7) |
|
echo "success" |
|
|
|
main() |
|
This intset implementation uses our hash table implementation which in turn uses our seq implementation that works entirely without compiler magic as the ARC based memory management loses no efficiency when compared to a compiler builtin type. |
|
|
|
Progress: Overflow checking that does not rely on exception handling has been designed and implemented: |
|
|
|
|
|
import std / [syncio] |
|
|
|
proc main(a, b: int) = |
|
{.keepOverflowFlag.}: |
|
let x = a + b |
|
echo overflowFlag() |
|
|
|
main(1, high(int)) |
|
This is mapped directly to NIFC's new overflow checking support which is then mapped to GCC/clang's builtins. |
|
|
|
No complaints about the syntax please, we will make it beautiful later and map it to try except OverflowDefect. Eventually... :-) |
|
|
|
Well I need to write many blog posts... Nimony's strings are easily explained: |
|
I want some support for O(1) slicing, that pretty much rules out terminating zeros. That's bad for C interop. But 10 years ago I did some measurements with a bootstrapping compiler -- the zeros cause every string to be longer by one byte which means the allocation sizes might be longer by 8 bytes due to alignment. The memory overhead was surprising, 5% or something like that. So terminating zeros are just a bad idea when you have the length information ready elsewhere. |
|
We already have the typical wasMoved check in the destructor and the corresponding state. We can exploit that state for the new borrowCStringUnsafe operation. |
|
I don't have any benchmarks and the current implementation focuses on simplicity. But I'm reasonably certain that the API is strict enough to allow for lots of implementation changes without breaking client code. The new string implementation is much easier to change as there is almost no compiler magic. |
|
|
|
I don't like SSO if it implies some avoidable code bloat as all of Nimony's runtime is developed with an eye to embedded devices. But recently many people found ways to have "slim" SSO implementations so it's likely such a design will win. |
|
|
|
Progress: Builtin arrays now have index checking. |
|
|
|
This program fails at runtime with a nice error message: |
|
|
|
|
|
import std / [syncio] |
|
|
|
type |
|
TA = array[0..3, int] |
|
|
|
proc main(a: TA) = |
|
for i in 0..4: |
|
echo a[i] |
|
|
|
main([1, 2, 3, 4]) |
|
Now the same need to be done for seq. Which is actually easier to implement as we only have to map the .requires annotation to runtime checks... |
|
|
|
Progress report. fields and fieldPairs iterators have been implemented. For example, this program works: |
|
|
|
|
|
import std / [syncio] |
|
|
|
type Obj = object |
|
a, b: int |
|
c: string |
|
|
|
var o = Obj(a: 1, b: 2, c: "xyz") |
|
for f in fields(o): |
|
echo f |
|
This is a key component for my plans to offer a macro emulation/VM layer... |
|
|
|
Progress: Source code filters are now supported. (This was easy as we reuse all of the parsing code from the existing compiler already.) |
|
|
|
Another milestone reached. Methods and inheritance begin to work: |
|
|
|
|
|
import std / [syncio] |
|
|
|
type |
|
RootObj {.inheritable.} = object |
|
|
|
type Obj = object of RootObj |
|
a, b: int |
|
c: string |
|
|
|
method m(o: RootObj) = |
|
echo "RootObj" |
|
|
|
method m(o: Obj) = |
|
echo "Obj" |
|
|
|
proc test(o: RootObj) = |
|
m o |
|
|
|
test(Obj(a: 1, b: 2, c: "3")) |
|
The implementation is based on good old v-tables. The of operator is again implemented by "displays". These operations are all O(1). |
|
|
|
Destructors are "virtual" if they have to be. Virtual calls are optimized to static calls if applicable. |
|
|
|
Progress: Today the first plugin worked! |
|
|
|
As a simple example I wrote |
|
|
|
|
|
template generateEcho(s: string) = echo s |
|
|
|
generateEcho("Hello, world!") |
|
as a compiler plugin: |
|
|
|
|
|
import std / syncio |
|
|
|
template generateEcho(s: string) {.plugin: "deps/mplugin1".} |
|
|
|
generateEcho("Hello, world!") |
|
As there is currently no API for plugins, we have to import parts of the Nimony compiler and the code is a bit ugly: |
|
|
|
|
|
# file: deps/mplugin1.nim |
|
import nimonyplugins |
|
|
|
proc tr(n: Node): Tree = |
|
result = createTree() |
|
let info = n.info |
|
var n = n |
|
if n.stmtKind == StmtsS: inc n |
|
result.withTree StmtsS, info: |
|
result.withTree CallS, info: |
|
result.addIdent "echo" |
|
result.takeTree n |
|
|
|
var inp = loadTree() |
|
saveTree tr(beginRead inp) |
|
But it works! And the results are cached so compile-times shouldn't suffer. |
|
|
|
Progress report: This program works now: |
|
|
|
|
|
import std / syncio |
|
|
|
proc willFail() {.raises.} = |
|
raise Failure |
|
|
|
try: |
|
willFail() |
|
except: |
|
echo "failed" |
|
But for now only raising the new ErrorCode enum is supported. The implementation maps it to tuples+gotos so the overhead is comparable with Nim's goto based exception handling. In fact, the produced code should be slightly better as the thread local storage is avoided and hardware registers can be used for error propagation. |
|
|
|
This way of error handling also composes well with the idea that new can return nil on OOM: If new is in a .raises proc and it returns nil we can map this to OOM automatically and when you're not in a .raises proc the nil checking will enforce you checked for nil. |
|
|
|
This is what good design does: It opens up new solutions. :-) |
|
|
|
Mini progress. The following program parses: |
|
|
|
|
|
import std / [syncio] |
|
|
|
type |
|
Obj = ref object of RootRef |
|
Obj2 = ref object of Obj |
|
|
|
proc testNil() = |
|
var nilRootObj: nil ref RootObj = nil |
|
var other: nil RootRef |
|
|
|
var nn: RootRef not nil |
|
var nn2: ref RootRef not nil |
|
|
|
testNil() |
|
But not-nil checking based on a control flow graph is not implemented yet as I debug our control flow graph. :-) |
|
|
|
However, I expect nil-checking to work in the next couple of days... |
|
|
|
Progress: The construction of the control flow graph has been rewritten to use the standard "split streams" algorithm (that is nowhere documented in the compiler literature afaict). This replaces lots of fragile logic. (Some background here: I thought I had found a better way than the split streams algorithm but it turns out, it was just a terrible hack...) |
|
|
|
Now that the control flow graph is solid, work on the not nil checking can finally continue. |
|
|
|
|
|
Progress report. This program works: |
|
|
|
|
|
import std / syncio |
|
|
|
type |
|
Myref = ref object |
|
x: int |
|
|
|
proc p(): Myref not nil {.raises.} = Myref(x: 5) |
|
|
|
proc hah2 {.raises.} = |
|
var r: nil Myref |
|
r = p() |
|
echo r[].x |
|
|
|
proc other = |
|
var r: nil Myref = nil |
|
if r != nil: |
|
echo r[].x |
|
else: |
|
echo "is nil" |
|
|
|
try: |
|
hah2() |
|
other() |
|
except: |
|
echo "god riddance" |
|
Nimony is now feature complete. In the sense that all important features for a MVP have been implemented. Now the "polish" phase begins. Error messages must improve, bugs must be fixed and a minimal stdlib must be added. |
|
|
|
After we released the MVP the focus will shift to better compatibility with Nim so that we can use its stdlib (and of course compile most of the wild Nim code out there). You will notice this shift when I start talking about Nim 3 instead of Nimony. ;-) |
|
|
|
There are 3 modes: |
|
|
|
unchecked ref/ptr: Just like in Nim. |
|
nil ref: can explicitly be nil and we want derefs to be protected by analysis. |
|
not nil ref: cannot be nil and we want analysis to ensure it. |
|
The default is still "unchecked ref" as we haven't implemented the mode switch. |
|
|
|
> About concepts, would abstract generics still work? |
|
|
|
When you mark your generic as .untyped, yes. |
|
|
|
Part 2: |
|
|
|
The first Nimony progress thread became too long to manage. This thread is the 2nd progress report for Nimony. |
|
|
|
Recently this program compiled (and produced the correct output): |
|
|
|
|
|
import std / [syncio] |
|
|
|
type |
|
BinaryTree[T] = ref object |
|
le, ri: nil BinaryTree[T] |
|
data: T |
|
|
|
proc newNode*[T](data: sink T): BinaryTree[T] = BinaryTree[T](data: data) |
|
|
|
proc append*[Ty: Comparable](root: var nil BinaryTree[Ty], n: BinaryTree[Ty]) = |
|
# insert a node into the tree |
|
if root == nil: |
|
root = n |
|
else: |
|
var it = root |
|
while it != nil: |
|
var c = cmp(n.data, it.data) |
|
if c < 0: |
|
if it.le == nil: |
|
it.le = n |
|
return |
|
it = it.le |
|
else: |
|
if it.ri == nil: |
|
it.ri = n |
|
return |
|
it = it.ri |
|
|
|
proc append*[Ty](root: var BinaryTree[Ty], data: sink Ty) = |
|
append(root, newNode(data)) |
|
|
|
type |
|
Stringable = concept |
|
proc `$`(x: Self): string |
|
|
|
proc toString[T: Stringable](n: nil BinaryTree[T]; result: var string) = |
|
if n == nil: return |
|
result.add $n.data |
|
toString n.le, result |
|
toString n.ri, result |
|
|
|
proc `$`*[T](n: BinaryTree[T]): string = |
|
result = "" |
|
toString n, result |
|
|
|
proc main = |
|
var x = newNode("abc") |
|
x.append "def" |
|
echo $x |
|
|
|
main() |
|
Feel free to continue the discussion about what syntax to use for nil ref T, ref T not nil, unchecked ref T here. |
|
|
|
Progress: Nimony now uses its own build tool called "nifmake" instead of "make". |
|
|
|
Progress: import module {.plugin: "v2".} worked for the first time. I wrote a simple module abc that contained fib: |
|
|
|
|
|
proc fib*(n: int): int = |
|
if n < 2: |
|
return n |
|
else: |
|
return fib(n - 1) + fib(n - 2) |
|
Then I imported this module via the new import plugin system: |
|
|
|
|
|
|
|
import std / syncio |
|
import abc {.plugin: "v2".} |
|
|
|
echo fib(10) |
|
And the result compiled and linked and produced the correct result! |
|
|
|
This used the Nim v2 compiler to precompile `fib` and it was imported successfully into Nimony! |
|
|
|
It's been integrated into the IC pipeline, it only invokes nim nif when your dependency changed. So actually you do benefit. However! When you change your v2 dependency nim nif compiles all the imported modules as it has no IC. |
|
|
|
> Can nimony solve the circular dependency problem? |
|
|
|
The compiler was built with solving this problem in mind but it's not been implemented yet. |
|
|
|
Progress: Closures begin to work: |
|
|
|
|
|
import std / [syncio] |
|
|
|
proc testClosure() = |
|
var x = 40 |
|
proc inner = |
|
echo x |
|
inner() |
|
|
|
testClosure() |
|
|
|
Progress: The compiler optimizes the closure heap allocations away in certain cases. We can make this technique part of the spec as it means capturing var T parameters can be allowed if the heap allocation can be avoided. |
|
|
|
In other words: |
|
|
|
|
|
proc outer(s: var string) = |
|
proc inner = |
|
s.add "abc" |
|
s.add "def" |
|
inner() |
|
becomes possible. |
|
|
|
Progress: The terrible and popular defer statement has been implemented. |
|
|
|
Progress: |
|
packed and union pragmas have been implemented. |
|
case object is beginning to work. |
|
proc types are more stable. |
|
Porting of hard test cases from Nim's extensive test suite has begun. |
|
|
|
The plan keeps changing. I am working on continuation-passing-style as the foundation for async+multi-threading. Then we can develop a standard library that is async-ready from the beginning, skipping multiple development iterations. |
|
|
|
Once we have more of a library, I think it's ready for a first release. I'm still optimistic for autumn this year, but who knows. |
|
|
|
Progress: |
|
The using statement has been implemented. |
|
The "prefer iterators in a for loop context" logic has been implemented. |
|
|
|
Progress: The simplest "passive" proc works now: |
|
|
|
|
|
import std / [syncio] |
|
|
|
proc passiveProc(x: string) {.passive.} = |
|
echo x |
|
|
|
passiveProc("abc") |
|
A .passive proc is one that is turned into its CPS representation. Usually this is called an async proc but for now "passive" is the working title. (It's a better name than "async" anyway.) |
|
|
|
Progress: |
|
astToStr exists now. |
|
The do notation has been implemented. |
|
|
|
Progress: system.compiles has been implemented. |
|
|
|
Progress: This program worked for the first time: |
|
|
|
|
|
import std/[assertions, syncio] |
|
|
|
proc myecho(inp: string) {.passive.} = |
|
echo inp |
|
|
|
proc pa(inp: string) {.passive.} = |
|
for i in 0..<3: |
|
myecho inp & " a" |
|
|
|
pa "abcdef" |
|
However, to make it play nice with try finally and raise we likely need to do the CPS transformation much later in the pipeline than we currently do. This then implies we need to generate the =hooks on our own as we then transform the code after the hook injections have been performed. These phase ordering problems are the truely hard stuff in compiler development. |
|
|
|
The compiler transforms .passive procs into their CPS representation which is the foundation for async. |
|
|
|
Progress: push/pop pragmas have been implemented. |
|
|
|
We are working on these stdlib modules: |
|
|
|
math, strutils, unicode, encodings. |
|
|
|
Once we have these we'll have a first release, version 0.4 or something. Most probably this will still lack the .passive features though. (September 2025) |
|
|
|
Then we need to bootstrap the compiler. (December 2025?!) Once that works, we can call it version 1. During bootstrapping we learn how to port Nim 2 code to Nimony and should document the process. This is not Nim 3 then but you can write code that works with both Nim 2 and Nimony. I wouldn't call that Nim 3 though as for Nim 3 the import v2 feature needs to work well which is full of evil details to get right... |
|
|
|
Progress: |
|
unicode.nim has been ported. |
|
the passL pragma has been implemented. |
|
|
|
Progress: |
|
Terrible bugs have been fixed. |
|
math.nim contains many consts & procs. |
|
|
|
|
|
Progress: |
|
The rewrite rule proc p {.t.}... --> t: proc p... has been implemented. |
|
The dynlib pragma has been implemented. |
|
We now have std/locks. |
|
Bugs have been fixed. |
|
|
|
Progress: |
|
std/encodings has been ported. |
|
std/monotimes has been ported. |
|
|
|
Overloading based on var T is gone as accessors become polymorphic instead: It is tracked if a write to a location like a[i].x.z is allowed. It is allowed if a is mutable. The builtin array indexing has always worked this way so this is a most natural extension. |
|
|
|
# Nimony |
|
proc `[]`(x: Container): var Element # polymorphic accessor |
|
It also means we know in the type system if a container access can resize the container or not as the resize would still require the var-ness for the parameter! |
|
|
|
Progress: There are now beginnings of an API for plugins. |
|
|
|
Progress: We now have Nimony documentation here |
|
|
|
The single page design is somewhat impractical, sorry. Will improve it later. |
|
|
|
Ever since Nimony gained a string implementation. That said, I agree that it's annoying... We should probably stop chasing the slices (which you can get with openarrays already anyway) and instead go for SSO strings that keep the zero terminator. |
|
|
|
Part 3: |
|
|
|
Progress: The beginnings of our stdlib now use Nimony's new ErrorCode based error handling. I'm particularly pleased with how this works, so far. The result still feels like Nim, it's just a better variant of it. |
|
|
|
> What about additional payload in the error? It seems that this option does not allow you to add it. |
|
|
|
That's some library specific getMoreErrorInfo API which can then decide of whether to use threadlocal storage for it or if some object that you have lying around somewhere (typically called a X-"Context") can keep this information. |
|
|
|
> In the same vein, would the standard library be built on top of it? i.e Optionals wherever relevant? |
|
|
|
They simply are not relevant when you have exceptions. (Or more generally speaking, when you have some version of raise.) |
|
|
|
Progress: Nimony now supports threads via its rawthreads module. This porting effort also made me aware of a bug in Nim's core threading support that I'm sure was reported multiple times but only now I actually understand the issue... :-) |
|
|
|
Progress. You nerd-sniped me into implementing a decent compile-time engine via a staticExec like mechanism. So now this program works: |
|
|
|
|
|
import std / [syncio] |
|
|
|
proc myop(a, b: string): string = a & ";" & b |
|
|
|
const |
|
MyConst = myop("Hello", "World") |
|
|
|
echo MyConst |
|
This also means we can add macro support to Nimony... |
|
|
|
Progress: |
|
The backend now does dead-code-elimination on entire programs (Nim 2 does too). It also merges generic instances so that generics become "0 overhead". |
|
The frontend now supports check combined with --usages or --def (find usages, goto definition). |
|
|
|
Part 4: |
|
|
|
Progress. Generic inner procs are now moved to a position where lambda lifting can handle them. |
|
|
|
In other words, this code now works: |
|
|
|
|
|
import std/[syncio] |
|
|
|
proc outer = |
|
var x = 120 |
|
proc inner[T] = echo x |
|
inner[int]() |
|
|
|
outer() |
|
It also does not use the heap as the closure does not escape. The echo implementation also does not allocate as it does not delegate to $ but to write. |
|
|
|
Progress: Bugs are fixed at a very good pace due to the superior compiler architecture. |
|
|
|
Progress. std/dirs is a thing and this program compiles&runs: |
|
|
|
|
|
try: |
|
for k, p in walkDir(path"nimcache"): |
|
echo $k, " ", $p |
|
except: |
|
echo "problem!" |
|
|
|
|