From d4dfbdd8d9ab796ab28b0094513fb9827d2fe47c Mon Sep 17 00:00:00 2001 From: santerijps Date: Fri, 15 May 2026 13:55:00 +0300 Subject: [PATCH 1/2] Add Odin syntax highlighting --- assets/highlighting-tests/markdown.md | 6 + assets/highlighting-tests/odin.odin | 259 ++++++++++++++++++++++++++ crates/lsh/definitions/markdown.lsh | 10 + crates/lsh/definitions/odin.lsh | 66 +++++++ 4 files changed, 341 insertions(+) create mode 100644 assets/highlighting-tests/odin.odin create mode 100644 crates/lsh/definitions/odin.lsh diff --git a/assets/highlighting-tests/markdown.md b/assets/highlighting-tests/markdown.md index 555b182f961..4de2fc4dde0 100644 --- a/assets/highlighting-tests/markdown.md +++ b/assets/highlighting-tests/markdown.md @@ -84,3 +84,9 @@ export function greet(name) { def greet(name: str) -> str: return f"hello {name}" ``` + +```odin +greet :: proc(name: string) -> string { + return fmt.tprintf("hello %s", name) +} +``` diff --git a/assets/highlighting-tests/odin.odin b/assets/highlighting-tests/odin.odin new file mode 100644 index 00000000000..c059d504979 --- /dev/null +++ b/assets/highlighting-tests/odin.odin @@ -0,0 +1,259 @@ +// Single-line comment + +/* + * Multi-line + * comment + */ + +#+feature dynamic-literals +package main + +import "core:fmt" + +// Package-level constants +MY_CONST :: 42 +PI :: 3.14159 +NAME :: "Odin" + +// Struct type +Vector3 :: struct { + x, y, z: f32, +} + +// Union type +Value :: union { + int, + f64, + string, +} + +// Enum type +Direction :: enum { + North, + South, + East, + West, +} + +// Bit set +Flags :: bit_set[Direction] + +// Distinct type +Meters :: distinct f32 + +// Procedures +add :: proc(a, b: int) -> int { + return a + b +} + +variadic :: proc(nums: ..int) -> int { + result := 0 + for n in nums { + result += n + } + return result +} + +get_value :: proc() -> (int, bool) { + return 42, true +} + +@(deprecated = "use new_func instead") +old_func :: proc() { + fmt.println("old") +} + +demo_numbers :: proc() { + // Integer and float literals + _ := 42 + _ := 3.14 + _ := 1_000_000 + _ := 0xFF + _ := 0b1010 + _ := 0o77 + _ := 1.5e-3 + _ := 2i // imaginary + + // Language constants + _ := true + _ := false + _ = nil +} + +demo_strings :: proc() { + // Double-quoted strings with escape sequences + _ := "hello, world" + _ := "escape: \" \n \t \\" + + // Raw strings (backtick — no escape processing) + _ := `C:\Windows\notepad.exe` + _ := `no \n escapes here` + + // Rune / character literals + _ := 'A' + _ := '\n' + _ := '世' +} + +demo_control_flow :: proc() { + // If / else if / else + if true { + fmt.println("yes") + } else if false { + fmt.println("no") + } else { + fmt.println("maybe") + } + + // C-style for loop + for i := 0; i < 10; i += 1 { + if i == 5 { continue } + if i == 8 { break } + } + + // Range-based for loop + for i in 0..<10 { + fmt.println(i) + } + + // Switch statement + x := 42 + switch x { + case 1: + fmt.println("one") + case 2, 3: + fmt.println("two or three") + case: + fmt.println("other") + } + + // Fallthrough + switch 0 { + case 0: + fallthrough + case 1: + fmt.println("zero or one") + } + + // When (compile-time conditional) + when ODIN_OS == .Windows { + fmt.println("Windows") + } else { + fmt.println("other OS") + } + + // Defer + defer fmt.println("deferred") + + // or_else / or_break + val := get_value() or_else 0 + _ = val + + for { + get_value() or_break + } +} + +demo_types :: proc() { + // Struct literal + v := Vector3{x = 1, y = 4, z = 9} + fmt.println(v) + + // Union + val: Value = 137 + if i, ok := val.(int); ok { + fmt.println(i) + } + + // Enum and switch + d := Direction.North + switch d { + case .North: + fmt.println("north") + case .South, .East, .West: + fmt.println("other direction") + } + + // Bit set + flags := Flags{.North, .East} + fmt.println(.North in flags) + + // Distinct type + dist := Meters(100.0) + fmt.println(dist) + + // Cast / transmute / auto_cast + x: int = cast(int)3.14 + y := transmute([4]u8)u32(0xDEAD_BEEF) + z := auto_cast x + _, _ = y, z +} + +demo_collections :: proc() { + // Dynamic array + arr := make([dynamic]int) + defer delete(arr) + append(&arr, 1, 2, 3) + fmt.println(arr) + + // Map + m := make(map[string]int) + defer delete(m) + m["key"] = 99 + fmt.println(m["key"]) + + // Pointer + val := 123 + ptr := &val + ptr^ = 456 + fmt.println(val) +} + +demo_builtins :: proc() { + // typeid / type_of + _ = typeid_of(int) + val := 0 + _ = type_of(val) + + // size_of / align_of / offset_of + _ = size_of(Vector3) + _ = align_of(f64) + _ = offset_of(Vector3, y) + + // context + context.user_index = 1 +} + +demo_directives :: proc() { + // Compile-time assert + #assert(size_of(u8) == 1) + + // SOA layout + v: #soa[4]Vector3 + _ = v + + // Partial switch + #partial switch d := Direction.North; d { + case .North: + fmt.println("north") + } + + // Unroll for + #unroll for elem, idx in [3]int{1, 4, 9} { + fmt.println(elem, idx) + } +} + +main :: proc() { + demo_numbers() + demo_strings() + demo_control_flow() + demo_types() + demo_collections() + demo_builtins() + demo_directives() + + fmt.println(add(1, 2)) + fmt.println(variadic(1, 2, 3, 4, 5)) + old_func() +} diff --git a/crates/lsh/definitions/markdown.lsh b/crates/lsh/definitions/markdown.lsh index 77a005fe925..fca215b8613 100644 --- a/crates/lsh/definitions/markdown.lsh +++ b/crates/lsh/definitions/markdown.lsh @@ -60,6 +60,16 @@ pub fn markdown() { if /.*/ {} } } + } else if /(?i:odin)/ { + loop { + await input; + if /\s*```/ { + return; + } else { + odin(); + if /.*/ {} + } + } } else if /(?i:py)/ { loop { await input; diff --git a/crates/lsh/definitions/odin.lsh b/crates/lsh/definitions/odin.lsh new file mode 100644 index 00000000000..fc0529df767 --- /dev/null +++ b/crates/lsh/definitions/odin.lsh @@ -0,0 +1,66 @@ +#[display_name = "Odin"] +#[path = "**/*.odin"] +pub fn odin() { + until /$/ { + yield other; + + if /\/\/.*/ { + yield comment; + } else if /\/\*/ { + loop { + yield comment; + if /\*\// { yield comment; break; } + await input; + } + } else if /"/ { + until /$/ { + yield string; + if /\\./ {} + else if /"/ { yield string; break; } + await input; + } + } else if /`/ { + loop { + yield string; + if /`/ { yield string; break; } + await input; + } + } else if /'/ { + until /$/ { + yield string; + if /\\./ {} + else if /'/ { yield string; break; } + await input; + } + } else if /(?:break|case|continue|do|else|fallthrough|for|if|in|not_in|or_break|or_continue|or_else|or_return|return|switch|when|where)\>/ { + yield keyword.control; + } else if /(?:align_of|auto_cast|bit_field|bit_set|cast|context|defer|distinct|dynamic|enum|foreign|import|map|matrix|offset_of|package|proc|size_of|struct|transmute|type_of|typeid|union|using)\>/ { + yield keyword.other; + } else if /(?:any|b8|b16|b32|b64|bool|complex32|complex64|complex128|cstring|f16|f16be|f16le|f32|f32be|f32le|f64|f64be|f64le|i8|i16|i16be|i16le|i32|i32be|i32le|i64|i64be|i64le|i128|i128be|i128le|int|quaternion64|quaternion128|quaternion256|rawptr|rune|string|u8|u16|u16be|u16le|u32|u32be|u32le|u64|u64be|u64le|u128|u128be|u128le|uint|uintptr)\>/ { + yield keyword.other; + } else if /(?:true|false|nil)\>/ { + yield constant.language; + } else if /#\+\w+/ { + yield keyword.other; + if /(?:.+)\>/ { + yield string; + } + } else if /#\w+/ { + yield keyword.other; + } else if /@/ { + yield markup.link; + } else if /(?i:-?(?:0x[\da-fA-F_]+|0b[01_]+|0o[0-7_]+|[\d_]+\.?[\d_]*|\.[\d_]+)(?:e[+-]?[\d_]+)?i?)/ { + if /\w+/ { + // Invalid numeric literal + } else { + yield constant.numeric; + } + } else if /(\w+)\s*\(/ { + yield $1 as method; + } else if /\w+/ { + // Gobble word chars to align the next iteration on a word boundary. + } + + yield other; + } +} From 73b2e93b52ed56826721446af4920f69355c7399 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Fri, 31 Jul 2026 17:59:32 +0200 Subject: [PATCH 2/2] Simplify LSH --- assets/highlighting-tests/odin.odin | 345 ++++++++++------------------ crates/lsh/definitions/odin.lsh | 49 ++-- 2 files changed, 135 insertions(+), 259 deletions(-) diff --git a/assets/highlighting-tests/odin.odin b/assets/highlighting-tests/odin.odin index c059d504979..753a323170e 100644 --- a/assets/highlighting-tests/odin.odin +++ b/assets/highlighting-tests/odin.odin @@ -1,5 +1,4 @@ -// Single-line comment - +// Line comment /* * Multi-line * comment @@ -10,250 +9,136 @@ package main import "core:fmt" -// Package-level constants -MY_CONST :: 42 -PI :: 3.14159 -NAME :: "Odin" - -// Struct type -Vector3 :: struct { - x, y, z: f32, +// Numbers +Dec :: 42 +Neg :: -7 +Hex :: 0xCAFE_BABE +Oct :: 0o755 +Bin :: 0b1010 +Sep :: 1_000_000 +Flt :: 3.14 +Half :: .5 +Exp :: 1e10 +Sci :: 1.5e-3 +Imag :: 2i +Quat :: 3j +HexF :: 0h3f800000 +Bad :: 1abc + +// Constants +yes := true +no := false +none := nil +_ = 0 + +// Basic types +b: bool +b32v: b32 +r: rune +s: string +cs: cstring +i: int +i128v: i128 +u: uint +up: uintptr +u32b: u32be +i16l: i16le +f16v: f16 +f64v: f64 +c128: complex128 +q256: quaternion256 +p: rawptr +t: typeid +a: any + +// Strings and runes +char := 'a' +escaped := '\n' +quote := '\'' +str := "double quotes with escapes: \" \n \t \\" +raw := `raw string +spans lines and may contain "quotes"` + +Speaker :: struct #packed { + name: string `fmt:"q"`, } -// Union type -Value :: union { - int, - f64, - string, -} +Value :: union {int, f64} -// Enum type -Direction :: enum { - North, - South, - East, - West, -} +Direction :: enum {North, South} -// Bit set Flags :: bit_set[Direction] -// Distinct type Meters :: distinct f32 -// Procedures -add :: proc(a, b: int) -> int { - return a + b -} - -variadic :: proc(nums: ..int) -> int { - result := 0 - for n in nums { - result += n - } - return result +Header :: bit_field u32 { + tag: u8 | 3, } -get_value :: proc() -> (int, bool) { - return 42, true +@(deprecated = "use speak instead") +old_speak :: proc(s: Speaker) -> string { + return fmt.tprintf("%s speaks", s.name) } -@(deprecated = "use new_func instead") -old_func :: proc() { - fmt.println("old") +@private +compare :: proc(a, b: $T) -> bool where intrinsics.type_is_comparable(T) { + return a == b } -demo_numbers :: proc() { - // Integer and float literals - _ := 42 - _ := 3.14 - _ := 1_000_000 - _ := 0xFF - _ := 0b1010 - _ := 0o77 - _ := 1.5e-3 - _ := 2i // imaginary +foreign import kernel32 "system:kernel32.lib" - // Language constants - _ := true - _ := false - _ = nil -} - -demo_strings :: proc() { - // Double-quoted strings with escape sequences - _ := "hello, world" - _ := "escape: \" \n \t \\" - - // Raw strings (backtick — no escape processing) - _ := `C:\Windows\notepad.exe` - _ := `no \n escapes here` - - // Rune / character literals - _ := 'A' - _ := '\n' - _ := '世' -} - -demo_control_flow :: proc() { - // If / else if / else - if true { - fmt.println("yes") - } else if false { - fmt.println("no") - } else { - fmt.println("maybe") - } - - // C-style for loop - for i := 0; i < 10; i += 1 { - if i == 5 { continue } - if i == 8 { break } - } - - // Range-based for loop - for i in 0..<10 { - fmt.println(i) - } - - // Switch statement - x := 42 - switch x { - case 1: - fmt.println("one") - case 2, 3: - fmt.println("two or three") - case: - fmt.println("other") - } - - // Fallthrough - switch 0 { - case 0: - fallthrough - case 1: - fmt.println("zero or one") - } - - // When (compile-time conditional) - when ODIN_OS == .Windows { - fmt.println("Windows") - } else { - fmt.println("other OS") - } - - // Defer - defer fmt.println("deferred") - - // or_else / or_break - val := get_value() or_else 0 - _ = val - - for { - get_value() or_break - } -} - -demo_types :: proc() { - // Struct literal - v := Vector3{x = 1, y = 4, z = 9} - fmt.println(v) - - // Union - val: Value = 137 - if i, ok := val.(int); ok { - fmt.println(i) - } - - // Enum and switch - d := Direction.North - switch d { - case .North: - fmt.println("north") - case .South, .East, .West: - fmt.println("other direction") - } - - // Bit set - flags := Flags{.North, .East} - fmt.println(.North in flags) - - // Distinct type - dist := Meters(100.0) - fmt.println(dist) - - // Cast / transmute / auto_cast - x: int = cast(int)3.14 - y := transmute([4]u8)u32(0xDEAD_BEEF) - z := auto_cast x - _, _ = y, z -} - -demo_collections :: proc() { - // Dynamic array - arr := make([dynamic]int) - defer delete(arr) - append(&arr, 1, 2, 3) - fmt.println(arr) - - // Map - m := make(map[string]int) - defer delete(m) - m["key"] = 99 - fmt.println(m["key"]) - - // Pointer - val := 123 - ptr := &val - ptr^ = 456 - fmt.println(val) -} - -demo_builtins :: proc() { - // typeid / type_of - _ = typeid_of(int) - val := 0 - _ = type_of(val) - - // size_of / align_of / offset_of - _ = size_of(Vector3) - _ = align_of(f64) - _ = offset_of(Vector3, y) - - // context - context.user_index = 1 -} - -demo_directives :: proc() { - // Compile-time assert - #assert(size_of(u8) == 1) - - // SOA layout - v: #soa[4]Vector3 - _ = v - - // Partial switch - #partial switch d := Direction.North; d { - case .North: - fmt.println("north") - } - - // Unroll for - #unroll for elem, idx in [3]int{1, 4, 9} { - fmt.println(elem, idx) - } +@(default_calling_convention = "std") +foreign kernel32 { + ExitProcess :: proc(code: u32) --- } main :: proc() { - demo_numbers() - demo_strings() - demo_control_flow() - demo_types() - demo_collections() - demo_builtins() - demo_directives() - - fmt.println(add(1, 2)) - fmt.println(variadic(1, 2, 3, 4, 5)) - old_func() + if yes { + fmt.println("enabled") + } else when ODIN_DEBUG { + fmt.println("debug") + } else { + fmt.println("disabled") + } + + for i := 0; i < 10; i += 1 { + switch { + case i == 5: + continue + case i == 8: + break + case: + fallthrough + } + } + + for i in 0..<10 {} + for i in 0..=9 {} + + values := [dynamic]int{1, 2, 3} + m := map[string]int{"one" = 1} + defer delete(values) + for value, index in values { + fmt.println(index, value, m["one"]) + } + + v: Value = 137 + #partial switch _ in v { + case int: + fmt.println("int") + } + + soa: #soa[4]Speaker + #unroll for i in 0..<2 {} + + x := cast(int)3.14 + y := transmute(u32)f32(1) + z := auto_cast x + w := m["two"] or_else 0 + _, _, _, _ = x, y, z, w + + #assert(size_of(u8) == 1) + context.user_index = 1 + ptr := &x + ptr^ = 456 } diff --git a/crates/lsh/definitions/odin.lsh b/crates/lsh/definitions/odin.lsh index fc0529df767..a8e13ea128c 100644 --- a/crates/lsh/definitions/odin.lsh +++ b/crates/lsh/definitions/odin.lsh @@ -9,52 +9,43 @@ pub fn odin() { } else if /\/\*/ { loop { yield comment; - if /\*\// { yield comment; break; } - await input; - } - } else if /"/ { - until /$/ { - yield string; - if /\\./ {} - else if /"/ { yield string; break; } await input; + if /\*\// { + yield comment; + break; + } } } else if /`/ { loop { yield string; - if /`/ { yield string; break; } + if /`/ { + yield string; + break; + } await input; } } else if /'/ { - until /$/ { - yield string; - if /\\./ {} - else if /'/ { yield string; break; } - await input; - } - } else if /(?:break|case|continue|do|else|fallthrough|for|if|in|not_in|or_break|or_continue|or_else|or_return|return|switch|when|where)\>/ { + single_quote_string(); + } else if /"/ { + double_quote_string(); + } else if /(?:break|case|continue|defer|do|else|fallthrough|for|if|in|not_in|or_break|or_continue|or_else|or_return|return|switch|when|where)\>/ { yield keyword.control; - } else if /(?:align_of|auto_cast|bit_field|bit_set|cast|context|defer|distinct|dynamic|enum|foreign|import|map|matrix|offset_of|package|proc|size_of|struct|transmute|type_of|typeid|union|using)\>/ { - yield keyword.other; - } else if /(?:any|b8|b16|b32|b64|bool|complex32|complex64|complex128|cstring|f16|f16be|f16le|f32|f32be|f32le|f64|f64be|f64le|i8|i16|i16be|i16le|i32|i32be|i32le|i64|i64be|i64le|i128|i128be|i128le|int|quaternion64|quaternion128|quaternion256|rawptr|rune|string|u8|u16|u16be|u16le|u32|u32be|u32le|u64|u64be|u64le|u128|u128be|u128le|uint|uintptr)\>/ { + } else if /(?:asm|auto_cast|bit_field|bit_set|cast|context|distinct|dynamic|enum|foreign|import|map|matrix|package|proc|struct|transmute|union|using)\>/ { yield keyword.other; } else if /(?:true|false|nil)\>/ { yield constant.language; - } else if /#\+\w+/ { - yield keyword.other; - if /(?:.+)\>/ { - yield string; - } - } else if /#\w+/ { - yield keyword.other; - } else if /@/ { - yield markup.link; - } else if /(?i:-?(?:0x[\da-fA-F_]+|0b[01_]+|0o[0-7_]+|[\d_]+\.?[\d_]*|\.[\d_]+)(?:e[+-]?[\d_]+)?i?)/ { + } else if /(?:any|bool|complex(?:32|64|128)|cstring|int|quaternion(?:64|128|256)|rawptr|rune|string|typeid|uintptr|uint|(?:[iu](?:8|16|32|64|128)|f(?:16|32|64)|b(?:8|16|32|64))(?:be|le)?)\>/ { + yield storage.type; + } else if /-?(?:0[xh][\da-fA-F_]+|0b[01_]+|0o[0-7_]+|\d[\d_]*\.[\d_]+|\d[\d_]*|\.\d[\d_]*)(?:[eE][+-]?[\d_]+)?[ijk]?/ { if /\w+/ { // Invalid numeric literal } else { yield constant.numeric; } + } else if /#\+?\w+/ { + yield keyword.other; + } else if /@\w*/ { + yield storage.annotation; } else if /(\w+)\s*\(/ { yield $1 as method; } else if /\w+/ {