aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDamian Tarnawski <gthetarnav@gmail.com>2024-08-12 15:21:46 +0200
committerDamian Tarnawski <gthetarnav@gmail.com>2024-08-29 20:52:27 +0200
commit92821300e4aef1b647de50ae74c0f374879e8a30 (patch)
tree3ba58e7e2d78552ffa294759cc2ed52e64b791e5
parent94a1a7aed567dd20eddfdc62044f4154d4530bab (diff)
Add a file tag parser to core:odin/parser
-rw-r--r--core/odin/parser/file_tags.odin256
-rw-r--r--tests/core/odin/test_file_tags.odin133
2 files changed, 389 insertions, 0 deletions
diff --git a/core/odin/parser/file_tags.odin b/core/odin/parser/file_tags.odin
new file mode 100644
index 000000000..229f87f1c
--- /dev/null
+++ b/core/odin/parser/file_tags.odin
@@ -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
+}
diff --git a/tests/core/odin/test_file_tags.odin b/tests/core/odin/test_file_tags.odin
new file mode 100644
index 000000000..181fac4e6
--- /dev/null
+++ b/tests/core/odin/test_file_tags.odin
@@ -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,
+ )
+ }
+}