Skip to content

Commit 5f128e6

Browse files
authored
Revise README for clarity and detail
Updated the README.md to clarify the typesystem and improve descriptions of features and examples.
1 parent 2b32cd7 commit 5f128e6

File tree

1 file changed

+58
-46
lines changed

1 file changed

+58
-46
lines changed

README.md

Lines changed: 58 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,30 @@
11
# About
22

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.
64

75
There is a [playground](https://capsadmin.github.io/NattLua/) you can try. It supports hover type information and other diagnostics.
86

9-
Complex type structures, such as array-like tables, map-like tables, metatables, and more are supported:
7+
Here are some examples:
108

119
```lua
10+
local x = 1 -- literal number 1
11+
x = x + 1 -- x is now 2
12+
13+
local x: number = 1 -- wide number 1
14+
x = x + 1 -- x is still number
15+
1216
local list: {[number] = string | nil} = {} -- -1 index is alllowed
13-
local list: {[number] = string} | {} = {} -- same as the above, but expressed differently
17+
local list: {[number] = string} | {} = {} -- same as the above, but expressed another way
1418
local list: {[1..inf] = string | nil} = {} -- only 1..inf index is allowed
1519

1620
local map: {[string] = string | nil} = {} -- any string index is allowed
17-
local map: {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+
local map: {foo = string, bar = string} = {foo = "hello", bar = "world"} -- only foo and bar is allowed as keys, but values can be any string
1822

19-
local a = "fo" -- a is literally "fo", and not string, because we don't specify a contract
23+
local a = "fo" -- a is a string literal "fo", and not the wide type string
2024
local b = string.char(string.byte("o")) -- these are type functions that take in literal and non literal types
2125
local map = {}
2226
map[a..b] = "hello"
27+
2328
-- this print call is a typesystem call, this will be ommitted when transpiling back to LuaJIT
2429
print<|map|> -- >> {foo = "hello"}
2530
```
@@ -53,11 +58,11 @@ local new_vector = Vector(1,2,3) + Vector(100,100,100) -- OK
5358

5459
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).
5560

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.
5762

5863
# Code analysis and typesystem
5964

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" conditions and 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.
6166

6267
```lua
6368
local cfg = [[
@@ -88,56 +93,58 @@ print<|tbl|>
8893

8994
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.
9095

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.
9297

9398
When the analyzer detects an error, it will try to recover from the error and continue. For example:
9499

95100
```lua
96-
local obj: nil | (function(): number)
97-
local x = obj()
98-
local y = x + 1
101+
local func = nil
102+
if math.random() > 0.5 then func = function() return 1336 end end
103+
-- func is now the type: nil | function=()>(1336)
104+
local x = func() -- error calling a nil value, but the value is 1336
105+
local y = x + 1 -- y is 1337
99106
```
100107

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.
102109

103110
# Current status and goals
104111

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)).
106113

107114
At the moment I focus strongly on type inference correctness, adding tests and keeping the codebase maintainable.
108115

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.
110117

111118
# Types
112119

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.
114121

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:
116123

117124
```lua
118125
local type Boolean = true | false
119-
local type Number = -inf .. inf | nan
126+
local type Number = -inf..inf | nan
120127
local type String = $".*"
121-
local type Any = Number | Boolean | String | nil
128+
local type AnyValue = Number | Boolean | String | nil
122129

123-
-- nil cannot be a key in tables
124-
local type Table = { [exclude<|Any, nil|> | self] = Any | self }
130+
-- nil and nan cannot be used as a key
131+
-- self means the current table type, useful for recursive type declarations
132+
local type Table = { [AnyValue ~ (nan | nil) | self] = AnyValue | self }
125133

126-
-- extend the Any type to also include Table
127-
type Any = Any | Table
134+
local type AnyValueWithTable = AnyValue | Table
128135

129136
-- CurrentType is a type function that lets us get the reference to the current type we're constructing
130-
local type Function = function=(...Any | CurrentType<|"function"|>)>(...Any | CurrentType<|"function"|>)
137+
local type Function = function=(...AnyValueWithTable | CurrentType<|"function"|>)>(...AnyValueWithTable | CurrentType<|"function"|>)
131138

