Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/GRAMMAR.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ keyword = "if" | "else" | "while" | "for" | "in" | "return"
| "var" | "let" | "fn" | "struct" | "enum" | "macro"
| "interface" | "where" | "arena" | "as"
| "true" | "false"
| "void" | "bool" | "i8" | "i32" | "u32" | "f32" | "f64" ;
| "void" | "bool" | "i8" | "u8" | "i32" | "u32" | "f32" | "f64" ;

(* Operators and punctuation *)
(* -> or U+2192 (→) arrow
Expand All @@ -48,7 +48,7 @@ keyword = "if" | "else" | "while" | "for" | "in" | "return"
type = basic_type { "->" basic_type } ;

basic_type = "void" | "bool"
| "i8" | "i32" | "u32" | "f32" | "f64"
| "i8" | "u8" | "i32" | "u32" | "f32" | "f64"
| "⟨" id "⟩"
| "typevar" id
| "[" type "]" (* slice *)
Expand Down
6 changes: 4 additions & 2 deletions docs/language-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ Note for Lua users: Lua lets one variable hold different kinds of values over ti
| `f32` | A decimal number (float) | `3.14`, `-0.5`, `1.0` |
| `bool` | True or false | `true`, `false` |
| `str` | Text (a string in quotes) | `"hello"` |
| `i8` | A very small whole number, used for individual characters | `65` (the letter `A`) |
| `i8` | A very small whole number from -128 to 127, used for individual characters. Supports comparisons and casts but not arithmetic | `65` (the letter `A`) |
| `u8` | A small whole number from 0 to 255 (an unsigned byte). Like `i8`, it supports casts and equality but not arithmetic or ordering | `(200 as u8)`, `(255 as u8)` |
| `u32` | A whole number that cannot be negative (unsigned) | `42`, `0` |

Single characters can also be written with single quotes: `'a'`, `'Z'`, `'\n'` (newline). This is called a character literal.
Expand Down Expand Up @@ -1326,7 +1327,8 @@ This example brings together structs, `f32` math, tuples, and functions — all
| `f32` | Decimal number |
| `bool` | `true` or `false` |
| `str` | Text in double quotes |
| `i8` | Very small whole number (used for characters) |
| `i8` | Very small whole number, -128 to 127 (used for characters; comparisons and casts, no arithmetic) |
| `u8` | Unsigned byte, 0 to 255 (casts only; no arithmetic or comparison) |
| `u32` | Whole number, cannot be negative |
| `f64` | Higher-precision decimal — present in the language but may not apply in Audulus |

Expand Down
12 changes: 12 additions & 0 deletions src/checker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ impl Default for Checker {
impl Checker {
pub fn new() -> Self {
let int8 = mk_type(Type::Int8);
let uint8 = mk_type(Type::UInt8);
let int32: TypeID = mk_type(Type::Int32);
let uint32: TypeID = mk_type(Type::UInt32);
let types = [Type::Int32, Type::UInt32, Type::Float32, Type::Float64];
Expand All @@ -176,6 +177,9 @@ impl Checker {
neg_overloads.push(func(t, t));
}

// i8: relational comparisons only (no arithmetic; widen to i32 for that)
rel_overloads.push(func(tuple(vec![int8, int8]), b));

// f32x4: arithmetic and negation, but not relational comparisons
let f32x4 = mk_type(Type::Float32x4);
let f32x4_pair = tuple(vec![f32x4, f32x4]);
Expand All @@ -187,6 +191,14 @@ impl Checker {
let cast_overloads = vec![
func(int8, int32),
func(int32, int8),
func(uint8, int32),
func(int32, uint8),
func(uint8, uint32),
func(uint32, uint8),
func(int8, uint8),
func(uint8, int8),
func(int8, uint32),
func(uint32, int8),
func(int32, uint32),
func(uint32, int32),
func(int32, float32),
Expand Down
12 changes: 10 additions & 2 deletions src/jit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2025,8 +2025,16 @@ impl<'a> FunctionTranslator<'a> {
(crate::Type::Float64, crate::Type::Float32) => {
self.builder.ins().fdemote(F32, val)
}
(crate::Type::Int8, crate::Type::Int32) => self.builder.ins().sextend(I32, val),
(crate::Type::Int32, crate::Type::Int8) => self.builder.ins().ireduce(I8, val),
(crate::Type::Int8, crate::Type::Int32 | crate::Type::UInt32) => {
self.builder.ins().sextend(I32, val)
}
(crate::Type::UInt8, crate::Type::Int32 | crate::Type::UInt32) => {
self.builder.ins().uextend(I32, val)
}
(
crate::Type::Int32 | crate::Type::UInt32,
crate::Type::Int8 | crate::Type::UInt8,
) => self.builder.ins().ireduce(I8, val),
_ => val,
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ pub enum Token {
Void,
Bool,
Int8,
UInt8,
Int32,
UInt32,
Int64,
Expand Down Expand Up @@ -200,6 +201,7 @@ impl Lexer {
"while" => Token::While,
"void" => Token::Void,
"i8" => Token::Int8,
"u8" => Token::UInt8,
"i32" => Token::Int32,
"u32" => Token::UInt32,
"f32" => Token::Float32,
Expand Down Expand Up @@ -646,6 +648,7 @@ mod tests {
assert_eq!(tokens("1.0u"), vec![InvalidNumberSuffix(Name::str("u"))]);
assert_eq!(tokens("void"), vec![Void]);
assert_eq!(tokens("i8"), vec![Int8]);
assert_eq!(tokens("u8"), vec![UInt8]);
assert_eq!(tokens("i32"), vec![Int32]);
assert_eq!(tokens("⟨"), vec![Lmath]);
assert_eq!(tokens("⟩"), vec![Rmath]);
Expand Down
9 changes: 7 additions & 2 deletions src/llvm_jit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3122,12 +3122,17 @@ impl<'a, 'ctx> FunctionTranslator<'a, 'ctx> {
.build_float_cast(val.into_float_value(), self.state.f32_ty(), "fdemote")
.unwrap()
.into(),
(crate::Type::Int8, crate::Type::Int32) => self
(crate::Type::Int8, crate::Type::Int32 | crate::Type::UInt32) => self
.builder()
.build_int_s_extend(val.into_int_value(), self.i32_ty(), "sext")
.unwrap()
.into(),
(crate::Type::Int32, crate::Type::Int8) => self
(crate::Type::UInt8, crate::Type::Int32 | crate::Type::UInt32) => self
.builder()
.build_int_z_extend(val.into_int_value(), self.i32_ty(), "zext")
.unwrap()
.into(),
(crate::Type::Int32 | crate::Type::UInt32, crate::Type::Int8 | crate::Type::UInt8) => self
.builder()
.build_int_truncate(val.into_int_value(), self.i8_ty(), "trunc")
.unwrap()
Expand Down
5 changes: 5 additions & 0 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ fn parse_basic_type(typevars: &[Name], cx: &mut ParseContext) -> TypeID {
cx.next();
Type::Int8
}
Token::UInt8 => {
cx.next();
Type::UInt8
}
Token::Int32 => {
cx.next();
Type::Int32
Expand Down Expand Up @@ -1314,6 +1318,7 @@ mod tests {
let func = mk_type(Type::Func(tup, int8));
test_type("void", mk_type(Type::Void));
test_type("i8", int8);
test_type("u8", mk_type(Type::UInt8));
test_type("i32", int32);
test_type("⟨T⟩", typevar("T"));
test_type("typevar T", typevar("T"));
Expand Down
15 changes: 15 additions & 0 deletions src/stack_codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3001,6 +3001,21 @@ impl<'a> FunctionTranslator<'a> {
(Type::Float64, Type::Float32) => func.emit(StackOp::F64ToF32D),
(Type::Int32, Type::Int8) | (Type::UInt32, Type::Int8) => func.emit(StackOp::I32ToI8),
(Type::Int8, Type::Int32) => func.emit(StackOp::I8ToI32),
(Type::Int8, Type::UInt32) => {
func.emit(StackOp::I8ToI32);
func.emit(StackOp::I64ToU32);
}
(Type::Int8, Type::UInt8) | (Type::UInt8, Type::Int8) => {
// Same sign-extended byte form on this backend.
}
// This backend's Load8 sign-extends, so a u8 is kept in its
// sign-extended byte form like i8 (that keeps `==` consistent
// between loaded and cast values) and masked when widened.
(Type::Int32 | Type::UInt32, Type::UInt8) => func.emit(StackOp::I32ToI8),
(Type::UInt8, Type::Int32 | Type::UInt32) => {
func.emit(StackOp::I64Const(0xFF));
func.emit(StackOp::And);
}
(Type::Int32, Type::UInt32) | (Type::UInt32, Type::Int32) => {
func.emit(StackOp::I64ToU32)
}
Expand Down
35 changes: 32 additions & 3 deletions src/vm_codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3574,12 +3574,31 @@ impl<'a> FunctionTranslator<'a> {
(Type::Float64, Type::Float32) => {
func.emit(Opcode::F64ToF32 { dst, src });
}
(Type::Int32, Type::Int8) | (Type::UInt32, Type::Int8) => {
(Type::Int32 | Type::UInt32 | Type::UInt8, Type::Int8) => {
func.emit(Opcode::I32ToI8 { dst, src });
}
(Type::Int8, Type::Int32) => {
func.emit(Opcode::I8ToI32 { dst, src });
}
(Type::Int8, Type::UInt32) => {
// Sign-extend to 32 bits, then keep the low 32 bits as u32.
func.emit(Opcode::I8ToI32 { dst, src });
func.emit(Opcode::I64ToU32 { dst, src: dst });
}
(Type::Int32 | Type::UInt32 | Type::Int8, Type::UInt8)
| (Type::UInt8, Type::Int32 | Type::UInt32) => {
// u8 lives in a register as its zero-extended byte value.
let mask = self.alloc_reg();
func.emit(Opcode::LoadImm {
dst: mask,
value: 0xFF,
});
func.emit(Opcode::And {
dst,
a: src,
b: mask,
});
}
(Type::Int32, Type::UInt32) | (Type::UInt32, Type::Int32) => {
// Same size, just reinterpret — mask to u32.
func.emit(Opcode::I64ToU32 { dst, src });
Expand Down Expand Up @@ -3699,7 +3718,12 @@ impl<'a> FunctionTranslator<'a> {
/// Emit a load instruction based on type.
fn emit_load(&self, ty: &TypeID, dst: Reg, addr: Reg, func: &mut VMFunction) {
match &**ty {
Type::Bool | Type::Int8 | Type::UInt8 => {
Type::Int8 => {
// Load8 zero-extends; i8 lives in a register sign-extended.
func.emit(Opcode::Load8 { dst, addr });
func.emit(Opcode::I8ToI32 { dst, src: dst });
}
Type::Bool | Type::UInt8 => {
func.emit(Opcode::Load8 { dst, addr });
}
Type::Int32 | Type::UInt32 | Type::Float32 => {
Expand All @@ -3724,7 +3748,12 @@ impl<'a> FunctionTranslator<'a> {
func: &mut VMFunction,
) {
match &**ty {
Type::Bool | Type::Int8 | Type::UInt8 => {
Type::Int8 => {
let addr = self.emit_offset_addr(base, offset, func);
func.emit(Opcode::Load8 { dst, addr });
func.emit(Opcode::I8ToI32 { dst, src: dst });
}
Type::Bool | Type::UInt8 => {
let addr = self.emit_offset_addr(base, offset, func);
func.emit(Opcode::Load8 { dst, addr });
}
Expand Down
43 changes: 43 additions & 0 deletions tests/cases/int_types_i8_compare.lyte
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// expected stdout:
// compilation successful
// assert(true)
// assert(true)
// assert(true)
// assert(true)
// assert(true)
// assert(true)
// assert(true)
// assert(true)
// assert(true)
// assert(true)
// assert(true)
// assert(true)

main {
let neg = (0 - 1) as i8
let small = 5 as i8
let big = 100 as i8

// Signed ordering on i8.
assert(neg < small)
assert(big > small)
assert(neg <= neg)
assert(big >= small)
assert('a' < 'b')

// A negative i8 loaded from memory equals and orders like a cast one.
var bytes: [i8; 2]
bytes[0] = (0 - 128) as i8
bytes[1] = 127 as i8
assert(bytes[0] == (0 - 128) as i8)
assert(bytes[0] < bytes[1])
assert(bytes[0] < 0 as i8)

// i8 <-> u8 reinterpret the byte.
assert((neg as u8) as i32 == 255)
assert(((200 as u8) as i8) as i32 == -56)

// i8 <-> u32 sign-extend then reinterpret.
assert((neg as u32) == 4294967295u32)
assert(((4294967295u32 as i8) as i32) == -1)
}
44 changes: 44 additions & 0 deletions tests/cases/int_types_u8.lyte
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// expected stdout:
// compilation successful
// assert(true)
// assert(true)
// assert(true)
// assert(true)
// assert(true)
// assert(true)
// assert(true)
// assert(true)

main {
// u8 holds the full 0..255 range, unlike i8.
var a: u8
a = 200 as u8
assert(a as i32 == 200)
assert(a as u32 == 200u32)

var b: u8
b = 255 as u8
assert(b as i32 == 255)

// Narrowing wraps to the low byte.
assert((300 as u8) as i32 == 44)
assert(((0 - 1) as u8) as i32 == 255)
assert((256u32 as u8) as i32 == 0)

// u8 round-trips through memory as an unsigned byte.
var bytes: [u8; 4]
bytes[0] = 128 as u8
bytes[1] = 255 as u8
bytes[2] = 1 as u8
bytes[3] = 0 as u8
var sum = 0
var i = 0
while i < 4 {
sum = sum + bytes[i] as i32
i = i + 1
}
assert(sum == 384)

// Equality between u8 values.
assert(bytes[1] == b)
}
27 changes: 14 additions & 13 deletions tests/cases/slices/empty_string_bytecode.lyte
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,20 @@
// 8: IMul { dst: 4, a: 2, b: 3 }
// 9: IAdd { dst: 2, a: 1, b: 4 }
// 10: Load8 { dst: 3, addr: 2 }
// 11: LoadImm { dst: 2, value: 0 }
// 12: I32ToI8 { dst: 4, src: 2 }
// 13: IEq { dst: 2, a: 3, b: 4 }
// 14: Assert { src: 2 }
// 15: LocalAddr { dst: 2, slot: 2 }
// 16: LoadImm { dst: 3, value: 0 }
// 17: Store8Off { base: 2, offset: 0, src: 3 }
// 18: MemEq { dst: 3, a: 1, b: 2, size: 1 }
// 19: Assert { src: 3 }
// 20: LoadImm { dst: 1, value: 0 }
// 21: Move { dst: 0, src: 1 }
// 22: RestoreRegs { start_reg: 1, count: 4, slot: 32 }
// 23: Return
// 11: I8ToI32 { dst: 3, src: 3 }
// 12: LoadImm { dst: 2, value: 0 }
// 13: I32ToI8 { dst: 4, src: 2 }
// 14: IEq { dst: 2, a: 3, b: 4 }
// 15: Assert { src: 2 }
// 16: LocalAddr { dst: 2, slot: 2 }
// 17: LoadImm { dst: 3, value: 0 }
// 18: Store8Off { base: 2, offset: 0, src: 3 }
// 19: MemEq { dst: 3, a: 1, b: 2, size: 1 }
// 20: Assert { src: 3 }
// 21: LoadImm { dst: 1, value: 0 }
// 22: Move { dst: 0, src: 1 }
// 23: RestoreRegs { start_reg: 1, count: 4, slot: 32 }
// 24: Return

main {
var s = ""
Expand Down
Loading