Questions
- How is 'Nelua' pronounced?
- What limitations come with disabling the GC?
- What C standard does Nelua adhere to? Can I affect the standards-compliance of the codegen?
- Does Nelua support tail-call optimization? Should I write like it's a Scheme?
- How can a method accept
selfas T instead of *T? - Does Nelua have named arguments?
- How can you slice an array, like
arr[start : end]
How is 'Nelua' pronounced?
Official answer: like translate.google.com says.
GC limitations
There aren't any. Nelua isn't like D where you have a distinct sublanguage in -betterC, or lots of restrictions with @nogc. Only five libraries check pragmas.nogc, and three of them are allocator libraries:
- allocators.default chooses between the GC and malloc/free:
## if not pragmas.nogc then -- GC enabled require 'allocators.gc' ## context.rootscope.symbols.default_allocator = gc_allocator ## context.rootscope.symbols.DefaultAllocator = GCAllocator ## else -- GC disabled require 'allocators.general' ## context.rootscope.symbols.default_allocator = general_allocator ## context.rootscope.symbols.DefaultAllocator = GeneralAllocator ## end
- C.threads rejects the GC
- coroutine has some GC interaction that's only done if there's a GC to interact with
There shouldn't any leaks when using the stdlib. Although without a GC you're responsible for cleaning up resources returned from stdlib functions.
NB. this doesn't mean that Nelua trivializes memory management. I've used -S/--sanitize and valgrind more with Nelua than ever before. It's a different experience and not absolutely a worse one.
What C standard does Nelua adhere to?
"Nelua code generator does not target C standards, it targets GCC and Clang" - of a C23 niltype definition.
Which links to this detailed answer:
- "Nelua does not depend on C11. It does emit some C11 specific code like _Noreturn, _Static_assert, but they are all used through ifdefs so the code can also compile on old compilers that don't support C11."
- you can avoid a C library it it has a dependency you don't like
- (there is also the 'threadlocal' annotation that expressly depends on C11)
- "But I can't say Nelua depends only on C99, and neither C11, because the C code generator depends on some C extensions are available in most C compilers,"
- "I would say Nelua targets Clang/GCC/TCC,"
TCO support?
Nelua doesn't treat recursion specially, and emits C that's very similar to what was written. So in short: no, you shouldn't write it like it's a Scheme.
But consider:
local function recur(n: number): void <cexport> print(n) recur(n+1) end recur(0)
This has an explicit return type because Nelua requires that for recursive functions. It has <export> only to make it easier to find the code in the resulting binary.
How does this run normally?
$ nelua recur.nelua ... 261684.0 261685.0 261686.0 Segmentation fault
It runs out of stack and crashes.
With -S/--sanitize this is shown in detail:
$ nelua -S recur.nelua
AddressSanitizer:DEADLYSIGNAL
=================================================================
==8477==ERROR: AddressSanitizer: stack-overflow on address 0x7fff89b43ff8 (pc 0x7f9aebe6b9c8 bp 0x7fff89b442d0 sp 0x7fff89b43fd0 T0)
#0 0x7f9aebe6b9c8 in __mpn_divrem stdlib/divrem.c:47
#1 0x7f9aebe71754 in hack_digit stdio-common/printf_fp.c:187
#2 0x7f9aebe72823 in __GI___printf_fp_l stdio-common/printf_fp.c:942
#3 0x7f9aebe7cf5c in __printf_fp_spec stdio-common/vfprintf-internal.c:354
#4 0x7f9aebe7cf5c in __vfprintf_internal stdio-common/vfprintf-internal.c:1061
#5 0x7f9aebe9d667 in __vsnprintf_internal libio/vsnprintf.c:114
#6 0x7f9aec87433d in __interceptor_vsnprintf ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:1665
#7 0x7f9aec8745be in __interceptor_snprintf ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:1736
#8 0x55b4017e8325 in nelua_print_1 .../.cache/nelua/recur.c:98
#9 0x55b4017e8a74 in recur_recur .../.cache/nelua/recur.c:116
#10 0x55b4017e8a94 in recur_recur .../.cache/nelua/recur.c:117
...
#247 0x55b4017e8a94 in recur_recur .../.cache/nelua/recur.c:117
#248 0x55b4017e8a94 in recur_recur .../.cache/nelua/recur.c:117
SUMMARY: AddressSanitizer: stack-overflow stdlib/divrem.c:47 in __mpn_divrem
==8477==ABORTING
How does a release build perform?
$ nelua -r recur.nelua ...
It never crashes, because the C compiler optimized the tail-recursive function into an iterative one. Note: there is no difference in the emitted C code. The only difference is in the C compiler flags: -O2 -DNDEBUG are passed instead of the default which is just -g.
How does that function disassemble?
$ objdump -dwr ~/.cache/nelua/recur
...
0000000000001180 <recur_recur>:
1180: 41 56 push %r14
1182: 66 49 0f 7e c6 movq %xmm0,%r14
1187: 55 push %rbp
1188: 48 8d 2d 75 0e 00 00 lea 0xe75(%rip),%rbp # 2004 <_IO_stdin_used+0x4>
118f: 53 push %rbx
1190: 48 83 ec 30 sub $0x30,%rsp
1194: 48 89 e3 mov %rsp,%rbx
1197: 66 0f 1f 84 00 00 00 00 00 nopw 0x0(%rax,%rax,1)
11a0: 48 89 ea mov %rbp,%rdx
11a3: 48 89 df mov %rbx,%rdi
11a6: 66 49 0f 6e c6 movq %r14,%xmm0
11ab: be 2f 00 00 00 mov $0x2f,%esi
11b0: b8 01 00 00 00 mov $0x1,%eax
11b5: c6 44 24 2f 00 movb $0x0,0x2f(%rsp)
11ba: e8 71 fe ff ff call 1030 <snprintf@plt>
11bf: 48 89 da mov %rbx,%rdx
11c2: 4c 63 c0 movslq %eax,%r8
11c5: 4a 8d 3c 03 lea (%rbx,%r8,1),%rdi
11c9: eb 1c jmp 11e7 <recur_recur+0x67>
11cb: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
11d0: 8d 71 d0 lea -0x30(%rcx),%esi
11d3: 40 80 fe 09 cmp $0x9,%sil
11d7: 76 05 jbe 11de <recur_recur+0x5e>
11d9: 80 f9 2d cmp $0x2d,%cl
11dc: 75 24 jne 1202 <recur_recur+0x82>
11de: 48 83 c2 01 add $0x1,%rdx
11e2: 48 39 fa cmp %rdi,%rdx
11e5: 74 07 je 11ee <recur_recur+0x6e>
11e7: 0f b6 0a movzbl (%rdx),%ecx
11ea: 84 c9 test %cl,%cl
11ec: 75 e2 jne 11d0 <recur_recur+0x50>
11ee: 8d 50 02 lea 0x2(%rax),%edx
11f1: 83 c0 01 add $0x1,%eax
11f4: 42 c6 04 04 2e movb $0x2e,(%rsp,%r8,1)
11f9: 48 98 cltq
11fb: 4c 63 c2 movslq %edx,%r8
11fe: c6 04 04 30 movb $0x30,(%rsp,%rax,1)
1202: 48 8b 0d 27 2e 00 00 mov 0x2e27(%rip),%rcx # 4030 <stdout@GLIBC_2.2.5>
1209: 4c 89 c2 mov %r8,%rdx
120c: be 01 00 00 00 mov $0x1,%esi
1211: 48 89 df mov %rbx,%rdi
1214: e8 47 fe ff ff call 1060 <fwrite@plt>
1219: 48 8b 35 10 2e 00 00 mov 0x2e10(%rip),%rsi # 4030 <stdout@GLIBC_2.2.5>
1220: bf 0a 00 00 00 mov $0xa,%edi
1225: e8 16 fe ff ff call 1040 <fputc@plt>
122a: 48 8b 3d ff 2d 00 00 mov 0x2dff(%rip),%rdi # 4030 <stdout@GLIBC_2.2.5>
1231: e8 1a fe ff ff call 1050 <fflush@plt>
1236: f2 0f 10 0d d2 0d 00 00 movsd 0xdd2(%rip),%xmm1 # 2010 <_IO_stdin_used+0x10>
123e: 66 49 0f 6e d6 movq %r14,%xmm2
1243: f2 0f 58 ca addsd %xmm2,%xmm1
1247: 66 49 0f 7e ce movq %xmm1,%r14
124c: e9 4f ff ff ff jmp 11a0 <recur_recur+0x20>
You can see quite a lot here, but there's some I/O and then on the very last line of the function a jmp to 11a0, near the top of the function.
How can a method accept self as T instead of *T?
The two foo:bar syntaxes are strictly unrelated.
-- these are equivalent: function T.foo(self: *T) ... end function T:foo() ... end -- these are equivalent: obj.foo(&obj) obj:foo()
So you can use the second calling syntax even if you didn't use it when defining the function:
local Note = @enum{C, D, E, F, G, A, B}
function Note.show(self: Note)
## for _, field in ipairs(Note.value.fields) do
if wf == #|field.name|# then return #[field.name]# end
## end
assert(false, 'invalid Note')
return ''
}
check(Note.show(Note.C) == 'C') -- this works
check(Note.C:show() == 'C') -- and so does thisDoes Nelua have named arguments?
It doesn't, but you can have functions that accept records instead of arguments, which is still pretty efficient, still type-checked, and has minimal extra syntax:
require 'string'
local function greet(args: record{name: string, traditional: boolean})
local name = args.name == '' and 'world' or args.name
local s: string
defer s:destroy() end
if args.traditional then
s = 'hello ' .. name
else
s = string.format('Hello, %s!', name)
end
print(s)
end
greet{'Bob'} -- Hello, Bob!
greet { traditional=true } -- hello world
greet{'world', true} -- hello worldNote the lack of a @ before the record{} definition.
How can you slice an array, like arr[start : end]
-- option 1. construct a span
require 'span'
local a1: []integer = {1, 2, 3, 4, 5}
local a2: span(integer) = {data=&a1[1], size=3}
a2[0] = 0
a2[2] = 0
for i, v in ipairs(a1) do
print(i, v)
end
-- option 2. span:sub()
require 'span'
local a1: []integer = {1, 2, 3, 4, 5}
local a2: span(integer) = a1
a2 = a2:sub(1, 4)
a2[0] = 0
a2[2] = 0
for i, v in ipairs(a1) do
print(i, v)
end
-- option 3. terse span:sub()
require 'span'
local a1: []integer = {1, 2, 3, 4, 5}
local a2 = (@span(integer))(a1):sub(1, 4)
a2[0] = 0
a2[2] = 0
for i, v in ipairs(a1) do
print(i, v)
end