├── .build └── .added_strings_w1.jai ├── .gitignore ├── CONTRIBUTING.md ├── README.md ├── TODO.md ├── compiling ├── check_for_shadowing.jai ├── compiler_utils.jai ├── generate_code_for_tags.jai ├── jaic.jai ├── make_constructors.jai ├── nodes.jai └── windows_application.jai ├── examples ├── bar.jai ├── foo.jai ├── make_constructors_example.jai ├── quux.jai └── qux.jai ├── guides └── directives.jai ├── modules ├── Default_Metaprogram.jai └── Notes.jai ├── scripts └── jecho.jai └── tools ├── Locate ├── .build │ └── .added_strings_w1.jai └── Locate.jai ├── jai_run.bat └── jai_run.jai /.build/.added_strings_w1.jai: -------------------------------------------------------------------------------- 1 | 2 | #import "Default_Metaprogram"; 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ./build/ 2 | */.build/ 3 | *.exe 4 | *.pdb 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | This is currently just a personal repo, so I won't be merging any PRs. 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jai Cookbook 2 | 3 | Stuff I make as I learn Jai. 4 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | * Abstract out typical make script 2 | * dump.jai : dump bytecode for specified proc 3 | * replace hard-coded "c:/temp" with env lookup 4 | 5 | * test suite that tests everything 6 | -------------------------------------------------------------------------------- /compiling/check_for_shadowing.jai: -------------------------------------------------------------------------------- 1 | // #load this file in your equivalent of make.jai, and in your message loop call `check_for_shadowing`. 2 | // It will report any shadowing as a warning by default, but you can optionally make it error instead. 3 | 4 | 5 | check_for_shadowing :: (message: *Message, report_as_error := false) { 6 | if message.kind == { 7 | case .TYPECHECKED; 8 | code := cast(*Message_Typechecked) message; 9 | for decl: code.declarations { 10 | if decl.expression.enclosing_load && decl.expression.enclosing_load.enclosing_import.module_type != .MAIN_PROGRAM 11 | continue; 12 | 13 | expr := decl.expression; 14 | if (expr.kind == .PROCEDURE_HEADER) 15 | table_set(*headers, cast(*Code_Procedure_Header) expr, false); 16 | 17 | if expr.name == "" 18 | || expr.type.type == .PROCEDURE 19 | continue; 20 | 21 | // global constants have no root expression, but global variables have a LITERAL container? 22 | // @TODO decl.root_expression no longer exists so I'll just disable this check for now... 23 | //if decl.root_expression 24 | //&& ((decl.flags & .IS_CONSTANT) || decl.root_expression.kind != .LITERAL) 25 | // continue; 26 | 27 | table_set(*global_variables, expr.name, expr); 28 | } 29 | 30 | case .COMPILATION_PHASE; 31 | phase := cast (*Message_Phase) message; 32 | if phase.phase == .TYPECHECKED_ALL_WE_CAN { 33 | report_as := ifx report_as_error then Report.ERROR else Report.WARNING; 34 | for checked, header: headers { 35 | if checked continue; 36 | if header.body_or_null { 37 | for argument, index: header.arguments 38 | store_local_variable(index, argument.name, cast(*Code_Node) argument); 39 | last_parameter_index = header.arguments.count - 1; 40 | check_block(header.body_or_null.block, last_parameter_index, report_as); 41 | 42 | table_set(*headers, header, true); 43 | } 44 | } 45 | } 46 | } 47 | 48 | check_block :: (block: *Code_Block, locals_head: int, report_as: Report) { 49 | next_head := locals_head; 50 | 51 | for variable: block.members 52 | next_head = check_variable(variable, next_head, report_as); 53 | 54 | for statement: block.statements { 55 | if statement.kind == { 56 | case .BLOCK; 57 | check_block(cast(*Code_Block) statement, next_head, report_as); 58 | 59 | case .PUSH_CONTEXT; 60 | check_block((cast(*Code_Push_Context) statement).block, next_head, report_as); 61 | 62 | case .DEFER; 63 | check_block((cast(*Code_Defer) statement).block, next_head, report_as); 64 | 65 | case .WHILE; 66 | check_block((cast(*Code_While) statement).block, next_head, report_as); 67 | 68 | case .IF; 69 | then_block := (cast(*Code_If) statement).then_block; 70 | else_block := (cast(*Code_If) statement).else_block; 71 | if then_block check_block(then_block, next_head, report_as); 72 | if else_block check_block(else_block, next_head, report_as); 73 | 74 | case .CASE; 75 | check_block((cast(*Code_Case) statement).then_block, next_head, report_as); 76 | 77 | case .FOR; 78 | code_for := cast(*Code_For) statement; 79 | for_head := next_head; 80 | 81 | if code_for.ident_decl && code_for.ident_decl.name != "it" 82 | for_head = check_variable(code_for.ident_decl, for_head, report_as); 83 | 84 | if code_for.index_decl && code_for.index_decl.name != "it_index" 85 | for_head = check_variable(code_for.index_decl, for_head, report_as); 86 | 87 | check_block(code_for.block, for_head, report_as); 88 | } 89 | } 90 | } 91 | 92 | check_variable :: (variable: *Code_Scope_Entry, locals_head: int, report_as: Report) -> next_head: int, added: bool { 93 | next_head := locals_head; 94 | 95 | value, found := table_find(*global_variables, variable.name); 96 | if found && value != variable { 97 | compiler_report(sprint("\nShadowing global: original at %\n", format_location(value)), loc = make_location(variable), mode = report_as); 98 | print("### % ###\n", variable.name); 99 | } 100 | else { 101 | found := false; 102 | for < index: locals_head..0 { 103 | local := local_variables[index]; 104 | if local.name == variable.name { 105 | if index <= last_parameter_index 106 | compiler_report(sprint("\nShadowing parameter: original at %\n", format_location(local.node)), loc = make_location(variable), mode = report_as); 107 | else 108 | compiler_report(sprint("\nShadowing local: original at %\n", format_location(local.node)), loc = make_location(variable), mode = report_as); 109 | found = true; 110 | break; 111 | } 112 | } 113 | 114 | if !found { 115 | next_head += 1; 116 | local : Local_Variable = ---; 117 | local.name = variable.name; 118 | local.node = variable; 119 | 120 | if next_head > local_variables.count - 1 121 | array_add(*local_variables, local); 122 | else 123 | local_variables[next_head] = local; 124 | } 125 | } 126 | 127 | return next_head, next_head > locals_head; 128 | } 129 | } 130 | 131 | 132 | #scope_file 133 | 134 | 135 | format_location :: (node: *Code_Node) -> string { 136 | location := make_location(node); 137 | return sprint("%:%,%", location.fully_pathed_filename, location.line_number, location.character_number); 138 | } 139 | 140 | 141 | store_local_variable :: (index: int, name: string, node: *Code_Node) { 142 | local : Local_Variable = ---; 143 | local.name = name; 144 | local.node = node; 145 | if local_variables.count <= index { 146 | while local_variables.count <= index 147 | array_add(*local_variables, local); 148 | } 149 | else { 150 | local_variables[index] = local; 151 | } 152 | } 153 | 154 | 155 | Local_Variable :: struct { 156 | name : string; 157 | node : *Code_Node; 158 | } 159 | 160 | local_variables : [..] Local_Variable; 161 | last_parameter_index := -1; 162 | 163 | global_variables : Table(string, *Code_Node); 164 | headers : Table(*Code_Procedure_Header, bool); 165 | -------------------------------------------------------------------------------- /compiling/compiler_utils.jai: -------------------------------------------------------------------------------- 1 | format_location :: (node: *Code_Node) -> string { 2 | location := make_location(node); 3 | return sprint("%:%,%", location.fully_pathed_filename, location.line_number, location.character_number); 4 | } 5 | 6 | 7 | /* 8 | name_from_scope :: (node: *Code_Node) -> string { 9 | // assumes decl filename is meta.jai 10 | 11 | //print("%,% - %,%\n", node.l0, node.c0, node.l1, node.c1); 12 | if node.l1 < node.l0 return "ERR"; 13 | if node.l1 == node.l0 && node.c1 <= node.c0 return "ERR"; 14 | 15 | start := source_line_offset[node.l0 - 1] + node.c0 - 1; 16 | end := source_line_offset[node.l1 - 1] + node.c1 - 1; 17 | 18 | return slice(source_text, start, end - start); 19 | } 20 | */ 21 | 22 | 23 | has_note :: (notes: [] *Code_Note, note: string) -> bool { 24 | for notes if it.text == note return true; 25 | return false; 26 | } 27 | 28 | 29 | /* 30 | make_code_range :: (node: *Code_Node) -> Source_Code_Range { 31 | range : Source_Code_Range = ---; 32 | range.fully_pathed_filename = node.filename; 33 | range.line_number_start = node.l0; 34 | range.line_number_end = node.l1; 35 | range.character_number_start = node.c0; 36 | if node.kind == .DECLARATION { 37 | decl := cast(*Code_Declaration) node; 38 | range.character_number_end = range.character_number_start + decl.name.count; 39 | } 40 | else 41 | range.character_number_end = node.c1; 42 | return range; 43 | } 44 | */ 45 | 46 | 47 | name_from_type_info :: (t: *Type_Info) -> name: string #must, valid: bool { 48 | if t.type == { 49 | case .INTEGER; 50 | integer := cast(*Type_Info_Integer)t; 51 | if integer.signed { 52 | if integer.runtime_size == { 53 | case 1; return "s8", true; 54 | case 2; return "s16", true; 55 | case 4; return "s32", true; 56 | case 8; return "s64", true; 57 | } 58 | } 59 | else { 60 | if integer.runtime_size == { 61 | case 1; return "u8", true; 62 | case 2; return "u16", true; 63 | case 4; return "u32", true; 64 | case 8; return "u64", true; 65 | } 66 | } 67 | 68 | case .FLOAT; 69 | if t.runtime_size == 4 70 | return "float32", true; 71 | else 72 | return "float64", true; 73 | 74 | 75 | case .POINTER; 76 | ptr := cast(*Type_Info_Pointer)t; 77 | name, valid := name_from_type_info(ptr.pointer_to); 78 | return sprint("*%", name), valid; 79 | 80 | case .BOOL; return "bool", true; 81 | case .STRING; return "string", true; 82 | case .VOID; return "void", true; 83 | case .STRUCT; return "struct", true; 84 | case .ANY; return "Any", true; 85 | case .ENUM; return "enum", true; 86 | case .TYPE; return "type", true; 87 | case .CODE; return "Code", true; 88 | } 89 | 90 | return "", false; 91 | } 92 | -------------------------------------------------------------------------------- /compiling/generate_code_for_tags.jai: -------------------------------------------------------------------------------- 1 | #import "Basic"; 2 | #import "Compiler"; 3 | #import "String"; 4 | #import "Hash_Table"; 5 | 6 | 7 | #run { 8 | filepath := "../examples/foo.jai"; 9 | executable := "foo"; 10 | 11 | output_path : string; 12 | 13 | build_options := get_build_options(); 14 | build_options.output_type = .NO_OUTPUT; 15 | set_build_options(build_options); 16 | 17 | build_options.output_type = .EXECUTABLE; 18 | build_options.output_executable_name = executable; 19 | build_options.output_path = output_path; 20 | 21 | workspace = compiler_create_workspace(); 22 | set_build_options(build_options, workspace); 23 | 24 | compiler_begin_intercept(workspace); 25 | add_build_file(filepath, workspace); 26 | 27 | while true { 28 | message := compiler_wait_for_message(); 29 | if !message continue; 30 | if message.workspace != workspace continue; 31 | if message.kind == .COMPLETE break; 32 | 33 | build_steps(message); 34 | } 35 | 36 | compiler_end_intercept(workspace); 37 | } 38 | 39 | 40 | tagged_variables : Table(string, Type_Info_Tag); 41 | workspace : Workspace; 42 | inserted_generated_code := false; 43 | 44 | 45 | build_steps :: (message : *Message) { 46 | if message.kind == { 47 | case .TYPECHECKED; 48 | // Record all global variables tagged with @my_tag 49 | type_checked := cast(*Message_Typechecked) message; 50 | for decl : type_checked.declarations { 51 | if decl.kind == .DECLARATION { 52 | if has_note(decl, "my_tag") { 53 | declaration := cast(*Code_Declaration) decl; 54 | table_set(*tagged_variables, declaration.name, declaration.type.type); 55 | } 56 | } 57 | } 58 | 59 | case .COMPILATION_PHASE; 60 | phase_message := cast (*Message_Phase) message; 61 | phase := phase_message.phase; 62 | if phase == .TYPECHECKED_ALL_WE_CAN && !inserted_generated_code { 63 | // All code has been parsed, so generate what we want from the tags. 64 | // We'll add it all to a string called `tag_code`, which should be 65 | // inserted into the source at the desired point by adding 66 | // #insert tag_code; 67 | 68 | builder: String_Builder; 69 | init_string_builder(*builder); 70 | defer reset(*builder); 71 | 72 | // In this example we'll just make a proc to dump all the tagged variables to stdout, 73 | // which will look like this: 74 | /* 75 | dump_vars :: () { 76 | print("foo (bool) = %\n", foo); 77 | print("bar (int) = %\n", bar); 78 | } 79 | */ 80 | 81 | append(*builder, "tag_code :: #string ___JAI\n"); 82 | append(*builder, "dump_vars :: () {\n"); 83 | for type, variable: tagged_variables { 84 | type_string : string; 85 | if type == .BOOL type_string = "bool"; 86 | else if type == .INTEGER type_string = "int"; 87 | else if type == .FLOAT type_string = "float"; 88 | else compiler_report("You may only dump a bool, int, or float"); 89 | 90 | print_to_builder(*builder, " print(\"% (%) = %%\\n\", %1);\n", variable, type_string); 91 | } 92 | append(*builder, "}\n"); 93 | append(*builder, "___JAI;\n"); 94 | 95 | add_build_string(builder_to_string(*builder), workspace); 96 | 97 | inserted_generated_code = true; 98 | } 99 | } 100 | } 101 | 102 | 103 | has_note :: (declaration: *Code_Declaration, note: string) -> bool { 104 | for declaration.notes if it.text == note return true; 105 | return false; 106 | } 107 | -------------------------------------------------------------------------------- /compiling/jaic.jai: -------------------------------------------------------------------------------- 1 | // Simple wrapper to compile any Jai program with different options. 2 | // Example use: jai jaic.jai -- -short -vs foo.jai 3 | 4 | 5 | #import "Basic"; 6 | #import "Compiler"; 7 | #import "String"; 8 | 9 | 10 | short : bool; 11 | vs : bool; 12 | lazy : bool; 13 | inline : bool; 14 | trace : bool; 15 | memdbg : bool; 16 | x64 : bool; 17 | 18 | 19 | set_options :: (using options: *Build_Options) { 20 | if short shorten_filenames_in_error_messages = !shorten_filenames_in_error_messages; 21 | if vs use_visual_studio_message_format = !use_visual_studio_message_format; 22 | if lazy lazy_foreign_function_lookups = !lazy_foreign_function_lookups; 23 | if inline enable_bytecode_inliner = !enable_bytecode_inliner; 24 | if trace stack_trace = !stack_trace; 25 | if memdbg memory_debugger = !memory_debugger; 26 | 27 | if x64 backend = .X64; 28 | } 29 | 30 | 31 | #run { 32 | args := compiler_get_command_line_arguments(); 33 | filepath : string; 34 | 35 | for arg: args { 36 | if starts_with(arg, "-") { 37 | if arg == { 38 | case "-short"; 39 | short = true; 40 | 41 | case "-vs"; 42 | vs = true; 43 | 44 | case "-lazy"; 45 | lazy = true; 46 | 47 | case "-inline"; 48 | inline = true; 49 | 50 | case "-trace"; 51 | trace = true; 52 | 53 | case "-memdbg"; 54 | memdbg = true; 55 | 56 | case; 57 | print("Unknown option: %\n", arg); 58 | exit(1); 59 | } 60 | } 61 | else if ends_with_nocase(args[0], ".jai") { 62 | filepath = arg; 63 | } 64 | else { 65 | print("Unknown option: %\n", arg); 66 | exit(1); 67 | } 68 | } 69 | 70 | if filepath = "" { 71 | print("Must specify a .jai src file!"); 72 | exit(1); 73 | } 74 | 75 | output_path := path_strip_filename(filepath); 76 | #if OS == .WINDOWS 77 | executable := join(path_strip_extension(basename(filepath)), ".exe"); 78 | else 79 | executable := path_strip_extension(basename(filepath)); 80 | 81 | set_working_directory(output_path); 82 | 83 | build_options := get_build_options(); 84 | build_options.output_type = .NO_OUTPUT; 85 | set_build_options(build_options); 86 | 87 | build_options.output_type = .EXECUTABLE; 88 | build_options.output_executable_name = executable; 89 | build_options.output_path = output_path; 90 | set_options(*build_options); 91 | workspace := compiler_create_workspace("Main"); 92 | set_build_options(build_options, workspace); 93 | add_build_file(filepath, workspace); 94 | } 95 | -------------------------------------------------------------------------------- /compiling/make_constructors.jai: -------------------------------------------------------------------------------- 1 | // This file allows you to tag structs with `@Constructable`; if you do then a `make_` procedure 2 | // will be automatically generated, which has parameters identical to the struct members. 3 | 4 | // To use, `#load` this file in your equivalent of `build.jai`, and in your message loop 5 | // call `make_constuctors` with each message. 6 | 7 | 8 | make_constructors :: (message: *Message) { 9 | if message.kind == { 10 | case .TYPECHECKED; 11 | code := cast(*Message_Typechecked) message; 12 | for decl: code.declarations { 13 | if decl.enclosing_load && decl.enclosing_load.enclosing_import.module_type != .MAIN_PROGRAM 14 | continue; 15 | 16 | for expr: decl.expressions { 17 | if expr.kind == { 18 | case .STRUCT; 19 | store_struct_declaration(decl.name, cast(*Code_Struct)expr); 20 | } 21 | } 22 | } 23 | 24 | case .COMPILATION_PHASE; 25 | phase := cast (*Message_Phase) message; 26 | if phase.phase == .TYPECHECKED_ALL_WE_CAN { 27 | if added_constructors return; 28 | added_constructors = true; 29 | 30 | for code_struct, type_name: struct_declarations { 31 | signature: String_Builder; 32 | body : String_Builder; 33 | done_a_member := false; 34 | proc_name := sprint("make_%", type_name); 35 | to_lower(proc_name); 36 | 37 | if code_struct && code_struct.block { 38 | for member: code_struct.block.members { 39 | if member.kind != .DECLARATION continue; 40 | 41 | decl := cast(*Code_Declaration) member; 42 | if decl.flags & (type_of(decl.flags).IS_CONSTANT | .IS_IMPORTED) continue; 43 | 44 | if !done_a_member { 45 | print_to_builder(*signature, "% :: (", proc_name); 46 | print_to_builder(*body, " result : % = ---;\n", type_name); 47 | } 48 | else 49 | append(*signature, ", "); 50 | 51 | member_type := ""; 52 | member_default_value := ""; 53 | for expr: decl.expressions { 54 | if expr.kind == { 55 | case .TYPE_INSTANTIATION; 56 | t := cast(*Code_Type_Instantiation)expr; 57 | i := cast(*Code_Ident)t.type_valued_expression; 58 | if member_type == "" { 59 | member_type = i.name; 60 | if member_type == "string" 61 | member_default_value = "\"\""; 62 | else 63 | member_default_value = "0"; 64 | } 65 | 66 | case .LITERAL; 67 | literal := cast(*Code_Literal)expr; 68 | 69 | if literal.value_type == { 70 | case .NUMBER; 71 | if literal.numeric_flags & .FLOAT64 72 | member_default_value = sprint("%", literal._float64); 73 | else if literal.numeric_flags & .FLOAT 74 | member_default_value = sprint("%", literal._float32); 75 | else 76 | member_default_value = sprint("%", literal._s64); 77 | 78 | case .STRING; 79 | member_default_value = sprint("\"%\"", literal._string); 80 | 81 | case .TRUE; 82 | member_default_value = "true"; 83 | 84 | case .FALSE; 85 | member_default_value = "false"; 86 | 87 | case .ARRAY; 88 | case .STRUCT; 89 | case .POINTER; 90 | } 91 | } 92 | } 93 | 94 | if member_default_value != "" { 95 | if member_type == "" || member_type == "string" 96 | print_to_builder(*signature, "% := %", member.name, member_default_value); 97 | else 98 | print_to_builder(*signature, "% : % = %", member.name, member_type, member_default_value); 99 | print_to_builder(*body, " result.%1 = %1;\n", member.name, member.name); 100 | done_a_member = true; 101 | } 102 | } 103 | } 104 | 105 | if done_a_member { 106 | print_to_builder(*signature, ") -> % #must {\n", type_name); 107 | append(*signature, builder_to_string(*body)); 108 | append(*signature, " return result;\n}\n\n"); 109 | add_build_string(builder_to_string(*signature), message.workspace); 110 | } 111 | } 112 | 113 | } 114 | } 115 | } 116 | 117 | 118 | 119 | #scope_file 120 | 121 | 122 | 123 | format_location :: (node: *Code_Node) -> string { 124 | location := make_location(node); 125 | return sprint("%:%,%", location.fully_pathed_filename, location.line_number, location.character_number); 126 | } 127 | 128 | 129 | store_struct_declaration :: (name: string, code_struct: *Code_Struct) { 130 | if name == "" return; 131 | 132 | table_set(*struct_declarations, name, code_struct); 133 | } 134 | 135 | 136 | added_constructors := false; 137 | struct_declarations : Table(string, *Code_Struct); 138 | 139 | 140 | #import "Hash_Table"; 141 | #load "compiler_utils.jai"; 142 | -------------------------------------------------------------------------------- /compiling/nodes.jai: -------------------------------------------------------------------------------- 1 | // Walk all nodes from root on down, performing `action(node)` on each one. 2 | // If `condition(node)` is false that node and its children will be ignored. 3 | // Call `init_node_walk()` before doing anything else. 4 | 5 | 6 | init_node_walk :: () { 7 | table_reset(*visited_serials); 8 | } 9 | 10 | 11 | revisit_node :: (node: *Code_Node) { 12 | table_remove(*visited_serials, node.serial); 13 | } 14 | 15 | 16 | found_node_count :: () -> int { 17 | return visited_serials.count; 18 | } 19 | 20 | 21 | walk_all_nodes :: (node: *Code_Node, condition: (*Code_Node) -> bool, action: (*Code_Node)) { 22 | if !node return; 23 | 24 | _, found := table_find(*visited_serials, node.serial); 25 | if found return; 26 | 27 | table_set(*visited_serials, node.serial, node); 28 | 29 | if !condition(node) return; 30 | 31 | action(node); 32 | 33 | 34 | walk :: (node: *Code_Node) #expand { 35 | walk_all_nodes(node, `condition, `action); 36 | } 37 | 38 | 39 | if node.kind == { 40 | // @Note '// others: ' comments identify other Code_Node members, 41 | // but which are not walked because they are not direct 42 | // children of this node or would in some other way be walked 43 | // from elsewhere in the tree. 44 | 45 | //case .UNINITIALIZED; 46 | // nop 47 | 48 | case .BLOCK; 49 | block := cast(*Code_Block) node; 50 | for block.members walk(it); 51 | for block.statements walk(it); 52 | walk(block.owning_statement); 53 | // others: parent 54 | 55 | case .LITERAL; 56 | literal := cast(*Code_Literal) node; 57 | if literal.value_type == { 58 | case .ARRAY; 59 | walk(literal.array_literal_info.element_type); 60 | for literal.array_literal_info.array_members walk(it); 61 | 62 | case .STRUCT; 63 | walk(literal.struct_literal_info.type_expression); 64 | for literal.struct_literal_info.arguments walk(it); 65 | } 66 | 67 | case .IDENT; 68 | ident := cast(*Code_Ident) node; 69 | walk(ident.resolved_declaration); 70 | 71 | case .UNARY_OPERATOR; 72 | unary := cast(*Code_Unary_Operator) node; 73 | walk(unary.subexpression); 74 | 75 | case .BINARY_OPERATOR; 76 | binary := cast(*Code_Binary_Operator) node; 77 | walk(binary.left); 78 | walk(binary.right); 79 | 80 | case .PROCEDURE_BODY; 81 | proc := cast(*Code_Procedure_Body) node; 82 | walk(proc.block); 83 | // others: header; 84 | 85 | case .PROCEDURE_CALL; 86 | proc := cast(*Code_Procedure_Call) node; 87 | walk(proc.procedure_expression); 88 | for argument: proc.arguments_unsorted { 89 | walk(argument.name); 90 | walk(argument.expression); 91 | } 92 | walk(proc.macro_expansion_block); 93 | // others: arguments_sorted 94 | 95 | //case .CONTEXT; 96 | // nop 97 | 98 | case .WHILE; 99 | _while := cast(*Code_While) node; 100 | walk(_while.condition); 101 | walk(_while.block); 102 | 103 | case .IF; 104 | _if := cast(*Code_If) node; 105 | walk(_if.condition); 106 | walk(_if.then_block); 107 | walk(_if.else_block); 108 | 109 | case .LOOP_CONTROL; 110 | 111 | case .CASE; 112 | _case := cast(*Code_Case) node; 113 | walk(_case.condition); 114 | walk(_case.then_block); 115 | // others: owning_if 116 | 117 | //case .REMOVE; 118 | //_remove := cast(*Code_Remove) node; 119 | //walk(_remove.target_ident); 120 | // others: target_ident 121 | 122 | case .RETURN; 123 | _return := cast(*Code_Return) node; 124 | for argument: _return.arguments_unsorted { 125 | walk(argument.name); 126 | walk(argument.expression); 127 | } 128 | // others: arguments_sorted 129 | 130 | case .FOR; 131 | _for := cast(*Code_For) node; 132 | walk(_for.iteration_expression); 133 | walk(_for.iteration_expression_right); 134 | walk(_for.want_pointer_expression); 135 | walk(_for.want_reverse_expression); 136 | walk(_for.ident_decl); 137 | walk(_for.index_decl); 138 | //if _for.macro_expansion_procedure_call 139 | // walk(_for.macro_expansion_procedure_call); 140 | //else 141 | walk(_for.block); 142 | 143 | // case .TYPE_DEFINITION; 144 | // nop 145 | 146 | case .TYPE_INSTANTIATION; 147 | _type := cast(*Code_Type_Instantiation) node; 148 | walk(_type.type_valued_expression); 149 | walk(_type.type_of_lambda); 150 | walk(_type.pointer_to); 151 | walk(_type.array_element_type); 152 | walk(_type.array_dimension); 153 | 154 | case .ENUM; 155 | _enum := cast(*Code_Enum) node; 156 | walk(_enum.internal_type_inst); 157 | walk(_enum.block); 158 | 159 | case .PROCEDURE_HEADER; 160 | header := cast(*Code_Procedure_Header) node; 161 | walk(header.constants_block); 162 | for header.arguments walk(it); 163 | for header.returns walk(it); 164 | walk(header.my_header); 165 | walk(header.polymorph_source_header); 166 | for header.modify_directives walk(it); 167 | walk(header.body_or_null); 168 | 169 | case .STRUCT; 170 | _struct := cast(*Code_Struct) node; 171 | walk(_struct.block); 172 | walk(_struct.constants_block); 173 | for _struct.notes walk(it); 174 | 175 | case .COMMA_SEPARATED_ARGUMENTS; 176 | args := cast(*Code_Comma_Separated_Arguments) node; 177 | for args.expressions walk(it); 178 | 179 | case .EXTRACT; 180 | extract := cast(*Code_Extract) node; 181 | walk(extract.from); 182 | 183 | case .SEQUENCE; 184 | sequence := cast(*Code_Sequence) node; 185 | for sequence.expressions walk(it); 186 | walk(sequence.expression_i_replaced); 187 | 188 | case .MAKE_VARARGS; 189 | make := cast(*Code_Make_Varargs) node; 190 | for make.expressions walk(it); 191 | 192 | case .DECLARATION; 193 | decl := cast(*Code_Declaration) node; 194 | walk(decl.type_inst); 195 | walk(decl.root_expression); 196 | //for decl.expressions walk(it); 197 | walk(decl.alignment_expression); 198 | for decl.notes walk(it); 199 | 200 | case .CAST; 201 | _cast := cast(*Code_Cast) node; 202 | walk(_cast.target_type); 203 | walk(_cast.expression); 204 | 205 | //case .DIRECTIVE_IMPORT; 206 | // ? 207 | 208 | //case .DIRECTIVE_INLINE; 209 | // ? 210 | 211 | // case .DIRECTIVE_THROUGH; 212 | // nop 213 | 214 | //case .DIRECTIVE_LOAD; 215 | // ? 216 | 217 | case .DIRECTIVE_RUN; 218 | run := cast(*Code_Directive_Run) node; 219 | walk(run.procedure); 220 | 221 | case .DIRECTIVE_CODE; 222 | code := cast(*Code_Directive_Code) node; 223 | walk(code.expression); 224 | 225 | case .DIRECTIVE_POKE_NAME; 226 | poke := cast(*Code_Directive_Poke_Name) node; 227 | walk(poke.module_struct); 228 | 229 | //case .DIRECTIVE_IF_DEFINED; // Not currently implemented. 230 | 231 | case .DIRECTIVE_BAKE; 232 | bake := cast(*Code_Directive_Bake) node; 233 | walk(bake.procedure_call); 234 | 235 | case .DIRECTIVE_MODIFY; 236 | modify := cast(*Code_Directive_Modify) node; 237 | walk(modify.block); 238 | 239 | //case .DIRECTIVE_FOREIGN_LIBRARY; 240 | // nop 241 | 242 | case .SIZE_OR_TYPE_INFO; 243 | size_or_type := cast(*Code_Size_Or_Type_Info) node; 244 | walk(size_or_type.type_to_query); 245 | walk(size_or_type.type_of_expression); 246 | 247 | case .PUSH_CONTEXT; 248 | push := cast(*Code_Push_Context) node; 249 | walk(push.to_push); 250 | walk(push.block); 251 | 252 | //case .NOTE; 253 | // nop 254 | 255 | //case .DIRECTIVE_PLACE; 256 | // nop 257 | 258 | // case .DIRECTIVE_SCOPE; 259 | // nop 260 | 261 | case .DIRECTIVE_STATIC_IF; 262 | _if := cast(*Code_Directive_Static_If) node; 263 | walk(_if.condition); 264 | walk(_if.then_block); 265 | walk(_if.else_block); 266 | 267 | case .DIRECTIVE_LOCATION; 268 | location := cast(*Code_Directive_Location) node; 269 | walk(location.ident); 270 | 271 | //case .DIRECTIVE_ALIGN; 272 | // ? 273 | 274 | //case .DIRECTIVE_ADD_CONTEXT; 275 | // ? 276 | 277 | case .COMPOUND_DECLARATION; 278 | compound := cast(*Code_Compound_Declaration) node; 279 | walk(compound.comma_separated_assignment); 280 | walk(compound.declaration_properties); 281 | walk(compound.alignment_expression); 282 | for compound.notes walk(it); 283 | 284 | case .DEFER; 285 | _defer := cast(*Code_Defer) node; 286 | walk(_defer.block); 287 | 288 | case .USING; 289 | _using := cast(*Code_Using) node; 290 | walk(_using.expression); 291 | 292 | //case .PLACEHOLDER; 293 | // nop 294 | } 295 | } 296 | 297 | 298 | #scope_file 299 | 300 | 301 | #import "Compiler"; 302 | #import "Hash_Table"; 303 | 304 | 305 | visited_serials: Table(s64, *Code_Node); 306 | -------------------------------------------------------------------------------- /compiling/windows_application.jai: -------------------------------------------------------------------------------- 1 | // To make a windows executable run without spawning a command prompt, you 2 | // need to run a custom linker command. You do that by setting up a workspace 3 | // and triggering the link on the .READY_FOR_CUSTOM_LINK_COMMAND message. 4 | // The custom link proc is mostly copied from the compile_me.jai example in the 5 | // Compiler module. 6 | 7 | // The run_custom_link_command below differs from the one in the compile_me.jai 8 | // file in the following ways (search for the @tag below): 9 | // 10 | // @WinApp Adds the necessary linker arguments to make it output a non-command line 11 | // oriented application. 12 | // 13 | // @Spam The line which spews all the linker crap has been commented out. 14 | // 15 | // @Color I added some ansi color escape sequences to the output. 16 | 17 | 18 | #import "Basic"; 19 | #import "Compiler"; 20 | #import "String"; 21 | #import "System"; 22 | #import "Process"; 23 | 24 | #if OS == .WINDOWS { 25 | Windows_Resources :: #import "Windows_Resources"; 26 | Ico_File :: #import "Ico_File"; 27 | } 28 | 29 | 30 | set_options :: (using options: *Build_Options) { 31 | use_custom_link_command = true; 32 | } 33 | 34 | 35 | #run { 36 | filepath := "../examples/bar.jai"; 37 | executable := "bar"; 38 | 39 | output_path : string; 40 | 41 | build_options := get_build_options(); 42 | build_options.output_type = .NO_OUTPUT; 43 | set_build_options(build_options); 44 | 45 | build_options.output_type = .EXECUTABLE; 46 | build_options.output_executable_name = executable; 47 | build_options.output_path = output_path; 48 | set_options(*build_options); 49 | workspace := compiler_create_workspace(); 50 | set_build_options(build_options, workspace); 51 | 52 | compiler_begin_intercept(workspace); 53 | add_build_file(filepath, workspace); 54 | 55 | while true { 56 | message := compiler_wait_for_message(); 57 | if !message continue; 58 | if message.workspace != workspace continue; 59 | if message.kind == .COMPLETE break; 60 | 61 | build_steps(message); 62 | } 63 | 64 | compiler_end_intercept(workspace); 65 | } 66 | 67 | 68 | build_steps :: (message : *Message) { 69 | if message.kind == .COMPILATION_PHASE { 70 | phase_message := cast (*Message_Phase) message; 71 | if phase_message.phase == .READY_FOR_CUSTOM_LINK_COMMAND 72 | run_custom_link_command(message.workspace, phase_message, use_system_linker_if_able=true); 73 | } 74 | } 75 | 76 | 77 | run_custom_link_command :: (w: Workspace, m: *Message_Phase, use_system_linker_if_able := true) { 78 | options := get_build_options(w); 79 | 80 | // Use the workspace's Os_Target since we want to support 81 | // cross-compilation at some point! 82 | target := options.os_target; 83 | target_windows := target == .WINDOWS; 84 | target_linux := target == .LINUX; 85 | target_macosx := target == .MACOS; 86 | 87 | use_lld := !((target == OS) && use_system_linker_if_able); 88 | 89 | #if OS == .WINDOWS { 90 | system_linker_exe_name := "link.exe"; 91 | } 92 | 93 | #if OS == .LINUX || OS == .MACOS { 94 | system_linker_exe_name := "ld"; 95 | } 96 | 97 | #if OS == .WINDOWS { 98 | if !use_lld && target_windows { 99 | // If we have opted to use the system linker and we're on Windows, 100 | // then attempt to find link.exe, otherwise fallback to LLD (or maybe error?) 101 | vc_path, linker_path := Windows_Resources.find_visual_studio_in_a_ridiculous_garbage_way(); 102 | if linker_path { 103 | system_linker_exe_name = tprint("%\\%", linker_path, system_linker_exe_name); 104 | } else { 105 | compiler_report("Could not find link.exe path, falling back to using LLD", mode = .WARNING); 106 | use_lld = true; 107 | } 108 | } 109 | } 110 | 111 | suffix := ""; 112 | #if OS == .WINDOWS { 113 | suffix = ".exe"; 114 | } 115 | 116 | target_filename := tprint("%1%2%3", options.output_path, options.output_executable_name, suffix); 117 | 118 | exe_name := get_path_of_running_executable(); 119 | index, found := last_index_of_string(exe_name, "jai/"); 120 | 121 | lld_exe_name: string; 122 | if use_lld { 123 | compiler_base_path := "c:/jai/"; // Will be overridden by the below... 124 | if found { 125 | compiler_base_path = exe_name; 126 | compiler_base_path.count = index + 4; 127 | } else { 128 | print("Metaprogram is unable to find the path to the compiler, in order to run lld.\n"); 129 | } 130 | 131 | #if OS == .WINDOWS { 132 | lld := "lld.exe"; 133 | } 134 | 135 | #if OS == .MACOS { 136 | lld := "lld-macosx"; 137 | } 138 | 139 | #if OS == .LINUX { 140 | lld := "lld-linux"; 141 | } 142 | 143 | lld_exe_name = tprint("%1%2%3", compiler_base_path, "bin/", lld); 144 | } 145 | 146 | 147 | // Unfortunately, right now we are using CreateProcessW because that allows 148 | // one to be specific about security, etc (or if it isn't specific enough we can 149 | // change to CreateProcessExW). HOWEVER, it doesn't inherit the path so we need 150 | // to provide the path explicitly. That sucks. Maybe we have to change to system. 151 | // Or duplicate system's ability to use the path. 152 | 153 | arguments: [..] string; 154 | 155 | if use_lld { 156 | array_add(*arguments, lld_exe_name); 157 | array_add(*arguments, "-flavor"); 158 | 159 | // choose the flavor based on the target OS. 160 | if target_windows array_add(*arguments, "link"); 161 | else if target_linux array_add(*arguments, "Gnu"); 162 | else if target_macosx array_add(*arguments, "Darwin"); 163 | } else { 164 | array_add(*arguments, system_linker_exe_name); 165 | } 166 | 167 | if target_windows array_add(*arguments, "/nologo"); 168 | 169 | // Object files. 170 | for m.compiler_generated_object_files array_add(*arguments, it); 171 | for m.support_object_files array_add(*arguments, it); 172 | 173 | if target_windows { 174 | // Target filename. 175 | array_add(*arguments, tprint("/OUT:%", target_filename)); 176 | 177 | array_add(*arguments, "/MACHINE:AMD64"); 178 | array_add(*arguments, "/INCREMENTAL:NO"); 179 | array_add(*arguments, "/DEBUG"); 180 | //array_add(*arguments, "/OPT:REF"); 181 | 182 | // @WinApp These arguments make the linker generate a Windows application, instead 183 | // of a command line program. 184 | array_add(*arguments, "/SUBSYSTEM:WINDOWS"); 185 | array_add(*arguments, "/ENTRY:main"); 186 | array_add(*arguments, "/IGNORE:4216"); 187 | 188 | // If generating an executable, then create lib and exp file inside intermediate directory. 189 | if options.output_type == .EXECUTABLE { 190 | array_add(*arguments, tprint("/IMPLIB:%1%2.lib", options.intermediate_path, path_strip_extension(options.output_executable_name))); 191 | } 192 | } else if target_linux { 193 | array_add(*arguments, "--eh-frame-hdr"); 194 | array_add(*arguments, "-export-dynamic"); 195 | array_add(*arguments, tprint("-o%", target_filename)); 196 | array_add(*arguments, "--dynamic-linker"); 197 | array_add(*arguments, "/lib64/ld-linux-x86-64.so.2"); 198 | array_add(*arguments, "-rpath=$ORIGIN"); 199 | } else { // target_macosx 200 | array_add(*arguments, "-export_dynamic"); 201 | array_add(*arguments, "-dynamic"); 202 | array_add(*arguments, "-o"); 203 | array_add(*arguments, target_filename); 204 | 205 | array_add(*arguments, "-macosx_version_min"); 206 | array_add(*arguments, "10.11"); 207 | 208 | array_add(*arguments, "-rpath"); 209 | array_add(*arguments, "@loader_path"); // NOTE: on the command-line we have to single-quote this, here it must be unquoted! 210 | } 211 | 212 | // @TODO we probably need to do something similar using LLD on non-Windows hosts 213 | #if OS == .WINDOWS { 214 | if target_windows { 215 | vc_path := Windows_Resources.find_visual_studio_in_a_ridiculous_garbage_way(); 216 | if vc_path { 217 | array_add(*arguments, tprint("/libpath:\"%\"", vc_path)); 218 | } else { 219 | compiler_report(#file, #line, 0, "Unable to find Visual Studio runtime library folder; can't compile.\n"); 220 | } 221 | 222 | kit_root, windows_version := Windows_Resources.find_windows_kit_root(); 223 | if kit_root { 224 | // I guess both Windows 8 and 10 use the same um / ucrt format. 225 | // kit_root does not have a slash at the end!! Sigh. 226 | array_add(*arguments, tprint("/libpath:\"%/um/x64\"", kit_root)); 227 | array_add(*arguments, tprint("/libpath:\"%/ucrt/x64\"", kit_root)); 228 | } else { 229 | // Report error. 230 | compiler_report(#file, #line, 0, "Unable to find Windows Kit root; can't compile.\n"); 231 | } 232 | 233 | 234 | if (options.output_type == .DYNAMIC_LIBRARY) { 235 | array_add(*arguments, "/DLL"); 236 | } 237 | 238 | array_add(*arguments, "-nodefaultlib"); 239 | } 240 | } 241 | 242 | for m.libraries array_add(*arguments, it); 243 | 244 | 245 | // @Spam Uncomment this line to revert the compilation output containing all the linker 246 | // args. 247 | //print("Link line: %\n", join(..arguments, " ")); 248 | 249 | success, exit_code, out, err := os_run_command(..arguments, capture_and_return_output=true); 250 | 251 | 252 | // @Color I added some color codes to the output here. 253 | if (!success) || (exit_code != 0) { 254 | print("\n\n\e[1;31mError: Link step failed! (Exit code %).\n", exit_code); 255 | if err != "" print("%\n", err); 256 | if out != "" print("%\n", out); 257 | print("\e[0;36m"); 258 | } 259 | else { 260 | displayed_something := false; 261 | for split(out, "\n") { 262 | line := trim(it, " \t\n\r"); 263 | if line != "" && !starts_with(line, "Creating library") { 264 | if !displayed_something { 265 | print("\n\n"); 266 | displayed_something = true; 267 | } 268 | print("%\n", line); 269 | } 270 | } 271 | } 272 | print("\e[0;36m"); 273 | //print("\e[1;30m"); 274 | 275 | // Report to the compiler that we have finished linking 276 | compiler_custom_link_command_is_complete(w); 277 | } 278 | -------------------------------------------------------------------------------- /examples/bar.jai: -------------------------------------------------------------------------------- 1 | #import "Input"; 2 | #import "Windows"; 3 | #import "Window_Creation"; 4 | 5 | main :: () { 6 | handle_window := create_window(window_name="Example", width=200, height=200); 7 | 8 | while true { 9 | update_window_events(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/foo.jai: -------------------------------------------------------------------------------- 1 | #import "Basic"; 2 | 3 | foo := true; @my_tag 4 | 5 | #insert tag_code; 6 | 7 | main :: () { 8 | dump_vars(); 9 | } 10 | -------------------------------------------------------------------------------- /examples/make_constructors_example.jai: -------------------------------------------------------------------------------- 1 | #import "Basic"; 2 | #import "Compiler"; 3 | #import "String"; 4 | 5 | #load "../compiling/make_constructors.jai"; 6 | 7 | 8 | #run { 9 | filepath := "quux.jai"; 10 | executable := "quux"; 11 | 12 | output_path : string; 13 | 14 | build_options := get_build_options(); 15 | build_options.output_type = .NO_OUTPUT; 16 | set_build_options(build_options); 17 | 18 | build_options.output_type = .EXECUTABLE; 19 | build_options.output_executable_name = executable; 20 | build_options.output_path = output_path; 21 | workspace := compiler_create_workspace(); 22 | set_build_options(build_options, workspace); 23 | 24 | compiler_begin_intercept(workspace); 25 | add_build_file(filepath, workspace); 26 | 27 | while true { 28 | message := compiler_wait_for_message(); 29 | if !message continue; 30 | if message.workspace != workspace continue; 31 | if message.kind == .COMPLETE break; 32 | 33 | make_constructors(message); 34 | } 35 | 36 | compiler_end_intercept(workspace); 37 | } 38 | -------------------------------------------------------------------------------- /examples/quux.jai: -------------------------------------------------------------------------------- 1 | // Example for `make_constructors.jai` 2 | // Don't compile this directly; compile `make_constructors_example.jai` instead. 3 | 4 | 5 | #import "Basic"; 6 | 7 | 8 | main :: () { 9 | foo := make_foo(alpha = 100, delta = "Hello"); 10 | bar := make_bar("World", 500); 11 | log("%", foo); 12 | log("%", bar); 13 | } 14 | 15 | 16 | Foo :: struct { 17 | alpha : int; 18 | beta := 100; 19 | gamma : float32; 20 | delta : string; 21 | } 22 | 23 | 24 | Bar :: struct { 25 | name : string; 26 | count := -1; 27 | } 28 | -------------------------------------------------------------------------------- /examples/qux.jai: -------------------------------------------------------------------------------- 1 | #import "Basic"; 2 | 3 | main :: () { 4 | print("f(%) = %\n", 3, factorial(3)); 5 | } 6 | 7 | factorial :: (f: int) -> int, int { 8 | if f <= 1 return 1, 1; 9 | n := 1; 10 | for < f..2 n *= it; 11 | 12 | return n, n; 13 | } 14 | -------------------------------------------------------------------------------- /guides/directives.jai: -------------------------------------------------------------------------------- 1 | // This file gives an example for each compiler directive 2 | 3 | 4 | // #add_context 5 | // Adds a declaration to the context which is passed around your entire program. 6 | #add_context this_is_the_way := true; 7 | 8 | main :: () { 9 | query :: () { 10 | print("This is the way: %\n", context.this_is_the_way); 11 | } 12 | query(); 13 | 14 | 15 | 16 | big : [16] u8 #align 64; 17 | big[0] = 1; 18 | big[1] = 2; 19 | print("size = %, value = %\n", size_of(type_of(big)), formatInt(big[0], base = 2)); 20 | 21 | print("%\n%\n", #file, #filepath); 22 | } 23 | 24 | 25 | #import "Basic"; 26 | 27 | /* 28 | 29 | #add_context Add a declaration to the context 30 | #align ? 31 | #assert compiler-time assert 32 | #bake currying 33 | #bake_arguments currying 34 | #c_call is a call to a c library? 35 | #caller_location use as default value of parameter to set it to source code location 36 | #char Character literal 37 | #code code block 38 | #compiler proc is internal to compiler 39 | #complete Ensure an if-case satement checks all values of the enum 40 | #expand macro 41 | #file path+filename of running executable 42 | #filepath path of the running executable 43 | #foreign proc is a foreign function 44 | #foreign_library specify file for foreign functions 45 | #foreign_system_library specify system file for foreign functions 46 | #if compile-time if 47 | #import Import a module 48 | #insert insert string 49 | #insert_internal insert string internal to proc? 50 | #intrinsic proc is an intrinsic 51 | #load Load source code, as if it were placed right here (very commonly used) 52 | #modify filter polymorphic parameter type 53 | #module_parameters ? 54 | #must Require a return value to be assigned to a variable 55 | #no_abc No array-bounds-check 56 | #no_alias ? 57 | #no_padding no padding in struct 58 | #place set location in struct of following members 59 | #placeholder placeholder identifier to be filled out at compile time 60 | #program_export 61 | #run Run this code right now (compile time) 62 | #run_and_insert @deprecated 63 | #runtime_support proc comes from runtime support 64 | #scope_export Set the scope for future declarations to be accessible by code that imports this module 65 | #scope_file Set the scope for foture declarations to just this file 66 | #scope_module Set the scope for foture declarations to just this module 67 | #specified Declare intention of maintaining enum values compatibility over time 68 | #string String literal with delimiter 69 | #symmetric proc is symmetric 70 | #through case has fall-through 71 | #type Type literal, e.g. for function types 72 | #type_info_none struct does not keep runtime type info 73 | #type_info_procedures_are_void_pointers 74 | 75 | */ 76 | -------------------------------------------------------------------------------- /modules/Default_Metaprogram.jai: -------------------------------------------------------------------------------- 1 | /** 2 | # Default_Metaprogram 3 | 4 | This is a copy of the `Default_Metaprogram` which comes with the compiler, with one additional 5 | feature: you may process notes on declarations without adding your own metaprogram. To use it either 6 | replace the default one in your compiler `modules` folder, or place it elsewhhere and use the `-meta` 7 | compiler option to use it. 8 | 9 | To process notes you need to import the `Notes` module. Create a procedure which takes an array 10 | of `Note_Data`, and tag it with `@Note`. 11 | 12 | Example program: 13 | 14 | ``` 15 | ConsoleCommand :: (notes: [] Note_Data) { 16 | builder : String_Builder; 17 | append(*builder, "register_console_commands_string :: #string ___jai\n"); 18 | for note: notes { 19 | permission : string = ---; 20 | error := false; 21 | if note.parameters.count == 0 22 | permission = ".USER"; 23 | else if note.parameters.count == 1 { 24 | if note.parameters[0] == { 25 | case "user"; permission = ".USER"; 26 | case "admin"; permission = ".ADMIN"; 27 | case "developer"; permission = ".DEVELOPER"; 28 | case; error = true; 29 | } 30 | } 31 | else 32 | error = true; 33 | 34 | if error compiler_report("\nBad @ConsoleCommand note: if present, permission parameter must be one of: user, admin, developer", make_location(note.declaration), .ERROR); 35 | 36 | print_to_builder(*builder, " table_set(*console_commands, \"%1\", console_command_data(%1, %2));\n", note.declaration.name, permission); 37 | } 38 | append(*builder, "___jai\n"); 39 | add_build_string(builder_to_string(*builder)); 40 | } @Note 41 | 42 | 43 | 44 | // console commands 45 | 46 | 47 | quit :: (parameters: .. string) -> bool { 48 | should_quit = true; 49 | return true; 50 | } @ConsoleCommand 51 | 52 | 53 | egm :: (parameters: .. string) -> bool { 54 | god_mode = true; 55 | return true; 56 | } @ConsoleCommand(developer) 57 | 58 | 59 | 60 | // rest of program 61 | 62 | 63 | should_quit := false; 64 | god_mode := false; 65 | console_commands : Table(string, Console_Command_Data); 66 | 67 | 68 | main :: () { 69 | register_console_commands(); 70 | 71 | for proc, command: console_commands 72 | log("%: %", command, proc); 73 | 74 | quit(); 75 | log("should_quit = %", should_quit); 76 | } 77 | 78 | 79 | 80 | #scope_file 81 | 82 | 83 | 84 | Console_Command_Proc :: #type([] string) -> bool; 85 | 86 | Console_Command_Data :: struct { 87 | proc : Console_Command_Proc = ---; 88 | permission : enum u8 {USER; ADMIN; DEVELOPER;} = .USER; 89 | } 90 | 91 | console_command_data :: (command: Console_Command_Proc, permission: type_of(Console_Command_Data.permission) = .USER) -> Console_Command_Data { 92 | result : Console_Command_Data; 93 | result.permission = permission; 94 | return result; 95 | } 96 | 97 | register_console_commands :: () { 98 | #insert register_console_commands_string; 99 | } 100 | 101 | #import "Basic"; 102 | #import "Compiler"; 103 | #import "Hash_Table"; 104 | #import "Notes"; 105 | ``` 106 | */ 107 | 108 | 109 | 110 | // @Incomplete: Exit code on failure, test with test programs! 111 | // @Incomplete: Test program deletes .exe files 112 | // @Incomplete: compiler_set_command_line_arguments passes a Workspace parameter. 113 | // @Incomplete: Should Workspaces be set to NO_OUTPUT by default, unless changed? 114 | // @Incomplete: Allow setting of debug/release build via Default_Metaprogram. 115 | 116 | #module_parameters(VERBOSE := false); 117 | 118 | is_absolute_path :: (path: string) -> bool { // This routine is probably not as correct as we'd like. We'd like to put in a better one! But maybe we will stop doing the cwd thing, or do it differently; hard to say. 119 | if !path return false; 120 | 121 | if path[0] == #char "/" return true; // Backslashes have not been converted to forward slashes by this point. 122 | if path[0] == #char "\\" return true; // Backslashes have not been converted to forward slashes by this point. 123 | if (path.count > 2) && (path[1] == #char ":") && (OS == .WINDOWS) return true; // Drive letter stuff. Probably incomplete. 124 | 125 | if path.count >= 3 { 126 | // @Robustness: Check for a drive letter in character 0? Anything else? 127 | if path[1] == #char ":" return true; 128 | } 129 | 130 | return false; 131 | } 132 | 133 | build :: () { 134 | 135 | // 136 | // Create a workspace to contain the program we want to compile. 137 | // We can pass a name to compiler_create_workspace that gets reported 138 | // back to us in error messages: 139 | // 140 | w := compiler_create_workspace("Target Program"); 141 | if !w { 142 | print("Workspace creation failed.\n"); 143 | return; 144 | } 145 | 146 | options := get_build_options(w); 147 | 148 | do_initial_cwd := true; 149 | options_modified := false; 150 | user, system := compiler_get_command_line_arguments(); 151 | 152 | #if VERBOSE { 153 | print("User arguments: %\n", user); 154 | print("System arguments: %\n", system); 155 | } 156 | 157 | for system { 158 | if it == "-no_cwd" do_initial_cwd = false; 159 | 160 | if it == "-release" { 161 | set_optimization_level(*options, 2, 0); 162 | options.stack_trace = false; 163 | options_modified = true; 164 | } 165 | 166 | if it == "-quiet" { 167 | options.text_output_flags = 0; 168 | } 169 | } 170 | 171 | files, run_strings := compiler_get_source_files(); 172 | if files { 173 | basename, path := get_basename_and_path(files[0]); 174 | 175 | #if VERBOSE { 176 | print("Basename: %\n", basename); 177 | print("Path: %\n", path); 178 | } 179 | 180 | if basename || path { 181 | if path options.output_path = path; 182 | if basename options.output_executable_name = basename; 183 | options_modified = true; 184 | } 185 | 186 | // We set the working directory, because relative paths specified in things 187 | // like #import should not have their meaning depend on where you were when 188 | // you started the compiler. @Cleanup: Maybe this becomes unnecessary if 189 | // import works a little differently. 190 | if do_initial_cwd && path { 191 | old_wd := get_working_directory(); 192 | #if VERBOSE print("Changing working directory to '%'.\n", path); 193 | set_working_directory(path); 194 | 195 | // To set the working directory, we need to change the filenames of all the arguments, 196 | // if they are relative, otherwise they will now be wrong ... sigh!! 197 | // The whole reason we are setting directory is, the meaning of the program should not 198 | // be dependent on where the compiler is when it starts. It should be invariant. 199 | for * files { 200 | old_filename := < :: (notes: [] Note_Data) {", 102 | make_location(declaration), .WARNING); 103 | return; 104 | } 105 | 106 | label = note_text; 107 | } 108 | 109 | existing_notes, found := table_find(*notes_by_label, label); 110 | 111 | notes : [..] Note_Data = ---; 112 | if found { 113 | notes = existing_notes; 114 | } 115 | else { 116 | new_notes : [..] Note_Data; 117 | notes = new_notes; 118 | } 119 | 120 | array_add(*notes, note); 121 | table_set(*notes_by_label, label, notes); 122 | } 123 | -------------------------------------------------------------------------------- /scripts/jecho.jai: -------------------------------------------------------------------------------- 1 | #import "Basic"; 2 | #import "String"; 3 | 4 | 5 | main :: () { 6 | args := get_command_line_arguments(); 7 | 8 | builder : String_Builder; 9 | init_string_builder(*builder); 10 | 11 | if args.count > 1 { 12 | append(*builder, args[1]); 13 | for index: 2..args.count - 1 { 14 | append(*builder, " "); 15 | append(*builder, args[index]); 16 | } 17 | } 18 | 19 | append(*builder, "\n"); 20 | print(builder_to_string(*builder)); 21 | } 22 | -------------------------------------------------------------------------------- /tools/Locate/.build/.added_strings_w1.jai: -------------------------------------------------------------------------------- 1 | 2 | #import "Default_Metaprogram"; 3 | -------------------------------------------------------------------------------- /tools/Locate/Locate.jai: -------------------------------------------------------------------------------- 1 | #import "Basic"; 2 | #import "Compiler"; 3 | #import "Hash_Table"; 4 | #import "String"; 5 | 6 | #load "../../compiling/compiler_utils.jai"; 7 | #load "../../compiling/nodes.jai"; 8 | 9 | 10 | help_text :: "Usage: jai -import_dir -meta Locate -- \n"; 11 | 12 | 13 | set_options :: (using options: *Build_Options) { 14 | emit_debug_info = .NONE; 15 | output_path = "c:/temp"; 16 | intermediate_path = "c:/temp"; 17 | } 18 | 19 | 20 | test_path :: "c:/repos/jaitest/foo.jai"; 21 | 22 | 23 | target_path : string; 24 | target_line : int; 25 | target_char : int; 26 | finished := false; 27 | 28 | 29 | #run { 30 | args := compiler_get_command_line_arguments(); 31 | if args.count != 3 { 32 | print(help_text); 33 | exit(1); 34 | } 35 | 36 | line_ok : bool; 37 | char_ok : bool; 38 | 39 | target_path = args[0]; 40 | target_line, line_ok = parse_int(*args[1]); 41 | target_char, char_ok = parse_int(*args[2]); 42 | //target_path = test_path; 43 | //target_line = 67; 44 | //target_char = 5; 45 | 46 | if !(line_ok && char_ok) { 47 | print(help_text); 48 | exit(1); 49 | } 50 | 51 | workspace := compiler_create_workspace("Target Program"); 52 | if !workspace { 53 | print("Workspace creation failed.\n"); 54 | return; 55 | } 56 | 57 | files, run_strings := compiler_get_source_files(); 58 | if files { 59 | basename, path := get_basename_and_path(files[0]); 60 | if path { 61 | old_wd := get_working_directory(); 62 | set_working_directory(path); 63 | 64 | for * files { 65 | print("%\n", it); 66 | old_filename := < valid: bool, complete: bool { 99 | if !message return false, false; 100 | if message.kind == .COMPLETE return false, true; 101 | 102 | get_idents(message, true); 103 | 104 | return true, false; 105 | } 106 | 107 | my_check_message :: (message: *Message) -> valid: bool, complete: bool { 108 | if !message return false, false; 109 | if message.kind == .COMPLETE return false, true; 110 | 111 | get_idents(message); 112 | 113 | return true, false; 114 | } 115 | 116 | 117 | is_absolute_path :: (path: string) -> bool { // This routine is probably not as correct as we'd like. We'd like to put in a better one! But maybe we will stop doing the cwd thing, or do it differently; hard to say. 118 | if !path return false; 119 | 120 | if path[0] == #char "/" return true; // Backslashes have already been converted to forward slashes by this point. 121 | if path.count >= 3 { 122 | // @Robustness: Check for a drive letter in character 0? Anything else? 123 | if path[1] == #char ":" return true; 124 | } 125 | 126 | return false; 127 | } 128 | } 129 | 130 | 131 | 132 | identifier : string; 133 | 134 | Location_List :: [..] Source_Code_Range; 135 | locations : Table(*Code_Declaration, Location_List); 136 | 137 | 138 | condition :: (node: *Code_Node) -> bool { 139 | return decl.enclosing_load && decl.enclosing_load.enclosing_import.module_type == .MAIN_PROGRAM 140 | } 141 | 142 | 143 | to_revisit : [..] *Code_Ident; 144 | 145 | 146 | action :: (node: *Code_Node) { 147 | name : string = ---; 148 | decl : *Code_Declaration = ---; 149 | range : Source_Code_Range; 150 | 151 | 152 | if node.kind == .IDENT { 153 | ident := cast(*Code_Ident) node; 154 | name = ident.name; 155 | decl = ident.resolved_declaration; 156 | if !decl { 157 | array_add(*to_revisit, ident); 158 | revisit_node(ident); 159 | //print("Missing declaration for: %\n%\n\n", name, format_location(ident)); 160 | return; 161 | } 162 | else { 163 | //print("Found declaration for: %\n%\n%\n\n", name, format_location(ident), format_location(decl)); 164 | } 165 | 166 | range = make_code_range(node); 167 | } 168 | else if node.kind == .DECLARATION { 169 | decl = cast(*Code_Declaration) node; 170 | name = decl.name; 171 | range = make_code_range(decl); 172 | //print("% % %:%:%\n\n", decl.name, decl, decl.filename, decl.l0, decl.c0); 173 | } 174 | else 175 | return; 176 | 177 | if name == "" || decl.filename == "" return; 178 | 179 | existing_list, found := table_find(*locations, decl); 180 | if !found { 181 | list : Location_List; 182 | existing_list = list; 183 | } 184 | array_add(*existing_list, range); 185 | table_set(*locations, decl, existing_list); 186 | } 187 | 188 | 189 | check_ident :: (node: *Code_Node) { 190 | if node.kind == .DECLARATION { 191 | decl := cast(*Code_Declaration) node; 192 | location, found := table_find(*locations, decl); 193 | if !found { 194 | location_list : Location_List; 195 | table_set(*locations, decl, location_list); 196 | for expression: decl.expressions 197 | if condition(expression) 198 | check_ident(expression); 199 | } 200 | return; 201 | } 202 | 203 | if node.kind != .IDENT return; 204 | 205 | ident := cast(*Code_Ident) node; 206 | 207 | decl := ident.resolved_declaration; 208 | if !decl { 209 | array_add(*to_revisit, ident); 210 | //print("Missing declaration for: %\n%\n\n", name, format_location(ident)); 211 | return; 212 | } 213 | //else { 214 | // print("Found declaration for: %\n%\n%\n\n", name, format_location(ident), format_location(decl)); 215 | //} 216 | 217 | name := ident.name; 218 | if name == "" || decl.filename == "" return; 219 | 220 | range := make_code_range(node); 221 | 222 | existing_list, found := table_find(*locations, decl); 223 | if !found { 224 | list : Location_List; 225 | existing_list = list; 226 | //free(*list); 227 | } 228 | array_add(*existing_list, range); 229 | table_set(*locations, decl, existing_list); 230 | } 231 | 232 | 233 | get_idents :: (message: *Message, debug := false) { 234 | if message.kind == { 235 | case .TYPECHECKED; 236 | code := cast(*Message_Typechecked) message; 237 | for decl: code.declarations { 238 | if debug print("%\n", < *Code_Declaration { 299 | for ranges, decl: locations { 300 | if decl.filename != filename continue; 301 | for range: ranges { 302 | if line < range.line_number_start || line > range.line_number_end 303 | continue; 304 | 305 | if line == range.line_number_start && char < range.character_number_start 306 | || line == range.line_number_end && char > range.character_number_end 307 | continue; 308 | 309 | return decl; 310 | } 311 | } 312 | return null; 313 | } 314 | 315 | 316 | #scope_file 317 | 318 | 319 | format_location :: (node: *Code_Node) -> string { 320 | location := make_location(node); 321 | return sprint("%:%:%", location.fully_pathed_filename, location.line_number, location.character_number); 322 | } 323 | 324 | format_range :: (range: Source_Code_Range) -> string { 325 | return sprint("%:%:%|%|%", range.fully_pathed_filename, range.line_number_start, range.character_number_start, range.line_number_end, range.character_number_end); 326 | } 327 | 328 | 329 | //format_range :: (range: Source_Code_Range) -> string { 330 | // return sprint("%|%|%|%|%", range.fully_pathed_filename, range.line_number_start, range.character_number_start, range.line_number_end, range.character_number_end); 331 | //} 332 | -------------------------------------------------------------------------------- /tools/jai_run.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | jai c:\repos\jai-cookbook\tools\jai_run.jai -no_cwd -import_dir . -import_dir .. - %* 3 | -------------------------------------------------------------------------------- /tools/jai_run.jai: -------------------------------------------------------------------------------- 1 | // Used to run a .jai file in bytecode, without producing an executable. 2 | // Call in long form like this: 3 | // jai.exe jai_run.jai -- foo.jai [arg [arg...]] 4 | // but it's easier to just use the jai_run.bat file, and do: 5 | // jai_run foo.jai [arg [arg...]] 6 | // 7 | // To associate .jai files with jai_run you can try to use ftype and assoc like this: 8 | // 9 | // assoc .jai=jaifile 10 | // ftype jaifile="C:\repos\jai-cookbook\tools\run_jai.bat" "%L" %* 11 | // 12 | // ...but this didn't work for me on Windows 10 because Windows 10. To get it to work I used 13 | // this tool from NirSoft: http://www.nirsoft.net/utils/filetypesman-x64.zip 14 | // setting the command line for .jai to: (replace with your own path to jai_run.bat) 15 | // 16 | // "C:\Repos\jai-cookbook\tools\jai_run.bat" "%1" %* 17 | // 18 | // After this I can type "foo.jai bar" on the command line to run foo.jai directly. 19 | 20 | // @TODO update to replace `default_metaprogram` instead? 21 | 22 | 23 | #import "Basic"; 24 | #import "Compiler"; 25 | #import "String"; 26 | 27 | set_options :: (using options: *Build_Options) { 28 | emit_debug_info = .NONE; 29 | output_path = "c:/temp"; 30 | intermediate_path = "c:/temp"; 31 | } 32 | 33 | 34 | #run { 35 | args := get_toplevel_command_line(); 36 | while args.count > 0 && args[0] != "-"{ 37 | args.data += 1; args.count -= 1; 38 | } 39 | args.data += 1; args.count -= 1; 40 | 41 | if args.count == 0 || !ends_with_nocase(args[0], ".jai") { 42 | print("Must specify a .jai src file!"); 43 | exit(1); 44 | } 45 | 46 | filepath := args[0]; 47 | output_path := path_strip_filename(filepath); 48 | #if OS == .WINDOWS 49 | executable := join(path_strip_extension(basename(filepath)), ".exe"); 50 | else 51 | executable := path_strip_extension(basename(filepath)); 52 | 53 | set_working_directory(output_path); 54 | set_build_options_dc(.{do_output = false}); 55 | 56 | build_options := get_build_options(); 57 | build_options.output_type = .NO_OUTPUT; 58 | build_options.output_executable_name = executable; 59 | 60 | workspace := compiler_create_workspace("Main"); 61 | set_build_options(build_options, workspace); 62 | add_build_file(filepath, workspace); 63 | 64 | builder : String_Builder; 65 | init_string_builder(*builder); 66 | 67 | if args.count > 0 { 68 | print_to_builder(*builder, "__new_command_line_strings : [%] string : .[\n", args.count); 69 | for arg: args print_to_builder(*builder, " \"%\",\n", replace(arg, "\\", "\\\\")); 70 | append(*builder, "];\n"); 71 | print_to_builder(*builder, "__new_command_line_arguments : [%] *u8;\n", args.count); 72 | append(*builder, "#run {\n"); 73 | for arg, index: args 74 | print_to_builder(*builder, " __new_command_line_arguments[%1] = __new_command_line_strings[%1].data;\n", index); 75 | append(*builder, " __command_line_arguments.data = __new_command_line_arguments.data;\n"); 76 | append(*builder, " __command_line_arguments.count = __new_command_line_arguments.count;\n"); 77 | append(*builder, "}\n"); 78 | } 79 | 80 | append(*builder, "#run main();\n"); 81 | add_build_string(builder_to_string(*builder), workspace); 82 | } 83 | --------------------------------------------------------------------------------