mirror of
https://github.com/Ed94/Odin.git
synced 2026-08-05 15:18:49 +00:00
Merge pull request #4009 from thetarnav/file-tag-parser
Add a file tag parser to core:odin/parser
This commit is contained in:
@@ -546,10 +546,23 @@ Odin_OS_Type :: type_of(ODIN_OS)
|
|||||||
arm64,
|
arm64,
|
||||||
wasm32,
|
wasm32,
|
||||||
wasm64p32,
|
wasm64p32,
|
||||||
|
riscv64,
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
Odin_Arch_Type :: type_of(ODIN_ARCH)
|
Odin_Arch_Type :: type_of(ODIN_ARCH)
|
||||||
|
|
||||||
|
Odin_Arch_Types :: bit_set[Odin_Arch_Type]
|
||||||
|
|
||||||
|
ALL_ODIN_ARCH_TYPES :: Odin_Arch_Types{
|
||||||
|
.amd64,
|
||||||
|
.i386,
|
||||||
|
.arm32,
|
||||||
|
.arm64,
|
||||||
|
.wasm32,
|
||||||
|
.wasm64p32,
|
||||||
|
.riscv64,
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
// Defined internally by the compiler
|
// Defined internally by the compiler
|
||||||
Odin_Build_Mode_Type :: enum int {
|
Odin_Build_Mode_Type :: enum int {
|
||||||
@@ -573,6 +586,22 @@ Odin_Build_Mode_Type :: type_of(ODIN_BUILD_MODE)
|
|||||||
*/
|
*/
|
||||||
Odin_Endian_Type :: type_of(ODIN_ENDIAN)
|
Odin_Endian_Type :: type_of(ODIN_ENDIAN)
|
||||||
|
|
||||||
|
Odin_OS_Types :: bit_set[Odin_OS_Type]
|
||||||
|
|
||||||
|
ALL_ODIN_OS_TYPES :: Odin_OS_Types{
|
||||||
|
.Windows,
|
||||||
|
.Darwin,
|
||||||
|
.Linux,
|
||||||
|
.Essence,
|
||||||
|
.FreeBSD,
|
||||||
|
.OpenBSD,
|
||||||
|
.NetBSD,
|
||||||
|
.Haiku,
|
||||||
|
.WASI,
|
||||||
|
.JS,
|
||||||
|
.Orca,
|
||||||
|
.Freestanding,
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
// Defined internally by the compiler
|
// Defined internally by the compiler
|
||||||
|
|||||||
@@ -0,0 +1,239 @@
|
|||||||
|
package odin_parser
|
||||||
|
|
||||||
|
import "base:runtime"
|
||||||
|
import "core:strings"
|
||||||
|
import "core:reflect"
|
||||||
|
|
||||||
|
import "../ast"
|
||||||
|
|
||||||
|
Private_Flag :: enum {
|
||||||
|
Public,
|
||||||
|
Package,
|
||||||
|
File,
|
||||||
|
}
|
||||||
|
|
||||||
|
Build_Kind :: struct {
|
||||||
|
os: runtime.Odin_OS_Types,
|
||||||
|
arch: runtime.Odin_Arch_Types,
|
||||||
|
}
|
||||||
|
|
||||||
|
File_Tags :: struct {
|
||||||
|
build_project_name: [][]string,
|
||||||
|
build: []Build_Kind,
|
||||||
|
private: Private_Flag,
|
||||||
|
ignore: bool,
|
||||||
|
lazy: bool,
|
||||||
|
no_instrumentation: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
@require_results
|
||||||
|
get_build_os_from_string :: proc(str: string) -> runtime.Odin_OS_Type {
|
||||||
|
fields := reflect.enum_fields_zipped(runtime.Odin_OS_Type)
|
||||||
|
for os in fields {
|
||||||
|
if strings.equal_fold(os.name, str) {
|
||||||
|
return runtime.Odin_OS_Type(os.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return .Unknown
|
||||||
|
}
|
||||||
|
@require_results
|
||||||
|
get_build_arch_from_string :: proc(str: string) -> runtime.Odin_Arch_Type {
|
||||||
|
fields := reflect.enum_fields_zipped(runtime.Odin_Arch_Type)
|
||||||
|
for os in fields {
|
||||||
|
if strings.equal_fold(os.name, str) {
|
||||||
|
return runtime.Odin_Arch_Type(os.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return .Unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
@require_results
|
||||||
|
parse_file_tags :: proc(file: ast.File, allocator := context.allocator) -> (tags: File_Tags) {
|
||||||
|
context.allocator = allocator
|
||||||
|
|
||||||
|
if file.docs == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
next_char :: proc(src: string, i: ^int) -> (ch: u8) {
|
||||||
|
if i^ < len(src) {
|
||||||
|
ch = src[i^]
|
||||||
|
}
|
||||||
|
i^ += 1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
skip_whitespace :: proc(src: string, i: ^int) {
|
||||||
|
for {
|
||||||
|
switch next_char(src, i) {
|
||||||
|
case ' ', '\t':
|
||||||
|
continue
|
||||||
|
case:
|
||||||
|
i^ -= 1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scan_value :: proc(src: string, i: ^int) -> string {
|
||||||
|
start := i^
|
||||||
|
for {
|
||||||
|
switch next_char(src, i) {
|
||||||
|
case ' ', '\t', '\n', '\r', 0, ',':
|
||||||
|
i^ -= 1
|
||||||
|
return src[start:i^]
|
||||||
|
case:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
build_kinds: [dynamic]Build_Kind
|
||||||
|
defer shrink(&build_kinds)
|
||||||
|
|
||||||
|
build_project_name_strings: [dynamic]string
|
||||||
|
defer shrink(&build_project_name_strings)
|
||||||
|
|
||||||
|
build_project_names: [dynamic][]string
|
||||||
|
defer shrink(&build_project_names)
|
||||||
|
|
||||||
|
for comment in file.docs.list {
|
||||||
|
if len(comment.text) < 3 || comment.text[:2] != "//" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
text := comment.text[2:]
|
||||||
|
i := 0
|
||||||
|
|
||||||
|
skip_whitespace(text, &i)
|
||||||
|
|
||||||
|
if next_char(text, &i) == '+' {
|
||||||
|
switch scan_value(text, &i) {
|
||||||
|
case "ignore":
|
||||||
|
tags.ignore = true
|
||||||
|
case "lazy":
|
||||||
|
tags.lazy = true
|
||||||
|
case "no-instrumentation":
|
||||||
|
tags.no_instrumentation = true
|
||||||
|
case "private":
|
||||||
|
skip_whitespace(text, &i)
|
||||||
|
switch scan_value(text, &i) {
|
||||||
|
case "file":
|
||||||
|
tags.private = .File
|
||||||
|
case "package", "":
|
||||||
|
tags.private = .Package
|
||||||
|
}
|
||||||
|
case "build-project-name":
|
||||||
|
groups_loop: for {
|
||||||
|
index_start := len(build_project_name_strings)
|
||||||
|
|
||||||
|
defer append(&build_project_names, build_project_name_strings[index_start:])
|
||||||
|
|
||||||
|
for {
|
||||||
|
skip_whitespace(text, &i)
|
||||||
|
name_start := i
|
||||||
|
|
||||||
|
switch next_char(text, &i) {
|
||||||
|
case 0, '\n':
|
||||||
|
i -= 1
|
||||||
|
break groups_loop
|
||||||
|
case ',':
|
||||||
|
continue groups_loop
|
||||||
|
case '!':
|
||||||
|
// include ! in the name
|
||||||
|
case:
|
||||||
|
i -= 1
|
||||||
|
}
|
||||||
|
|
||||||
|
scan_value(text, &i)
|
||||||
|
append(&build_project_name_strings, text[name_start:i])
|
||||||
|
}
|
||||||
|
|
||||||
|
append(&build_project_names, build_project_name_strings[index_start:])
|
||||||
|
}
|
||||||
|
case "build":
|
||||||
|
kinds_loop: for {
|
||||||
|
os_positive: runtime.Odin_OS_Types
|
||||||
|
os_negative: runtime.Odin_OS_Types
|
||||||
|
|
||||||
|
arch_positive: runtime.Odin_Arch_Types
|
||||||
|
arch_negative: runtime.Odin_Arch_Types
|
||||||
|
|
||||||
|
defer append(&build_kinds, Build_Kind{
|
||||||
|
os = (os_positive == {} ? runtime.ALL_ODIN_OS_TYPES : os_positive) -os_negative,
|
||||||
|
arch = (arch_positive == {} ? runtime.ALL_ODIN_ARCH_TYPES : arch_positive)-arch_negative,
|
||||||
|
})
|
||||||
|
|
||||||
|
for {
|
||||||
|
skip_whitespace(text, &i)
|
||||||
|
|
||||||
|
is_notted: bool
|
||||||
|
switch next_char(text, &i) {
|
||||||
|
case 0, '\n':
|
||||||
|
i -= 1
|
||||||
|
break kinds_loop
|
||||||
|
case ',':
|
||||||
|
continue kinds_loop
|
||||||
|
case '!':
|
||||||
|
is_notted = true
|
||||||
|
case:
|
||||||
|
i -= 1
|
||||||
|
}
|
||||||
|
|
||||||
|
value := scan_value(text, &i)
|
||||||
|
|
||||||
|
if value == "ignore" {
|
||||||
|
tags.ignore = true
|
||||||
|
} else if os := get_build_os_from_string(value); os != .Unknown {
|
||||||
|
if is_notted {
|
||||||
|
os_negative += {os}
|
||||||
|
} else {
|
||||||
|
os_positive += {os}
|
||||||
|
}
|
||||||
|
} else if arch := get_build_arch_from_string(value); arch != .Unknown {
|
||||||
|
if is_notted {
|
||||||
|
arch_negative += {arch}
|
||||||
|
} else {
|
||||||
|
arch_positive += {arch}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tags.build = build_kinds[:]
|
||||||
|
tags.build_project_name = build_project_names[:]
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Build_Target :: struct {
|
||||||
|
os: runtime.Odin_OS_Type,
|
||||||
|
arch: runtime.Odin_Arch_Type,
|
||||||
|
project_name: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
@require_results
|
||||||
|
match_build_tags :: proc(file_tags: File_Tags, target: Build_Target) -> bool {
|
||||||
|
|
||||||
|
project_name_correct := len(target.project_name) == 0 || len(file_tags.build_project_name) == 0
|
||||||
|
|
||||||
|
for group in file_tags.build_project_name {
|
||||||
|
group_correct := true
|
||||||
|
for name in group {
|
||||||
|
if name[0] == '!' {
|
||||||
|
group_correct &&= target.project_name != name[1:]
|
||||||
|
} else {
|
||||||
|
group_correct &&= target.project_name == name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
project_name_correct ||= group_correct
|
||||||
|
}
|
||||||
|
|
||||||
|
os_and_arch_correct := len(file_tags.build) == 0
|
||||||
|
|
||||||
|
for kind in file_tags.build {
|
||||||
|
os_and_arch_correct ||= target.os in kind.os && target.arch in kind.arch
|
||||||
|
}
|
||||||
|
|
||||||
|
return !file_tags.ignore && project_name_correct && os_and_arch_correct
|
||||||
|
}
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
package test_core_odin_parser
|
||||||
|
|
||||||
|
import "base:runtime"
|
||||||
|
import "core:testing"
|
||||||
|
import "core:slice"
|
||||||
|
import "core:odin/ast"
|
||||||
|
import "core:odin/parser"
|
||||||
|
|
||||||
|
@test
|
||||||
|
test_parse_file_tags :: proc(t: ^testing.T) {
|
||||||
|
context.allocator = context.temp_allocator
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
|
|
||||||
|
Test_Case :: struct {
|
||||||
|
src: string,
|
||||||
|
tags: parser.File_Tags,
|
||||||
|
matching_targets: []struct{
|
||||||
|
target: parser.Build_Target,
|
||||||
|
result: bool,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
test_cases := []Test_Case{
|
||||||
|
{// [0]
|
||||||
|
src = ``,
|
||||||
|
tags = {},
|
||||||
|
}, {// [1]
|
||||||
|
src = `
|
||||||
|
package main
|
||||||
|
`,
|
||||||
|
tags = {},
|
||||||
|
matching_targets = {
|
||||||
|
{{.Windows, .amd64, "foo"}, true},
|
||||||
|
},
|
||||||
|
}, {// [2]
|
||||||
|
src = `
|
||||||
|
//+build linux, darwin, freebsd, openbsd, netbsd, haiku
|
||||||
|
//+build arm32, arm64
|
||||||
|
package main
|
||||||
|
`,
|
||||||
|
tags = {
|
||||||
|
build = {
|
||||||
|
{os = {.Linux}, arch = runtime.ALL_ODIN_ARCH_TYPES},
|
||||||
|
{os = {.Darwin}, arch = runtime.ALL_ODIN_ARCH_TYPES},
|
||||||
|
{os = {.FreeBSD}, arch = runtime.ALL_ODIN_ARCH_TYPES},
|
||||||
|
{os = {.OpenBSD}, arch = runtime.ALL_ODIN_ARCH_TYPES},
|
||||||
|
{os = {.NetBSD}, arch = runtime.ALL_ODIN_ARCH_TYPES},
|
||||||
|
{os = {.Haiku}, arch = runtime.ALL_ODIN_ARCH_TYPES},
|
||||||
|
{os = runtime.ALL_ODIN_OS_TYPES, arch = {.arm32}},
|
||||||
|
{os = runtime.ALL_ODIN_OS_TYPES, arch = {.arm64}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
matching_targets = {
|
||||||
|
{{.Linux, .amd64, "foo"}, true},
|
||||||
|
{{.Windows, .arm64, "foo"}, true},
|
||||||
|
{{.Windows, .amd64, "foo"}, false},
|
||||||
|
},
|
||||||
|
}, {// [3]
|
||||||
|
src = `
|
||||||
|
// +private
|
||||||
|
//+lazy
|
||||||
|
// +no-instrumentation
|
||||||
|
//+ignore
|
||||||
|
// some other comment
|
||||||
|
package main
|
||||||
|
`,
|
||||||
|
tags = {
|
||||||
|
private = .Package,
|
||||||
|
no_instrumentation = true,
|
||||||
|
lazy = true,
|
||||||
|
ignore = true,
|
||||||
|
},
|
||||||
|
matching_targets = {
|
||||||
|
{{.Linux, .amd64, "foo"}, false},
|
||||||
|
},
|
||||||
|
}, {// [4]
|
||||||
|
src = `
|
||||||
|
//+build-project-name foo !bar, baz
|
||||||
|
//+build js wasm32, js wasm64p32
|
||||||
|
package main
|
||||||
|
`,
|
||||||
|
tags = {
|
||||||
|
build_project_name = {{"foo", "!bar"}, {"baz"}},
|
||||||
|
build = {
|
||||||
|
{
|
||||||
|
os = {.JS},
|
||||||
|
arch = {.wasm32},
|
||||||
|
}, {
|
||||||
|
os = {.JS},
|
||||||
|
arch = {.wasm64p32},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
matching_targets = {
|
||||||
|
{{.JS, .wasm32, "foo"}, true},
|
||||||
|
{{.JS, .wasm64p32, "baz"}, true},
|
||||||
|
{{.JS, .wasm64p32, "bar"}, false},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for test_case, test_case_i in test_cases {
|
||||||
|
|
||||||
|
file := ast.File{
|
||||||
|
fullpath = "test.odin",
|
||||||
|
src = test_case.src,
|
||||||
|
}
|
||||||
|
|
||||||
|
p := parser.default_parser()
|
||||||
|
ok := parser.parse_file(&p, &file)
|
||||||
|
|
||||||
|
testing.expect(t, ok, "bad parse")
|
||||||
|
|
||||||
|
tags := parser.parse_file_tags(file)
|
||||||
|
|
||||||
|
|
||||||
|
build_project_name_the_same: bool
|
||||||
|
check: if len(test_case.tags.build_project_name) == len(tags.build_project_name) {
|
||||||
|
for tag, i in test_case.tags.build_project_name {
|
||||||
|
slice.equal(tag, tags.build_project_name[i]) or_break check
|
||||||
|
}
|
||||||
|
build_project_name_the_same = true
|
||||||
|
}
|
||||||
|
testing.expectf(t, build_project_name_the_same,
|
||||||
|
"[%d] file_tags.build_project_name expected:\n%#v, got:\n%#v",
|
||||||
|
test_case_i, test_case.tags.build_project_name, tags.build_project_name)
|
||||||
|
|
||||||
|
testing.expectf(t, slice.equal(test_case.tags.build, tags.build),
|
||||||
|
"[%d] file_tags.build expected:\n%#v, got:\n%#v",
|
||||||
|
test_case_i, test_case.tags.build, tags.build)
|
||||||
|
|
||||||
|
testing.expectf(t, test_case.tags.private == tags.private,
|
||||||
|
"[%d] file_tags.private expected:\n%v, got:\n%v",
|
||||||
|
test_case_i, test_case.tags.private, tags.private)
|
||||||
|
|
||||||
|
testing.expectf(t, test_case.tags.ignore == tags.ignore,
|
||||||
|
"[%d] file_tags.ignore expected:\n%v, got:\n%v",
|
||||||
|
test_case_i, test_case.tags.ignore, tags.ignore)
|
||||||
|
|
||||||
|
testing.expectf(t, test_case.tags.lazy == tags.lazy,
|
||||||
|
"[%d] file_tags.lazy expected:\n%v, got:\n%v",
|
||||||
|
test_case_i, test_case.tags.lazy, tags.lazy)
|
||||||
|
|
||||||
|
testing.expectf(t, test_case.tags.no_instrumentation == tags.no_instrumentation,
|
||||||
|
"[%d] file_tags.no_instrumentation expected:\n%v, got:\n%v",
|
||||||
|
test_case_i, test_case.tags.no_instrumentation, tags.no_instrumentation)
|
||||||
|
|
||||||
|
for target in test_case.matching_targets {
|
||||||
|
matches := parser.match_build_tags(test_case.tags, target.target)
|
||||||
|
testing.expectf(t, matches == target.result,
|
||||||
|
"[%d] Expected parser.match_build_tags(%#v) == %v, got %v",
|
||||||
|
test_case_i, target.target, target.result, matches)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user