From: Brendan Hansen Date: Thu, 5 May 2022 14:27:36 +0000 (-0500) Subject: added qoi decoding; file_contents no longer require string literals X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;h=fe2a6c0229c2cd9dd5673a093eeb4ed2c1683bd1;p=onyx.git added qoi decoding; file_contents no longer require string literals --- diff --git a/include/astnodes.h b/include/astnodes.h index 358c67f5..644acd6d 100644 --- a/include/astnodes.h +++ b/include/astnodes.h @@ -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 index 00000000..55cb1e60 --- /dev/null +++ b/modules/qoi/module.onyx @@ -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 index 00000000..f03c5d8d --- /dev/null +++ b/modules/qoi/qoi.onyx @@ -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; +} diff --git a/src/astnodes.c b/src/astnodes.c index 862b1726..2d276316 100644 --- a/src/astnodes.c +++ b/src/astnodes.c @@ -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 diff --git a/src/clone.c b/src/clone.c index 55ffc3fd..9c92cfda 100644 --- a/src/clone.c +++ b/src/clone.c @@ -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--; diff --git a/src/parser.c b/src/parser.c index 80ba0f8b..27972445 100644 --- a/src/parser.c +++ b/src/parser.c @@ -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; diff --git a/src/symres.c b/src/symres.c index 16a03db6..3cd5ddb5 100644 --- a/src/symres.c +++ b/src/symres.c @@ -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: diff --git a/src/wasm_emit.c b/src/wasm_emit.c index 3d4bbae9..9ddd934a 100644 --- a/src/wasm_emit.c +++ b/src/wasm_emit.c @@ -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;