Skip to content

Commit

Permalink
rewrite how importing works
Browse files Browse the repository at this point in the history
 * Introduce the concept of packages. Closes #3
 * Add support for error notes.
 * Introduce `@import` and `@c_import` builtin functions and
   remove the `import` and `c_import` top level declarations.
 * Introduce the `use` top level declaration.
 * Add `--check-unused` parameter to perform semantic
   analysis and codegen on all top level declarations, not
   just exported ones and ones referenced by exported ones.
 * Delete the root export node and add `--library` argument.
  • Loading branch information
andrewrk committed Mar 1, 2016
1 parent 28fe994 commit f1d3381
Show file tree
Hide file tree
Showing 31 changed files with 1,466 additions and 1,840 deletions.
3 changes: 2 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -137,12 +137,13 @@ set(ZIG_STD_SRC
"${CMAKE_SOURCE_DIR}/std/test_runner.zig"
"${CMAKE_SOURCE_DIR}/std/test_runner_libc.zig"
"${CMAKE_SOURCE_DIR}/std/test_runner_nolibc.zig"
"${CMAKE_SOURCE_DIR}/std/std.zig"
"${CMAKE_SOURCE_DIR}/std/io.zig"
"${CMAKE_SOURCE_DIR}/std/os.zig"
"${CMAKE_SOURCE_DIR}/std/syscall.zig"
"${CMAKE_SOURCE_DIR}/std/errno.zig"
"${CMAKE_SOURCE_DIR}/std/rand.zig"
"${CMAKE_SOURCE_DIR}/std/math.zig"
"${CMAKE_SOURCE_DIR}/std/index.zig"
)


Expand Down
8 changes: 2 additions & 6 deletions doc/langref.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@
```
Root = many(TopLevelDecl) "EOF"
TopLevelDecl = many(Directive) option(VisibleMod) (FnDef | ExternDecl | RootExportDecl | Import | ContainerDecl | GlobalVarDecl | ErrorValueDecl | CImportDecl | TypeDecl)
CImportDecl = "c_import" Block
TopLevelDecl = many(Directive) option(VisibleMod) (FnDef | ExternDecl | ContainerDecl | GlobalVarDecl | ErrorValueDecl | TypeDecl | UseDecl)
TypeDecl = "type" "Symbol" "=" TypeExpr ";"
Expand All @@ -23,9 +21,7 @@ StructMember = many(Directive) option(VisibleMod) (StructField | FnDef)
StructField = "Symbol" option(":" Expression) ",")
Import = "import" "String" ";"
RootExportDecl = "export" "Symbol" "String" ";"
UseDecl = "use" Expression ";"
ExternDecl = "extern" (FnProto | VariableDeclaration) ";"
Expand Down
75 changes: 75 additions & 0 deletions doc/semantic_analysis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# How Semantic Analysis Works

We start with a set of files. Typically the user only has one entry point file,
which imports the other files they want to use. However, the compiler may
choose to add more files to the compilation, for example bootstrap.zig which
contains the code that calls main.

Our goal now is to treat everything that is marked with the `export` keyword
as a root node, and then then parse and semantically analyze as little as
possible in order to fulfill these exports.

So, some parts of the code very well may have uncaught semantic errors, but as
long as the code is not referenced in any way, the compiler will not complain
because the code may as well not exist. This is similar to the fact that code
excluded from compilation with an `#ifdef` in C is not analyzed. Avoiding
analyzing unused code will save compilation time - one of Zig's goals.

So, for each file, we iterate over the top level declarations. The set of top
level declarations are:

* Function Definition
* Global Variable Declaration
* Container Declaration (struct or enum)
* Type Declaration
* Error Value Declaration
* Use Declaration

Each of these can have `export` attached to them except for error value
declarations and use declarations.

When we see a top level declaration during this iteration, we determine its
unique name identifier within the file. For example, for a function definition,
the unique name identifier is simply its name. Using this name we add the top
level declaration to a map.

If the top level declaration is exported, we add it to a set of exported top
level identifiers.

If the top level declaration is a use declaration, we add it to a set of use
declarations.

If the top level declaration is an error value declaration, we assign it a value
and increment the count of error values.

After this preliminary iteration over the top level declarations, we iterate
over the use declarations and resolve them. To resolve a use declaration, we
analyze the associated expression, verify that its type is the namespace type,
and then add all the items from the namespace into the top level declaration
map for the current file.

