Merge branch 'master' into pr/1726

This commit is contained in:
Jeroen van Rijn
2022-04-30 12:29:28 +02:00
80 changed files with 13561 additions and 699 deletions
+8 -3
View File
@@ -39,7 +39,9 @@ jobs:
make
timeout-minutes: 10
- name: Odin issues tests
run: tests/issues/run.sh
run: |
cd tests/issues
./run.sh
timeout-minutes: 10
- name: Odin check examples/all for Linux i386
run: ./odin check examples/all -vet -strict-style -target:linux_i386
@@ -91,7 +93,9 @@ jobs:
make
timeout-minutes: 10
- name: Odin issues tests
run: tests/issues/run.sh
run: |
cd tests/issues
./run.sh
timeout-minutes: 10
- name: Odin check examples/all for Darwin arm64
run: ./odin check examples/all -vet -strict-style -target:darwin_arm64
@@ -163,7 +167,8 @@ jobs:
shell: cmd
run: |
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat
call tests\issues\run.bat
cd tests\issues
call run.bat
timeout-minutes: 10
- name: Odin check examples/all for Windows 32bits
shell: cmd
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2016-2021 Ginger Bill. All rights reserved.
Copyright (c) 2016-2022 Ginger Bill. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
+2 -2
View File
@@ -1,7 +1,7 @@
all: debug demo
all: debug
demo:
./odin run examples/demo/demo.odin
./odin run examples/demo/demo.odin -file
report:
./odin report
+148
View File
@@ -0,0 +1,148 @@
/*
This file was generated, so don't edit this by hand.
Transliterated from https://github.com/Ed-von-Schleck/shoco/blob/master/shoco_model.h,
which is an English word model.
*/
// package shoco is an implementation of the shoco short string compressor
package shoco
DEFAULT_MODEL :: Shoco_Model {
min_char = 39,
max_char = 122,
characters_by_id = {
'e', 'a', 'i', 'o', 't', 'h', 'n', 'r', 's', 'l', 'u', 'c', 'w', 'm', 'd', 'b', 'p', 'f', 'g', 'v', 'y', 'k', '-', 'H', 'M', 'T', '\'', 'B', 'x', 'I', 'W', 'L',
},
ids_by_character = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 26, -1, -1, -1, -1, -1, 22, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 27, -1, -1, -1, -1, -1, 23, 29, -1, -1, 31, 24, -1, -1, -1, -1, -1, -1, 25, -1, -1, 30, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 15, 11, 14, 0, 17, 18, 5, 2, -1, 21, 9, 13, 6, 3, 16, -1, 7, 8, 4, 10, 19, 12, 28, 20, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
},
successors_by_bigram = {
7, 4, 12, -1, 6, -1, 1, 0, 3, 5, -1, 9, -1, 8, 2, -1, 15, 14, -1, 10, 11, -1, -1, -1, -1, -1, -1, -1, 13, -1, -1, -1,
1, -1, 6, -1, 1, -1, 0, 3, 2, 4, 15, 11, -1, 9, 5, 10, 13, -1, 12, 8, 7, 14, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
9, 11, -1, 4, 2, -1, 0, 8, 1, 5, -1, 6, -1, 3, 7, 15, -1, 12, 10, 13, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, 14, 7, 5, -1, 1, 2, 8, 9, 0, 15, 6, 4, 11, -1, 12, 3, -1, 10, -1, 13, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
2, 4, 3, 1, 5, 0, -1, 6, 10, 9, 7, 12, 11, -1, -1, -1, -1, 13, -1, -1, 8, -1, 15, -1, -1, -1, 14, -1, -1, -1, -1, -1,
0, 1, 2, 3, 4, -1, -1, 5, 9, 10, 6, -1, -1, 8, 15, 11, -1, 14, -1, -1, 7, -1, 13, -1, -1, -1, 12, -1, -1, -1, -1, -1,
2, 8, 7, 4, 3, -1, 9, -1, 6, 11, -1, 5, -1, -1, 0, -1, -1, 14, 1, 15, 10, 12, -1, -1, -1, -1, 13, -1, -1, -1, -1, -1,
0, 3, 1, 2, 6, -1, 9, 8, 4, 12, 13, 10, -1, 11, 7, -1, -1, 15, 14, -1, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
0, 6, 3, 4, 1, 2, -1, -1, 5, 10, 7, 9, 11, 12, -1, -1, 8, 14, -1, -1, 15, 13, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
0, 6, 2, 5, 9, -1, -1, -1, 10, 1, 8, -1, 12, 14, 4, -1, 15, 7, -1, 13, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
8, 10, 9, 15, 1, -1, 4, 0, 3, 2, -1, 6, -1, 12, 11, 13, 7, 14, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
1, 3, 6, 0, 4, 2, -1, 7, 13, 8, 9, 11, -1, -1, 15, -1, -1, -1, -1, -1, 10, 5, 14, -1, -1, -1, -1, -1, -1, -1, -1, -1,
3, 0, 1, 4, -1, 2, 5, 6, 7, 8, -1, 14, -1, -1, 9, 15, -1, 12, -1, -1, -1, 10, 11, -1, -1, -1, 13, -1, -1, -1, -1, -1,
0, 1, 3, 2, 15, -1, 12, -1, 7, 14, 4, -1, -1, 9, -1, 8, 5, 10, -1, -1, 6, -1, 13, -1, -1, -1, 11, -1, -1, -1, -1, -1,
0, 3, 1, 2, -1, -1, 12, 6, 4, 9, 7, -1, -1, 14, 8, -1, -1, 15, 11, 13, 5, -1, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1,
0, 5, 7, 2, 10, 13, -1, 6, 8, 1, 3, -1, -1, 14, 15, 11, -1, -1, -1, 12, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
0, 2, 6, 3, 7, 10, -1, 1, 9, 4, 8, -1, -1, 15, -1, 12, 5, -1, -1, -1, 11, -1, 13, -1, -1, -1, 14, -1, -1, -1, -1, -1,
1, 3, 4, 0, 7, -1, 12, 2, 11, 8, 6, 13, -1, -1, -1, -1, -1, 5, -1, -1, 10, 15, 9, -1, -1, -1, 14, -1, -1, -1, -1, -1,
1, 3, 5, 2, 13, 0, 9, 4, 7, 6, 8, -1, -1, 15, -1, 11, -1, -1, 10, -1, 14, -1, 12, -1, -1, -1, -1, -1, -1, -1, -1, -1,
0, 2, 1, 3, -1, -1, -1, 6, -1, -1, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
1, 11, 4, 0, 3, -1, 13, 12, 2, 7, -1, -1, 15, 10, 5, 8, 14, -1, -1, -1, -1, -1, 9, -1, -1, -1, 6, -1, -1, -1, -1, -1,
0, 9, 2, 14, 15, 4, 1, 13, 3, 5, -1, -1, 10, -1, -1, -1, -1, 6, 12, -1, 7, -1, 8, -1, -1, -1, 11, -1, -1, -1, -1, -1,
-1, 2, 14, -1, 1, 5, 8, 7, 4, 12, -1, 6, 9, 11, 13, 3, 10, 15, -1, -1, -1, -1, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1,
0, 1, 3, 2, -1, -1, -1, -1, -1, -1, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
4, 3, 1, 5, -1, -1, -1, 0, -1, -1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
2, 8, 4, 1, -1, 0, -1, 6, -1, -1, 5, -1, 7, -1, -1, -1, -1, -1, -1, -1, 10, -1, -1, 9, -1, -1, -1, -1, -1, -1, -1, -1,
12, 5, -1, -1, 1, -1, -1, 7, 0, 3, -1, 2, -1, 4, 6, -1, -1, -1, -1, 8, -1, -1, 15, -1, 13, 9, -1, -1, -1, -1, -1, 11,
1, 3, 2, 4, -1, -1, -1, 5, -1, 7, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, 6, -1, -1, -1, -1, -1, -1, -1, -1, 8, -1, -1,
5, 3, 4, 12, 1, 6, -1, -1, -1, -1, 8, 2, -1, -1, -1, -1, 0, 9, -1, -1, 11, -1, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, 0, -1, 1, 12, 3, -1, -1, -1, -1, 5, -1, -1, -1, 2, -1, -1, -1, -1, -1, -1, -1, -1, 4, -1, -1, 6, -1, 10,
2, 3, 1, 4, -1, 0, -1, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 7, -1, -1, -1, -1, -1, -1, -1, -1, 6, -1, -1,
5, 1, 3, 0, -1, -1, -1, -1, -1, -1, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, -1, -1, -1, -1, -1, 9, -1, -1, 6, -1, 7,
},
successors_reversed = {
's', 't', 'c', 'l', 'm', 'a', 'd', 'r', 'v', 'T', 'A', 'L', 'e', 'M', 'Y', '-',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'-', 't', 'a', 'b', 's', 'h', 'c', 'r', 'n', 'w', 'p', 'm', 'l', 'd', 'i', 'f',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'u', 'e', 'i', 'a', 'o', 'r', 'y', 'l', 'I', 'E', 'R', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'e', 'a', 'o', 'i', 'u', 'A', 'y', 'E', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
't', 'n', 'f', 's', '\'', 'm', 'I', 'N', 'A', 'E', 'L', 'Z', 'r', 'V', 'R', 'C',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'o', 'a', 'y', 'i', 'u', 'e', 'I', 'L', 'D', '\'', 'E', 'Y', '\x00', '\x00', '\x00', '\x00',
'r', 'i', 'y', 'a', 'e', 'o', 'u', 'Y', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'h', 'o', 'e', 'E', 'i', 'u', 'r', 'w', 'a', 'H', 'y', 'R', 'Z', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'h', 'i', 'e', 'a', 'o', 'r', 'I', 'y', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'n', 't', 's', 'r', 'l', 'd', 'i', 'y', 'v', 'm', 'b', 'c', 'g', 'p', 'k', 'u',
'e', 'l', 'o', 'u', 'y', 'a', 'r', 'i', 's', 'j', 't', 'b', 'v', 'h', 'm', 'd',
'o', 'e', 'h', 'a', 't', 'k', 'i', 'r', 'l', 'u', 'y', 'c', 'q', 's', '-', 'd',
'e', 'i', 'o', 'a', 's', 'y', 'r', 'u', 'd', 'l', '-', 'g', 'n', 'v', 'm', 'f',
'r', 'n', 'd', 's', 'a', 'l', 't', 'e', 'm', 'c', 'v', 'y', 'i', 'x', 'f', 'p',
'o', 'e', 'r', 'a', 'i', 'f', 'u', 't', 'l', '-', 'y', 's', 'n', 'c', '\'', 'k',
'h', 'e', 'o', 'a', 'r', 'i', 'l', 's', 'u', 'n', 'g', 'b', '-', 't', 'y', 'm',
'e', 'a', 'i', 'o', 't', 'r', 'u', 'y', 'm', 's', 'l', 'b', '\'', '-', 'f', 'd',
'n', 's', 't', 'm', 'o', 'l', 'c', 'd', 'r', 'e', 'g', 'a', 'f', 'v', 'z', 'b',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'e', 'n', 'i', 's', 'h', 'l', 'f', 'y', '-', 'a', 'w', '\'', 'g', 'r', 'o', 't',
'e', 'l', 'i', 'y', 'd', 'o', 'a', 'f', 'u', 't', 's', 'k', 'w', 'v', 'm', 'p',
'e', 'a', 'o', 'i', 'u', 'p', 'y', 's', 'b', 'm', 'f', '\'', 'n', '-', 'l', 't',
'd', 'g', 'e', 't', 'o', 'c', 's', 'i', 'a', 'n', 'y', 'l', 'k', '\'', 'f', 'v',
'u', 'n', 'r', 'f', 'm', 't', 'w', 'o', 's', 'l', 'v', 'd', 'p', 'k', 'i', 'c',
'e', 'r', 'a', 'o', 'l', 'p', 'i', 't', 'u', 's', 'h', 'y', 'b', '-', '\'', 'm',
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'e', 'i', 'o', 'a', 's', 'y', 't', 'd', 'r', 'n', 'c', 'm', 'l', 'u', 'g', 'f',
'e', 't', 'h', 'i', 'o', 's', 'a', 'u', 'p', 'c', 'l', 'w', 'm', 'k', 'f', 'y',
'h', 'o', 'e', 'i', 'a', 't', 'r', 'u', 'y', 'l', 's', 'w', 'c', 'f', '\'', '-',
'r', 't', 'l', 's', 'n', 'g', 'c', 'p', 'e', 'i', 'a', 'd', 'm', 'b', 'f', 'o',
'e', 'i', 'a', 'o', 'y', 'u', 'r', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
'a', 'i', 'h', 'e', 'o', 'n', 'r', 's', 'l', 'd', 'k', '-', 'f', '\'', 'c', 'b',
'p', 't', 'c', 'a', 'i', 'e', 'h', 'q', 'u', 'f', '-', 'y', 'o', '\x00', '\x00', '\x00',
'o', 'e', 's', 't', 'i', 'd', '\'', 'l', 'b', '-', 'm', 'a', 'r', 'n', 'p', 'w',
},
character_count = 32,
successor_count = 16,
max_successor_n = 7,
packs = {
{ 0x80000000, 1, 2, { 26, 24, 24, 24, 24, 24, 24, 24 }, { 15, 3, 0, 0, 0, 0, 0, 0 }, 0xc0, 0x80 },
{ 0xc0000000, 2, 4, { 25, 22, 19, 16, 16, 16, 16, 16 }, { 15, 7, 7, 7, 0, 0, 0, 0 }, 0xe0, 0xc0 },
{ 0xe0000000, 4, 8, { 23, 19, 15, 11, 8, 5, 2, 0 }, { 31, 15, 15, 15, 7, 7, 7, 3 }, 0xf0, 0xe0 },
},
}
+318
View File
@@ -0,0 +1,318 @@
/*
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
Made available under Odin's BSD-3 license.
List of contributors:
Jeroen van Rijn: Initial implementation.
An implementation of [shoco](https://github.com/Ed-von-Schleck/shoco) by Christian Schramm.
*/
// package shoco is an implementation of the shoco short string compressor
package shoco
import "core:intrinsics"
import "core:compress"
Shoco_Pack :: struct {
word: u32,
bytes_packed: i8,
bytes_unpacked: i8,
offsets: [8]u16,
masks: [8]i16,
header_mask: u8,
header: u8,
}
Shoco_Model :: struct {
min_char: u8,
max_char: u8,
characters_by_id: []u8,
ids_by_character: [256]i16,
successors_by_bigram: []i8,
successors_reversed: []u8,
character_count: u8,
successor_count: u8,
max_successor_n: i8,
packs: []Shoco_Pack,
}
compress_bound :: proc(uncompressed_size: int) -> (worst_case_compressed_size: int) {
// Worst case compression happens when input is non-ASCII (128-255)
// Encoded as 0x00 + the byte in question.
return uncompressed_size * 2
}
decompress_bound :: proc(compressed_size: int, model := DEFAULT_MODEL) -> (maximum_decompressed_size: int) {
// Best case compression is 2:1
most: f64
for pack in model.packs {
val := f64(compressed_size) / f64(pack.bytes_packed) * f64(pack.bytes_unpacked)
most = max(most, val)
}
return int(most)
}
find_best_encoding :: proc(indices: []i16, n_consecutive: i8, model := DEFAULT_MODEL) -> (res: int) {
for p := len(model.packs); p > 0; p -= 1 {
pack := model.packs[p - 1]
if n_consecutive >= pack.bytes_unpacked {
have_index := true
for i := 0; i < int(pack.bytes_unpacked); i += 1 {
if indices[i] > pack.masks[i] {
have_index = false
break
}
}
if have_index {
return p - 1
}
}
}
return -1
}
validate_model :: proc(model: Shoco_Model) -> (int, compress.Error) {
if len(model.characters_by_id) != int(model.character_count) {
return 0, .Unknown_Compression_Method
}
if len(model.successors_by_bigram) != int(model.character_count) * int(model.character_count) {
return 0, .Unknown_Compression_Method
}
if len(model.successors_reversed) != int(model.successor_count) * int(model.max_char - model.min_char) {
return 0, .Unknown_Compression_Method
}
// Model seems legit.
return 0, nil
}
// Decompresses into provided buffer.
decompress_slice_to_output_buffer :: proc(input: []u8, output: []u8, model := DEFAULT_MODEL) -> (size: int, err: compress.Error) {
inp, inp_end := 0, len(input)
out, out_end := 0, len(output)
validate_model(model) or_return
for inp < inp_end {
val := transmute(i8)input[inp]
mark := int(-1)
for val < 0 {
val <<= 1
mark += 1
}
if mark > len(model.packs) {
return out, .Unknown_Compression_Method
}
if mark < 0 {
if out >= out_end {
return out, .Output_Too_Short
}
// Ignore the sentinel value for non-ASCII chars
if input[inp] == 0x00 {
inp += 1
if inp >= inp_end {
return out, .Stream_Too_Short
}
}
output[out] = input[inp]
inp, out = inp + 1, out + 1
} else {
pack := model.packs[mark]
if out + int(pack.bytes_unpacked) > out_end {
return out, .Output_Too_Short
} else if inp + int(pack.bytes_packed) > inp_end {
return out, .Stream_Too_Short
}
code := intrinsics.unaligned_load((^u32)(&input[inp]))
when ODIN_ENDIAN == .Little {
code = intrinsics.byte_swap(code)
}
// Unpack the leading char
offset := pack.offsets[0]
mask := pack.masks[0]
last_chr := model.characters_by_id[(code >> offset) & u32(mask)]
output[out] = last_chr
// Unpack the successor chars
for i := 1; i < int(pack.bytes_unpacked); i += 1 {
offset = pack.offsets[i]
mask = pack.masks[i]
index_major := u32(last_chr - model.min_char) * u32(model.successor_count)
index_minor := (code >> offset) & u32(mask)
last_chr = model.successors_reversed[index_major + index_minor]
output[out + i] = last_chr
}
out += int(pack.bytes_unpacked)
inp += int(pack.bytes_packed)
}
}
return out, nil
}
decompress_slice_to_string :: proc(input: []u8, model := DEFAULT_MODEL, allocator := context.allocator) -> (res: string, err: compress.Error) {
context.allocator = allocator
if len(input) == 0 {
return "", .Stream_Too_Short
}
max_output_size := decompress_bound(len(input), model)
buf: [dynamic]u8
if !resize(&buf, max_output_size) {
return "", .Out_Of_Memory
}
length, result := decompress_slice_to_output_buffer(input, buf[:])
resize(&buf, length)
return string(buf[:]), result
}
decompress :: proc{decompress_slice_to_output_buffer, decompress_slice_to_string}
compress_string_to_buffer :: proc(input: string, output: []u8, model := DEFAULT_MODEL, allocator := context.allocator) -> (size: int, err: compress.Error) {
inp, inp_end := 0, len(input)
out, out_end := 0, len(output)
output := output
validate_model(model) or_return
indices := make([]i16, model.max_successor_n + 1)
defer delete(indices)
last_resort := false
encode: for inp < inp_end {
if last_resort {
last_resort = false
if input[inp] & 0x80 == 0x80 {
// Non-ASCII case
if out + 2 > out_end {
return out, .Output_Too_Short
}
// Put in a sentinel byte
output[out] = 0x00
out += 1
} else {
// An ASCII byte
if out + 1 > out_end {
return out, .Output_Too_Short
}
}
output[out] = input[inp]
out, inp = out + 1, inp + 1
} else {
// Find the longest string of known successors
indices[0] = model.ids_by_character[input[inp]]
last_chr_index := indices[0]
if last_chr_index < 0 {
last_resort = true
continue encode
}
rest := inp_end - inp
n_consecutive: i8 = 1
for ; n_consecutive <= model.max_successor_n; n_consecutive += 1 {
if inp_end > 0 && int(n_consecutive) == rest {
break
}
current_index := model.ids_by_character[input[inp + int(n_consecutive)]]
if current_index < 0 { // '\0' is always -1
break
}
successor_index := model.successors_by_bigram[last_chr_index * i16(model.character_count) + current_index]
if successor_index < 0 {
break
}
indices[n_consecutive] = i16(successor_index)
last_chr_index = current_index
}
if n_consecutive < 2 {
last_resort = true
continue encode
}
pack_n := find_best_encoding(indices, n_consecutive)
if pack_n >= 0 {
if out + int(model.packs[pack_n].bytes_packed) > out_end {
return out, .Output_Too_Short
}
pack := model.packs[pack_n]
code := pack.word
for i := 0; i < int(pack.bytes_unpacked); i += 1 {
code |= u32(indices[i]) << pack.offsets[i]
}
// In the little-endian world, we need to swap what's in the register to match the memory representation.
when ODIN_ENDIAN == .Little {
code = intrinsics.byte_swap(code)
}
out_ptr := raw_data(output[out:])
switch pack.bytes_packed {
case 4:
intrinsics.unaligned_store(transmute(^u32)out_ptr, code)
case 2:
intrinsics.unaligned_store(transmute(^u16)out_ptr, u16(code))
case 1:
intrinsics.unaligned_store(transmute(^u8)out_ptr, u8(code))
case:
return out, .Unknown_Compression_Method
}
out += int(pack.bytes_packed)
inp += int(pack.bytes_unpacked)
} else {
last_resort = true
continue encode
}
}
}
return out, nil
}
compress_string :: proc(input: string, model := DEFAULT_MODEL, allocator := context.allocator) -> (output: []u8, err: compress.Error) {
context.allocator = allocator
if len(input) == 0 {
return {}, .Stream_Too_Short
}
max_output_size := compress_bound(len(input))
buf: [dynamic]u8
if !resize(&buf, max_output_size) {
return {}, .Out_Of_Memory
}
length, result := compress_string_to_buffer(input, buf[:])
resize(&buf, length)
return buf[:length], result
}
compress :: proc{compress_string_to_buffer, compress_string}
@@ -0,0 +1,173 @@
package container_intrusive_list
import "core:intrinsics"
// An intrusive doubly-linked list
//
// As this is an intrusive container, a `Node` must be embedded in your own
// structure which is conventionally called a "link". The use of `push_front`
// and `push_back` take the address of this node. Retrieving the data
// associated with the node requires finding the relative offset of the node
// of the parent structure. The parent type and field name are given to
// `iterator_*` procedures, or to the built-in `container_of` procedure.
//
// This data structure is two-pointers in size:
// 8 bytes on 32-bit platforms and 16 bytes on 64-bit platforms
List :: struct {
head: ^Node,
tail: ^Node,
}
Node :: struct {
next, prev: ^Node,
}
push_front :: proc(list: ^List, node: ^Node) {
if list.head != nil {
list.head.prev = node
node.prev, node.next = nil, list.head
list.head = node
} else {
list.head, list.tail = node, node
node.prev, node.next = nil, nil
}
}
push_back :: proc(list: ^List, node: ^Node) {
if list.tail != nil {
list.tail.next = node
node.prev, node.next = list.tail, nil
list.tail = node
} else {
list.head, list.tail = node, node
node.prev, node.next = nil, nil
}
}
remove :: proc(list: ^List, node: ^Node) {
if node != nil {
if node.next != nil {
node.next.prev = node.prev
}
if node.prev != nil {
node.prev.next = node.next
}
if list.head == node {
list.head = node.next
}
if list.tail == node {
list.tail = node.prev
}
}
}
remove_by_proc :: proc(list: ^List, to_erase: proc(^Node) -> bool) {
for node := list.head; node != nil; {
next := node.next
if to_erase(node) {
if node.next != nil {
node.next.prev = node.prev
}
if node.prev != nil {
node.prev.next = node.next
}
if list.head == node {
list.head = node.next
}
if list.tail == node {
list.tail = node.prev
}
}
node = next
}
}
is_empty :: proc(list: ^List) -> bool {
return list.head == nil
}
pop_front :: proc(list: ^List) -> ^Node {
link := list.head
if link == nil {
return nil
}
if link.next != nil {
link.next.prev = link.prev
}
if link.prev != nil {
link.prev.next = link.next
}
if link == list.head {
list.head = link.next
}
if link == list.tail {
list.tail = link.prev
}
return link
}
pop_back :: proc(list: ^List) -> ^Node {
link := list.tail
if link == nil {
return nil
}
if link.next != nil {
link.next.prev = link.prev
}
if link.prev != nil {
link.prev.next = link.next
}
if link == list.head {
list.head = link.next
}
if link == list.tail {
list.tail = link.prev
}
return link
}
Iterator :: struct($T: typeid) {
curr: ^Node,
offset: uintptr,
}
iterator_head :: proc(list: List, $T: typeid, $field_name: string) -> Iterator(T)
where intrinsics.type_has_field(T, field_name),
intrinsics.type_field_type(T, field_name) == Node {
return {list.head, offset_of_by_string(T, field_name)}
}
iterator_tail :: proc(list: List, $T: typeid, $field_name: string) -> Iterator(T)
where intrinsics.type_has_field(T, field_name),
intrinsics.type_field_type(T, field_name) == Node {
return {list.tail, offset_of_by_string(T, field_name)}
}
iterator_from_node :: proc(node: ^Node, $T: typeid, $field_name: string) -> Iterator(T)
where intrinsics.type_has_field(T, field_name),
intrinsics.type_field_type(T, field_name) == Node {
return {node, offset_of_by_string(T, field_name)}
}
iterate_next :: proc(it: ^Iterator($T)) -> (ptr: ^T, ok: bool) {
node := it.curr
if node == nil {
return nil, false
}
it.curr = node.next
return (^T)(uintptr(node) - it.offset), true
}
iterate_prev :: proc(it: ^Iterator($T)) -> (ptr: ^T, ok: bool) {
node := it.curr
if node == nil {
return nil, false
}
it.curr = node.prev
return (^T)(uintptr(node) - it.offset), true
}
+9 -9
View File
@@ -65,20 +65,22 @@ set :: proc(c: ^$C/Cache($Key, $Value), key: Key, value: Value) -> runtime.Alloc
return nil
}
e := new(Node(Key, Value), c.node_allocator) or_return
e.key = key
e.value = value
e : ^Node(Key, Value) = nil
assert(c.count <= c.capacity)
if c.count == c.capacity {
_remove_node(c, c.tail)
e = c.tail
_remove_node(c, e)
}
else {
c.count += 1
e = new(Node(Key, Value), c.node_allocator) or_return
}
_push_front_node(c, e)
e.key = key
e.value = value
_push_front_node(c, e)
c.entries[key] = e
return nil
}
@@ -128,6 +130,7 @@ remove :: proc(c: ^$C/Cache($Key, $Value), key: Key) -> bool {
return false
}
_remove_node(c, e)
free(node, c.node_allocator)
c.count -= 1
return true
}
@@ -153,9 +156,6 @@ _remove_node :: proc(c: ^$C/Cache($Key, $Value), node: ^Node(Key, Value)) {
delete_key(&c.entries, node.key)
_call_on_remove(c, node)
free(node, c.node_allocator)
}
@(private)
+2 -2
View File
@@ -86,7 +86,7 @@ pop_back_safe :: proc(a: ^$A/Small_Array($N, $T)) -> (item: T, ok: bool) {
return
}
pop_front_safe :: proc(a: ^$A/Small_Array($N, $T)) -> (T, bool) {
pop_front_safe :: proc(a: ^$A/Small_Array($N, $T)) -> (item: T, ok: bool) {
if N > 0 && a.len > 0 {
item = a.data[0]
s := slice(a)
@@ -114,4 +114,4 @@ push_back_elems :: proc(a: ^$A/Small_Array($N, $T), items: ..T) {
append_elem :: push_back
append_elems :: push_back_elems
push :: proc{push_back, push_back_elems}
append :: proc{push_back, push_back_elems}
append :: proc{push_back, push_back_elems}
+21
View File
@@ -0,0 +1,21 @@
# License
By obtaining, using and/or copying this work, you (the licensee) agree that you have read, understood, and will comply with the following terms and conditions.
Permission to copy, modify, and distribute this software and its documentation, with or without modification, for any purpose and without fee or royalty is hereby granted, provided that you include the following on ALL copies of the software and documentation or portions thereof, including modifications:
The full text of this NOTICE in a location viewable to users of the redistributed or derivative work.
Any pre-existing intellectual property disclaimers, notices, or terms and conditions. If none exist, the W3C Software Short Notice should be included (hypertext is preferred, text is permitted) within the body of any redistributed or derivative code.
Notice of any changes or modifications to the files, including the date changes were made. (We recommend you provide URIs to the location from which the code is derived.)
# Disclaimers
THIS SOFTWARE AND DOCUMENTATION IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE OR DOCUMENTATION WILL NOT INFRINGE ANY THIRD PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS.
COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE SOFTWARE OR DOCUMENTATION.
The name and trademarks of copyright holders may NOT be used in advertising or publicity pertaining to the software without specific, written prior permission. Title to copyright in this software and any associated documentation will at all times remain with copyright holders.
# Notes
This version: http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231
+374
View File
@@ -0,0 +1,374 @@
package unicode_entity
/*
A unicode entity encoder/decoder
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
Made available under Odin's BSD-3 license.
This code has several procedures to map unicode runes to/from different textual encodings.
- SGML/XML/HTML entity
-- &#<decimal>;
-- &#x<hexadecimal>;
-- &<entity name>; (If the lookup tables are compiled in).
Reference: https://www.w3.org/2003/entities/2007xml/unicode.xml
- URL encode / decode %hex entity
Reference: https://datatracker.ietf.org/doc/html/rfc3986/#section-2.1
List of contributors:
Jeroen van Rijn: Initial implementation.
*/
import "core:unicode/utf8"
import "core:unicode"
import "core:strings"
MAX_RUNE_CODEPOINT :: int(unicode.MAX_RUNE)
write_rune :: strings.write_rune_builder
write_string :: strings.write_string_builder
Error :: enum u8 {
None = 0,
Tokenizer_Is_Nil,
Illegal_NUL_Character,
Illegal_UTF_Encoding,
Illegal_BOM,
CDATA_Not_Terminated,
Comment_Not_Terminated,
Invalid_Entity_Encoding,
}
Tokenizer :: struct {
r: rune,
w: int,
src: string,
offset: int,
read_offset: int,
}
CDATA_START :: "<![CDATA["
CDATA_END :: "]]>"
COMMENT_START :: "<!--"
COMMENT_END :: "-->"
/*
Default: CDATA and comments are passed through unchanged.
*/
XML_Decode_Option :: enum u8 {
/*
Do not decode & entities. It decodes by default.
If given, overrides `Decode_CDATA`.
*/
No_Entity_Decode,
/*
CDATA is unboxed.
*/
Unbox_CDATA,
/*
Unboxed CDATA is decoded as well.
Ignored if `.Unbox_CDATA` is not given.
*/
Decode_CDATA,
/*
Comments are stripped.
*/
Comment_Strip,
}
XML_Decode_Options :: bit_set[XML_Decode_Option; u8]
/*
Decode a string that may include SGML/XML/HTML entities.
The caller has to free the result.
*/
decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator := context.allocator) -> (decoded: string, err: Error) {
context.allocator = allocator
l := len(input)
if l == 0 { return "", .None }
builder := strings.make_builder()
defer strings.destroy_builder(&builder)
t := Tokenizer{src=input}
in_data := false
loop: for {
advance(&t) or_return
if t.r < 0 { break loop }
/*
Below here we're never inside a CDATA tag.
At most we'll see the start of one, but that doesn't affect the logic.
*/
switch t.r {
case '<':
/*
Might be the start of a CDATA tag or comment.
We don't need to check if we need to write a `<`, because if it isn't CDATA or a comment,
it couldn't have been part of an XML tag body to be decoded here.
Keep in mind that we could already *be* inside a CDATA tag.
If so, write `>` as a literal and continue.
*/
if in_data {
write_rune(&builder, '<')
continue
}
in_data = _handle_xml_special(&t, &builder, options) or_return
case ']':
/*
If we're unboxing _and_ decoding CDATA, we'll have to check for the end tag.
*/
if in_data {
if t.read_offset + len(CDATA_END) < len(t.src) {
if string(t.src[t.offset:][:len(CDATA_END)]) == CDATA_END {
in_data = false
t.read_offset += len(CDATA_END) - 1
}
}
continue
} else {
write_rune(&builder, ']')
}
case:
if in_data && .Decode_CDATA not_in options {
/*
Unboxed, but undecoded.
*/
write_rune(&builder, t.r)
continue
}
if t.r == '&' {
if entity, entity_err := _extract_xml_entity(&t); entity_err != .None {
/*
We read to the end of the string without closing the entity.
Pass through as-is.
*/
write_string(&builder, entity)
} else {
if .No_Entity_Decode not_in options {
if decoded, ok := xml_decode_entity(entity); ok {
write_rune(&builder, decoded)
continue
}
}
/*
Literal passthrough because the decode failed or we want entities not decoded.
*/
write_string(&builder, "&")
write_string(&builder, entity)
write_string(&builder, ";")
}
} else {
write_rune(&builder, t.r)
}
}
}
return strings.clone(strings.to_string(builder), allocator), err
}
advance :: proc(t: ^Tokenizer) -> (err: Error) {
if t == nil { return .Tokenizer_Is_Nil }
using t
#no_bounds_check {
if read_offset < len(src) {
offset = read_offset
r, w = rune(src[read_offset]), 1
switch {
case r == 0:
return .Illegal_NUL_Character
case r >= utf8.RUNE_SELF:
r, w = utf8.decode_rune_in_string(src[read_offset:])
if r == utf8.RUNE_ERROR && w == 1 {
return .Illegal_UTF_Encoding
} else if r == utf8.RUNE_BOM && offset > 0 {
return .Illegal_BOM
}
}
read_offset += w
return .None
} else {
offset = len(src)
r = -1
return
}
}
}
xml_decode_entity :: proc(entity: string) -> (decoded: rune, ok: bool) {
entity := entity
if len(entity) == 0 { return -1, false }
switch entity[0] {
case '#':
base := 10
val := 0
entity = entity[1:]
if len(entity) == 0 { return -1, false }
if entity[0] == 'x' || entity[0] == 'X' {
base = 16
entity = entity[1:]
}
for len(entity) > 0 {
r := entity[0]
switch r {
case '0'..'9':
val *= base
val += int(r - '0')
case 'a'..'f':
if base == 10 { return -1, false }
val *= base
val += int(r - 'a' + 10)
case 'A'..'F':
if base == 10 { return -1, false }
val *= base
val += int(r - 'A' + 10)
case:
return -1, false
}
if val > MAX_RUNE_CODEPOINT { return -1, false }
entity = entity[1:]
}
return rune(val), true
case:
/*
Named entity.
*/
return named_xml_entity_to_rune(entity)
}
}
/*
Private XML helper to extract `&<stuff>;` entity.
*/
@(private="file")
_extract_xml_entity :: proc(t: ^Tokenizer) -> (entity: string, err: Error) {
assert(t != nil && t.r == '&')
/*
All of these would be in the ASCII range.
Even if one is not, it doesn't matter. All characters we need to compare to extract are.
*/
using t
length := len(t.src)
found := false
#no_bounds_check {
for read_offset < length {
if src[read_offset] == ';' {
found = true
read_offset += 1
break
}
read_offset += 1
}
}
if found {
return string(src[offset + 1 : read_offset - 1]), .None
}
return string(src[offset : read_offset]), .Invalid_Entity_Encoding
}
/*
Private XML helper for CDATA and comments.
*/
@(private="file")
_handle_xml_special :: proc(t: ^Tokenizer, builder: ^strings.Builder, options: XML_Decode_Options) -> (in_data: bool, err: Error) {
assert(t != nil && t.r == '<')
if t.read_offset + len(CDATA_START) >= len(t.src) { return false, .None }
if string(t.src[t.offset:][:len(CDATA_START)]) == CDATA_START {
t.read_offset += len(CDATA_START) - 1
if .Unbox_CDATA in options && .Decode_CDATA in options {
/*
We're unboxing _and_ decoding CDATA
*/
return true, .None
}
/*
CDATA is passed through.
*/
offset := t.offset
/*
Scan until end of CDATA.
*/
for {
advance(t) or_return
if t.r < 0 { return true, .CDATA_Not_Terminated }
if t.read_offset + len(CDATA_END) < len(t.src) {
if string(t.src[t.offset:][:len(CDATA_END)]) == CDATA_END {
t.read_offset += len(CDATA_END) - 1
cdata := string(t.src[offset : t.read_offset])
if .Unbox_CDATA in options {
cdata = cdata[len(CDATA_START):]
cdata = cdata[:len(cdata) - len(CDATA_END)]
}
write_string(builder, cdata)
return false, .None
}
}
}
} else if string(t.src[t.offset:][:len(COMMENT_START)]) == COMMENT_START {
t.read_offset += len(COMMENT_START)
/*
Comment is passed through by default.
*/
offset := t.offset
/*
Scan until end of Comment.
*/
for {
advance(t) or_return
if t.r < 0 { return true, .Comment_Not_Terminated }
if t.read_offset + len(COMMENT_END) < len(t.src) {
if string(t.src[t.offset:][:len(COMMENT_END)]) == COMMENT_END {
t.read_offset += len(COMMENT_END) - 1
if .Comment_Strip not_in options {
comment := string(t.src[offset : t.read_offset])
write_string(builder, comment)
}
return false, .None
}
}
}
}
return false, .None
}
@@ -0,0 +1,76 @@
package unicode_entity_example
import "core:encoding/xml"
import "core:strings"
import "core:mem"
import "core:fmt"
import "core:time"
doc_print :: proc(doc: ^xml.Document) {
buf: strings.Builder
defer strings.destroy_builder(&buf)
w := strings.to_writer(&buf)
xml.print(w, doc)
fmt.println(strings.to_string(buf))
}
_entities :: proc() {
doc: ^xml.Document
err: xml.Error
DOC :: #load("../../../../tests/core/assets/XML/unicode.xml")
OPTIONS :: xml.Options{
flags = {
.Ignore_Unsupported, .Intern_Comments,
},
expected_doctype = "",
}
parse_duration: time.Duration
{
time.SCOPED_TICK_DURATION(&parse_duration)
doc, err = xml.parse(DOC, OPTIONS)
}
defer xml.destroy(doc)
doc_print(doc)
ms := time.duration_milliseconds(parse_duration)
speed := (f64(1000.0) / ms) * f64(len(DOC)) / 1_024.0 / 1_024.0
fmt.printf("Parse time: %.2f ms (%.2f MiB/s).\n", ms, speed)
fmt.printf("Error: %v\n", err)
}
_main :: proc() {
using fmt
options := xml.Options{ flags = { .Ignore_Unsupported, .Intern_Comments, .Unbox_CDATA, .Decode_SGML_Entities }}
doc, _ := xml.parse(#load("test.html"), options)
defer xml.destroy(doc)
doc_print(doc)
}
main :: proc() {
using fmt
track: mem.Tracking_Allocator
mem.tracking_allocator_init(&track, context.allocator)
context.allocator = mem.tracking_allocator(&track)
// _main()
_entities()
if len(track.allocation_map) > 0 {
println()
for _, v in track.allocation_map {
printf("%v Leaked %v bytes.\n", v.location, v.size)
}
}
}
+28
View File
@@ -0,0 +1,28 @@
<html>
<head>
<title>Entity Reference Test</title>
<style>
body {
background: #000; color: #eee;
width: 40%;
margin-left: auto;
margin-right: auto;
font-size: 14pt;
}
</style>
</head>
<body>
<h1>Entity Reference Test</h1>
<div id="test_cdata_in_comment" foo="">
Foozle]!&#32;&copy;&#x20;<!-- <![CDATA[&#32;&reg;&#x20;]]> -->42&;1234&
</div>
<!-- EXPECTED: Foozle]! © 42&;1234& -->
<div id="test_cdata_unwrap_and_passthrough">
Foozle]!&#32;&copy;&#x20;<![CDATA[BOX&#32;&reg;&#x20;/BOX]]>42&;1234&
</div>
<!-- EXPECTED: Foozle]! © BOX ® /BOX42&;1234& -->
<div>
&verbar; &vert; &VerticalLine; &fjlig; &grave; &bsol; &reg; &rhov; &CounterClockwiseContourIntegral; &bsemi;
</div>
</body>
</html>
File diff suppressed because it is too large Load Diff
+6
View File
@@ -354,6 +354,12 @@ unquote_string :: proc(token: Token, spec: Specification, allocator := context.a
b := bytes_make(len(s) + 2*utf8.UTF_MAX, 1, allocator) or_return
w := copy(b, s[0:i])
if len(b) == 0 && allocator.data == nil {
// `unmarshal_count_array` calls us with a nil allocator
return string(b[:w]), nil
}
loop: for i < len(s) {
c := s[i]
switch {
+86
View File
@@ -0,0 +1,86 @@
/*
An XML 1.0 / 1.1 parser
Copyright 2021-2022 Jeroen van Rijn <nom@duclavier.com>.
Made available under Odin's BSD-3 license.
A from-scratch XML implementation, loosely modeled on the [spec](https://www.w3.org/TR/2006/REC-xml11-20060816).
List of contributors:
Jeroen van Rijn: Initial implementation.
*/
package xml
import "core:io"
import "core:fmt"
/*
Just for debug purposes.
*/
print :: proc(writer: io.Writer, doc: ^Document) -> (written: int, err: io.Error) {
if doc == nil { return }
using fmt
written += wprintf(writer, "[XML Prolog]\n")
for attr in doc.prolog {
written += wprintf(writer, "\t%v: %v\n", attr.key, attr.val)
}
written += wprintf(writer, "[Encoding] %v\n", doc.encoding)
if len(doc.doctype.ident) > 0 {
written += wprintf(writer, "[DOCTYPE] %v\n", doc.doctype.ident)
if len(doc.doctype.rest) > 0 {
wprintf(writer, "\t%v\n", doc.doctype.rest)
}
}
for comment in doc.comments {
written += wprintf(writer, "[Pre-root comment] %v\n", comment)
}
if len(doc.elements) > 0 {
wprintln(writer, " --- ")
print_element(writer, doc, 0)
wprintln(writer, " --- ")
}
return written, .None
}
print_element :: proc(writer: io.Writer, doc: ^Document, element_id: Element_ID, indent := 0) -> (written: int, err: io.Error) {
using fmt
tab :: proc(writer: io.Writer, indent: int) {
for _ in 0..=indent {
wprintf(writer, "\t")
}
}
tab(writer, indent)
element := doc.elements[element_id]
if element.kind == .Element {
wprintf(writer, "<%v>\n", element.ident)
if len(element.value) > 0 {
tab(writer, indent + 1)
wprintf(writer, "[Value] %v\n", element.value)
}
for attr in element.attribs {
tab(writer, indent + 1)
wprintf(writer, "[Attr] %v: %v\n", attr.key, attr.val)
}
for child in element.children {
print_element(writer, doc, child, indent + 1)
}
} else if element.kind == .Comment {
wprintf(writer, "[COMMENT] %v\n", element.value)
}
return written, .None
}
+112
View File
@@ -0,0 +1,112 @@
package xml_example
import "core:encoding/xml"
import "core:mem"
import "core:fmt"
import "core:time"
import "core:strings"
import "core:hash"
N :: 1
example :: proc() {
using fmt
docs: [N]^xml.Document
errs: [N]xml.Error
times: [N]time.Duration
defer for round in 0..<N {
xml.destroy(docs[round])
}
DOC :: #load("../../../../tests/core/assets/XML/unicode.xml")
input := DOC
for round in 0..<N {
start := time.tick_now()
docs[round], errs[round] = xml.parse(input, xml.Options{
flags={.Ignore_Unsupported},
expected_doctype = "",
})
end := time.tick_now()
times[round] = time.tick_diff(start, end)
}
fastest := time.Duration(max(i64))
slowest := time.Duration(0)
total := time.Duration(0)
for round in 0..<N {
fastest = min(fastest, times[round])
slowest = max(slowest, times[round])
total += times[round]
}
fastest_ms := time.duration_milliseconds(fastest)
slowest_ms := time.duration_milliseconds(slowest)
average_ms := time.duration_milliseconds(time.Duration(f64(total) / f64(N)))
fastest_speed := (f64(1000.0) / fastest_ms) * f64(len(DOC)) / 1_024.0 / 1_024.0
slowest_speed := (f64(1000.0) / slowest_ms) * f64(len(DOC)) / 1_024.0 / 1_024.0
average_speed := (f64(1000.0) / average_ms) * f64(len(DOC)) / 1_024.0 / 1_024.0
fmt.printf("N = %v\n", N)
fmt.printf("[Fastest]: %v bytes in %.2f ms (%.2f MiB/s).\n", len(input), fastest_ms, fastest_speed)
fmt.printf("[Slowest]: %v bytes in %.2f ms (%.2f MiB/s).\n", len(input), slowest_ms, slowest_speed)
fmt.printf("[Average]: %v bytes in %.2f ms (%.2f MiB/s).\n", len(input), average_ms, average_speed)
if errs[0] != .None {
printf("Load/Parse error: %v\n", errs[0])
if errs[0] == .File_Error {
println("\"unicode.xml\" not found. Did you run \"tests\\download_assets.py\"?")
}
return
}
charlist, charlist_ok := xml.find_child_by_ident(docs[0], 0, "charlist")
if !charlist_ok {
eprintln("Could not locate top-level `<charlist>` tag.")
return
}
printf("Found `<charlist>` with %v children, %v elements total\n", len(docs[0].elements[charlist].children), docs[0].element_count)
crc32 := doc_hash(docs[0])
printf("[%v] CRC32: 0x%08x\n", "🎉" if crc32 == 0xcaa042b9 else "🤬", crc32)
for round in 0..<N {
defer xml.destroy(docs[round])
}
}
doc_hash :: proc(doc: ^xml.Document, print := false) -> (crc32: u32) {
buf: strings.Builder
defer strings.destroy_builder(&buf)
w := strings.to_writer(&buf)
xml.print(w, doc)
tree := strings.to_string(buf)
if print { fmt.println(tree) }
return hash.crc32(transmute([]u8)tree)
}
main :: proc() {
using fmt
track: mem.Tracking_Allocator
mem.tracking_allocator_init(&track, context.allocator)
context.allocator = mem.tracking_allocator(&track)
example()
if len(track.allocation_map) > 0 {
println()
for _, v in track.allocation_map {
printf("%v Leaked %v bytes.\n", v.location, v.size)
}
}
println("Done and cleaned up!")
}
+45
View File
@@ -0,0 +1,45 @@
/*
An XML 1.0 / 1.1 parser
Copyright 2021-2022 Jeroen van Rijn <nom@duclavier.com>.
Made available under Odin's BSD-3 license.
This file contains helper functions.
*/
package xml
// Find parent's nth child with a given ident.
find_child_by_ident :: proc(doc: ^Document, parent_id: Element_ID, ident: string, nth := 0) -> (res: Element_ID, found: bool) {
tag := doc.elements[parent_id]
count := 0
for child_id in tag.children {
child := doc.elements[child_id]
/*
Skip commments. They have no name.
*/
if child.kind != .Element { continue }
/*
If the ident matches and it's the nth such child, return it.
*/
if child.ident == ident {
if count == nth { return child_id, true }
count += 1
}
}
return 0, false
}
// Find an attribute by key.
find_attribute_val_by_key :: proc(doc: ^Document, parent_id: Element_ID, key: string) -> (val: string, found: bool) {
tag := doc.elements[parent_id]
for attr in tag.attribs {
/*
If the ident matches, we're done. There can only ever be one attribute with the same name.
*/
if attr.key == key { return attr.val, true }
}
return "", false
}
+436
View File
@@ -0,0 +1,436 @@
/*
An XML 1.0 / 1.1 parser
Copyright 2021-2022 Jeroen van Rijn <nom@duclavier.com>.
Made available under Odin's BSD-3 license.
A from-scratch XML implementation, loosely modeled on the [spec](https://www.w3.org/TR/2006/REC-xml11-20060816).
List of contributors:
Jeroen van Rijn: Initial implementation.
*/
package xml
import "core:fmt"
import "core:unicode"
import "core:unicode/utf8"
Error_Handler :: #type proc(pos: Pos, fmt: string, args: ..any)
Token :: struct {
kind: Token_Kind,
text: string,
pos: Pos,
}
Pos :: struct {
file: string,
offset: int, // starting at 0
line: int, // starting at 1
column: int, // starting at 1
}
Token_Kind :: enum {
Invalid,
Ident,
Literal,
Rune,
String,
Double_Quote, // "
Single_Quote, // '
Colon, // :
Eq, // =
Lt, // <
Gt, // >
Exclaim, // !
Question, // ?
Hash, // #
Slash, // /
Dash, // -
Open_Bracket, // [
Close_Bracket, // ]
EOF,
}
CDATA_START :: "<![CDATA["
CDATA_END :: "]]>"
COMMENT_START :: "<!--"
COMMENT_END :: "-->"
Tokenizer :: struct {
// Immutable data
path: string,
src: string,
err: Error_Handler,
// Tokenizing state
ch: rune,
offset: int,
read_offset: int,
line_offset: int,
line_count: int,
// Mutable data
error_count: int,
}
init :: proc(t: ^Tokenizer, src: string, path: string, err: Error_Handler = default_error_handler) {
t.src = src
t.err = err
t.ch = ' '
t.offset = 0
t.read_offset = 0
t.line_offset = 0
t.line_count = len(src) > 0 ? 1 : 0
t.error_count = 0
t.path = path
advance_rune(t)
if t.ch == utf8.RUNE_BOM {
advance_rune(t)
}
}
@(private)
offset_to_pos :: proc(t: ^Tokenizer, offset: int) -> Pos {
line := t.line_count
column := offset - t.line_offset + 1
return Pos {
file = t.path,
offset = offset,
line = line,
column = column,
}
}
default_error_handler :: proc(pos: Pos, msg: string, args: ..any) {
fmt.eprintf("%s(%d:%d) ", pos.file, pos.line, pos.column)
fmt.eprintf(msg, ..args)
fmt.eprintf("\n")
}
error :: proc(t: ^Tokenizer, offset: int, msg: string, args: ..any) {
pos := offset_to_pos(t, offset)
if t.err != nil {
t.err(pos, msg, ..args)
}
t.error_count += 1
}
@(optimization_mode="speed")
advance_rune :: proc(using t: ^Tokenizer) {
#no_bounds_check {
/*
Already bounds-checked here.
*/
if read_offset < len(src) {
offset = read_offset
if ch == '\n' {
line_offset = offset
line_count += 1
}
r, w := rune(src[read_offset]), 1
switch {
case r == 0:
error(t, t.offset, "illegal character NUL")
case r >= utf8.RUNE_SELF:
r, w = #force_inline utf8.decode_rune_in_string(src[read_offset:])
if r == utf8.RUNE_ERROR && w == 1 {
error(t, t.offset, "illegal UTF-8 encoding")
} else if r == utf8.RUNE_BOM && offset > 0 {
error(t, t.offset, "illegal byte order mark")
}
}
read_offset += w
ch = r
} else {
offset = len(src)
if ch == '\n' {
line_offset = offset
line_count += 1
}
ch = -1
}
}
}
peek_byte :: proc(t: ^Tokenizer, offset := 0) -> byte {
if t.read_offset+offset < len(t.src) {
#no_bounds_check return t.src[t.read_offset+offset]
}
return 0
}
@(optimization_mode="speed")
skip_whitespace :: proc(t: ^Tokenizer) {
for {
switch t.ch {
case ' ', '\t', '\r', '\n':
advance_rune(t)
case:
return
}
}
}
@(optimization_mode="speed")
is_letter :: proc(r: rune) -> bool {
if r < utf8.RUNE_SELF {
switch r {
case '_':
return true
case 'A'..='Z', 'a'..='z':
return true
}
}
return unicode.is_letter(r)
}
is_valid_identifier_rune :: proc(r: rune) -> bool {
if r < utf8.RUNE_SELF {
switch r {
case '_', '-', ':': return true
case 'A'..='Z', 'a'..='z': return true
case '0'..'9': return true
case -1: return false
}
}
if unicode.is_letter(r) || unicode.is_digit(r) {
return true
}
return false
}
scan_identifier :: proc(t: ^Tokenizer) -> string {
offset := t.offset
namespaced := false
for is_valid_identifier_rune(t.ch) {
advance_rune(t)
if t.ch == ':' {
/*
A namespaced attr can have at most two parts, `namespace:ident`.
*/
if namespaced {
break
}
namespaced = true
}
}
return string(t.src[offset : t.offset])
}
/*
A comment ends when we see -->, preceded by a character that's not a dash.
"For compatibility, the string "--" (double-hyphen) must not occur within comments."
See: https://www.w3.org/TR/2006/REC-xml11-20060816/#dt-comment
Thanks to the length (4) of the comment start, we also have enough lookback,
and the peek at the next byte asserts that there's at least one more character
that's a `>`.
*/
scan_comment :: proc(t: ^Tokenizer) -> (comment: string, err: Error) {
offset := t.offset
for {
advance_rune(t)
ch := t.ch
if ch < 0 {
error(t, offset, "[parse] Comment was not terminated\n")
return "", .Unclosed_Comment
}
if string(t.src[t.offset - 1:][:2]) == "--" {
if peek_byte(t) == '>' {
break
} else {
error(t, t.offset - 1, "Invalid -- sequence in comment.\n")
return "", .Invalid_Sequence_In_Comment
}
}
}
expect(t, .Dash)
expect(t, .Gt)
return string(t.src[offset : t.offset - 1]), .None
}
/*
Skip CDATA
*/
skip_cdata :: proc(t: ^Tokenizer) -> (err: Error) {
if t.read_offset + len(CDATA_START) >= len(t.src) {
/*
Can't be the start of a CDATA tag.
*/
return .None
}
if string(t.src[t.offset:][:len(CDATA_START)]) == CDATA_START {
t.read_offset += len(CDATA_START)
offset := t.offset
cdata_scan: for {
advance_rune(t)
if t.ch < 0 {
error(t, offset, "[scan_string] CDATA was not terminated\n")
return .Premature_EOF
}
/*
Scan until the end of a CDATA tag.
*/
if t.read_offset + len(CDATA_END) < len(t.src) {
if string(t.src[t.offset:][:len(CDATA_END)]) == CDATA_END {
t.read_offset += len(CDATA_END)
break cdata_scan
}
}
}
}
return
}
@(optimization_mode="speed")
scan_string :: proc(t: ^Tokenizer, offset: int, close: rune = '<', consume_close := false, multiline := true) -> (value: string, err: Error) {
err = .None
loop: for {
ch := t.ch
switch ch {
case -1:
error(t, t.offset, "[scan_string] Premature end of file.\n")
return "", .Premature_EOF
case '<':
if peek_byte(t) == '!' {
if peek_byte(t, 1) == '[' {
/*
Might be the start of a CDATA tag.
*/
skip_cdata(t) or_return
} else if peek_byte(t, 1) == '-' && peek_byte(t, 2) == '-' {
/*
Comment start. Eat comment.
*/
t.read_offset += 3
_ = scan_comment(t) or_return
}
}
case '\n':
if !multiline {
error(t, offset, string(t.src[offset : t.offset]))
error(t, offset, "[scan_string] Not terminated\n")
err = .Invalid_Tag_Value
break loop
}
}
if t.ch == close {
/*
If it's not a CDATA or comment, it's the end of this body.
*/
break loop
}
advance_rune(t)
}
/*
Strip trailing whitespace.
*/
lit := string(t.src[offset : t.offset])
end := len(lit)
eat: for ; end > 0; end -= 1 {
ch := lit[end - 1]
switch ch {
case ' ', '\t', '\r', '\n':
case:
break eat
}
}
lit = lit[:end]
if consume_close {
advance_rune(t)
}
/*
TODO: Handle decoding escape characters and unboxing CDATA.
*/
return lit, err
}
peek :: proc(t: ^Tokenizer) -> (token: Token) {
old := t^
token = scan(t)
t^ = old
return token
}
scan :: proc(t: ^Tokenizer) -> Token {
skip_whitespace(t)
offset := t.offset
kind: Token_Kind
err: Error
lit: string
pos := offset_to_pos(t, offset)
switch ch := t.ch; true {
case is_letter(ch):
lit = scan_identifier(t)
kind = .Ident
case:
advance_rune(t)
switch ch {
case -1:
kind = .EOF
case '<': kind = .Lt
case '>': kind = .Gt
case '!': kind = .Exclaim
case '?': kind = .Question
case '=': kind = .Eq
case '#': kind = .Hash
case '/': kind = .Slash
case '-': kind = .Dash
case ':': kind = .Colon
case '"', '\'':
kind = .Invalid
lit, err = scan_string(t, t.offset, ch, true, false)
if err == .None {
kind = .String
}
case '\n':
lit = "\n"
case:
kind = .Invalid
}
}
if kind != .String && lit == "" {
lit = string(t.src[offset : t.offset])
}
return Token{kind, lit, pos}
}
+708
View File
@@ -0,0 +1,708 @@
/*
An XML 1.0 / 1.1 parser
Copyright 2021-2022 Jeroen van Rijn <nom@duclavier.com>.
Made available under Odin's BSD-3 license.
A from-scratch XML implementation, loosely modelled on the [spec](https://www.w3.org/TR/2006/REC-xml11-20060816).
Features:
- Supports enough of the XML 1.0/1.1 spec to handle the 99.9% of XML documents in common current usage.
- Simple to understand and use. Small.
Caveats:
- We do NOT support HTML in this package, as that may or may not be valid XML.
If it works, great. If it doesn't, that's not considered a bug.
- We do NOT support UTF-16. If you have a UTF-16 XML file, please convert it to UTF-8 first. Also, our condolences.
- <[!ELEMENT and <[!ATTLIST are not supported, and will be either ignored or return an error depending on the parser options.
MAYBE:
- XML writer?
- Serialize/deserialize Odin types?
List of contributors:
Jeroen van Rijn: Initial implementation.
*/
package xml
// An XML 1.0 / 1.1 parser
import "core:bytes"
import "core:encoding/entity"
import "core:intrinsics"
import "core:mem"
import "core:os"
import "core:strings"
likely :: intrinsics.expect
DEFAULT_Options :: Options{
flags = {
.Ignore_Unsupported,
},
expected_doctype = "",
}
Option_Flag :: enum {
/*
If the caller says that input may be modified, we can perform in-situ parsing.
If this flag isn't provided, the XML parser first duplicates the input so that it can.
*/
Input_May_Be_Modified,
/*
Document MUST start with `<?xml` prolog.
*/
Must_Have_Prolog,
/*
Document MUST have a `<!DOCTYPE`.
*/
Must_Have_DocType,
/*
By default we skip comments. Use this option to intern a comment on a parented Element.
*/
Intern_Comments,
/*
How to handle unsupported parts of the specification, like <! other than <!DOCTYPE and <![CDATA[
*/
Error_on_Unsupported,
Ignore_Unsupported,
/*
By default CDATA tags are passed-through as-is.
This option unwraps them when encountered.
*/
Unbox_CDATA,
/*
By default SGML entities like `&gt;`, `&#32;` and `&#x20;` are passed-through as-is.
This option decodes them when encountered.
*/
Decode_SGML_Entities,
/*
If a tag body has a comment, it will be stripped unless this option is given.
*/
Keep_Tag_Body_Comments,
}
Option_Flags :: bit_set[Option_Flag; u16]
Document :: struct {
elements: [dynamic]Element,
element_count: Element_ID,
prolog: Attributes,
encoding: Encoding,
doctype: struct {
/*
We only scan the <!DOCTYPE IDENT part and skip the rest.
*/
ident: string,
rest: string,
},
/*
If we encounter comments before the root node, and the option to intern comments is given, this is where they'll live.
Otherwise they'll be in the element tree.
*/
comments: [dynamic]string,
/*
Internal
*/
tokenizer: ^Tokenizer,
allocator: mem.Allocator,
/*
Input. Either the original buffer, or a copy if `.Input_May_Be_Modified` isn't specified.
*/
input: []u8,
strings_to_free: [dynamic]string,
}
Element :: struct {
ident: string,
value: string,
attribs: Attributes,
kind: enum {
Element = 0,
Comment,
},
parent: Element_ID,
children: [dynamic]Element_ID,
}
Attr :: struct {
key: string,
val: string,
}
Attributes :: [dynamic]Attr
Options :: struct {
flags: Option_Flags,
expected_doctype: string,
}
Encoding :: enum {
Unknown,
UTF_8,
ISO_8859_1,
/*
Aliases
*/
LATIN_1 = ISO_8859_1,
}
Error :: enum {
/*
General return values.
*/
None = 0,
General_Error,
Unexpected_Token,
Invalid_Token,
/*
Couldn't find, open or read file.
*/
File_Error,
/*
File too short.
*/
Premature_EOF,
/*
XML-specific errors.
*/
No_Prolog,
Invalid_Prolog,
Too_Many_Prologs,
No_DocType,
Too_Many_DocTypes,
DocType_Must_Preceed_Elements,
/*
If a DOCTYPE is present _or_ the caller
asked for a specific DOCTYPE and the DOCTYPE
and root tag don't match, we return `.Invalid_DocType`.
*/
Invalid_DocType,
Invalid_Tag_Value,
Mismatched_Closing_Tag,
Unclosed_Comment,
Comment_Before_Root_Element,
Invalid_Sequence_In_Comment,
Unsupported_Version,
Unsupported_Encoding,
/*
<!FOO are usually skipped.
*/
Unhandled_Bang,
Duplicate_Attribute,
Conflicting_Options,
}
/*
Implementation starts here.
*/
parse_from_slice :: proc(data: []u8, options := DEFAULT_Options, path := "", error_handler := default_error_handler, allocator := context.allocator) -> (doc: ^Document, err: Error) {
data := data
context.allocator = allocator
opts := validate_options(options) or_return
/*
If `.Input_May_Be_Modified` is not specified, we duplicate the input so that we can modify it in-place.
*/
if .Input_May_Be_Modified not_in opts.flags {
data = bytes.clone(data)
}
t := &Tokenizer{}
init(t, string(data), path, error_handler)
doc = new(Document)
doc.allocator = allocator
doc.tokenizer = t
doc.input = data
doc.elements = make([dynamic]Element, 1024, 1024, allocator)
// strings.intern_init(&doc.intern, allocator, allocator)
err = .Unexpected_Token
element, parent: Element_ID
tag_is_open := false
first_element := true
open: Token
/*
If a DOCTYPE is present, the root tag has to match.
If an expected DOCTYPE is given in options (i.e. it's non-empty), the DOCTYPE (if present) and root tag have to match.
*/
expected_doctype := options.expected_doctype
loop: for {
skip_whitespace(t)
// NOTE(Jeroen): This is faster as a switch.
switch t.ch {
case '<':
/*
Consume peeked `<`
*/
advance_rune(t)
open = scan(t)
// NOTE(Jeroen): We're not using a switch because this if-else chain ordered by likelihood is 2.5% faster at -o:size and -o:speed.
if likely(open.kind, Token_Kind.Ident) == .Ident {
/*
e.g. <odin - Start of new element.
*/
element = new_element(doc)
tag_is_open = true
if first_element {
/*
First element.
*/
parent = element
first_element = false
} else {
append(&doc.elements[parent].children, element)
}
doc.elements[element].parent = parent
doc.elements[element].ident = open.text
parse_attributes(doc, &doc.elements[element].attribs) or_return
/*
If a DOCTYPE is present _or_ the caller
asked for a specific DOCTYPE and the DOCTYPE
and root tag don't match, we return .Invalid_Root_Tag.
*/
if element == 0 { // Root tag?
if len(expected_doctype) > 0 && expected_doctype != open.text {
error(t, t.offset, "Root Tag doesn't match DOCTYPE. Expected: %v, got: %v\n", expected_doctype, open.text)
return doc, .Invalid_DocType
}
}
/*
One of these should follow:
- `>`, which means we've just opened this tag and expect a later element to close it.
- `/>`, which means this is an 'empty' or self-closing tag.
*/
end_token := scan(t)
#partial switch end_token.kind {
case .Gt:
/*
We're now the new parent.
*/
parent = element
case .Slash:
/*
Empty tag. Close it.
*/
expect(t, .Gt) or_return
parent = doc.elements[element].parent
element = parent
tag_is_open = false
case:
error(t, t.offset, "Expected close tag, got: %#v\n", end_token)
return
}
} else if open.kind == .Slash {
/*
Close tag.
*/
ident := expect(t, .Ident) or_return
_ = expect(t, .Gt) or_return
if doc.elements[element].ident != ident.text {
error(t, t.offset, "Mismatched Closing Tag. Expected %v, got %v\n", doc.elements[element].ident, ident.text)
return doc, .Mismatched_Closing_Tag
}
parent = doc.elements[element].parent
element = parent
tag_is_open = false
} else if open.kind == .Exclaim {
/*
<!
*/
next := scan(t)
#partial switch next.kind {
case .Ident:
switch next.text {
case "DOCTYPE":
if len(doc.doctype.ident) > 0 {
return doc, .Too_Many_DocTypes
}
if doc.element_count > 0 {
return doc, .DocType_Must_Preceed_Elements
}
parse_doctype(doc) or_return
if len(expected_doctype) > 0 && expected_doctype != doc.doctype.ident {
error(t, t.offset, "Invalid DOCTYPE. Expected: %v, got: %v\n", expected_doctype, doc.doctype.ident)
return doc, .Invalid_DocType
}
expected_doctype = doc.doctype.ident
case:
if .Error_on_Unsupported in opts.flags {
error(t, t.offset, "Unhandled: <!%v\n", next.text)
return doc, .Unhandled_Bang
}
skip_element(t) or_return
}
case .Dash:
/*
Comment: <!-- -->.
The grammar does not allow a comment to end in --->
*/
expect(t, .Dash)
comment := scan_comment(t) or_return
if .Intern_Comments in opts.flags {
if len(doc.elements) == 0 {
append(&doc.comments, comment)
} else {
el := new_element(doc)
doc.elements[el].parent = element
doc.elements[el].kind = .Comment
doc.elements[el].value = comment
append(&doc.elements[element].children, el)
}
}
case:
error(t, t.offset, "Invalid Token after <!. Expected .Ident, got %#v\n", next)
return
}
} else if open.kind == .Question {
/*
<?xml
*/
next := scan(t)
#partial switch next.kind {
case .Ident:
if len(next.text) == 3 && strings.to_lower(next.text, context.temp_allocator) == "xml" {
parse_prolog(doc) or_return
} else if len(doc.prolog) > 0 {
/*
We've already seen a prolog.
*/
return doc, .Too_Many_Prologs
} else {
/*
Could be `<?xml-stylesheet`, etc. Ignore it.
*/
skip_element(t) or_return
}
case:
error(t, t.offset, "Expected \"<?xml\", got \"<?%v\".", next.text)
return
}
} else {
error(t, t.offset, "Invalid Token after <: %#v\n", open)
return
}
case -1:
/*
End of file.
*/
if tag_is_open {
return doc, .Premature_EOF
}
break loop
case:
/*
This should be a tag's body text.
*/
body_text := scan_string(t, t.offset) or_return
needs_processing := .Unbox_CDATA in opts.flags
needs_processing |= .Decode_SGML_Entities in opts.flags
if !needs_processing {
doc.elements[element].value = body_text
continue
}
decode_opts := entity.XML_Decode_Options{}
if .Keep_Tag_Body_Comments not_in opts.flags {
decode_opts += { .Comment_Strip }
}
if .Decode_SGML_Entities not_in opts.flags {
decode_opts += { .No_Entity_Decode }
}
if .Unbox_CDATA in opts.flags {
decode_opts += { .Unbox_CDATA }
if .Decode_SGML_Entities in opts.flags {
decode_opts += { .Decode_CDATA }
}
}
decoded, decode_err := entity.decode_xml(body_text, decode_opts)
if decode_err == .None {
doc.elements[element].value = decoded
append(&doc.strings_to_free, decoded)
} else {
doc.elements[element].value = body_text
}
}
}
if .Must_Have_Prolog in opts.flags && len(doc.prolog) == 0 {
return doc, .No_Prolog
}
if .Must_Have_DocType in opts.flags && len(doc.doctype.ident) == 0 {
return doc, .No_DocType
}
resize(&doc.elements, int(doc.element_count))
return doc, .None
}
parse_from_file :: proc(filename: string, options := DEFAULT_Options, error_handler := default_error_handler, allocator := context.allocator) -> (doc: ^Document, err: Error) {
context.allocator = allocator
options := options
data, data_ok := os.read_entire_file(filename)
if !data_ok { return {}, .File_Error }
options.flags += { .Input_May_Be_Modified }
return parse_from_slice(data, options, filename, error_handler, allocator)
}
parse :: proc { parse_from_file, parse_from_slice }
destroy :: proc(doc: ^Document) {
if doc == nil { return }
for el in doc.elements {
delete(el.attribs)
delete(el.children)
}
delete(doc.elements)
delete(doc.prolog)
delete(doc.comments)
delete(doc.input)
for s in doc.strings_to_free {
delete(s)
}
delete(doc.strings_to_free)
free(doc)
}
/*
Helpers.
*/
validate_options :: proc(options: Options) -> (validated: Options, err: Error) {
validated = options
if .Error_on_Unsupported in validated.flags && .Ignore_Unsupported in validated.flags {
return options, .Conflicting_Options
}
return validated, .None
}
expect :: proc(t: ^Tokenizer, kind: Token_Kind) -> (tok: Token, err: Error) {
tok = scan(t)
if tok.kind == kind { return tok, .None }
error(t, t.offset, "Expected \"%v\", got \"%v\".", kind, tok.kind)
return tok, .Unexpected_Token
}
parse_attribute :: proc(doc: ^Document) -> (attr: Attr, offset: int, err: Error) {
assert(doc != nil)
context.allocator = doc.allocator
t := doc.tokenizer
key := expect(t, .Ident) or_return
offset = t.offset - len(key.text)
_ = expect(t, .Eq) or_return
value := expect(t, .String) or_return
attr.key = key.text
attr.val = value.text
err = .None
return
}
check_duplicate_attributes :: proc(t: ^Tokenizer, attribs: Attributes, attr: Attr, offset: int) -> (err: Error) {
for a in attribs {
if attr.key == a.key {
error(t, offset, "Duplicate attribute: %v\n", attr.key)
return .Duplicate_Attribute
}
}
return .None
}
parse_attributes :: proc(doc: ^Document, attribs: ^Attributes) -> (err: Error) {
assert(doc != nil)
context.allocator = doc.allocator
t := doc.tokenizer
for peek(t).kind == .Ident {
attr, offset := parse_attribute(doc) or_return
check_duplicate_attributes(t, attribs^, attr, offset) or_return
append(attribs, attr)
}
skip_whitespace(t)
return .None
}
parse_prolog :: proc(doc: ^Document) -> (err: Error) {
assert(doc != nil)
context.allocator = doc.allocator
t := doc.tokenizer
offset := t.offset
parse_attributes(doc, &doc.prolog) or_return
for attr in doc.prolog {
switch attr.key {
case "version":
switch attr.val {
case "1.0", "1.1":
case:
error(t, offset, "[parse_prolog] Warning: Unhandled XML version: %v\n", attr.val)
}
case "encoding":
switch strings.to_lower(attr.val, context.temp_allocator) {
case "utf-8", "utf8":
doc.encoding = .UTF_8
case "latin-1", "latin1", "iso-8859-1":
doc.encoding = .LATIN_1
case:
/*
Unrecognized encoding, assume UTF-8.
*/
error(t, offset, "[parse_prolog] Warning: Unrecognized encoding: %v\n", attr.val)
}
case:
// Ignored.
}
}
_ = expect(t, .Question) or_return
_ = expect(t, .Gt) or_return
return .None
}
skip_element :: proc(t: ^Tokenizer) -> (err: Error) {
close := 1
loop: for {
tok := scan(t)
#partial switch tok.kind {
case .EOF:
error(t, t.offset, "[skip_element] Premature EOF\n")
return .Premature_EOF
case .Lt:
close += 1
case .Gt:
close -= 1
if close == 0 {
break loop
}
case:
}
}
return .None
}
parse_doctype :: proc(doc: ^Document) -> (err: Error) {
/*
<!DOCTYPE greeting SYSTEM "hello.dtd">
<!DOCTYPE greeting [
<!ELEMENT greeting (#PCDATA)>
]>
*/
assert(doc != nil)
context.allocator = doc.allocator
t := doc.tokenizer
tok := expect(t, .Ident) or_return
doc.doctype.ident = tok.text
skip_whitespace(t)
offset := t.offset
skip_element(t) or_return
/*
-1 because the current offset is that of the closing tag, so the rest of the DOCTYPE tag ends just before it.
*/
doc.doctype.rest = string(t.src[offset : t.offset - 1])
return .None
}
Element_ID :: u32
new_element :: proc(doc: ^Document) -> (id: Element_ID) {
element_space := len(doc.elements)
// Need to resize
if int(doc.element_count) + 1 > element_space {
if element_space < 65536 {
element_space *= 2
} else {
element_space += 65536
}
resize(&doc.elements, element_space)
}
cur := doc.element_count
doc.element_count += 1
return cur
}
+14
View File
@@ -365,6 +365,20 @@ QOI_Info :: struct {
header: QOI_Header,
}
TGA_Header :: struct #packed {
id_length: u8,
color_map_type: u8,
data_type_code: u8,
color_map_origin: u16le,
color_map_length: u16le,
color_map_depth: u8,
origin: [2]u16le,
dimensions: [2]u16le,
bits_per_pixel: u8,
image_descriptor: u8,
}
#assert(size_of(TGA_Header) == 18)
// Function to help with image buffer calculations
compute_buffer_size :: proc(width, height, channels, depth: int, extra_row_bytes := int(0)) -> (size: int) {
size = ((((channels * width * depth) + 7) >> 3) + extra_row_bytes) * height
+103
View File
@@ -0,0 +1,103 @@
/*
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
Made available under Odin's BSD-3 license.
List of contributors:
Jeroen van Rijn: Initial implementation.
*/
// package tga implements a TGA image writer for 8-bit RGB and RGBA images.
package tga
import "core:mem"
import "core:image"
import "core:compress"
import "core:bytes"
import "core:os"
Error :: image.Error
General :: compress.General_Error
Image :: image.Image
Options :: image.Options
RGB_Pixel :: image.RGB_Pixel
RGBA_Pixel :: image.RGBA_Pixel
save_to_memory :: proc(output: ^bytes.Buffer, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) {
context.allocator = allocator
if img == nil {
return .Invalid_Input_Image
}
if output == nil {
return .Invalid_Output
}
pixels := img.width * img.height
if pixels == 0 || pixels > image.MAX_DIMENSIONS || img.width > 65535 || img.height > 65535 {
return .Invalid_Input_Image
}
// Our TGA writer supports only 8-bit images with 3 or 4 channels.
if img.depth != 8 || img.channels < 3 || img.channels > 4 {
return .Invalid_Input_Image
}
if img.channels * pixels != len(img.pixels.buf) {
return .Invalid_Input_Image
}
written := 0
// Calculate and allocate necessary space.
necessary := pixels * img.channels + size_of(image.TGA_Header)
if !resize(&output.buf, necessary) {
return General.Resize_Failed
}
header := image.TGA_Header{
data_type_code = 0x02, // Color, uncompressed.
dimensions = {u16le(img.width), u16le(img.height)},
bits_per_pixel = u8(img.depth * img.channels),
image_descriptor = 1 << 5, // Origin is top left.
}
header_bytes := transmute([size_of(image.TGA_Header)]u8)header
copy(output.buf[written:], header_bytes[:])
written += size_of(image.TGA_Header)
/*
Encode loop starts here.
*/
if img.channels == 3 {
pix := mem.slice_data_cast([]RGB_Pixel, img.pixels.buf[:])
out := mem.slice_data_cast([]RGB_Pixel, output.buf[written:])
for p, i in pix {
out[i] = p.bgr
}
} else if img.channels == 4 {
pix := mem.slice_data_cast([]RGBA_Pixel, img.pixels.buf[:])
out := mem.slice_data_cast([]RGBA_Pixel, output.buf[written:])
for p, i in pix {
out[i] = p.bgra
}
}
return nil
}
save_to_file :: proc(output: string, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) {
context.allocator = allocator
out := &bytes.Buffer{}
defer bytes.buffer_destroy(out)
save_to_memory(out, img, options) or_return
write_ok := os.write_entire_file(output, out.buf[:])
return nil if write_ok else General.Cannot_Open_File
}
save :: proc{save_to_memory, save_to_file}
+42 -13
View File
@@ -41,6 +41,10 @@ mem_copy_non_overlapping :: proc(dst, src: rawptr, len: int) ---
mem_zero :: proc(ptr: rawptr, len: int) ---
mem_zero_volatile :: proc(ptr: rawptr, len: int) ---
// prefer [^]T operations if possible
ptr_offset :: proc(ptr: ^$T, offset: int) -> ^T ---
ptr_sub :: proc(a, b: ^$T) -> int ---
unaligned_load :: proc(src: ^$T) -> T ---
unaligned_store :: proc(dst: ^$T, val: T) -> T ---
@@ -82,6 +86,7 @@ atomic_store_explicit :: proc(dst: ^$T, val: T, order: Atomic_Memory_Order) ---
atomic_load :: proc(dst: ^$T) -> T ---
atomic_load_explicit :: proc(dst: ^$T, order: Atomic_Memory_Order) -> T ---
// fetch then operator
atomic_add :: proc(dst; ^$T, val: T) -> T ---
atomic_add_explicit :: proc(dst; ^$T, val: T, order: Atomic_Memory_Order) -> T ---
atomic_sub :: proc(dst; ^$T, val: T) -> T ---
@@ -119,19 +124,20 @@ type_is_string :: proc($T: typeid) -> bool ---
type_is_typeid :: proc($T: typeid) -> bool ---
type_is_any :: proc($T: typeid) -> bool ---
type_is_endian_platform :: proc($T: typeid) -> bool ---
type_is_endian_little :: proc($T: typeid) -> bool ---
type_is_endian_big :: proc($T: typeid) -> bool ---
type_is_unsigned :: proc($T: typeid) -> bool ---
type_is_numeric :: proc($T: typeid) -> bool ---
type_is_ordered :: proc($T: typeid) -> bool ---
type_is_ordered_numeric :: proc($T: typeid) -> bool ---
type_is_indexable :: proc($T: typeid) -> bool ---
type_is_sliceable :: proc($T: typeid) -> bool ---
type_is_comparable :: proc($T: typeid) -> bool ---
type_is_simple_compare :: proc($T: typeid) -> bool --- // easily compared using memcmp (== and !=)
type_is_dereferenceable :: proc($T: typeid) -> bool ---
type_is_valid_map_key :: proc($T: typeid) -> bool ---
type_is_endian_platform :: proc($T: typeid) -> bool ---
type_is_endian_little :: proc($T: typeid) -> bool ---
type_is_endian_big :: proc($T: typeid) -> bool ---
type_is_unsigned :: proc($T: typeid) -> bool ---
type_is_numeric :: proc($T: typeid) -> bool ---
type_is_ordered :: proc($T: typeid) -> bool ---
type_is_ordered_numeric :: proc($T: typeid) -> bool ---
type_is_indexable :: proc($T: typeid) -> bool ---
type_is_sliceable :: proc($T: typeid) -> bool ---
type_is_comparable :: proc($T: typeid) -> bool ---
type_is_simple_compare :: proc($T: typeid) -> bool --- // easily compared using memcmp (== and !=)
type_is_dereferenceable :: proc($T: typeid) -> bool ---
type_is_valid_map_key :: proc($T: typeid) -> bool ---
type_is_valid_matrix_elements :: proc($T: typeid) -> bool ---
type_is_named :: proc($T: typeid) -> bool ---
type_is_pointer :: proc($T: typeid) -> bool ---
@@ -146,6 +152,7 @@ type_is_enum :: proc($T: typeid) -> bool ---
type_is_proc :: proc($T: typeid) -> bool ---
type_is_bit_set :: proc($T: typeid) -> bool ---
type_is_simd_vector :: proc($T: typeid) -> bool ---
type_is_matrix :: proc($T: typeid) -> bool ---
type_has_nil :: proc($T: typeid) -> bool ---
@@ -153,6 +160,7 @@ type_is_specialization_of :: proc($T, $S: typeid) -> bool ---
type_is_variant_of :: proc($U, $V: typeid) -> bool where type_is_union(U) ---
type_has_field :: proc($T: typeid, $name: string) -> bool ---
type_field_type :: proc($T: typeid, $name: string) -> typeid ---
type_proc_parameter_count :: proc($T: typeid) -> int where type_is_proc(T) ---
type_proc_return_count :: proc($T: typeid) -> int where type_is_proc(T) ---
@@ -160,20 +168,41 @@ type_proc_return_count :: proc($T: typeid) -> int where type_is_proc(T) ---
type_proc_parameter_type :: proc($T: typeid, index: int) -> typeid where type_is_proc(T) ---
type_proc_return_type :: proc($T: typeid, index: int) -> typeid where type_is_proc(T) ---
type_struct_field_count :: proc($T: typeid) -> int where type_is_struct(T) ---
type_polymorphic_record_parameter_count :: proc($T: typeid) -> typeid ---
type_polymorphic_record_parameter_value :: proc($T: typeid, index: int) -> $V ---
type_is_specialized_polymorphic_record :: proc($T: typeid) -> bool ---
type_is_unspecialized_polymorphic_record :: proc($T: typeid) -> bool ---
type_is_subtype_of :: proc($T, $U: typeid) -> bool ---
type_field_index_of :: proc($T: typeid, $name: string) -> uintptr ---
type_equal_proc :: proc($T: typeid) -> (equal: proc "contextless" (rawptr, rawptr) -> bool) where type_is_comparable(T) ---
type_hasher_proc :: proc($T: typeid) -> (hasher: proc "contextless" (data: rawptr, seed: uintptr) -> uintptr) where type_is_comparable(T) ---
constant_utf16_cstring :: proc($literal: string) -> [^]u16 ---
// WASM targets only
wasm_memory_grow :: proc(index, delta: uintptr) -> int ---
wasm_memory_size :: proc(index: uintptr) -> int ---
// Darwin targets only
objc_object :: struct{}
objc_selector :: struct{}
objc_class :: struct{}
objc_id :: ^objc_object
objc_SEL :: ^objc_selector
objc_Class :: ^objc_class
objc_find_selector :: proc($name: string) -> objc_SEL ---
objc_register_selector :: proc($name: string) -> objc_SEL ---
objc_find_class :: proc($name: string) -> objc_Class ---
objc_register_class :: proc($name: string) -> objc_Class ---
// Internal compiler use only
__entry_point :: proc() ---
+3 -3
View File
@@ -479,21 +479,21 @@ angle_from_quaternion_f16 :: proc(q: Quaternionf16) -> f16 {
return math.asin(q.x*q.x + q.y*q.y + q.z*q.z) * 2
}
return math.cos(q.x) * 2
return math.acos(q.w) * 2
}
angle_from_quaternion_f32 :: proc(q: Quaternionf32) -> f32 {
if abs(q.w) > math.SQRT_THREE*0.5 {
return math.asin(q.x*q.x + q.y*q.y + q.z*q.z) * 2
}
return math.cos(q.x) * 2
return math.acos(q.w) * 2
}
angle_from_quaternion_f64 :: proc(q: Quaternionf64) -> f64 {
if abs(q.w) > math.SQRT_THREE*0.5 {
return math.asin(q.x*q.x + q.y*q.y + q.z*q.z) * 2
}
return math.cos(q.x) * 2
return math.acos(q.w) * 2
}
angle_from_quaternion :: proc{
angle_from_quaternion_f16,
+1 -18
View File
@@ -6,24 +6,7 @@ import "core:runtime"
nil_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
size, alignment: int,
old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, Allocator_Error) {
switch mode {
case .Alloc:
return nil, .Out_Of_Memory
case .Free:
return nil, .None
case .Free_All:
return nil, .Mode_Not_Implemented
case .Resize:
if size == 0 {
return nil, .None
}
return nil, .Out_Of_Memory
case .Query_Features:
return nil, .Mode_Not_Implemented
case .Query_Info:
return nil, .Mode_Not_Implemented
}
return nil, .None
return nil, nil
}
nil_allocator :: proc() -> Allocator {
+1 -1
View File
@@ -120,7 +120,7 @@ alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: int)
do_commit_if_necessary :: proc(block: ^Memory_Block, size: uint) -> (err: Allocator_Error) {
if block.committed - block.used < size {
pmblock := (^Platform_Memory_Block)(block)
base_offset := uint(uintptr(block) - uintptr(pmblock))
base_offset := uint(uintptr(pmblock.block.base) - uintptr(pmblock))
platform_total_commit := base_offset + block.used + size
assert(pmblock.committed <= pmblock.reserved)
+124 -9
View File
@@ -4,6 +4,8 @@ package filepath
import "core:strings"
SEPARATOR_CHARS :: `/\`
// is_separator checks whether the byte is a valid separator character
is_separator :: proc(c: byte) -> bool {
switch c {
@@ -69,6 +71,16 @@ volume_name_len :: proc(path: string) -> int {
return 0
}
/*
Gets the file name and extension from a path.
i.e:
'path/to/name.tar.gz' -> 'name.tar.gz'
'path/to/name.txt' -> 'name.txt'
'path/to/name' -> 'name'
Returns "." if the path is an empty string.
*/
base :: proc(path: string) -> string {
if path == "" {
return "."
@@ -94,6 +106,118 @@ base :: proc(path: string) -> string {
return path
}
/*
Gets the name of a file from a path.
The stem of a file is such that stem(path) + ext(path) = base(path).
Only the last dot is considered when splitting the file extension.
See `short_stem`.
i.e:
'name.tar.gz' -> 'name.tar'
'name.txt' -> 'name'
Returns an empty string if there is no stem. e.g: '.gitignore'.
Returns an empty string if there's a trailing path separator.
*/
stem :: proc(path: string) -> string {
if len(path) > 0 && is_separator(path[len(path) - 1]) {
// NOTE(tetra): Trailing separator
return ""
}
// NOTE(tetra): Get the basename
path := path
if i := strings.last_index_any(path, SEPARATOR_CHARS); i != -1 {
path = path[i+1:]
}
if i := strings.last_index_byte(path, '.'); i != -1 {
return path[:i]
}
return path
}
/*
Gets the name of a file from a path.
The short stem is such that short_stem(path) + long_ext(path) = base(path).
The first dot is used to split off the file extension, unlike `stem` which uses the last dot.
i.e:
'name.tar.gz' -> 'name'
'name.txt' -> 'name'
Returns an empty string if there is no stem. e.g: '.gitignore'.
Returns an empty string if there's a trailing path separator.
*/
short_stem :: proc(path: string) -> string {
s := stem(path)
if i := strings.index_byte(s, '.'); i != -1 {
return s[:i]
}
return s
}
/*
Gets the file extension from a path, including the dot.
The file extension is such that stem(path) + ext(path) = base(path).
Only the last dot is considered when splitting the file extension.
See `long_ext`.
i.e:
'name.tar.gz' -> '.gz'
'name.txt' -> '.txt'
Returns an empty string if there is no dot.
Returns an empty string if there is a trailing path separator.
*/
ext :: proc(path: string) -> string {
for i := len(path)-1; i >= 0 && !is_separator(path[i]); i -= 1 {
if path[i] == '.' {
return path[i:]
}
}
return ""
}
/*
Gets the file extension from a path, including the dot.
The long file extension is such that short_stem(path) + long_ext(path) = base(path).
The first dot is used to split off the file extension, unlike `ext` which uses the last dot.
i.e:
'name.tar.gz' -> '.tar.gz'
'name.txt' -> '.txt'
Returns an empty string if there is no dot.
Returns an empty string if there is a trailing path separator.
*/
long_ext :: proc(path: string) -> string {
if len(path) > 0 && is_separator(path[len(path) - 1]) {
// NOTE(tetra): Trailing separator
return ""
}
// NOTE(tetra): Get the basename
path := path
if i := strings.last_index_any(path, SEPARATOR_CHARS); i != -1 {
path = path[i+1:]
}
if i := strings.index_byte(path, '.'); i != -1 {
return path[i:]
}
return ""
}
clean :: proc(path: string, allocator := context.allocator) -> string {
context.allocator = allocator
@@ -189,15 +313,6 @@ to_slash :: proc(path: string, allocator := context.allocator) -> (new_path: str
return strings.replace_all(path, SEPARATOR_STRING, "/", allocator)
}
ext :: proc(path: string) -> string {
for i := len(path)-1; i >= 0 && !is_separator(path[i]); i -= 1 {
if path[i] == '.' {
return path[i:]
}
}
return ""
}
Relative_Error :: enum {
None,
+10
View File
@@ -5,6 +5,16 @@ import "core:intrinsics"
@builtin
Maybe :: union($T: typeid) #maybe {T}
@builtin
container_of :: #force_inline proc "contextless" (ptr: $P/^$Field_Type, $T: typeid, $field_name: string) -> ^T
where intrinsics.type_has_field(T, field_name),
intrinsics.type_field_type(T, field_name) == Field_Type {
offset :: offset_of_by_string(T, field_name)
return (^T)(uintptr(ptr) - offset) if ptr != nil else nil
}
@thread_local global_default_temp_allocator_data: Default_Temp_Allocator
@builtin
+66 -4
View File
@@ -15,7 +15,7 @@ clone :: proc(s: string, allocator := context.allocator, loc := #caller_location
}
// returns a clone of the string `s` allocated using the `allocator` as a cstring
// a nul byte is appended to the clone, to make the cstring safe
// a nul byte is appended to the clone, to make the cstring safe
clone_to_cstring :: proc(s: string, allocator := context.allocator, loc := #caller_location) -> cstring {
c := make([]byte, len(s)+1, allocator, loc)
copy(c, s)
@@ -37,7 +37,7 @@ string_from_nul_terminated_ptr :: proc(ptr: ^byte, len: int) -> string {
return s
}
// returns the raw ^byte start of the string `str`
// returns the raw ^byte start of the string `str`
ptr_from_string :: proc(str: string) -> ^byte {
d := transmute(mem.Raw_String)str
return d.data
@@ -969,7 +969,7 @@ count :: proc(s, substr: string) -> int {
repeats the string `s` multiple `count` times and returns the allocated string
panics when `count` is below 0
strings.repeat("abc", 2) -> "abcabc"
strings.repeat("abc", 2) -> "abcabc"
*/
repeat :: proc(s: string, count: int, allocator := context.allocator) -> string {
if count < 0 {
@@ -1378,7 +1378,7 @@ split_multi :: proc(s: string, substrs: []string, allocator := context.allocator
// skip when no results
if substrings_found < 1 {
return
return
}
buf = make([]string, substrings_found + 1, allocator)
@@ -1809,3 +1809,65 @@ fields_iterator :: proc(s: ^string) -> (field: string, ok: bool) {
s^ = s[len(s):]
return
}
// `levenshtein_distance` returns the Levenshtein edit distance between 2 strings.
// This is a single-row-version of the WagnerFischer algorithm, based on C code by Martin Ettl.
// Note: allocator isn't used if the length of string b in runes is smaller than 64.
levenshtein_distance :: proc(a, b: string, allocator := context.allocator) -> int {
LEVENSHTEIN_DEFAULT_COSTS: []int : {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
60, 61, 62, 63,
}
m, n := utf8.rune_count_in_string(a), utf8.rune_count_in_string(b)
if m == 0 {
return n
}
if n == 0 {
return m
}
costs: []int
if n + 1 > len(LEVENSHTEIN_DEFAULT_COSTS) {
costs = make([]int, n + 1, allocator)
for k in 0..=n {
costs[k] = k
}
} else {
costs = LEVENSHTEIN_DEFAULT_COSTS
}
defer if n + 1 > len(LEVENSHTEIN_DEFAULT_COSTS) {
delete(costs, allocator)
}
i: int
for c1 in a {
costs[0] = i + 1
corner := i
j: int
for c2 in b {
upper := costs[j + 1]
if c1 == c2 {
costs[j + 1] = corner
} else {
t := upper if upper < corner else corner
costs[j + 1] = (costs[j] if costs[j] < t else t) + 1
}
corner = upper
j += 1
}
i += 1
}
return costs[n]
}
+30 -35
View File
@@ -400,30 +400,28 @@ atomic_cond_broadcast :: proc(c: ^Atomic_Cond) {
//
// An Atomic_Sema must not be copied after first use
Atomic_Sema :: struct {
mutex: Atomic_Mutex,
cond: Atomic_Cond,
count: int,
count: Futex,
}
atomic_sema_post :: proc(s: ^Atomic_Sema, count := 1) {
atomic_mutex_lock(&s.mutex)
defer atomic_mutex_unlock(&s.mutex)
s.count += count
atomic_cond_signal(&s.cond)
atomic_add_explicit(&s.count, Futex(count), .Release)
if count == 1 {
futex_signal(&s.count)
} else {
futex_broadcast(&s.count)
}
}
atomic_sema_wait :: proc(s: ^Atomic_Sema) {
atomic_mutex_lock(&s.mutex)
defer atomic_mutex_unlock(&s.mutex)
for s.count == 0 {
atomic_cond_wait(&s.cond, &s.mutex)
}
s.count -= 1
if s.count > 0 {
atomic_cond_signal(&s.cond)
for {
original_count := atomic_load_explicit(&s.count, .Relaxed)
for original_count == 0 {
futex_wait(&s.count, u32(original_count))
original_count = s.count
}
if original_count == atomic_compare_exchange_strong_explicit(&s.count, original_count, original_count-1, .Acquire, .Acquire) {
return
}
}
}
@@ -431,25 +429,22 @@ atomic_sema_wait_with_timeout :: proc(s: ^Atomic_Sema, duration: time.Duration)
if duration <= 0 {
return false
}
atomic_mutex_lock(&s.mutex)
defer atomic_mutex_unlock(&s.mutex)
start := time.tick_now()
for {
for s.count == 0 {
remaining := duration - time.tick_since(start)
if remaining < 0 {
return false
original_count := atomic_load_explicit(&s.count, .Relaxed)
for start := time.tick_now(); original_count == 0; /**/ {
remaining := duration - time.tick_since(start)
if remaining < 0 {
return false
}
if !futex_wait_with_timeout(&s.count, u32(original_count), remaining) {
return false
}
original_count = s.count
}
if !atomic_cond_wait_with_timeout(&s.cond, &s.mutex, remaining) {
return false
if original_count == atomic_compare_exchange_strong_explicit(&s.count, original_count, original_count-1, .Acquire, .Acquire) {
return true
}
}
s.count -= 1
if s.count > 0 {
atomic_cond_signal(&s.cond)
}
return true
}
+4 -38
View File
@@ -6,53 +6,19 @@ import "core:time"
when #config(ODIN_SYNC_SEMA_USE_FUTEX, true) {
_Sema :: struct {
count: Futex,
atomic: Atomic_Sema,
}
_sema_post :: proc(s: ^Sema, count := 1) {
atomic_add_explicit(&s.impl.count, Futex(count), .Release)
if count == 1 {
futex_signal(&s.impl.count)
} else {
futex_broadcast(&s.impl.count)
}
atomic_sema_post(&s.impl.atomic, count)
}
_sema_wait :: proc(s: ^Sema) {
for {
original_count := atomic_load_explicit(&s.impl.count, .Relaxed)
for original_count == 0 {
futex_wait(&s.impl.count, u32(original_count))
original_count = s.impl.count
}
if original_count == atomic_compare_exchange_strong_explicit(&s.impl.count, original_count, original_count-1, .Acquire, .Acquire) {
return
}
}
atomic_sema_wait(&s.impl.atomic)
}
_sema_wait_with_timeout :: proc(s: ^Sema, duration: time.Duration) -> bool {
if duration <= 0 {
return false
}
for {
original_count := atomic_load_explicit(&s.impl.count, .Relaxed)
for start := time.tick_now(); original_count == 0; /**/ {
remaining := duration - time.tick_since(start)
if remaining < 0 {
return false
}
if !futex_wait_with_timeout(&s.impl.count, u32(original_count), remaining) {
return false
}
original_count = s.impl.count
}
if original_count == atomic_compare_exchange_strong_explicit(&s.impl.count, original_count, original_count-1, .Acquire, .Acquire) {
return true
}
}
return atomic_sema_wait_with_timeout(&s.impl.atomic, duration)
}
} else {
_Sema :: struct {
+7
View File
@@ -62,6 +62,13 @@ foreign kernel32 {
GetCurrentProcessId :: proc() -> DWORD ---
GetCurrentThread :: proc() -> HANDLE ---
GetCurrentThreadId :: proc() -> DWORD ---
GetProcessTimes :: proc(
hProcess: HANDLE,
lpCreationTime: LPFILETIME,
lpExitTime: LPFILETIME,
lpKernelTime: LPFILETIME,
lpUserTime: LPFILETIME,
) -> BOOL ---
GetStdHandle :: proc(which: DWORD) -> HANDLE ---
ExitProcess :: proc(uExitCode: c_uint) -> ! ---
DeviceIoControl :: proc(
+6
View File
@@ -60,6 +60,12 @@ foreign user32 {
DestroyWindow :: proc(hWnd: HWND) -> BOOL ---
ShowWindow :: proc(hWnd: HWND, nCmdShow: c_int) -> BOOL ---
BringWindowToTop :: proc(hWnd: HWND) -> BOOL ---
GetTopWindow :: proc(hWnd: HWND) -> HWND ---
SetForegroundWindow :: proc(hWnd: HWND) -> BOOL ---
GetForegroundWindow :: proc() -> HWND ---
SetActiveWindow :: proc(hWnd: HWND) -> HWND ---
GetActiveWindow :: proc() -> HWND ---
GetMessageA :: proc(lpMsg: ^MSG, hWnd: HWND, wMsgFilterMin: UINT, wMsgFilterMax: UINT) -> BOOL ---
GetMessageW :: proc(lpMsg: ^MSG, hWnd: HWND, wMsgFilterMin: UINT, wMsgFilterMax: UINT) -> BOOL ---
+111
View File
@@ -0,0 +1,111 @@
//+ignore
package i18n
/*
The i18n package is flexible and easy to use.
It has one call to get a translation: `get`, which the user can alias into something like `T`.
`get`, referred to as `T` here, has a few different signatures.
All of them will return the key if the entry can't be found in the active translation catalog.
- `T(key)` returns the translation of `key`.
- `T(key, n)` returns a pluralized translation of `key` according to value `n`.
- `T(section, key)` returns the translation of `key` in `section`.
- `T(section, key, n)` returns a pluralized translation of `key` in `section` according to value `n`.
By default lookup take place in the global `i18n.ACTIVE` catalog for ease of use.
If you want to override which translation to use, for example in a language preview dialog, you can use the following:
- `T(key, n, catalog)` returns the pluralized version of `key` from explictly supplied catalog.
- `T(section, key, n, catalog)` returns the pluralized version of `key` in `section` from explictly supplied catalog.
If a catalog has translation contexts or sections, then ommitting it in the above calls looks up in section "".
The default pluralization rule is n != 1, which is to say that passing n == 1 (or not passing n) returns the singular form.
Passing n != 1 returns plural form 1.
Should a language not conform to this rule, you can pass a pluralizer procedure to the catalog parser.
This is a procedure that maps an integer to an integer, taking a value and returning which plural slot should be used.
You can also assign it to a loaded catalog after parsing, of course.
Some code examples follow.
*/
/*
```cpp
import "core:fmt"
import "core:text/i18n"
T :: i18n.get
mo :: proc() {
using fmt
err: i18n.Error
/*
Parse MO file and set it as the active translation so we can omit `get`'s "catalog" parameter.
*/
i18n.ACTIVE, err = i18n.parse_mo(#load("translations/nl_NL.mo"))
defer i18n.destroy()
if err != .None { return }
/*
These are in the .MO catalog.
*/
println("-----")
println(T(""))
println("-----")
println(T("There are 69,105 leaves here."))
println("-----")
println(T("Hellope, World!"))
println("-----")
// We pass 1 into `T` to get the singular format string, then 1 again into printf.
printf(T("There is %d leaf.\n", 1), 1)
// We pass 42 into `T` to get the plural format string, then 42 again into printf.
printf(T("There is %d leaf.\n", 42), 42)
/*
This isn't in the translation catalog, so the key is passed back untranslated.
*/
println("-----")
println(T("Come visit us on Discord!"))
}
qt :: proc() {
using fmt
err: i18n.Error
/*
Parse QT file and set it as the active translation so we can omit `get`'s "catalog" parameter.
*/
i18n.ACTIVE, err = i18n.parse_qt(#load("translations/nl_NL-qt-ts.ts"))
defer i18n.destroy()
if err != .None {
return
}
/*
These are in the .TS catalog. As you can see they have sections.
*/
println("--- Page section ---")
println("Page:Text for translation =", T("Page", "Text for translation"))
println("-----")
println("Page:Also text to translate =", T("Page", "Also text to translate"))
println("-----")
println("--- installscript section ---")
println("installscript:99 bottles of beer on the wall =", T("installscript", "99 bottles of beer on the wall"))
println("-----")
println("--- apple_count section ---")
println("apple_count:%d apple(s) =")
println("\t 1 =", T("apple_count", "%d apple(s)", 1))
println("\t 42 =", T("apple_count", "%d apple(s)", 42))
}
```
*/
+167
View File
@@ -0,0 +1,167 @@
package i18n
/*
A parser for GNU GetText .MO files.
Copyright 2021-2022 Jeroen van Rijn <nom@duclavier.com>.
Made available under Odin's BSD-3 license.
A from-scratch implementation based after the specification found here:
https://www.gnu.org/software/gettext/manual/html_node/MO-Files.html
Options are ignored as they're not applicable to this format.
They're part of the signature for consistency with other catalog formats.
List of contributors:
Jeroen van Rijn: Initial implementation.
*/
import "core:os"
import "core:strings"
import "core:bytes"
parse_mo_from_slice :: proc(data: []u8, options := DEFAULT_PARSE_OPTIONS, pluralizer: proc(int) -> int = nil, allocator := context.allocator) -> (translation: ^Translation, err: Error) {
context.allocator = allocator
/*
An MO file should have at least a 4-byte magic, 2 x 2 byte version info,
a 4-byte number of strings value, and 2 x 4-byte offsets.
*/
if len(data) < 20 {
return {}, .MO_File_Invalid
}
/*
Check magic. Should be 0x950412de in native Endianness.
*/
native := true
magic := read_u32(data, native) or_return
if magic != 0x950412de {
native = false
magic = read_u32(data, native) or_return
if magic != 0x950412de { return {}, .MO_File_Invalid_Signature }
}
/*
We can ignore version_minor at offset 6.
*/
version_major := read_u16(data[4:]) or_return
if version_major > 1 { return {}, .MO_File_Unsupported_Version }
count := read_u32(data[ 8:]) or_return
original_offset := read_u32(data[12:]) or_return
translated_offset := read_u32(data[16:]) or_return
if count == 0 { return {}, .Empty_Translation_Catalog }
/*
Initalize Translation, interner and optional pluralizer.
*/
translation = new(Translation)
translation.pluralize = pluralizer
strings.intern_init(&translation.intern, allocator, allocator)
// Gettext MO files only have one section.
translation.k_v[""] = {}
section := &translation.k_v[""]
for n := u32(0); n < count; n += 1 {
/*
Grab string's original length and offset.
*/
offset := original_offset + 8 * n
if len(data) < int(offset + 8) { return translation, .MO_File_Invalid }
o_length := read_u32(data[offset :], native) or_return
o_offset := read_u32(data[offset + 4:], native) or_return
offset = translated_offset + 8 * n
if len(data) < int(offset + 8) { return translation, .MO_File_Invalid }
t_length := read_u32(data[offset :], native) or_return
t_offset := read_u32(data[offset + 4:], native) or_return
max_offset := int(max(o_offset + o_length + 1, t_offset + t_length + 1))
if len(data) < max_offset { return translation, .Premature_EOF }
key := data[o_offset:][:o_length]
val := data[t_offset:][:t_length]
/*
Could be a pluralized string.
*/
zero := []byte{0}
keys := bytes.split(key, zero)
vals := bytes.split(val, zero)
if len(keys) != len(vals) || max(len(keys), len(vals)) > MAX_PLURALS {
return translation, .MO_File_Incorrect_Plural_Count
}
for k in keys {
interned_key := strings.intern_get(&translation.intern, string(k))
interned_vals := make([]string, len(keys))
last_val: string
i := 0
for v in vals {
interned_vals[i] = strings.intern_get(&translation.intern, string(v))
last_val = interned_vals[i]
i += 1
}
section[interned_key] = interned_vals
}
delete(vals)
delete(keys)
}
return
}
parse_mo_file :: proc(filename: string, options := DEFAULT_PARSE_OPTIONS, pluralizer: proc(int) -> int = nil, allocator := context.allocator) -> (translation: ^Translation, err: Error) {
context.allocator = allocator
data, data_ok := os.read_entire_file(filename)
defer delete(data)
if !data_ok { return {}, .File_Error }
return parse_mo_from_slice(data, options, pluralizer, allocator)
}
parse_mo :: proc { parse_mo_file, parse_mo_from_slice }
/*
Helpers.
*/
read_u32 :: proc(data: []u8, native_endian := true) -> (res: u32, err: Error) {
if len(data) < size_of(u32) { return 0, .Premature_EOF }
val := (^u32)(raw_data(data))^
if native_endian {
return val, .None
} else {
when ODIN_ENDIAN == .Little {
return u32(transmute(u32be)val), .None
} else {
return u32(transmute(u32le)val), .None
}
}
}
read_u16 :: proc(data: []u8, native_endian := true) -> (res: u16, err: Error) {
if len(data) < size_of(u16) { return 0, .Premature_EOF }
val := (^u16)(raw_data(data))^
if native_endian {
return val, .None
} else {
when ODIN_ENDIAN == .Little {
return u16(transmute(u16be)val), .None
} else {
return u16(transmute(u16le)val), .None
}
}
}
+182
View File
@@ -0,0 +1,182 @@
package i18n
/*
Internationalization helpers.
Copyright 2021-2022 Jeroen van Rijn <nom@duclavier.com>.
Made available under Odin's BSD-3 license.
List of contributors:
Jeroen van Rijn: Initial implementation.
*/
import "core:strings"
/*
TODO:
- Support for more translation catalog file formats.
*/
/*
Currently active catalog.
*/
ACTIVE: ^Translation
// Allow between 1 and 255 plural forms. Default: 10.
MAX_PLURALS :: min(max(#config(ODIN_i18N_MAX_PLURAL_FORMS, 10), 1), 255)
/*
The main data structure. This can be generated from various different file formats, as long as we have a parser for them.
*/
Section :: map[string][]string
Translation :: struct {
k_v: map[string]Section, // k_v[section][key][plural_form] = ...
intern: strings.Intern,
pluralize: proc(number: int) -> int,
}
Error :: enum {
/*
General return values.
*/
None = 0,
Empty_Translation_Catalog,
Duplicate_Key,
/*
Couldn't find, open or read file.
*/
File_Error,
/*
File too short.
*/
Premature_EOF,
/*
GNU Gettext *.MO file errors.
*/
MO_File_Invalid_Signature,
MO_File_Unsupported_Version,
MO_File_Invalid,
MO_File_Incorrect_Plural_Count,
/*
Qt Linguist *.TS file errors.
*/
TS_File_Parse_Error,
TS_File_Expected_Context,
TS_File_Expected_Context_Name,
TS_File_Expected_Source,
TS_File_Expected_Translation,
TS_File_Expected_NumerusForm,
}
Parse_Options :: struct {
merge_sections: bool,
}
DEFAULT_PARSE_OPTIONS :: Parse_Options{
merge_sections = false,
}
/*
Several ways to use:
- get(key), which defaults to the singular form and i18n.ACTIVE catalog, or
- get(key, number), which returns the appropriate plural from the active catalog, or
- get(key, number, catalog) to grab text from a specific one.
*/
get_single_section :: proc(key: string, number := 0, catalog: ^Translation = ACTIVE) -> (value: string) {
/*
A lot of languages use singular for 1 item and plural for 0 or more than 1 items. This is our default pluralize rule.
*/
plural := 1 if number != 1 else 0
if catalog.pluralize != nil {
plural = catalog.pluralize(number)
}
return get_by_slot(key, plural, catalog)
}
/*
Several ways to use:
- get(section, key), which defaults to the singular form and i18n.ACTIVE catalog, or
- get(section, key, number), which returns the appropriate plural from the active catalog, or
- get(section, key, number, catalog) to grab text from a specific one.
*/
get_by_section :: proc(section, key: string, number := 0, catalog: ^Translation = ACTIVE) -> (value: string) {
/*
A lot of languages use singular for 1 item and plural for 0 or more than 1 items. This is our default pluralize rule.
*/
plural := 1 if number != 1 else 0
if catalog.pluralize != nil {
plural = catalog.pluralize(number)
}
return get_by_slot(section, key, plural, catalog)
}
get :: proc{get_single_section, get_by_section}
/*
Several ways to use:
- get_by_slot(key), which defaults to the singular form and i18n.ACTIVE catalog, or
- get_by_slot(key, slot), which returns the requested plural from the active catalog, or
- get_by_slot(key, slot, catalog) to grab text from a specific one.
If a file format parser doesn't (yet) support plural slots, each of the slots will point at the same string.
*/
get_by_slot_single_section :: proc(key: string, slot := 0, catalog: ^Translation = ACTIVE) -> (value: string) {
return get_by_slot_by_section("", key, slot, catalog)
}
/*
Several ways to use:
- get_by_slot(key), which defaults to the singular form and i18n.ACTIVE catalog, or
- get_by_slot(key, slot), which returns the requested plural from the active catalog, or
- get_by_slot(key, slot, catalog) to grab text from a specific one.
If a file format parser doesn't (yet) support plural slots, each of the slots will point at the same string.
*/
get_by_slot_by_section :: proc(section, key: string, slot := 0, catalog: ^Translation = ACTIVE) -> (value: string) {
if catalog == nil || section not_in catalog.k_v {
/*
Return the key if the catalog catalog hasn't been initialized yet, or the section is not present.
*/
return key
}
/*
Return the translation from the requested slot if this key is known, else return the key.
*/
if translations, ok := catalog.k_v[section][key]; ok {
plural := min(max(0, slot), len(catalog.k_v[section][key]) - 1)
return translations[plural]
}
return key
}
get_by_slot :: proc{get_by_slot_single_section, get_by_slot_by_section}
/*
Same for destroy:
- destroy(), to clean up the currently active catalog catalog i18n.ACTIVE
- destroy(catalog), to clean up a specific catalog.
*/
destroy :: proc(catalog: ^Translation = ACTIVE, allocator := context.allocator) {
context.allocator = allocator
if catalog == nil {
return
}
for section in &catalog.k_v {
for key in &catalog.k_v[section] {
delete(catalog.k_v[section][key])
}
delete(catalog.k_v[section])
}
delete(catalog.k_v)
strings.intern_destroy(&catalog.intern)
free(catalog)
}
+156
View File
@@ -0,0 +1,156 @@
package i18n
/*
A parser for Qt Linguist TS files.
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
Made available under Odin's BSD-3 license.
A from-scratch implementation based after the specification found here:
https://doc.qt.io/qt-5/linguist-ts-file-format.html
List of contributors:
Jeroen van Rijn: Initial implementation.
*/
import "core:os"
import "core:encoding/xml"
import "core:strings"
TS_XML_Options := xml.Options{
flags = {
.Input_May_Be_Modified,
.Must_Have_Prolog,
.Must_Have_DocType,
.Ignore_Unsupported,
.Unbox_CDATA,
.Decode_SGML_Entities,
},
expected_doctype = "TS",
}
parse_qt_linguist_from_slice :: proc(data: []u8, options := DEFAULT_PARSE_OPTIONS, pluralizer: proc(int) -> int = nil, allocator := context.allocator) -> (translation: ^Translation, err: Error) {
context.allocator = allocator
ts, xml_err := xml.parse(data, TS_XML_Options)
defer xml.destroy(ts)
if xml_err != .None || ts.element_count < 1 || ts.elements[0].ident != "TS" || len(ts.elements[0].children) == 0 {
return nil, .TS_File_Parse_Error
}
/*
Initalize Translation, interner and optional pluralizer.
*/
translation = new(Translation)
translation.pluralize = pluralizer
strings.intern_init(&translation.intern, allocator, allocator)
section: ^Section
for child_id in ts.elements[0].children {
// These should be <context>s.
child := ts.elements[child_id]
if child.ident != "context" {
return translation, .TS_File_Expected_Context
}
// Find section name.
section_name_id, section_name_found := xml.find_child_by_ident(ts, child_id, "name")
if !section_name_found {
return translation, .TS_File_Expected_Context_Name,
}
section_name := strings.intern_get(&translation.intern, "")
if !options.merge_sections {
section_name = strings.intern_get(&translation.intern, ts.elements[section_name_id].value)
}
if section_name not_in translation.k_v {
translation.k_v[section_name] = {}
}
section = &translation.k_v[section_name]
// Find messages in section.
nth: int
for {
message_id, message_found := xml.find_child_by_ident(ts, child_id, "message", nth)
if !message_found {
break
}
numerus_tag, _ := xml.find_attribute_val_by_key(ts, message_id, "numerus")
has_plurals := numerus_tag == "yes"
// We must have a <source> = key
source_id, source_found := xml.find_child_by_ident(ts, message_id, "source")
if !source_found {
return translation, .TS_File_Expected_Source
}
// We must have a <translation>
translation_id, translation_found := xml.find_child_by_ident(ts, message_id, "translation")
if !translation_found {
return translation, .TS_File_Expected_Translation
}
source := strings.intern_get(&translation.intern, ts.elements[source_id].value)
xlat := strings.intern_get(&translation.intern, ts.elements[translation_id].value)
if source in section {
return translation, .Duplicate_Key
}
if has_plurals {
if xlat != "" {
return translation, .TS_File_Expected_NumerusForm
}
num_plurals: int
for {
numerus_id, numerus_found := xml.find_child_by_ident(ts, translation_id, "numerusform", num_plurals)
if !numerus_found {
break
}
num_plurals += 1
}
if num_plurals < 2 {
return translation, .TS_File_Expected_NumerusForm
}
section[source] = make([]string, num_plurals)
num_plurals = 0
for {
numerus_id, numerus_found := xml.find_child_by_ident(ts, translation_id, "numerusform", num_plurals)
if !numerus_found {
break
}
numerus := strings.intern_get(&translation.intern, ts.elements[numerus_id].value)
section[source][num_plurals] = numerus
num_plurals += 1
}
} else {
// Single translation
section[source] = make([]string, 1)
section[source][0] = xlat
}
nth += 1
}
}
return
}
parse_qt_linguist_file :: proc(filename: string, options := DEFAULT_PARSE_OPTIONS, pluralizer: proc(int) -> int = nil, allocator := context.allocator) -> (translation: ^Translation, err: Error) {
context.allocator = allocator
data, data_ok := os.read_entire_file(filename)
defer delete(data)
if !data_ok { return {}, .File_Error }
return parse_qt_linguist_from_slice(data, options, pluralizer, allocator)
}
parse_qt :: proc { parse_qt_linguist_file, parse_qt_linguist_from_slice }
+4 -5
View File
@@ -44,10 +44,10 @@ Pool :: struct {
}
// Once initialized, the pool's memory address is not allowed to change until
// it is destroyed. If thread_count < 1, thread count 1 will be used.
// it is destroyed.
//
// The thread pool requires an allocator which it either owns, or which is thread safe.
pool_init :: proc(pool: ^Pool, thread_count: int, allocator: mem.Allocator) {
pool_init :: proc(pool: ^Pool, allocator: mem.Allocator, thread_count: int) {
context.allocator = allocator
pool.allocator = allocator
pool.tasks = make([dynamic]Task)
@@ -113,9 +113,8 @@ pool_join :: proc(pool: ^Pool) {
// the thread pool. You can even add tasks from inside other tasks.
//
// Each task also needs an allocator which it either owns, or which is thread
// safe. By default, allocations in the task are disabled by use of the
// nil_allocator.
pool_add_task :: proc(pool: ^Pool, procedure: Task_Proc, data: rawptr, user_index: int = 0, allocator := context.allocator) {
// safe.
pool_add_task :: proc(pool: ^Pool, allocator: mem.Allocator, procedure: Task_Proc, data: rawptr, user_index: int = 0) {
sync.guard(&pool.mutex)
append(&pool.tasks, Task{
@@ -0,0 +1,287 @@
package xml_example
import "core:encoding/xml"
import "core:os"
import "core:path"
import "core:mem"
import "core:strings"
import "core:strconv"
import "core:slice"
import "core:fmt"
/*
Silent error handler for the parser.
*/
Error_Handler :: proc(pos: xml.Pos, fmt: string, args: ..any) {}
OPTIONS :: xml.Options{ flags = { .Ignore_Unsupported, }, expected_doctype = "unicode", }
Entity :: struct {
name: string,
codepoint: rune,
description: string,
}
generate_encoding_entity_table :: proc() {
using fmt
filename := path.join(ODIN_ROOT, "tests", "core", "assets", "XML", "unicode.xml")
defer delete(filename)
generated_filename := path.join(ODIN_ROOT, "core", "encoding", "entity", "generated.odin")
defer delete(generated_filename)
doc, err := xml.parse(filename, OPTIONS, Error_Handler)
defer xml.destroy(doc)
if err != .None {
printf("Load/Parse error: %v\n", err)
if err == .File_Error {
printf("\"%v\" not found. Did you run \"tests\\download_assets.py\"?", filename)
}
os.exit(1)
}
printf("\"%v\" loaded and parsed.\n", filename)
generated_buf: strings.Builder
defer strings.destroy_builder(&generated_buf)
w := strings.to_writer(&generated_buf)
charlist, charlist_ok := xml.find_child_by_ident(doc.root, "charlist")
if !charlist_ok {
eprintln("Could not locate top-level `<charlist>` tag.")
os.exit(1)
}
printf("Found `<charlist>` with %v children.\n", len(charlist.children))
entity_map: map[string]Entity
names: [dynamic]string
min_name_length := max(int)
max_name_length := min(int)
shortest_name: string
longest_name: string
count := 0
for char in charlist.children {
if char.ident != "character" {
eprintf("Expected `<character>`, got `<%v>`\n", char.ident)
os.exit(1)
}
if codepoint_string, ok := xml.find_attribute_val_by_key(char, "dec"); !ok {
eprintln("`<character id=\"...\">` attribute not found.")
os.exit(1)
} else {
codepoint := strconv.atoi(codepoint_string)
desc, desc_ok := xml.find_child_by_ident(char, "description")
description := desc.value if desc_ok else ""
/*
For us to be interested in this codepoint, it has to have at least one entity.
*/
nth := 0
for {
character_entity, entity_ok := xml.find_child_by_ident(char, "entity", nth)
if !entity_ok { break }
nth += 1
if name, name_ok := xml.find_attribute_val_by_key(character_entity, "id"); name_ok {
if len(name) == 0 {
/*
Invalid name. Skip.
*/
continue
}
if name == "\"\"" {
printf("%#v\n", char)
printf("%#v\n", character_entity)
}
if len(name) > max_name_length { longest_name = name }
if len(name) < min_name_length { shortest_name = name }
min_name_length = min(min_name_length, len(name))
max_name_length = max(max_name_length, len(name))
e := Entity{
name = name,
codepoint = rune(codepoint),
description = description,
}
if _, seen := entity_map[name]; seen {
continue
}
entity_map[name] = e
append(&names, name)
count += 1
}
}
}
}
/*
Sort by name.
*/
slice.sort(names[:])
printf("Found %v unique `&name;` -> rune mappings.\n", count)
printf("Shortest name: %v (%v)\n", shortest_name, min_name_length)
printf("Longest name: %v (%v)\n", longest_name, max_name_length)
// println(rune_to_string(1234))
/*
Generate table.
*/
wprintln(w, "package unicode_entity")
wprintln(w, "")
wprintln(w, GENERATED)
wprintln(w, "")
wprintf (w, TABLE_FILE_PROLOG)
wprintln(w, "")
wprintf (w, "// `&%v;`\n", shortest_name)
wprintf (w, "XML_NAME_TO_RUNE_MIN_LENGTH :: %v\n", min_name_length)
wprintf (w, "// `&%v;`\n", longest_name)
wprintf (w, "XML_NAME_TO_RUNE_MAX_LENGTH :: %v\n", max_name_length)
wprintln(w, "")
wprintln(w,
`
/*
Input:
entity_name - a string, like "copy" that describes a user-encoded Unicode entity as used in XML.
Output:
"decoded" - The decoded rune if found by name, or -1 otherwise.
"ok" - true if found, false if not.
IMPORTANT: XML processors (including browsers) treat these names as case-sensitive. So do we.
*/
named_xml_entity_to_rune :: proc(name: string) -> (decoded: rune, ok: bool) {
/*
Early out if the name is too short or too long.
min as a precaution in case the generated table has a bogus value.
*/
if len(name) < min(1, XML_NAME_TO_RUNE_MIN_LENGTH) || len(name) > XML_NAME_TO_RUNE_MAX_LENGTH {
return -1, false
}
switch rune(name[0]) {
`)
prefix := '?'
should_close := false
for v in names {
if rune(v[0]) != prefix {
if should_close {
wprintln(w, "\t\t}\n")
}
prefix = rune(v[0])
wprintf (w, "\tcase '%v':\n", prefix)
wprintln(w, "\t\tswitch name {")
}
e := entity_map[v]
wprintf(w, "\t\t\tcase \"%v\": \n", e.name)
wprintf(w, "\t\t\t\t// %v\n", e.description)
wprintf(w, "\t\t\t\treturn %v, true\n", rune_to_string(e.codepoint))
should_close = true
}
wprintln(w, "\t\t}")
wprintln(w, "\t}")
wprintln(w, "\treturn -1, false")
wprintln(w, "}\n")
wprintln(w, GENERATED)
println()
println(strings.to_string(generated_buf))
println()
written := os.write_entire_file(generated_filename, transmute([]byte)strings.to_string(generated_buf))
if written {
fmt.printf("Successfully written generated \"%v\".", generated_filename)
} else {
fmt.printf("Failed to write generated \"%v\".", generated_filename)
}
delete(entity_map)
delete(names)
for name in &names {
free(&name)
}
}
GENERATED :: `/*
------ GENERATED ------ DO NOT EDIT ------ GENERATED ------ DO NOT EDIT ------ GENERATED ------
*/`
TABLE_FILE_PROLOG :: `/*
This file is generated from "https://www.w3.org/2003/entities/2007xml/unicode.xml".
UPDATE:
- Ensure the XML file was downloaded using "tests\core\download_assets.py".
- Run "core/unicode/tools/generate_entity_table.odin"
Odin unicode generated tables: https://github.com/odin-lang/Odin/tree/master/core/encoding/entity
Copyright © 2021 World Wide Web Consortium, (Massachusetts Institute of Technology,
European Research Consortium for Informatics and Mathematics, Keio University, Beihang).
All Rights Reserved.
This work is distributed under the W3C® Software License [1] in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
[1] http://www.w3.org/Consortium/Legal/copyright-software
See also: LICENSE_table.md
*/
`
rune_to_string :: proc(r: rune) -> (res: string) {
res = fmt.tprintf("%08x", int(r))
for len(res) > 2 && res[:2] == "00" {
res = res[2:]
}
return fmt.tprintf("rune(0x%v)", res)
}
is_dotted_name :: proc(name: string) -> (dotted: bool) {
for r in name {
if r == '.' { return true}
}
return false
}
main :: proc() {
using fmt
track: mem.Tracking_Allocator
mem.tracking_allocator_init(&track, context.allocator)
context.allocator = mem.tracking_allocator(&track)
generate_encoding_entity_table()
if len(track.allocation_map) > 0 {
println()
for _, v in track.allocation_map {
printf("%v Leaked %v bytes.\n", v.location, v.size)
}
}
println("Done and cleaned up!")
}
+6
View File
@@ -10,6 +10,7 @@ import c "core:c"
import libc "core:c/libc"
import compress "core:compress"
import shoco "core:compress/shoco"
import gzip "core:compress/gzip"
import zlib "core:compress/zlib"
@@ -55,6 +56,7 @@ import csv "core:encoding/csv"
import hxa "core:encoding/hxa"
import json "core:encoding/json"
import varint "core:encoding/varint"
import xml "core:encoding/xml"
import fmt "core:fmt"
import hash "core:hash"
@@ -99,6 +101,7 @@ import strings "core:strings"
import sync "core:sync"
import testing "core:testing"
import scanner "core:text/scanner"
import i18n "core:text/i18n"
import thread "core:thread"
import time "core:time"
@@ -115,6 +118,7 @@ _ :: bytes
_ :: c
_ :: libc
_ :: compress
_ :: shoco
_ :: gzip
_ :: zlib
_ :: bit_array
@@ -156,6 +160,7 @@ _ :: csv
_ :: hxa
_ :: json
_ :: varint
_ :: xml
_ :: fmt
_ :: hash
_ :: image
@@ -190,6 +195,7 @@ _ :: strings
_ :: sync
_ :: testing
_ :: scanner
_ :: i18n
_ :: thread
_ :: time
_ :: unicode
+2 -1
View File
@@ -1166,7 +1166,8 @@ threading_example :: proc() {
for i in 0..<30 {
thread.pool_add_task(pool=&pool, procedure=task_proc, data=nil, user_index=i)
// be mindful of the allocator used for tasks. The allocator needs to be thread safe, or be owned by the task for exclusive use
thread.pool_add_task(pool=&pool, procedure=task_proc, data=nil, user_index=i, allocator=context.allocator)
}
thread.pool_start(&pool)
+208 -28
View File
@@ -3,7 +3,6 @@
#include <sys/sysctl.h>
#endif
// #if defined(GB_SYSTEM_WINDOWS)
// #define DEFAULT_TO_THREADED_CHECKER
// #endif
@@ -198,6 +197,22 @@ enum RelocMode : u8 {
RelocMode_DynamicNoPIC,
};
enum BuildPath : u8 {
BuildPath_Main_Package, // Input Path to the package directory (or file) we're building.
BuildPath_RC, // Input Path for .rc file, can be set with `-resource:`.
BuildPath_RES, // Output Path for .res file, generated from previous.
BuildPath_Win_SDK_Root, // windows_sdk_root
BuildPath_Win_SDK_UM_Lib, // windows_sdk_um_library_path
BuildPath_Win_SDK_UCRT_Lib, // windows_sdk_ucrt_library_path
BuildPath_VS_EXE, // vs_exe_path
BuildPath_VS_LIB, // vs_library_path
BuildPath_Output, // Output Path for .exe, .dll, .so, etc. Can be overridden with `-out:`.
BuildPath_PDB, // Output Path for .pdb file, can be overridden with `-pdb-name:`.
BuildPathCOUNT,
};
// This stores the information for the specify architecture of this build
struct BuildContext {
// Constants
@@ -226,9 +241,13 @@ struct BuildContext {
bool show_help;
Array<Path> build_paths; // Contains `Path` objects to output filename, pdb, resource and intermediate files.
// BuildPath enum contains the indices of paths we know *before* the work starts.
String out_filepath;
String resource_filepath;
String pdb_filepath;
bool has_resource;
String link_flags;
String extra_linker_flags;
@@ -300,8 +319,6 @@ struct BuildContext {
};
gb_global BuildContext build_context = {0};
bool global_warnings_as_errors(void) {
@@ -605,28 +622,6 @@ bool allow_check_foreign_filepath(void) {
// is_abs_path
// has_subdir
enum TargetFileValidity : u8 {
TargetFileValidity_Invalid,
TargetFileValidity_Writable_File,
TargetFileValidity_No_Write_Permission,
TargetFileValidity_Directory,
TargetTargetFileValidity_COUNT,
};
TargetFileValidity set_output_filename(void) {
// Assembles the output filename from build_context information.
// Returns `true` if it doesn't exist or is a file.
// Returns `false` if a directory or write-protected file.
return TargetFileValidity_Writable_File;
}
String const WIN32_SEPARATOR_STRING = {cast(u8 *)"\\", 1};
String const NIX_SEPARATOR_STRING = {cast(u8 *)"/", 1};
@@ -973,7 +968,6 @@ char *token_pos_to_string(TokenPos const &pos) {
return s;
}
void init_build_context(TargetMetrics *cross_target) {
BuildContext *bc = &build_context;
@@ -1152,8 +1146,194 @@ void init_build_context(TargetMetrics *cross_target) {
bc->optimization_level = gb_clamp(bc->optimization_level, 0, 3);
#undef LINK_FLAG_X64
#undef LINK_FLAG_386
}
#if defined(GB_SYSTEM_WINDOWS)
// NOTE(IC): In order to find Visual C++ paths without relying on environment variables.
// NOTE(Jeroen): No longer needed in `main.cpp -> linker_stage`. We now resolve those paths in `init_build_paths`.
#include "microsoft_craziness.h"
#endif
// NOTE(Jeroen): Set/create the output and other paths and report an error as appropriate.
// We've previously called `parse_build_flags`, so `out_filepath` should be set.
bool init_build_paths(String init_filename) {
gbAllocator ha = heap_allocator();
BuildContext *bc = &build_context;
// NOTE(Jeroen): We're pre-allocating BuildPathCOUNT slots so that certain paths are always at the same enumerated index.
array_init(&bc->build_paths, permanent_allocator(), BuildPathCOUNT);
// [BuildPathMainPackage] Turn given init path into a `Path`, which includes normalizing it into a full path.
bc->build_paths[BuildPath_Main_Package] = path_from_string(ha, init_filename);
bool produces_output_file = false;
if (bc->command_kind == Command_doc && bc->cmd_doc_flags & CmdDocFlag_DocFormat) {
produces_output_file = true;
} else if (bc->command_kind & Command__does_build) {
produces_output_file = true;
}
if (!produces_output_file) {
// Command doesn't produce output files. We're done.
return true;
}
#if defined(GB_SYSTEM_WINDOWS)
if (bc->resource_filepath.len > 0) {
bc->build_paths[BuildPath_RC] = path_from_string(ha, bc->resource_filepath);
bc->build_paths[BuildPath_RES] = path_from_string(ha, bc->resource_filepath);
bc->build_paths[BuildPath_RC].ext = copy_string(ha, STR_LIT("rc"));
bc->build_paths[BuildPath_RES].ext = copy_string(ha, STR_LIT("res"));
}
if (bc->pdb_filepath.len > 0) {
bc->build_paths[BuildPath_PDB] = path_from_string(ha, bc->pdb_filepath);
}
if ((bc->command_kind & Command__does_build) && (!bc->ignore_microsoft_magic)) {
// NOTE(ic): It would be nice to extend this so that we could specify the Visual Studio version that we want instead of defaulting to the latest.
Find_Result_Utf8 find_result = find_visual_studio_and_windows_sdk_utf8();
if (find_result.windows_sdk_version == 0) {
gb_printf_err("Windows SDK not found.\n");
return false;
}
GB_ASSERT(find_result.windows_sdk_um_library_path.len > 0);
GB_ASSERT(find_result.windows_sdk_ucrt_library_path.len > 0);
if (find_result.windows_sdk_root.len > 0) {
bc->build_paths[BuildPath_Win_SDK_Root] = path_from_string(ha, find_result.windows_sdk_root);
}
if (find_result.windows_sdk_um_library_path.len > 0) {
bc->build_paths[BuildPath_Win_SDK_UM_Lib] = path_from_string(ha, find_result.windows_sdk_um_library_path);
}
if (find_result.windows_sdk_ucrt_library_path.len > 0) {
bc->build_paths[BuildPath_Win_SDK_UCRT_Lib] = path_from_string(ha, find_result.windows_sdk_ucrt_library_path);
}
if (find_result.vs_exe_path.len > 0) {
bc->build_paths[BuildPath_VS_EXE] = path_from_string(ha, find_result.vs_exe_path);
}
if (find_result.vs_library_path.len > 0) {
bc->build_paths[BuildPath_VS_LIB] = path_from_string(ha, find_result.vs_library_path);
}
gb_free(ha, find_result.windows_sdk_root.text);
gb_free(ha, find_result.windows_sdk_um_library_path.text);
gb_free(ha, find_result.windows_sdk_ucrt_library_path.text);
gb_free(ha, find_result.vs_exe_path.text);
gb_free(ha, find_result.vs_library_path.text);
}
#endif
// All the build targets and OSes.
String output_extension;
if (bc->command_kind == Command_doc && bc->cmd_doc_flags & CmdDocFlag_DocFormat) {
output_extension = STR_LIT("odin-doc");
} else if (is_arch_wasm()) {
output_extension = STR_LIT("wasm");
} else if (build_context.build_mode == BuildMode_Executable) {
// By default use a .bin executable extension.
output_extension = STR_LIT("bin");
if (build_context.metrics.os == TargetOs_windows) {
output_extension = STR_LIT("exe");
} else if (build_context.cross_compiling && selected_target_metrics->metrics == &target_essence_amd64) {
output_extension = make_string(nullptr, 0);
}
} else if (build_context.build_mode == BuildMode_DynamicLibrary) {
// By default use a .so shared library extension.
output_extension = STR_LIT("so");
if (build_context.metrics.os == TargetOs_windows) {
output_extension = STR_LIT("dll");
} else if (build_context.metrics.os == TargetOs_darwin) {
output_extension = STR_LIT("dylib");
}
} else if (build_context.build_mode == BuildMode_Object) {
// By default use a .o object extension.
output_extension = STR_LIT("o");
if (build_context.metrics.os == TargetOs_windows) {
output_extension = STR_LIT("obj");
}
} else if (build_context.build_mode == BuildMode_Assembly) {
// By default use a .S asm extension.
output_extension = STR_LIT("S");
} else if (build_context.build_mode == BuildMode_LLVM_IR) {
output_extension = STR_LIT("ll");
} else {
GB_PANIC("Unhandled build mode/target combination.\n");
}
if (bc->out_filepath.len > 0) {
bc->build_paths[BuildPath_Output] = path_from_string(ha, bc->out_filepath);
if (build_context.metrics.os == TargetOs_windows) {
String output_file = path_to_string(ha, bc->build_paths[BuildPath_Output]);
defer (gb_free(ha, output_file.text));
if (path_is_directory(bc->build_paths[BuildPath_Output])) {
gb_printf_err("Output path %.*s is a directory.\n", LIT(output_file));
return false;
} else if (bc->build_paths[BuildPath_Output].ext.len == 0) {
gb_printf_err("Output path %.*s must have an appropriate extension.\n", LIT(output_file));
return false;
}
}
} else {
Path output_path;
if (str_eq(init_filename, str_lit("."))) {
// We must name the output file after the current directory.
debugf("Output name will be created from current base name %.*s.\n", LIT(bc->build_paths[BuildPath_Main_Package].basename));
String last_element = last_path_element(bc->build_paths[BuildPath_Main_Package].basename);
if (last_element.len == 0) {
gb_printf_err("The output name is created from the last path element. `%.*s` has none. Use `-out:output_name.ext` to set it.\n", LIT(bc->build_paths[BuildPath_Main_Package].basename));
return false;
}
output_path.basename = copy_string(ha, bc->build_paths[BuildPath_Main_Package].basename);
output_path.name = copy_string(ha, last_element);
} else {
// Init filename was not 'current path'.
// Contruct the output name from the path elements as usual.
String output_name = remove_directory_from_path(init_filename);
output_name = remove_extension_from_path(output_name);
output_name = copy_string(ha, string_trim_whitespace(output_name));
output_path = path_from_string(ha, output_name);
// Replace extension.
if (output_path.ext.len > 0) {
gb_free(ha, output_path.ext.text);
}
}
output_path.ext = copy_string(ha, output_extension);
bc->build_paths[BuildPath_Output] = output_path;
}
// Do we have an extension? We might not if the output filename was supplied.
if (bc->build_paths[BuildPath_Output].ext.len == 0) {
if (build_context.metrics.os == TargetOs_windows || build_context.build_mode != BuildMode_Executable) {
bc->build_paths[BuildPath_Output].ext = copy_string(ha, output_extension);
}
}
// Check if output path is a directory.
if (path_is_directory(bc->build_paths[BuildPath_Output])) {
String output_file = path_to_string(ha, bc->build_paths[BuildPath_Output]);
defer (gb_free(ha, output_file.text));
gb_printf_err("Output path %.*s is a directory.\n", LIT(output_file));
return false;
}
return true;
}
+33
View File
@@ -29,6 +29,7 @@ BuiltinTypeIsProc *builtin_type_is_procs[BuiltinProc__type_simple_boolean_end -
is_type_named,
is_type_pointer,
is_type_multi_pointer,
is_type_array,
is_type_enumerated_array,
is_type_slice,
@@ -3866,6 +3867,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
case BuiltinProc_type_is_valid_matrix_elements:
case BuiltinProc_type_is_named:
case BuiltinProc_type_is_pointer:
case BuiltinProc_type_is_multi_pointer:
case BuiltinProc_type_is_array:
case BuiltinProc_type_is_enumerated_array:
case BuiltinProc_type_is_slice:
@@ -3926,6 +3928,37 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
break;
}
break;
case BuiltinProc_type_field_type:
{
Operand op = {};
Type *bt = check_type(c, ce->args[0]);
Type *type = base_type(bt);
if (type == nullptr || type == t_invalid) {
error(ce->args[0], "Expected a type for '%.*s'", LIT(builtin_name));
return false;
}
Operand x = {};
check_expr(c, &x, ce->args[1]);
if (!is_type_string(x.type) || x.mode != Addressing_Constant || x.value.kind != ExactValue_String) {
error(ce->args[1], "Expected a const string for field argument");
return false;
}
String field_name = x.value.value_string;
Selection sel = lookup_field(type, field_name, false);
if (sel.index.count == 0) {
gbString t = type_to_string(type);
error(ce->args[1], "'%.*s' is not a field of type %s", LIT(field_name), t);
gb_string_free(t);
return false;
}
operand->mode = Addressing_Type;
operand->type = sel.entity->type;
break;
}
break;
case BuiltinProc_type_is_specialization_of:
{
+4
View File
@@ -158,6 +158,7 @@ BuiltinProc__type_simple_boolean_begin,
BuiltinProc_type_is_named,
BuiltinProc_type_is_pointer,
BuiltinProc_type_is_multi_pointer,
BuiltinProc_type_is_array,
BuiltinProc_type_is_enumerated_array,
BuiltinProc_type_is_slice,
@@ -179,6 +180,7 @@ BuiltinProc__type_simple_boolean_begin,
BuiltinProc__type_simple_boolean_end,
BuiltinProc_type_has_field,
BuiltinProc_type_field_type,
BuiltinProc_type_is_specialization_of,
@@ -375,6 +377,7 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
{STR_LIT("type_is_named"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
{STR_LIT("type_is_pointer"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
{STR_LIT("type_is_multi_pointer"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
{STR_LIT("type_is_array"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
{STR_LIT("type_is_enumerated_array"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
{STR_LIT("type_is_slice"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
@@ -395,6 +398,7 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
{STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics},
{STR_LIT("type_has_field"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
{STR_LIT("type_field_type"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
{STR_LIT("type_is_specialization_of"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
+1 -256
View File
@@ -675,262 +675,7 @@ wchar_t **command_line_to_wargv(wchar_t *cmd_line, int *_argc) {
#endif
#if defined(GB_SYSTEM_WINDOWS)
bool path_is_directory(String path) {
gbAllocator a = heap_allocator();
String16 wstr = string_to_string16(a, path);
defer (gb_free(a, wstr.text));
i32 attribs = GetFileAttributesW(wstr.text);
if (attribs < 0) return false;
return (attribs & FILE_ATTRIBUTE_DIRECTORY) != 0;
}
#else
bool path_is_directory(String path) {
gbAllocator a = heap_allocator();
char *copy = cast(char *)copy_string(a, path).text;
defer (gb_free(a, copy));
struct stat s;
if (stat(copy, &s) == 0) {
return (s.st_mode & S_IFDIR) != 0;
}
return false;
}
#endif
String path_to_full_path(gbAllocator a, String path) {
gbAllocator ha = heap_allocator();
char *path_c = gb_alloc_str_len(ha, cast(char *)path.text, path.len);
defer (gb_free(ha, path_c));
char *fullpath = gb_path_get_full_name(a, path_c);
String res = string_trim_whitespace(make_string_c(fullpath));
#if defined(GB_SYSTEM_WINDOWS)
for (isize i = 0; i < res.len; i++) {
if (res.text[i] == '\\') {
res.text[i] = '/';
}
}
#endif
return res;
}
struct FileInfo {
String name;
String fullpath;
i64 size;
bool is_dir;
};
enum ReadDirectoryError {
ReadDirectory_None,
ReadDirectory_InvalidPath,
ReadDirectory_NotExists,
ReadDirectory_Permission,
ReadDirectory_NotDir,
ReadDirectory_Empty,
ReadDirectory_Unknown,
ReadDirectory_COUNT,
};
i64 get_file_size(String path) {
char *c_str = alloc_cstring(heap_allocator(), path);
defer (gb_free(heap_allocator(), c_str));
gbFile f = {};
gbFileError err = gb_file_open(&f, c_str);
defer (gb_file_close(&f));
if (err != gbFileError_None) {
return -1;
}
return gb_file_size(&f);
}
#if defined(GB_SYSTEM_WINDOWS)
ReadDirectoryError read_directory(String path, Array<FileInfo> *fi) {
GB_ASSERT(fi != nullptr);
gbAllocator a = heap_allocator();
while (path.len > 0) {
Rune end = path[path.len-1];
if (end == '/') {
path.len -= 1;
} else if (end == '\\') {
path.len -= 1;
} else {
break;
}
}
if (path.len == 0) {
return ReadDirectory_InvalidPath;
}
{
char *c_str = alloc_cstring(a, path);
defer (gb_free(a, c_str));
gbFile f = {};
gbFileError file_err = gb_file_open(&f, c_str);
defer (gb_file_close(&f));
switch (file_err) {
case gbFileError_Invalid: return ReadDirectory_InvalidPath;
case gbFileError_NotExists: return ReadDirectory_NotExists;
// case gbFileError_Permission: return ReadDirectory_Permission;
}
}
if (!path_is_directory(path)) {
return ReadDirectory_NotDir;
}
char *new_path = gb_alloc_array(a, char, path.len+3);
defer (gb_free(a, new_path));
gb_memmove(new_path, path.text, path.len);
gb_memmove(new_path+path.len, "/*", 2);
new_path[path.len+2] = 0;
String np = make_string(cast(u8 *)new_path, path.len+2);
String16 wstr = string_to_string16(a, np);
defer (gb_free(a, wstr.text));
WIN32_FIND_DATAW file_data = {};
HANDLE find_file = FindFirstFileW(wstr.text, &file_data);
if (find_file == INVALID_HANDLE_VALUE) {
return ReadDirectory_Unknown;
}
defer (FindClose(find_file));
array_init(fi, a, 0, 100);
do {
wchar_t *filename_w = file_data.cFileName;
i64 size = cast(i64)file_data.nFileSizeLow;
size |= (cast(i64)file_data.nFileSizeHigh) << 32;
String name = string16_to_string(a, make_string16_c(filename_w));
if (name == "." || name == "..") {
gb_free(a, name.text);
continue;
}
String filepath = {};
filepath.len = path.len+1+name.len;
filepath.text = gb_alloc_array(a, u8, filepath.len+1);
defer (gb_free(a, filepath.text));
gb_memmove(filepath.text, path.text, path.len);
gb_memmove(filepath.text+path.len, "/", 1);
gb_memmove(filepath.text+path.len+1, name.text, name.len);
FileInfo info = {};
info.name = name;
info.fullpath = path_to_full_path(a, filepath);
info.size = size;
info.is_dir = (file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
array_add(fi, info);
} while (FindNextFileW(find_file, &file_data));
if (fi->count == 0) {
return ReadDirectory_Empty;
}
return ReadDirectory_None;
}
#elif defined(GB_SYSTEM_LINUX) || defined(GB_SYSTEM_OSX) || defined(GB_SYSTEM_FREEBSD) || defined(GB_SYSTEM_OPENBSD)
#include <dirent.h>
ReadDirectoryError read_directory(String path, Array<FileInfo> *fi) {
GB_ASSERT(fi != nullptr);
gbAllocator a = heap_allocator();
char *c_path = alloc_cstring(a, path);
defer (gb_free(a, c_path));
DIR *dir = opendir(c_path);
if (!dir) {
switch (errno) {
case ENOENT:
return ReadDirectory_NotExists;
case EACCES:
return ReadDirectory_Permission;
case ENOTDIR:
return ReadDirectory_NotDir;
default:
// ENOMEM: out of memory
// EMFILE: per-process limit on open fds reached
// ENFILE: system-wide limit on total open files reached
return ReadDirectory_Unknown;
}
GB_PANIC("unreachable");
}
array_init(fi, a, 0, 100);
for (;;) {
struct dirent *entry = readdir(dir);
if (entry == nullptr) {
break;
}
String name = make_string_c(entry->d_name);
if (name == "." || name == "..") {
continue;
}
String filepath = {};
filepath.len = path.len+1+name.len;
filepath.text = gb_alloc_array(a, u8, filepath.len+1);
defer (gb_free(a, filepath.text));
gb_memmove(filepath.text, path.text, path.len);
gb_memmove(filepath.text+path.len, "/", 1);
gb_memmove(filepath.text+path.len+1, name.text, name.len);
filepath.text[filepath.len] = 0;
struct stat dir_stat = {};
if (stat((char *)filepath.text, &dir_stat)) {
continue;
}
if (S_ISDIR(dir_stat.st_mode)) {
continue;
}
i64 size = dir_stat.st_size;
FileInfo info = {};
info.name = name;
info.fullpath = path_to_full_path(a, filepath);
info.size = size;
array_add(fi, info);
}
if (fi->count == 0) {
return ReadDirectory_Empty;
}
return ReadDirectory_None;
}
#else
#error Implement read_directory
#endif
#include "path.cpp"
struct LoadedFile {
void *handle;
+37 -13
View File
@@ -6273,20 +6273,44 @@ char *gb_path_get_full_name(gbAllocator a, char const *path) {
#else
char *p, *result, *fullpath = NULL;
isize len;
p = realpath(path, NULL);
fullpath = p;
if (p == NULL) {
// NOTE(bill): File does not exist
fullpath = cast(char *)path;
fullpath = realpath(path, NULL);
if (fullpath == NULL) {
// NOTE(Jeroen): Path doesn't exist.
if (gb_strlen(path) > 0 && path[0] == '/') {
// But it is an absolute path, so return as-is.
fullpath = (char *)path;
len = gb_strlen(fullpath) + 1;
result = gb_alloc_array(a, char, len + 1);
gb_memmove(result, fullpath, len);
result[len] = 0;
} else {
// Appears to be a relative path, so construct an absolute one relative to <cwd>.
char cwd[4096];
getcwd(&cwd[0], 4096);
isize path_len = gb_strlen(path);
isize cwd_len = gb_strlen(cwd);
len = cwd_len + 1 + path_len + 1;
result = gb_alloc_array(a, char, len);
gb_memmove(result, (void *)cwd, cwd_len);
result[cwd_len] = '/';
gb_memmove(result + cwd_len + 1, (void *)path, gb_strlen(path));
result[len] = 0;
}
} else {
len = gb_strlen(fullpath) + 1;
result = gb_alloc_array(a, char, len + 1);
gb_memmove(result, fullpath, len);
result[len] = 0;
free(fullpath);
}
len = gb_strlen(fullpath);
result = gb_alloc_array(a, char, len + 1);
gb_memmove(result, fullpath, len);
result[len] = 0;
free(p);
return result;
#endif
}
+12 -2
View File
@@ -967,7 +967,12 @@ lbProcedure *lb_create_main_procedure(lbModule *m, lbProcedure *startup_runtime)
}
String lb_filepath_ll_for_module(lbModule *m) {
String path = m->gen->output_base;
String path = concatenate3_strings(permanent_allocator(),
build_context.build_paths[BuildPath_Output].basename,
STR_LIT("/"),
build_context.build_paths[BuildPath_Output].name
);
if (m->pkg) {
path = concatenate3_strings(permanent_allocator(), path, STR_LIT("-"), m->pkg->name);
} else if (USE_SEPARATE_MODULES) {
@@ -978,7 +983,12 @@ String lb_filepath_ll_for_module(lbModule *m) {
return path;
}
String lb_filepath_obj_for_module(lbModule *m) {
String path = m->gen->output_base;
String path = concatenate3_strings(permanent_allocator(),
build_context.build_paths[BuildPath_Output].basename,
STR_LIT("/"),
build_context.build_paths[BuildPath_Output].name
);
if (m->pkg) {
path = concatenate3_strings(permanent_allocator(), path, STR_LIT("-"), m->pkg->name);
}
-1
View File
@@ -87,7 +87,6 @@ bool lb_init_generator(lbGenerator *gen, Checker *c) {
return false;
}
String init_fullpath = c->parser->init_fullpath;
if (build_context.out_filepath.len == 0) {
+88 -80
View File
@@ -46,7 +46,6 @@ gb_global Timings global_timings = {0};
#include "checker.cpp"
#include "docs.cpp"
#include "llvm_backend.cpp"
#if defined(GB_SYSTEM_OSX)
@@ -57,16 +56,8 @@ gb_global Timings global_timings = {0};
#endif
#include "query_data.cpp"
#if defined(GB_SYSTEM_WINDOWS)
// NOTE(IC): In order to find Visual C++ paths without relying on environment variables.
#include "microsoft_craziness.h"
#endif
#include "bug_report.cpp"
// NOTE(bill): 'name' is used in debugging and profiling modes
i32 system_exec_command_line_app(char const *name, char const *fmt, ...) {
isize const cmd_cap = 64<<20; // 64 MiB should be more than enough
@@ -130,34 +121,35 @@ i32 system_exec_command_line_app(char const *name, char const *fmt, ...) {
}
i32 linker_stage(lbGenerator *gen) {
i32 result = 0;
Timings *timings = &global_timings;
String output_base = gen->output_base;
String output_filename = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_Output]);
debugf("Linking %.*s\n", LIT(output_filename));
// TOOD(Jeroen): Make a `build_paths[BuildPath_Object] to avoid `%.*s.o`.
if (is_arch_wasm()) {
timings_start_section(timings, str_lit("wasm-ld"));
#if defined(GB_SYSTEM_WINDOWS)
result = system_exec_command_line_app("wasm-ld",
"\"%.*s\\bin\\wasm-ld\" \"%.*s.wasm.o\" -o \"%.*s.wasm\" %.*s %.*s",
"\"%.*s\\bin\\wasm-ld\" \"%.*s.o\" -o \"%.*s\" %.*s %.*s",
LIT(build_context.ODIN_ROOT),
LIT(output_base), LIT(output_base), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags));
LIT(output_filename), LIT(output_filename), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags));
#else
result = system_exec_command_line_app("wasm-ld",
"wasm-ld \"%.*s.wasm.o\" -o \"%.*s.wasm\" %.*s %.*s",
LIT(output_base), LIT(output_base), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags));
"wasm-ld \"%.*s.o\" -o \"%.*s\" %.*s %.*s",
LIT(output_filename), LIT(output_filename), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags));
#endif
return result;
}
if (build_context.cross_compiling && selected_target_metrics->metrics == &target_essence_amd64) {
#ifdef GB_SYSTEM_UNIX
#if defined(GB_SYSTEM_UNIX)
result = system_exec_command_line_app("linker", "x86_64-essence-gcc \"%.*s.o\" -o \"%.*s\" %.*s %.*s",
LIT(output_base), LIT(output_base), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags));
LIT(output_filename), LIT(output_filename), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags));
#else
gb_printf_err("Linking for cross compilation for this platform is not yet supported (%.*s %.*s)\n",
LIT(target_os_names[build_context.metrics.os]),
@@ -181,28 +173,11 @@ i32 linker_stage(lbGenerator *gen) {
gbString lib_str = gb_string_make(heap_allocator(), "");
defer (gb_string_free(lib_str));
char const *output_ext = "exe";
gbString link_settings = gb_string_make_reserve(heap_allocator(), 256);
defer (gb_string_free(link_settings));
// NOTE(ic): It would be nice to extend this so that we could specify the Visual Studio version that we want instead of defaulting to the latest.
Find_Result_Utf8 find_result = find_visual_studio_and_windows_sdk_utf8();
if (find_result.windows_sdk_version == 0) {
gb_printf_err("Windows SDK not found.\n");
exit(1);
}
if (build_context.ignore_microsoft_magic) {
find_result = {};
}
// Add library search paths.
if (find_result.vs_library_path.len > 0) {
GB_ASSERT(find_result.windows_sdk_um_library_path.len > 0);
GB_ASSERT(find_result.windows_sdk_ucrt_library_path.len > 0);
if (build_context.build_paths[BuildPath_VS_LIB].basename.len > 0) {
String path = {};
auto add_path = [&](String path) {
if (path[path.len-1] == '\\') {
@@ -210,9 +185,9 @@ i32 linker_stage(lbGenerator *gen) {
}
link_settings = gb_string_append_fmt(link_settings, " /LIBPATH:\"%.*s\"", LIT(path));
};
add_path(find_result.windows_sdk_um_library_path);
add_path(find_result.windows_sdk_ucrt_library_path);
add_path(find_result.vs_library_path);
add_path(build_context.build_paths[BuildPath_Win_SDK_UM_Lib].basename);
add_path(build_context.build_paths[BuildPath_Win_SDK_UCRT_Lib].basename);
add_path(build_context.build_paths[BuildPath_VS_LIB].basename);
}
@@ -252,14 +227,14 @@ i32 linker_stage(lbGenerator *gen) {
if (build_context.build_mode == BuildMode_DynamicLibrary) {
output_ext = "dll";
link_settings = gb_string_append_fmt(link_settings, " /DLL");
} else {
link_settings = gb_string_append_fmt(link_settings, " /ENTRY:mainCRTStartup");
}
if (build_context.pdb_filepath != "") {
link_settings = gb_string_append_fmt(link_settings, " /PDB:%.*s", LIT(build_context.pdb_filepath));
String pdb_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_PDB]);
link_settings = gb_string_append_fmt(link_settings, " /PDB:%.*s", LIT(pdb_path));
}
if (build_context.no_crt) {
@@ -300,13 +275,21 @@ i32 linker_stage(lbGenerator *gen) {
object_files = gb_string_append_fmt(object_files, "\"%.*s\" ", LIT(object_path));
}
String vs_exe_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_VS_EXE]);
defer (gb_free(heap_allocator(), vs_exe_path.text));
char const *subsystem_str = build_context.use_subsystem_windows ? "WINDOWS" : "CONSOLE";
if (!build_context.use_lld) { // msvc
if (build_context.has_resource) {
String rc_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_RC]);
String res_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_RES]);
defer (gb_free(heap_allocator(), rc_path.text));
defer (gb_free(heap_allocator(), res_path.text));
result = system_exec_command_line_app("msvc-link",
"\"rc.exe\" /nologo /fo \"%.*s.res\" \"%.*s.rc\"",
LIT(output_base),
LIT(build_context.resource_filepath)
"\"rc.exe\" /nologo /fo \"%.*s\" \"%.*s\"",
LIT(res_path),
LIT(rc_path)
);
if (result) {
@@ -314,13 +297,13 @@ i32 linker_stage(lbGenerator *gen) {
}
result = system_exec_command_line_app("msvc-link",
"\"%.*slink.exe\" %s \"%.*s.res\" -OUT:\"%.*s.%s\" %s "
"\"%.*slink.exe\" %s \"%.*s\" -OUT:\"%.*s\" %s "
"/nologo /incremental:no /opt:ref /subsystem:%s "
" %.*s "
" %.*s "
" %s "
"",
LIT(find_result.vs_exe_path), object_files, LIT(output_base), LIT(output_base), output_ext,
LIT(vs_exe_path), object_files, LIT(res_path), LIT(output_filename),
link_settings,
subsystem_str,
LIT(build_context.link_flags),
@@ -329,13 +312,13 @@ i32 linker_stage(lbGenerator *gen) {
);
} else {
result = system_exec_command_line_app("msvc-link",
"\"%.*slink.exe\" %s -OUT:\"%.*s.%s\" %s "
"\"%.*slink.exe\" %s -OUT:\"%.*s\" %s "
"/nologo /incremental:no /opt:ref /subsystem:%s "
" %.*s "
" %.*s "
" %s "
"",
LIT(find_result.vs_exe_path), object_files, LIT(output_base), output_ext,
LIT(vs_exe_path), object_files, LIT(output_filename),
link_settings,
subsystem_str,
LIT(build_context.link_flags),
@@ -350,13 +333,13 @@ i32 linker_stage(lbGenerator *gen) {
} else { // lld
result = system_exec_command_line_app("msvc-lld-link",
"\"%.*s\\bin\\lld-link\" %s -OUT:\"%.*s.%s\" %s "
"\"%.*s\\bin\\lld-link\" %s -OUT:\"%.*s\" %s "
"/nologo /incremental:no /opt:ref /subsystem:%s "
" %.*s "
" %.*s "
" %s "
"",
LIT(build_context.ODIN_ROOT), object_files, LIT(output_base),output_ext,
LIT(build_context.ODIN_ROOT), object_files, LIT(output_filename),
link_settings,
subsystem_str,
LIT(build_context.link_flags),
@@ -415,7 +398,7 @@ i32 linker_stage(lbGenerator *gen) {
} else if (string_ends_with(lib, str_lit(".so"))) {
// dynamic lib, relative path to executable
// NOTE(vassvik): it is the user's responsibility to make sure the shared library files are visible
// at runtimeto the executable
// at runtime to the executable
lib_str = gb_string_append_fmt(lib_str, " -l:\"%s/%.*s\" ", cwd, LIT(lib));
} else {
// dynamic or static system lib, just link regularly searching system library paths
@@ -431,9 +414,6 @@ i32 linker_stage(lbGenerator *gen) {
object_files = gb_string_append_fmt(object_files, "\"%.*s\" ", LIT(object_path));
}
// Unlike the Win32 linker code, the output_ext includes the dot, because
// typically executable files on *NIX systems don't have extensions.
String output_ext = {};
gbString link_settings = gb_string_make_reserve(heap_allocator(), 32);
if (build_context.no_crt) {
@@ -461,26 +441,12 @@ i32 linker_stage(lbGenerator *gen) {
// correctly this way since all the other dependencies provided implicitly
// by the compiler frontend are still needed and most of the command
// line arguments prepared previously are incompatible with ld.
//
// Shared libraries are .dylib on MacOS and .so on Linux.
if (build_context.metrics.os == TargetOs_darwin) {
output_ext = STR_LIT(".dylib");
} else {
output_ext = STR_LIT(".so");
}
link_settings = gb_string_appendc(link_settings, "-Wl,-init,'_odin_entry_point' ");
link_settings = gb_string_appendc(link_settings, "-Wl,-fini,'_odin_exit_point' ");
} else if (build_context.metrics.os != TargetOs_openbsd) {
// OpenBSD defaults to PIE executable. do not pass -no-pie for it.
link_settings = gb_string_appendc(link_settings, "-no-pie ");
}
if (build_context.out_filepath.len > 0) {
//NOTE(thebirk): We have a custom -out arguments, so we should use the extension from that
isize pos = string_extension_position(build_context.out_filepath);
if (pos > 0) {
output_ext = substring(build_context.out_filepath, pos, build_context.out_filepath.len);
}
}
gbString platform_lib_str = gb_string_make(heap_allocator(), "");
defer (gb_string_free(platform_lib_str));
@@ -507,7 +473,7 @@ i32 linker_stage(lbGenerator *gen) {
defer (gb_string_free(link_command_line));
link_command_line = gb_string_appendc(link_command_line, object_files);
link_command_line = gb_string_append_fmt(link_command_line, " -o \"%.*s%.*s\" ", LIT(output_base), LIT(output_ext));
link_command_line = gb_string_append_fmt(link_command_line, " -o \"%.*s\" ", LIT(output_filename));
link_command_line = gb_string_append_fmt(link_command_line, " %s ", platform_lib_str);
link_command_line = gb_string_append_fmt(link_command_line, " %s ", lib_str);
link_command_line = gb_string_append_fmt(link_command_line, " %.*s ", LIT(build_context.link_flags));
@@ -524,9 +490,7 @@ i32 linker_stage(lbGenerator *gen) {
if (build_context.ODIN_DEBUG) {
// NOTE: macOS links DWARF symbols dynamically. Dsymutil will map the stubs in the exe
// to the symbols in the object file
result = system_exec_command_line_app("dsymutil",
"dsymutil %.*s%.*s", LIT(output_base), LIT(output_ext)
);
result = system_exec_command_line_app("dsymutil", "dsymutil %.*s", LIT(output_filename));
if (result) {
return result;
@@ -666,6 +630,7 @@ enum BuildFlagKind {
BuildFlag_IgnoreWarnings,
BuildFlag_WarningsAsErrors,
BuildFlag_VerboseErrors,
BuildFlag_ErrorPosStyle,
// internal use only
BuildFlag_InternalIgnoreLazy,
@@ -829,6 +794,7 @@ bool parse_build_flags(Array<String> args) {
add_flag(&build_flags, BuildFlag_IgnoreWarnings, str_lit("ignore-warnings"), BuildFlagParam_None, Command_all);
add_flag(&build_flags, BuildFlag_WarningsAsErrors, str_lit("warnings-as-errors"), BuildFlagParam_None, Command_all);
add_flag(&build_flags, BuildFlag_VerboseErrors, str_lit("verbose-errors"), BuildFlagParam_None, Command_all);
add_flag(&build_flags, BuildFlag_ErrorPosStyle, str_lit("error-pos-style"), BuildFlagParam_String, Command_all);
add_flag(&build_flags, BuildFlag_InternalIgnoreLazy, str_lit("internal-ignore-lazy"), BuildFlagParam_None, Command_all);
@@ -1508,6 +1474,20 @@ bool parse_build_flags(Array<String> args) {
case BuildFlag_VerboseErrors:
build_context.show_error_line = true;
break;
case BuildFlag_ErrorPosStyle:
GB_ASSERT(value.kind == ExactValue_String);
if (str_eq_ignore_case(value.value_string, str_lit("odin")) || str_eq_ignore_case(value.value_string, str_lit("default"))) {
build_context.ODIN_ERROR_POS_STYLE = ErrorPosStyle_Default;
} else if (str_eq_ignore_case(value.value_string, str_lit("unix"))) {
build_context.ODIN_ERROR_POS_STYLE = ErrorPosStyle_Unix;
} else {
gb_printf_err("-error-pos-style options are 'unix', 'odin' and 'default' (odin)\n");
bad_flags = true;
}
break;
case BuildFlag_InternalIgnoreLazy:
build_context.ignore_lazy = true;
break;
@@ -1526,6 +1506,10 @@ bool parse_build_flags(Array<String> args) {
gb_printf_err("Invalid -resource path %.*s, missing .rc\n", LIT(path));
bad_flags = true;
break;
} else if (!gb_file_exists((const char *)path.text)) {
gb_printf_err("Invalid -resource path %.*s, file does not exist.\n", LIT(path));
bad_flags = true;
break;
}
build_context.resource_filepath = substring(path, 0, string_extension_position(path));
build_context.has_resource = true;
@@ -1540,6 +1524,11 @@ bool parse_build_flags(Array<String> args) {
String path = value.value_string;
path = string_trim_whitespace(path);
if (is_build_flag_path_valid(path)) {
if (path_is_directory(path)) {
gb_printf_err("Invalid -pdb-name path. %.*s, is a directory.\n", LIT(path));
bad_flags = true;
break;
}
// #if defined(GB_SYSTEM_WINDOWS)
// String ext = path_extension(path);
// if (ext != ".pdb") {
@@ -2666,6 +2655,8 @@ int main(int arg_count, char const **arg_ptr) {
return 1;
}
init_filename = copy_string(permanent_allocator(), init_filename);
if (init_filename == "-help" ||
init_filename == "--help") {
build_context.show_help = true;
@@ -2688,6 +2679,12 @@ int main(int arg_count, char const **arg_ptr) {
gb_printf_err("Did you mean `%.*s %.*s %.*s -file`?\n", LIT(args[0]), LIT(command), LIT(init_filename));
gb_printf_err("The `-file` flag tells it to treat a file as a self-contained package.\n");
return 1;
} else {
String const ext = str_lit(".odin");
if (!string_ends_with(init_filename, ext)) {
gb_printf_err("Expected either a directory or a .odin file, got '%.*s'\n", LIT(init_filename));
return 1;
}
}
}
}
@@ -2709,13 +2706,24 @@ int main(int arg_count, char const **arg_ptr) {
get_fullpath_relative(heap_allocator(), odin_root_dir(), str_lit("shared")));
}
init_build_context(selected_target_metrics ? selected_target_metrics->metrics : nullptr);
// if (build_context.word_size == 4 && build_context.metrics.os != TargetOs_js) {
// print_usage_line(0, "%.*s 32-bit is not yet supported for this platform", LIT(args[0]));
// return 1;
// }
// Set and check build paths...
if (!init_build_paths(init_filename)) {
return 1;
}
if (build_context.show_debug_messages) {
for_array(i, build_context.build_paths) {
String build_path = path_to_string(heap_allocator(), build_context.build_paths[i]);
debugf("build_paths[%ld]: %.*s\n", i, LIT(build_path));
}
}
init_global_thread_pool();
defer (thread_pool_destroy(&global_thread_pool));
@@ -2732,6 +2740,8 @@ int main(int arg_count, char const **arg_ptr) {
}
defer (destroy_parser(parser));
// TODO(jeroen): Remove the `init_filename` param.
// Let's put that on `build_context.build_paths[0]` instead.
if (parse_packages(parser, init_filename) != ParseFile_None) {
return 1;
}
@@ -2810,16 +2820,14 @@ int main(int arg_count, char const **arg_ptr) {
}
if (run_output) {
String exe_name = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_Output]);
defer (gb_free(heap_allocator(), exe_name.text));
#if defined(GB_SYSTEM_WINDOWS)
return system_exec_command_line_app("odin run", "%.*s.exe %.*s", LIT(gen->output_base), LIT(run_args_string));
return system_exec_command_line_app("odin run", "%.*s %.*s", LIT(exe_name), LIT(run_args_string));
#else
//NOTE(thebirk): This whole thing is a little leaky
String output_ext = {};
String complete_path = concatenate_strings(permanent_allocator(), gen->output_base, output_ext);
complete_path = path_to_full_path(permanent_allocator(), complete_path);
return system_exec_command_line_app("odin run", "\"%.*s\" %.*s", LIT(complete_path), LIT(run_args_string));
return system_exec_command_line_app("odin run", "\"%.*s\" %.*s", LIT(exe_name), LIT(run_args_string));
#endif
}
return 0;
}
+1 -1
View File
@@ -5751,7 +5751,7 @@ ParseFileError parse_packages(Parser *p, String init_filename) {
}
}
}
{ // Add these packages serially and then process them parallel
mutex_lock(&p->wait_mutex);
+394
View File
@@ -0,0 +1,394 @@
/*
Path handling utilities.
*/
String remove_extension_from_path(String const &s) {
for (isize i = s.len-1; i >= 0; i--) {
if (s[i] == '.') {
return substring(s, 0, i);
}
}
return s;
}
String remove_directory_from_path(String const &s) {
isize len = 0;
for (isize i = s.len-1; i >= 0; i--) {
if (s[i] == '/' ||
s[i] == '\\') {
break;
}
len += 1;
}
return substring(s, s.len-len, s.len);
}
bool path_is_directory(String path);
String directory_from_path(String const &s) {
if (path_is_directory(s)) {
return s;
}
isize i = s.len-1;
for (; i >= 0; i--) {
if (s[i] == '/' ||
s[i] == '\\') {
break;
}
}
if (i >= 0) {
return substring(s, 0, i);
}
return substring(s, 0, 0);
}
#if defined(GB_SYSTEM_WINDOWS)
bool path_is_directory(String path) {
gbAllocator a = heap_allocator();
String16 wstr = string_to_string16(a, path);
defer (gb_free(a, wstr.text));
i32 attribs = GetFileAttributesW(wstr.text);
if (attribs < 0) return false;
return (attribs & FILE_ATTRIBUTE_DIRECTORY) != 0;
}
#else
bool path_is_directory(String path) {
gbAllocator a = heap_allocator();
char *copy = cast(char *)copy_string(a, path).text;
defer (gb_free(a, copy));
struct stat s;
if (stat(copy, &s) == 0) {
return (s.st_mode & S_IFDIR) != 0;
}
return false;
}
#endif
String path_to_full_path(gbAllocator a, String path) {
gbAllocator ha = heap_allocator();
char *path_c = gb_alloc_str_len(ha, cast(char *)path.text, path.len);
defer (gb_free(ha, path_c));
char *fullpath = gb_path_get_full_name(a, path_c);
String res = string_trim_whitespace(make_string_c(fullpath));
#if defined(GB_SYSTEM_WINDOWS)
for (isize i = 0; i < res.len; i++) {
if (res.text[i] == '\\') {
res.text[i] = '/';
}
}
#endif
return copy_string(a, res);
}
struct Path {
String basename;
String name;
String ext;
};
// NOTE(Jeroen): Naively turns a Path into a string.
String path_to_string(gbAllocator a, Path path) {
if (path.basename.len + path.name.len + path.ext.len == 0) {
return make_string(nullptr, 0);
}
isize len = path.basename.len + 1 + path.name.len + 1;
if (path.ext.len > 0) {
len += path.ext.len + 1;
}
u8 *str = gb_alloc_array(a, u8, len);
isize i = 0;
gb_memmove(str+i, path.basename.text, path.basename.len); i += path.basename.len;
gb_memmove(str+i, "/", 1); i += 1;
gb_memmove(str+i, path.name.text, path.name.len); i += path.name.len;
if (path.ext.len > 0) {
gb_memmove(str+i, ".", 1); i += 1;
gb_memmove(str+i, path.ext.text, path.ext.len); i += path.ext.len;
}
str[i] = 0;
String res = make_string(str, i);
res = string_trim_whitespace(res);
return res;
}
// NOTE(Jeroen): Naively turns a Path into a string, then normalizes it using `path_to_full_path`.
String path_to_full_path(gbAllocator a, Path path) {
String temp = path_to_string(heap_allocator(), path);
defer (gb_free(heap_allocator(), temp.text));
return path_to_full_path(a, temp);
}
// NOTE(Jeroen): Takes a path like "odin" or "W:\Odin", turns it into a full path,
// and then breaks it into its components to make a Path.
Path path_from_string(gbAllocator a, String const &path) {
Path res = {};
if (path.len == 0) return res;
String fullpath = path_to_full_path(a, path);
defer (gb_free(heap_allocator(), fullpath.text));
res.basename = directory_from_path(fullpath);
res.basename = copy_string(a, res.basename);
if (path_is_directory(fullpath)) {
// It's a directory. We don't need to tinker with the name and extension.
// It could have a superfluous trailing `/`. Remove it if so.
if (res.basename.len > 0 && res.basename.text[res.basename.len - 1] == '/') {
res.basename.len--;
}
return res;
}
isize name_start = (res.basename.len > 0) ? res.basename.len + 1 : res.basename.len;
res.name = substring(fullpath, name_start, fullpath.len);
res.name = remove_extension_from_path(res.name);
res.name = copy_string(a, res.name);
res.ext = path_extension(fullpath, false); // false says not to include the dot.
res.ext = copy_string(a, res.ext);
return res;
}
// NOTE(Jeroen): Takes a path String and returns the last path element.
String last_path_element(String const &path) {
isize count = 0;
u8 * start = (u8 *)(&path.text[path.len - 1]);
for (isize length = path.len; length > 0 && path.text[length - 1] != '/'; length--) {
count++;
start--;
}
if (count > 0) {
start++; // Advance past the `/` and return the substring.
String res = make_string(start, count);
return res;
}
// Must be a root path like `/` or `C:/`, return empty String.
return STR_LIT("");
}
bool path_is_directory(Path path) {
String path_string = path_to_full_path(heap_allocator(), path);
defer (gb_free(heap_allocator(), path_string.text));
return path_is_directory(path_string);
}
struct FileInfo {
String name;
String fullpath;
i64 size;
bool is_dir;
};
enum ReadDirectoryError {
ReadDirectory_None,
ReadDirectory_InvalidPath,
ReadDirectory_NotExists,
ReadDirectory_Permission,
ReadDirectory_NotDir,
ReadDirectory_Empty,
ReadDirectory_Unknown,
ReadDirectory_COUNT,
};
i64 get_file_size(String path) {
char *c_str = alloc_cstring(heap_allocator(), path);
defer (gb_free(heap_allocator(), c_str));
gbFile f = {};
gbFileError err = gb_file_open(&f, c_str);
defer (gb_file_close(&f));
if (err != gbFileError_None) {
return -1;
}
return gb_file_size(&f);
}
#if defined(GB_SYSTEM_WINDOWS)
ReadDirectoryError read_directory(String path, Array<FileInfo> *fi) {
GB_ASSERT(fi != nullptr);
gbAllocator a = heap_allocator();
while (path.len > 0) {
Rune end = path[path.len-1];
if (end == '/') {
path.len -= 1;
} else if (end == '\\') {
path.len -= 1;
} else {
break;
}
}
if (path.len == 0) {
return ReadDirectory_InvalidPath;
}
{
char *c_str = alloc_cstring(a, path);
defer (gb_free(a, c_str));
gbFile f = {};
gbFileError file_err = gb_file_open(&f, c_str);
defer (gb_file_close(&f));
switch (file_err) {
case gbFileError_Invalid: return ReadDirectory_InvalidPath;
case gbFileError_NotExists: return ReadDirectory_NotExists;
// case gbFileError_Permission: return ReadDirectory_Permission;
}
}
if (!path_is_directory(path)) {
return ReadDirectory_NotDir;
}
char *new_path = gb_alloc_array(a, char, path.len+3);
defer (gb_free(a, new_path));
gb_memmove(new_path, path.text, path.len);
gb_memmove(new_path+path.len, "/*", 2);
new_path[path.len+2] = 0;
String np = make_string(cast(u8 *)new_path, path.len+2);
String16 wstr = string_to_string16(a, np);
defer (gb_free(a, wstr.text));
WIN32_FIND_DATAW file_data = {};
HANDLE find_file = FindFirstFileW(wstr.text, &file_data);
if (find_file == INVALID_HANDLE_VALUE) {
return ReadDirectory_Unknown;
}
defer (FindClose(find_file));
array_init(fi, a, 0, 100);
do {
wchar_t *filename_w = file_data.cFileName;
i64 size = cast(i64)file_data.nFileSizeLow;
size |= (cast(i64)file_data.nFileSizeHigh) << 32;
String name = string16_to_string(a, make_string16_c(filename_w));
if (name == "." || name == "..") {
gb_free(a, name.text);
continue;
}
String filepath = {};
filepath.len = path.len+1+name.len;
filepath.text = gb_alloc_array(a, u8, filepath.len+1);
defer (gb_free(a, filepath.text));
gb_memmove(filepath.text, path.text, path.len);
gb_memmove(filepath.text+path.len, "/", 1);
gb_memmove(filepath.text+path.len+1, name.text, name.len);
FileInfo info = {};
info.name = name;
info.fullpath = path_to_full_path(a, filepath);
info.size = size;
info.is_dir = (file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
array_add(fi, info);
} while (FindNextFileW(find_file, &file_data));
if (fi->count == 0) {
return ReadDirectory_Empty;
}
return ReadDirectory_None;
}
#elif defined(GB_SYSTEM_LINUX) || defined(GB_SYSTEM_OSX) || defined(GB_SYSTEM_FREEBSD) || defined(GB_SYSTEM_OPENBSD)
#include <dirent.h>
ReadDirectoryError read_directory(String path, Array<FileInfo> *fi) {
GB_ASSERT(fi != nullptr);
gbAllocator a = heap_allocator();
char *c_path = alloc_cstring(a, path);
defer (gb_free(a, c_path));
DIR *dir = opendir(c_path);
if (!dir) {
switch (errno) {
case ENOENT:
return ReadDirectory_NotExists;
case EACCES:
return ReadDirectory_Permission;
case ENOTDIR:
return ReadDirectory_NotDir;
default:
// ENOMEM: out of memory
// EMFILE: per-process limit on open fds reached
// ENFILE: system-wide limit on total open files reached
return ReadDirectory_Unknown;
}
GB_PANIC("unreachable");
}
array_init(fi, a, 0, 100);
for (;;) {
struct dirent *entry = readdir(dir);
if (entry == nullptr) {
break;
}
String name = make_string_c(entry->d_name);
if (name == "." || name == "..") {
continue;
}
String filepath = {};
filepath.len = path.len+1+name.len;
filepath.text = gb_alloc_array(a, u8, filepath.len+1);
defer (gb_free(a, filepath.text));
gb_memmove(filepath.text, path.text, path.len);
gb_memmove(filepath.text+path.len, "/", 1);
gb_memmove(filepath.text+path.len+1, name.text, name.len);
filepath.text[filepath.len] = 0;
struct stat dir_stat = {};
if (stat((char *)filepath.text, &dir_stat)) {
continue;
}
if (S_ISDIR(dir_stat.st_mode)) {
continue;
}
i64 size = dir_stat.st_size;
FileInfo info = {};
info.name = name;
info.fullpath = path_to_full_path(a, filepath);
info.size = size;
array_add(fi, info);
}
if (fi->count == 0) {
return ReadDirectory_Empty;
}
return ReadDirectory_None;
}
#else
#error Implement read_directory
#endif
+2 -35
View File
@@ -245,15 +245,14 @@ gb_inline isize string_extension_position(String const &str) {
return dot_pos;
}
String path_extension(String const &str) {
String path_extension(String const &str, bool include_dot = true) {
isize pos = string_extension_position(str);
if (pos < 0) {
return make_string(nullptr, 0);
}
return substring(str, pos, str.len);
return substring(str, include_dot ? pos : pos + 1, str.len);
}
String string_trim_whitespace(String str) {
while (str.len > 0 && rune_is_whitespace(str[str.len-1])) {
str.len--;
@@ -299,38 +298,6 @@ String filename_from_path(String s) {
return make_string(nullptr, 0);
}
String remove_extension_from_path(String const &s) {
for (isize i = s.len-1; i >= 0; i--) {
if (s[i] == '.') {
return substring(s, 0, i);
}
}
return s;
}
String remove_directory_from_path(String const &s) {
isize len = 0;
for (isize i = s.len-1; i >= 0; i--) {
if (s[i] == '/' ||
s[i] == '\\') {
break;
}
len += 1;
}
return substring(s, s.len-len, s.len);
}
String directory_from_path(String const &s) {
isize i = s.len-1;
for (; i >= 0; i--) {
if (s[i] == '/' ||
s[i] == '\\') {
break;
}
}
return substring(s, 0, i);
}
String concatenate_strings(gbAllocator a, String const &x, String const &y) {
isize len = x.len+y.len;
u8 *data = gb_alloc_array(a, u8, len+1);
+19 -15
View File
@@ -2,45 +2,49 @@ ODIN=../../odin
PYTHON=$(shell which python3)
all: download_test_assets image_test compress_test strings_test hash_test crypto_test noise_test encoding_test \
math_test linalg_glsl_math_test filepath_test reflect_test os_exit_test
math_test linalg_glsl_math_test filepath_test reflect_test os_exit_test i18n_test
download_test_assets:
$(PYTHON) download_assets.py
image_test:
$(ODIN) run image/test_core_image.odin -file
$(ODIN) run image/test_core_image.odin -file -out:test_core_image
compress_test:
$(ODIN) run compress/test_core_compress.odin -file
$(ODIN) run compress/test_core_compress.odin -file -out:test_core_compress
strings_test:
$(ODIN) run strings/test_core_strings.odin -file
$(ODIN) run strings/test_core_strings.odin -file -out:test_core_strings
hash_test:
$(ODIN) run hash -out=test_hash -o:speed -no-bounds-check
$(ODIN) run hash -o:speed -no-bounds-check -out:test_hash
crypto_test:
$(ODIN) run crypto -out=test_crypto_hash -o:speed -no-bounds-check
$(ODIN) run crypto -o:speed -no-bounds-check -out:test_crypto_hash
noise_test:
$(ODIN) run math/noise -out=test_noise
$(ODIN) run math/noise -out:test_noise
encoding_test:
$(ODIN) run encoding/hxa -out=test_hxa -collection:tests=..
$(ODIN) run encoding/json -out=test_json
$(ODIN) run encoding/varint -out=test_varint
$(ODIN) run encoding/hxa -out:test_hxa -collection:tests=..
$(ODIN) run encoding/json -out:test_json
$(ODIN) run encoding/varint -out:test_varint
$(ODIN) run encoding/xml -out:test_xml
math_test:
$(ODIN) run math/test_core_math.odin -out=test_core_math -file -collection:tests=..
$(ODIN) run math/test_core_math.odin -file -collection:tests=.. -out:test_core_math
linalg_glsl_math_test:
$(ODIN) run math/linalg/glsl/test_linalg_glsl_math.odin -file -out=test_linalg_glsl_math -collection:tests=..
$(ODIN) run math/linalg/glsl/test_linalg_glsl_math.odin -file -collection:tests=.. -out:test_linalg_glsl_math
filepath_test:
$(ODIN) run path/filepath/test_core_filepath.odin -file -out=test_core_filepath -collection:tests=..
$(ODIN) run path/filepath/test_core_filepath.odin -file -collection:tests=.. -out:test_core_filepath
reflect_test:
$(ODIN) run reflect/test_core_reflect.odin -file -out=test_core_reflect -collection:tests=..
$(ODIN) run reflect/test_core_reflect.odin -file -collection:tests=.. -out:test_core_reflect
os_exit_test:
$(ODIN) run os/test_core_os_exit.odin -file -out=test_core_os_exit && exit 1 || exit 0
$(ODIN) run os/test_core_os_exit.odin -file -out:test_core_os_exit && exit 1 || exit 0
i18n_test:
$(ODIN) run text/i18n -out:test_core_i18n
+22
View File
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="nl" sourcelanguage="en">
<context>
<name>Page</name>
<message>
<source>%d apple(s)</source>
<comment>commenting</comment>
<translation type="obsolete">Tekst om te vertalen</translation>
</message>
</context>
<context>
<name>apple_count</name>
<message numerus="yes">
<source>%d apple(s)</source>
<translation>
<numerusform>%d appel</numerusform>
<numerusform>%d appels</numerusform>
</translation>
</message>
</context>
</TS>
+30
View File
@@ -0,0 +1,30 @@
# Odin i18n Example
# Copyright (C) 2021 Jeroen van Rijn
# This file is distributed under the same license as the PACKAGE package.
# Jeroen van Rijn <Kelimion@users.noreply.github.com>, 2021.
#
#, fuzzy
msgid ""
msgstr "Project-Id-Version: Example 0.0.1\n"
"Report-Msgid-Bugs-To: Jeroen van Rijn <Kelimion@users.noreply.github.com>\n"
"POT-Creation-Date: 2021-11-27 19:23+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: en-GB\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: i18n_example.odin:28
msgid "There are 69,105 leaves here."
msgstr "Er zijn hier 69.105 bladeren."
#: i18n_example.odin:30
msgid "Hellope, World!"
msgstr "Hallo, Wereld!"
#: i18n_example.odin:36
msgid "There is %d leaf.\n"
msgid_plural "There are %d leaves.\n"
msgstr[0] "Er is %d blad.\n"
msgstr[1] "Er zijn %d bladeren.\n"
+35
View File
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="nl" sourcelanguage="en">
<context>
<name>Page</name>
<message>
<source>Text for translation</source>
<comment>commenting</comment>
<translation type="obsolete">Tekst om te vertalen</translation>
</message>
<message>
<source>Also text to translate</source>
<extracomment>some text</extracomment>
<translation>Ook tekst om te vertalen</translation>
</message>
</context>
<context>
<name>installscript</name>
<message>
<source>99 bottles of beer on the wall</source>
<oldcomment>some new comments here</oldcomment>
<translation>99 flessen bier op de muur</translation>
</message>
</context>
<context>
<name>apple_count</name>
<message numerus="yes">
<source>%d apple(s)</source>
<translation>
<numerusform>%d appel</numerusform>
<numerusform>%d appels</numerusform>
</translation>
</message>
</context>
</TS>
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file id="42" original="Foozle.xml" source-language="en" target-language="nl-NL" datatype="plaintext">
<body>
<trans-unit id="874396" maxwidth="20" size-unit="char">
<source>text</source>
<target state="translated">tekst</target>
<note>Context</note>
</trans-unit>
<trans-unit id="874397" approved="yes">
<source>text 1</source>
<target state="translated">tekst 1</target>
<note>Context 1</note>
</trans-unit>
<trans-unit id="874398">
<source>text 2</source>
<target state="needs-translation"/>
<context context-type="context">Context of the segment 2</context>
</trans-unit>
<trans-unit id="874399" translate="no">
<source>text 3</source>
<target state="final">translation 3</target>
<note>Context 3</note>
</trans-unit>
<group restype="x-gettext-plurals">
<note>Plurals</note>
<trans-unit id="14343743[0]">
<source>%d month</source>
<target xml:lang="nl" state="translated">%d maand</target>
</trans-unit>
<trans-unit id="14343743[1]">
<source>%d months</source>
<target xml:lang="nl" state="translated">%d maanden</target>
</trans-unit>
</group>
</body>
</file>
</xliff>
@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:2.0" version="2.0" srcLang="en" trgLang="nl">
<file id="f1">
<notes>
<note id="n1">Note for file</note>
</notes>
<unit id="u1">
<notes>
<note id="n1">Note for unit</note>
</notes>
<segment id="s1" state="initial">
<source>text</source>
<target></target>
</segment>
</unit>
<unit id="u2">
<notes>
<note id="n2">Note for unit 2</note>
</notes>
<segment id="s2" state="translated">
<source>text 2</source>
<target>translation 2</target>
</segment>
</unit>
<unit id="u3">
<notes>
<note id="n3">Note for unit 3</note>
</notes>
<segment id="s3" state="final">
<source>text 3</source>
<target>approved translation 3</target>
</segment>
</unit>
<group id="90290" type="x-gettext:plurals">
<unit id="90291" name="90290[0]">
<notes>
<note category="context">Plurals</note>
</notes>
<segment>
<source>%d month</source>
<target xml:lang="nl">%d maand</target>
</segment>
</unit>
<unit id="90292" name="90290[1]">
<segment>
<source>%d months</source>
<target xml:lang="nl">%d maanden</target>
</segment>
</unit>
</group>
</file>
</xliff>
Binary file not shown.
+33
View File
@@ -0,0 +1,33 @@
# Odin i18n Example
# Copyright (C) 2021 Jeroen van Rijn
# This file is distributed under the same license as the PACKAGE package.
# Jeroen van Rijn <Kelimion@users.noreply.github.com>, 2021.
#
msgid ""
msgstr ""
"Project-Id-Version: Example 0.0.1\n"
"Report-Msgid-Bugs-To: Jeroen van Rijn <Kelimion@users.noreply.github.com>\n"
"POT-Creation-Date: 2021-11-27 19:23+0100\n"
"PO-Revision-Date: 2021-11-28 02:56+0100\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language-Team: Odin Language Team\n"
"X-Generator: Poedit 3.0\n"
"Last-Translator: Jeroen van Rijn\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"Language: nl_NL\n"
#: i18n_example.odin:28
msgid "There are 69,105 leaves here."
msgstr "Er zijn hier 69.105 bladeren."
#: i18n_example.odin:30
msgid "Hellope, World!"
msgstr "Hallo, Wereld!"
#: i18n_example.odin:36
msgid "There is %d leaf.\n"
msgid_plural "There are %d leaves.\n"
msgstr[0] "Er is %d blad.\n"
msgstr[1] "Er zijn %d bladeren.\n"
+26
View File
@@ -0,0 +1,26 @@
Copyright (c) 2016-2021 Ginger Bill. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Binary file not shown.
+95
View File
@@ -0,0 +1,95 @@
<p align="center">
<img src="misc/logo-slim.png" alt="Odin logo" style="width:65%">
<br/>
The Data-Oriented Language for Sane Software Development.
<br/>
<br/>
<a href="https://github.com/odin-lang/odin/releases/latest">
<img src="https://img.shields.io/github/release/odin-lang/odin.svg">
</a>
<a href="https://github.com/odin-lang/odin/releases/latest">
<img src="https://img.shields.io/badge/platforms-Windows%20|%20Linux%20|%20macOS-green.svg">
</a>
<br>
<a href="https://discord.gg/odinlang">
<img src="https://img.shields.io/discord/568138951836172421?logo=discord">
</a>
<a href="https://github.com/odin-lang/odin/actions">
<img src="https://github.com/odin-lang/odin/workflows/CI/badge.svg?branch=master&event=push">
</a>
</p>
# The Odin Programming Language
Odin is a general-purpose programming language with distinct typing, built for high performance, modern systems, and built-in data-oriented data types. The Odin Programming Language, the C alternative for the joy of programming.
Website: [https://odin-lang.org/](https://odin-lang.org/)
```odin
package main
import "core:fmt"
main :: proc() {
program := "+ + * 😃 - /"
accumulator := 0
for token in program {
switch token {
case '+': accumulator += 1
case '-': accumulator -= 1
case '*': accumulator *= 2
case '/': accumulator /= 2
case '😃': accumulator *= accumulator
case: // Ignore everything else
}
}
fmt.printf("The program \"%s\" calculates the value %d\n",
program, accumulator)
}
```
## Documentation
#### [Getting Started](https://odin-lang.org/docs/install)
Instructions for downloading and installing the Odin compiler and libraries.
#### [Nightly Builds](https://odin-lang.org/docs/nightly/)
Get the latest nightly builds of Odin.
### Learning Odin
#### [Overview of Odin](https://odin-lang.org/docs/overview)
An overview of the Odin programming language.
#### [Frequently Asked Questions (FAQ)](https://odin-lang.org/docs/faq)
Answers to common questions about Odin.
#### [Packages](https://pkg.odin-lang.org/)
Documentation for all the official packages part of the [core](https://pkg.odin-lang.org/core/) and [vendor](https://pkg.odin-lang.org/vendor/) library collections.
#### [The Odin Wiki](https://github.com/odin-lang/Odin/wiki)
A wiki maintained by the Odin community.
#### [Odin Discord](https://discord.gg/sVBPHEv)
Get live support and talk with other odiners on the Odin Discord.
### Articles
#### [The Odin Blog](https://odin-lang.org/news/)
The official blog of the Odin programming language, featuring announcements, news, and in-depth articles by the Odin team and guests.
## Warnings
* The Odin compiler is still in development.
Binary file not shown.
+2
View File
@@ -0,0 +1,2 @@
# This file will be downloaded by download_assets.py
unicode.xml
+29
View File
@@ -0,0 +1,29 @@
<html>
<head>
<title>Entity Reference Test</title>
<style>
body {
background: #000; color: #eee;
width: 40%;
margin-left: auto;
margin-right: auto;
font-size: 14pt;
}
</style>
</head>
<body>
<h1>Entity Reference Test</h1>
<div id="test_cdata_in_comment" foo="">
Foozle]!&#32;&copy;&#x20;<!-- <![CDATA[&#32;&reg;&#x20;]]> -->42&;1234&
</div>
<!-- foo attribute should be empty but present -->
<!-- EXPECTED: Foozle]! © 42&;1234& -->
<div id="test_cdata_unwrap_and_passthrough">
Foozle]!&#32;&copy;&#x20;<![CDATA[BOX&#32;&reg;&#x20;/BOX]]>42&;1234&
</div>
<!-- EXPECTED: Foozle]! © BOX ® /BOX42&;1234& -->
<div>
&verbar; &vert; &VerticalLine; &fjlig; &grave; &bsol; &reg; &rhov; &CounterClockwiseContourIntegral; &bsemi;
</div>
</body>
</html>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE 恥ずべきフクロウ>
<恥ずべきフクロウ 올빼미_id="Foozle&#32;<![CDATA[<greeting>Hello, world!"</greeting>]]>Barzle">
<부끄러운:barzle>
<name foo:bar="birmese">ရှက်စရာ ဇီးကွက်</name>
<nickname>Owl of Shame</nickname>
<data>More CDATA <![CDATA[<greeting>Hello, world!</greeting><![CDATA] <$]]> Nonsense.</data>
</부끄러운:barzle>
+22 -15
View File
@@ -1,65 +1,72 @@
@echo off
set COMMON=-show-timings -no-bounds-check -vet -strict-style -collection:tests=..
set COMMON=-no-bounds-check -vet -strict-style
set COLLECTION=-collection:tests=..
set PATH_TO_ODIN==..\..\odin
python3 download_assets.py
echo ---
echo Running core:image tests
echo ---
%PATH_TO_ODIN% run image %COMMON%
%PATH_TO_ODIN% run image %COMMON% -out:test_core_image.exe
echo ---
echo Running core:compress tests
echo ---
%PATH_TO_ODIN% run compress %COMMON%
%PATH_TO_ODIN% run compress %COMMON% -out:test_core_compress.exe
echo ---
echo Running core:strings tests
echo ---
%PATH_TO_ODIN% run strings %COMMON%
%PATH_TO_ODIN% run strings %COMMON% -out:test_core_strings.exe
echo ---
echo Running core:hash tests
echo ---
%PATH_TO_ODIN% run hash %COMMON% -o:size
%PATH_TO_ODIN% run hash %COMMON% -o:size -out:test_core_hash.exe
echo ---
echo Running core:odin tests
echo ---
%PATH_TO_ODIN% run odin %COMMON% -o:size
%PATH_TO_ODIN% run odin %COMMON% -o:size -out:test_core_odin.exe
echo ---
echo Running core:crypto hash tests
echo ---
%PATH_TO_ODIN% run crypto %COMMON%
%PATH_TO_ODIN% run crypto %COMMON% -out:test_crypto_hash.exe
echo ---
echo Running core:encoding tests
echo ---
%PATH_TO_ODIN% run encoding/hxa %COMMON%
%PATH_TO_ODIN% run encoding/json %COMMON%
%PATH_TO_ODIN% run encoding/varint %COMMON%
%PATH_TO_ODIN% run encoding/hxa %COMMON% %COLLECTION% -out:test_hxa.exe
%PATH_TO_ODIN% run encoding/json %COMMON% -out:test_json.exe
%PATH_TO_ODIN% run encoding/varint %COMMON% -out:test_varint.exe
%PATH_TO_ODIN% run encoding/xml %COMMON% -out:test_xml.exe
echo ---
echo Running core:math/noise tests
echo ---
%PATH_TO_ODIN% run math/noise %COMMON%
%PATH_TO_ODIN% run math/noise %COMMON% -out:test_noise.exe
echo ---
echo Running core:math tests
echo ---
%PATH_TO_ODIN% run math %COMMON%
%PATH_TO_ODIN% run math %COMMON% %COLLECTION% -out:test_core_math.exe
echo ---
echo Running core:math/linalg/glsl tests
echo ---
%PATH_TO_ODIN% run math/linalg/glsl %COMMON%
%PATH_TO_ODIN% run math/linalg/glsl %COMMON% %COLLECTION% -out:test_linalg_glsl.exe
echo ---
echo Running core:path/filepath tests
echo ---
%PATH_TO_ODIN% run path/filepath %COMMON%
%PATH_TO_ODIN% run path/filepath %COMMON% %COLLECTION% -out:test_core_filepath.exe
echo ---
echo Running core:reflect tests
echo ---
%PATH_TO_ODIN% run reflect %COMMON%
%PATH_TO_ODIN% run reflect %COMMON% %COLLECTION% -out:test_core_reflect.exe
echo ---
echo Running core:text/i18n tests
echo ---
%PATH_TO_ODIN% run text\i18n %COMMON% -out:test_core_i18n.exe
+56 -1
View File
@@ -7,13 +7,14 @@ package test_core_compress
List of contributors:
Jeroen van Rijn: Initial implementation.
A test suite for ZLIB, GZIP.
A test suite for ZLIB, GZIP and Shoco.
*/
import "core:testing"
import "core:compress/zlib"
import "core:compress/gzip"
import "core:compress/shoco"
import "core:bytes"
import "core:fmt"
@@ -48,6 +49,7 @@ main :: proc() {
t := testing.T{w=w}
zlib_test(&t)
gzip_test(&t)
shoco_test(&t)
fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
if TEST_fail > 0 {
@@ -134,3 +136,56 @@ gzip_test :: proc(t: ^testing.T) {
expect(t, false, error)
}
}
@test
shoco_test :: proc(t: ^testing.T) {
Shoco_Tests :: []struct{
compressed: []u8,
raw: []u8,
short_pack: int,
short_sentinel: int,
}{
{ #load("../assets/Shoco/README.md.shoco"), #load("../assets/Shoco/README.md"), 10, 1006 },
{ #load("../assets/Shoco/LICENSE.shoco"), #load("../assets/Shoco/LICENSE"), 25, 68 },
}
for v in Shoco_Tests {
expected_raw := len(v.raw)
expected_compressed := len(v.compressed)
biggest_unpacked := shoco.decompress_bound(expected_compressed)
biggest_packed := shoco.compress_bound(expected_raw)
buffer := make([]u8, max(biggest_packed, biggest_unpacked))
defer delete(buffer)
size, err := shoco.decompress(v.compressed, buffer[:])
msg := fmt.tprintf("Expected `decompress` to return `nil`, got %v", err)
expect(t, err == nil, msg)
msg = fmt.tprintf("Decompressed %v bytes into %v. Expected to decompress into %v bytes.", len(v.compressed), size, expected_raw)
expect(t, size == expected_raw, msg)
expect(t, string(buffer[:size]) == string(v.raw), "Decompressed contents don't match.")
size, err = shoco.compress(string(v.raw), buffer[:])
expect(t, err == nil, "Expected `compress` to return `nil`.")
msg = fmt.tprintf("Compressed %v bytes into %v. Expected to compress into %v bytes.", expected_raw, size, expected_compressed)
expect(t, size == expected_compressed, msg)
size, err = shoco.decompress(v.compressed, buffer[:expected_raw - 10])
msg = fmt.tprintf("Decompressing into too small a buffer returned %v, expected `.Output_Too_Short`", err)
expect(t, err == .Output_Too_Short, msg)
size, err = shoco.compress(string(v.raw), buffer[:expected_compressed - 10])
msg = fmt.tprintf("Compressing into too small a buffer returned %v, expected `.Output_Too_Short`", err)
expect(t, err == .Output_Too_Short, msg)
size, err = shoco.decompress(v.compressed[:v.short_pack], buffer[:])
expect(t, err == .Stream_Too_Short, "Expected `decompress` to return `Stream_Too_Short` because there was no more data after selecting a pack.")
size, err = shoco.decompress(v.compressed[:v.short_sentinel], buffer[:])
expect(t, err == .Stream_Too_Short, "Expected `decompress` to return `Stream_Too_Short` because there was no more data after non-ASCII sentinel.")
}
}
+18 -15
View File
@@ -5,8 +5,9 @@ import sys
import os
import zipfile
TEST_SUITES = ['PNG', 'XML']
DOWNLOAD_BASE_PATH = "assets/{}"
ASSETS_BASE_URL = "https://raw.githubusercontent.com/Kelimion/compress-odin/master/tests/assets/{}/{}"
ASSETS_BASE_URL = "https://raw.githubusercontent.com/odin-lang/test-assets/master/{}/{}"
PNG_IMAGES = [
"basi0g01.png", "basi0g02.png", "basi0g04.png", "basi0g08.png", "basi0g16.png", "basi2c08.png",
"basi2c16.png", "basi3p01.png", "basi3p02.png", "basi3p04.png", "basi3p08.png", "basi4a08.png",
@@ -73,25 +74,27 @@ def try_download_and_unpack_zip(suite):
print("Could not extract ZIP file")
return 2
def main():
print("Downloading PNG assets")
for suite in TEST_SUITES:
print("Downloading {} assets".format(suite))
# Make PNG assets path
try:
path = DOWNLOAD_BASE_PATH.format("PNG")
os.makedirs(path)
except FileExistsError:
pass
# Make assets path
try:
path = DOWNLOAD_BASE_PATH.format(suite)
os.makedirs(path)
except FileExistsError:
pass
# Try downloading and unpacking the assets
r = try_download_and_unpack_zip(suite)
if r is not None:
return r
# We could fall back on downloading the PNG files individually, but it's slow
print("Done downloading {} assets.".format(suite))
# Try downloading and unpacking the PNG assets
r = try_download_and_unpack_zip("PNG")
if r is not None:
return r
# We could fall back on downloading the PNG files individually, but it's slow
print("Done downloading PNG assets")
return 0
if __name__ == '__main__':
+258 -3
View File
@@ -31,6 +31,7 @@ main :: proc() {
parse_json(&t)
marshal_json(&t)
unmarshal_json(&t)
fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
if TEST_fail > 0 {
@@ -70,7 +71,8 @@ parse_json :: proc(t: ^testing.T) {
_, err := json.parse(transmute([]u8)json_data)
expect(t, err == .None, "expected json error to be none")
msg := fmt.tprintf("Expected `json.parse` to return nil, got %v", err)
expect(t, err == nil, msg)
}
@test
@@ -87,6 +89,259 @@ marshal_json :: proc(t: ^testing.T) {
}
_, err := json.marshal(my_struct)
expect(t, err == nil, "expected json error to be none")
msg := fmt.tprintf("Expected `json.marshal` to return nil, got %v", err)
expect(t, err == nil, msg)
}
PRODUCTS := `
{
"cash": "0",
"products": [
{
"name": "Cog\nCola",
"cost": "3",
"owned": "1",
"profit": "4",
"seconds": 3,
"multiplier": 1,
"auto_click": false
},
{
"name": "gingerBeer",
"cost": "9",
"owned": "0",
"profit": "16",
"seconds": 5,
"multiplier": 1,
"auto_click": false
},
{
"name": "Coffee",
"cost": "27",
"owned": "0",
"profit": "64",
"seconds": 7,
"multiplier": 1,
"auto_click": false
},
{
"name": "Haggis",
"cost": "81",
"owned": "0",
"profit": "256",
"seconds": 11,
"multiplier": 1,
"auto_click": false
},
{
"name": "Lasagna",
"cost": "243",
"owned": "0",
"profit": "1024",
"seconds": 13,
"multiplier": 1,
"auto_click": false
},
{
"name": "Asparagus",
"cost": "729",
"owned": "0",
"profit": "4096",
"seconds": 17,
"multiplier": 1,
"auto_click": false
},
{
"name": "Yorkshire Pudding",
"cost": "2187",
"owned": "0",
"profit": "16384",
"seconds": 19,
"multiplier": 1,
"auto_click": false
},
{
"name": "Salmon Wrap",
"cost": "6561",
"owned": "0",
"profit": "65536",
"seconds": 23,
"multiplier": 1,
"auto_click": false
},
{
"name": "Poke Bowl",
"cost": "19683",
"owned": "0",
"profit": "262144",
"seconds": 29,
"multiplier": 1,
"auto_click": false
},
{
"name": "Chili Con Carne",
"cost": "59049",
"owned": "0",
"profit": "1048576",
"seconds": 59,
"multiplier": 1,
"auto_click": false
},
],
}
`
original_data := Game_Marshal{
cash = "0",
products = {
{
name = "Cog\nCola",
cost = "3",
owned = "1",
profit = "4",
seconds = 3,
multiplier = 1,
auto_click = false,
},
{
name = "gingerBeer",
cost = "9",
owned = "0",
profit = "16",
seconds = 5,
multiplier = 1,
auto_click = false,
},
{
name = "Coffee",
cost = "27",
owned = "0",
profit = "64",
seconds = 7,
multiplier = 1,
auto_click = false,
},
{
name = "Haggis",
cost = "81",
owned = "0",
profit = "256",
seconds = 11,
multiplier = 1,
auto_click = false,
},
{
name = "Lasagna",
cost = "243",
owned = "0",
profit = "1024",
seconds = 13,
multiplier = 1,
auto_click = false,
},
{
name = "Asparagus",
cost = "729",
owned = "0",
profit = "4096",
seconds = 17,
multiplier = 1,
auto_click = false,
},
{
name = "Yorkshire Pudding",
cost = "2187",
owned = "0",
profit = "16384",
seconds = 19,
multiplier = 1,
auto_click = false,
},
{
name = "Salmon Wrap",
cost = "6561",
owned = "0",
profit = "65536",
seconds = 23,
multiplier = 1,
auto_click = false,
},
{
name = "Poke Bowl",
cost = "19683",
owned = "0",
profit = "262144",
seconds = 29,
multiplier = 1,
auto_click = false,
},
{
name = "Chili Con Carne",
cost = "59049",
owned = "0",
profit = "1048576",
seconds = 59,
multiplier = 1,
auto_click = false,
},
},
}
Product_Marshal :: struct {
name: cstring,
owned: string,
cost: string,
profit: string,
seconds: int,
multiplier: int,
auto_click: bool,
}
Game_Marshal :: struct {
cash: string,
products: []Product_Marshal,
}
cleanup :: proc(g: Game_Marshal) {
for p in g.products {
delete(p.name)
delete(p.owned)
delete(p.cost)
delete(p.profit)
}
delete(g.products)
delete(g.cash)
}
@test
unmarshal_json :: proc(t: ^testing.T) {
g: Game_Marshal
err := json.unmarshal(transmute([]u8)PRODUCTS, &g, json.DEFAULT_SPECIFICATION)
defer cleanup(g)
msg := fmt.tprintf("Expected `json.unmarshal` to return nil, got %v", err)
expect(t, err == nil, msg)
msg = fmt.tprintf("Expected %v products to have been unmarshaled, got %v", len(original_data.products), len(g.products))
expect(t, len(g.products) == len(original_data.products), msg)
msg = fmt.tprintf("Expected cash to have been unmarshaled as %v, got %v", original_data.cash, g.cash)
expect(t, original_data.cash == g.cash, msg)
for p, i in g.products {
expect(t, p == original_data.products[i], "Producted unmarshaled improperly")
}
}
+342
View File
@@ -0,0 +1,342 @@
package test_core_xml
import "core:encoding/xml"
import "core:testing"
import "core:mem"
import "core:strings"
import "core:io"
import "core:fmt"
import "core:hash"
Silent :: proc(pos: xml.Pos, format: string, args: ..any) {}
OPTIONS :: xml.Options{ flags = { .Ignore_Unsupported, .Intern_Comments, },
expected_doctype = "",
}
TEST_count := 0
TEST_fail := 0
TEST :: struct {
filename: string,
options: xml.Options,
err: xml.Error,
crc32: u32,
}
/*
Relative to ODIN_ROOT
*/
TEST_FILE_PATH_PREFIX :: "tests/core/assets"
TESTS :: []TEST{
/*
First we test that certain files parse without error.
*/
{
/*
Tests UTF-8 idents and values.
Test namespaced ident.
Tests that nested partial CDATA start doesn't trip up parser.
*/
filename = "XML/utf8.xml",
options = {
flags = {
.Ignore_Unsupported, .Intern_Comments,
},
expected_doctype = "恥ずべきフクロウ",
},
crc32 = 0x30d82264,
},
{
/*
Same as above.
Unbox CDATA in data tag.
*/
filename = "XML/utf8.xml",
options = {
flags = {
.Ignore_Unsupported, .Intern_Comments, .Unbox_CDATA,
},
expected_doctype = "恥ずべきフクロウ",
},
crc32 = 0xad31d8e8,
},
{
/*
Simple Qt TS translation file.
`core:i18n` requires it to be parsed properly.
*/
filename = "I18N/nl_NL-qt-ts.ts",
options = {
flags = {
.Ignore_Unsupported, .Intern_Comments, .Unbox_CDATA, .Decode_SGML_Entities,
},
expected_doctype = "TS",
},
crc32 = 0x7bce2630,
},
{
/*
Simple XLiff 1.2 file.
`core:i18n` requires it to be parsed properly.
*/
filename = "I18N/nl_NL-xliff-1.2.xliff",
options = {
flags = {
.Ignore_Unsupported, .Intern_Comments, .Unbox_CDATA, .Decode_SGML_Entities,
},
expected_doctype = "xliff",
},
crc32 = 0x43f19d61,
},
{
/*
Simple XLiff 2.0 file.
`core:i18n` requires it to be parsed properly.
*/
filename = "I18N/nl_NL-xliff-2.0.xliff",
options = {
flags = {
.Ignore_Unsupported, .Intern_Comments, .Unbox_CDATA, .Decode_SGML_Entities,
},
expected_doctype = "xliff",
},
crc32 = 0x961e7635,
},
{
filename = "XML/entities.html",
options = {
flags = {
.Ignore_Unsupported, .Intern_Comments,
},
expected_doctype = "html",
},
crc32 = 0x573c1033,
},
{
filename = "XML/entities.html",
options = {
flags = {
.Ignore_Unsupported, .Intern_Comments, .Unbox_CDATA,
},
expected_doctype = "html",
},
crc32 = 0x82588917,
},
{
filename = "XML/entities.html",
options = {
flags = {
.Ignore_Unsupported, .Intern_Comments, .Unbox_CDATA, .Decode_SGML_Entities,
},
expected_doctype = "html",
},
crc32 = 0x5e74d8a6,
},
/*
Then we test that certain errors are returned as expected.
*/
{
filename = "XML/utf8.xml",
options = {
flags = {
.Ignore_Unsupported, .Intern_Comments,
},
expected_doctype = "Odin",
},
err = .Invalid_DocType,
crc32 = 0x49b83d0a,
},
/*
Parse the 8.2 MiB unicode.xml for good measure.
*/
{
filename = "XML/unicode.xml",
options = {
flags = {
.Ignore_Unsupported,
},
expected_doctype = "",
},
err = .None,
crc32 = 0xcaa042b9,
},
}
when ODIN_TEST {
expect :: testing.expect
log :: testing.log
} else {
expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
TEST_count += 1
if !condition {
TEST_fail += 1
fmt.printf("[%v] %v\n", loc, message)
return
}
}
log :: proc(t: ^testing.T, v: any, loc := #caller_location) {
fmt.printf("[%v] LOG:\n\t%v\n", loc, v)
}
}
test_file_path :: proc(filename: string) -> (path: string) {
path = fmt.tprintf("%v%v/%v", ODIN_ROOT, TEST_FILE_PATH_PREFIX, filename)
temp := transmute([]u8)path
for r, i in path {
if r == '\\' {
temp[i] = '/'
}
}
return path
}
doc_to_string :: proc(doc: ^xml.Document) -> (result: string) {
/*
Effectively a clone of the debug printer in the xml package.
We duplicate it here so that the way it prints an XML document to a string is stable.
This way we can hash the output. If it changes, it means that the document or how it was parsed changed,
not how it was printed. One less source of variability.
*/
print :: proc(writer: io.Writer, doc: ^xml.Document) -> (written: int, err: io.Error) {
if doc == nil { return }
using fmt
written += wprintf(writer, "[XML Prolog]\n")
for attr in doc.prolog {
written += wprintf(writer, "\t%v: %v\n", attr.key, attr.val)
}
written += wprintf(writer, "[Encoding] %v\n", doc.encoding)
if len(doc.doctype.ident) > 0 {
written += wprintf(writer, "[DOCTYPE] %v\n", doc.doctype.ident)
if len(doc.doctype.rest) > 0 {
wprintf(writer, "\t%v\n", doc.doctype.rest)
}
}
for comment in doc.comments {
written += wprintf(writer, "[Pre-root comment] %v\n", comment)
}
if doc.element_count > 0 {
wprintln(writer, " --- ")
print_element(writer, doc, 0)
wprintln(writer, " --- ")
}
return written, .None
}
print_element :: proc(writer: io.Writer, doc: ^xml.Document, element_id: xml.Element_ID, indent := 0) -> (written: int, err: io.Error) {
using fmt
tab :: proc(writer: io.Writer, indent: int) {
for _ in 0..=indent {
wprintf(writer, "\t")
}
}
tab(writer, indent)
element := doc.elements[element_id]
if element.kind == .Element {
wprintf(writer, "<%v>\n", element.ident)
if len(element.value) > 0 {
tab(writer, indent + 1)
wprintf(writer, "[Value] %v\n", element.value)
}
for attr in element.attribs {
tab(writer, indent + 1)
wprintf(writer, "[Attr] %v: %v\n", attr.key, attr.val)
}
for child in element.children {
print_element(writer, doc, child, indent + 1)
}
} else if element.kind == .Comment {
wprintf(writer, "[COMMENT] %v\n", element.value)
}
return written, .None
}
buf: strings.Builder
defer strings.destroy_builder(&buf)
print(strings.to_writer(&buf), doc)
return strings.clone(strings.to_string(buf))
}
@test
run_tests :: proc(t: ^testing.T) {
using fmt
for test in TESTS {
path := test_file_path(test.filename)
log(t, fmt.tprintf("Trying to parse %v", path))
doc, err := xml.parse(path, test.options, Silent)
defer xml.destroy(doc)
tree_string := doc_to_string(doc)
tree_bytes := transmute([]u8)tree_string
defer delete(tree_bytes)
crc32 := hash.crc32(tree_bytes)
failed := err != test.err
err_msg := tprintf("Expected return value %v, got %v", test.err, err)
expect(t, err == test.err, err_msg)
failed |= crc32 != test.crc32
err_msg = tprintf("Expected CRC 0x%08x, got 0x%08x, with options %v", test.crc32, crc32, test.options)
expect(t, crc32 == test.crc32, err_msg)
if failed {
/*
Don't fully print big trees.
*/
tree_string = tree_string[:min(2_048, len(tree_string))]
println(tree_string)
}
}
}
main :: proc() {
t := testing.T{}
track: mem.Tracking_Allocator
mem.tracking_allocator_init(&track, context.allocator)
context.allocator = mem.tracking_allocator(&track)
run_tests(&t)
if len(track.allocation_map) > 0 {
for _, v in track.allocation_map {
err_msg := fmt.tprintf("%v Leaked %v bytes.", v.location, v.size)
expect(&t, false, err_msg)
}
}
fmt.printf("\n%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
}
+1 -1
View File
@@ -4,7 +4,7 @@ set PATH_TO_ODIN==..\..\..\..\odin
set TEST_ARGS=-fast-tests
set TEST_ARGS=-no-random
set TEST_ARGS=
set OUT_NAME=math_big_test_library
set OUT_NAME=math_big_test_library.dll
set COMMON=-build-mode:shared -show-timings -no-bounds-check -define:MATH_BIG_EXE=false -vet -strict-style
echo ---
echo Running core:math/big tests
@@ -0,0 +1,165 @@
package test_core_text_i18n
import "core:mem"
import "core:fmt"
import "core:os"
import "core:testing"
import "core:text/i18n"
TEST_count := 0
TEST_fail := 0
when ODIN_TEST {
expect :: testing.expect
log :: testing.log
} else {
expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
TEST_count += 1
if !condition {
TEST_fail += 1
fmt.printf("[%v] %v\n", loc, message)
return
}
}
log :: proc(t: ^testing.T, v: any, loc := #caller_location) {
fmt.printf("[%v] ", loc)
fmt.printf("log: %v\n", v)
}
}
T :: i18n.get
Test :: struct {
section: string,
key: string,
val: string,
n: int,
}
Test_Suite :: struct {
file: string,
loader: proc(string, i18n.Parse_Options, proc(int) -> int, mem.Allocator) -> (^i18n.Translation, i18n.Error),
err: i18n.Error,
options: i18n.Parse_Options,
tests: []Test,
}
TESTS := []Test_Suite{
{
file = "assets/I18N/nl_NL.mo",
loader = i18n.parse_mo_file,
tests = {
// These are in the catalog.
{ "", "There are 69,105 leaves here.", "Er zijn hier 69.105 bladeren.", 1 },
{ "", "Hellope, World!", "Hallo, Wereld!", 1 },
{ "", "There is %d leaf.\n", "Er is %d blad.\n", 1 },
{ "", "There are %d leaves.\n", "Er is %d blad.\n", 1 },
{ "", "There is %d leaf.\n", "Er zijn %d bladeren.\n", 42 },
{ "", "There are %d leaves.\n", "Er zijn %d bladeren.\n", 42 },
// This isn't in the catalog, so should ruturn the key.
{ "", "Come visit us on Discord!", "Come visit us on Discord!", 1 },
},
},
// QT Linguist with default loader options.
{
file = "assets/I18N/nl_NL-qt-ts.ts",
loader = i18n.parse_qt_linguist_file,
tests = {
// These are in the catalog.
{ "Page", "Text for translation", "Tekst om te vertalen", 1},
{ "Page", "Also text to translate", "Ook tekst om te vertalen", 1},
{ "installscript", "99 bottles of beer on the wall", "99 flessen bier op de muur", 1},
{ "apple_count", "%d apple(s)", "%d appel", 1},
{ "apple_count", "%d apple(s)", "%d appels", 42},
// These aren't in the catalog, so should ruturn the key.
{ "", "Come visit us on Discord!", "Come visit us on Discord!", 1 },
{ "Fake_Section", "Come visit us on Discord!", "Come visit us on Discord!", 1 },
},
},
// QT Linguist, merging sections.
{
file = "assets/I18N/nl_NL-qt-ts.ts",
loader = i18n.parse_qt_linguist_file,
options = {merge_sections = true},
tests = {
// All of them are now in section "", lookup with original section should return the key.
{ "", "Text for translation", "Tekst om te vertalen", 1},
{ "", "Also text to translate", "Ook tekst om te vertalen", 1},
{ "", "99 bottles of beer on the wall", "99 flessen bier op de muur", 1},
{ "", "%d apple(s)", "%d appel", 1},
{ "", "%d apple(s)", "%d appels", 42},
// All of them are now in section "", lookup with original section should return the key.
{ "Page", "Text for translation", "Text for translation", 1},
{ "Page", "Also text to translate", "Also text to translate", 1},
{ "installscript", "99 bottles of beer on the wall", "99 bottles of beer on the wall", 1},
{ "apple_count", "%d apple(s)", "%d apple(s)", 1},
{ "apple_count", "%d apple(s)", "%d apple(s)", 42},
},
},
// QT Linguist, merging sections. Expecting .Duplicate_Key error because same key exists in more than 1 section.
{
file = "assets/I18N/duplicate-key.ts",
loader = i18n.parse_qt_linguist_file,
options = {merge_sections = true},
err = .Duplicate_Key,
},
// QT Linguist, not merging sections. Shouldn't return error despite same key existing in more than 1 section.
{
file = "assets/I18N/duplicate-key.ts",
loader = i18n.parse_qt_linguist_file,
},
}
@test
tests :: proc(t: ^testing.T) {
using fmt
cat: ^i18n.Translation
err: i18n.Error
for suite in TESTS {
cat, err = suite.loader(suite.file, suite.options, nil, context.allocator)
msg := fmt.tprintf("Expected loading %v to return %v, got %v", suite.file, suite.err, err)
expect(t, err == suite.err, msg)
if err == .None {
for test in suite.tests {
val := T(test.section, test.key, test.n, cat)
msg = fmt.tprintf("Expected key `%v` from section `%v`'s form for value `%v` to equal `%v`, got `%v`", test.key, test.section, test.n, test.val, val)
expect(t, val == test.val, msg)
}
}
i18n.destroy(cat)
}
}
main :: proc() {
using fmt
track: mem.Tracking_Allocator
mem.tracking_allocator_init(&track, context.allocator)
context.allocator = mem.tracking_allocator(&track)
t := testing.T{}
tests(&t)
fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
if TEST_fail > 0 {
os.exit(1)
}
if len(track.allocation_map) > 0 {
println()
for _, v in track.allocation_map {
printf("%v Leaked %v bytes.\n", v.location, v.size)
}
}
}
+7 -7
View File
@@ -1,17 +1,17 @@
@echo off
if not exist "tests\issues\build\" mkdir tests\issues\build
if not exist "build\" mkdir build
set COMMON=-collection:tests=tests -out:tests\issues\build\test_issue
set COMMON=-collection:tests=.. -out:build\test_issue.exe
@echo on
.\odin build tests\issues\test_issue_829.odin %COMMON% -file
tests\issues\build\test_issue
..\..\odin build test_issue_829.odin %COMMON% -file
build\test_issue
.\odin build tests\issues\test_issue_1592.odin %COMMON% -file
tests\issues\build\test_issue
..\..\odin build test_issue_1592.odin %COMMON% -file
build\test_issue
@echo off
rmdir /S /Q tests\issues\build
rmdir /S /Q build
+8 -8
View File
@@ -1,18 +1,18 @@
#!/bin/bash
set -eu
mkdir -p tests/issues/build
COMMON="-collection:tests=tests -out:tests/issues/build/test_issue"
mkdir -p build
ODIN=../../odin
COMMON="-collection:tests=.. -out:build/test_issue"
set -x
./odin build tests/issues/test_issue_829.odin $COMMON -file
tests/issues/build/test_issue
$ODIN build test_issue_829.odin $COMMON -file
./build/test_issue
./odin build tests/issues/test_issue_1592.odin $COMMON -file
tests/issues/build/test_issue
$ODIN build test_issue_1592.odin $COMMON -file
./build/test_issue
set +x
rm -rf tests/issues/build
rm -rf build
+1 -1
View File
@@ -10,4 +10,4 @@ endif
all: botan_test
botan_test:
$(ODIN) run botan -out=botan_hash -o:speed -no-bounds-check $(ODINFLAGS)
$(ODIN) run botan -o:speed -no-bounds-check $(ODINFLAGS) -out=vendor_botan
+2 -2
View File
@@ -5,9 +5,9 @@ set PATH_TO_ODIN==..\..\odin
echo ---
echo Running vendor:botan tests
echo ---
%PATH_TO_ODIN% run botan %COMMON%
%PATH_TO_ODIN% run botan %COMMON% -out:vendor_botan.exe
echo ---
echo Running vendor:glfw tests
echo ---
%PATH_TO_ODIN% run glfw %COMMON%
%PATH_TO_ODIN% run glfw %COMMON% -out:vendor_glfw.exe
+134 -71
View File
@@ -189,7 +189,8 @@ SRV_DIMENSION :: enum i32 {
PFN_DESTRUCTION_CALLBACK :: #type proc "c" (a0: rawptr)
ID3DDestructionNotifier_UUID :: "a06eb39a-50da-425b-8c31-4eecd6c270f3"
ID3DDestructionNotifier_UUID_STRING :: "a06eb39a-50da-425b-8c31-4eecd6c270f3"
ID3DDestructionNotifier_UUID := &IID{0xa06eb39a, 0x50da, 0x425b, {0x8c, 0x31, 0x4e, 0xec, 0xd6, 0xc2, 0x70, 0xf3}}
ID3DDestructionNotifier :: struct #raw_union {
#subtype iunknown: IUnknown,
using id3ddestructionnotifier_vtable: ^ID3DDestructionNotifier_VTable,
@@ -658,7 +659,8 @@ RASTERIZER_DESC :: struct {
}
IObject_UUID :: "c4fec28f-7966-4e95-9f94-f431cb56c3b8"
IObject_UUID_STRING :: "c4fec28f-7966-4e95-9f94-f431cb56c3b8"
IObject_UUID := &IID{0xc4fec28f, 0x7966, 0x4e95, {0x9f, 0x94, 0xf4, 0x31, 0xcb, 0x56, 0xc3, 0xb8}}
IObject :: struct #raw_union {
#subtype iunknown: IUnknown,
using id3d12object_vtable: ^IObject_VTable,
@@ -672,7 +674,8 @@ IObject_VTable :: struct {
}
IDeviceChild_UUID :: "905db94b-a00c-4140-9df5-2b64ca9ea357"
IDeviceChild_UUID_STRING :: "905db94b-a00c-4140-9df5-2b64ca9ea357"
IDeviceChild_UUID := &IID{0x905db94b, 0xa00c, 0x4140, {0x9d, 0xf5, 0x2b, 0x64, 0xca, 0x9e, 0xa3, 0x57}}
IDeviceChild :: struct #raw_union {
#subtype id3d12object: IObject,
using id3d12devicechild_vtable: ^IDeviceChild_VTable,
@@ -683,7 +686,8 @@ IDeviceChild_VTable :: struct {
}
IRootSignature_UUID :: "c54a6b66-72df-4ee8-8be5-a946a1429214"
IRootSignature_UUID_STRING :: "c54a6b66-72df-4ee8-8be5-a946a1429214"
IRootSignature_UUID := &IID{0xc54a6b66, 0x72df, 0x4ee8, {0x8b, 0xe5, 0xa9, 0x46, 0xa1, 0x42, 0x92, 0x14}}
IRootSignature :: struct {
using id3d12devicechild: IDeviceChild,
}
@@ -2058,7 +2062,8 @@ VERSIONED_ROOT_SIGNATURE_DESC :: struct {
}
IRootSignatureDeserializer_UUID :: "34AB647B-3CC8-46AC-841B-C0965645C046"
IRootSignatureDeserializer_UUID_STRING :: "34AB647B-3CC8-46AC-841B-C0965645C046"
IRootSignatureDeserializer_UUID := &IID{0x34AB647B, 0x3CC8, 0x46AC, {0x84, 0x1B, 0xC0, 0x96, 0x56, 0x45, 0xC0, 0x46}}
IRootSignatureDeserializer :: struct #raw_union {
#subtype iunknown: IUnknown,
using id3d12rootsignaturedeserializer_vtable: ^IRootSignatureDeserializer_VTable,
@@ -2069,7 +2074,8 @@ IRootSignatureDeserializer_VTable :: struct {
}
IVersionedRootSignatureDeserializer_UUID :: "7F91CE67-090C-4BB7-B78E-ED8FF2E31DA0"
IVersionedRootSignatureDeserializer_UUID_STRING :: "7F91CE67-090C-4BB7-B78E-ED8FF2E31DA0"
IVersionedRootSignatureDeserializer_UUID := &IID{0x7F91CE67, 0x090C, 0x4BB7, {0xB7, 0x8E, 0xED, 0x8F, 0xF2, 0xE3, 0x1D, 0xA0}}
IVersionedRootSignatureDeserializer :: struct #raw_union {
#subtype iunknown: IUnknown,
using id3d12versionedrootsignaturedeserializer_vtable: ^IVersionedRootSignatureDeserializer_VTable,
@@ -2236,13 +2242,15 @@ COMMAND_SIGNATURE_DESC :: struct {
}
IPageable_UUID :: "63ee58fb-1268-4835-86da-f008ce62f0d6"
IPageable_UUID_STRING :: "63ee58fb-1268-4835-86da-f008ce62f0d6"
IPageable_UUID := &IID{0x63ee58fb, 0x1268, 0x4835, {0x86, 0xda, 0xf0, 0x08, 0xce, 0x62, 0xf0, 0xd6}}
IPageable :: struct {
using id3d12devicechild: IDeviceChild,
}
IHeap_UUID :: "6b3b2502-6e51-45b3-90ee-9884265e8df3"
IHeap_UUID_STRING :: "6b3b2502-6e51-45b3-90ee-9884265e8df3"
IHeap_UUID := &IID{0x6b3b2502, 0x6e51, 0x45b3, {0x90, 0xee, 0x98, 0x84, 0x26, 0x5e, 0x8d, 0xf3}}
IHeap :: struct #raw_union {
#subtype id3d12pageable: IPageable,
using id3d12heap_vtable: ^IHeap_VTable,
@@ -2253,7 +2261,8 @@ IHeap_VTable :: struct {
}
IResource_UUID :: "696442be-a72e-4059-bc79-5b5c98040fad"
IResource_UUID_STRING :: "696442be-a72e-4059-bc79-5b5c98040fad"
IResource_UUID := &IID{0x696442be, 0xa72e, 0x4059, {0xbc, 0x79, 0x5b, 0x5c, 0x98, 0x04, 0x0f, 0xad}}
IResource :: struct #raw_union {
#subtype id3d12pageable: IPageable,
using id3d12resource_vtable: ^IResource_VTable,
@@ -2270,7 +2279,8 @@ IResource_VTable :: struct {
}
ICommandAllocator_UUID :: "6102dee4-af59-4b09-b999-b44d73f09b24"
ICommandAllocator_UUID_STRING :: "6102dee4-af59-4b09-b999-b44d73f09b24"
ICommandAllocator_UUID := &IID{0x6102dee4, 0xaf59, 0x4b09, {0xb9, 0x99, 0xb4, 0x4d, 0x73, 0xf0, 0x9b, 0x24}}
ICommandAllocator :: struct #raw_union {
#subtype id3d12pageable: IPageable,
using id3d12commandallocator_vtable: ^ICommandAllocator_VTable,
@@ -2281,7 +2291,8 @@ ICommandAllocator_VTable :: struct {
}
IFence_UUID :: "0a753dcf-c4d8-4b91-adf6-be5a60d95a76"
IFence_UUID_STRING :: "0a753dcf-c4d8-4b91-adf6-be5a60d95a76"
IFence_UUID := &IID {0x0a753dcf, 0xc4d8, 0x4b91, {0xad, 0xf6, 0xbe, 0x5a, 0x60, 0xd9, 0x5a, 0x76}}
IFence :: struct #raw_union {
#subtype id3d12pageable: IPageable,
using id3d12fence_vtable: ^IFence_VTable,
@@ -2294,7 +2305,8 @@ IFence_VTable :: struct {
}
IFence1_UUID :: "433685fe-e22b-4ca0-a8db-b5b4f4dd0e4a"
IFence1_UUID_STRING :: "433685fe-e22b-4ca0-a8db-b5b4f4dd0e4a"
IFence1_UUID := &IID{0x433685fe, 0xe22b, 0x4ca0, {0xa8, 0xdb, 0xb5, 0xb4, 0xf4, 0xdd, 0x0e, 0x4a}}
IFence1 :: struct #raw_union {
#subtype id3d12fence: IFence,
using id3d12fence1_vtable: ^IFence1_VTable,
@@ -2305,7 +2317,8 @@ IFence1_VTable :: struct {
}
IPipelineState_UUID :: "765a30f3-f624-4c6f-a828-ace948622445"
IPipelineState_UUID_STRING :: "765a30f3-f624-4c6f-a828-ace948622445"
IPipelineState_UUID := &IID{0x765a30f3, 0xf624, 0x4c6f, {0xa8, 0x28, 0xac, 0xe9, 0x48, 0x62, 0x24, 0x45}}
IPipelineState :: struct #raw_union {
#subtype id3d12pageable: IPageable,
using id3d12pipelinestate_vtable: ^IPipelineState_VTable,
@@ -2316,32 +2329,35 @@ IPipelineState_VTable :: struct {
}
IDescriptorHeap_UUID :: "8efb471d-616c-4f49-90f7-127bb763fa51"
IDescriptorHeap_UUID_STRING :: "8efb471d-616c-4f49-90f7-127bb763fa51"
IDescriptorHeap_UUID := &IID{0x8efb471d, 0x616c, 0x4f49, { 0x90, 0xf7, 0x12, 0x7b, 0xb7, 0x63, 0xfa, 0x51}}
IDescriptorHeap :: struct #raw_union {
#subtype id3d12pageable: IPageable,
using id3d12descriptorheap_vtable: ^IDescriptorHeap_VTable,
}
IDescriptorHeap_VTable :: struct {
using id3d12devicechild_vtable: IDeviceChild_VTable,
GetDesc: proc "stdcall" (this: ^IDescriptorHeap) -> DESCRIPTOR_HEAP_DESC,
GetCPUDescriptorHandleForHeapStart: proc "stdcall" (this: ^IDescriptorHeap) -> CPU_DESCRIPTOR_HANDLE,
GetGPUDescriptorHandleForHeapStart: proc "stdcall" (this: ^IDescriptorHeap) -> GPU_DESCRIPTOR_HANDLE,
}
GetDesc: proc "stdcall" (this: ^IDescriptorHeap, desc: ^DESCRIPTOR_HEAP_DESC),
GetCPUDescriptorHandleForHeapStart: proc "stdcall" (this: ^IDescriptorHeap, handle: ^CPU_DESCRIPTOR_HANDLE),
GetGPUDescriptorHandleForHeapStart: proc "stdcall" (this: ^IDescriptorHeap, handle: ^GPU_DESCRIPTOR_HANDLE),
}
IQueryHeap_UUID :: "0d9658ae-ed45-469e-a61d-970ec583cab4"
IQueryHeap_UUID_STRING :: "0d9658ae-ed45-469e-a61d-970ec583cab4"
IQueryHeap_UUID := &IID{0x0d9658ae, 0xed45, 0x469e, {0xa6, 0x1d, 0x97, 0x0e, 0xc5, 0x83, 0xca, 0xb4}}
IQueryHeap :: struct {
#subtype id3d12pageable: IPageable,
}
ICommandSignature_UUID :: "c36a797c-ec80-4f0a-8985-a7b2475082d1"
ICommandSignature_UUID_STRING :: "c36a797c-ec80-4f0a-8985-a7b2475082d1"
ICommandSignature_UUID := &IID{0xc36a797c, 0xec80, 0x4f0a, {0x89, 0x85, 0xa7, 0xb2, 0x47, 0x50, 0x82, 0xd1}}
ICommandSignature :: struct {
#subtype id3d12pageable: IPageable,
}
ICommandList_UUID :: "7116d91c-e7e4-47ce-b8c6-ec8168f437e5"
ICommandList_UUID_STRING :: "7116d91c-e7e4-47ce-b8c6-ec8168f437e5"
ICommandList_UUID := &IID {0x7116d91c, 0xe7e4, 0x47ce, {0xb8, 0xc6, 0xec, 0x81, 0x68, 0xf4, 0x37, 0xe5}}
ICommandList :: struct #raw_union {
#subtype id3d12devicechild: IDeviceChild,
using id3d12commandlist_vtable: ^ICommandList_VTable,
@@ -2352,7 +2368,8 @@ ICommandList_VTable :: struct {
}
IGraphicsCommandList_UUID :: "5b160d0f-ac1b-4185-8ba8-b3ae42a5a455"
IGraphicsCommandList_UUID_STRING :: "5b160d0f-ac1b-4185-8ba8-b3ae42a5a455"
IGraphicsCommandList_UUID := &IID{0x5b160d0f, 0xac1b, 0x4185, {0x8b, 0xa8, 0xb3, 0xae, 0x42, 0xa5, 0xa4, 0x55}}
IGraphicsCommandList :: struct #raw_union {
#subtype id3d12commandlist: ICommandList,
using id3d12graphicscommandlist_vtable: ^IGraphicsCommandList_VTable,
@@ -2413,7 +2430,8 @@ IGraphicsCommandList_VTable :: struct {
}
IGraphicsCommandList1_UUID :: "553103fb-1fe7-4557-bb38-946d7d0e7ca7"
IGraphicsCommandList1_UUID_STRING :: "553103fb-1fe7-4557-bb38-946d7d0e7ca7"
IGraphicsCommandList1_UUID := &IID{0x553103fb, 0x1fe7, 0x4557, {0xbb, 0x38, 0x94, 0x6d, 0x7d, 0x0e, 0x7c, 0xa7}}
IGraphicsCommandList1 :: struct #raw_union {
#subtype id3d12graphicscommandlist: IGraphicsCommandList,
using id3d12graphicscommandlist1_vtable: ^IGraphicsCommandList1_VTable,
@@ -2440,7 +2458,8 @@ WRITEBUFFERIMMEDIATE_MODE :: enum i32 {
}
IGraphicsCommandList2_UUID :: "38C3E585-FF17-412C-9150-4FC6F9D72A28"
IGraphicsCommandList2_UUID_STRING :: "38C3E585-FF17-412C-9150-4FC6F9D72A28"
IGraphicsCommandList2_UUID := &IID{0x38C3E585, 0xFF17, 0x412C, {0x91, 0x50, 0x4F, 0xC6, 0xF9, 0xD7, 0x2A, 0x28}}
IGraphicsCommandList2 :: struct #raw_union {
#subtype id3d12graphicscommandlist1: IGraphicsCommandList1,
using id3d12graphicscommandlist2_vtable: ^IGraphicsCommandList2_VTable,
@@ -2451,7 +2470,8 @@ IGraphicsCommandList2_VTable :: struct {
}
ICommandQueue_UUID :: "0ec870a6-5d7e-4c22-8cfc-5baae07616ed"
ICommandQueue_UUID_STRING :: "0ec870a6-5d7e-4c22-8cfc-5baae07616ed"
ICommandQueue_UUID := &IID{0x0ec870a6, 0x5d7e, 0x4c22, { 0x8c, 0xfc, 0x5b, 0xaa, 0xe0, 0x76, 0x16, 0xed}}
ICommandQueue :: struct #raw_union {
#subtype id3d12pageable: IPageable,
using id3d12commandqueue_vtable: ^ICommandQueue_VTable,
@@ -2472,7 +2492,8 @@ ICommandQueue_VTable :: struct {
}
IDevice_UUID :: "189819f1-1db6-4b57-be54-1821339b85f7"
IDevice_UUID_STRING :: "189819f1-1db6-4b57-be54-1821339b85f7"
IDevice_UUID := &IID{0x189819f1, 0x1db6, 0x4b57, { 0xbe, 0x54, 0x18, 0x21, 0x33, 0x9b, 0x85, 0xf7}}
IDevice :: struct #raw_union {
#subtype id3d12object: IObject,
using id3d12device_vtable: ^IDevice_VTable,
@@ -2519,7 +2540,8 @@ IDevice_VTable :: struct {
}
IPipelineLibrary_UUID :: "c64226a8-9201-46af-b4cc-53fb9ff7414f"
IPipelineLibrary_UUID_STRING :: "c64226a8-9201-46af-b4cc-53fb9ff7414f"
IPipelineLibrary_UUID := &IID{0xc64226a8, 0x9201, 0x46af, {0xb4, 0xcc, 0x53, 0xfb, 0x9f, 0xf7, 0x41, 0x4f}}
IPipelineLibrary :: struct #raw_union {
#subtype id3d12devicechild: IDeviceChild,
using id3d12pipelinelibrary_vtable: ^IPipelineLibrary_VTable,
@@ -2534,7 +2556,8 @@ IPipelineLibrary_VTable :: struct {
}
IPipelineLibrary1_UUID :: "80eabf42-2568-4e5e-bd82-c37f86961dc3"
IPipelineLibrary1_UUID_STRING :: "80eabf42-2568-4e5e-bd82-c37f86961dc3"
IPipelineLibrary1_UUID := &IID{0x80eabf42, 0x2568, 0x4e5e, {0xbd, 0x82, 0xc3, 0x7f, 0x86, 0x96, 0x1d, 0xc3}}
IPipelineLibrary1 :: struct #raw_union {
#subtype id3d12pipelinelibrary: IPipelineLibrary,
using id3d12pipelinelibrary1_vtable: ^IPipelineLibrary1_VTable,
@@ -2559,7 +2582,8 @@ RESIDENCY_PRIORITY :: enum i32 {
}
IDevice1_UUID :: "77acce80-638e-4e65-8895-c1f23386863e"
IDevice1_UUID_STRING :: "77acce80-638e-4e65-8895-c1f23386863e"
IDevice1_UUID := &IID{0x77acce80, 0x638e, 0x4e65, {0x88, 0x95, 0xc1, 0xf2, 0x33, 0x86, 0x86, 0x3e}}
IDevice1 :: struct #raw_union {
#subtype id3d12device: IDevice,
using id3d12device1_vtable: ^IDevice1_VTable,
@@ -2572,7 +2596,8 @@ IDevice1_VTable :: struct {
}
IDevice2_UUID :: "30baa41e-b15b-475c-a0bb-1af5c5b64328"
IDevice2_UUID_STRING :: "30baa41e-b15b-475c-a0bb-1af5c5b64328"
IDevice2_UUID := &IID{0x30baa41e, 0xb15b, 0x475c, {0xa0, 0xbb, 0x1a, 0xf5, 0xc5, 0xb6, 0x43, 0x28}}
IDevice2 :: struct #raw_union {
#subtype id3d12device1: IDevice1,
using id3d12device2_vtable: ^IDevice2_VTable,
@@ -2588,7 +2613,8 @@ RESIDENCY_FLAGS :: enum u32 { // TODO: make bit_set
}
IDevice3_UUID :: "81dadc15-2bad-4392-93c5-101345c4aa98"
IDevice3_UUID_STRING :: "81dadc15-2bad-4392-93c5-101345c4aa98"
IDevice3_UUID := &IID{0x81dadc15, 0x2bad, 0x4392, {0x93, 0xc5, 0x10, 0x13, 0x45, 0xc4, 0xaa, 0x98}}
IDevice3 :: struct #raw_union {
#subtype id3d12device2: IDevice2,
using id3d12device3_vtable: ^IDevice3_VTable,
@@ -2618,7 +2644,8 @@ PROTECTED_SESSION_STATUS :: enum i32 {
}
IProtectedSession_UUID :: "A1533D18-0AC1-4084-85B9-89A96116806B"
IProtectedSession_UUID_STRING :: "A1533D18-0AC1-4084-85B9-89A96116806B"
IProtectedSession_UUID := &IID{0xA1533D18, 0x0AC1, 0x4084, {0x85, 0xB9, 0x89, 0xA9, 0x61, 0x16, 0x80, 0x6B}}
IProtectedSession :: struct #raw_union {
#subtype id3d12devicechild: IDeviceChild,
using id3d12protectedsession_vtable: ^IProtectedSession_VTable,
@@ -2649,7 +2676,8 @@ PROTECTED_RESOURCE_SESSION_DESC :: struct {
}
IProtectedResourceSession_UUID :: "6CD696F4-F289-40CC-8091-5A6C0A099C3D"
IProtectedResourceSession_UUID_STRING :: "6CD696F4-F289-40CC-8091-5A6C0A099C3D"
IProtectedResourceSession_UUID := &IID{0x6CD696F4, 0xF289, 0x40CC, {0x80, 0x91, 0x5A, 0x6C, 0x0A, 0x09, 0x9C, 0x3D}}
IProtectedResourceSession :: struct #raw_union {
#subtype id3d12protectedsession: IProtectedSession,
using id3d12protectedresourcesession_vtable: ^IProtectedResourceSession_VTable,
@@ -2660,7 +2688,8 @@ IProtectedResourceSession_VTable :: struct {
}
IDevice4_UUID :: "e865df17-a9ee-46f9-a463-3098315aa2e5"
IDevice4_UUID_STRING :: "e865df17-a9ee-46f9-a463-3098315aa2e5"
IDevice4_UUID := &IID{0xe865df17, 0xa9ee, 0x46f9, {0xa4, 0x63, 0x30, 0x98, 0x31, 0x5a, 0xa2, 0xe5}}
IDevice4 :: struct #raw_union {
#subtype id3d12device3: IDevice3,
using id3d12device4_vtable: ^IDevice4_VTable,
@@ -2681,7 +2710,8 @@ LIFETIME_STATE :: enum i32 {
}
ILifetimeOwner_UUID :: "e667af9f-cd56-4f46-83ce-032e595d70a8"
ILifetimeOwner_UUID_STRING :: "e667af9f-cd56-4f46-83ce-032e595d70a8"
ILifetimeOwner_UUID := &IID{0xe667af9f, 0xcd56, 0x4f46, {0x83, 0xce, 0x03, 0x2e, 0x59, 0x5d, 0x70, 0xa8}}
ILifetimeOwner :: struct #raw_union {
#subtype iunknown: IUnknown,
using id3d12lifetimeowner_vtable: ^ILifetimeOwner_VTable,
@@ -2692,7 +2722,8 @@ ILifetimeOwner_VTable :: struct {
}
ISwapChainAssistant_UUID :: "f1df64b6-57fd-49cd-8807-c0eb88b45c8f"
ISwapChainAssistant_UUID_STRING :: "f1df64b6-57fd-49cd-8807-c0eb88b45c8f"
ISwapChainAssistant_UUID := &IID{0xf1df64b6, 0x57fd, 0x49cd, {0x88, 0x07, 0xc0, 0xeb, 0x88, 0xb4, 0x5c, 0x8f}}
ISwapChainAssistant :: struct #raw_union {
#subtype iunknown: IUnknown,
using id3d12swapchainassistant_vtable: ^ISwapChainAssistant_VTable,
@@ -2706,7 +2737,8 @@ ISwapChainAssistant_VTable :: struct {
}
ILifetimeTracker_UUID :: "3fd03d36-4eb1-424a-a582-494ecb8ba813"
ILifetimeTracker_UUID_STRING :: "3fd03d36-4eb1-424a-a582-494ecb8ba813"
ILifetimeTracker_UUID := &IID{0x3fd03d36, 0x4eb1, 0x424a, {0xa5, 0x82, 0x49, 0x4e, 0xcb, 0x8b, 0xa8, 0x13}}
ILifetimeTracker :: struct #raw_union {
#subtype id3d12devicechild: IDeviceChild,
using id3d12lifetimetracker_vtable: ^ILifetimeTracker_VTable,
@@ -2772,13 +2804,15 @@ META_COMMAND_DESC :: struct {
}
IStateObject_UUID :: "47016943-fca8-4594-93ea-af258b55346d"
IStateObject_UUID_STRING :: "47016943-fca8-4594-93ea-af258b55346d"
IStateObject_UUID := &IID{0x47016943, 0xfca8, 0x4594, {0x93, 0xea, 0xaf, 0x25, 0x8b, 0x55, 0x34, 0x6d}}
IStateObject :: struct #raw_union {
#subtype id3d12pageable: IPageable,
}
IStateObjectProperties_UUID :: "de5fa827-9bf9-4f26-89ff-d7f56fde3860"
IStateObjectProperties_UUID_STRING :: "de5fa827-9bf9-4f26-89ff-d7f56fde3860"
IStateObjectProperties_IID := &IID{0xde5fa827, 0x9bf9, 0x4f26, {0x89, 0xff, 0xd7, 0xf5, 0x6f, 0xde, 0x38, 0x60}}
IStateObjectProperties :: struct #raw_union {
#subtype iunknown: IUnknown,
using id3d12stateobjectproperties_vtable: ^IStateObjectProperties_VTable,
@@ -3119,7 +3153,8 @@ HIT_KIND :: enum i32 {
}
IDevice5_UUID :: "8b4f173b-2fea-4b80-8f58-4307191ab95d"
IDevice5_UUID_STRING :: "8b4f173b-2fea-4b80-8f58-4307191ab95d"
IDevice5_UUID := &IID{0x8b4f173b, 0x2fea, 0x4b80, {0x8f, 0x58, 0x43, 0x07, 0x19, 0x1a, 0xb9, 0x5d}}
IDevice5 :: struct #raw_union {
#subtype id3d12device4: IDevice4,
using id3d12device5_vtable: ^IDevice5_VTable,
@@ -3325,7 +3360,8 @@ VERSIONED_DEVICE_REMOVED_EXTENDED_DATA :: struct {
}
IDeviceRemovedExtendedDataSettings_UUID :: "82BC481C-6B9B-4030-AEDB-7EE3D1DF1E63"
IDeviceRemovedExtendedDataSettings_UUID_STRING :: "82BC481C-6B9B-4030-AEDB-7EE3D1DF1E63"
IDeviceRemovedExtendedDataSettings_UUID := &IID{0x82BC481C, 0x6B9B, 0x4030, {0xAE, 0xDB, 0x7E, 0xE3, 0xD1, 0xDF, 0x1E, 0x63}}
IDeviceRemovedExtendedDataSettings :: struct #raw_union {
#subtype iunknown: IUnknown,
using id3d12deviceremovedextendeddatasettings_vtable: ^IDeviceRemovedExtendedDataSettings_VTable,
@@ -3338,7 +3374,8 @@ IDeviceRemovedExtendedDataSettings_VTable :: struct {
}
IDeviceRemovedExtendedDataSettings1_UUID :: "DBD5AE51-3317-4F0A-ADF9-1D7CEDCAAE0B"
IDeviceRemovedExtendedDataSettings1_UUID_STRING :: "DBD5AE51-3317-4F0A-ADF9-1D7CEDCAAE0B"
IDeviceRemovedExtendedDataSettings1_UUID := &IID{0xDBD5AE51, 0x3317, 0x4F0A, {0xAD, 0xF9, 0x1D, 0x7C, 0xED, 0xCA, 0xAE, 0x0B}}
IDeviceRemovedExtendedDataSettings1 :: struct #raw_union {
#subtype id3d12deviceremovedextendeddatasettings: IDeviceRemovedExtendedDataSettings,
using id3d12deviceremovedextendeddatasettings1_vtable: ^IDeviceRemovedExtendedDataSettings1_VTable,
@@ -3349,7 +3386,8 @@ IDeviceRemovedExtendedDataSettings1_VTable :: struct {
}
IDeviceRemovedExtendedData_UUID :: "98931D33-5AE8-4791-AA3C-1A73A2934E71"
IDeviceRemovedExtendedData_UUID_STRING :: "98931D33-5AE8-4791-AA3C-1A73A2934E71"
IDeviceRemovedExtendedData_UUID := &IID{0x98931D33, 0x5AE8, 0x4791, {0xAA, 0x3C, 0x1A, 0x73, 0xA2, 0x93, 0x4E, 0x71}}
IDeviceRemovedExtendedData :: struct #raw_union {
#subtype iunknown: IUnknown,
using id3d12deviceremovedextendeddata_vtable: ^IDeviceRemovedExtendedData_VTable,
@@ -3361,7 +3399,8 @@ IDeviceRemovedExtendedData_VTable :: struct {
}
IDeviceRemovedExtendedData1_UUID :: "9727A022-CF1D-4DDA-9EBA-EFFA653FC506"
IDeviceRemovedExtendedData1_UUID_STRING :: "9727A022-CF1D-4DDA-9EBA-EFFA653FC506"
IDeviceRemovedExtendedData1_UUID := &IID{0x9727A022, 0xCF1D, 0x4DDA, {0x9E, 0xBA, 0xEF, 0xFA, 0x65, 0x3F, 0xC5, 0x06}}
IDeviceRemovedExtendedData1 :: struct #raw_union {
#subtype id3d12deviceremovedextendeddata: IDeviceRemovedExtendedData,
using id3d12deviceremovedextendeddata1_vtable: ^IDeviceRemovedExtendedData1_VTable,
@@ -3387,7 +3426,8 @@ MEASUREMENTS_ACTION :: enum i32 {
}
IDevice6_UUID :: "c70b221b-40e4-4a17-89af-025a0727a6dc"
IDevice6_UUID_STRING :: "c70b221b-40e4-4a17-89af-025a0727a6dc"
IDevice6_UUID := &IID{0xc70b221b, 0x40e4, 0x4a17, {0x89, 0xaf, 0x02, 0x5a, 0x07, 0x27, 0xa6, 0xdc}}
IDevice6 :: struct #raw_union {
#subtype id3d12device5: IDevice5,
using id3d12device6_vtable: ^IDevice6_VTable,
@@ -3415,7 +3455,8 @@ PROTECTED_RESOURCE_SESSION_DESC1 :: struct {
}
IProtectedResourceSession1_UUID :: "D6F12DD6-76FB-406E-8961-4296EEFC0409"
IProtectedResourceSession1_UUID_STRING :: "D6F12DD6-76FB-406E-8961-4296EEFC0409"
IProtectedResourceSession1_UUID := &IID{0xD6F12DD6, 0x76FB, 0x406E, {0x89, 0x61, 0x42, 0x96, 0xEE, 0xFC, 0x04, 0x09}}
IProtectedResourceSession1 :: struct #raw_union {
#subtype id3d12protectedresourcesession: IProtectedResourceSession,
using id3d12protectedresourcesession1_vtable: ^IProtectedResourceSession1_VTable,
@@ -3426,7 +3467,8 @@ IProtectedResourceSession1_VTable :: struct {
}
IDevice7_UUID :: "5c014b53-68a1-4b9b-8bd1-dd6046b9358b"
IDevice7_UUID_STRING :: "5c014b53-68a1-4b9b-8bd1-dd6046b9358b"
IDevice7_UUID := &IID{0x5c014b53, 0x68a1, 0x4b9b, {0x8b, 0xd1, 0xdd, 0x60, 0x46, 0xb9, 0x35, 0x8b}}
IDevice7 :: struct #raw_union {
#subtype id3d12device6: IDevice6,
using id3d12device7_vtable: ^IDevice7_VTable,
@@ -3438,7 +3480,8 @@ IDevice7_VTable :: struct {
}
IDevice8_UUID :: "9218E6BB-F944-4F7E-A75C-B1B2C7B701F3"
IDevice8_UUID_STRING :: "9218E6BB-F944-4F7E-A75C-B1B2C7B701F3"
IDevice8_UUID := &IID{0x9218E6BB, 0xF944, 0x4F7E, {0xA7, 0x5C, 0xB1, 0xB2, 0xC7, 0xB7, 0x01, 0xF3}}
IDevice8 :: struct #raw_union {
#subtype id3d12device7: IDevice7,
using id3d12device8_vtable: ^IDevice8_VTable,
@@ -3453,7 +3496,8 @@ IDevice8_VTable :: struct {
}
IResource1_UUID :: "9D5E227A-4430-4161-88B3-3ECA6BB16E19"
IResource1_UUID_STRING :: "9D5E227A-4430-4161-88B3-3ECA6BB16E19"
IResource1_UUID := &IID{0x9D5E227A, 0x4430, 0x4161, {0x88, 0xB3, 0x3E, 0xCA, 0x6B, 0xB1, 0x6E, 0x19}}
IResource1 :: struct #raw_union {
#subtype id3d12resource: IResource,
using id3d12resource1_vtable: ^IResource1_VTable,
@@ -3464,7 +3508,8 @@ IResource1_VTable :: struct {
}
IResource2_UUID :: "BE36EC3B-EA85-4AEB-A45A-E9D76404A495"
IResource2_UUID_STRING :: "BE36EC3B-EA85-4AEB-A45A-E9D76404A495"
IResource2_UUID := &IID{0xBE36EC3B, 0xEA85, 0x4AEB, {0xA4, 0x5A, 0xE9, 0xD7, 0x64, 0x04, 0xA4, 0x95}}
IResource2 :: struct #raw_union {
#subtype id3d12resource1: IResource1,
using id3d12resource2_vtable: ^IResource2_VTable,
@@ -3475,7 +3520,8 @@ IResource2_VTable :: struct {
}
IHeap1_UUID :: "572F7389-2168-49E3-9693-D6DF5871BF6D"
IHeap1_UUID_STRING :: "572F7389-2168-49E3-9693-D6DF5871BF6D"
IHeap1_UUID := &IID{0x572F7389, 0x2168, 0x49E3, {0x96, 0x93, 0xD6, 0xDF, 0x58, 0x71, 0xBF, 0x6D}}
IHeap1 :: struct #raw_union {
#subtype id3d12heap: IHeap,
using id3d12heap1_vtable: ^IHeap1_VTable,
@@ -3486,7 +3532,8 @@ IHeap1_VTable :: struct {
}
IGraphicsCommandList3_UUID :: "6FDA83A7-B84C-4E38-9AC8-C7BD22016B3D"
IGraphicsCommandList3_UUID_STRING :: "6FDA83A7-B84C-4E38-9AC8-C7BD22016B3D"
IGraphicsCommandList3_UUID := &IID{0x6FDA83A7, 0xB84C, 0x4E38, {0x9A, 0xC8, 0xC7, 0xBD, 0x22, 0x01, 0x6B, 0x3D}}
IGraphicsCommandList3 :: struct #raw_union {
#subtype id3d12graphicscommandlist2: IGraphicsCommandList2,
using id3d12graphicscommandlist3_vtable: ^IGraphicsCommandList3_VTable,
@@ -3568,7 +3615,8 @@ RENDER_PASS_FLAGS :: enum u32 { // TODO: make bit_set
}
IMetaCommand_UUID :: "DBB84C27-36CE-4FC9-B801-F048C46AC570"
IMetaCommand_UUID_STRING :: "DBB84C27-36CE-4FC9-B801-F048C46AC570"
IMetaCommand_UUID := &IID{0xDBB84C27, 0x36CE, 0x4FC9, {0xB8, 0x01, 0xF0, 0x48, 0xC4, 0x6A, 0xC5, 0x70}}
IMetaCommand :: struct #raw_union {
#subtype id3d12pageable: IPageable,
using id3d12metacommand_vtable: ^IMetaCommand_VTable,
@@ -3589,7 +3637,8 @@ DISPATCH_RAYS_DESC :: struct {
}
IGraphicsCommandList4_UUID :: "8754318e-d3a9-4541-98cf-645b50dc4874"
IGraphicsCommandList4_UUID_STRING :: "8754318e-d3a9-4541-98cf-645b50dc4874"
IGraphicsCommandList4_UUID := &IID{0x8754318e, 0xd3a9, 0x4541, {0x98, 0xcf, 0x64, 0x5b, 0x50, 0xdc, 0x48, 0x74}}
IGraphicsCommandList4 :: struct #raw_union {
#subtype id3d12graphicscommandlist3: IGraphicsCommandList3,
using id3d12graphicscommandlist4_vtable: ^IGraphicsCommandList4_VTable,
@@ -3608,7 +3657,8 @@ IGraphicsCommandList4_VTable :: struct {
}
ITools_UUID :: "7071e1f0-e84b-4b33-974f-12fa49de65c5"
ITools_UUID_STRING :: "7071e1f0-e84b-4b33-974f-12fa49de65c5"
ITools_UUID := &IID{0x7071e1f0, 0xe84b, 0x4b33, {0x97, 0x4f, 0x12, 0xfa, 0x49, 0xde, 0x65, 0xc5}}
ITools :: struct #raw_union {
#subtype iunknown: IUnknown,
using id3d12tools_vtable: ^ITools_VTable,
@@ -3632,7 +3682,8 @@ MEMCPY_DEST :: struct {
}
IDebug_UUID :: "344488b7-6846-474b-b989-f027448245e0"
IDebug_UUID_STRING :: "344488b7-6846-474b-b989-f027448245e0"
IDebug_UUID := &IID{0x344488b7, 0x6846, 0x474b, {0xb9, 0x89, 0xf0, 0x27, 0x44, 0x82, 0x45, 0xe0}}
IDebug :: struct #raw_union {
#subtype iunknown: IUnknown,
using id3d12debug_vtable: ^IDebug_VTable,
@@ -3648,7 +3699,8 @@ GPU_BASED_VALIDATION_FLAGS :: enum u32 { // TODO: make bit_set
}
IDebug1_UUID :: "affaa4ca-63fe-4d8e-b8ad-159000af4304"
IDebug1_UUID_STRING :: "affaa4ca-63fe-4d8e-b8ad-159000af4304"
IDebug1_UUID := &IID{0xaffaa4ca, 0x63fe, 0x4d8e, {0xb8, 0xad, 0x15, 0x90, 0x00, 0xaf, 0x43, 0x04}}
IDebug1 :: struct #raw_union {
#subtype iunknown: IUnknown,
using id3d12debug1_vtable: ^IDebug1_VTable,
@@ -3672,7 +3724,8 @@ IDebug2_VTable :: struct {
}
IDebug3_UUID :: "5cf4e58f-f671-4ff1-a542-3686e3d153d1"
IDebug3_UUID_STRING :: "5cf4e58f-f671-4ff1-a542-3686e3d153d1"
IDebug3_UUID := &IID{0x5cf4e58f, 0xf671, 0x4ff1, {0xa5, 0x42, 0x36, 0x86, 0xe3, 0xd1, 0x53, 0xd1}}
IDebug3 :: struct #raw_union {
#subtype id3d12debug: IDebug,
using id3d12debug3_vtable: ^IDebug3_VTable,
@@ -3732,7 +3785,8 @@ DEBUG_DEVICE_GPU_SLOWDOWN_PERFORMANCE_FACTOR :: struct {
}
IDebugDevice1_UUID :: "a9b71770-d099-4a65-a698-3dee10020f88"
IDebugDevice1_UUID_STRING :: "a9b71770-d099-4a65-a698-3dee10020f88"
IDebugDevice1_UUID := &IID{0xa9b71770, 0xd099, 0x4a65, {0xa6, 0x98, 0x3d, 0xee, 0x10, 0x02, 0x0f, 0x88}}
IDebugDevice1 :: struct #raw_union {
#subtype iunknown: IUnknown,
using id3d12debugdevice1_vtable: ^IDebugDevice1_VTable,
@@ -3745,7 +3799,8 @@ IDebugDevice1_VTable :: struct {
}
IDebugDevice_UUID :: "3febd6dd-4973-4787-8194-e45f9e28923e"
IDebugDevice_UUID_STRING :: "3febd6dd-4973-4787-8194-e45f9e28923e"
IDebugDevice_UUID := &IID{0x3febd6dd, 0x4973, 0x4787, {0x81, 0x94, 0xe4, 0x5f, 0x9e, 0x28, 0x92, 0x3e}}
IDebugDevice :: struct #raw_union {
#subtype iunknown: IUnknown,
using id3d12debugdevice_vtable: ^IDebugDevice_VTable,
@@ -3758,7 +3813,8 @@ IDebugDevice_VTable :: struct {
}
IDebugDevice2_UUID :: "60eccbc1-378d-4df1-894c-f8ac5ce4d7dd"
IDebugDevice2_UUID_STRING :: "60eccbc1-378d-4df1-894c-f8ac5ce4d7dd"
IDebugDevice2_UUID := &IID{0x60eccbc1, 0x378d, 0x4df1, {0x89, 0x4c, 0xf8, 0xac, 0x5c, 0xe4, 0xd7, 0xdd}}
IDebugDevice2 :: struct #raw_union {
#subtype id3d12debugdevice: IDebugDevice,
using id3d12debugdevice2_vtable: ^IDebugDevice2_VTable,
@@ -3770,8 +3826,8 @@ IDebugDevice2_VTable :: struct {
}
IDebugCommandQueue_UUID :: "09e0bf36-54ac-484f-8847-4baeeab6053a"
IDebugCommandQueue_UUID_STRING :: "09e0bf36-54ac-484f-8847-4baeeab6053a"
IDebugCommandQueue_UUID := &IID{0x09e0bf36, 0x54ac, 0x484f, {0x88, 0x47, 0x4b, 0xae, 0xea, 0xb6, 0x05, 0x3a}}
IDebugCommandQueue :: struct #raw_union {
#subtype iunknown: IUnknown,
using id3d12debugcommandqueue_vtable: ^IDebugCommandQueue_VTable,
@@ -3790,7 +3846,8 @@ DEBUG_COMMAND_LIST_GPU_BASED_VALIDATION_SETTINGS :: struct {
}
IDebugCommandList1_UUID :: "102ca951-311b-4b01-b11f-ecb83e061b37"
IDebugCommandList1_UUID_STRING :: "102ca951-311b-4b01-b11f-ecb83e061b37"
IDebugCommandList1_UUID := &IID{0x102ca951, 0x311b, 0x4b01, {0xb1, 0x1f, 0xec, 0xb8, 0x3e, 0x06, 0x1b, 0x37}}
IDebugCommandList1 :: struct #raw_union {
#subtype iunknown: IUnknown,
using id3d12debugcommandlist1_vtable: ^IDebugCommandList1_VTable,
@@ -3803,7 +3860,8 @@ IDebugCommandList1_VTable :: struct {
}
IDebugCommandList_UUID :: "09e0bf36-54ac-484f-8847-4baeeab6053f"
IDebugCommandList_UUID_STRING :: "09e0bf36-54ac-484f-8847-4baeeab6053f"
IDebugCommandList_UUID := &IID{0x09e0bf36, 0x54ac, 0x484f, {0x88, 0x47, 0x4b, 0xae, 0xea, 0xb6, 0x05, 0x3f}}
IDebugCommandList :: struct #raw_union {
#subtype iunknown: IUnknown,
using id3d12debugcommandlist_vtable: ^IDebugCommandList_VTable,
@@ -3816,7 +3874,8 @@ IDebugCommandList_VTable :: struct {
}
IDebugCommandList2_UUID :: "aeb575cf-4e06-48be-ba3b-c450fc96652e"
IDebugCommandList2_UUID_STRING :: "aeb575cf-4e06-48be-ba3b-c450fc96652e"
IDebugCommandList2_UUID := &IID{0xaeb575cf, 0x4e06, 0x48be, {0xba, 0x3b, 0xc4, 0x50, 0xfc, 0x96, 0x65, 0x2e}}
IDebugCommandList2 :: struct #raw_union {
#subtype id3d12debugcommandlist: IDebugCommandList,
using id3d12debugcommandlist2_vtable: ^IDebugCommandList2_VTable,
@@ -3828,7 +3887,8 @@ IDebugCommandList2_VTable :: struct {
}
ISharingContract_UUID :: "0adf7d52-929c-4e61-addb-ffed30de66ef"
ISharingContract_UUID_STRING :: "0adf7d52-929c-4e61-addb-ffed30de66ef"
ISharingContract_UUID := &IID{0x0adf7d52, 0x929c, 0x4e61, {0xad, 0xdb, 0xff, 0xed, 0x30, 0xde, 0x66, 0xef}}
ISharingContract :: struct #raw_union {
#subtype iunknown: IUnknown,
using id3d12sharingcontract_vtable: ^ISharingContract_VTable,
@@ -4733,7 +4793,8 @@ INFO_QUEUE_FILTER :: struct {
}
IInfoQueue_UUID :: "0742a90b-c387-483f-b946-30a7e4e61458"
IInfoQueue_UUID_STRING :: "0742a90b-c387-483f-b946-30a7e4e61458"
IInfoQueue_UUID := &IID{0x0742a90b, 0xc387, 0x483f, {0xb9, 0x46, 0x30, 0xa7, 0xe4, 0xe6, 0x14, 0x58}}
IInfoQueue :: struct #raw_union {
#subtype iunknown: IUnknown,
using id3d12infoqueue_vtable: ^IInfoQueue_VTable,
@@ -4805,7 +4866,8 @@ SHADING_RATE_COMBINER :: enum i32 {
}
IGraphicsCommandList5_UUID :: "55050859-4024-474c-87f5-6472eaee44ea"
IGraphicsCommandList5_UUID_STRING :: "55050859-4024-474c-87f5-6472eaee44ea"
IGraphicsCommandList5_UUID := &IID{0x55050859, 0x4024, 0x474c, {0x87, 0xf5, 0x64, 0x72, 0xea, 0xee, 0x44, 0xea}}
IGraphicsCommandList5 :: struct #raw_union {
#subtype id3d12graphicscommandlist4: IGraphicsCommandList4,
using id3d12graphicscommandlist5_vtable: ^IGraphicsCommandList5_VTable,
@@ -4823,7 +4885,8 @@ DISPATCH_MESH_ARGUMENTS :: struct {
}
IGraphicsCommandList6_UUID :: "c3827890-e548-4cfa-96cf-5689a9370f80"
IGraphicsCommandList6_UUID_STRING :: "c3827890-e548-4cfa-96cf-5689a9370f80"
IGraphicsCommandList6_UUID := &IID{0xc3827890, 0xe548, 0x4cfa, {0x96, 0xcf, 0x56, 0x89, 0xa9, 0x37, 0x0f, 0x80}}
IGraphicsCommandList6 :: struct #raw_union {
#subtype id3d12graphicscommandlist5: IGraphicsCommandList5,
using id3d12graphicscommandlist6_vtable: ^IGraphicsCommandList6_VTable,
+3
View File
@@ -626,6 +626,9 @@ with open("../core.odin", 'w', encoding='utf-8') as f:
f.write(BASE)
f.write("""
API_VERSION_1_0 :: (1<<22) | (0<<12) | (0)
API_VERSION_1_1 :: (1<<22) | (1<<12) | (0)
API_VERSION_1_2 :: (1<<22) | (2<<12) | (0)
API_VERSION_1_3 :: (1<<22) | (3<<12) | (0)
MAKE_VERSION :: proc(major, minor, patch: u32) -> u32 {
return (major<<22) | (minor<<12) | (patch)
+3
View File
@@ -3,6 +3,9 @@
//
package vulkan
API_VERSION_1_0 :: (1<<22) | (0<<12) | (0)
API_VERSION_1_1 :: (1<<22) | (1<<12) | (0)
API_VERSION_1_2 :: (1<<22) | (2<<12) | (0)
API_VERSION_1_3 :: (1<<22) | (3<<12) | (0)
MAKE_VERSION :: proc(major, minor, patch: u32) -> u32 {
return (major<<22) | (minor<<12) | (patch)