Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] core:odin/frontend #2960

Draft
wants to merge 18 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,050 changes: 1,050 additions & 0 deletions core/odin/frontend/ast.odin

Large diffs are not rendered by default.

30 changes: 30 additions & 0 deletions core/odin/frontend/checker.odin
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package odin_frontend

import "core:sync"

Scope_Flag :: enum i32 {

}

Scope_Flags :: distinct bit_set[Scope_Flag; i32]

Scope :: struct {
node : ^Node,
parent : ^Scope,
next : ^Scope,
head_child: ^Scope,

mutex: sync.RW_Mutex,
elements: map[string]^Entity,

imported: map[^Scope]bool,

flags: Scope_Flags,

variant: union {
^Package,
^File,
^Entity, // procedure_entry
}
}

33 changes: 33 additions & 0 deletions core/odin/frontend/directives.odin
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package odin_frontend

Directive_Kind :: enum {
// Record memory layout
Packed,
Raw_Union,
Align,
No_Nil,
// Control statements
Partial,
// Procedure parameters
No_Alias,
Any_Int,
Caller_Location,
C_Vararg,
By_Ptr,
Optional_Ok,
// Expressions
Type,
// Statements
Bounds_Check,
No_Bounds_Check,
// Built-in procedures
Assert,
Panic,
Config, // (<identifier>, default)
Defined, // (identifier)
File, Line, Procedure,
Location, // (<entity>)
Load,
Load_Or,
Load_Hash,
}
5 changes: 5 additions & 0 deletions core/odin/frontend/entity.odin
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package odin_frontend

Entity :: struct {

}
52 changes: 52 additions & 0 deletions core/odin/frontend/errors.odin
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package odin_frontend

import "core:fmt"

Warning_Handler :: #type proc(pos: Pos, format: string, args: ..any)
Error_Handler :: #type proc(pos: Pos, format: string, args: ..any)

default_warning_handler :: proc(pos: Pos, msg: string, args: ..any) {
fmt.eprintf("%s(%d:%d): Warning: ", pos.file, pos.line, pos.column)
fmt.eprintf(msg, ..args)
fmt.eprintf("\n")
}

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")
}

tokenizer_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
}

parser_error :: proc(p: ^Parser, pos: Pos, msg: string, args: ..any) {
if p.err != nil {
p.err(pos, msg, ..args)
}
//p.file.syntax_error_count += 1
//p.error_count += 1
// TODO(Dragos): Modify this
}

parser_warn :: proc(p: ^Parser, pos: Pos, msg: string, args: ..any) {
if p.warn != nil {
p.warn(pos, msg, ..args)
}
//p.file.syntax_warning_count += 1
}

error :: proc {
tokenizer_error,
parser_error,
}

warn :: proc {
parser_warn,
}

2 changes: 2 additions & 0 deletions core/odin/frontend/frontend.odin
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
package odin_frontend

55 changes: 55 additions & 0 deletions core/odin/frontend/frontend_test.odin
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package odin_frontend

import "core:os"
import "core:testing"
import "core:fmt"
import "core:strings"
import "core:path/filepath"

@test
test_tokenizer :: proc(T: ^testing.T) {
sb := strings.builder_make()
defer strings.builder_destroy(&sb)
tokenizer: Tokenizer
src_path := "examples/demo/demo.odin"
src, src_ok := os.read_entire_file(src_path)
testing.expect(T, src_ok, "Failed to read the input file")
tokenizer_init(&tokenizer, string(src), src_path)

for tok := scan(&tokenizer); tok.kind != .EOF; tok = scan(&tokenizer) {
fmt.sbprintf(&sb, "[%v](%d:%d):%v\n", tok.kind, tok.pos.line, tok.pos.column, tok.text)
}
str := strings.to_string(sb)
out_ok := os.write_entire_file("demo_tokens.txt", transmute([]byte)str)
testing.expect(T, out_ok, "Failed to write demo_tokens.txt")
testing.expect(T, tokenizer.error_count == 0, "Tokenization failed with errors")
}

@test
test_paths :: proc(T: ^testing.T) {
ok: bool
parser := default_parser()
// Note(Dragos): Parser doesn't need collections. Only the type checker does
ok = parser_add_collection(&parser, "core", filepath.join({ODIN_ROOT, "core"}, context.temp_allocator))
ok = parser_add_collection(&parser, "vendor", filepath.join({ODIN_ROOT, "vendor"}, context.temp_allocator))
ok = parser_add_collection(&parser, "shared", filepath.join({ODIN_ROOT, "shared"}, context.temp_allocator))
testing.expect(T, ok)
}