132-
-- extend the Any type to also include Function
133-
type Any = Any | Function
139+
-- declare the global type
140+
type Any = AnyValueWithTable | Function
134141
```
135142

136143
So here all the PascalCase types should have semantically the same meaning as their lowercase counter parts.
137144

138145
# Numbers
139146

140-
From narrow to wide
147+
From literal > narrow > wide
141148

142149
```lua
143150
type N = 1
@@ -175,11 +182,11 @@ local qux: N = 0/0
175182
^^^: nan is not a subset of -inf .. inf
176183
```
177184

178-
The logical progression is to define N as `-inf .. inf | nan` but that has semantically the same meaning as `number`
185+
The logical progression would be defining N as `-inf .. inf | nan` but that has semantically the same meaning as `number`
179186

180187
# Strings
181188

182-
Strings can be defined as lua string patterns to constrain them:
189+
Strings can be defined more narrowly as lua string patterns:
183190

184191
```lua
185192
local type MyString = $"FOO_.-"
@@ -189,7 +196,7 @@ local b: MyString = "lol"
189196
^^^^^ : the pattern failed to match
190197
```
191198

192-
A narrow value:
199+
A literal value:
193200

194201
```lua
195202
type foo = "foo"
@@ -229,11 +236,14 @@ local type MyTable = {
229236
[any] = any
230237
}
231238
}
239+
240+
-- extend the type
241+
type MyTable.bar = number
232242
```
233243

234244
# Unions
235245

236-
A Union is a type separated by `|` I feel these tend to show up in uncertain conditions.
246+
A Union is a type separated by `|` These tend to show up in uncertain conditions.
237247

238248
For example this case:
239249

@@ -266,6 +276,7 @@ if true then
266276
-- x is 1 here
267277
end
268278
-- x is still 1 here because the mutation = 1 occured in a certain branch
279+
-- we would also get a warning saying the branch is always truthy
269280
```
270281

271282
This happens because `true` is true as opposed to `true | false` and so there's no uncertainty in executing the if block.
@@ -335,7 +346,7 @@ local list: Array<|number, 3|> = {1, 2, 3, 4}
335346
^^^^^^^^^^^^: 4 is not a subset of 1..3
336347
```
337348

338-
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.
339350

340351
In Typescript it would be something like
341352

@@ -363,15 +374,14 @@ names[-1] = "faz"
363374
364375
## ffi.cdef parse errors to type errors
365376
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:
367379
368380
```lua
369381
analyzer function ffi.cdef(c_declaration: string)
370-
-- this requires using analyzer functions
371-
372382
if c_declaration:IsLiteral() then
373383
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
375385
end
376386
end
377387

@@ -430,7 +440,7 @@ local func = build_summary_function({
430440
-> | myfunc:3:14 : expected assignment or call expression gotsymbol❳ (❲!❳)
431441
```
432442
433-
This works because there is no uncertainty about the code generated passed to the load function. If we did `body = "sum = sum + 1" .. (unknown_global as string)`, 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" as string`, 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.
434444
435445
## anagram proof
436446
@@ -449,9 +459,9 @@ assert(anagram == "SLEEP")
449459
print<|anagram|> -- >> "SLEEP"
450460
```
451461

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".
453463

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.
455465

456466
If we did assert<|anagram == "SLEEP"|> (a type call) it would error, because the typesystem operates more literally.
457467

@@ -465,7 +475,7 @@ As a learning experience I wrote the lexer and parser trying not to look at exis
465475
- Both single-line C comments (from GLua) and the Lua 5.4 division operator can be used in the same source file.
466476
- Transpiles bitwise operators, integer division, \_ENV, etc down to valid LuaJIT code.
467477
- 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.
469479

470480
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.
471481

@@ -483,9 +493,11 @@ To install run `luajit nattlua.lua install`
483493

484494
If you install you'd get the binary `nattlua` which behaves the same as `luajit nattlua.lua ...`
485495

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.
487499

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.
489501

490502
Some debug language features are:
491503

@@ -506,10 +518,10 @@ local x = 1337
506518

507519
# Similar projects
508520

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.
510522

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.
512524

513-
[sumneko lua](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+
[sumneko lua](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.
514526

515527
[EmmyLua](https://github.com/EmmyLua/VSCode-EmmyLua) Similar to sumneko lua.

0 commit comments

Comments
 (0)