mirror of
https://github.com/Ed94/Odin.git
synced 2026-06-13 01:21:38 -07:00
Add a file tag parser to core:odin/parser
This commit is contained in:
@@ -0,0 +1,256 @@
|
||||
package odin_parser
|
||||
|
||||
import "base:runtime"
|
||||
import "core:strings"
|
||||
|
||||
import "../ast"
|
||||
|
||||
Private_Flag :: enum {
|
||||
Public,
|
||||
Package,
|
||||
File,
|
||||
}
|
||||
|
||||
Odin_OS_Type :: runtime.Odin_OS_Type
|
||||
Odin_Arch_Type :: runtime.Odin_Arch_Type
|
||||
|
||||
Odin_OS_Types :: bit_set[Odin_OS_Type]
|
||||
Odin_Arch_Types :: bit_set[Odin_Arch_Type]
|
||||
|
||||
Build_Kind :: struct {
|
||||
os: Odin_OS_Types,
|
||||
arch: Odin_Arch_Types,
|
||||
}
|
||||
|
||||
File_Tags :: struct {
|
||||
build_project_name: []string,
|
||||
build: []Build_Kind,
|
||||
private: Private_Flag,
|
||||
ignore: bool,
|
||||
lazy: bool,
|
||||
no_instrumentation: bool,
|
||||
}
|
||||
|
||||
ALL_ODIN_OS_TYPES :: Odin_OS_Types{
|
||||
.Windows,
|
||||
.Darwin,
|
||||
.Linux,
|
||||
.Essence,
|
||||
.FreeBSD,
|
||||
.OpenBSD,
|
||||
.NetBSD,
|
||||
.Haiku,
|
||||
.WASI,
|
||||
.JS,
|
||||
.Orca,
|
||||
.Freestanding,
|
||||
}
|
||||
ALL_ODIN_ARCH_TYPES :: Odin_Arch_Types{
|
||||
.amd64,
|
||||
.i386,
|
||||
.arm32,
|
||||
.arm64,
|
||||
.wasm32,
|
||||
.wasm64p32,
|
||||
}
|
||||
|
||||
ODIN_OS_NAMES :: [Odin_OS_Type]string{
|
||||
.Unknown = "",
|
||||
.Windows = "windows",
|
||||
.Darwin = "darwin",
|
||||
.Linux = "linux",
|
||||
.Essence = "essence",
|
||||
.FreeBSD = "freebsd",
|
||||
.OpenBSD = "openbsd",
|
||||
.NetBSD = "netbsd",
|
||||
.Haiku = "haiku",
|
||||
.WASI = "wasi",
|
||||
.JS = "js",
|
||||
.Orca = "orca",
|
||||
.Freestanding = "freestanding",
|
||||
}
|
||||
|
||||
ODIN_ARCH_NAMES :: [Odin_Arch_Type]string{
|
||||
.Unknown = "",
|
||||
.amd64 = "amd64",
|
||||
.i386 = "i386",
|
||||
.arm32 = "arm32",
|
||||
.arm64 = "arm64",
|
||||
.wasm32 = "wasm32",
|
||||
.wasm64p32 = "wasm64p32",
|
||||
}
|
||||
|
||||
@require_results
|
||||
get_build_os_from_string :: proc(str: string) -> Odin_OS_Type {
|
||||
for os_name, os in ODIN_OS_NAMES {
|
||||
if strings.equal_fold(os_name, str) {
|
||||
return os
|
||||
}
|
||||
}
|
||||
return .Unknown
|
||||
}
|
||||
@require_results
|
||||
get_build_arch_from_string :: proc(str: string) -> Odin_Arch_Type {
|
||||
for arch_name, arch in ODIN_ARCH_NAMES {
|
||||
if strings.equal_fold(arch_name, str) {
|
||||
return arch
|
||||
}
|
||||
}
|
||||
return .Unknown
|
||||
}
|
||||
|
||||
parse_file_tags :: proc(file: ast.File) -> (tags: File_Tags) {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
skip_rest_of_line :: proc(src: string, i: ^int) {
|
||||
for {
|
||||
switch next_char(src, i) {
|
||||
case '\n', 0:
|
||||
return
|
||||
case:
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
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_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":
|
||||
values_loop: for {
|
||||
skip_whitespace(text, &i)
|
||||
|
||||
name_start := i
|
||||
|
||||
switch next_char(text, &i) {
|
||||
case 0, '\n':
|
||||
i -= 1
|
||||
break values_loop
|
||||
case '!':
|
||||
// include ! in the name
|
||||
case:
|
||||
i -= 1
|
||||
}
|
||||
|
||||
scan_value(text, &i)
|
||||
append(&build_project_names, text[name_start:i])
|
||||
}
|
||||
case "build":
|
||||
kinds_loop: for {
|
||||
os_positive: Odin_OS_Types
|
||||
os_negative: Odin_OS_Types
|
||||
|
||||
arch_positive: Odin_Arch_Types
|
||||
arch_negative: Odin_Arch_Types
|
||||
|
||||
defer append(&build_kinds, Build_Kind{
|
||||
os = (os_positive == {} ? ALL_ODIN_OS_TYPES : os_positive) -os_negative,
|
||||
arch = (arch_positive == {} ? 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}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
skip_rest_of_line(text, &i)
|
||||
}
|
||||
|
||||
tags.build = build_kinds[:]
|
||||
tags.build_project_name = build_project_names[:]
|
||||
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
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,
|
||||
}
|
||||
|
||||
test_cases := []Test_Case{
|
||||
{// [0]
|
||||
src = ``,
|
||||
tags = {},
|
||||
}, {// [1]
|
||||
src = `
|
||||
package main
|
||||
`,
|
||||
tags = {},
|
||||
}, {// [2]
|
||||
src = `
|
||||
//+build linux, darwin, freebsd, openbsd, netbsd, haiku
|
||||
//+build arm32, arm64
|
||||
package main
|
||||
`,
|
||||
tags = {
|
||||
build = {
|
||||
{os = {.Linux}, arch = parser.ALL_ODIN_ARCH_TYPES},
|
||||
{os = {.Darwin}, arch = parser.ALL_ODIN_ARCH_TYPES},
|
||||
{os = {.FreeBSD}, arch = parser.ALL_ODIN_ARCH_TYPES},
|
||||
{os = {.OpenBSD}, arch = parser.ALL_ODIN_ARCH_TYPES},
|
||||
{os = {.NetBSD}, arch = parser.ALL_ODIN_ARCH_TYPES},
|
||||
{os = {.Haiku}, arch = parser.ALL_ODIN_ARCH_TYPES},
|
||||
{os = parser.ALL_ODIN_OS_TYPES, arch = {.arm32}},
|
||||
{os = parser.ALL_ODIN_OS_TYPES, arch = {.arm64}},
|
||||
},
|
||||
},
|
||||
}, {// [3]
|
||||
src = `
|
||||
// +private
|
||||
//+lazy
|
||||
// +no-instrumentation
|
||||
//+ignore
|
||||
// some other comment
|
||||
package main
|
||||
`,
|
||||
tags = {
|
||||
private = .Package,
|
||||
no_instrumentation = true,
|
||||
lazy = true,
|
||||
ignore = true,
|
||||
},
|
||||
}, {// [4]
|
||||
src = `
|
||||
//+build-project-name foo !bar
|
||||
//+build js wasm32, js wasm64p32
|
||||
package main
|
||||
`,
|
||||
tags = {
|
||||
build_project_name = {"foo", "!bar"},
|
||||
build = {
|
||||
{
|
||||
os = {.JS},
|
||||
arch = {.wasm32},
|
||||
}, {
|
||||
os = {.JS},
|
||||
arch = {.wasm64p32},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expect :: proc(t: ^testing.T, ok: bool, name: string, i: int, expected, actual: $T, loc := #caller_location) {
|
||||
testing.expectf(t, ok,
|
||||
"[%d] expected %s:\n\e[0;32m%#v\e[0m, actual:\n\e[0;31m%#v\e[0m",
|
||||
i, name, expected, actual, loc=loc
|
||||
)
|
||||
}
|
||||
|
||||
for 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)
|
||||
|
||||
expect(t,
|
||||
slice.equal(test_case.tags.build_project_name, tags.build_project_name),
|
||||
"build_project_name", i, test_case.tags.build_project_name, tags.build_project_name,
|
||||
)
|
||||
|
||||
expect(t,
|
||||
slice.equal(test_case.tags.build, tags.build),
|
||||
"build", i, test_case.tags.build, tags.build,
|
||||
)
|
||||
|
||||
expect(t,
|
||||
test_case.tags.private == tags.private,
|
||||
"private", i, test_case.tags.private, tags.private,
|
||||
)
|
||||
|
||||
expect(t,
|
||||
test_case.tags.ignore == tags.ignore,
|
||||
"ignore", i, test_case.tags.ignore, tags.ignore,
|
||||
)
|
||||
|
||||
expect(t,
|
||||
test_case.tags.lazy == tags.lazy,
|
||||
"lazy", i, test_case.tags.lazy, tags.lazy,
|
||||
)
|
||||
|
||||
expect(t,
|
||||
test_case.tags.no_instrumentation == tags.no_instrumentation,
|
||||
"no_instrumentation", i, test_case.tags.no_instrumentation, tags.no_instrumentation,
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user