To analyze an expression, we recurse the abstract syntax tree of the
expression. Whenever we must look up a symbol, if the symbol exists already,
we can use it. Otherwise, we look it up in the top level declaration map.
If it exists, we can use it. Otherwise, we interrupt resolving this use
declaration to resolve the next one. If a dependency loop is detected, emit
an error. If all use declarations are resolved yet the symbol we need still
does not exist, emit an error.

To analyze an `@import` expression, find the referenced file, parse it, and
add it to the set of files to perform semantic analysis on.

Proceed through the rest of the use declarations the same way.

If we make it through the use declarations without an error, then we have a
complete map of all globals that exist in the current file.

Next we iterate over the set of exported top level declarations.

If it's a function definition, add it to the set of exported function
definitions and resolve the function prototype only. Otherwise, resolve the
top level declaration completely. This may involve recursively resolving other
top level declarations that expressions depend on.

Finally, iterate over the set of exported function definitions and analyze the
bodies.
2 changes: 1 addition & 1 deletion doc/targets.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ How to pass a byvalue struct parameter in the C calling convention is
target-specific. Add logic for how to do function prototypes and function calls
for the target when an exported or external function has a byvalue struct.

Write the target-specific code in std.zig.
Write the target-specific code in the standard library.

Update the C integer types to be the correct size for the target.

Expand Down
2 changes: 1 addition & 1 deletion doc/vim/syntax/zig.vim
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ syn keyword zigConditional if else switch
syn keyword zigRepeat while for

syn keyword zigConstant null undefined
syn keyword zigKeyword fn import c_import
syn keyword zigKeyword fn use
syn keyword zigType bool i8 u8 i16 u16 i32 u32 i64 u64 isize usize f32 f64 void unreachable type error
syn keyword zigType c_short c_ushort c_int c_uint c_long c_ulong c_longlong c_ulonglong

Expand Down
31 changes: 15 additions & 16 deletions example/guess_number/main.zig
Original file line number Diff line number Diff line change
@@ -1,39 +1,38 @@
export executable "guess_number";

import "std.zig";
import "rand.zig";
import "os.zig";
const std = @import("std");
const io = std.io;
const Rand = std.Rand;
const os = std.os;

pub fn main(args: [][]u8) -> %void {
%%stdout.printf("Welcome to the Guess Number Game in Zig.\n");
%%io.stdout.printf("Welcome to the Guess Number Game in Zig.\n");

var seed : u32 = undefined;
const seed_bytes = (&u8)(&seed)[0...4];
%%os_get_random_bytes(seed_bytes);
%%os.get_random_bytes(seed_bytes);

var rand = rand_new(seed);
var rand = Rand.init(seed);

const answer = rand.range_u64(0, 100) + 1;

while (true) {
%%stdout.printf("\nGuess a number between 1 and 100: ");
%%io.stdout.printf("\nGuess a number between 1 and 100: ");
var line_buf : [20]u8 = undefined;

const line_len = stdin.read(line_buf) %% |err| {
%%stdout.printf("Unable to read from stdin.\n");
const line_len = io.stdin.read(line_buf) %% |err| {
%%io.stdout.printf("Unable to read from stdin.\n");
return err;
};

const guess = parse_u64(line_buf[0...line_len - 1], 10) %% {
%%stdout.printf("Invalid number.\n");
const guess = io.parse_u64(line_buf[0...line_len - 1], 10) %% {
%%io.stdout.printf("Invalid number.\n");
continue;
};
if (guess > answer) {
%%stdout.printf("Guess lower.\n");
%%io.stdout.printf("Guess lower.\n");
} else if (guess < answer) {
%%stdout.printf("Guess higher.\n");
%%io.stdout.printf("Guess higher.\n");
} else {
%%stdout.printf("You win!\n");
%%io.stdout.printf("You win!\n");
return;
}
}
Expand Down
6 changes: 2 additions & 4 deletions example/hello_world/hello.zig
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
export executable "hello";

import "std.zig";
const io = @import("std").io;

pub fn main(args: [][]u8) -> %void {
%%stdout.printf("Hello, world!\n");
%%io.stdout.printf("Hello, world!\n");
}
9 changes: 2 additions & 7 deletions example/hello_world/hello_libc.zig
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
#link("c")
export executable "hello";

c_import {
@c_include("stdio.h");
}
const c = @c_import(@c_include("stdio.h"));

export fn main(argc: c_int, argv: &&u8) -> c_int {
printf(c"Hello, world!\n");
c.printf(c"Hello, world!\n");
return 0;
}
Loading

0 comments on commit f1d3381

Please sign in to comment.