diff --git a/README.md b/README.md index fe130e6f4..0de6d8d5d 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,9 @@ readable, safe, optimal, and concise code to solve any computing problem. ## Roadmap - * Simple .so library + * Math expression + * Export .so library + * Export .o file * Multiple files * inline assembly and syscalls * running code at compile time @@ -66,7 +68,9 @@ zig | C equivalent | Description ### Grammar ``` -Root : many(TopLevelDecl) token(EOF) +Root : RootExportDecl many(TopLevelDecl) token(EOF) + +RootExportDecl : token(Export) token(Symbol) token(String) token(Semicolon) TopLevelDecl : FnDef | ExternBlock diff --git a/doc/vim/syntax/zig.vim b/doc/vim/syntax/zig.vim index df04a7988..9acf57a7d 100644 --- a/doc/vim/syntax/zig.vim +++ b/doc/vim/syntax/zig.vim @@ -10,7 +10,22 @@ endif syn keyword zigKeyword fn return mut const extern unreachable export pub syn keyword zigType bool i8 u8 i16 u16 i32 u32 i64 u64 isize usize f32 f64 f128 void +syn region zigCommentLine start="//" end="$" contains=zigTodo,@Spell +syn region zigCommentLineDoc start="//\%(//\@!\|!\)" end="$" contains=zigTodo,@Spell +syn region zigCommentBlock matchgroup=zigCommentBlock start="/\*\%(!\|\*[*/]\@!\)\@!" end="\*/" contains=zigTodo,zigCommentBlockNest,@Spell +syn region zigCommentBlockDoc matchgroup=zigCommentBlockDoc start="/\*\%(!\|\*[*/]\@!\)" end="\*/" contains=zigTodo,zigCommentBlockDocNest,@Spell +syn region zigCommentBlockNest matchgroup=zigCommentBlock start="/\*" end="\*/" contains=zigTodo,zigCommentBlockNest,@Spell contained transparent +syn region zigCommentBlockDocNest matchgroup=zigCommentBlockDoc start="/\*" end="\*/" contains=zigTodo,zigCommentBlockDocNest,@Spell contained transparent + +syn keyword zigTodo contained TODO XXX + let b:current_syntax = "zig" hi def link zigKeyword Keyword hi def link zigType Type +hi def link zigCommentLine Comment +hi def link zigCommentLineDoc SpecialComment +hi def link zigCommentBlock zigCommentLine +hi def link zigCommentBlockDoc zigCommentLineDoc +hi def link zigTodo Todo + diff --git a/example/hello.zig b/example/hello.zig index 9e6257eaf..d9efc46e9 100644 --- a/example/hello.zig +++ b/example/hello.zig @@ -1,3 +1,5 @@ +export executable "hello"; + #link("c") extern { fn puts(s: *mut u8) -> i32; diff --git a/example/math.zig b/example/math.zig new file mode 100644 index 000000000..aa135426e --- /dev/null +++ b/example/math.zig @@ -0,0 +1,6 @@ +export library "math"; + +export fn add(a: i32, b: i32) -> i32 { + return a + b; +} + diff --git a/src/codegen.cpp b/src/codegen.cpp index 3f339ad73..01d8adadd 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -75,6 +75,8 @@ struct CodeGen { ZigList block_scopes; llvm::DIFile *di_file; ZigList fn_defs; + Buf *out_name; + OutType out_type; }; struct TypeNode { @@ -103,6 +105,8 @@ CodeGen *create_codegen(AstNode *root, Buf *in_full_path) { g->is_static = false; g->build_type = CodeGenBuildTypeDebug; g->strip_debug_symbols = false; + g->out_name = nullptr; + g->out_type = OutTypeUnknown; os_path_split(in_full_path, &g->in_dir, &g->in_file); return g; @@ -120,6 +124,14 @@ void codegen_set_strip(CodeGen *g, bool strip) { g->strip_debug_symbols = strip; } +void codegen_set_out_type(CodeGen *g, OutType out_type) { + g->out_type = out_type; +} + +void codegen_set_out_name(CodeGen *g, Buf *out_name) { + g->out_name = out_name; +} + static void add_node_error(CodeGen *g, AstNode *node, Buf *msg) { g->errors.add_one(); ErrorMsg *last_msg = &g->errors.last(); @@ -294,6 +306,7 @@ static void find_declarations(CodeGen *g, AstNode *node) { case NodeTypeBlock: case NodeTypeExpression: case NodeTypeFnCall: + case NodeTypeRootExportDecl: zig_unreachable(); } } @@ -355,15 +368,50 @@ static void check_fn_def_control_flow(CodeGen *g, AstNode *node) { static void analyze_node(CodeGen *g, AstNode *node) { switch (node->type) { case NodeTypeRoot: - // Iterate once over the top level declarations to build the function table - for (int i = 0; i < node->data.root.top_level_decls.length; i += 1) { - AstNode *child = node->data.root.top_level_decls.at(i); - find_declarations(g, child); - } - for (int i = 0; i < node->data.root.top_level_decls.length; i += 1) { - AstNode *child = node->data.root.top_level_decls.at(i); - analyze_node(g, child); + { + AstNode *root_export_decl_node = node->data.root.root_export_decl; + if (root_export_decl_node) { + assert(root_export_decl_node->type == NodeTypeRootExportDecl); + if (!g->out_name) + g->out_name = &root_export_decl_node->data.root_export_decl.name; + + Buf *out_type = &root_export_decl_node->data.root_export_decl.type; + OutType export_out_type; + if (buf_eql_str(out_type, "executable")) { + export_out_type = OutTypeExe; + } else if (buf_eql_str(out_type, "library")) { + export_out_type = OutTypeLib; + } else if (buf_eql_str(out_type, "object")) { + export_out_type = OutTypeObj; + } else { + add_node_error(g, root_export_decl_node, + buf_sprintf("invalid export type: '%s'", buf_ptr(out_type))); + } + if (g->out_type == OutTypeUnknown) + g->out_type = export_out_type; + } else { + if (!g->out_name) { + add_node_error(g, node, + buf_sprintf("missing export declaration and output name not provided")); + } else if (g->out_type == OutTypeUnknown) { + add_node_error(g, node, + buf_sprintf("missing export declaration and export type not provided")); + } + } + + // Iterate once over the top level declarations to build the function table + for (int i = 0; i < node->data.root.top_level_decls.length; i += 1) { + AstNode *child = node->data.root.top_level_decls.at(i); + find_declarations(g, child); + } + for (int i = 0; i < node->data.root.top_level_decls.length; i += 1) { + AstNode *child = node->data.root.top_level_decls.at(i); + analyze_node(g, child); + } + break; } + case NodeTypeRootExportDecl: + // handled in parent break; case NodeTypeExternBlock: for (int fn_decl_i = 0; fn_decl_i < node->data.extern_block.fn_decls.length; fn_decl_i += 1) { @@ -674,6 +722,7 @@ static void gen_block(CodeGen *g, AstNode *block_node, bool add_implicit_return) case NodeTypeFnCall: case NodeTypeExternBlock: case NodeTypeDirective: + case NodeTypeRootExportDecl: zig_unreachable(); } } @@ -929,6 +978,15 @@ static Buf *get_dynamic_linker(CodeGen *g) { } } +/* + +# static link into libfoo.a +ar cq libfoo.a foo1.o foo2.o + +# dynamic link into libfoo.so +gcc -fPIC -g -Werror -pedantic -shared -Wl,-soname,libsoundio.so.1 -o libsoundio.so.1.0.3 foo1.o foo2.o -ljack -lpulse -lasound -lpthread + +*/ void code_gen_link(CodeGen *g, const char *out_file) { LLVMPassRegistryRef registry = LLVMGetGlobalPassRegistry(); LLVMInitializeCore(registry); @@ -937,6 +995,10 @@ void code_gen_link(CodeGen *g, const char *out_file) { LLVMZigInitializeLowerIntrinsicsPass(registry); LLVMZigInitializeUnreachableBlockElimPass(registry); + if (!out_file) { + out_file = buf_ptr(g->out_name); + } + Buf out_file_o = BUF_INIT; buf_init_from_str(&out_file_o, out_file); buf_append_str(&out_file_o, ".o"); diff --git a/src/codegen.hpp b/src/codegen.hpp index bfc880b2c..ee2557f7b 100644 --- a/src/codegen.hpp +++ b/src/codegen.hpp @@ -12,6 +12,14 @@ struct CodeGen; +enum OutType { + OutTypeUnknown, + OutTypeExe, + OutTypeLib, + OutTypeObj, +}; + + struct ErrorMsg { int line_start; int column_start; @@ -30,6 +38,8 @@ enum CodeGenBuildType { void codegen_set_build_type(CodeGen *codegen, CodeGenBuildType build_type); void codegen_set_is_static(CodeGen *codegen, bool is_static); void codegen_set_strip(CodeGen *codegen, bool strip); +void codegen_set_out_type(CodeGen *codegen, OutType out_type); +void codegen_set_out_name(CodeGen *codegen, Buf *out_name); void semantic_analyze(CodeGen *g); diff --git a/src/main.cpp b/src/main.cpp index 8e1ced7cb..c16ab50b2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -28,18 +28,24 @@ static int usage(const char *arg0) { fprintf(stderr, "Usage: %s [command] [options] target\n" "Commands:\n" - " build create an executable from target\n" - "Options:\n" - " --output output file\n" - " --version print version number and exit\n" - " -Ipath add path to header include path\n" - " --release build with optimizations on\n" - " --strip exclude debug symbols\n" - " --static build a static executable\n" + " build create executable, object, or library from target\n" + " version print version number and exit\n" + "Optional Options:\n" + " --release build with optimizations on and debug protection off\n" + " --static output will be statically linked\n" + " --strip exclude debug symbols\n" + " --export [exe|lib|obj] override output type\n" + " --name [name] override output name\n" + " --output [file] override destination path\n" , arg0); return EXIT_FAILURE; } +static int version(void) { + printf("%s\n", ZIG_VERSION_STRING); + return EXIT_SUCCESS; +} + static Buf *fetch_file(FILE *f) { int fd = fileno(f); struct stat st; @@ -58,12 +64,12 @@ static Buf *fetch_file(FILE *f) { return buf; } -static int build(const char *arg0, const char *in_file, const char *out_file, - ZigList *include_paths, bool release, bool strip, bool is_static) +static int build(const char *arg0, const char *in_file, const char *out_file, bool release, + bool strip, bool is_static, OutType out_type, char *out_name) { static char cur_dir[1024]; - if (!in_file || !out_file) + if (!in_file) return usage(arg0); FILE *in_f; @@ -100,6 +106,10 @@ static int build(const char *arg0, const char *in_file, const char *out_file, codegen_set_build_type(codegen, release ? CodeGenBuildTypeRelease : CodeGenBuildTypeDebug); codegen_set_strip(codegen, strip); codegen_set_is_static(codegen, is_static); + if (out_type != OutTypeUnknown) + codegen_set_out_type(codegen, out_type); + if (out_name) + codegen_set_out_name(codegen, buf_create_from_str(out_name)); semantic_analyze(codegen); ZigList *errors = codegen_error_messages(codegen); if (errors->length == 0) { @@ -135,25 +145,25 @@ static int build(const char *arg0, const char *in_file, const char *out_file, enum Cmd { CmdNone, CmdBuild, + CmdVersion, }; int main(int argc, char **argv) { char *arg0 = argv[0]; char *in_file = NULL; char *out_file = NULL; - ZigList include_paths = {0}; bool release = false; bool strip = false; bool is_static = false; + OutType out_type = OutTypeUnknown; + char *out_name = NULL; + Cmd cmd = CmdNone; for (int i = 1; i < argc; i += 1) { char *arg = argv[i]; if (arg[0] == '-' && arg[1] == '-') { - if (strcmp(arg, "--version") == 0) { - printf("%s\n", ZIG_VERSION_STRING); - return EXIT_SUCCESS; - } else if (strcmp(arg, "--release") == 0) { + if (strcmp(arg, "--release") == 0) { release = true; } else if (strcmp(arg, "--strip") == 0) { strip = true; @@ -165,15 +175,27 @@ int main(int argc, char **argv) { i += 1; if (strcmp(arg, "--output") == 0) { out_file = argv[i]; + } else if (strcmp(arg, "--export") == 0) { + if (strcmp(argv[i], "exe") == 0) { + out_type = OutTypeExe; + } else if (strcmp(argv[i], "lib") == 0) { + out_type = OutTypeLib; + } else if (strcmp(argv[i], "obj") == 0) { + out_type = OutTypeObj; + } else { + return usage(arg0); + } + } else if (strcmp(arg, "--name") == 0) { + out_name = argv[i]; } else { return usage(arg0); } } - } else if (arg[0] == '-' && arg[1] == 'I') { - include_paths.append(arg + 2); } else if (cmd == CmdNone) { if (strcmp(arg, "build") == 0) { cmd = CmdBuild; + } else if (strcmp(arg, "version") == 0) { + cmd = CmdVersion; } else { fprintf(stderr, "Unrecognized command: %s\n", arg); return usage(arg0); @@ -189,6 +211,8 @@ int main(int argc, char **argv) { return usage(arg0); } break; + case CmdVersion: + return usage(arg0); } } } @@ -197,9 +221,10 @@ int main(int argc, char **argv) { case CmdNone: return usage(arg0); case CmdBuild: - return build(arg0, in_file, out_file, &include_paths, release, strip, is_static); + return build(arg0, in_file, out_file, release, strip, is_static, out_type, out_name); + case CmdVersion: + return version(); } zig_unreachable(); } - diff --git a/src/parser.cpp b/src/parser.cpp index d9f488a25..d8694b7aa 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -29,6 +29,8 @@ const char *node_type_str(NodeType node_type) { switch (node_type) { case NodeTypeRoot: return "Root"; + case NodeTypeRootExportDecl: + return "RootExportDecl"; case NodeTypeFnDef: return "FnDef"; case NodeTypeFnDecl: @@ -68,6 +70,11 @@ void ast_print(AstNode *node, int indent) { ast_print(child, indent + 2); } break; + case NodeTypeRootExportDecl: + fprintf(stderr, "%s %s '%s'\n", node_type_str(node->type), + buf_ptr(&node->data.root_export_decl.type), + buf_ptr(&node->data.root_export_decl.name)); + break; case NodeTypeFnDef: { fprintf(stderr, "%s\n", node_type_str(node->type)); @@ -714,6 +721,36 @@ static void ast_parse_top_level_decls(ParseContext *pc, int *token_index, ZigLis zig_unreachable(); } +static AstNode *ast_parse_root_export_decl(ParseContext *pc, int *token_index) { + Token *export_kw = &pc->tokens->at(*token_index); + if (export_kw->id != TokenIdKeywordExport) + return nullptr; + *token_index += 1; + + AstNode *node = ast_create_node(NodeTypeRootExportDecl, export_kw); + + Token *export_type = &pc->tokens->at(*token_index); + *token_index += 1; + ast_expect_token(pc, export_type, TokenIdSymbol); + + ast_buf_from_token(pc, export_type, &node->data.root_export_decl.type); + + Token *export_name = &pc->tokens->at(*token_index); + *token_index += 1; + ast_expect_token(pc, export_name, TokenIdStringLiteral); + + parse_string_literal(pc, export_name, &node->data.root_export_decl.name); + + Token *semicolon = &pc->tokens->at(*token_index); + *token_index += 1; + ast_expect_token(pc, semicolon, TokenIdSemicolon); + + return node; +} + +/* +Root : RootExportDecl many(TopLevelDecl) token(EOF) + */ AstNode *ast_parse(Buf *buf, ZigList *tokens) { ParseContext pc = {0}; pc.buf = buf; @@ -721,6 +758,9 @@ AstNode *ast_parse(Buf *buf, ZigList *tokens) { pc.tokens = tokens; int token_index = 0; + + pc.root->data.root.root_export_decl = ast_parse_root_export_decl(&pc, &token_index); + ast_parse_top_level_decls(&pc, &token_index, &pc.root->data.root.top_level_decls); if (token_index != tokens->length - 1) { diff --git a/src/parser.hpp b/src/parser.hpp index 8d92a40a7..9a7272815 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -17,6 +17,7 @@ struct CodeGenNode; enum NodeType { NodeTypeRoot, + NodeTypeRootExportDecl, NodeTypeFnProto, NodeTypeFnDef, NodeTypeFnDecl, @@ -31,6 +32,7 @@ enum NodeType { }; struct AstNodeRoot { + AstNode *root_export_decl; ZigList top_level_decls; }; @@ -113,6 +115,11 @@ struct AstNodeDirective { Buf param; }; +struct AstNodeRootExportDecl { + Buf type; + Buf name; +}; + struct AstNode { enum NodeType type; AstNode *parent; @@ -121,6 +128,7 @@ struct AstNode { CodeGenNode *codegen_node; union { AstNodeRoot root; + AstNodeRootExportDecl root_export_decl; AstNodeFnDef fn_def; AstNodeFnDecl fn_decl; AstNodeFnProto fn_proto; diff --git a/test/run_tests.cpp b/test/run_tests.cpp index 63f83afe5..e20eaf840 100644 --- a/test/run_tests.cpp +++ b/test/run_tests.cpp @@ -39,6 +39,10 @@ static void add_simple_case(const char *case_name, const char *source, const cha test_case->compiler_args.append("build"); test_case->compiler_args.append(tmp_source_path); + test_case->compiler_args.append("--export"); + test_case->compiler_args.append("exe"); + test_case->compiler_args.append("--name"); + test_case->compiler_args.append("test"); test_case->compiler_args.append("--output"); test_case->compiler_args.append(tmp_exe_path); test_case->compiler_args.append("--release");