Update spec & examples
This commit is contained in:
parent
38e1e77aed
commit
1e5e366dfa
2 changed files with 180 additions and 47 deletions
|
|
@ -28,7 +28,7 @@ func merge(x: u32, y: u32) -> u64 {
|
|||
mut v = vec2(10, 20)
|
||||
length(v)
|
||||
|
||||
// can be called using method syntax
|
||||
// TODO: can be called using method syntax
|
||||
v.length()
|
||||
|
||||
// function pointers
|
||||
|
|
|
|||
225
spec.txt
225
spec.txt
|
|
@ -73,14 +73,14 @@ Null pointer literal:
|
|||
let p: u32* = null // special like empty array literal, type cannot be inferred
|
||||
|
||||
Variables must always be initialized. // TODO: really? What about arrays? Maybe need special syntax for zero-initialization or mass-initialization. Alternative: default to zero-initialization
|
||||
Const variables must be initialized with a const expression (any expression that doesn't include non-const values).
|
||||
Const variables must be initialized with a const expression (any expression that doesn't include non-const values & calls only pure functions).
|
||||
|
||||
======== OPERATORS ========
|
||||
|
||||
Logical (only bool type):
|
||||
!x
|
||||
x & y
|
||||
x | y
|
||||
x & y // evaluates both arguments
|
||||
x | y // evaluates both arguments
|
||||
x && y // short-circuit
|
||||
x || y // short-circuit
|
||||
x ^ y
|
||||
|
|
@ -97,8 +97,8 @@ Comparison (all built-in types, all pointer types, all array/struct types, only
|
|||
|
||||
Bitwise (integer types, only same type):
|
||||
!x
|
||||
x & y
|
||||
x | y
|
||||
x & y // evaluates both arguments
|
||||
x | y // evaluates both arguments
|
||||
x && y // short-circuit
|
||||
x || y // short-circuit
|
||||
x ^ y
|
||||
|
|
@ -120,13 +120,14 @@ Pointer arithmetic (any pointer type + any integer type):
|
|||
p - q // returns i64
|
||||
|
||||
Pointer arithmetic works element-wise (like C or C++), i.e. p + n advances by n * sizeof(T) when typeof(p) is *T
|
||||
Pointer arithmetic is prohibited on empty types
|
||||
|
||||
Casting:
|
||||
x as u32 // always explicit, no implicit casts allowed
|
||||
|
||||
The only implicit casting allowed is T mut* -> T* (maybe?)
|
||||
TODO: The only implicit casting allowed is T mut* -> T* (maybe?)
|
||||
Any integer/floating-point types can be cast to each other.
|
||||
Any pointer types can be cast to each other // TODO: alignment? UB or safe fallback? Probably UB.
|
||||
Any pointer types can be cast to each other // Misalignment => UB
|
||||
|
||||
Ternary if operator:
|
||||
if condition then true_value else false_value
|
||||
|
|
@ -170,16 +171,16 @@ While loop:
|
|||
}
|
||||
}
|
||||
|
||||
Iterator interface:
|
||||
TODO: Iterator interface:
|
||||
get(it) returns the currently pointed-to value
|
||||
get_ref(it) returns the pointer to the currently pointed-to value
|
||||
next(it) returns the next iterator
|
||||
|
||||
Range interface:
|
||||
TODO: Range interface:
|
||||
begin(range) returns the begin iterator
|
||||
end(range) returns the end iterator
|
||||
|
||||
For loop:
|
||||
TODO: For loop:
|
||||
Operates only on ranges.
|
||||
|
||||
for x in range(10) {
|
||||
|
|
@ -216,7 +217,7 @@ Struct types:
|
|||
|
||||
Creating a struct value:
|
||||
let x = rect(10u, 20u)
|
||||
let y = rect(width = 10u, height = 20u) // named function arguments in general?
|
||||
let y = rect(width = 10u, height = 20u) // TODO, also named function arguments in general?
|
||||
|
||||
Struct field access:
|
||||
let r = rect(1u, 2u)
|
||||
|
|
@ -224,7 +225,54 @@ Struct field access:
|
|||
let p = &r
|
||||
let y = p.height // field access through pointer is the same
|
||||
|
||||
// TODO: inner struct functions maybe? to act as namespace/module containers
|
||||
======== ENUMS (TODO) ========
|
||||
|
||||
Simplified enum syntax:
|
||||
enum buffer_usage {
|
||||
read
|
||||
write
|
||||
vertex
|
||||
index
|
||||
uniform
|
||||
}
|
||||
|
||||
Stored as tag + payload bytes
|
||||
Tag inferred as minimal sufficient unsigned integer
|
||||
|
||||
Explicit tag type:
|
||||
enum color: u32 {
|
||||
...
|
||||
}
|
||||
|
||||
Full enum with arguments:
|
||||
enum node {
|
||||
literal: i32,
|
||||
variable: string,
|
||||
unary_op: unary_op,
|
||||
binary_op: binary_op,
|
||||
}
|
||||
|
||||
TODO: match statement
|
||||
TODO: support multi-argument enum values, e.g. unary_op(op_type, node_ptr)? Make them named, i.e. treat them like anonymous structs?
|
||||
Or allow only one type, and require explicit struct types when multi-arg is necessary?
|
||||
|
||||
======== STATIC METHODS/VARIABLES (TODO) ========
|
||||
|
||||
Can be declared for any type:
|
||||
in my_type {
|
||||
const RESULT: type = f32
|
||||
|
||||
const DEFAULT_SIZE = 16
|
||||
|
||||
global mut instance_count = 0
|
||||
|
||||
func new(size: i32) -> my_type {
|
||||
...
|
||||
}
|
||||
}
|
||||
|
||||
Can be only accessed explicitly through the type name:
|
||||
let x = my_type.new(my_type.DEFAULT_SIZE)
|
||||
|
||||
======== FUNCTIONS ========
|
||||
|
||||
|
|
@ -250,11 +298,62 @@ Function definition:
|
|||
foreign func powf(x: f32, y: f32) -> f32 // no implementation
|
||||
|
||||
// TODO: function overloading? Probably requires selecting a specific overload using `as` operator to save to a value (but not on call site)
|
||||
// TODO: alternative - Rust-like traits, aka parametric polymorphism?
|
||||
// TODO: alternative - Rust-like traits, aka parametric polymorphism? Jesus, no, NO, noooo, probably not
|
||||
|
||||
======== TEMPLATES ========
|
||||
======== OPERATOR OVERLOADING (TODO) ========
|
||||
|
||||
Most operators can be overloaded by providing a function with an appropriate name:
|
||||
func add(a: vec2i, b: vec2i) -> vec2i {
|
||||
return vec2i(a.x + b.x, a.y + b.y)
|
||||
}
|
||||
|
||||
======== RAII/MOVE (TODO) ========
|
||||
|
||||
Defer statement:
|
||||
func foo(size: u64) {
|
||||
let temp = malloc(size) as u8 mut*
|
||||
defer free(temp)
|
||||
}
|
||||
|
||||
Can be used with any other statement, e.g.
|
||||
defer if temp != null {
|
||||
free(temp)
|
||||
}
|
||||
|
||||
defer for file in files {
|
||||
file.close()
|
||||
}
|
||||
|
||||
A `destroy` function is automatically called (if present) with pointer argument when a value lifetime ends:
|
||||
func destroy(x: file*) {
|
||||
x.close()
|
||||
}
|
||||
|
||||
Some types can be declared as noncopyable (TODO: how? `move`/`linear` attribute before declataion? Mere presence of `move` function?)
|
||||
A struct/enum containing a noncopyable field must itself be marked as noncopyable
|
||||
Noncopyable types can be moved by calling the `move` function (it must exist for any noncopyable type):
|
||||
move struct file {
|
||||
handle: unit*
|
||||
}
|
||||
|
||||
func move(from: file mut*, to: file mut*) {
|
||||
to.handle = from.handle
|
||||
from.handle = null
|
||||
}
|
||||
|
||||
======== ERROR HANDLING (TODO) ========
|
||||
|
||||
Error type interface:
|
||||
type.from_error(error)
|
||||
type.from_value(value)
|
||||
|
||||
Try expression - immediately returns on error, continues on value:
|
||||
let file = try open("file.txt", file.mode.read)
|
||||
|
||||
TODO: multiple error types? Converting one error to another? `catch` statement?
|
||||
|
||||
======== TEMPLATES (TODO) ========
|
||||
|
||||
// TODO
|
||||
// Definitely monomorphized.
|
||||
// Parametric (C++ templates) vs ad-hoc (Rust traits, Haskell typeclasses)?
|
||||
//
|
||||
|
|
@ -273,7 +372,23 @@ Function definition:
|
|||
// - Harder to use in basic cases (e.g. have to declare type constraints or create new traits for any desired per-type behavior)
|
||||
// - A bunch of new required language concepts (trait, impl, constraint), much more complicated to implement in compiler
|
||||
|
||||
======== TYPE OF TYPES ========
|
||||
Basic idea:
|
||||
// Can be just <t> - assumed to be a type by default
|
||||
struct array_view<t: type> {
|
||||
data: t*
|
||||
size: u64
|
||||
}
|
||||
|
||||
enum option<t: type> {
|
||||
none,
|
||||
some: t,
|
||||
}
|
||||
|
||||
func prefix<t: type>(view: array_view<t>, size: u64) -> array_view<t> {
|
||||
return array_view<t>(view.data, min(view.size, size))
|
||||
}
|
||||
|
||||
======== TYPE OF TYPES (TODO) ========
|
||||
|
||||
Types are also considered to be values. The keyword `type` denotes the type of all types.
|
||||
I.e. `typeof(16) == i32` and `typeof(i32) == type`. Incidentally, `typeof(type) == type` as well; there are no type kinds or etc.
|
||||
|
|
@ -288,32 +403,29 @@ E.g.
|
|||
do_smth()
|
||||
}
|
||||
|
||||
======== CONST EXPRESSIONS ========
|
||||
======== CONST EXPRESSIONS (TODO) ========
|
||||
|
||||
// TODO
|
||||
// Auto-upgrading values to compile-time when a pure function is executed from const-only values?
|
||||
A function that doesn't access global variables is called pure (TODO: pointers? Probably all good but need to think a bit more)
|
||||
An expression involving only const values (const variables, literals), built-in operators, and pure functions can be promoted
|
||||
to be a compile-time expression, it can be evaluated at compile-time.
|
||||
|
||||
======== METAPROGRAMMING ========
|
||||
Some expressions must be compile-time expressions:
|
||||
Initializer of a const variable
|
||||
Array type size
|
||||
|
||||
// TODO
|
||||
// Functions returning functions/structs
|
||||
// Syntactic sugar for common cases
|
||||
// Figure out: max(a,b) - how to deduce type parameters? How does it play with overloading?
|
||||
// func max(t: type) {
|
||||
// return func(x : t, y : t) {
|
||||
// if x > y:
|
||||
// return x
|
||||
// else:
|
||||
// return y
|
||||
// }
|
||||
// }
|
||||
|
||||
======== PRELUDE ========
|
||||
======== PRELUDE (TODO) ========
|
||||
|
||||
Prelude is a special source file implicitly included in any project (unless explicitly requested otherwise).
|
||||
|
||||
It contains:
|
||||
|
||||
Numeric types limits:
|
||||
|
||||
in u8 {
|
||||
const min = 0ub
|
||||
const max = 255ub
|
||||
}
|
||||
|
||||
An array_view template struct:
|
||||
|
||||
struct array_view<t: type> {
|
||||
|
|
@ -341,18 +453,39 @@ It contains:
|
|||
for i in range(5u, 10u) { ... }
|
||||
for i in range(1.0, 10.0, 0.5) { ... }
|
||||
|
||||
======== MODULES AND IMPORTS ========
|
||||
======== PREPROCESSOR-LIKE (TODO) ========
|
||||
|
||||
// TODO
|
||||
// A build system / package metadata? How to e.g. conditionally add some files based on environment (maybe just forbid that)?
|
||||
// How to describe platform-dependent behavior?
|
||||
// * Different files - who decides which files to include?
|
||||
// * Compile-time built-ins - how flexible are they?
|
||||
// Can we create a different type based on platform?
|
||||
// Important for some posix stuff like timespec or threads
|
||||
// * Special compiler intrinsics/attributes/macros/whatever - need a new concept in the language
|
||||
// Maybe a good thing - can merge with alignment specification and other stuff
|
||||
Basic features for conditional code compilation:
|
||||
|
||||
======== STANDARD LIBRARY ========
|
||||
@if(windows) {
|
||||
// Code included only on windows
|
||||
}
|
||||
|
||||
// TODO: containers, memory management, strings, io, math, threads, networking(?)
|
||||
The condition is evaluated in a type const context having just a few constants in scope
|
||||
|
||||
Can be reused for other annotations later (e.g. @vertex for compiling to shaders, etc)
|
||||
|
||||
======== MODULES AND IMPORTS (TODO) ========
|
||||
|
||||
Every file is a separate module
|
||||
All files/directories in a directory are considered submodules
|
||||
Everything is exported by default
|
||||
|
||||
Import statement:
|
||||
// Imports the whole library
|
||||
import math
|
||||
|
||||
// Imports a sub-library (can be a single file)
|
||||
import math.vector
|
||||
|
||||
let x = vec2i(1, 2)
|
||||
|
||||
// Can use explicit name resolution
|
||||
math.vector.vec2i(1, 2)
|
||||
|
||||
// Submodule's names are automatically present in the parent module
|
||||
math.vec2i(1, 2)
|
||||
|
||||
======== STANDARD LIBRARY (TODO) ========
|
||||
|
||||
Containers, memory management, strings, io, math, threads, networking(?)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue