You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+58-46Lines changed: 58 additions & 46 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,25 +1,30 @@
1
1
# About
2
2
3
-
NattLua is a superset of LuaJIT that introduces a typesystem. The typesystem aims to provide, by default, precise code analysis, while allowing you to optionally constrain variables with types.
4
-
5
-
The typesystem itself follows the same philosophy and feel as Lua; built on simple primitives that can be extended with type functions.
3
+
NattLua is variant of LuaJIT with optional types. The main goal is to provide a complete picture of how a program might run or fail in all possible paths.
6
4
7
5
There is a [playground](https://capsadmin.github.io/NattLua/) you can try. It supports hover type information and other diagnostics.
8
6
9
-
Complex type structures, such as array-like tables, map-like tables, metatables, and more are supported:
7
+
Here are some examples:
10
8
11
9
```lua
10
+
localx=1-- literal number 1
11
+
x=x+1-- x is now 2
12
+
13
+
localx: number=1-- wide number 1
14
+
x=x+1-- x is still number
15
+
12
16
locallist: {[number] =string | nil} = {} -- -1 index is alllowed
13
-
locallist: {[number] =string} | {} = {} -- same as the above, but expressed differently
17
+
locallist: {[number] =string} | {} = {} -- same as the above, but expressed another way
14
18
locallist: {[1..inf] =string | nil} = {} -- only 1..inf index is allowed
15
19
16
20
localmap: {[string] =string | nil} = {} -- any string index is allowed
17
-
localmap: {foo=string, bar=string} = {foo="hello", bar="world"} -- only foo and bar is allowed as keys, but value can be of any string type
21
+
localmap: {foo=string, bar=string} = {foo="hello", bar="world"} -- only foo and bar is allowed as keys, but values can be any string
18
22
19
-
locala="fo" -- a is literally "fo", and not string, because we don't specify a contract
23
+
locala="fo" -- a is a string literal "fo", and not the wide type string
20
24
localb=string.char(string.byte("o")) -- these are type functions that take in literal and non literal types
21
25
localmap= {}
22
26
map[a..b] ="hello"
27
+
23
28
-- this print call is a typesystem call, this will be ommitted when transpiling back to LuaJIT
24
29
print<|map|>-- >> {foo = "hello"}
25
30
```
@@ -53,11 +58,11 @@ local new_vector = Vector(1,2,3) + Vector(100,100,100) -- OK
53
58
54
59
It aims to be compatible with LuaJIT as a frst class citizen, but also 5.1, 5.2, 5.3, 5.4 and Garry's Mod Lua (a variant of Lua 5.1).
55
60
56
-
The `build_output.lua` file is a bundle of this project that can be required in your project. It also should work in garry's mod.
61
+
The `build_output.lua` file is a bundle of this project that can be require()'d in your project. It also should work in garry's mod, though type definitions there are lacking.
57
62
58
63
# Code analysis and typesystem
59
64
60
-
The analyzer works by evaluating the syntax tree. It runs similar to how Lua runs, but on a more general level, and can take take multiple branches if its not sure about if conditions, loops and so on. If everything is known about a program and you didn't add any types, you may get the actual output at type-check time.
65
+
The analyzer works by evaluating the syntax tree. It runs similar to how Lua runs, but on a more general level, and can take take multiple paths when "if" conditionsand loops are uncertain. If everything is known about a program and you didn't add any types, you may get the actual output during analysis.
61
66
62
67
```lua
63
68
localcfg=[[
@@ -88,56 +93,58 @@ print<|tbl|>
88
93
89
94
The `ref` keyword means that the `cfg` variable should be passed in as a type reference. This is similar to how type arguments in a generic function is passed to the function itself. If we removed the `ref` keyword, the output of the function is be inferred to be `{ string = string }` because `str` would become a non literal string.
90
95
91
-
We can also add a return type to `parse` by writing `parse(str: ref string): {[string] = string}`, but if you don't it will be inferred.
96
+
We can also add a return type to `parse` by writing `function parse(str: ref string): {[string] = string}` to help constrain the ouput, but if you don't it will be inferred. The the `ref` keyword is also supported on the return type so that you may get the literal output, serving as a typical generic function.
92
97
93
98
When the analyzer detects an error, it will try to recover from the error and continue. For example:
localx=func() -- error calling a nil value, but the value is 1336
105
+
localy=x+1-- y is 1337
99
106
```
100
107
101
-
This code will report an error about potentially calling a nil value. Internally the analyzer would duplicate the current state, remove nil from the union `nil | (function(): number)` and continue.
108
+
When the analyser reports an error in this case, it would would branch out, creating a scope where nil is removed from the union `nil | (function(): number)` after the call and continue.
102
109
103
110
# Current status and goals
104
111
105
-
My long term goal is to develop a capable language to use for my other projects (such as [goluwa](https://github.com/CapsAdmin/goluwa)).
112
+
My long term goal is to develop a language to use for my other projects (such as [goluwa](https://github.com/CapsAdmin/goluwa)).
106
113
107
114
At the moment I focus strongly on type inference correctness, adding tests and keeping the codebase maintainable.
108
115
109
-
I'm also in the middle of bootstrapping the project with comment types. So far the lexer part of the project and some other parts are typed and is part of the test suite.
116
+
I'm also working on bootstrapping the project with comment types. So far the lexer part of the project and some other parts are typed and is part of the test suite.
110
117
111
118
# Types
112
119
113
-
Fundamentally the typesystem consists of number, string, table, function, symbol, union, tuple and any. Tuples and unions exist only in the typesystem. Symbols are things like true, false, nil, etc.
120
+
Fundamentally the typesystem consists of number, string, table, function, symbol, range, union, tuple and any. Tuples and unions and ranges exist only in the typesystem. Symbols are things like true, false, nil, etc.
114
121
115
-
These types can also be literals, so as a showcase example we can describe the fundamental types like this:
122
+
Most types can go from wide, narrow and literal, so as a showcase example we can describe the fundamental types like this:
In type functions, the type is by default passed by reference. So `T: any` does not meant that T will be any. It just means that T is allowed to be anything.
349
+
In type functions, the type is by default passed by reference. So `T: any` does not mean that T will be any in the function body. It just means that T is allowed to be anything.
339
350
340
351
In Typescript it would be something like
341
352
@@ -363,15 +374,14 @@ names[-1] = "faz"
363
374
364
375
## ffi.cdef parse errors to type errors
365
376
366
-
ffi functions including cdef are already typed, but to showcase how we might throw parsing errors to the type system we can do the following:
377
+
In NattLua, ffi type definitions are mostly complete. There is a c declaration parser and type definitions for ctype and cdata,
378
+
but to showcase analyzer functions, here's an example of a minimal but useful ffi.def definition:
367
379
368
380
```lua
369
381
analyzerfunction ffi.cdef(c_declaration:string)
370
-
-- this requires using analyzer functions
371
-
372
382
if c_declaration:IsLiteral() then
373
383
local ffi = require("ffi")
374
-
ffi.cdef(c_declaration:GetData()) -- if this function throws it's propagated up to the compiler as an error
384
+
ffi.cdef(c_declaration:GetData()) -- if cdef throws an error, it's propagated up to the compiler as an error
375
385
end
376
386
end
377
387
@@ -430,7 +440,7 @@ local func = build_summary_function({
This works because there is no uncertainty about the code generated passed to the load function. If we did`body="sum = sum + 1".. (unknown_globalasstring)`, that would make the table itself become uncertain so that table.concat would return `string` and not the actual results of the concatenation.
443
+
This works because there is no uncertainty about the code generated passed to the load function. If we wrote`body="sum = sum + 1"asstring`, it would widen the body value in the table so, which in turn would cause table.concat return `string` and not the actual results of the concatenation.
434
444
435
445
## anagram proof
436
446
@@ -449,9 +459,9 @@ assert(anagram == "SLEEP")
449
459
print<|anagram|> -- >> "SLEEP"
450
460
```
451
461
452
-
This is true because `anagram` becomes a union of all possible letter combinations which contains the string "SLEEP".
462
+
This is true because `anagram` becomes a union of all possible letter combinations which also contains the string "SLEEP".
453
463
454
-
It's also false as it contains all the other combinations, but since we use assert to check the result at runtime, it will silently "error" and mutate anagram to become "SLEEP" after the assertion.
464
+
However, it's also false as it contains all the other combinations, but since we use assert to check the result at runtime, it will silently "error" and mutate the anagram upvalue to become "SLEEP" after the assertion.
455
465
456
466
If we did assert<|anagram == "SLEEP"|> (a type call) it would error, because the typesystem operates more literally.
457
467
@@ -465,7 +475,7 @@ As a learning experience I wrote the lexer and parser trying not to look at exis
465
475
- Both single-line C comments (from GLua) and the Lua 5.4 division operator can be used in the same source file.
466
476
- Transpiles bitwise operators, integer division, \_ENV, etc down to valid LuaJIT code.
467
477
- Supports inline importing via require, loadfile, and dofile.
468
-
- Supports teal syntax, but does not currently support its scoping rules.
478
+
- Supports teal syntax, however the analyser does not currently support its scoping rules.
469
479
470
480
I have not fully decided the syntax for the language and runtime semantics for lua 5.3/4 features. But I feel this is more of a detail that can easily be changed later.
471
481
@@ -483,9 +493,11 @@ To install run `luajit nattlua.lua install`
483
493
484
494
If you install you'd get the binary `nattlua` which behaves the same as `luajit nattlua.lua ...`
485
495
486
-
I've setup vscode to run the task `onsave` when a file is saved with the plugin `gruntfuggly.triggertaskonsave`. This runs `on_editor_save.lua` which has some logic to choose which files to run when modifying project.
496
+
I've setup vscode to run the task `onsave` when a file is saved with the plugin `pucelle.run-on-save`. This runs `on_editor_save.lua` which has some logic to choose which files to run when modifying project.
497
+
498
+
There is also some hotreload comment syntax which can let you specify which tests to run when saving a file, along with hotreload.lua scripts that specify how any file in the directory and sub directories will be ran when saved.
487
499
488
-
I also locally have a file called `test_focus.nlua` in root which will override the test suite when the file is not empty. This makes it easier to debug specific tests and code.
500
+
I also locally have a file called `test_focus.nlua` in root which will override hotreload logic when the file is not empty. This makes it easier to debug specific tests and code.
489
501
490
502
Some debug language features are:
491
503
@@ -506,10 +518,10 @@ local x = 1337
506
518
507
519
# Similarprojects
508
520
509
-
[Teal](https://github.com/teal-language/tl) is a language similar to this which has a more pragmatic approach. I'm thinking a nice goal is that I can contribute what I've learned here, be it through tests or other things.
521
+
[Teal](https://github.com/teal-language/tl) has a more pragmatic and stricter approach when it comes to type inference.
510
522
511
-
[Luau](https://github.com/Roblox/luau) is another project similar to this, but I have not looked so much into it yet.
523
+
[Luau](https://github.com/Roblox/luau) Similar to teal, but closer to typescript in syntax for types.
512
524
513
-
[sumnekolua](https://github.com/sumneko/lua-language-server) a language server for lua that supports analyzing lua code. It a typesystem that can be controlled by using comments.
525
+
[sumnekolua](https://github.com/sumneko/lua-language-server) a language server for lua that supports analyzing lua code. It has a typesystem that can be controlled by using comments.
514
526
515
527
[EmmyLua](https://github.com/EmmyLua/VSCode-EmmyLua) Similar to sumneko lua.
0 commit comments