Compare commits
13 Commits
main
...
declared_t
Author | SHA1 | Date | |
---|---|---|---|
53f5178f71 | |||
c9198cb480 | |||
4299f6e243 | |||
486f2153e8 | |||
81316e0d97 | |||
e2ddd23b55 | |||
cb6a5f264c | |||
00fd2b9e67 | |||
316eff8b4f | |||
355ad95321 | |||
f0b1a0f227 | |||
7b735ab6fc | |||
0b8074154e |
@ -18,7 +18,7 @@ you want to have an empty array, you must specify what type goes inside the arra
|
||||
like this:
|
||||
|
||||
```tomo
|
||||
empty := [:Int]
|
||||
empty : [Int] = []
|
||||
```
|
||||
|
||||
For type annotations, an array that holds items with type `T` is written as `[T]`.
|
||||
@ -246,13 +246,13 @@ variable or dereference a heap pointer, it may trigger copy-on-write behavior.
|
||||
- [`func insert(arr: @[T], item: T, at: Int = 0 -> Void)`](#insert)
|
||||
- [`func insert_all(arr: @[T], items: [T], at: Int = 0 -> Void)`](#insert_all)
|
||||
- [`func pop(arr: &[T], index: Int = -1 -> T?)`](#pop)
|
||||
- [`func random(arr: [T], random: func(min,max:Int64->Int64)? = none:func(min,max:Int64->Int64) -> T)`](#random)
|
||||
- [`func random(arr: [T], random: func(min,max:Int64->Int64)? = none -> T)`](#random)
|
||||
- [`func remove_at(arr: @[T], at: Int = -1, count: Int = 1 -> Void)`](#remove_at)
|
||||
- [`func remove_item(arr: @[T], item: T, max_count: Int = -1 -> Void)`](#remove_item)
|
||||
- [`func reversed(arr: [T] -> [T])`](#reversed)
|
||||
- [`func sample(arr: [T], count: Int, weights: [Num]? = ![Num], random: func(->Num)? = none:func(->Num) -> [T])`](#sample)
|
||||
- [`func shuffle(arr: @[T], random: func(min,max:Int64->Int64)? = none:func(min,max:Int64->Int64) -> Void)`](#shuffle)
|
||||
- [`func shuffled(arr: [T], random: func(min,max:Int64->Int64)? = none:func(min,max:Int64->Int64) -> [T])`](#shuffled)
|
||||
- [`func sample(arr: [T], count: Int, weights: [Num]? = ![Num], random: func(->Num)? = none -> [T])`](#sample)
|
||||
- [`func shuffle(arr: @[T], random: func(min,max:Int64->Int64)? = none -> Void)`](#shuffle)
|
||||
- [`func shuffled(arr: [T], random: func(min,max:Int64->Int64)? = none -> [T])`](#shuffled)
|
||||
- [`func slice(arr: [T], from: Int, to: Int -> [T])`](#slice)
|
||||
- [`func sort(arr: @[T], by=T.compare -> Void)`](#sort)
|
||||
- [`sorted(arr: [T], by=T.compare -> [T])`](#sorted)
|
||||
@ -607,7 +607,7 @@ otherwise the item at the given index.
|
||||
Selects a random element from the array.
|
||||
|
||||
```tomo
|
||||
func random(arr: [T], random: func(min,max:Int64->Int64)? = none:func(min,max:Int64->Int64) -> T)
|
||||
func random(arr: [T], random: func(min,max:Int64->Int64)? = none -> T)
|
||||
```
|
||||
|
||||
- `arr`: The array from which to select a random element.
|
||||
@ -707,7 +707,7 @@ Selects a sample of elements from the array, optionally with weighted
|
||||
probabilities.
|
||||
|
||||
```tomo
|
||||
func sample(arr: [T], count: Int, weights: [Num]? = ![Num], random: func(->Num)? = none:func(->Num) -> [T])
|
||||
func sample(arr: [T], count: Int, weights: [Num]? = ![Num], random: func(->Num)? = none -> [T])
|
||||
```
|
||||
|
||||
- `arr`: The array to sample from.
|
||||
@ -744,7 +744,7 @@ A list of sampled elements from the array.
|
||||
Shuffles the elements of the array in place.
|
||||
|
||||
```tomo
|
||||
func shuffle(arr: @[T], random: func(min,max:Int64->Int64)? = none:func(min,max:Int64->Int64) -> Void)
|
||||
func shuffle(arr: @[T], random: func(min,max:Int64->Int64)? = none -> Void)
|
||||
```
|
||||
|
||||
- `arr`: The mutable reference to the array to be shuffled.
|
||||
@ -766,7 +766,7 @@ Nothing.
|
||||
Creates a new array with elements shuffled.
|
||||
|
||||
```tomo
|
||||
func shuffled(arr: [T], random: func(min,max:Int64->Int64)? = none:func(min,max:Int64->Int64) -> [T])
|
||||
func shuffled(arr: [T], random: func(min,max:Int64->Int64)? = none -> [T])
|
||||
```
|
||||
|
||||
- `arr`: The array to be shuffled.
|
||||
|
@ -82,7 +82,7 @@ func _add(x, y:Int -> Int):
|
||||
return x + y
|
||||
|
||||
struct add_args(x,y:Int)
|
||||
add_cache := @{:add_args,Int}
|
||||
add_cache : @{add_args=Int} = @{}
|
||||
|
||||
func add(x, y:Int -> Int):
|
||||
args := add_args(x, y)
|
||||
|
@ -135,7 +135,7 @@ can be called either on the type itself: `Int.sqrt(x)` or as a method call:
|
||||
- [`func parse(text: Text -> Int?)`](#parse)
|
||||
- [`func prev_prime(x: Int -> Int)`](#prev_prime)
|
||||
- [`func sqrt(x: Int -> Int)`](#sqrt)
|
||||
- [`func to(first: Int, last: Int, step : Int? = none:Int -> func(->Int?))`](#to)
|
||||
- [`func to(first: Int, last: Int, step : Int? = none -> func(->Int?))`](#to)
|
||||
|
||||
### `abs`
|
||||
Calculates the absolute value of an integer.
|
||||
@ -361,7 +361,7 @@ An iterator function that counts onward from the starting integer.
|
||||
|
||||
**Example:**
|
||||
```tomo
|
||||
nums := &[:Int]
|
||||
nums : &[Int] = &[]
|
||||
for i in 5:onward():
|
||||
nums:insert(i)
|
||||
stop if i == 10
|
||||
@ -458,7 +458,7 @@ Returns an iterator function that iterates over the range of numbers specified.
|
||||
Iteration is assumed to be nonempty and
|
||||
|
||||
```tomo
|
||||
func to(first: Int, last: Int, step : Int? = none:Int -> func(->Int?))
|
||||
func to(first: Int, last: Int, step : Int? = none -> func(->Int?))
|
||||
```
|
||||
|
||||
- `first`: The starting value of the range.
|
||||
|
@ -75,7 +75,7 @@ first option is to not account for it, in which case you'll get a runtime error
|
||||
if you use a reducer on something that has no values:
|
||||
|
||||
```tomo
|
||||
>> nums := [:Int]
|
||||
>> nums : [Int] = []
|
||||
>> (+: nums)!
|
||||
|
||||
Error: this collection was empty!
|
||||
|
@ -40,7 +40,7 @@ example, if you wanted to declare a variable that could be either an integer
|
||||
value or `none` and initialize it as none, you would write it as:
|
||||
|
||||
```tomo
|
||||
x := none:Int
|
||||
x : Int = none
|
||||
```
|
||||
|
||||
Similarly, if you wanted to declare a variable that could be an array of texts
|
||||
@ -57,7 +57,7 @@ keep open the possibility of assigning `none` later, you can use the postfix
|
||||
```tomo
|
||||
x := 5?
|
||||
# Later on, assign none:
|
||||
x = !Int
|
||||
x = none
|
||||
```
|
||||
|
||||
## Type Inference
|
||||
@ -86,7 +86,7 @@ Non-none values can also be automatically promoted to optional values without
|
||||
the need for an explicit `?` operator in the cases listed above:
|
||||
|
||||
```tomo
|
||||
x := !Int
|
||||
x : Int? = none
|
||||
x = 5
|
||||
|
||||
func doop(arg:Int?)->Text?:
|
||||
@ -109,7 +109,7 @@ maybe_x := 5?
|
||||
>> maybe_x or fail("No value!")
|
||||
= 5 : Int
|
||||
|
||||
maybe_x = !Int
|
||||
maybe_x = none
|
||||
>> maybe_x or -1
|
||||
= -1 : Int
|
||||
>> maybe_x or fail("No value!")
|
||||
|
@ -64,11 +64,11 @@ intended. Paths can be created from text with slashes using
|
||||
- [`func owner(path: Path, follow_symlinks=yes -> Text?)`](#owner)
|
||||
- [`func parent(path: Path -> Path)`](#parent)
|
||||
- [`func read(path: Path -> Text?)`](#read)
|
||||
- [`func read_bytes(path: Path -> [Byte]?)`](#read_bytes)
|
||||
- [`func read_bytes(path: Path, limit: Int? = none -> [Byte]?)`](#read_bytes)
|
||||
- [`func relative_to(path: Path, relative_to=(./) -> Path)`](#relative_to)
|
||||
- [`func remove(path: Path, ignore_missing=no -> Void)`](#remove)
|
||||
- [`func resolved(path: Path, relative_to=(./) -> Path)`](#resolved)
|
||||
- [`func set_owner(path:Path, owner=none:Text, group=none:Text, follow_symlinks=yes)`](#set_owner)
|
||||
- [`func set_owner(path:Path, owner:Text?=none, group:Text?=none, follow_symlinks=yes)`](#set_owner)
|
||||
- [`func subdirectories(path: Path, include_hidden=no -> [Path])`](#subdirectories)
|
||||
- [`func unique_directory(path: Path -> Path)`](#unique_directory)
|
||||
- [`func write(path: Path, text: Text, permissions=0o644[32] -> Void)`](#write)
|
||||
@ -95,7 +95,7 @@ accessed, or `none` if no such file or directory exists.
|
||||
>> (./file.txt):accessed()
|
||||
= 1704221100?
|
||||
>> (./not-a-file):accessed()
|
||||
= none:Int64?
|
||||
= none
|
||||
```
|
||||
|
||||
---
|
||||
@ -289,7 +289,7 @@ changed, or `none` if no such file or directory exists.
|
||||
>> (./file.txt):changed()
|
||||
= 1704221100?
|
||||
>> (./not-a-file):changed()
|
||||
= none:Int64
|
||||
= none
|
||||
```
|
||||
|
||||
---
|
||||
@ -535,7 +535,7 @@ The name of the group which owns the file or directory, or `none` if the path do
|
||||
>> (/bin):group()
|
||||
= "root"
|
||||
>> (/non/existent/file):group()
|
||||
= none:Text
|
||||
= none
|
||||
```
|
||||
|
||||
---
|
||||
@ -648,7 +648,7 @@ modified, or `none` if no such file or directory exists.
|
||||
>> (./file.txt):modified()
|
||||
= 1704221100?
|
||||
>> (./not-a-file):modified()
|
||||
= none:Int64
|
||||
= none
|
||||
```
|
||||
|
||||
---
|
||||
@ -671,7 +671,7 @@ The name of the user who owns the file or directory, or `none` if the path does
|
||||
>> (/bin):owner()
|
||||
= "root"
|
||||
>> (/non/existent/file):owner()
|
||||
= none:Text
|
||||
= none
|
||||
```
|
||||
|
||||
---
|
||||
@ -717,7 +717,7 @@ raised.
|
||||
= "Hello"?
|
||||
|
||||
>> (./nosuchfile.xxx):read()
|
||||
= none:Text
|
||||
= none
|
||||
```
|
||||
---
|
||||
|
||||
@ -726,10 +726,11 @@ Reads the contents of the file at the specified path or a null value if the
|
||||
file could not be read.
|
||||
|
||||
```tomo
|
||||
func read_bytes(path: Path -> [Byte]?)
|
||||
func read_bytes(path: Path, limit: Int? = none -> [Byte]?)
|
||||
```
|
||||
|
||||
- `path`: The path of the file to read.
|
||||
- `limit`: A limit to how many bytes should be read.
|
||||
|
||||
**Returns:**
|
||||
The byte contents of the file. If the file cannot be read, a null value will be
|
||||
@ -741,7 +742,7 @@ returned.
|
||||
= [72[B], 101[B], 108[B], 108[B], 111[B]]?
|
||||
|
||||
>> (./nosuchfile.xxx):read()
|
||||
= none:[Byte]
|
||||
= none
|
||||
```
|
||||
|
||||
---
|
||||
@ -815,7 +816,7 @@ The resolved absolute path.
|
||||
Set the owning user and/or group for a path.
|
||||
|
||||
```tomo
|
||||
func set_owner(path:Path, owner: Text? = none:Text, group: Text? = none:Text, follow_symlinks: Bool = yes)
|
||||
func set_owner(path:Path, owner: Text? = none, group: Text? = none, follow_symlinks: Bool = yes)
|
||||
```
|
||||
|
||||
- `path`: The path to change the permissions for.
|
||||
|
@ -18,7 +18,7 @@ a runtime check and error if there's a null value, or you can use `or` to
|
||||
provide a fallback value:
|
||||
|
||||
```tomo
|
||||
nums := [:Int]
|
||||
nums : [Int] = []
|
||||
sum := (+: nums)
|
||||
|
||||
>> sum
|
||||
|
@ -52,7 +52,7 @@ cyclic datastructures correctly, enabling you to serialize cyclic structures
|
||||
like circularly linked lists or graphs:
|
||||
|
||||
```tomo
|
||||
struct Cycle(name:Text, next=none:@Cycle)
|
||||
struct Cycle(name:Text, next:@Cycle?=none)
|
||||
|
||||
c := @Cycle("A")
|
||||
c.next = @Cycle("B", next=c)
|
||||
|
@ -12,16 +12,17 @@ b := {20, 30}
|
||||
|
||||
## Syntax
|
||||
|
||||
Sets are written using `{}` curly braces with comma-separated items:
|
||||
Sets are written using `{...}` curly braces with comma-separated items:
|
||||
|
||||
```tomo
|
||||
nums := {10, 20, 30}
|
||||
```
|
||||
|
||||
Empty sets must specify the item type explicitly:
|
||||
Empty sets must specify the set type explicitly and use `{/}` for an empty set
|
||||
(because `{}` is an empty table).
|
||||
|
||||
```tomo
|
||||
empty := {:Int}
|
||||
empty : {Int} = {/}
|
||||
```
|
||||
|
||||
For type annotations, a set that holds items with type `T` is written as `{T}`.
|
||||
|
@ -17,7 +17,7 @@ table := {"A"=10, "B"=20}
|
||||
Empty tables must specify the key and value types explicitly:
|
||||
|
||||
```tomo
|
||||
empty := {:Text=Int}
|
||||
empty : {Text=Int} = {}
|
||||
```
|
||||
|
||||
For type annotations, a table that maps keys with type `K` to values of type
|
||||
@ -43,7 +43,7 @@ table := {"A"=1, "B"=2}
|
||||
>> table["A"]
|
||||
= 1?
|
||||
>> table["missing"]
|
||||
= none:Int
|
||||
= none
|
||||
```
|
||||
|
||||
As with all optional values, you can use the `!` postfix operator to assert
|
||||
@ -77,7 +77,7 @@ table value:
|
||||
>> t2.fallback
|
||||
= {"A"=10}?
|
||||
>> t.fallback
|
||||
= none:{Text=Int}
|
||||
= none
|
||||
```
|
||||
|
||||
### Default Values
|
||||
@ -225,7 +225,7 @@ The value associated with the key or `none` if the key is not found.
|
||||
= 1?
|
||||
|
||||
>> t:get("????")
|
||||
= none:Int
|
||||
= none
|
||||
|
||||
>> t:get("A")!
|
||||
= 1
|
||||
|
@ -4,8 +4,7 @@ _enc := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/":bytes
|
||||
|
||||
_EQUAL_BYTE := Byte(0x3D)
|
||||
|
||||
_dec := [
|
||||
:Byte
|
||||
_dec : [Byte] = [
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
@ -32,7 +31,7 @@ lang Base64:
|
||||
output := &[Byte(0) for _ in bytes.length * 4 / 3 + 4]
|
||||
src := Int64(1)
|
||||
dest := Int64(1)
|
||||
while src + 2 <= bytes.length:
|
||||
while src + 2 <= Int64(bytes.length):
|
||||
chunk24 := (
|
||||
(Int32(bytes[src]) <<< 16) or (Int32(bytes[src+1]) <<< 8) or Int32(bytes[src+2])
|
||||
)
|
||||
@ -69,7 +68,7 @@ lang Base64:
|
||||
output := &[Byte(0) for _ in bytes.length/4 * 3]
|
||||
src := Int64(1)
|
||||
dest := Int64(1)
|
||||
while src + 3 <= bytes.length:
|
||||
while src + 3 <= Int64(bytes.length):
|
||||
chunk24 := (
|
||||
(Int32(_dec[1+bytes[src]]) <<< 18) or
|
||||
(Int32(_dec[1+bytes[src+1]]) <<< 12) or
|
||||
|
@ -24,7 +24,7 @@ lang Colorful:
|
||||
say(c:for_terminal(), newline=newline)
|
||||
|
||||
|
||||
func main(texts:[Text], files=[:Path], by_line=no):
|
||||
func main(texts:[Text], files:[Path]=[], by_line=no):
|
||||
for i,text in texts:
|
||||
colorful := Colorful.from_text(text)
|
||||
colorful:print(newline=no)
|
||||
@ -141,7 +141,7 @@ struct _TermState(
|
||||
):
|
||||
|
||||
func apply(old,new:_TermState -> Text):
|
||||
sequences := &[:Text]
|
||||
sequences : &[Text] = &[]
|
||||
_toggle2(sequences, old.bold, old.dim, new.bold, new.dim, "1", "2", "22")
|
||||
_toggle2(sequences, old.italic, old.fraktur, new.italic, new.fraktur, "3", "20", "23")
|
||||
_toggle(sequences, old.underline, new.underline, "4", "24")
|
||||
|
@ -11,12 +11,12 @@ enum ExitType(Exited(status:Int32), Signaled(signal:Int32), Failed):
|
||||
when e is Exited(status): return (status == 0)
|
||||
else: return no
|
||||
|
||||
func or_fail(e:ExitType, message=none:Text):
|
||||
func or_fail(e:ExitType, message:Text?=none):
|
||||
if not e:succeeded():
|
||||
fail(message or "Program failed: $e")
|
||||
|
||||
struct ProgramResult(stdout:[Byte], stderr:[Byte], exit_type:ExitType):
|
||||
func or_fail(r:ProgramResult, message=none:Text -> ProgramResult):
|
||||
func or_fail(r:ProgramResult, message:Text?=none -> ProgramResult):
|
||||
when r.exit_type is Exited(status):
|
||||
if status == 0:
|
||||
return r
|
||||
@ -46,16 +46,16 @@ struct ProgramResult(stdout:[Byte], stderr:[Byte], exit_type:ExitType):
|
||||
else:
|
||||
return no
|
||||
|
||||
struct Command(command:Text, args=[:Text], env={:Text=Text}):
|
||||
func from_path(path:Path, args=[:Text], env={:Text=Text} -> Command):
|
||||
struct Command(command:Text, args:[Text]=[], env:{Text=Text}={}):
|
||||
func from_path(path:Path, args:[Text]=[], env:{Text=Text}={} -> Command):
|
||||
return Command(Text(path), args, env)
|
||||
|
||||
func result(command:Command, input="", input_bytes=[:Byte] -> ProgramResult):
|
||||
func result(command:Command, input="", input_bytes:[Byte]=[] -> ProgramResult):
|
||||
if input.length > 0:
|
||||
(&input_bytes):insert_all(input:bytes())
|
||||
|
||||
stdout := [:Byte]
|
||||
stderr := [:Byte]
|
||||
stdout : [Byte] = []
|
||||
stderr : [Byte] = []
|
||||
status := run_command(command.command, command.args, command.env, input_bytes, &stdout, &stderr)
|
||||
|
||||
if inline C : Bool { WIFEXITED(_$status) }:
|
||||
@ -80,7 +80,7 @@ struct Command(command:Text, args=[:Text], env={:Text=Text}):
|
||||
func get_output(command:Command, input="", trim_newline=yes -> Text?):
|
||||
return command:result(input=input):output_text(trim_newline=trim_newline)
|
||||
|
||||
func get_output_bytes(command:Command, input="", input_bytes=[:Byte] -> [Byte]?):
|
||||
func get_output_bytes(command:Command, input="", input_bytes:[Byte]=[] -> [Byte]?):
|
||||
result := command:result(input=input, input_bytes=input_bytes)
|
||||
when result.exit_type is Exited(status):
|
||||
if status == 0: return result.stdout
|
||||
|
@ -10,7 +10,7 @@ func main(map=(./map.txt)):
|
||||
world := @World(
|
||||
player=@Player(Vector2(0,0), Vector2(0,0)),
|
||||
goal=@Box(Vector2(0,0), Vector2(50,50), color=Color(0x10,0xa0,0x10)),
|
||||
boxes=@[:@Box],
|
||||
boxes=@[],
|
||||
)
|
||||
world:load_map(map_contents)
|
||||
|
||||
|
@ -19,7 +19,7 @@ struct Vector2(x,y:Num32; extern):
|
||||
func negative(v:Vector2->Vector2; inline):
|
||||
return Vector2(-v.x, -v.y)
|
||||
func dot(a,b:Vector2->Num32; inline):
|
||||
return ((a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y))!
|
||||
return ((a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y))
|
||||
func cross(a,b:Vector2->Num32; inline):
|
||||
return a.x*b.y - a.y*b.x
|
||||
func scaled_by(v:Vector2, k:Num32->Vector2; inline):
|
||||
@ -27,7 +27,7 @@ struct Vector2(x,y:Num32; extern):
|
||||
func divided_by(v:Vector2, divisor:Num32->Vector2; inline):
|
||||
return Vector2(v.x/divisor, v.y/divisor)
|
||||
func length(v:Vector2->Num32; inline):
|
||||
return (v.x*v.x + v.y*v.y)!:sqrt()
|
||||
return (v.x*v.x + v.y*v.y):sqrt()
|
||||
func dist(a,b:Vector2->Num32; inline):
|
||||
return a:minus(b):length()
|
||||
func angle(v:Vector2->Num32; inline):
|
||||
|
@ -38,7 +38,7 @@ func solve_overlap(a_pos:Vector2, a_size:Vector2, b_pos:Vector2, b_size:Vector2
|
||||
return Vector2(0, 0)
|
||||
|
||||
struct World(player:@Player, goal:@Box, boxes:@[@Box], dt_accum=Num32(0.0), won=no):
|
||||
DT := (Num32(1.)/Num32(60.))!
|
||||
DT := (Num32(1.)/Num32(60.))
|
||||
STIFFNESS := Num32(0.3)
|
||||
|
||||
func update(w:@World, dt:Num32):
|
||||
@ -70,7 +70,7 @@ struct World(player:@Player, goal:@Box, boxes:@[@Box], dt_accum=Num32(0.0), won=
|
||||
func load_map(w:@World, map:Text):
|
||||
if map:has("[]"):
|
||||
map = map:translate({"[]"="#", "@ "="@", " "=" "})
|
||||
w.boxes = @[:@Box]
|
||||
w.boxes = @[]
|
||||
box_size := Vector2(50., 50.)
|
||||
for y,line in map:lines():
|
||||
for x,cell in line:split():
|
||||
|
@ -3,7 +3,7 @@ use pthreads
|
||||
func _assert_success(name:Text, val:Int32; inline):
|
||||
fail("$name() failed!") if val < 0
|
||||
|
||||
struct ConnectionQueue(_connections=@[:Int32], _mutex=pthread_mutex_t.new(), _cond=pthread_cond_t.new()):
|
||||
struct ConnectionQueue(_connections:@[Int32]=@[], _mutex=pthread_mutex_t.new(), _cond=pthread_cond_t.new()):
|
||||
func enqueue(queue:ConnectionQueue, connection:Int32):
|
||||
queue._mutex:lock()
|
||||
queue._connections:insert(connection)
|
||||
@ -12,7 +12,7 @@ struct ConnectionQueue(_connections=@[:Int32], _mutex=pthread_mutex_t.new(), _co
|
||||
|
||||
|
||||
func dequeue(queue:ConnectionQueue -> Int32):
|
||||
conn := none:Int32
|
||||
conn : Int32? = none
|
||||
|
||||
queue._mutex:lock()
|
||||
|
||||
|
@ -17,7 +17,7 @@ use ./connection-queue.tm
|
||||
|
||||
func serve(port:Int32, handler:func(request:HTTPRequest -> HTTPResponse), num_threads=16):
|
||||
connections := ConnectionQueue()
|
||||
workers := &[:@pthread_t]
|
||||
workers : &[@pthread_t] = &[]
|
||||
for i in num_threads:
|
||||
workers:insert(pthread_t.new(func():
|
||||
repeat:
|
||||
@ -82,7 +82,7 @@ struct HTTPRequest(method:Text, path:Text, version:Text, headers:[Text], body:Te
|
||||
body := rest[-1]
|
||||
return HTTPRequest(method, path, version, headers, body)
|
||||
|
||||
struct HTTPResponse(body:Text, status=200, content_type="text/plain", headers={:Text=Text}):
|
||||
struct HTTPResponse(body:Text, status=200, content_type="text/plain", headers:{Text=Text}={}):
|
||||
func bytes(r:HTTPResponse -> [Byte]):
|
||||
body_bytes := r.body:bytes()
|
||||
extra_headers := (++: "$k: $v$(\r\n)" for k,v in r.headers) or ""
|
||||
@ -114,7 +114,7 @@ enum RouteEntry(ServeFile(file:Path), Redirect(destination:Text)):
|
||||
return HTTPResponse("Found", 302, headers={"Location"=destination})
|
||||
|
||||
func load_routes(directory:Path -> {Text=RouteEntry}):
|
||||
routes := &{:Text=RouteEntry}
|
||||
routes : &{Text=RouteEntry} = &{}
|
||||
for file in (directory ++ (./*)):glob():
|
||||
skip unless file:is_file()
|
||||
contents := file:read() or skip
|
||||
|
@ -7,8 +7,8 @@ struct HTTPResponse(code:Int, body:Text)
|
||||
|
||||
enum _Method(GET, POST, PUT, PATCH, DELETE)
|
||||
|
||||
func _send(method:_Method, url:Text, data:Text?, headers=[:Text] -> HTTPResponse):
|
||||
chunks := @[:Text]
|
||||
func _send(method:_Method, url:Text, data:Text?, headers:[Text]=[] -> HTTPResponse):
|
||||
chunks : @[Text] = @[]
|
||||
save_chunk := func(chunk:CString, size:Int64, n:Int64):
|
||||
chunks:insert(inline C:Text {
|
||||
Text$format("%.*s", _$size*_$n, _$chunk)
|
||||
@ -81,7 +81,7 @@ func _send(method:_Method, url:Text, data:Text?, headers=[:Text] -> HTTPResponse
|
||||
}
|
||||
return HTTPResponse(Int(code), "":join(chunks))
|
||||
|
||||
func get(url:Text, headers=[:Text] -> HTTPResponse):
|
||||
func get(url:Text, headers:[Text]=[] -> HTTPResponse):
|
||||
return _send(GET, url, none, headers)
|
||||
|
||||
func post(url:Text, data="", headers=["Content-Type: application/json", "Accept: application/json"] -> HTTPResponse):
|
||||
@ -93,7 +93,7 @@ func put(url:Text, data="", headers=["Content-Type: application/json", "Accept:
|
||||
func patch(url:Text, data="", headers=["Content-Type: application/json", "Accept: application/json"] -> HTTPResponse):
|
||||
return _send(PATCH, url, data, headers)
|
||||
|
||||
func delete(url:Text, data=none:Text, headers=["Content-Type: application/json", "Accept: application/json"] -> HTTPResponse):
|
||||
func delete(url:Text, data:Text?=none, headers=["Content-Type: application/json", "Accept: application/json"] -> HTTPResponse):
|
||||
return _send(DELETE, url, data, headers)
|
||||
|
||||
func main():
|
||||
|
@ -11,8 +11,8 @@ _HELP := "
|
||||
|
||||
func parse_ini(path:Path -> {Text={Text=Text}}):
|
||||
text := path:read() or exit("Could not read INI file: $\[31;1]$(path)$\[]")
|
||||
sections := @{:Text=@{Text=Text}}
|
||||
current_section := @{:Text=Text}
|
||||
sections : @{Text=@{Text=Text}} = @{}
|
||||
current_section : @{Text=Text} = @{}
|
||||
|
||||
# Line wraps:
|
||||
text = text:replace_pattern($Pat/\{1 nl}{0+space}/, " ")
|
||||
@ -22,7 +22,7 @@ func parse_ini(path:Path -> {Text={Text=Text}}):
|
||||
skip if line:starts_with(";") or line:starts_with("#")
|
||||
if line:matches_pattern($Pat/[?]/):
|
||||
section_name := line:replace($Pat/[?]/, "\1"):trim():lower()
|
||||
current_section = @{:Text=Text}
|
||||
current_section = @{}
|
||||
sections[section_name] = current_section
|
||||
else if line:matches_pattern($Pat/{..}={..}/):
|
||||
key := line:replace_pattern($Pat/{..}={..}/, "\1"):trim():lower()
|
||||
|
@ -59,7 +59,7 @@ func main():
|
||||
my_numbers := [10, 20, 30]
|
||||
|
||||
# Empty arrays require specifying the type:
|
||||
empty_array := [:Int]
|
||||
empty_array : [Int] = []
|
||||
>> empty_array.length
|
||||
= 0
|
||||
|
||||
@ -111,7 +111,7 @@ func main():
|
||||
# The value returned is optional because none will be returned if the key
|
||||
# is not in the table:
|
||||
>> table["xxx"]
|
||||
= none : Int
|
||||
= none
|
||||
|
||||
# Optional values can be converted to regular values using `!` (which will
|
||||
# create a runtime error if the value is null):
|
||||
@ -123,7 +123,7 @@ func main():
|
||||
= 0
|
||||
|
||||
# Empty tables require specifying the key and value types:
|
||||
empty_table := {:Text=Int}
|
||||
empty_table : {Text=Int} = {}
|
||||
|
||||
# Tables can be iterated over either by key or key,value:
|
||||
for key in table:
|
||||
|
@ -3,7 +3,7 @@ use <stdio.h>
|
||||
|
||||
timestamp_format := CString("%F %T")
|
||||
|
||||
logfiles := @{:Path}
|
||||
logfiles : @{Path} = @{/}
|
||||
|
||||
func _timestamp(->Text):
|
||||
c_str := inline C:CString {
|
||||
|
@ -332,7 +332,7 @@ not match the pattern.
|
||||
>> "123 boxes":pattern_captures($Pat"{int} {id}")
|
||||
= ["123", "boxes"]?
|
||||
>> "xxx":pattern_captures($Pat"{int} {id}")
|
||||
= none:[Text]
|
||||
= none
|
||||
```
|
||||
|
||||
---
|
||||
|
@ -65,7 +65,7 @@ struct pthread_t(; extern, opaque):
|
||||
func detatch(p:pthread_t): inline C { pthread_detach(_$p); }
|
||||
|
||||
struct IntQueue(_queue:@[Int], _mutex:@pthread_mutex_t, _cond:@pthread_cond_t):
|
||||
func new(initial=[:Int] -> IntQueue):
|
||||
func new(initial:[Int]=[] -> IntQueue):
|
||||
return IntQueue(@initial, pthread_mutex_t.new(), pthread_cond_t.new())
|
||||
|
||||
func give(q:IntQueue, n:Int):
|
||||
|
@ -110,7 +110,7 @@ A copy of the given RNG.
|
||||
|
||||
**Example:**
|
||||
```tomo
|
||||
>> rng := RNG.new([:Byte])
|
||||
>> rng := RNG.new([])
|
||||
>> copy := rng:copy()
|
||||
|
||||
>> rng:bytes(10)
|
||||
|
@ -4,7 +4,7 @@ use ./sysrandom.h
|
||||
use ./chacha.h
|
||||
|
||||
struct chacha_ctx(j0,j1,j2,j3,j4,j5,j6,j7,j8,j9,j10,j11,j12,j13,j14,j15:Int32; extern, secret):
|
||||
func from_seed(seed=[:Byte] -> chacha_ctx):
|
||||
func from_seed(seed:[Byte]=[] -> chacha_ctx):
|
||||
return inline C : chacha_ctx {
|
||||
chacha_ctx ctx;
|
||||
uint8_t seed_bytes[KEYSZ + IVSZ] = {};
|
||||
@ -24,10 +24,10 @@ func _os_random_bytes(count:Int64 -> [Byte]):
|
||||
(Array_t){.length=_$count, .data=random_bytes, .stride=1, .atomic=1};
|
||||
}
|
||||
|
||||
struct RandomNumberGenerator(_chacha:chacha_ctx, _random_bytes=[:Byte]; secret):
|
||||
func new(seed=none:[Byte], -> @RandomNumberGenerator):
|
||||
struct RandomNumberGenerator(_chacha:chacha_ctx, _random_bytes:[Byte]=[]; secret):
|
||||
func new(seed:[Byte]?=none, -> @RandomNumberGenerator):
|
||||
ctx := chacha_ctx.from_seed(seed or _os_random_bytes(40))
|
||||
return @RandomNumberGenerator(ctx, [:Byte])
|
||||
return @RandomNumberGenerator(ctx, [])
|
||||
|
||||
func _rekey(rng:&RandomNumberGenerator):
|
||||
rng._random_bytes = inline C : [Byte] {
|
||||
|
@ -24,7 +24,7 @@ lang Shell:
|
||||
func command(shell:Shell -> Command):
|
||||
return Command("sh", ["-c", shell.text])
|
||||
|
||||
func result(shell:Shell, input="", input_bytes=[:Byte] -> ProgramResult):
|
||||
func result(shell:Shell, input="", input_bytes:[Byte]=[] -> ProgramResult):
|
||||
return shell:command():result(input=input, input_bytes=input_bytes)
|
||||
|
||||
func run(shell:Shell -> ExitType):
|
||||
@ -33,7 +33,7 @@ lang Shell:
|
||||
func get_output(shell:Shell, input="", trim_newline=yes -> Text?):
|
||||
return shell:command():get_output(input=input, trim_newline=trim_newline)
|
||||
|
||||
func get_output_bytes(shell:Shell, input="", input_bytes=[:Byte] -> [Byte]?):
|
||||
func get_output_bytes(shell:Shell, input="", input_bytes:[Byte]=[] -> [Byte]?):
|
||||
return shell:command():get_output_bytes(input=input, input_bytes=input_bytes)
|
||||
|
||||
func by_line(shell:Shell -> func(->Text?)?):
|
||||
|
@ -11,7 +11,7 @@ _HELP := "
|
||||
"
|
||||
|
||||
func find_urls(path:Path -> [Text]):
|
||||
urls := @[:Text]
|
||||
urls : @[Text] = @[]
|
||||
if path:is_directory():
|
||||
for f in path:children():
|
||||
urls:insert_all(find_urls(f))
|
||||
@ -25,7 +25,7 @@ func main(paths:[Path]):
|
||||
if paths.length == 0:
|
||||
paths = [(./)]
|
||||
|
||||
urls := (++: find_urls(p) for p in paths) or [:Text]
|
||||
urls := (++: find_urls(p) for p in paths) or []
|
||||
|
||||
github_token := (~/.config/tomo/github-token):read()
|
||||
|
||||
@ -40,7 +40,7 @@ func main(paths:[Path]):
|
||||
say("Already installed: $url")
|
||||
skip
|
||||
|
||||
alias := none:Text
|
||||
alias : Text? = none
|
||||
curl_flags := ["-L"]
|
||||
if github := url_without_protocol:pattern_captures($Pat"github.com/{!/}/{!/}#{..}"):
|
||||
user := github[1]
|
||||
|
@ -14,9 +14,9 @@ enum Dependency(File(path:Path), Module(name:Text))
|
||||
func _get_file_dependencies(file:Path -> {Dependency}):
|
||||
if not file:is_file():
|
||||
!! Could not read file: $file
|
||||
return {:Dependency}
|
||||
return {}
|
||||
|
||||
deps := @{:Dependency}
|
||||
deps : @{Dependency} = @{}
|
||||
if lines := file:by_line():
|
||||
for line in lines:
|
||||
if line:matches_pattern($Pat/use {..}.tm/):
|
||||
@ -27,17 +27,17 @@ func _get_file_dependencies(file:Path -> {Dependency}):
|
||||
deps:add(Dependency.Module(module_name))
|
||||
return deps[]
|
||||
|
||||
func _build_dependency_graph(dep:Dependency, dependencies:@{Dependency,{Dependency}}):
|
||||
func _build_dependency_graph(dep:Dependency, dependencies:@{Dependency={Dependency}}):
|
||||
return if dependencies:has(dep)
|
||||
|
||||
dependencies[dep] = {:Dependency} # Placeholder
|
||||
dependencies[dep] = {} # Placeholder
|
||||
|
||||
dep_deps := when dep is File(path):
|
||||
_get_file_dependencies(path)
|
||||
is Module(module):
|
||||
dir := (~/.local/share/tomo/installed/$module)
|
||||
module_deps := @{:Dependency}
|
||||
visited := @{:Path}
|
||||
module_deps : @{Dependency} = @{}
|
||||
visited : @{Path} = @{}
|
||||
unvisited := @{f:resolved() for f in dir:files() if f:extension() == ".tm"}
|
||||
while unvisited.length > 0:
|
||||
file := unvisited.items[-1]
|
||||
@ -57,8 +57,8 @@ func _build_dependency_graph(dep:Dependency, dependencies:@{Dependency,{Dependen
|
||||
for dep2 in dep_deps:
|
||||
_build_dependency_graph(dep2, dependencies)
|
||||
|
||||
func get_dependency_graph(dep:Dependency -> {Dependency,{Dependency}}):
|
||||
graph := @{:Dependency,{Dependency}}
|
||||
func get_dependency_graph(dep:Dependency -> {Dependency={Dependency}}):
|
||||
graph : @{Dependency={Dependency}} = @{}
|
||||
_build_dependency_graph(dep, graph)
|
||||
return graph
|
||||
|
||||
@ -72,7 +72,7 @@ func _printable_name(dep:Dependency -> Text):
|
||||
else:
|
||||
return "$(\x1b)[31;1m$(f) (not found)$(\x1b)[m"
|
||||
|
||||
func _draw_tree(dep:Dependency, dependencies:{Dependency,{Dependency}}, already_printed:@{Dependency}, prefix="", is_last=yes):
|
||||
func _draw_tree(dep:Dependency, dependencies:{Dependency={Dependency}}, already_printed:@{Dependency}, prefix="", is_last=yes):
|
||||
if already_printed:has(dep):
|
||||
say(prefix ++ (if is_last: "└── " else: "├── ") ++ _printable_name(dep) ++ " $\x1b[2m(recursive)$\x1b[m")
|
||||
return
|
||||
@ -82,16 +82,16 @@ func _draw_tree(dep:Dependency, dependencies:{Dependency,{Dependency}}, already_
|
||||
|
||||
child_prefix := prefix ++ (if is_last: " " else: "│ ")
|
||||
|
||||
children := dependencies[dep] or {:Dependency}
|
||||
children := dependencies[dep] or {/}
|
||||
for i,child in children.items:
|
||||
is_child_last := (i == children.length)
|
||||
_draw_tree(child, dependencies, already_printed, child_prefix, is_child_last)
|
||||
|
||||
func draw_tree(dep:Dependency, dependencies:{Dependency,{Dependency}}):
|
||||
printed := @{:Dependency}
|
||||
func draw_tree(dep:Dependency, dependencies:{Dependency={Dependency}}):
|
||||
printed : @{Dependency} = @{}
|
||||
say(_printable_name(dep))
|
||||
printed:add(dep)
|
||||
deps := dependencies[dep] or {:Dependency}
|
||||
deps := dependencies[dep] or {/}
|
||||
for i,child in deps.items:
|
||||
is_child_last := (i == deps.length)
|
||||
_draw_tree(child, dependencies, already_printed=printed, is_last=is_child_last)
|
||||
|
@ -19,7 +19,7 @@ struct Vec2(x,y:Num):
|
||||
func divided_by(v:Vec2, divisor:Num->Vec2; inline):
|
||||
return Vec2(v.x/divisor, v.y/divisor)
|
||||
func length(v:Vec2->Num; inline):
|
||||
return (v.x*v.x + v.y*v.y)!:sqrt()
|
||||
return (v.x*v.x + v.y*v.y):sqrt()
|
||||
func dist(a,b:Vec2->Num; inline):
|
||||
return a:minus(b):length()
|
||||
func angle(v:Vec2->Num; inline):
|
||||
@ -58,7 +58,7 @@ struct Vec3(x,y,z:Num):
|
||||
func divided_by(v:Vec3, divisor:Num->Vec3; inline):
|
||||
return Vec3(v.x/divisor, v.y/divisor, v.z/divisor)
|
||||
func length(v:Vec3->Num; inline):
|
||||
return (v.x*v.x + v.y*v.y + v.z*v.z)!:sqrt()
|
||||
return (v.x*v.x + v.y*v.y + v.z*v.z):sqrt()
|
||||
func dist(a,b:Vec3->Num; inline):
|
||||
return a:minus(b):length()
|
||||
func norm(v:Vec3->Vec3; inline):
|
||||
|
@ -33,7 +33,7 @@ func wrap(text:Text, width:Int, min_split=3, hyphen="-" -> Text):
|
||||
... and I can't split it without splitting into chunks smaller than $min_split.
|
||||
")
|
||||
|
||||
lines := @[:Text]
|
||||
lines : @[Text] = @[]
|
||||
line := ""
|
||||
for word in text:split($/{whitespace}/):
|
||||
letters := word:split()
|
||||
@ -93,7 +93,7 @@ func main(files:[Path], width=80, inplace=no, min_split=3, rewrap=yes, hyphen=UN
|
||||
(/dev/stdout)
|
||||
|
||||
first := yes
|
||||
wrapped_paragraphs := @[:Text]
|
||||
wrapped_paragraphs : @[Text] = @[]
|
||||
for paragraph in text:split($/{2+ nl}/):
|
||||
wrapped_paragraphs:insert(
|
||||
wrap(paragraph, width=width, min_split=min_split, hyphen=hyphen)
|
||||
|
120
src/ast.c
120
src/ast.c
@ -10,23 +10,47 @@
|
||||
#include "stdlib/text.h"
|
||||
#include "cordhelpers.h"
|
||||
|
||||
static const char *OP_NAMES[] = {
|
||||
[BINOP_UNKNOWN]="unknown",
|
||||
[BINOP_POWER]="^", [BINOP_MULT]="*", [BINOP_DIVIDE]="/",
|
||||
[BINOP_MOD]="mod", [BINOP_MOD1]="mod1", [BINOP_PLUS]="+", [BINOP_MINUS]="minus",
|
||||
[BINOP_CONCAT]="++", [BINOP_LSHIFT]="<<", [BINOP_ULSHIFT]="<<<",
|
||||
[BINOP_RSHIFT]=">>", [BINOP_URSHIFT]=">>>", [BINOP_MIN]="min",
|
||||
[BINOP_MAX]="max", [BINOP_EQ]="==", [BINOP_NE]="!=", [BINOP_LT]="<",
|
||||
[BINOP_LE]="<=", [BINOP_GT]=">", [BINOP_GE]=">=", [BINOP_CMP]="<>",
|
||||
[BINOP_AND]="and", [BINOP_OR]="or", [BINOP_XOR]="xor",
|
||||
CONSTFUNC const char *binop_method_name(ast_e tag) {
|
||||
switch (tag) {
|
||||
case Power: case PowerUpdate: return "power";
|
||||
case Multiply: case MultiplyUpdate: return "times";
|
||||
case Divide: case DivideUpdate: return "divided_by";
|
||||
case Mod: case ModUpdate: return "modulo";
|
||||
case Mod1: case Mod1Update: return "modulo1";
|
||||
case Plus: case PlusUpdate: return "plus";
|
||||
case Minus: case MinusUpdate: return "minus";
|
||||
case Concat: case ConcatUpdate: return "concatenated_with";
|
||||
case LeftShift: case LeftShiftUpdate: return "left_shifted";
|
||||
case RightShift: case RightShiftUpdate: return "right_shifted";
|
||||
case UnsignedLeftShift: case UnsignedLeftShiftUpdate: return "unsigned_left_shifted";
|
||||
case UnsignedRightShift: case UnsignedRightShiftUpdate: return "unsigned_right_shifted";
|
||||
case And: case AndUpdate: return "bit_and";
|
||||
case Or: case OrUpdate: return "bit_or";
|
||||
case Xor: case XorUpdate: return "bit_xor";
|
||||
default: return NULL;
|
||||
}
|
||||
};
|
||||
|
||||
const char *binop_method_names[BINOP_XOR+1] = {
|
||||
[BINOP_POWER]="power", [BINOP_MULT]="times", [BINOP_DIVIDE]="divided_by",
|
||||
[BINOP_MOD]="modulo", [BINOP_MOD1]="modulo1", [BINOP_PLUS]="plus", [BINOP_MINUS]="minus",
|
||||
[BINOP_CONCAT]="concatenated_with", [BINOP_LSHIFT]="left_shifted", [BINOP_RSHIFT]="right_shifted",
|
||||
[BINOP_ULSHIFT]="unsigned_left_shifted", [BINOP_URSHIFT]="unsigned_right_shifted",
|
||||
[BINOP_AND]="bit_and", [BINOP_OR]="bit_or", [BINOP_XOR]="bit_xor",
|
||||
CONSTFUNC const char *binop_operator(ast_e tag) {
|
||||
switch (tag) {
|
||||
case Multiply: case MultiplyUpdate: return "*";
|
||||
case Divide: case DivideUpdate: return "/";
|
||||
case Mod: case ModUpdate: return "%";
|
||||
case Plus: case PlusUpdate: return "+";
|
||||
case Minus: case MinusUpdate: return "-";
|
||||
case LeftShift: case LeftShiftUpdate: return "<<";
|
||||
case RightShift: case RightShiftUpdate: return ">>";
|
||||
case And: case AndUpdate: return "&";
|
||||
case Or: case OrUpdate: return "|";
|
||||
case Xor: case XorUpdate: return "^";
|
||||
case Equals: return "==";
|
||||
case NotEquals: return "!=";
|
||||
case LessThan: return "<";
|
||||
case LessThanOrEquals: return "<=";
|
||||
case GreaterThan: return ">";
|
||||
case GreaterThanOrEquals: return ">=";
|
||||
default: return NULL;
|
||||
}
|
||||
};
|
||||
|
||||
static CORD ast_list_to_xml(ast_list_t *asts);
|
||||
@ -100,7 +124,7 @@ CORD ast_to_xml(ast_t *ast)
|
||||
switch (ast->tag) {
|
||||
#define T(type, ...) case type: { auto data = ast->__data.type; (void)data; return CORD_asprintf(__VA_ARGS__); }
|
||||
T(Unknown, "<Unknown>")
|
||||
T(None, "<None>%r</None>", type_ast_to_xml(data.type))
|
||||
T(None, "<None/>")
|
||||
T(Bool, "<Bool value=\"%s\" />", data.b ? "yes" : "no")
|
||||
T(Var, "<Var>%s</Var>", data.name)
|
||||
T(Int, "<Int>%s</Int>", data.str)
|
||||
@ -108,22 +132,25 @@ CORD ast_to_xml(ast_t *ast)
|
||||
T(TextLiteral, "%r", xml_escape(data.cord))
|
||||
T(TextJoin, "<Text%r>%r</Text>", data.lang ? CORD_all(" lang=\"", data.lang, "\"") : CORD_EMPTY, ast_list_to_xml(data.children))
|
||||
T(Path, "<Path>%s</Path>", data.path)
|
||||
T(Declare, "<Declare var=\"%r\">%r</Declare>", ast_to_xml(data.var), ast_to_xml(data.value))
|
||||
T(Declare, "<Declare var=\"%r\">%r%r</Declare>", ast_to_xml(data.var), type_ast_to_xml(data.type), ast_to_xml(data.value))
|
||||
T(Assign, "<Assign><targets>%r</targets><values>%r</values></Assign>", ast_list_to_xml(data.targets), ast_list_to_xml(data.values))
|
||||
T(BinaryOp, "<BinaryOp op=\"%r\">%r %r</BinaryOp>", xml_escape(OP_NAMES[data.op]), ast_to_xml(data.lhs), ast_to_xml(data.rhs))
|
||||
T(UpdateAssign, "<UpdateAssign op=\"%r\">%r %r</UpdateAssign>", xml_escape(OP_NAMES[data.op]), ast_to_xml(data.lhs), ast_to_xml(data.rhs))
|
||||
#define BINOP(name) T(name, "<" #name ">%r %r</" #name ">", ast_to_xml(data.lhs), ast_to_xml(data.rhs))
|
||||
BINOP(Power) BINOP(PowerUpdate) BINOP(Multiply) BINOP(MultiplyUpdate) BINOP(Divide) BINOP(DivideUpdate) BINOP(Mod) BINOP(ModUpdate)
|
||||
BINOP(Mod1) BINOP(Mod1Update) BINOP(Plus) BINOP(PlusUpdate) BINOP(Minus) BINOP(MinusUpdate) BINOP(Concat) BINOP(ConcatUpdate)
|
||||
BINOP(LeftShift) BINOP(LeftShiftUpdate) BINOP(RightShift) BINOP(RightShiftUpdate) BINOP(UnsignedLeftShift) BINOP(UnsignedLeftShiftUpdate)
|
||||
BINOP(UnsignedRightShift) BINOP(UnsignedRightShiftUpdate) BINOP(And) BINOP(AndUpdate) BINOP(Or) BINOP(OrUpdate)
|
||||
BINOP(Xor) BINOP(XorUpdate) BINOP(Compare)
|
||||
BINOP(Equals) BINOP(NotEquals) BINOP(LessThan) BINOP(LessThanOrEquals) BINOP(GreaterThan) BINOP(GreaterThanOrEquals)
|
||||
#undef BINOP
|
||||
T(Negative, "<Negative>%r</Negative>", ast_to_xml(data.value))
|
||||
T(Not, "<Not>%r</Not>", ast_to_xml(data.value))
|
||||
T(HeapAllocate, "<HeapAllocate>%r</HeapAllocate>", ast_to_xml(data.value))
|
||||
T(StackReference, "<StackReference>%r</StackReference>", ast_to_xml(data.value))
|
||||
T(Min, "<Min>%r%r%r</Min>", ast_to_xml(data.lhs), ast_to_xml(data.rhs), optional_tagged("key", data.key))
|
||||
T(Max, "<Max>%r%r%r</Max>", ast_to_xml(data.lhs), ast_to_xml(data.rhs), optional_tagged("key", data.key))
|
||||
T(Array, "<Array>%r%r</Array>", optional_tagged_type("item-type", data.item_type), ast_list_to_xml(data.items))
|
||||
T(Set, "<Set>%r%r</Set>",
|
||||
optional_tagged_type("item-type", data.item_type),
|
||||
ast_list_to_xml(data.items))
|
||||
T(Table, "<Table>%r%r%r%r</Table>",
|
||||
optional_tagged_type("key-type", data.key_type), optional_tagged_type("value-type", data.value_type),
|
||||
T(Array, "<Array>%r</Array>", ast_list_to_xml(data.items))
|
||||
T(Set, "<Set>%r</Set>", ast_list_to_xml(data.items))
|
||||
T(Table, "<Table>%r%r</Table>",
|
||||
optional_tagged("default-value", data.default_value),
|
||||
ast_list_to_xml(data.entries), optional_tagged("fallback", data.fallback))
|
||||
T(TableEntry, "<TableEntry>%r%r</TableEntry>", ast_to_xml(data.key), ast_to_xml(data.value))
|
||||
@ -145,7 +172,7 @@ CORD ast_to_xml(ast_t *ast)
|
||||
T(Repeat, "<Repeat>%r</Repeat>", optional_tagged("body", data.body))
|
||||
T(If, "<If>%r%r%r</If>", optional_tagged("condition", data.condition), optional_tagged("body", data.body), optional_tagged("else", data.else_body))
|
||||
T(When, "<When><subject>%r</subject>%r%r</When>", ast_to_xml(data.subject), when_clauses_to_xml(data.clauses), optional_tagged("else", data.else_body))
|
||||
T(Reduction, "<Reduction op=%r%r>%r</Reduction>", xml_escape(OP_NAMES[data.op]), optional_tagged("key", data.key),
|
||||
T(Reduction, "<Reduction op=%r%r>%r</Reduction>", xml_escape(binop_method_name(data.op)), optional_tagged("key", data.key),
|
||||
optional_tagged("iterable", data.iter))
|
||||
T(Skip, "<Skip>%r</Skip>", data.target)
|
||||
T(Stop, "<Stop>%r</Stop>", data.target)
|
||||
@ -313,4 +340,45 @@ void visit_topologically(ast_list_t *asts, Closure_t fn)
|
||||
}
|
||||
}
|
||||
|
||||
CONSTFUNC bool is_binary_operation(ast_t *ast)
|
||||
{
|
||||
switch (ast->tag) {
|
||||
case BINOP_CASES: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
CONSTFUNC bool is_update_assignment(ast_t *ast)
|
||||
{
|
||||
switch (ast->tag) {
|
||||
case PowerUpdate: case MultiplyUpdate: case DivideUpdate: case ModUpdate: case Mod1Update:
|
||||
case PlusUpdate: case MinusUpdate: case ConcatUpdate: case LeftShiftUpdate: case UnsignedLeftShiftUpdate:
|
||||
case RightShiftUpdate: case UnsignedRightShiftUpdate: case AndUpdate: case OrUpdate: case XorUpdate:
|
||||
return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
CONSTFUNC ast_e binop_tag(ast_e tag)
|
||||
{
|
||||
switch (tag) {
|
||||
case PowerUpdate: return Power;
|
||||
case MultiplyUpdate: return Multiply;
|
||||
case DivideUpdate: return Divide;
|
||||
case ModUpdate: return Mod;
|
||||
case Mod1Update: return Mod1;
|
||||
case PlusUpdate: return Plus;
|
||||
case MinusUpdate: return Minus;
|
||||
case ConcatUpdate: return Concat;
|
||||
case LeftShiftUpdate: return LeftShift;
|
||||
case UnsignedLeftShiftUpdate: return UnsignedLeftShift;
|
||||
case RightShiftUpdate: return RightShift;
|
||||
case UnsignedRightShiftUpdate: return UnsignedRightShift;
|
||||
case AndUpdate: return And;
|
||||
case OrUpdate: return Or;
|
||||
case XorUpdate: return Xor;
|
||||
default: return Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0
|
||||
|
55
src/ast.h
55
src/ast.h
@ -20,6 +20,7 @@
|
||||
#define WrapAST(ast, ast_tag, ...) (new(ast_t, .file=(ast)->file, .start=(ast)->start, .end=(ast)->end, .tag=ast_tag, .__data.ast_tag={__VA_ARGS__}))
|
||||
#define TextAST(ast, _str) WrapAST(ast, TextLiteral, .str=GC_strdup(_str))
|
||||
#define Match(x, _tag) ((x)->tag == _tag ? &(x)->__data._tag : (errx(1, __FILE__ ":%d This was supposed to be a " # _tag "\n", __LINE__), &(x)->__data._tag))
|
||||
#define BINARY_OPERANDS(ast) ({ if (!is_binary_operation(ast)) errx(1, __FILE__ ":%d This is not a binary operation!", __LINE__); (ast)->__data.Plus; })
|
||||
|
||||
#define REVERSE_LIST(list) do { \
|
||||
__typeof(list) _prev = NULL; \
|
||||
@ -37,6 +38,9 @@
|
||||
struct binding_s;
|
||||
typedef struct type_ast_s type_ast_t;
|
||||
typedef struct ast_s ast_t;
|
||||
typedef struct {
|
||||
ast_t *lhs, *rhs;
|
||||
} binary_operands_t;
|
||||
|
||||
typedef struct ast_list_s {
|
||||
ast_t *ast;
|
||||
@ -55,17 +59,6 @@ typedef struct when_clause_s {
|
||||
struct when_clause_s *next;
|
||||
} when_clause_t;
|
||||
|
||||
typedef enum {
|
||||
BINOP_UNKNOWN,
|
||||
BINOP_POWER=100, BINOP_MULT, BINOP_DIVIDE, BINOP_MOD, BINOP_MOD1, BINOP_PLUS,
|
||||
BINOP_MINUS, BINOP_CONCAT, BINOP_LSHIFT, BINOP_ULSHIFT, BINOP_RSHIFT, BINOP_URSHIFT, BINOP_MIN,
|
||||
BINOP_MAX, BINOP_EQ, BINOP_NE, BINOP_LT, BINOP_LE, BINOP_GT, BINOP_GE,
|
||||
BINOP_CMP,
|
||||
BINOP_AND, BINOP_OR, BINOP_XOR,
|
||||
} binop_e;
|
||||
|
||||
extern const char *binop_method_names[BINOP_XOR+1];
|
||||
|
||||
typedef enum {
|
||||
UnknownTypeAST,
|
||||
VarTypeAST,
|
||||
@ -117,6 +110,15 @@ struct type_ast_s {
|
||||
} __data;
|
||||
};
|
||||
|
||||
#define BINOP_CASES Power: case Multiply: case Divide: case Mod: case Mod1: case Plus: case Minus: case Concat: case LeftShift: case UnsignedLeftShift: \
|
||||
case RightShift: case UnsignedRightShift: case Equals: case NotEquals: case LessThan: case LessThanOrEquals: case GreaterThan: \
|
||||
case GreaterThanOrEquals: case Compare: case And: case Or: case Xor: \
|
||||
case PowerUpdate: case MultiplyUpdate: case DivideUpdate: case ModUpdate: case Mod1Update: case PlusUpdate: case MinusUpdate: case ConcatUpdate: \
|
||||
case LeftShiftUpdate: case UnsignedLeftShiftUpdate
|
||||
#define UPDATE_CASES PowerUpdate: case MultiplyUpdate: case DivideUpdate: case ModUpdate: case Mod1Update: case PlusUpdate: case MinusUpdate: \
|
||||
case ConcatUpdate: case LeftShiftUpdate: case UnsignedLeftShiftUpdate: case RightShiftUpdate: case UnsignedRightShiftUpdate: \
|
||||
case AndUpdate: case OrUpdate: case XorUpdate
|
||||
|
||||
typedef enum {
|
||||
Unknown = 0,
|
||||
None, Bool, Var,
|
||||
@ -124,7 +126,11 @@ typedef enum {
|
||||
TextLiteral, TextJoin, PrintStatement,
|
||||
Path,
|
||||
Declare, Assign,
|
||||
BinaryOp, UpdateAssign,
|
||||
Power, Multiply, Divide, Mod, Mod1, Plus, Minus, Concat, LeftShift, UnsignedLeftShift,
|
||||
RightShift, UnsignedRightShift, Equals, NotEquals, LessThan, LessThanOrEquals, GreaterThan,
|
||||
GreaterThanOrEquals, Compare, And, Or, Xor,
|
||||
PowerUpdate, MultiplyUpdate, DivideUpdate, ModUpdate, Mod1Update, PlusUpdate, MinusUpdate, ConcatUpdate, LeftShiftUpdate, UnsignedLeftShiftUpdate,
|
||||
RightShiftUpdate, UnsignedRightShiftUpdate, AndUpdate, OrUpdate, XorUpdate,
|
||||
Not, Negative, HeapAllocate, StackReference,
|
||||
Min, Max,
|
||||
Array, Set, Table, TableEntry, Comprehension,
|
||||
@ -152,9 +158,7 @@ struct ast_s {
|
||||
const char *start, *end;
|
||||
union {
|
||||
struct {} Unknown;
|
||||
struct {
|
||||
type_ast_t *type;
|
||||
} None;
|
||||
struct {} None;
|
||||
struct {
|
||||
bool b;
|
||||
} Bool;
|
||||
@ -182,16 +186,17 @@ struct ast_s {
|
||||
} PrintStatement;
|
||||
struct {
|
||||
ast_t *var;
|
||||
type_ast_t *type;
|
||||
ast_t *value;
|
||||
} Declare;
|
||||
struct {
|
||||
ast_list_t *targets, *values;
|
||||
} Assign;
|
||||
struct {
|
||||
ast_t *lhs;
|
||||
binop_e op;
|
||||
ast_t *rhs;
|
||||
} BinaryOp, UpdateAssign;
|
||||
binary_operands_t Power, Multiply, Divide, Mod, Mod1, Plus, Minus, Concat, LeftShift, UnsignedLeftShift,
|
||||
RightShift, UnsignedRightShift, Equals, NotEquals, LessThan, LessThanOrEquals, GreaterThan,
|
||||
GreaterThanOrEquals, Compare, And, Or, Xor,
|
||||
PowerUpdate, MultiplyUpdate, DivideUpdate, ModUpdate, Mod1Update, PlusUpdate, MinusUpdate, ConcatUpdate, LeftShiftUpdate, UnsignedLeftShiftUpdate,
|
||||
RightShiftUpdate, UnsignedRightShiftUpdate, AndUpdate, OrUpdate, XorUpdate;
|
||||
struct {
|
||||
ast_t *value;
|
||||
} Not, Negative, HeapAllocate, StackReference;
|
||||
@ -199,15 +204,12 @@ struct ast_s {
|
||||
ast_t *lhs, *rhs, *key;
|
||||
} Min, Max;
|
||||
struct {
|
||||
type_ast_t *item_type;
|
||||
ast_list_t *items;
|
||||
} Array;
|
||||
struct {
|
||||
type_ast_t *item_type;
|
||||
ast_list_t *items;
|
||||
} Set;
|
||||
struct {
|
||||
type_ast_t *key_type, *value_type;
|
||||
ast_t *default_value;
|
||||
ast_t *fallback;
|
||||
ast_list_t *entries;
|
||||
@ -272,7 +274,7 @@ struct ast_s {
|
||||
} When;
|
||||
struct {
|
||||
ast_t *iter, *key;
|
||||
binop_e op;
|
||||
ast_e op;
|
||||
} Reduction;
|
||||
struct {
|
||||
const char *target;
|
||||
@ -345,5 +347,10 @@ const char *ast_source(ast_t *ast);
|
||||
CORD type_ast_to_xml(type_ast_t *ast);
|
||||
PUREFUNC bool is_idempotent(ast_t *ast);
|
||||
void visit_topologically(ast_list_t *ast, Closure_t fn);
|
||||
CONSTFUNC bool is_update_assignment(ast_t *ast);
|
||||
CONSTFUNC const char *binop_method_name(ast_e tag);
|
||||
CONSTFUNC const char *binop_operator(ast_e tag);
|
||||
CONSTFUNC ast_e binop_tag(ast_e tag);
|
||||
CONSTFUNC bool is_binary_operation(ast_t *ast);
|
||||
|
||||
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0
|
||||
|
1370
src/compile.c
1370
src/compile.c
File diff suppressed because it is too large
Load Diff
@ -122,7 +122,7 @@ env_t *global_env(void)
|
||||
{"right_shifted", "Int$right_shifted", "func(x,y:Int -> Int)"},
|
||||
{"sqrt", "Int$sqrt", "func(x:Int -> Int?)"},
|
||||
{"times", "Int$times", "func(x,y:Int -> Int)"},
|
||||
{"to", "Int$to", "func(first:Int,last:Int,step=none:Int -> func(->Int?))"},
|
||||
{"to", "Int$to", "func(first:Int,last:Int,step:Int?=none -> func(->Int?))"},
|
||||
)},
|
||||
{"Int64", Type(IntType, .bits=TYPE_IBITS64), "Int64_t", "Int64$info", TypedArray(ns_entry_t,
|
||||
{"abs", "labs", "func(i:Int64 -> Int64)"},
|
||||
@ -139,7 +139,7 @@ env_t *global_env(void)
|
||||
{"modulo1", "Int64$modulo1", "func(x,y:Int64 -> Int64)"},
|
||||
{"octal", "Int64$octal", "func(i:Int64, digits=0, prefix=yes -> Text)"},
|
||||
{"onward", "Int64$onward", "func(first:Int64,step=Int64(1) -> func(->Int64?))"},
|
||||
{"to", "Int64$to", "func(first:Int64,last:Int64,step=none:Int64 -> func(->Int64?))"},
|
||||
{"to", "Int64$to", "func(first:Int64,last:Int64,step:Int64?=none -> func(->Int64?))"},
|
||||
{"unsigned_left_shifted", "Int64$unsigned_left_shifted", "func(x:Int64,y:Int64 -> Int64)"},
|
||||
{"unsigned_right_shifted", "Int64$unsigned_right_shifted", "func(x:Int64,y:Int64 -> Int64)"},
|
||||
{"wrapping_minus", "Int64$wrapping_minus", "func(x:Int64,y:Int64 -> Int64)"},
|
||||
@ -160,7 +160,7 @@ env_t *global_env(void)
|
||||
{"modulo1", "Int32$modulo1", "func(x,y:Int32 -> Int32)"},
|
||||
{"octal", "Int32$octal", "func(i:Int32, digits=0, prefix=yes -> Text)"},
|
||||
{"onward", "Int32$onward", "func(first:Int32,step=Int32(1) -> func(->Int32?))"},
|
||||
{"to", "Int32$to", "func(first:Int32,last:Int32,step=none:Int32 -> func(->Int32?))"},
|
||||
{"to", "Int32$to", "func(first:Int32,last:Int32,step:Int32?=none -> func(->Int32?))"},
|
||||
{"unsigned_left_shifted", "Int32$unsigned_left_shifted", "func(x:Int32,y:Int32 -> Int32)"},
|
||||
{"unsigned_right_shifted", "Int32$unsigned_right_shifted", "func(x:Int32,y:Int32 -> Int32)"},
|
||||
{"wrapping_minus", "Int32$wrapping_minus", "func(x:Int32,y:Int32 -> Int32)"},
|
||||
@ -181,7 +181,7 @@ env_t *global_env(void)
|
||||
{"modulo1", "Int16$modulo1", "func(x,y:Int16 -> Int16)"},
|
||||
{"octal", "Int16$octal", "func(i:Int16, digits=0, prefix=yes -> Text)"},
|
||||
{"onward", "Int16$onward", "func(first:Int16,step=Int16(1) -> func(->Int16?))"},
|
||||
{"to", "Int16$to", "func(first:Int16,last:Int16,step=none:Int16 -> func(->Int16?))"},
|
||||
{"to", "Int16$to", "func(first:Int16,last:Int16,step:Int16?=none -> func(->Int16?))"},
|
||||
{"unsigned_left_shifted", "Int16$unsigned_left_shifted", "func(x:Int16,y:Int16 -> Int16)"},
|
||||
{"unsigned_right_shifted", "Int16$unsigned_right_shifted", "func(x:Int16,y:Int16 -> Int16)"},
|
||||
{"wrapping_minus", "Int16$wrapping_minus", "func(x:Int16,y:Int16 -> Int16)"},
|
||||
@ -202,7 +202,7 @@ env_t *global_env(void)
|
||||
{"modulo1", "Int8$modulo1", "func(x,y:Int8 -> Int8)"},
|
||||
{"octal", "Int8$octal", "func(i:Int8, digits=0, prefix=yes -> Text)"},
|
||||
{"onward", "Int8$onward", "func(first:Int8,step=Int8(1) -> func(->Int8?))"},
|
||||
{"to", "Int8$to", "func(first:Int8,last:Int8,step=none:Int8 -> func(->Int8?))"},
|
||||
{"to", "Int8$to", "func(first:Int8,last:Int8,step:Int8?=none -> func(->Int8?))"},
|
||||
{"unsigned_left_shifted", "Int8$unsigned_left_shifted", "func(x:Int8,y:Int8 -> Int8)"},
|
||||
{"unsigned_right_shifted", "Int8$unsigned_right_shifted", "func(x:Int8,y:Int8 -> Int8)"},
|
||||
{"wrapping_minus", "Int8$wrapping_minus", "func(x:Int8,y:Int8 -> Int8)"},
|
||||
@ -310,11 +310,11 @@ env_t *global_env(void)
|
||||
{"owner", "Path$owner", "func(path:Path, follow_symlinks=yes -> Text?)"},
|
||||
{"parent", "Path$parent", "func(path:Path -> Path)"},
|
||||
{"read", "Path$read", "func(path:Path -> Text?)"},
|
||||
{"read_bytes", "Path$read_bytes", "func(path:Path, limit=none:Int -> [Byte]?)"},
|
||||
{"read_bytes", "Path$read_bytes", "func(path:Path, limit:Int?=none -> [Byte]?)"},
|
||||
{"relative_to", "Path$relative_to", "func(path:Path, relative_to:Path -> Path)"},
|
||||
{"remove", "Path$remove", "func(path:Path, ignore_missing=no)"},
|
||||
{"resolved", "Path$resolved", "func(path:Path, relative_to=(./) -> Path)"},
|
||||
{"set_owner", "Path$set_owner", "func(path:Path, owner=none:Text, group=none:Text, follow_symlinks=yes)"},
|
||||
{"set_owner", "Path$set_owner", "func(path:Path, owner:Text?=none, group:Text?=none, follow_symlinks=yes)"},
|
||||
{"subdirectories", "Path$children", "func(path:Path, include_hidden=no -> [Path])"},
|
||||
{"unique_directory", "Path$unique_directory", "func(path:Path -> Path)"},
|
||||
{"write", "Path$write", "func(path:Path, text:Text, permissions=Int32(0o644))"},
|
||||
@ -508,7 +508,7 @@ env_t *global_env(void)
|
||||
{"say", "say", "func(text:Text, newline=yes)"},
|
||||
{"print", "say", "func(text:Text, newline=yes)"},
|
||||
{"ask", "ask", "func(prompt:Text, bold=yes, force_tty=yes -> Text?)"},
|
||||
{"exit", "tomo_exit", "func(message=none:Text, code=Int32(1) -> Abort)"},
|
||||
{"exit", "tomo_exit", "func(message:Text?=none, code=Int32(1) -> Abort)"},
|
||||
{"fail", "fail_text", "func(message:Text -> Abort)"},
|
||||
{"sleep", "sleep_num", "func(seconds:Num)"},
|
||||
};
|
||||
@ -749,6 +749,18 @@ PUREFUNC binding_t *get_constructor(env_t *env, type_t *t, arg_ast_t *args)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PUREFUNC binding_t *get_metamethod_binding(env_t *env, ast_e tag, ast_t *lhs, ast_t *rhs, type_t *ret)
|
||||
{
|
||||
const char *method_name = binop_method_name(tag);
|
||||
if (!method_name) return NULL;
|
||||
binding_t *b = get_namespace_binding(env, lhs, method_name);
|
||||
if (!b || b->type->tag != FunctionType) return NULL;
|
||||
auto fn = Match(b->type, FunctionType);
|
||||
if (!type_eq(fn->ret, ret)) return NULL;
|
||||
arg_ast_t *args = new(arg_ast_t, .value=lhs, .next=new(arg_ast_t, .value=rhs));
|
||||
return is_valid_call(env, fn->args, args, true) ? b : NULL;
|
||||
}
|
||||
|
||||
void set_binding(env_t *env, const char *name, type_t *type, CORD code)
|
||||
{
|
||||
assert(name);
|
||||
|
@ -85,6 +85,7 @@ env_t *namespace_env(env_t *env, const char *namespace_name);
|
||||
})
|
||||
binding_t *get_binding(env_t *env, const char *name);
|
||||
binding_t *get_constructor(env_t *env, type_t *t, arg_ast_t *args);
|
||||
PUREFUNC binding_t *get_metamethod_binding(env_t *env, ast_e tag, ast_t *lhs, ast_t *rhs, type_t *ret);
|
||||
void set_binding(env_t *env, const char *name, type_t *type, CORD code);
|
||||
binding_t *get_namespace_binding(env_t *env, ast_t *self, const char *name);
|
||||
#define code_err(ast, ...) compiler_err((ast)->file, (ast)->start, (ast)->end, __VA_ARGS__)
|
||||
|
180
src/parse.c
180
src/parse.c
@ -48,16 +48,16 @@ typedef struct {
|
||||
#define PARSER(name) ast_t *name(parse_ctx_t *ctx, const char *pos)
|
||||
|
||||
int op_tightness[] = {
|
||||
[BINOP_POWER]=9,
|
||||
[BINOP_MULT]=8, [BINOP_DIVIDE]=8, [BINOP_MOD]=8, [BINOP_MOD1]=8,
|
||||
[BINOP_PLUS]=7, [BINOP_MINUS]=7,
|
||||
[BINOP_CONCAT]=6,
|
||||
[BINOP_LSHIFT]=5, [BINOP_RSHIFT]=5,
|
||||
[BINOP_MIN]=4, [BINOP_MAX]=4,
|
||||
[BINOP_EQ]=3, [BINOP_NE]=3,
|
||||
[BINOP_LT]=2, [BINOP_LE]=2, [BINOP_GT]=2, [BINOP_GE]=2,
|
||||
[BINOP_CMP]=2,
|
||||
[BINOP_AND]=1, [BINOP_OR]=1, [BINOP_XOR]=1,
|
||||
[Power]=9,
|
||||
[Multiply]=8, [Divide]=8, [Mod]=8, [Mod1]=8,
|
||||
[Plus]=7, [Minus]=7,
|
||||
[Concat]=6,
|
||||
[LeftShift]=5, [RightShift]=5, [UnsignedLeftShift]=5, [UnsignedRightShift]=5,
|
||||
[Min]=4, [Max]=4,
|
||||
[Equals]=3, [NotEquals]=3,
|
||||
[LessThan]=2, [LessThanOrEquals]=2, [GreaterThan]=2, [GreaterThanOrEquals]=2,
|
||||
[Compare]=2,
|
||||
[And]=1, [Or]=1, [Xor]=1,
|
||||
};
|
||||
|
||||
static const char *keywords[] = {
|
||||
@ -79,7 +79,7 @@ static INLINE const char* get_word(const char **pos);
|
||||
static INLINE const char* get_id(const char **pos);
|
||||
static INLINE bool comment(const char **pos);
|
||||
static INLINE bool indent(parse_ctx_t *ctx, const char **pos);
|
||||
static INLINE binop_e match_binary_operator(const char **pos);
|
||||
static INLINE ast_e match_binary_operator(const char **pos);
|
||||
static ast_t *parse_comprehension_suffix(parse_ctx_t *ctx, ast_t *expr);
|
||||
static ast_t *parse_field_suffix(parse_ctx_t *ctx, ast_t *lhs);
|
||||
static ast_t *parse_fncall_suffix(parse_ctx_t *ctx, ast_t *fn);
|
||||
@ -685,15 +685,6 @@ PARSER(parse_array) {
|
||||
whitespace(&pos);
|
||||
|
||||
ast_list_t *items = NULL;
|
||||
type_ast_t *item_type = NULL;
|
||||
if (match(&pos, ":")) {
|
||||
whitespace(&pos);
|
||||
item_type = expect(ctx, pos-1, &pos, parse_type, "I couldn't parse a type for this array");
|
||||
whitespace(&pos);
|
||||
match(&pos, ",");
|
||||
whitespace(&pos);
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
ast_t *item = optional(ctx, &pos, parse_extended_expr);
|
||||
if (!item) break;
|
||||
@ -711,7 +702,7 @@ PARSER(parse_array) {
|
||||
expect_closing(ctx, &pos, "]", "I wasn't able to parse the rest of this array");
|
||||
|
||||
REVERSE_LIST(items);
|
||||
return NewAST(ctx->file, start, pos, Array, .item_type=item_type, .items=items);
|
||||
return NewAST(ctx->file, start, pos, Array, .items=items);
|
||||
}
|
||||
|
||||
PARSER(parse_table) {
|
||||
@ -722,20 +713,6 @@ PARSER(parse_table) {
|
||||
whitespace(&pos);
|
||||
|
||||
ast_list_t *entries = NULL;
|
||||
type_ast_t *key_type = NULL, *value_type = NULL;
|
||||
if (match(&pos, ":")) {
|
||||
whitespace(&pos);
|
||||
key_type = expect(ctx, pos-1, &pos, parse_type, "I couldn't parse a key type for this table");
|
||||
whitespace(&pos);
|
||||
if (match(&pos, "=")) {
|
||||
value_type = expect(ctx, pos-1, &pos, parse_type, "I couldn't parse the value type for this table");
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
whitespace(&pos);
|
||||
match(&pos, ",");
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
const char *entry_start = pos;
|
||||
ast_t *key = optional(ctx, &pos, parse_extended_expr);
|
||||
@ -787,8 +764,7 @@ PARSER(parse_table) {
|
||||
whitespace(&pos);
|
||||
expect_closing(ctx, &pos, "}", "I wasn't able to parse the rest of this table");
|
||||
|
||||
return NewAST(ctx->file, start, pos, Table, .key_type=key_type, .value_type=value_type,
|
||||
.default_value=default_value, .entries=entries, .fallback=fallback);
|
||||
return NewAST(ctx->file, start, pos, Table, .default_value=default_value, .entries=entries, .fallback=fallback);
|
||||
}
|
||||
|
||||
PARSER(parse_set) {
|
||||
@ -801,18 +777,6 @@ PARSER(parse_set) {
|
||||
whitespace(&pos);
|
||||
|
||||
ast_list_t *items = NULL;
|
||||
type_ast_t *item_type = NULL;
|
||||
if (match(&pos, ":")) {
|
||||
whitespace(&pos);
|
||||
item_type = expect(ctx, pos-1, &pos, parse_type, "I couldn't parse a key type for this set");
|
||||
whitespace(&pos);
|
||||
if (match(&pos, ","))
|
||||
return NULL;
|
||||
whitespace(&pos);
|
||||
match(&pos, ",");
|
||||
whitespace(&pos);
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
ast_t *item = optional(ctx, &pos, parse_extended_expr);
|
||||
if (!item) break;
|
||||
@ -834,7 +798,7 @@ PARSER(parse_set) {
|
||||
whitespace(&pos);
|
||||
expect_closing(ctx, &pos, "}", "I wasn't able to parse the rest of this set");
|
||||
|
||||
return NewAST(ctx->file, start, pos, Set, .item_type=item_type, .items=items);
|
||||
return NewAST(ctx->file, start, pos, Set, .items=items);
|
||||
}
|
||||
|
||||
ast_t *parse_field_suffix(parse_ctx_t *ctx, ast_t *lhs) {
|
||||
@ -874,11 +838,11 @@ PARSER(parse_reduction) {
|
||||
if (!match(&pos, "(")) return NULL;
|
||||
|
||||
whitespace(&pos);
|
||||
binop_e op = match_binary_operator(&pos);
|
||||
if (op == BINOP_UNKNOWN) return NULL;
|
||||
ast_e op = match_binary_operator(&pos);
|
||||
if (op == Unknown) return NULL;
|
||||
|
||||
ast_t *key = NULL;
|
||||
if (op == BINOP_MIN || op == BINOP_MAX) {
|
||||
if (op == Min || op == Max) {
|
||||
key = NewAST(ctx->file, pos, pos, Var, .name="$");
|
||||
for (bool progress = true; progress; ) {
|
||||
ast_t *new_term;
|
||||
@ -1425,16 +1389,7 @@ PARSER(parse_none) {
|
||||
const char *start = pos;
|
||||
if (!match_word(&pos, "none"))
|
||||
return NULL;
|
||||
|
||||
const char *none_end = pos;
|
||||
spaces(&pos);
|
||||
if (!match(&pos, ":"))
|
||||
return NewAST(ctx->file, start, none_end, None, .type=NULL);
|
||||
|
||||
spaces(&pos);
|
||||
type_ast_t *type = parse_type(ctx, pos);
|
||||
if (!type) return NULL;
|
||||
return NewAST(ctx->file, start, type->end, None, .type=type);
|
||||
return NewAST(ctx->file, start, pos, None);
|
||||
}
|
||||
|
||||
PARSER(parse_deserialize) {
|
||||
@ -1602,53 +1557,53 @@ ast_t *parse_fncall_suffix(parse_ctx_t *ctx, ast_t *fn) {
|
||||
return NewAST(ctx->file, start, pos, FunctionCall, .fn=fn, .args=args);
|
||||
}
|
||||
|
||||
binop_e match_binary_operator(const char **pos)
|
||||
ast_e match_binary_operator(const char **pos)
|
||||
{
|
||||
switch (**pos) {
|
||||
case '+': {
|
||||
*pos += 1;
|
||||
return match(pos, "+") ? BINOP_CONCAT : BINOP_PLUS;
|
||||
return match(pos, "+") ? Concat : Plus;
|
||||
}
|
||||
case '-': {
|
||||
*pos += 1;
|
||||
if ((*pos)[0] != ' ' && (*pos)[-2] == ' ') // looks like `fn -5`
|
||||
return BINOP_UNKNOWN;
|
||||
return BINOP_MINUS;
|
||||
return Unknown;
|
||||
return Minus;
|
||||
}
|
||||
case '*': *pos += 1; return BINOP_MULT;
|
||||
case '/': *pos += 1; return BINOP_DIVIDE;
|
||||
case '^': *pos += 1; return BINOP_POWER;
|
||||
case '*': *pos += 1; return Multiply;
|
||||
case '/': *pos += 1; return Divide;
|
||||
case '^': *pos += 1; return Power;
|
||||
case '<': {
|
||||
*pos += 1;
|
||||
if (match(pos, "=")) return BINOP_LE; // "<="
|
||||
else if (match(pos, ">")) return BINOP_CMP; // "<>"
|
||||
if (match(pos, "=")) return LessThanOrEquals; // "<="
|
||||
else if (match(pos, ">")) return Compare; // "<>"
|
||||
else if (match(pos, "<")) {
|
||||
if (match(pos, "<"))
|
||||
return BINOP_ULSHIFT; // "<<<"
|
||||
return BINOP_LSHIFT; // "<<"
|
||||
} else return BINOP_LT;
|
||||
return UnsignedLeftShift; // "<<<"
|
||||
return LeftShift; // "<<"
|
||||
} else return LessThan;
|
||||
}
|
||||
case '>': {
|
||||
*pos += 1;
|
||||
if (match(pos, "=")) return BINOP_GE; // ">="
|
||||
if (match(pos, "=")) return GreaterThanOrEquals; // ">="
|
||||
if (match(pos, ">")) {
|
||||
if (match(pos, ">"))
|
||||
return BINOP_URSHIFT; // ">>>"
|
||||
return BINOP_RSHIFT; // ">>"
|
||||
return UnsignedRightShift; // ">>>"
|
||||
return RightShift; // ">>"
|
||||
}
|
||||
return BINOP_GT;
|
||||
return GreaterThan;
|
||||
}
|
||||
default: {
|
||||
if (match(pos, "!=")) return BINOP_NE;
|
||||
else if (match(pos, "==") && **pos != '=') return BINOP_EQ;
|
||||
else if (match_word(pos, "and")) return BINOP_AND;
|
||||
else if (match_word(pos, "or")) return BINOP_OR;
|
||||
else if (match_word(pos, "xor")) return BINOP_XOR;
|
||||
else if (match_word(pos, "mod1")) return BINOP_MOD1;
|
||||
else if (match_word(pos, "mod")) return BINOP_MOD;
|
||||
else if (match_word(pos, "_min_")) return BINOP_MIN;
|
||||
else if (match_word(pos, "_max_")) return BINOP_MAX;
|
||||
else return BINOP_UNKNOWN;
|
||||
if (match(pos, "!=")) return NotEquals;
|
||||
else if (match(pos, "==") && **pos != '=') return Equals;
|
||||
else if (match_word(pos, "and")) return And;
|
||||
else if (match_word(pos, "or")) return Or;
|
||||
else if (match_word(pos, "xor")) return Xor;
|
||||
else if (match_word(pos, "mod1")) return Mod1;
|
||||
else if (match_word(pos, "mod")) return Mod;
|
||||
else if (match_word(pos, "_min_")) return Min;
|
||||
else if (match_word(pos, "_max_")) return Max;
|
||||
else return Unknown;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1660,9 +1615,9 @@ static ast_t *parse_infix_expr(parse_ctx_t *ctx, const char *pos, int min_tightn
|
||||
int64_t starting_line = get_line_number(ctx->file, pos);
|
||||
int64_t starting_indent = get_indent(ctx, pos);
|
||||
spaces(&pos);
|
||||
for (binop_e op; (op=match_binary_operator(&pos)) != BINOP_UNKNOWN && op_tightness[op] >= min_tightness; spaces(&pos)) {
|
||||
for (ast_e op; (op=match_binary_operator(&pos)) != Unknown && op_tightness[op] >= min_tightness; spaces(&pos)) {
|
||||
ast_t *key = NULL;
|
||||
if (op == BINOP_MIN || op == BINOP_MAX) {
|
||||
if (op == Min || op == Max) {
|
||||
key = NewAST(ctx->file, pos, pos, Var, .name="$");
|
||||
for (bool progress = true; progress; ) {
|
||||
ast_t *new_term;
|
||||
@ -1688,12 +1643,12 @@ static ast_t *parse_infix_expr(parse_ctx_t *ctx, const char *pos, int min_tightn
|
||||
if (!rhs) break;
|
||||
pos = rhs->end;
|
||||
|
||||
if (op == BINOP_MIN) {
|
||||
if (op == Min) {
|
||||
return NewAST(ctx->file, lhs->start, rhs->end, Min, .lhs=lhs, .rhs=rhs, .key=key);
|
||||
} else if (op == BINOP_MAX) {
|
||||
} else if (op == Max) {
|
||||
return NewAST(ctx->file, lhs->start, rhs->end, Max, .lhs=lhs, .rhs=rhs, .key=key);
|
||||
} else {
|
||||
lhs = NewAST(ctx->file, lhs->start, rhs->end, BinaryOp, .lhs=lhs, .op=op, .rhs=rhs);
|
||||
lhs = new(ast_t, .file=ctx->file, .start=lhs->start, .end=rhs->end, .tag=op, .__data.Plus.lhs=lhs, .__data.Plus.rhs=rhs);
|
||||
}
|
||||
}
|
||||
return lhs;
|
||||
@ -1709,8 +1664,11 @@ PARSER(parse_declaration) {
|
||||
if (!var) return NULL;
|
||||
pos = var->end;
|
||||
spaces(&pos);
|
||||
if (!match(&pos, ":=")) return NULL;
|
||||
if (!match(&pos, ":")) return NULL;
|
||||
spaces(&pos);
|
||||
type_ast_t *type = optional(ctx, &pos, parse_type);
|
||||
spaces(&pos);
|
||||
if (!match(&pos, "=")) return NULL;
|
||||
ast_t *val = optional(ctx, &pos, parse_extended_expr);
|
||||
if (!val) {
|
||||
if (optional(ctx, &pos, parse_use))
|
||||
@ -1718,7 +1676,7 @@ PARSER(parse_declaration) {
|
||||
else
|
||||
parser_err(ctx, pos, eol(pos), "This is not a valid expression");
|
||||
}
|
||||
return NewAST(ctx->file, start, pos, Declare, .var=var, .value=val);
|
||||
return NewAST(ctx->file, start, pos, Declare, .var=var, .type=type, .value=val);
|
||||
}
|
||||
|
||||
PARSER(parse_update) {
|
||||
@ -1726,23 +1684,23 @@ PARSER(parse_update) {
|
||||
ast_t *lhs = optional(ctx, &pos, parse_expr);
|
||||
if (!lhs) return NULL;
|
||||
spaces(&pos);
|
||||
binop_e op;
|
||||
if (match(&pos, "+=")) op = BINOP_PLUS;
|
||||
else if (match(&pos, "++=")) op = BINOP_CONCAT;
|
||||
else if (match(&pos, "-=")) op = BINOP_MINUS;
|
||||
else if (match(&pos, "*=")) op = BINOP_MULT;
|
||||
else if (match(&pos, "/=")) op = BINOP_DIVIDE;
|
||||
else if (match(&pos, "^=")) op = BINOP_POWER;
|
||||
else if (match(&pos, "<<=")) op = BINOP_LSHIFT;
|
||||
else if (match(&pos, "<<<=")) op = BINOP_ULSHIFT;
|
||||
else if (match(&pos, ">>=")) op = BINOP_RSHIFT;
|
||||
else if (match(&pos, ">>>=")) op = BINOP_URSHIFT;
|
||||
else if (match(&pos, "and=")) op = BINOP_AND;
|
||||
else if (match(&pos, "or=")) op = BINOP_OR;
|
||||
else if (match(&pos, "xor=")) op = BINOP_XOR;
|
||||
ast_e op;
|
||||
if (match(&pos, "+=")) op = PlusUpdate;
|
||||
else if (match(&pos, "++=")) op = ConcatUpdate;
|
||||
else if (match(&pos, "-=")) op = MinusUpdate;
|
||||
else if (match(&pos, "*=")) op = MultiplyUpdate;
|
||||
else if (match(&pos, "/=")) op = DivideUpdate;
|
||||
else if (match(&pos, "^=")) op = PowerUpdate;
|
||||
else if (match(&pos, "<<=")) op = LeftShiftUpdate;
|
||||
else if (match(&pos, "<<<=")) op = UnsignedLeftShiftUpdate;
|
||||
else if (match(&pos, ">>=")) op = RightShiftUpdate;
|
||||
else if (match(&pos, ">>>=")) op = UnsignedRightShiftUpdate;
|
||||
else if (match(&pos, "and=")) op = AndUpdate;
|
||||
else if (match(&pos, "or=")) op = OrUpdate;
|
||||
else if (match(&pos, "xor=")) op = XorUpdate;
|
||||
else return NULL;
|
||||
ast_t *rhs = expect(ctx, start, &pos, parse_extended_expr, "I expected an expression here");
|
||||
return NewAST(ctx->file, start, pos, UpdateAssign, .lhs=lhs, .rhs=rhs, .op=op);
|
||||
return new(ast_t, .file=ctx->file, .start=start, .end=pos, .tag=op, .__data.PlusUpdate.lhs=lhs, .__data.PlusUpdate.rhs=rhs);
|
||||
}
|
||||
|
||||
PARSER(parse_assignment) {
|
||||
|
120
src/repl.c
120
src/repl.c
@ -186,31 +186,31 @@ static Int_t ast_to_int(env_t *env, ast_t *ast)
|
||||
}
|
||||
}
|
||||
|
||||
static double ast_to_num(env_t *env, ast_t *ast)
|
||||
{
|
||||
type_t *t = get_type(env, ast);
|
||||
switch (t->tag) {
|
||||
case BigIntType: case IntType: {
|
||||
number_t num;
|
||||
eval(env, ast, &num);
|
||||
if (t->tag == BigIntType)
|
||||
return Num$from_int(num.integer, false);
|
||||
switch (Match(t, IntType)->bits) {
|
||||
case TYPE_IBITS64: return Num$from_int64(num.i64, false);
|
||||
case TYPE_IBITS32: return Num$from_int32(num.i32);
|
||||
case TYPE_IBITS16: return Num$from_int16(num.i16);
|
||||
case TYPE_IBITS8: return Num$from_int8(num.i8);
|
||||
default: print_err("Invalid int bits");
|
||||
}
|
||||
}
|
||||
case NumType: {
|
||||
number_t num;
|
||||
eval(env, ast, &num);
|
||||
return Match(t, NumType)->bits == TYPE_NBITS32 ? (double)num.n32 : (double)num.n64;
|
||||
}
|
||||
default: print_err("Cannot convert to number");
|
||||
}
|
||||
}
|
||||
// static double ast_to_num(env_t *env, ast_t *ast)
|
||||
// {
|
||||
// type_t *t = get_type(env, ast);
|
||||
// switch (t->tag) {
|
||||
// case BigIntType: case IntType: {
|
||||
// number_t num;
|
||||
// eval(env, ast, &num);
|
||||
// if (t->tag == BigIntType)
|
||||
// return Num$from_int(num.integer, false);
|
||||
// switch (Match(t, IntType)->bits) {
|
||||
// case TYPE_IBITS64: return Num$from_int64(num.i64, false);
|
||||
// case TYPE_IBITS32: return Num$from_int32(num.i32);
|
||||
// case TYPE_IBITS16: return Num$from_int16(num.i16);
|
||||
// case TYPE_IBITS8: return Num$from_int8(num.i8);
|
||||
// default: print_err("Invalid int bits");
|
||||
// }
|
||||
// }
|
||||
// case NumType: {
|
||||
// number_t num;
|
||||
// eval(env, ast, &num);
|
||||
// return Match(t, NumType)->bits == TYPE_NBITS32 ? (double)num.n32 : (double)num.n64;
|
||||
// }
|
||||
// default: print_err("Cannot convert to number");
|
||||
// }
|
||||
// }
|
||||
|
||||
static Text_t obj_to_text(type_t *t, const void *obj, bool use_color)
|
||||
{
|
||||
@ -386,76 +386,6 @@ void eval(env_t *env, ast_t *ast, void *dest)
|
||||
if (dest) *(CORD*)dest = ret;
|
||||
break;
|
||||
}
|
||||
case BinaryOp: {
|
||||
auto binop = Match(ast, BinaryOp);
|
||||
if (t->tag == IntType || t->tag == BigIntType) {
|
||||
#define CASE_OP(OP_NAME, method_name) case BINOP_##OP_NAME: {\
|
||||
Int_t lhs = ast_to_int(env, binop->lhs); \
|
||||
Int_t rhs = ast_to_int(env, binop->rhs); \
|
||||
Int_t result = Int$ ## method_name (lhs, rhs); \
|
||||
if (t->tag == BigIntType) {\
|
||||
*(Int_t*)dest = result; \
|
||||
return; \
|
||||
} \
|
||||
switch (Match(t, IntType)->bits) { \
|
||||
case 64: *(int64_t*)dest = Int64$from_int(result, false); return; \
|
||||
case 32: *(int32_t*)dest = Int32$from_int(result, false); return; \
|
||||
case 16: *(int16_t*)dest = Int16$from_int(result, false); return; \
|
||||
case 8: *(int8_t*)dest = Int8$from_int(result, false); return; \
|
||||
default: print_err("Invalid int bits"); \
|
||||
} \
|
||||
break; \
|
||||
}
|
||||
switch (binop->op) {
|
||||
CASE_OP(MULT, times) CASE_OP(DIVIDE, divided_by) CASE_OP(PLUS, plus) CASE_OP(MINUS, minus)
|
||||
CASE_OP(RSHIFT, right_shifted) CASE_OP(LSHIFT, left_shifted)
|
||||
CASE_OP(MOD, modulo) CASE_OP(MOD1, modulo1)
|
||||
CASE_OP(AND, bit_and) CASE_OP(OR, bit_or) CASE_OP(XOR, bit_xor)
|
||||
default: break;
|
||||
}
|
||||
#undef CASE_OP
|
||||
} else if (t->tag == NumType) {
|
||||
#define CASE_OP(OP_NAME, C_OP) case BINOP_##OP_NAME: {\
|
||||
double lhs = ast_to_num(env, binop->lhs); \
|
||||
double rhs = ast_to_num(env, binop->rhs); \
|
||||
if (Match(t, NumType)->bits == 64) \
|
||||
*(double*)dest = (double)(lhs C_OP rhs); \
|
||||
else \
|
||||
*(float*)dest = (float)(lhs C_OP rhs); \
|
||||
return; \
|
||||
}
|
||||
switch (binop->op) {
|
||||
CASE_OP(MULT, *) CASE_OP(DIVIDE, /) CASE_OP(PLUS, +) CASE_OP(MINUS, -)
|
||||
default: break;
|
||||
}
|
||||
#undef CASE_OP
|
||||
}
|
||||
switch (binop->op) {
|
||||
case BINOP_EQ: case BINOP_NE: case BINOP_LT: case BINOP_LE: case BINOP_GT: case BINOP_GE: {
|
||||
type_t *t_lhs = get_type(env, binop->lhs);
|
||||
if (!type_eq(t_lhs, get_type(env, binop->rhs)))
|
||||
print_err("Comparisons between different types aren't supported");
|
||||
const TypeInfo_t *info = type_to_type_info(t_lhs);
|
||||
size_t value_size = type_size(t_lhs);
|
||||
char lhs[value_size], rhs[value_size];
|
||||
eval(env, binop->lhs, lhs);
|
||||
eval(env, binop->rhs, rhs);
|
||||
int cmp = generic_compare(lhs, rhs, info);
|
||||
switch (binop->op) {
|
||||
case BINOP_EQ: *(bool*)dest = (cmp == 0); break;
|
||||
case BINOP_NE: *(bool*)dest = (cmp != 0); break;
|
||||
case BINOP_GT: *(bool*)dest = (cmp > 0); break;
|
||||
case BINOP_GE: *(bool*)dest = (cmp >= 0); break;
|
||||
case BINOP_LT: *(bool*)dest = (cmp < 0); break;
|
||||
case BINOP_LE: *(bool*)dest = (cmp <= 0); break;
|
||||
default: break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: print_err(1, "Binary op not implemented for ", type_to_str(t), ": ", ast_to_xml_str(ast));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Index: {
|
||||
auto index = Match(ast, Index);
|
||||
type_t *indexed_t = get_type(env, index->indexed);
|
||||
|
651
src/typecheck.c
651
src/typecheck.c
@ -108,12 +108,13 @@ type_t *parse_type_ast(env_t *env, type_ast_t *ast)
|
||||
arg_t *type_args = NULL;
|
||||
for (arg_ast_t *arg = fn->args; arg; arg = arg->next) {
|
||||
type_args = new(arg_t, .name=arg->name, .next=type_args);
|
||||
if (arg->type) {
|
||||
if (arg->type)
|
||||
type_args->type = parse_type_ast(env, arg->type);
|
||||
} else {
|
||||
type_args->default_val = arg->value;
|
||||
else if (arg->value)
|
||||
type_args->type = get_type(env, arg->value);
|
||||
}
|
||||
|
||||
if (arg->value)
|
||||
type_args->default_val = arg->value;
|
||||
}
|
||||
REVERSE_LIST(type_args);
|
||||
return Type(ClosureType, Type(FunctionType, .args=type_args, .ret=ret_t));
|
||||
@ -135,27 +136,27 @@ type_t *parse_type_ast(env_t *env, type_ast_t *ast)
|
||||
errx(1, "Unreachable");
|
||||
}
|
||||
|
||||
static PUREFUNC bool risks_zero_or_inf(ast_t *ast)
|
||||
{
|
||||
switch (ast->tag) {
|
||||
case Int: {
|
||||
const char *str = Match(ast, Int)->str;
|
||||
OptionalInt_t int_val = Int$from_str(str);
|
||||
return (int_val.small == 0x1); // zero
|
||||
}
|
||||
case Num: {
|
||||
return Match(ast, Num)->n == 0.0;
|
||||
}
|
||||
case BinaryOp: {
|
||||
auto binop = Match(ast, BinaryOp);
|
||||
if (binop->op == BINOP_MULT || binop->op == BINOP_DIVIDE || binop->op == BINOP_MIN || binop->op == BINOP_MAX)
|
||||
return risks_zero_or_inf(binop->lhs) || risks_zero_or_inf(binop->rhs);
|
||||
else
|
||||
return true;
|
||||
}
|
||||
default: return true;
|
||||
}
|
||||
}
|
||||
// static PUREFUNC bool risks_zero_or_inf(ast_t *ast)
|
||||
// {
|
||||
// switch (ast->tag) {
|
||||
// case Int: {
|
||||
// const char *str = Match(ast, Int)->str;
|
||||
// OptionalInt_t int_val = Int$from_str(str);
|
||||
// return (int_val.small == 0x1); // zero
|
||||
// }
|
||||
// case Num: {
|
||||
// return Match(ast, Num)->n == 0.0;
|
||||
// }
|
||||
// case BINOP_CASES: {
|
||||
// binary_operands_t binop = BINARY_OPERANDS(ast);
|
||||
// if (ast->tag == Multiply || ast->tag == Divide || ast->tag == Min || ast->tag == Max)
|
||||
// return risks_zero_or_inf(binop.lhs) || risks_zero_or_inf(binop.rhs);
|
||||
// else
|
||||
// return true;
|
||||
// }
|
||||
// default: return true;
|
||||
// }
|
||||
// }
|
||||
|
||||
PUREFUNC type_t *get_math_type(env_t *env, ast_t *ast, type_t *lhs_t, type_t *rhs_t)
|
||||
{
|
||||
@ -312,7 +313,7 @@ void bind_statement(env_t *env, ast_t *statement)
|
||||
if (get_binding(env, name))
|
||||
code_err(decl->var, "A ", type_to_str(get_binding(env, name)->type), " called ", quoted(name), " has already been defined");
|
||||
bind_statement(env, decl->value);
|
||||
type_t *type = get_type(env, decl->value);
|
||||
type_t *type = decl->type ? parse_type_ast(env, decl->type) : get_type(env, decl->value);
|
||||
if (!type)
|
||||
code_err(decl->value, "I couldn't figure out the type of this value");
|
||||
if (type->tag == FunctionType)
|
||||
@ -617,12 +618,7 @@ type_t *get_type(env_t *env, ast_t *ast)
|
||||
#endif
|
||||
switch (ast->tag) {
|
||||
case None: {
|
||||
if (!Match(ast, None)->type)
|
||||
return Type(OptionalType, .type=NULL);
|
||||
type_t *t = parse_type_ast(env, Match(ast, None)->type);
|
||||
if (t->tag == OptionalType)
|
||||
code_err(ast, "Nested optional types are not supported. This should be: `none:", type_to_str(Match(t, OptionalType)->type), "`");
|
||||
return Type(OptionalType, .type=t);
|
||||
return Type(OptionalType, .type=NULL);
|
||||
}
|
||||
case Bool: {
|
||||
return Type(BoolType);
|
||||
@ -714,115 +710,91 @@ type_t *get_type(env_t *env, ast_t *ast)
|
||||
case Array: {
|
||||
auto array = Match(ast, Array);
|
||||
type_t *item_type = NULL;
|
||||
if (array->item_type) {
|
||||
item_type = parse_type_ast(env, array->item_type);
|
||||
} else if (array->items) {
|
||||
for (ast_list_t *item = array->items; item; item = item->next) {
|
||||
ast_t *item_ast = item->ast;
|
||||
env_t *scope = env;
|
||||
while (item_ast->tag == Comprehension) {
|
||||
auto comp = Match(item_ast, Comprehension);
|
||||
scope = for_scope(
|
||||
scope, FakeAST(For, .iter=comp->iter, .vars=comp->vars));
|
||||
item_ast = comp->expr;
|
||||
}
|
||||
type_t *t2 = get_type(scope, item_ast);
|
||||
type_t *merged = item_type ? type_or_type(item_type, t2) : t2;
|
||||
if (!merged)
|
||||
code_err(item->ast,
|
||||
"This array item has type ", type_to_str(t2),
|
||||
", which is different from earlier array items which have type ", type_to_str(item_type));
|
||||
item_type = merged;
|
||||
for (ast_list_t *item = array->items; item; item = item->next) {
|
||||
ast_t *item_ast = item->ast;
|
||||
env_t *scope = env;
|
||||
while (item_ast->tag == Comprehension) {
|
||||
auto comp = Match(item_ast, Comprehension);
|
||||
scope = for_scope(
|
||||
scope, FakeAST(For, .iter=comp->iter, .vars=comp->vars));
|
||||
item_ast = comp->expr;
|
||||
}
|
||||
} else {
|
||||
code_err(ast, "I can't figure out what type this array has because it has no members or explicit type");
|
||||
type_t *t2 = get_type(scope, item_ast);
|
||||
type_t *merged = item_type ? type_or_type(item_type, t2) : t2;
|
||||
if (!merged)
|
||||
code_err(item->ast,
|
||||
"This array item has type ", type_to_str(t2),
|
||||
", which is different from earlier array items which have type ", type_to_str(item_type));
|
||||
item_type = merged;
|
||||
}
|
||||
if (has_stack_memory(item_type))
|
||||
code_err(ast, "Arrays cannot hold stack references, because the array may outlive the stack frame the reference was created in.");
|
||||
|
||||
if (!item_type)
|
||||
code_err(ast, "I couldn't figure out the item type for this array!");
|
||||
if (item_type && has_stack_memory(item_type))
|
||||
code_err(ast, "Arrays cannot hold stack references, because the array may outlive the stack frame the reference was created in.");
|
||||
|
||||
return Type(ArrayType, .item_type=item_type);
|
||||
}
|
||||
case Set: {
|
||||
auto set = Match(ast, Set);
|
||||
type_t *item_type = NULL;
|
||||
if (set->item_type) {
|
||||
item_type = parse_type_ast(env, set->item_type);
|
||||
} else {
|
||||
for (ast_list_t *item = set->items; item; item = item->next) {
|
||||
ast_t *item_ast = item->ast;
|
||||
env_t *scope = env;
|
||||
while (item_ast->tag == Comprehension) {
|
||||
auto comp = Match(item_ast, Comprehension);
|
||||
scope = for_scope(
|
||||
scope, FakeAST(For, .iter=comp->iter, .vars=comp->vars));
|
||||
item_ast = comp->expr;
|
||||
}
|
||||
|
||||
type_t *this_item_type = get_type(scope, item_ast);
|
||||
type_t *item_merged = type_or_type(item_type, this_item_type);
|
||||
if (!item_merged)
|
||||
code_err(item_ast,
|
||||
"This set item has type ", type_to_str(this_item_type),
|
||||
", which is different from earlier set items which have type ", type_to_str(item_type));
|
||||
item_type = item_merged;
|
||||
for (ast_list_t *item = set->items; item; item = item->next) {
|
||||
ast_t *item_ast = item->ast;
|
||||
env_t *scope = env;
|
||||
while (item_ast->tag == Comprehension) {
|
||||
auto comp = Match(item_ast, Comprehension);
|
||||
scope = for_scope(
|
||||
scope, FakeAST(For, .iter=comp->iter, .vars=comp->vars));
|
||||
item_ast = comp->expr;
|
||||
}
|
||||
|
||||
type_t *this_item_type = get_type(scope, item_ast);
|
||||
type_t *item_merged = type_or_type(item_type, this_item_type);
|
||||
if (!item_merged)
|
||||
code_err(item_ast,
|
||||
"This set item has type ", type_to_str(this_item_type),
|
||||
", which is different from earlier set items which have type ", type_to_str(item_type));
|
||||
item_type = item_merged;
|
||||
}
|
||||
|
||||
if (!item_type)
|
||||
code_err(ast, "I couldn't figure out the item type for this set!");
|
||||
|
||||
if (has_stack_memory(item_type))
|
||||
if (item_type && has_stack_memory(item_type))
|
||||
code_err(ast, "Sets cannot hold stack references because the set may outlive the reference's stack frame.");
|
||||
|
||||
return Type(SetType, .item_type=item_type);
|
||||
}
|
||||
case Table: {
|
||||
auto table = Match(ast, Table);
|
||||
type_t *key_type = NULL, *value_type = NULL;
|
||||
if (table->key_type && table->value_type) {
|
||||
key_type = parse_type_ast(env, table->key_type);
|
||||
value_type = parse_type_ast(env, table->value_type);
|
||||
} else if (table->key_type && table->default_value) {
|
||||
key_type = parse_type_ast(env, table->key_type);
|
||||
value_type = get_type(env, table->default_value);
|
||||
} else {
|
||||
for (ast_list_t *entry = table->entries; entry; entry = entry->next) {
|
||||
ast_t *entry_ast = entry->ast;
|
||||
env_t *scope = env;
|
||||
while (entry_ast->tag == Comprehension) {
|
||||
auto comp = Match(entry_ast, Comprehension);
|
||||
scope = for_scope(
|
||||
scope, FakeAST(For, .iter=comp->iter, .vars=comp->vars));
|
||||
entry_ast = comp->expr;
|
||||
}
|
||||
|
||||
auto e = Match(entry_ast, TableEntry);
|
||||
type_t *key_t = get_type(scope, e->key);
|
||||
type_t *value_t = get_type(scope, e->value);
|
||||
|
||||
type_t *key_merged = key_type ? type_or_type(key_type, key_t) : key_t;
|
||||
if (!key_merged)
|
||||
code_err(entry->ast,
|
||||
"This table entry has type ", type_to_str(key_t),
|
||||
", which is different from earlier table entries which have type ", type_to_str(key_type));
|
||||
key_type = key_merged;
|
||||
|
||||
type_t *val_merged = value_type ? type_or_type(value_type, value_t) : value_t;
|
||||
if (!val_merged)
|
||||
code_err(entry->ast,
|
||||
"This table entry has type ", type_to_str(value_t),
|
||||
", which is different from earlier table entries which have type ", type_to_str(value_type));
|
||||
value_type = val_merged;
|
||||
for (ast_list_t *entry = table->entries; entry; entry = entry->next) {
|
||||
ast_t *entry_ast = entry->ast;
|
||||
env_t *scope = env;
|
||||
while (entry_ast->tag == Comprehension) {
|
||||
auto comp = Match(entry_ast, Comprehension);
|
||||
scope = for_scope(
|
||||
scope, FakeAST(For, .iter=comp->iter, .vars=comp->vars));
|
||||
entry_ast = comp->expr;
|
||||
}
|
||||
|
||||
auto e = Match(entry_ast, TableEntry);
|
||||
type_t *key_t = get_type(scope, e->key);
|
||||
type_t *value_t = get_type(scope, e->value);
|
||||
|
||||
type_t *key_merged = key_type ? type_or_type(key_type, key_t) : key_t;
|
||||
if (!key_merged)
|
||||
code_err(entry->ast,
|
||||
"This table entry has type ", type_to_str(key_t),
|
||||
", which is different from earlier table entries which have type ", type_to_str(key_type));
|
||||
key_type = key_merged;
|
||||
|
||||
type_t *val_merged = value_type ? type_or_type(value_type, value_t) : value_t;
|
||||
if (!val_merged)
|
||||
code_err(entry->ast,
|
||||
"This table entry has type ", type_to_str(value_t),
|
||||
", which is different from earlier table entries which have type ", type_to_str(value_type));
|
||||
value_type = val_merged;
|
||||
}
|
||||
|
||||
if (!key_type || !value_type)
|
||||
code_err(ast, "I couldn't figure out the key and value types for this table!");
|
||||
|
||||
if (has_stack_memory(key_type) || has_stack_memory(value_type))
|
||||
if ((key_type && has_stack_memory(key_type)) || (value_type && has_stack_memory(value_type)))
|
||||
code_err(ast, "Tables cannot hold stack references because the table may outlive the reference's stack frame.");
|
||||
|
||||
return Type(TableType, .key_type=key_type, .value_type=value_type, .default_value=table->default_value, .env=env);
|
||||
}
|
||||
case TableEntry: {
|
||||
@ -920,7 +892,9 @@ type_t *get_type(env_t *env, ast_t *ast)
|
||||
if (streq(call->name, "serialized")) // Data serialization
|
||||
return Type(ArrayType, Type(ByteType));
|
||||
|
||||
type_t *self_value_t = value_type(get_type(env, call->self));
|
||||
type_t *self_value_t = get_type(env, call->self);
|
||||
if (!self_value_t) code_err(call->self, "Couldn't get the type of this value");
|
||||
self_value_t = value_type(self_value_t);
|
||||
switch (self_value_t->tag) {
|
||||
case ArrayType: {
|
||||
type_t *item_type = Match(self_value_t, ArrayType)->item_type;
|
||||
@ -998,7 +972,7 @@ type_t *get_type(env_t *env, ast_t *ast)
|
||||
|
||||
// Early out if the type is knowable without any context from the block:
|
||||
switch (last->ast->tag) {
|
||||
case UpdateAssign: case Assign: case Declare: case FunctionDef: case ConvertDef: case StructDef: case EnumDef: case LangDef: case Extend:
|
||||
case UPDATE_CASES: case Assign: case Declare: case FunctionDef: case ConvertDef: case StructDef: case EnumDef: case LangDef: case Extend:
|
||||
return Type(VoidType);
|
||||
default: break;
|
||||
}
|
||||
@ -1022,7 +996,7 @@ type_t *get_type(env_t *env, ast_t *ast)
|
||||
case Extern: {
|
||||
return parse_type_ast(env, Match(ast, Extern)->type);
|
||||
}
|
||||
case Declare: case Assign: case DocTest: {
|
||||
case Declare: case Assign: case UPDATE_CASES: case DocTest: {
|
||||
return Type(VoidType);
|
||||
}
|
||||
case Use: {
|
||||
@ -1078,169 +1052,233 @@ type_t *get_type(env_t *env, ast_t *ast)
|
||||
}
|
||||
code_err(ast, "I only know how to get 'not' of boolean, numeric, and optional pointer types, not ", type_to_str(t));
|
||||
}
|
||||
case BinaryOp: {
|
||||
auto binop = Match(ast, BinaryOp);
|
||||
type_t *lhs_t = get_type(env, binop->lhs),
|
||||
*rhs_t = get_type(env, binop->rhs);
|
||||
case Or: {
|
||||
binary_operands_t binop = BINARY_OPERANDS(ast);
|
||||
type_t *lhs_t = get_type(env, binop.lhs);
|
||||
type_t *rhs_t = get_type(env, binop.rhs);
|
||||
|
||||
if (lhs_t->tag == BigIntType && rhs_t->tag != BigIntType && is_numeric_type(rhs_t) && binop->lhs->tag == Int) {
|
||||
lhs_t = rhs_t;
|
||||
} else if (rhs_t->tag == BigIntType && lhs_t->tag != BigIntType && is_numeric_type(lhs_t) && binop->rhs->tag == Int) {
|
||||
|
||||
rhs_t = lhs_t;
|
||||
}
|
||||
|
||||
#define binding_works(name, self, lhs_t, rhs_t, ret_t) \
|
||||
({ binding_t *b = get_namespace_binding(env, self, name); \
|
||||
(b && b->type->tag == FunctionType && ({ auto fn = Match(b->type, FunctionType); \
|
||||
(type_eq(fn->ret, ret_t) \
|
||||
&& (fn->args && type_eq(fn->args->type, lhs_t)) \
|
||||
&& (fn->args->next && can_promote(rhs_t, fn->args->next->type))); })); })
|
||||
// Check for a binop method like plus() etc:
|
||||
switch (binop->op) {
|
||||
case BINOP_MULT: {
|
||||
if (is_numeric_type(lhs_t) && binding_works("scaled_by", binop->rhs, rhs_t, lhs_t, rhs_t))
|
||||
return rhs_t;
|
||||
else if (is_numeric_type(rhs_t) && binding_works("scaled_by", binop->lhs, lhs_t, rhs_t, lhs_t))
|
||||
return lhs_t;
|
||||
else if (type_eq(lhs_t, rhs_t) && binding_works(binop_method_names[binop->op], binop->lhs, lhs_t, rhs_t, lhs_t))
|
||||
return lhs_t;
|
||||
break;
|
||||
}
|
||||
case BINOP_PLUS: case BINOP_MINUS: case BINOP_AND: case BINOP_OR: case BINOP_XOR: case BINOP_CONCAT: {
|
||||
if (type_eq(lhs_t, rhs_t) && binding_works(binop_method_names[binop->op], binop->lhs, lhs_t, rhs_t, lhs_t))
|
||||
return lhs_t;
|
||||
break;
|
||||
}
|
||||
case BINOP_DIVIDE: case BINOP_MOD: case BINOP_MOD1: {
|
||||
if (is_numeric_type(rhs_t) && binding_works(binop_method_names[binop->op], binop->lhs, lhs_t, rhs_t, lhs_t))
|
||||
return lhs_t;
|
||||
break;
|
||||
}
|
||||
case BINOP_LSHIFT: case BINOP_RSHIFT: case BINOP_ULSHIFT: case BINOP_URSHIFT: {
|
||||
if (binop.lhs->tag == Int && (is_int_type(rhs_t) || rhs_t->tag == ByteType))
|
||||
return rhs_t;
|
||||
else if (binop.rhs->tag == Int && (is_int_type(lhs_t) || lhs_t->tag == ByteType))
|
||||
return lhs_t;
|
||||
}
|
||||
case BINOP_POWER: {
|
||||
if (is_numeric_type(rhs_t) && binding_works(binop_method_names[binop->op], binop->lhs, lhs_t, rhs_t, lhs_t))
|
||||
return lhs_t;
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
#undef binding_works
|
||||
|
||||
switch (binop->op) {
|
||||
case BINOP_AND: {
|
||||
if (lhs_t->tag == BoolType && rhs_t->tag == BoolType) {
|
||||
return lhs_t;
|
||||
} else if ((lhs_t->tag == BoolType && rhs_t->tag == OptionalType) ||
|
||||
(lhs_t->tag == OptionalType && rhs_t->tag == BoolType)) {
|
||||
return Type(BoolType);
|
||||
} else if (lhs_t->tag == BoolType && (rhs_t->tag == AbortType || rhs_t->tag == ReturnType)) {
|
||||
return lhs_t;
|
||||
} else if (rhs_t->tag == AbortType || rhs_t->tag == ReturnType) {
|
||||
return lhs_t;
|
||||
} else if (rhs_t->tag == OptionalType) {
|
||||
if (can_promote(lhs_t, rhs_t))
|
||||
return rhs_t;
|
||||
} else if (lhs_t->tag == PointerType && rhs_t->tag == PointerType) {
|
||||
auto lhs_ptr = Match(lhs_t, PointerType);
|
||||
auto rhs_ptr = Match(rhs_t, PointerType);
|
||||
if (type_eq(lhs_ptr->pointed, rhs_ptr->pointed))
|
||||
return Type(PointerType, .pointed=lhs_ptr->pointed);
|
||||
} else if ((is_int_type(lhs_t) && is_int_type(rhs_t))
|
||||
|| (lhs_t->tag == ByteType && rhs_t->tag == ByteType)) {
|
||||
return get_math_type(env, ast, lhs_t, rhs_t);
|
||||
}
|
||||
code_err(ast, "I can't figure out the type of this `and` expression between a ", type_to_str(lhs_t), " and a ", type_to_str(rhs_t));
|
||||
}
|
||||
case BINOP_OR: {
|
||||
if (lhs_t->tag == BoolType && rhs_t->tag == BoolType) {
|
||||
return lhs_t;
|
||||
} else if ((lhs_t->tag == BoolType && rhs_t->tag == OptionalType) ||
|
||||
(lhs_t->tag == OptionalType && rhs_t->tag == BoolType)) {
|
||||
return Type(BoolType);
|
||||
} else if (lhs_t->tag == BoolType && (rhs_t->tag == AbortType || rhs_t->tag == ReturnType)) {
|
||||
return lhs_t;
|
||||
} else if ((is_int_type(lhs_t) && is_int_type(rhs_t))
|
||||
|| (lhs_t->tag == ByteType && rhs_t->tag == ByteType)) {
|
||||
return get_math_type(env, ast, lhs_t, rhs_t);
|
||||
} else if (lhs_t->tag == OptionalType) {
|
||||
if (rhs_t->tag == AbortType || rhs_t->tag == ReturnType)
|
||||
return Match(lhs_t, OptionalType)->type;
|
||||
if (can_promote(rhs_t, lhs_t))
|
||||
return rhs_t;
|
||||
} else if (lhs_t->tag == PointerType) {
|
||||
auto lhs_ptr = Match(lhs_t, PointerType);
|
||||
if (rhs_t->tag == AbortType || rhs_t->tag == ReturnType) {
|
||||
return Type(PointerType, .pointed=lhs_ptr->pointed);
|
||||
} else if (rhs_t->tag == PointerType) {
|
||||
auto rhs_ptr = Match(rhs_t, PointerType);
|
||||
if (type_eq(rhs_ptr->pointed, lhs_ptr->pointed))
|
||||
return Type(PointerType, .pointed=lhs_ptr->pointed);
|
||||
}
|
||||
} else if (rhs_t->tag == OptionalType) {
|
||||
return type_or_type(lhs_t, rhs_t);
|
||||
}
|
||||
code_err(ast, "I can't figure out the type of this `or` expression between a ", type_to_str(lhs_t), " and a ", type_to_str(rhs_t));
|
||||
}
|
||||
case BINOP_XOR: {
|
||||
if (lhs_t->tag == BoolType && rhs_t->tag == BoolType) {
|
||||
return lhs_t;
|
||||
} else if ((lhs_t->tag == BoolType && rhs_t->tag == OptionalType) ||
|
||||
(lhs_t->tag == OptionalType && rhs_t->tag == BoolType)) {
|
||||
return Type(BoolType);
|
||||
} else if ((is_int_type(lhs_t) && is_int_type(rhs_t))
|
||||
|| (lhs_t->tag == ByteType && rhs_t->tag == ByteType)) {
|
||||
return get_math_type(env, ast, lhs_t, rhs_t);
|
||||
}
|
||||
|
||||
code_err(ast, "I can't figure out the type of this `xor` expression between a ", type_to_str(lhs_t), " and a ", type_to_str(rhs_t));
|
||||
}
|
||||
case BINOP_CONCAT: {
|
||||
if (!type_eq(lhs_t, rhs_t))
|
||||
code_err(ast, "The type on the left side of this concatenation doesn't match the right side: ", type_to_str(lhs_t),
|
||||
" vs. ", type_to_str(rhs_t));
|
||||
if (lhs_t->tag == ArrayType || lhs_t->tag == TextType || lhs_t->tag == SetType)
|
||||
return lhs_t;
|
||||
|
||||
code_err(ast, "Only array/set/text value types support concatenation, not ", type_to_str(lhs_t));
|
||||
}
|
||||
case BINOP_EQ: case BINOP_NE: case BINOP_LT: case BINOP_LE: case BINOP_GT: case BINOP_GE: {
|
||||
if (!can_promote(lhs_t, rhs_t) && !can_promote(rhs_t, lhs_t))
|
||||
code_err(ast, "I can't compare these two different types: ", type_to_str(lhs_t), " vs ", type_to_str(rhs_t));
|
||||
// `opt? or (x == y)` / `(x == y) or opt?` is a boolean conditional:
|
||||
if ((lhs_t->tag == OptionalType && rhs_t->tag == BoolType)
|
||||
|| (lhs_t->tag == BoolType && rhs_t->tag == OptionalType)) {
|
||||
return Type(BoolType);
|
||||
}
|
||||
case BINOP_CMP:
|
||||
return Type(IntType, .bits=TYPE_IBITS32);
|
||||
case BINOP_POWER: {
|
||||
type_t *result = get_math_type(env, ast, lhs_t, rhs_t);
|
||||
if (result->tag == NumType)
|
||||
|
||||
if (type_eq(lhs_t, rhs_t)) {
|
||||
binding_t *b = get_metamethod_binding(env, ast->tag, binop.lhs, binop.rhs, lhs_t);
|
||||
if (b) return lhs_t;
|
||||
}
|
||||
|
||||
if (lhs_t->tag == OptionalType) {
|
||||
if (rhs_t->tag == OptionalType) {
|
||||
type_t *result = most_complete_type(lhs_t, rhs_t);
|
||||
if (result == NULL)
|
||||
code_err(ast, "I could not determine the type of ", type_to_str(lhs_t), " `or` ", type_to_str(rhs_t));
|
||||
return result;
|
||||
return Type(NumType, .bits=TYPE_NBITS64);
|
||||
}
|
||||
case BINOP_MULT: case BINOP_DIVIDE: {
|
||||
type_t *math_type = get_math_type(env, ast, value_type(lhs_t), value_type(rhs_t));
|
||||
if (value_type(lhs_t)->tag == NumType || value_type(rhs_t)->tag == NumType) {
|
||||
if (risks_zero_or_inf(binop->lhs) && risks_zero_or_inf(binop->rhs))
|
||||
return Type(OptionalType, math_type);
|
||||
else
|
||||
return math_type;
|
||||
} else if (rhs_t->tag == AbortType || rhs_t->tag == ReturnType) {
|
||||
return Match(lhs_t, OptionalType)->type;
|
||||
}
|
||||
return math_type;
|
||||
type_t *non_opt = Match(lhs_t, OptionalType)->type;
|
||||
non_opt = most_complete_type(non_opt, rhs_t);
|
||||
if (non_opt != NULL)
|
||||
return non_opt;
|
||||
} else if ((is_numeric_type(lhs_t) || lhs_t->tag == BoolType)
|
||||
&& (is_numeric_type(rhs_t) || rhs_t->tag == BoolType)
|
||||
&& lhs_t->tag != NumType && rhs_t->tag != NumType) {
|
||||
if (can_promote(rhs_t, lhs_t))
|
||||
return lhs_t;
|
||||
else if (can_promote(lhs_t, rhs_t))
|
||||
return rhs_t;
|
||||
} else if (lhs_t->tag == SetType && rhs_t->tag == SetType && type_eq(lhs_t, rhs_t)) {
|
||||
return lhs_t;
|
||||
}
|
||||
default: {
|
||||
return get_math_type(env, ast, lhs_t, rhs_t);
|
||||
code_err(ast, "I couldn't figure out how to do `or` between ", type_to_str(lhs_t), " and ", type_to_str(rhs_t));
|
||||
}
|
||||
case And: {
|
||||
binary_operands_t binop = BINARY_OPERANDS(ast);
|
||||
type_t *lhs_t = get_type(env, binop.lhs);
|
||||
type_t *rhs_t = get_type(env, binop.rhs);
|
||||
|
||||
if (binop.lhs->tag == Int && (is_int_type(rhs_t) || rhs_t->tag == ByteType))
|
||||
return rhs_t;
|
||||
else if (binop.rhs->tag == Int && (is_int_type(lhs_t) || lhs_t->tag == ByteType))
|
||||
return lhs_t;
|
||||
|
||||
// `and` between optionals/bools is a boolean expression like `if opt? and opt?:` or `if x > 0 and opt?:`
|
||||
if ((lhs_t->tag == OptionalType || lhs_t->tag == BoolType)
|
||||
&& (rhs_t->tag == OptionalType || rhs_t->tag == BoolType)) {
|
||||
return Type(BoolType);
|
||||
}
|
||||
|
||||
if (type_eq(lhs_t, rhs_t)) {
|
||||
binding_t *b = get_metamethod_binding(env, ast->tag, binop.lhs, binop.rhs, lhs_t);
|
||||
if (b) return lhs_t;
|
||||
}
|
||||
|
||||
// Bitwise AND:
|
||||
if ((is_numeric_type(lhs_t) || lhs_t->tag == BoolType)
|
||||
&& (is_numeric_type(rhs_t) || rhs_t->tag == BoolType)
|
||||
&& lhs_t->tag != NumType && rhs_t->tag != NumType) {
|
||||
if (can_promote(rhs_t, lhs_t))
|
||||
return lhs_t;
|
||||
else if (can_promote(lhs_t, rhs_t))
|
||||
return rhs_t;
|
||||
} else if (lhs_t->tag == SetType && rhs_t->tag == SetType && type_eq(lhs_t, rhs_t)) {
|
||||
return lhs_t;
|
||||
}
|
||||
code_err(ast, "I couldn't figure out how to do `and` between ", type_to_str(lhs_t), " and ", type_to_str(rhs_t));
|
||||
}
|
||||
case Xor: {
|
||||
binary_operands_t binop = BINARY_OPERANDS(ast);
|
||||
type_t *lhs_t = get_type(env, binop.lhs);
|
||||
type_t *rhs_t = get_type(env, binop.rhs);
|
||||
|
||||
if (binop.lhs->tag == Int && (is_int_type(rhs_t) || rhs_t->tag == ByteType))
|
||||
return rhs_t;
|
||||
else if (binop.rhs->tag == Int && (is_int_type(lhs_t) || lhs_t->tag == ByteType))
|
||||
return lhs_t;
|
||||
|
||||
// `xor` between optionals/bools is a boolean expression like `if opt? xor opt?:` or `if x > 0 xor opt?:`
|
||||
if ((lhs_t->tag == OptionalType || lhs_t->tag == BoolType)
|
||||
&& (rhs_t->tag == OptionalType || rhs_t->tag == BoolType)) {
|
||||
return Type(BoolType);
|
||||
}
|
||||
|
||||
if (type_eq(lhs_t, rhs_t)) {
|
||||
binding_t *b = get_metamethod_binding(env, ast->tag, binop.lhs, binop.rhs, lhs_t);
|
||||
if (b) return lhs_t;
|
||||
}
|
||||
|
||||
// Bitwise XOR:
|
||||
if ((is_numeric_type(lhs_t) || lhs_t->tag == BoolType)
|
||||
&& (is_numeric_type(rhs_t) || rhs_t->tag == BoolType)
|
||||
&& lhs_t->tag != NumType && rhs_t->tag != NumType) {
|
||||
if (can_promote(rhs_t, lhs_t))
|
||||
return lhs_t;
|
||||
else if (can_promote(lhs_t, rhs_t))
|
||||
return rhs_t;
|
||||
} else if (lhs_t->tag == SetType && rhs_t->tag == SetType && type_eq(lhs_t, rhs_t)) {
|
||||
return lhs_t;
|
||||
}
|
||||
code_err(ast, "I couldn't figure out how to do `xor` between ", type_to_str(lhs_t), " and ", type_to_str(rhs_t));
|
||||
}
|
||||
case Compare:
|
||||
case Equals: case NotEquals: case LessThan: case LessThanOrEquals: case GreaterThan: case GreaterThanOrEquals: {
|
||||
binary_operands_t binop = BINARY_OPERANDS(ast);
|
||||
type_t *lhs_t = get_type(env, binop.lhs);
|
||||
type_t *rhs_t = get_type(env, binop.rhs);
|
||||
|
||||
if ((binop.lhs->tag == Int && is_numeric_type(rhs_t))
|
||||
|| (binop.rhs->tag == Int && is_numeric_type(lhs_t))
|
||||
|| can_promote(rhs_t, lhs_t)
|
||||
|| can_promote(lhs_t, rhs_t))
|
||||
return ast->tag == Compare ? Type(IntType, .bits=TYPE_IBITS32) : Type(BoolType);
|
||||
|
||||
code_err(ast, "I don't know how to compare ", type_to_str(lhs_t), " and ", type_to_str(rhs_t));
|
||||
}
|
||||
case Power: case Multiply: case Divide: case Mod: case Mod1: case Plus: case Minus: case LeftShift:
|
||||
case UnsignedLeftShift: case RightShift: case UnsignedRightShift: {
|
||||
binary_operands_t binop = BINARY_OPERANDS(ast);
|
||||
type_t *lhs_t = get_type(env, binop.lhs);
|
||||
type_t *rhs_t = get_type(env, binop.rhs);
|
||||
|
||||
if (ast->tag == LeftShift || ast->tag == UnsignedLeftShift || ast->tag == RightShift || ast->tag == UnsignedRightShift) {
|
||||
if (!is_int_type(rhs_t))
|
||||
code_err(binop.rhs, "I only know how to do bit shifting by integer amounts, not ", type_to_str(rhs_t));
|
||||
}
|
||||
|
||||
if (is_numeric_type(lhs_t) && binop.rhs->tag == Int) {
|
||||
return lhs_t;
|
||||
} else if (is_numeric_type(rhs_t) && binop.lhs->tag == Int) {
|
||||
return rhs_t;
|
||||
} else {
|
||||
switch (compare_precision(lhs_t, rhs_t)) {
|
||||
case NUM_PRECISION_LESS: return rhs_t;
|
||||
case NUM_PRECISION_MORE: return lhs_t;
|
||||
case NUM_PRECISION_EQUAL: return lhs_t;
|
||||
default: {
|
||||
if (can_compile_to_type(env, binop.rhs, lhs_t)) {
|
||||
return lhs_t;
|
||||
} else if (can_compile_to_type(env, binop.lhs, rhs_t)) {
|
||||
return rhs_t;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ast->tag == Multiply && is_numeric_type(lhs_t)) {
|
||||
binding_t *b = get_namespace_binding(env, binop.rhs, "scaled_by");
|
||||
if (b && b->type->tag == FunctionType) {
|
||||
auto fn = Match(b->type, FunctionType);
|
||||
if (type_eq(fn->ret, rhs_t)) {
|
||||
arg_ast_t *args = new(arg_ast_t, .value=binop.rhs, .next=new(arg_ast_t, .value=binop.lhs));
|
||||
if (is_valid_call(env, fn->args, args, true))
|
||||
return rhs_t;
|
||||
}
|
||||
}
|
||||
} else if (ast->tag == Multiply && is_numeric_type(rhs_t)) {
|
||||
binding_t *b = get_namespace_binding(env, binop.lhs, "scaled_by");
|
||||
if (b && b->type->tag == FunctionType) {
|
||||
auto fn = Match(b->type, FunctionType);
|
||||
if (type_eq(fn->ret, lhs_t)) {
|
||||
arg_ast_t *args = new(arg_ast_t, .value=binop.lhs, .next=new(arg_ast_t, .value=binop.rhs));
|
||||
if (is_valid_call(env, fn->args, args, true))
|
||||
return lhs_t;
|
||||
}
|
||||
}
|
||||
} else if ((ast->tag == Divide || ast->tag == Mod || ast->tag == Mod1) && is_numeric_type(rhs_t)) {
|
||||
binding_t *b = get_namespace_binding(env, binop.lhs, binop_method_name(ast->tag));
|
||||
if (b && b->type->tag == FunctionType) {
|
||||
auto fn = Match(b->type, FunctionType);
|
||||
if (type_eq(fn->ret, lhs_t)) {
|
||||
arg_ast_t *args = new(arg_ast_t, .value=binop.lhs, .next=new(arg_ast_t, .value=binop.rhs));
|
||||
if (is_valid_call(env, fn->args, args, true))
|
||||
return lhs_t;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type_t *overall_t = (can_promote(rhs_t, lhs_t) ? lhs_t : (can_promote(lhs_t, rhs_t) ? rhs_t : NULL));
|
||||
if (overall_t == NULL)
|
||||
code_err(ast, "I don't know how to do math operations between ", type_to_str(lhs_t), " and ", type_to_str(rhs_t));
|
||||
|
||||
binding_t *b = get_metamethod_binding(env, ast->tag, binop.lhs, binop.rhs, overall_t);
|
||||
if (b) return overall_t;
|
||||
|
||||
if (is_numeric_type(lhs_t) && is_numeric_type(rhs_t))
|
||||
return overall_t;
|
||||
|
||||
code_err(ast, "I don't know how to do math operations between ", type_to_str(lhs_t), " and ", type_to_str(rhs_t));
|
||||
}
|
||||
case Concat: {
|
||||
binary_operands_t binop = BINARY_OPERANDS(ast);
|
||||
type_t *lhs_t = get_type(env, binop.lhs);
|
||||
type_t *rhs_t = get_type(env, binop.rhs);
|
||||
|
||||
type_t *overall_t = (can_promote(rhs_t, lhs_t) ? lhs_t : (can_promote(lhs_t, rhs_t) ? rhs_t : NULL));
|
||||
if (overall_t == NULL)
|
||||
code_err(ast, "I don't know how to do operations between ", type_to_str(lhs_t), " and ", type_to_str(rhs_t));
|
||||
|
||||
binding_t *b = get_metamethod_binding(env, ast->tag, binop.lhs, binop.rhs, overall_t);
|
||||
if (b) return overall_t;
|
||||
|
||||
if (overall_t->tag == ArrayType || overall_t->tag == SetType || overall_t->tag == TextType)
|
||||
return overall_t;
|
||||
|
||||
code_err(ast, "I don't know how to do concatenation between ", type_to_str(lhs_t), " and ", type_to_str(rhs_t));
|
||||
}
|
||||
|
||||
case Reduction: {
|
||||
auto reduction = Match(ast, Reduction);
|
||||
type_t *iter_t = get_type(env, reduction->iter);
|
||||
|
||||
if (reduction->op == BINOP_EQ || reduction->op == BINOP_NE || reduction->op == BINOP_LT
|
||||
|| reduction->op == BINOP_LE || reduction->op == BINOP_GT || reduction->op == BINOP_GE)
|
||||
if (reduction->op == Equals || reduction->op == NotEquals || reduction->op == LessThan
|
||||
|| reduction->op == LessThanOrEquals || reduction->op == GreaterThan || reduction->op == GreaterThanOrEquals)
|
||||
return Type(OptionalType, .type=Type(BoolType));
|
||||
|
||||
type_t *iterated = get_iterated_type(iter_t);
|
||||
@ -1249,9 +1287,6 @@ type_t *get_type(env_t *env, ast_t *ast)
|
||||
return iterated->tag == OptionalType ? iterated : Type(OptionalType, .type=iterated);
|
||||
}
|
||||
|
||||
case UpdateAssign:
|
||||
return Type(VoidType);
|
||||
|
||||
case Min: case Max: {
|
||||
// Unsafe! These types *should* have the same fields and this saves a lot of duplicate code:
|
||||
ast_t *lhs = ast->__data.Min.lhs, *rhs = ast->__data.Min.rhs;
|
||||
@ -1310,8 +1345,9 @@ type_t *get_type(env_t *env, ast_t *ast)
|
||||
env_t *truthy_scope = env;
|
||||
env_t *falsey_scope = env;
|
||||
if (if_->condition->tag == Declare) {
|
||||
type_t *condition_type = get_type(env, Match(if_->condition, Declare)->value);
|
||||
const char *varname = Match(Match(if_->condition, Declare)->var, Var)->name;
|
||||
auto decl = Match(if_->condition, Declare);
|
||||
type_t *condition_type = decl->type ? parse_type_ast(env, decl->type) : get_type(env, decl->value);
|
||||
const char *varname = Match(decl->var, Var)->name;
|
||||
if (streq(varname, "_"))
|
||||
code_err(if_->condition, "To use `if var := ...:`, you must choose a real variable name, not `_`");
|
||||
|
||||
@ -1456,7 +1492,7 @@ type_t *get_type(env_t *env, ast_t *ast)
|
||||
PUREFUNC bool is_discardable(env_t *env, ast_t *ast)
|
||||
{
|
||||
switch (ast->tag) {
|
||||
case UpdateAssign: case Assign: case Declare: case FunctionDef: case ConvertDef: case StructDef: case EnumDef:
|
||||
case UPDATE_CASES: case Assign: case Declare: case FunctionDef: case ConvertDef: case StructDef: case EnumDef:
|
||||
case LangDef: case Use: case Extend:
|
||||
return true;
|
||||
default: break;
|
||||
@ -1492,7 +1528,9 @@ Table_t *get_arg_bindings(env_t *env, arg_t *spec_args, arg_ast_t *call_args, bo
|
||||
for (arg_t *spec_arg = spec_args; spec_arg; spec_arg = spec_arg->next) {
|
||||
if (!streq(call_arg->name, spec_arg->name)) continue;
|
||||
type_t *spec_type = get_arg_type(env, spec_arg);
|
||||
if (!(type_eq(call_type, spec_type) || (promotion_allowed && can_promote(call_type, spec_type))
|
||||
type_t *complete_call_type = is_incomplete_type(call_type) ? most_complete_type(call_type, spec_type) : call_type;
|
||||
if (!complete_call_type) return NULL;
|
||||
if (!(type_eq(complete_call_type, spec_type) || (promotion_allowed && can_promote(complete_call_type, spec_type))
|
||||
|| (promotion_allowed && call_arg->value->tag == Int && is_numeric_type(spec_type))
|
||||
|| (promotion_allowed && call_arg->value->tag == Num && spec_type->tag == NumType)))
|
||||
return NULL;
|
||||
@ -1512,7 +1550,9 @@ Table_t *get_arg_bindings(env_t *env, arg_t *spec_args, arg_ast_t *call_args, bo
|
||||
for (; unused_args; unused_args = unused_args->next) {
|
||||
if (unused_args->name) continue; // Already handled the keyword args
|
||||
type_t *call_type = get_arg_ast_type(env, unused_args);
|
||||
if (!(type_eq(call_type, spec_type) || (promotion_allowed && can_promote(call_type, spec_type))
|
||||
type_t *complete_call_type = is_incomplete_type(call_type) ? most_complete_type(call_type, spec_type) : call_type;
|
||||
if (!complete_call_type) return NULL;
|
||||
if (!(type_eq(complete_call_type, spec_type) || (promotion_allowed && can_promote(complete_call_type, spec_type))
|
||||
|| (promotion_allowed && unused_args->value->tag == Int && is_numeric_type(spec_type))
|
||||
|| (promotion_allowed && unused_args->value->tag == Num && spec_type->tag == NumType)))
|
||||
return NULL; // Positional arg trying to fill in
|
||||
@ -1610,13 +1650,13 @@ PUREFUNC bool is_constant(env_t *env, ast_t *ast)
|
||||
}
|
||||
case Not: return is_constant(env, Match(ast, Not)->value);
|
||||
case Negative: return is_constant(env, Match(ast, Negative)->value);
|
||||
case BinaryOp: {
|
||||
auto binop = Match(ast, BinaryOp);
|
||||
switch (binop->op) {
|
||||
case BINOP_UNKNOWN: case BINOP_POWER: case BINOP_CONCAT: case BINOP_MIN: case BINOP_MAX: case BINOP_CMP:
|
||||
case BINOP_CASES: {
|
||||
binary_operands_t binop = BINARY_OPERANDS(ast);
|
||||
switch (ast->tag) {
|
||||
case Power: case Concat: case Min: case Max: case Compare:
|
||||
return false;
|
||||
default:
|
||||
return is_constant(env, binop->lhs) && is_constant(env, binop->rhs);
|
||||
return is_constant(env, binop.lhs) && is_constant(env, binop.rhs);
|
||||
}
|
||||
}
|
||||
case Use: return true;
|
||||
@ -1626,4 +1666,51 @@ PUREFUNC bool is_constant(env_t *env, ast_t *ast)
|
||||
}
|
||||
}
|
||||
|
||||
PUREFUNC bool can_compile_to_type(env_t *env, ast_t *ast, type_t *needed)
|
||||
{
|
||||
if (is_incomplete_type(needed))
|
||||
return false;
|
||||
|
||||
if (needed->tag == OptionalType && ast->tag == None)
|
||||
return true;
|
||||
|
||||
needed = non_optional(needed);
|
||||
if (needed->tag == ArrayType && ast->tag == Array) {
|
||||
type_t *item_type = Match(needed, ArrayType)->item_type;
|
||||
for (ast_list_t *item = Match(ast, Array)->items; item; item = item->next) {
|
||||
if (!can_compile_to_type(env, item->ast, item_type))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else if (needed->tag == SetType && ast->tag == Set) {
|
||||
type_t *item_type = Match(needed, SetType)->item_type;
|
||||
for (ast_list_t *item = Match(ast, Set)->items; item; item = item->next) {
|
||||
if (!can_compile_to_type(env, item->ast, item_type))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else if (needed->tag == TableType && ast->tag == Table) {
|
||||
type_t *key_type = Match(needed, TableType)->key_type;
|
||||
type_t *value_type = Match(needed, TableType)->value_type;
|
||||
for (ast_list_t *entry = Match(ast, Table)->entries; entry; entry = entry->next) {
|
||||
if (entry->ast->tag != TableEntry)
|
||||
continue; // TODO: fix this
|
||||
auto e = Match(entry->ast, TableEntry);
|
||||
if (!can_compile_to_type(env, e->key, key_type) || !can_compile_to_type(env, e->value, value_type))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else if (needed->tag == PointerType) {
|
||||
auto ptr = Match(needed, PointerType);
|
||||
if (ast->tag == HeapAllocate)
|
||||
return !ptr->is_stack && can_compile_to_type(env, Match(ast, HeapAllocate)->value, ptr->pointed);
|
||||
else if (ast->tag == StackReference)
|
||||
return ptr->is_stack && can_compile_to_type(env, Match(ast, StackReference)->value, ptr->pointed);
|
||||
else
|
||||
return can_promote(needed, get_type(env, ast));
|
||||
} else {
|
||||
return can_promote(needed, get_type(env, ast));
|
||||
}
|
||||
}
|
||||
|
||||
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0
|
||||
|
@ -29,5 +29,6 @@ type_t *get_method_type(env_t *env, ast_t *self, const char *name);
|
||||
PUREFUNC bool is_constant(env_t *env, ast_t *ast);
|
||||
Table_t *get_arg_bindings(env_t *env, arg_t *spec_args, arg_ast_t *call_args, bool promotion_allowed);
|
||||
bool is_valid_call(env_t *env, arg_t *spec_args, arg_ast_t *call_args, bool promotion_allowed);
|
||||
PUREFUNC bool can_compile_to_type(env_t *env, ast_t *ast, type_t *needed);
|
||||
|
||||
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0
|
||||
|
123
src/types.c
123
src/types.c
@ -111,6 +111,8 @@ PUREFUNC const char *get_type_name(type_t *t)
|
||||
bool type_eq(type_t *a, type_t *b)
|
||||
{
|
||||
if (a == b) return true;
|
||||
if (!a && !b) return true;
|
||||
if (!a || !b) return false;
|
||||
if (a->tag != b->tag) return false;
|
||||
return (CORD_cmp(type_to_cord(a), type_to_cord(b)) == 0);
|
||||
}
|
||||
@ -151,6 +153,12 @@ type_t *type_or_type(type_t *a, type_t *b)
|
||||
return a->tag == OptionalType ? a : Type(OptionalType, a);
|
||||
if (a->tag == ReturnType && b->tag == ReturnType)
|
||||
return Type(ReturnType, .ret=type_or_type(Match(a, ReturnType)->ret, Match(b, ReturnType)->ret));
|
||||
|
||||
if (is_incomplete_type(a) && type_eq(b, most_complete_type(a, b)))
|
||||
return b;
|
||||
if (is_incomplete_type(b) && type_eq(a, most_complete_type(a, b)))
|
||||
return a;
|
||||
|
||||
if (type_is_a(b, a)) return a;
|
||||
if (type_is_a(a, b)) return b;
|
||||
if (a->tag == AbortType || a->tag == ReturnType) return non_optional(b);
|
||||
@ -208,10 +216,8 @@ static PUREFUNC INLINE double type_max_magnitude(type_t *t)
|
||||
|
||||
PUREFUNC precision_cmp_e compare_precision(type_t *a, type_t *b)
|
||||
{
|
||||
if (a->tag == OptionalType && Match(a, OptionalType)->type->tag == NumType)
|
||||
a = Match(a, OptionalType)->type;
|
||||
if (b->tag == OptionalType && Match(b, OptionalType)->type->tag == NumType)
|
||||
b = Match(b, OptionalType)->type;
|
||||
if (a == NULL || b == NULL)
|
||||
return NUM_PRECISION_INCOMPARABLE;
|
||||
|
||||
if (is_int_type(a) && b->tag == NumType)
|
||||
return NUM_PRECISION_LESS;
|
||||
@ -360,6 +366,16 @@ PUREFUNC bool can_promote(type_t *actual, type_t *needed)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Empty literals:
|
||||
if (actual->tag == ArrayType && needed->tag == ArrayType && Match(actual, ArrayType)->item_type == NULL)
|
||||
return true; // [] -> [T]
|
||||
if (actual->tag == SetType && needed->tag == SetType && Match(actual, SetType)->item_type == NULL)
|
||||
return true; // {/} -> {T}
|
||||
if (actual->tag == TableType && needed->tag == SetType && Match(actual, TableType)->key_type == NULL && Match(actual, TableType)->value_type == NULL)
|
||||
return true; // {} -> {T}
|
||||
if (actual->tag == TableType && needed->tag == TableType && Match(actual, TableType)->key_type == NULL && Match(actual, TableType)->value_type == NULL)
|
||||
return true; // {} -> {K=V}
|
||||
|
||||
// Cross-promotion between tables with default values and without
|
||||
if (needed->tag == TableType && actual->tag == TableType) {
|
||||
auto actual_table = Match(actual, TableType);
|
||||
@ -708,4 +724,103 @@ PUREFUNC type_t *get_iterated_type(type_t *t)
|
||||
}
|
||||
}
|
||||
|
||||
CONSTFUNC bool is_incomplete_type(type_t *t)
|
||||
{
|
||||
if (t == NULL) return true;
|
||||
switch (t->tag) {
|
||||
case ReturnType: return is_incomplete_type(Match(t, ReturnType)->ret);
|
||||
case OptionalType: return is_incomplete_type(Match(t, OptionalType)->type);
|
||||
case ArrayType: return is_incomplete_type(Match(t, ArrayType)->item_type);
|
||||
case SetType: return is_incomplete_type(Match(t, SetType)->item_type);
|
||||
case TableType: {
|
||||
auto table = Match(t, TableType);
|
||||
return is_incomplete_type(table->key_type) || is_incomplete_type(table->value_type);
|
||||
}
|
||||
case FunctionType: {
|
||||
auto fn = Match(t, FunctionType);
|
||||
for (arg_t *arg = fn->args; arg; arg = arg->next) {
|
||||
if (arg->type == NULL || is_incomplete_type(arg->type))
|
||||
return true;
|
||||
}
|
||||
return fn->ret ? is_incomplete_type(fn->ret) : false;
|
||||
}
|
||||
case ClosureType: return is_incomplete_type(Match(t, ClosureType)->fn);
|
||||
case PointerType: return is_incomplete_type(Match(t, PointerType)->pointed);
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
CONSTFUNC type_t *most_complete_type(type_t *t1, type_t *t2)
|
||||
{
|
||||
if (!t1) return t2;
|
||||
if (!t2) return t1;
|
||||
|
||||
if (is_incomplete_type(t1) && is_incomplete_type(t2))
|
||||
return NULL;
|
||||
else if (!is_incomplete_type(t1) && !is_incomplete_type(t2) && type_eq(t1, t2))
|
||||
return t1;
|
||||
|
||||
if (t1->tag != t2->tag)
|
||||
return NULL;
|
||||
|
||||
switch (t1->tag) {
|
||||
case ReturnType: {
|
||||
type_t *ret = most_complete_type(Match(t1, ReturnType)->ret, Match(t1, ReturnType)->ret);
|
||||
return ret ? Type(ReturnType, ret) : NULL;
|
||||
}
|
||||
case OptionalType: {
|
||||
type_t *opt = most_complete_type(Match(t1, OptionalType)->type, Match(t2, OptionalType)->type);
|
||||
return opt ? Type(OptionalType, opt) : NULL;
|
||||
}
|
||||
case ArrayType: {
|
||||
type_t *item = most_complete_type(Match(t1, ArrayType)->item_type, Match(t2, ArrayType)->item_type);
|
||||
return item ? Type(ArrayType, item) : NULL;
|
||||
}
|
||||
case SetType: {
|
||||
type_t *item = most_complete_type(Match(t1, SetType)->item_type, Match(t2, SetType)->item_type);
|
||||
return item ? Type(SetType, item) : NULL;
|
||||
}
|
||||
case TableType: {
|
||||
auto table1 = Match(t1, TableType);
|
||||
auto table2 = Match(t2, TableType);
|
||||
type_t *key = most_complete_type(table1->key_type, table2->key_type);
|
||||
type_t *value = most_complete_type(table1->value_type, table2->value_type);
|
||||
return (key && value) ? Type(TableType, key, value) : NULL;
|
||||
}
|
||||
case FunctionType: {
|
||||
auto fn1 = Match(t1, FunctionType);
|
||||
auto fn2 = Match(t2, FunctionType);
|
||||
arg_t *args = NULL;
|
||||
for (arg_t *arg1 = fn1->args, *arg2 = fn2->args; arg1 || arg2; arg1 = arg1->next, arg2 = arg2->next) {
|
||||
if (!arg1 || !arg2)
|
||||
return NULL;
|
||||
|
||||
type_t *arg_type = most_complete_type(arg1->type, arg2->type);
|
||||
if (!arg_type) return NULL;
|
||||
args = new(arg_t, .type=arg_type, .next=args);
|
||||
}
|
||||
REVERSE_LIST(args);
|
||||
type_t *ret = most_complete_type(fn1->ret, fn2->ret);
|
||||
return ret ? Type(FunctionType, .args=args, .ret=ret) : NULL;
|
||||
}
|
||||
case ClosureType: {
|
||||
type_t *fn = most_complete_type(Match(t1, ClosureType)->fn, Match(t1, ClosureType)->fn);
|
||||
return fn ? Type(ClosureType, fn) : NULL;
|
||||
}
|
||||
case PointerType: {
|
||||
auto ptr1 = Match(t1, PointerType);
|
||||
auto ptr2 = Match(t2, PointerType);
|
||||
if (ptr1->is_stack != ptr2->is_stack)
|
||||
return NULL;
|
||||
type_t *pointed = most_complete_type(ptr1->pointed, ptr2->pointed);
|
||||
return pointed ? Type(PointerType, .is_stack=ptr1->is_stack, .pointed=pointed) : NULL;
|
||||
}
|
||||
default: {
|
||||
if (is_incomplete_type(t1) || is_incomplete_type(t2))
|
||||
return NULL;
|
||||
return type_eq(t1, t2) ? t1 : NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0
|
||||
|
@ -147,6 +147,8 @@ PUREFUNC const char *enum_single_value_tag(type_t *enum_type, type_t *t);
|
||||
PUREFUNC bool is_int_type(type_t *t);
|
||||
PUREFUNC bool is_numeric_type(type_t *t);
|
||||
PUREFUNC bool is_packed_data(type_t *t);
|
||||
CONSTFUNC bool is_incomplete_type(type_t *t);
|
||||
CONSTFUNC type_t *most_complete_type(type_t *t1, type_t *t2);
|
||||
PUREFUNC size_t type_size(type_t *t);
|
||||
PUREFUNC size_t type_align(type_t *t);
|
||||
PUREFUNC size_t unpadded_struct_size(type_t *t);
|
||||
|
@ -1,7 +1,7 @@
|
||||
func main():
|
||||
do:
|
||||
>> [:Num32]
|
||||
= [:Num32]
|
||||
>> nums : [Num32] = []
|
||||
= []
|
||||
|
||||
do:
|
||||
>> arr := [10, 20, 30]
|
||||
@ -104,18 +104,18 @@ func main():
|
||||
>> heap := @[(i * 1337) mod 37 for i in 10]
|
||||
>> heap:heapify()
|
||||
>> heap
|
||||
sorted := @[:Int]
|
||||
sorted : @[Int] = @[]
|
||||
repeat:
|
||||
sorted:insert(heap:heap_pop() or stop)
|
||||
>> sorted == sorted:sorted()
|
||||
>> sorted[] == sorted:sorted()
|
||||
= yes
|
||||
for i in 10:
|
||||
heap:heap_push((i*13337) mod 37)
|
||||
>> heap
|
||||
sorted = @[:Int]
|
||||
sorted = @[]
|
||||
repeat:
|
||||
sorted:insert(heap:heap_pop() or stop)
|
||||
>> sorted == sorted:sorted()
|
||||
>> sorted[] == sorted:sorted()
|
||||
= yes
|
||||
|
||||
do:
|
||||
@ -162,10 +162,10 @@ func main():
|
||||
>> ["a", "b", "c"]:find("b")
|
||||
= 2?
|
||||
>> ["a", "b", "c"]:find("XXX")
|
||||
= none:Int
|
||||
= none
|
||||
|
||||
>> [10, 20]:first(func(i:&Int): i:is_prime())
|
||||
= none:Int
|
||||
= none
|
||||
>> [4, 5, 6]:first(func(i:&Int): i:is_prime())
|
||||
= 2?
|
||||
|
||||
@ -181,6 +181,6 @@ func main():
|
||||
= &[10, 30, 40]
|
||||
>> nums:clear()
|
||||
>> nums
|
||||
= &[:Int]
|
||||
= &[]
|
||||
>> nums:pop()
|
||||
= none:Int
|
||||
= none
|
||||
|
@ -1,6 +1,6 @@
|
||||
func main():
|
||||
x := 123
|
||||
nums := @[:Int]
|
||||
nums : @[Int] = @[]
|
||||
do:
|
||||
defer:
|
||||
nums:insert(x)
|
||||
|
@ -32,18 +32,18 @@ func table_key_str(t:{Text=Text} -> Text):
|
||||
func main():
|
||||
>> all_nums([10,20,30])
|
||||
= "10,20,30,"
|
||||
>> all_nums([:Int])
|
||||
>> all_nums([])
|
||||
= "EMPTY"
|
||||
|
||||
>> labeled_nums([10,20,30])
|
||||
= "1:10,2:20,3:30,"
|
||||
>> labeled_nums([:Int])
|
||||
>> labeled_nums([])
|
||||
= "EMPTY"
|
||||
|
||||
>> t := {"key1"="value1", "key2"="value2"}
|
||||
>> table_str(t)
|
||||
= "key1:value1,key2:value2,"
|
||||
>> table_str({:Text=Text})
|
||||
>> table_str({})
|
||||
= "EMPTY"
|
||||
|
||||
>> table_key_str(t)
|
||||
|
@ -8,11 +8,11 @@ func returns_imported_type(->ImportedType):
|
||||
return get_value() # Imported from ./use_import.tm
|
||||
|
||||
func main():
|
||||
>> [:vectors.Vec2]
|
||||
>> empty : [vectors.Vec2] = []
|
||||
>> returns_vec()
|
||||
= Vec2(x=1, y=2)
|
||||
|
||||
>> [:ImportedType]
|
||||
>> imported : [ImportedType] = []
|
||||
>> returns_imported_type()
|
||||
= ImportedType("Hello")
|
||||
|
||||
|
@ -4,7 +4,7 @@ struct Pair(x:Text, y:Text)
|
||||
func pairwise(strs:[Text] -> func(->Pair?)):
|
||||
i := 1
|
||||
return func():
|
||||
if i + 1 > strs.length: return none:Pair
|
||||
if i + 1 > strs.length: return none
|
||||
i += 1
|
||||
return Pair(strs[i-1], strs[i])?
|
||||
|
||||
@ -12,7 +12,7 @@ func range(first:Int, last:Int -> func(->Int?)):
|
||||
i := first
|
||||
return func():
|
||||
if i > last:
|
||||
return none:Int
|
||||
return none
|
||||
i += 1
|
||||
return (i-1)?
|
||||
|
||||
@ -25,7 +25,7 @@ func main():
|
||||
= ["AB", "BC", "CD"]
|
||||
|
||||
do:
|
||||
result := @[:Text]
|
||||
result : @[Text] = @[]
|
||||
for foo in pairwise(values):
|
||||
result:insert("$(foo.x)$(foo.y)")
|
||||
>> result[]
|
||||
|
32
test/nums.tm
32
test/nums.tm
@ -22,34 +22,36 @@ func main():
|
||||
>> Num.INF:isinf()
|
||||
= yes
|
||||
|
||||
>> nan := none : Num
|
||||
= none:Num
|
||||
>> nan == nan
|
||||
>> none_num : Num? = none
|
||||
= none
|
||||
>> none_num == none_num
|
||||
= yes
|
||||
>> nan < nan
|
||||
>> none_num < none_num
|
||||
= no
|
||||
>> nan > nan
|
||||
>> none_num > none_num
|
||||
= no
|
||||
>> nan != nan
|
||||
>> none_num != none_num
|
||||
= no
|
||||
>> nan <> nan
|
||||
>> none_num <> none_num
|
||||
= Int32(0)
|
||||
>> nan == 0.0
|
||||
>> none_num == 0.0
|
||||
= no
|
||||
>> nan < 0.0
|
||||
>> none_num < 0.0
|
||||
= yes
|
||||
>> nan > 0.0
|
||||
>> none_num > 0.0
|
||||
= no
|
||||
>> nan != 0.0
|
||||
>> none_num != 0.0
|
||||
= yes
|
||||
>> nan <> 0.0
|
||||
>> none_num <> 0.0
|
||||
= Int32(-1)
|
||||
|
||||
>> nan + 1
|
||||
= none:Num
|
||||
# >> nan + 1
|
||||
# = none
|
||||
|
||||
>> 0./0.
|
||||
= none:Num
|
||||
|
||||
# >> 0./0.
|
||||
# = none
|
||||
|
||||
>> Num.PI:cos()!:near(-1)
|
||||
= yes
|
||||
|
@ -66,7 +66,8 @@ func main():
|
||||
= 5?
|
||||
|
||||
>> if no:
|
||||
none:Int
|
||||
x : Int? = none
|
||||
x
|
||||
else:
|
||||
5
|
||||
= 5?
|
||||
@ -80,7 +81,8 @@ func main():
|
||||
>> 5? or exit("Non-null is falsey")
|
||||
= 5
|
||||
|
||||
>> (none:Int) or -1
|
||||
>> none_int : Int? = none
|
||||
>> none_int or -1
|
||||
= -1
|
||||
|
||||
do:
|
||||
@ -88,7 +90,7 @@ func main():
|
||||
>> yep := maybe_int(yes)
|
||||
= 123?
|
||||
>> nope := maybe_int(no)
|
||||
= none:Int
|
||||
= none
|
||||
>> if yep:
|
||||
>> yep
|
||||
= 123
|
||||
@ -103,7 +105,7 @@ func main():
|
||||
>> yep := maybe_int64(yes)
|
||||
= Int64(123)?
|
||||
>> nope := maybe_int64(no)
|
||||
= none:Int64
|
||||
= none
|
||||
>> if yep:
|
||||
>> yep
|
||||
= Int64(123)
|
||||
@ -118,7 +120,7 @@ func main():
|
||||
>> yep := maybe_array(yes)
|
||||
= [10, 20, 30]?
|
||||
>> nope := maybe_array(no)
|
||||
= none:[Int]
|
||||
= none
|
||||
>> if yep:
|
||||
>> yep
|
||||
= [10, 20, 30]
|
||||
@ -133,7 +135,7 @@ func main():
|
||||
>> yep := maybe_bool(yes)
|
||||
= no?
|
||||
>> nope := maybe_bool(no)
|
||||
= none:Bool
|
||||
= none
|
||||
>> if yep:
|
||||
>> yep
|
||||
= no
|
||||
@ -148,7 +150,7 @@ func main():
|
||||
>> yep := maybe_text(yes)
|
||||
= "Hello"?
|
||||
>> nope := maybe_text(no)
|
||||
= none:Text
|
||||
= none
|
||||
>> if yep:
|
||||
>> yep
|
||||
= "Hello"
|
||||
@ -163,7 +165,7 @@ func main():
|
||||
>> yep := maybe_num(yes)
|
||||
= 12.3?
|
||||
>> nope := maybe_num(no)
|
||||
= none:Num
|
||||
= none
|
||||
>> if yep:
|
||||
>> yep
|
||||
= 12.3
|
||||
@ -178,7 +180,7 @@ func main():
|
||||
# >> yep := maybe_lambda(yes)
|
||||
# = func() [optionals.tm:54] : func()?
|
||||
>> nope := maybe_lambda(no)
|
||||
= none : func()
|
||||
= none
|
||||
# >> if yep:
|
||||
# >> yep
|
||||
# = func() [optionals.tm:54]
|
||||
@ -193,7 +195,7 @@ func main():
|
||||
>> yep := Struct.maybe(yes)
|
||||
= Struct(x=123, y="hello")?
|
||||
>> nope := Struct.maybe(no)
|
||||
= none:Struct
|
||||
= none
|
||||
>> if yep:
|
||||
>> yep
|
||||
= Struct(x=123, y="hello")
|
||||
@ -208,7 +210,7 @@ func main():
|
||||
>> yep := Enum.maybe(yes)
|
||||
= Enum.Y(123)?
|
||||
>> nope := Enum.maybe(no)
|
||||
= none : Enum
|
||||
= none
|
||||
>> if yep:
|
||||
>> yep
|
||||
= Enum.Y(123)
|
||||
@ -223,7 +225,7 @@ func main():
|
||||
>> yep := maybe_c_string(yes)
|
||||
= CString("hi")?
|
||||
>> nope := maybe_c_string(no)
|
||||
= none : CString
|
||||
= none
|
||||
>> if yep:
|
||||
>> yep
|
||||
= CString("hi")
|
||||
@ -241,16 +243,15 @@ func main():
|
||||
= 123
|
||||
|
||||
# Test comparisons, hashing, equality:
|
||||
>> (none:Int == 5?)
|
||||
>> (none == 5?)
|
||||
= no
|
||||
>> (5? == 5?)
|
||||
= yes
|
||||
>> {none:Int, none:Int}
|
||||
= {none:Int}
|
||||
>> {:Int? none, none}
|
||||
= {none:Int}
|
||||
>> [5?, none:Int, none:Int, 6?]:sorted()
|
||||
= [none:Int, none:Int, 5, 6]
|
||||
>> nones : {Int?} = {none, none}
|
||||
>> also_nones : {Int?} = {none}
|
||||
>> nones == also_nones
|
||||
>> [5?, none, none, 6?]:sorted()
|
||||
= [none, none, 5, 6]
|
||||
|
||||
do:
|
||||
>> value := if var := 5?:
|
||||
@ -260,7 +261,7 @@ func main():
|
||||
= 5
|
||||
|
||||
do:
|
||||
>> value := if var := none:Int:
|
||||
>> value := if var : Int? = none:
|
||||
var
|
||||
else:
|
||||
0
|
||||
@ -274,7 +275,7 @@ func main():
|
||||
>> opt
|
||||
|
||||
do:
|
||||
>> opt := none:Int
|
||||
>> opt : Int? = none
|
||||
>> if opt:
|
||||
>> opt
|
||||
else:
|
||||
@ -283,7 +284,8 @@ func main():
|
||||
>> not 5?
|
||||
= no
|
||||
|
||||
>> not none:Int
|
||||
>> nah : Int? = none
|
||||
>> not nah
|
||||
= yes
|
||||
|
||||
>> [Struct(5,"A")?, Struct(6,"B"), Struct(7,"C")]
|
||||
|
@ -24,8 +24,8 @@ func main():
|
||||
>> tmpfile:append("!")
|
||||
>> tmpfile:read()
|
||||
= "Hello world!"?
|
||||
>> tmpfile:read_bytes()
|
||||
= [:Byte, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x77, 0x6F, 0x72, 0x6C, 0x64, 0x21]?
|
||||
>> tmpfile:read_bytes()!
|
||||
= [0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x77, 0x6F, 0x72, 0x6C, 0x64, 0x21]
|
||||
>> tmpdir:files():has(tmpfile)
|
||||
= yes
|
||||
|
||||
@ -36,9 +36,9 @@ func main():
|
||||
fail("Couldn't read lines in $tmpfile")
|
||||
|
||||
>> (./does-not-exist.xxx):read()
|
||||
= none : Text
|
||||
= none
|
||||
>> (./does-not-exist.xxx):read_bytes()
|
||||
= none : [Byte]
|
||||
= none
|
||||
if lines := (./does-not-exist.xxx):by_line():
|
||||
fail("I could read lines in a nonexistent file")
|
||||
else:
|
||||
|
@ -4,13 +4,14 @@ func main():
|
||||
>> (+: [10, 20, 30])
|
||||
= 60?
|
||||
|
||||
>> (+: [:Int])
|
||||
= none : Int
|
||||
>> empty_ints : [Int] = []
|
||||
>> (+: empty_ints)
|
||||
= none
|
||||
|
||||
>> (+: [10, 20, 30]) or 0
|
||||
= 60
|
||||
|
||||
>> (+: [:Int]) or 0
|
||||
>> (+: empty_ints) or 0
|
||||
= 0
|
||||
|
||||
>> (_max_: [3, 5, 2, 1, 4])
|
||||
@ -36,8 +37,8 @@ func main():
|
||||
>> (<=: [1, 2, 2, 3, 4])!
|
||||
= yes
|
||||
|
||||
>> (<=: [:Int])
|
||||
= none : Bool
|
||||
>> (<=: empty_ints)
|
||||
= none
|
||||
|
||||
>> (<=: [5, 4, 3, 2, 1])!
|
||||
= no
|
||||
|
@ -1,5 +1,5 @@
|
||||
|
||||
struct Foo(name:Text, next=none:@Foo)
|
||||
struct Foo(name:Text, next:@Foo?=none)
|
||||
|
||||
enum MyEnum(Zero, One(x:Int), Two(x:Num, y:Text))
|
||||
|
||||
@ -82,7 +82,7 @@ func main():
|
||||
= yes
|
||||
|
||||
do:
|
||||
>> obj := none:Num
|
||||
>> obj : Num? = none
|
||||
>> bytes := obj:serialized()
|
||||
>> deserialize(bytes -> Num?) == obj
|
||||
= yes
|
||||
|
@ -2,11 +2,11 @@
|
||||
struct Single(x:Int)
|
||||
struct Pair(x,y:Int)
|
||||
struct Mixed(x:Int, text:Text)
|
||||
struct LinkedList(x:Int, next=none:@LinkedList)
|
||||
struct LinkedList(x:Int, next:@LinkedList?=none)
|
||||
struct Password(text:Text; secret)
|
||||
|
||||
struct CorecursiveA(other:@CorecursiveB?)
|
||||
struct CorecursiveB(other=none:@CorecursiveA)
|
||||
struct CorecursiveB(other:@CorecursiveA?=none)
|
||||
|
||||
func test_literals():
|
||||
>> Single(123)
|
||||
|
@ -7,7 +7,7 @@ func main():
|
||||
>> t["two"]
|
||||
= 2?
|
||||
>> t["???"]
|
||||
= none:Int
|
||||
= none
|
||||
>> t["one"]!
|
||||
= 1
|
||||
>> t["???"] or -1
|
||||
@ -22,7 +22,7 @@ func main():
|
||||
>> t.length
|
||||
= 2
|
||||
>> t.fallback
|
||||
= none : {Text=Int}
|
||||
= none
|
||||
|
||||
>> t.keys
|
||||
= ["one", "two"]
|
||||
@ -37,7 +37,7 @@ func main():
|
||||
>> t2["three"]
|
||||
= 3?
|
||||
>> t2["???"]
|
||||
= none:Int
|
||||
= none
|
||||
|
||||
>> t2.length
|
||||
= 1
|
||||
@ -96,11 +96,11 @@ func main():
|
||||
|
||||
>> {1=1, 2=2} <> {2=2, 1=1}
|
||||
= Int32(0)
|
||||
>> [{:Int=Int}, {0=0}, {99=99}, {1=1, 2=2, 3=3}, {1=1, 99=99, 3=3}, {1=1, 2=-99, 3=3}, {1=1, 99=-99, 3=4}]:sorted()
|
||||
= [{:Int=Int}, {0=0}, {1=1, 2=-99, 3=3}, {1=1, 2=2, 3=3}, {1=1, 99=99, 3=3}, {1=1, 99=-99, 3=4}, {99=99}]
|
||||
>> ints : [{Int=Int}] = [{}, {0=0}, {99=99}, {1=1, 2=2, 3=3}, {1=1, 99=99, 3=3}, {1=1, 2=-99, 3=3}, {1=1, 99=-99, 3=4}]:sorted()
|
||||
= [{}, {0=0}, {1=1, 2=-99, 3=3}, {1=1, 2=2, 3=3}, {1=1, 99=99, 3=3}, {1=1, 99=-99, 3=4}, {99=99}]
|
||||
|
||||
>> [{:Int}, {1}, {2}, {99}, {0, 3}, {1, 2}, {99}]:sorted()
|
||||
= [{:Int}, {0, 3}, {1}, {1, 2}, {2}, {99}, {99}]
|
||||
>> other_ints : [{Int}] = [{/}, {1}, {2}, {99}, {0, 3}, {1, 2}, {99}]:sorted()
|
||||
= [{/}, {0, 3}, {1}, {1, 2}, {2}, {99}, {99}]
|
||||
|
||||
do:
|
||||
# Default values:
|
||||
|
22
test/text.tm
22
test/text.tm
@ -53,21 +53,21 @@ func main():
|
||||
>> amelie:split()
|
||||
= ["A", "m", "é", "l", "i", "e"]
|
||||
>> amelie:utf32_codepoints()
|
||||
= [:Int32, 65, 109, 233, 108, 105, 101]
|
||||
= [65, 109, 233, 108, 105, 101]
|
||||
>> amelie:bytes()
|
||||
= [:Byte, 0x41, 0x6D, 0xC3, 0xA9, 0x6C, 0x69, 0x65]
|
||||
>> Text.from_bytes([:Byte 0x41, 0x6D, 0xC3, 0xA9, 0x6C, 0x69, 0x65])!
|
||||
= [0x41, 0x6D, 0xC3, 0xA9, 0x6C, 0x69, 0x65]
|
||||
>> Text.from_bytes([0x41, 0x6D, 0xC3, 0xA9, 0x6C, 0x69, 0x65])!
|
||||
= "Amélie"
|
||||
>> Text.from_bytes([Byte(0xFF)])
|
||||
= none:Text
|
||||
= none
|
||||
|
||||
amelie2 := "Am$(\U65\U301)lie"
|
||||
>> amelie2:split()
|
||||
= ["A", "m", "é", "l", "i", "e"]
|
||||
>> amelie2:utf32_codepoints()
|
||||
= [:Int32, 65, 109, 233, 108, 105, 101]
|
||||
= [65, 109, 233, 108, 105, 101]
|
||||
>> amelie2:bytes()
|
||||
= [:Byte, 0x41, 0x6D, 0xC3, 0xA9, 0x6C, 0x69, 0x65]
|
||||
= [0x41, 0x6D, 0xC3, 0xA9, 0x6C, 0x69, 0x65]
|
||||
|
||||
>> amelie:codepoint_names()
|
||||
= ["LATIN CAPITAL LETTER A", "LATIN SMALL LETTER M", "LATIN SMALL LETTER E WITH ACUTE", "LATIN SMALL LETTER L", "LATIN SMALL LETTER I", "LATIN SMALL LETTER E"]
|
||||
@ -136,7 +136,7 @@ func main():
|
||||
>> "one$(\r\n)two$(\r\n)three$(\r\n)":lines()
|
||||
= ["one", "two", "three"]
|
||||
>> "":lines()
|
||||
= [:Text]
|
||||
= []
|
||||
|
||||
!! Test splitting and joining text:
|
||||
>> "one,, two,three":split(",")
|
||||
@ -171,11 +171,11 @@ func main():
|
||||
>> "+":join(["one"])
|
||||
= "one"
|
||||
|
||||
>> "+":join([:Text])
|
||||
>> "+":join([])
|
||||
= ""
|
||||
|
||||
>> "":split()
|
||||
= [:Text]
|
||||
= []
|
||||
|
||||
!! Test text slicing:
|
||||
>> "abcdef":slice()
|
||||
@ -196,13 +196,13 @@ func main():
|
||||
>> house:codepoint_names()
|
||||
= ["CJK Unified Ideographs-5BB6"]
|
||||
>> house:utf32_codepoints()
|
||||
= [:Int32, 23478]
|
||||
= [23478]
|
||||
|
||||
>> "🐧":codepoint_names()
|
||||
= ["PENGUIN"]
|
||||
|
||||
>> Text.from_codepoint_names(["not a valid name here buddy"])
|
||||
= none : Text
|
||||
= none
|
||||
|
||||
>> "Hello":replace("ello", "i")
|
||||
= "Hi"
|
||||
|
Loading…
Reference in New Issue
Block a user