Odin rewrite #1

Manually merged
N0VA merged 7 commits from odin-rewrite into main 2026-07-13 15:03:05 +02:00
9 changed files with 453 additions and 151 deletions
Showing only changes of commit 5c43b180a3 - Show all commits
+3 -3
View File
@@ -1,13 +1,13 @@
all: build
build:
v src/
odin build src/ -out:TypoML
run:
v run src/
odin run src/
clean:
rm -f TypoML
format:
v fmt -w src/
odinfmt -w src/
+1 -2
View File
@@ -1,10 +1,9 @@
#!/bin/sh
input=$(cat -)
input="${input#v run src/}"
input="${input#odin run src/}"
style="<style>$(cat styles.css)</style>"
start="<html><head><title>TypoML Preview</title>$style</head><body>"
end="</body></html>"
output="$start$input$end"
echo "$output" > /tmp/preview.html
xdg-open /tmp/preview.html
+374
View File
@@ -0,0 +1,374 @@
package main
import "core:strings"
get_ext :: proc(url: string) -> string {
clean := url
if question := strings.index_byte(clean, '?'); question != -1 {
clean = clean[:question]
}
if hash := strings.index_byte(clean, '#'); hash != -1 {
clean = clean[:hash]
}
if dot := strings.last_index_byte(clean, '.'); dot != -1 {
return strings.to_lower(clean[dot:], context.temp_allocator)
}
return ""
}
detect_type :: proc(url: string) -> string {
ext := get_ext(url)
switch ext {
case ".jpg",
".jpeg",
".jpe",
".jfif",
".jif",
".pjpeg",
".pjp",
".png",
".apng",
".gif",
".webp",
".avif",
".heic",
".heif",
".hif",
".bmp",
".dib",
".tif",
".tiff",
".ico",
".cur",
".svg",
".svgz",
".raw",
".cr2",
".cr3",
".nef",
".nrw",
".orf",
".sr2",
".arw",
".srf",
".rw2",
".pef",
".raf",
".dng",
".3fr",
".ari",
".bay",
".cap",
".dcr",
".erf",
".fff",
".iiq",
".k25",
".kdc",
".mdc",
".mos",
".mrw",
".obm",
".ptx",
".pxn",
".r3d",
".rwl",
".rwz",
".srw",
".x3f",
".gpr",
".pgm",
".ppm",
".pbm",
".pnm",
".pam",
".pfm",
".tga",
".icb",
".vda",
".vst",
".pcx",
".xbm",
".xpm",
".xwd",
".sgi",
".rgb",
".rgba",
".bw",
".jp2",
".j2k",
".j2c",
".jpf",
".jpx",
".jpm",
".mj2",
".wbmp",
".emf",
".wmf",
".eps",
".epsi",
".epsf",
".psd",
".psb",
".ai",
".cdr",
".xcf",
".gbr",
".gih",
".pat",
".art",
".jxl",
".jxr",
".wdp",
".hdp",
".exr",
".hdr",
".dpx",
".cin",
".fits",
".fit",
".fts",
".icns",
".flif",
".dds",
".ktx",
".ktx2",
".astc",
".kodak",
".fpx",
".pcd",
".cmx",
".drw",
".cgm",
".plt",
".hpgl",
".iff",
".ilbm",
".lbm",
".mac",
".msp",
".pct",
".pic",
".pict",
".pntg",
".qtif",
".qti",
".ras",
".sun",
".tpic",
".wpg",
".g3",
".g4",
".dcm",
".dicom",
".ora",
".kra",
".clip":
return "image"
case ".mp3",
".mp2",
".mp1",
".mpa",
".mpga",
".ogg",
".oga",
".opus",
".spx",
".wav",
".wave",
".flac",
".aac",
".adts",
".wma",
".m4a",
".m4b",
".m4p",
".m4r",
".aif",
".aiff",
".aifc",
".au",
".snd",
".mid",
".midi",
".rmi",
".kar",
".ra",
".ram",
".mka",
".ape",
".wv",
".ac3",
".ec3",
".eac3",
".dts",
".dtshd",
".amr",
".awb",
".caf",
".gsm",
".mpc",
".shn",
".tta",
".voc",
".vox",
".8svx",
".mod",
".s3m",
".xm",
".it",
".stm",
".med",
".mtm",
".umx",
".669",
".far",
".amf",
".dsm",
".gdm",
".imf",
".okt",
".sfx",
".ult",
".uni",
".psm",
".j2b",
".dbm",
".digi",
".dtm",
".plm",
".nsf",
".nsfe",
".spc",
".gym",
".vgm",
".vgz",
".gbs",
".hes",
".ay",
".sid",
".oma",
".aa3",
".at3",
".at9",
".qcp",
".tak",
".ofr",
".ofs",
".sox",
".weba",
".dss",
".alac",
".vqf",
".vql",
".mxmf",
".ota",
".imy",
".rtttl",
".rtx",
".xmf",
".w64",
".rf64",
".bwf",
".cda",
".aup":
return "audio"
case ".mp4",
".m4v",
".mp4v",
".webm",
".mov",
".qt",
".avi",
".mkv",
".flv",
".f4v",
".f4p",
".f4a",
".f4b",
".wmv",
".mpg",
".mpeg",
".mpe",
".m1v",
".m2v",
".mpv",
".mp2v",
".mpv2",
".ogv",
".ogx",
".3gp",
".3g2",
".3gpp",
".3gpp2",
".asf",
".asx",
".vob",
".ts",
".mts",
".m2ts",
".tsv",
".tsa",
".divx",
".xvid",
".rm",
".rmvb",
".swf",
".dv",
".dif",
".gifv",
".roq",
".nsv",
".svi",
".annodex",
".axv",
".amv",
".bik",
".bk2",
".smk",
".ivf",
".mxf",
".nut",
".ogm",
".fli",
".flc",
".y4m",
".264",
".h264",
".265",
".h265",
".hevc",
".av1",
".vc1",
".vp8",
".vp9",
".wtv",
".yuv",
".str",
".pva",
".trp",
".rec",
".vid",
".rv",
".cpk",
".film":
return "video"
case:
return "iframe"
}
}
parse_type_hint :: proc(type_hint: string) -> string {
switch type_hint {
case "i", "image", "img":
return "image"
case "a", "audio":
return "audio"
case "v", "video":
return "video"
case "f", "iframe", "frame":
return "iframe"
case:
return ""
}
}
-74
View File
@@ -1,74 +0,0 @@
fn get_ext(url string) string {
mut clean := url
if question := clean.index('?') {
clean = clean[..question]
}
if hash := clean.index('#') {
clean = clean[..hash]
}
if dot := clean.last_index('.') {
return clean[dot..].to_lower()
}
return ''
}
fn detect_type(url string) string {
ext := get_ext(url)
return match ext {
'.jpg', '.jpeg', '.jpe', '.jfif', '.jif', '.pjpeg', '.pjp', '.png', '.apng', '.gif',
'.webp', '.avif', '.heic', '.heif', '.hif', '.bmp', '.dib', '.tif', '.tiff', '.ico',
'.cur', '.svg', '.svgz', '.raw', '.cr2', '.cr3', '.nef', '.nrw', '.orf', '.sr2', '.arw',
'.srf', '.rw2', '.pef', '.raf', '.dng', '.3fr', '.ari', '.bay', '.cap', '.dcr', '.erf',
'.fff', '.iiq', '.k25', '.kdc', '.mdc', '.mos', '.mrw', '.obm', '.ptx', '.pxn', '.r3d',
'.rwl', '.rwz', '.srw', '.x3f', '.gpr', '.pgm', '.ppm', '.pbm', '.pnm', '.pam', '.pfm',
'.tga', '.icb', '.vda', '.vst', '.pcx', '.xbm', '.xpm', '.xwd', '.sgi', '.rgb', '.rgba',
'.bw', '.jp2', '.j2k', '.j2c', '.jpf', '.jpx', '.jpm', '.mj2', '.wbmp', '.emf', '.wmf',
'.eps', '.epsi', '.epsf', '.psd', '.psb', '.ai', '.cdr', '.xcf', '.gbr', '.gih', '.pat',
'.art', '.jxl', '.jxr', '.wdp', '.hdp', '.exr', '.hdr', '.dpx', '.cin', '.fits', '.fit',
'.fts', '.icns', '.flif', '.dds', '.ktx', '.ktx2', '.astc', '.kodak', '.fpx', '.pcd',
'.cmx', '.drw', '.cgm', '.plt', '.hpgl', '.iff', '.ilbm', '.lbm', '.mac', '.msp', '.pct',
'.pic', '.pict', '.pntg', '.qtif', '.qti', '.ras', '.sun', '.tpic', '.wpg', '.g3', '.g4',
'.dcm', '.dicom', '.ora', '.kra', '.clip' {
'image'
}
'.mp3', '.mp2', '.mp1', '.mpa', '.mpga', '.ogg', '.oga', '.opus', '.spx', '.wav', '.wave',
'.flac', '.aac', '.adts', '.wma', '.m4a', '.m4b', '.m4p', '.m4r', '.aif', '.aiff', '.aifc',
'.au', '.snd', '.mid', '.midi', '.rmi', '.kar', '.ra', '.ram', '.mka', '.ape', '.wv',
'.ac3', '.ec3', '.eac3', '.dts', '.dtshd', '.amr', '.awb', '.caf', '.gsm', '.mpc', '.shn',
'.tta', '.voc', '.vox', '.8svx', '.mod', '.s3m', '.xm', '.it', '.stm', '.med', '.mtm',
'.umx', '.669', '.far', '.amf', '.dsm', '.gdm', '.imf', '.okt', '.sfx', '.ult', '.uni',
'.psm', '.j2b', '.dbm', '.digi', '.dtm', '.plm', '.nsf', '.nsfe', '.spc', '.gym', '.vgm',
'.vgz', '.gbs', '.hes', '.ay', '.sid', '.oma', '.aa3', '.at3', '.at9', '.qcp', '.tak',
'.ofr', '.ofs', '.sox', '.weba', '.dss', '.alac', '.vqf', '.vql', '.mxmf', '.ota', '.imy',
'.rtttl', '.rtx', '.xmf', '.w64', '.rf64', '.bwf', '.cda', '.aup' {
'audio'
}
'.mp4', '.m4v', '.mp4v', '.webm', '.mov', '.qt', '.avi', '.mkv', '.flv', '.f4v', '.f4p',
'.f4a', '.f4b', '.wmv', '.mpg', '.mpeg', '.mpe', '.m1v', '.m2v', '.mpv', '.mp2v', '.mpv2',
'.ogv', '.ogx', '.3gp', '.3g2', '.3gpp', '.3gpp2', '.asf', '.asx', '.vob', '.ts', '.mts',
'.m2ts', '.tsv', '.tsa', '.divx', '.xvid', '.rm', '.rmvb', '.swf', '.dv', '.dif', '.gifv',
'.roq', '.nsv', '.svi', '.annodex', '.axv', '.amv', '.bik', '.bk2', '.smk', '.ivf', '.mxf',
'.nut', '.ogm', '.fli', '.flc', '.y4m', '.264', '.h264', '.265', '.h265', '.hevc', '.av1',
'.vc1', '.vp8', '.vp9', '.wtv', '.yuv', '.str', '.pva', '.trp', '.rec', '.vid', '.rv',
'.cpk', '.film' {
'video'
}
else {
'iframe'
}
}
}
// will look like this:
// $[https://krzak.org/krzak-icon.png|i]
// $(https://krzak.org/krzak-icon.png|image)
// $[Krzak.org icon](https://krzak.org/krzak-icon.png|i)
fn parse_type_hint(type_hint string) string {
return match type_hint {
'i', 'image', 'img' { 'image' }
'a', 'audio' { 'audio' }
'v', 'video' { 'video' }
'f', 'iframe' { 'iframe' }
else { '' }
}
}
+17
View File
@@ -0,0 +1,17 @@
package main
import "core:fmt"
import "core:os"
import "core:strings"
read_stdin :: proc() -> []string {
data, err := os.read_entire_file_from_file(os.stdin, context.allocator)
content := string(data)
return strings.split_lines(content)
}
main :: proc() {
lines := read_stdin()
parsed := parse(lines)
fmt.println(parsed)
}
-18
View File
@@ -1,18 +0,0 @@
import os
fn read_stdin() []string {
mut lines := []string{}
for {
line := os.get_line()
if line.len == 0 && os.stdin().eof() {
break
}
lines << line
}
return lines
}
fn main() {
lines := read_stdin()
println(parse(lines))
}
+58
View File
@@ -0,0 +1,58 @@
package main
import "core:fmt"
import "core:strings"
replace_text :: proc(text: string, start: int, len_: int, repl: string) -> string {
builder := strings.builder_make(context.temp_allocator)
strings.write_string(&builder, text[:start])
strings.write_string(&builder, repl)
strings.write_string(&builder, text[start + len_:])
return strings.to_string(builder)
}
parse_header :: proc(line: string) -> (int, string) {
if len(line) == 0 {
return 0, line
}
lvl := 0
for lvl < len(line) && line[lvl] == '!' {
lvl += 1
}
if lvl == 0 || lvl >= len(line) || line[lvl] != ' ' {
return 0, line
}
return lvl, line[lvl + 1:]
}
parse :: proc(lines: []string, allocator := context.allocator) -> string {
builder := strings.builder_make(allocator)
strings.write_string(&builder, "<p>")
for line in lines {
trimmed := strings.trim_space(line)
if len(trimmed) == 0 {
strings.write_string(&builder, "<br>\n")
continue
}
lvl, text := parse_header(trimmed)
is_header := lvl > 0
if is_header {
fmt.sbprintf(&builder, "<h%d>", lvl)
strings.write_string(&builder, text)
fmt.sbprintf(&builder, "</h%d>", lvl)
} else {
strings.write_string(&builder, text)
strings.write_string(&builder, "<br>")
}
strings.write_string(&builder, "\n")
}
strings.write_string(&builder, "</p>")
return strings.to_string(builder)
}
-47
View File
@@ -1,47 +0,0 @@
fn replace(text string, start int, len int, repl string) string {
return text[..start] + repl + text[start + len..]
}
fn parse_header(line string) (int, string) {
if line.len == 0 {
return 0, line
}
mut lvl := 0
for lvl < line.len && line[lvl] == '!'.bytes()[0] {
lvl++
}
if lvl == 0 || lvl >= line.len || line[lvl] != ' '.bytes()[0] {
return 0, line
}
return lvl, line[lvl + 1..]
}
fn parse(lines []string) string {
mut output := '<p>'
for line in lines {
mut trimmed := line.trim_space()
if trimmed.len == 0 {
output += '<br>\n'
continue
}
lvl, mut text := parse_header(trimmed)
is_header := lvl > 0
if is_header {
output += '<h' + lvl.str() + '>'
output += text + '</h' + lvl.str() + '>'
} else {
output += text
output += '<br>'
}
output += '\n'
}
output += '</p>'
return output
}
-7
View File
@@ -1,7 +0,0 @@
Module {
name: 'TypoML'
description: ''
version: '0.0.0'
license: 'ISC'
dependencies: []
}