@test
test_file_loading :: proc(T: ^testing.T) {
ok: bool
pkg: ^Package
pkg, ok = read_package("examples/demo", context.allocator, context.allocator)
testing.expect(T, ok, "Failed to read package")
testing.expect(T, len(pkg.files) == 1, "Failed to read the files")
for path, file in pkg.files {
fmt.printf("Read file %s\n", path)

}
}

@test
test_parser :: proc(T: ^testing.T) {

}
157 changes: 157 additions & 0 deletions core/odin/frontend/package.odin
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package odin_frontend

import "core:path/filepath"
import "core:os"
import "core:sync"
import "core:fmt"
import "core:strings"
import "core:runtime"
import "core:mem"

/*
File :: struct {
using node: Node,
id: int,
pkg: ^Package,

fullpath: string,
src: string,

docs: ^Comment_Group,

pkg_decl: ^Package_Decl,
pkg_token: Token,
pkg_name: string,

decls: [dynamic]^Stmt,
imports: [dynamic]^Import_Decl,
directive_count: int,

comments: [dynamic]^Comment_Group,

syntax_warning_count: int,
syntax_error_count: int,
}
*/

Package_Kind :: enum {
Normal,
Runtime,
Init,
}


Foreign_File_Kind :: enum {
Invalid,
Source,
}

Foreign_File :: struct {
kind: Foreign_File_Kind,
source: string,
}

File_Flag :: enum u32 {
Is_Private_Pkg = 1<<0,
Is_Private_File = 1<<1,

Is_Test = 1<<3,
Is_Lazy = 1<<4,
}
File_Flags :: bit_set[File_Flag]


File :: struct {
id: int,

pkg: ^Package,
pkg_decl: ^Node,

src: string,
fullpath: string,
filename: string,
directory: string,

tokens: [dynamic]Token,

docs: ^Comment_Group,
}

Package :: struct {
kind: Package_Kind,
id: int,
name: string,
fullpath: string,
files: map[string]^File,

is_single_file: bool,
order: int,

file_allocator: mem.Allocator,
}

read_file :: proc(pkg: ^Package, path: string) -> (file: ^File) {
context.allocator = pkg.file_allocator
fullpath, fullpath_ok := filepath.abs(path)
if !fullpath_ok {
return nil
}
fmt.assertf(fullpath not_in pkg.files, "File %s already part of the package\n", fullpath)
src, src_ok := os.read_entire_file(fullpath)
if !src_ok {
return nil
}
file = new(File)
file.fullpath = fullpath
file.filename = filepath.base(file.fullpath)
file.directory = filepath.dir(file.filename)
file.src = string(src)
file.tokens = make([dynamic]Token) // Note(Dragos): Maybe this can have a different allocator
file.pkg = pkg
pkg.files[file.fullpath] = file
return file
}

delete_file :: proc(file: ^File) {
fmt.assertf(file.fullpath in file.pkg.files, "File %s is not part of the package\n", file.fullpath)
context.allocator = file.pkg.file_allocator
delete_key(&file.pkg.files, file.fullpath)
delete(file.fullpath)
delete(file.directory)
free(file)
}

read_package :: proc(path: string, file_allocator: mem.Allocator, allocator := context.allocator) -> (pkg: ^Package, ok: bool) {
context.allocator = allocator
pkg_path, pkg_path_ok := filepath.abs(path)
if !pkg_path_ok {
return nil, false
}
path_pattern := fmt.tprintf("%s/*.odin", pkg_path)
matches, matches_err := filepath.glob(path_pattern, context.temp_allocator)
if matches_err != nil {
return nil, false
}

pkg = new(Package)
pkg.fullpath = pkg_path
pkg.files = make(map[string]^File)
pkg.file_allocator = file_allocator
defer if !ok {
delete(pkg.fullpath)
delete(pkg.files)
free(pkg)
}
for match in matches {
file := read_file(pkg, match)
if file == nil {
return nil, false
}
}

return pkg, true
}

delete_package :: proc(pkg: ^Package, allocator := context.allocator) {
context.allocator = allocator
}
Loading