added qoi decoding; file_contents no longer require string literals
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Thu, 5 May 2022 14:27:36 +0000 (09:27 -0500)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Thu, 5 May 2022 14:27:36 +0000 (09:27 -0500)
include/astnodes.h
modules/qoi/module.onyx [new file with mode: 0644]
modules/qoi/qoi.onyx [new file with mode: 0644]
src/astnodes.c
src/clone.c
src/parser.c
src/symres.c
src/wasm_emit.c

index 358c67f5905a6167836c6fd1ca4ce7ebdb35d647..644acd6d85cd07bd21e75424c0d7956385d73381 100644 (file)
@@ -649,7 +649,7 @@ struct AstFieldAccess   {
 struct AstFileContents  {
     AstTyped_base;
 
-    OnyxToken *filename_token;
+    AstTyped *filename_expr;
     char *filename; // The parsed file name, with '\' sequences removed and resolved to a particular file if possible.
 
     u32 addr, size;
diff --git a/modules/qoi/module.onyx b/modules/qoi/module.onyx
new file mode 100644 (file)
index 0000000..55cb1e6
--- /dev/null
@@ -0,0 +1,7 @@
+package qoi
+
+#package {
+    math :: package core.math    
+}
+
+#load "./qoi"
\ No newline at end of file
diff --git a/modules/qoi/qoi.onyx b/modules/qoi/qoi.onyx
new file mode 100644 (file)
index 0000000..f03c5d8
--- /dev/null
@@ -0,0 +1,165 @@
+package qoi
+
+Image :: struct {
+    width: u32;
+    height: u32;
+    channels: u32;
+
+    // This is dynamic so it can be freed.
+    data: [] u8;
+}
+
+#local Header :: struct #pack {
+    magic: [4] u8;
+    be_width: u32;
+    be_height: u32;
+    channels: u8;
+    colorspace: u8;
+}
+
+#local Color :: struct {
+    r, g, b, a: u8;
+}
+
+image_free :: (img: Image) {
+    delete(^img.data);
+}
+
+decode_image :: (buf: [] u8, allocator := context.allocator) -> Image {
+    img: Image;
+
+    pos := 0;
+    header := cast(^Header) buf.data;
+    pos += sizeof Header;
+
+    img.width  = swap_endian(header.be_width);
+    img.height = swap_endian(header.be_height);
+    img.channels = ~~header.channels;
+    img.data = make([] u8, img.width * img.height * img.channels, allocator=allocator);
+
+    out_pos := 0;
+
+    prev_pixel := Color.{0, 0, 0, 255};
+    prev_pixels: [64] Color;
+
+    while out_pos < img.data.count {
+        tag := buf[pos];
+        if tag == OP_RGB {
+            prev_pixel.r = buf[pos + 1];
+            prev_pixel.g = buf[pos + 2];
+            prev_pixel.b = buf[pos + 3];
+            output_color(prev_pixel);
+            pos += 4;
+
+        } elseif tag == OP_RGBA {
+            prev_pixel.r = buf[pos + 1];
+            prev_pixel.g = buf[pos + 2];
+            prev_pixel.b = buf[pos + 3];
+            prev_pixel.a = buf[pos + 4];
+            output_color(prev_pixel);
+            pos += 5;
+
+        } elseif tag & OP_MASK == OP_INDEX {
+            index := tag & ~~0x3f;
+
+            p := prev_pixels[index];
+            output_color(p);
+
+            pos += 1;
+            prev_pixel = p;
+
+        } elseif tag & OP_MASK == OP_DIFF {
+            dr := cast(i32) ((tag & ~~0x30) >> 4) - 2;
+            dg := cast(i32) ((tag & ~~0x0c) >> 2) - 2;
+            db := cast(i32) ((tag & ~~0x03)     ) - 2;
+
+            p := prev_pixel;
+            p.r = ~~(~~p.r + dr); @TODO // check that these wrap properly
+            p.g = ~~(~~p.g + dg); @TODO // check that these wrap properly
+            p.b = ~~(~~p.b + db); @TODO // check that these wrap properly
+
+            output_color(p);
+
+            pos += 1;
+            prev_pixel = p;
+
+        } elseif tag & OP_MASK == OP_LUMA {
+            dg    := cast(i32) ((buf[pos + 0] & ~~0x3f)     ) - 32;
+            dr_dg := cast(i32) ((buf[pos + 1] & ~~0xf0) >> 4) - 8;
+            db_dg := cast(i32) ((buf[pos + 1] & ~~0x0f)     ) - 8;
+
+            p := prev_pixel;
+            p.r = ~~(~~p.r + dr_dg + dg); @TODO // check that these wrap properly
+            p.g = ~~(~~p.g + dg); @TODO // check that these wrap properly
+            p.b = ~~(~~p.b + db_dg + dg); @TODO // check that these wrap properly
+
+            output_color(p);
+
+            pos += 2;
+            prev_pixel = p;
+
+        } elseif tag & OP_MASK == OP_RUN {
+            len := (tag & ~~0x3f) + 1;
+            for cast(u32) len {
+                output_color(prev_pixel);
+            }
+            pos += 1;
+        }
+
+        prev_pixels[hash_for_index(prev_pixel)] = prev_pixel;
+    }
+
+    return img;
+}
+
+#local {
+    OP_RGB   :: cast(u8) 0xfe
+    OP_RGBA  :: cast(u8) 0xff
+
+    OP_MASK  :: cast(u8) 0xc0
+    OP_INDEX :: cast(u8) 0x00
+    OP_DIFF  :: cast(u8) 0x40
+    OP_LUMA  :: cast(u8) 0x80
+    OP_RUN   :: cast(u8) 0xc0
+}
+
+#local hash_for_index :: macro (c: Color) -> u32 {
+    return ~~ ((c.r * 3 + c.g * 5 + c.b * 7 + c.a * 11) % 64);
+}
+
+#local swap_endian :: macro (x: u32) -> u32 {
+    y := x;
+    return (((y >> 24) & 0xff)      )
+        |  (((y >> 16) & 0xff) << 8 )
+        |  (((y >>  8) & 0xff) << 16)
+        |  (((y >>  0) & 0xff) << 24);
+}
+
+#local output_color :: macro (c: Color) {
+    if header.colorspace == 1 {
+        img.data[out_pos+0], img.data[out_pos+1], img.data[out_pos+2] = srgb_to_rgb(c);
+
+    } else {
+        img.data[out_pos + 0] = c.r;
+        img.data[out_pos + 1] = c.g;
+        img.data[out_pos + 2] = c.b;
+    }
+
+    if img.channels == 4 do img.data[out_pos + 3] = c.a;
+    out_pos += img.channels;
+}
+
+#local srgb_to_rgb :: macro (c: Color) -> (r: u8, g: u8, b: u8) {
+    sr := cast(f32) cast(u32) c.r / 255;
+    sg := cast(f32) cast(u32) c.g / 255;
+    sb := cast(f32) cast(u32) c.b / 255;
+
+    r := (sr / 12.92) if sr <= 0.0405 else math.pow((sr + 0.055)/1.055, 2.4);
+    g := (sg / 12.92) if sg <= 0.0405 else math.pow((sg + 0.055)/1.055, 2.4);
+    b := (sb / 12.92) if sb <= 0.0405 else math.pow((sb + 0.055)/1.055, 2.4);
+
+    or := cast(u8) cast(u32) math.floor(r * 255);
+    og := cast(u8) cast(u32) math.floor(g * 255);
+    ob := cast(u8) cast(u32) math.floor(b * 255);
+    return or, og, ob;
+}
index 862b17269e04e1b526cecacfdfa1abc98b9dc630..2d276316296ff75ca1c54c3960da296905f7b45d 100644 (file)
@@ -665,7 +665,10 @@ TypeMatch unify_node_and_type_(AstTyped** pnode, Type* type, b32 permanent) {
     Type* node_type = get_expression_type(node);
     if (types_are_compatible(node_type, type)) return TYPE_MATCH_SUCCESS;
 
-    i64 any_id = type_build_from_ast(context.ast_alloc, builtin_any_type)->id;
+    Type* any_type = type_build_from_ast(context.ast_alloc, builtin_any_type);
+    if (any_type == NULL) return TYPE_MATCH_YIELD;
+    i64 any_id = any_type->id;
+
     if (node_type && node_type->id != any_id && type->id == any_id) return TYPE_MATCH_SUCCESS;
 
     // Here are some of the ways you can unify a node with a type if the type of the
index 55ffc3fd194039144db431a6bc990e08ddd90e64..9c92cfda57aef5071c36d7c6fe4190f1596b221a 100644 (file)
@@ -24,7 +24,6 @@ static inline b32 should_clone(AstNode* node) {
         case Ast_Kind_Alias:
         case Ast_Kind_Code_Block:
         case Ast_Kind_Macro:
-        case Ast_Kind_File_Contents:
         case Ast_Kind_Symbol:
         case Ast_Kind_Poly_Struct_Type:
         case Ast_Kind_Basic_Type:
@@ -564,6 +563,11 @@ AstNode* ast_clone(bh_allocator a, void* n) {
             C(AstDoBlock, block);
             ((AstDoBlock *) nn)->type_node = (AstType *) &basic_type_auto_return;
             break;
+
+        case Ast_Kind_File_Contents:
+            C(AstFileContents, filename_expr);
+            E(nn);
+            break;
     }
 
     clone_depth--;
index 80ba0f8b2cd3b964547d2046ea1f2069c1e4e674..2797244585274d059cf1ce7ea427a82e4ee16f60 100644 (file)
@@ -545,10 +545,15 @@ static AstTyped* parse_factor(OnyxParser* parser) {
             if (parse_possible_directive(parser, "file_contents")) {
                 AstFileContents* fc = make_node(AstFileContents, Ast_Kind_File_Contents);
                 fc->token = parser->prev - 1;
-                fc->filename_token = expect_token(parser, Token_Type_Literal_String);
+                fc->filename_expr = parse_expression(parser, 0);
                 fc->type = type_make_slice(parser->allocator, &basic_types[Basic_Kind_U8]);
 
-                ENTITY_SUBMIT(fc);
+                if (parser->current_function_stack && bh_arr_length(parser->current_function_stack) > 0) {
+                    bh_arr_push(bh_arr_last(parser->current_function_stack)->nodes_that_need_entities_after_clone, (AstNode *) fc);
+                    
+                } else {
+                    ENTITY_SUBMIT(fc);
+                }
 
                 retval = (AstTyped *) fc;
                 break;
index 16a03db6a1ec2564251ce63628307875943a7d20..3cd5ddb58280f594cbf365bba1573def629accc0 100644 (file)
@@ -1045,7 +1045,7 @@ SymresStatus symres_function_header(AstFunction* func) {
             // This makes a lot of assumptions about how these nodes are being processed,
             // and I don't want to start using this with other nodes without considering
             // what the ramifications of that is.
-            assert((*node)->kind == Ast_Kind_Static_If);
+            assert((*node)->kind == Ast_Kind_Static_If || (*node)->kind == Ast_Kind_File_Contents);
 
             // Need to curr_scope->parent because curr_scope is the function body scope.
             Scope *scope = curr_scope->parent;
@@ -1575,6 +1575,17 @@ static SymresStatus symres_include(AstInclude* include) {
     return Symres_Goto_Parse;
 }
 
+static SymresStatus symres_file_contents(AstFileContents* fc) {
+    SYMRES(expression, &fc->filename_expr);
+
+    if (fc->filename_expr->kind != Ast_Kind_StrLit) {
+        onyx_report_error(fc->token->pos, Error_Critical, "Expected given expression to be a compile-time stirng literal.");
+        return Symres_Error;
+    }
+
+    return Symres_Success;
+}
+
 void symres_entity(Entity* ent) {
     if (ent->scope) scope_enter(ent->scope);
 
@@ -1595,6 +1606,7 @@ void symres_entity(Entity* ent) {
 
         case Entity_Type_Load_Path:
         case Entity_Type_Load_File:               ss = symres_include(ent->include); break;
+        case Entity_Type_File_Contents:           ss = symres_file_contents(ent->file_contents); break;
 
         case Entity_Type_Foreign_Function_Header:
         case Entity_Type_Temp_Function_Header:
index 3d4bbae9cdc5313369d8187025e5109e16c13aa4..9ddd934a1af93b9b849f18cfddc6f7eaa091d3a0 100644 (file)
@@ -3762,8 +3762,6 @@ static void emit_memory_reservation(OnyxWasmModule* mod, AstMemRes* memres) {
 }
 
 static void emit_file_contents(OnyxWasmModule* mod, AstFileContents* fc) {
-    token_toggle_end(fc->filename_token);
-
     // INVESTIGATE: I think that filename should always be NULL when this function starts because
     // a file contents entity is only processed once and therefore should only go through this step
     // once. But somehow filename isn't NULL occasionally so I have to check for that...
@@ -3773,15 +3771,17 @@ static void emit_file_contents(OnyxWasmModule* mod, AstFileContents* fc) {
         if (parent_file == NULL) parent_file = ".";
 
         char* parent_folder = bh_path_get_parent(parent_file, global_scratch_allocator);
+        
+        OnyxToken *filename_token = fc->filename_expr->token;
 
-        char* temp_fn     = bh_alloc_array(global_scratch_allocator, char, fc->filename_token->length);
-        i32   temp_fn_len = string_process_escape_seqs(temp_fn, fc->filename_token->text, fc->filename_token->length);
+        token_toggle_end(filename_token);
+        char* temp_fn     = bh_alloc_array(global_scratch_allocator, char, filename_token->length);
+        i32   temp_fn_len = string_process_escape_seqs(temp_fn, filename_token->text, filename_token->length);
         char* filename    = bh_lookup_file(temp_fn, parent_folder, "", 0, NULL, 0);
         fc->filename      = bh_strdup(global_heap_allocator, filename);
+        token_toggle_end(filename_token);
     }
 
-    token_toggle_end(fc->filename_token);
-
     i32 index = shgeti(mod->loaded_file_info, fc->filename);
     if (index != -1) {
         StrLitInfo info = mod->loaded_file_info[index].value;
@@ -3794,7 +3794,7 @@ static void emit_file_contents(OnyxWasmModule* mod, AstFileContents* fc) {
     bh_align(offset, 16);
 
     if (!bh_file_exists(fc->filename)) {
-        onyx_report_error(fc->filename_token->pos, Error_Critical,
+        onyx_report_error(fc->token->pos, Error_Critical,
                 "Unable to open file for reading, '%s'.",
                 fc->filename);
         return;