From d29423c24e654bccaf823a1e14f4dc322c7ec52f Mon Sep 17 00:00:00 2001 From: skytrias Date: Tue, 20 Dec 2022 12:17:23 +0100 Subject: [PATCH 01/97] add fontstash and nanovg port from heimdall --- vendor/fontstash/fontstash.odin | 1227 +++++++++++ vendor/nanovg/gl/frag.glsl | 123 ++ vendor/nanovg/gl/gl.odin | 1452 +++++++++++++ vendor/nanovg/gl/vert.glsl | 19 + vendor/nanovg/nanovg.odin | 3503 +++++++++++++++++++++++++++++++ 5 files changed, 6324 insertions(+) create mode 100644 vendor/fontstash/fontstash.odin create mode 100644 vendor/nanovg/gl/frag.glsl create mode 100644 vendor/nanovg/gl/gl.odin create mode 100644 vendor/nanovg/gl/vert.glsl create mode 100644 vendor/nanovg/nanovg.odin diff --git a/vendor/fontstash/fontstash.odin b/vendor/fontstash/fontstash.odin new file mode 100644 index 000000000..ba19d068a --- /dev/null +++ b/vendor/fontstash/fontstash.odin @@ -0,0 +1,1227 @@ +package fontstash + +import "core:runtime" +import "core:fmt" +import "core:log" +import "core:os" +import "core:mem" +import "core:math" +import "core:unicode" +import "core:strings" +import stbtt "vendor:stb/truetype" + +// This is a port from Fontstash into odin - specialized for nanovg + +// Notable features of Fontstash: +// Contains a *single* channel texture atlas for multiple fonts +// Manages a lookup table for frequent glyphs +// Allows blurred font glyphs +// Atlas can resize + +// Changes from the original: +// stb truetype only +// no scratch allocation -> parts use odins dynamic arrays +// leaves GPU vertex creation & texture management up to the user +// texture atlas expands by default + +INVALID :: -1 +MAX_STATES :: 20 +HASH_LUT_SIZE :: 256 +INIT_GLYPHS :: 256 +INIT_ATLAS_NODES :: 256 +MAX_FALLBACKS :: 20 +Glyph_Index :: i32 // in case you want to change the handle for glyph indices + +AlignHorizontal :: enum { + LEFT, + CENTER, + RIGHT, +} + +AlignVertical :: enum { + TOP, + MIDDLE, + BOTTOM, + BASELINE, +} + +Font :: struct { + name: string, // allocated + + info: stbtt.fontinfo, + loadedData: []byte, + + ascender: f32, + descender: f32, + lineHeight: f32, + + glyphs: [dynamic]Glyph, + lut: [HASH_LUT_SIZE]int, + + fallbacks: [MAX_FALLBACKS]int, + nfallbacks: int, +} + +Glyph :: struct { + codepoint: rune, + index: Glyph_Index, + next: int, + isize: i16, + blurSize: i16, + x0, y0, x1, y1: i16, + xoff, yoff: i16, + xadvance: i16, +} + +AtlasNode :: struct { + x, y, width: i16, +} + +Vertex :: struct #packed { + x, y: f32, + u, v: f32, + color: [4]u8, +} + +QuadLocation :: enum { + TOPLEFT, + BOTTOMLEFT, +} + +FontContext :: struct { + fonts: [dynamic]Font, // allocated using context.allocator + + // always assuming user wants to resize + nodes: [dynamic]AtlasNode, + + // actual pixels + textureData: []byte, // allocated using context.allocator + width, height: int, + // 1 / texture_atlas_width, 1 / texture_atlas_height + itw, ith: f32, + + // state + states: []State, + state_count: int, // used states + + location: QuadLocation, + + // dirty rectangle of the texture region that was updated + dirtyRect: [4]f32, + + // callbacks with userData passed + userData: rawptr, // by default set to the context + + // called when a texture is expanded and needs handling + callbackResize: proc(data: rawptr, w, h: int), + // called in state_end to update the texture region that changed + callbackUpdate: proc(data: rawptr, dirtyRect: [4]f32, textureData: rawptr), +} + +Init :: proc(using ctx: ^FontContext, w, h: int, loc: QuadLocation) { + userData = ctx + location = loc + fonts = make([dynamic]Font, 0, 8) + + itw = f32(1) / f32(w) + ith = f32(1) / f32(h) + textureData = make([]byte, w * h) + + width = w + height = h + nodes = make([dynamic]AtlasNode, 0, INIT_ATLAS_NODES) + __dirtyRectReset(ctx) + + states = make([]State, MAX_STATES) + + // NOTE NECESSARY + append(&nodes, AtlasNode { + width = i16(w), + }) + + __AtlasAddWhiteRect(ctx, 2, 2) + + PushState(ctx) + ClearState(ctx) +} + +Destroy :: proc(using ctx: ^FontContext) { + for font in &fonts { + delete(font.loadedData) + delete(font.glyphs) + } + + delete(states) + delete(textureData) + delete(fonts) + delete(nodes) +} + +Reset :: proc(using ctx: ^FontContext) { + __atlasReset(ctx, width, height) + __dirtyRectReset(ctx) + mem.zero_slice(textureData) + + for font in &fonts { + __lutReset(&font) + } + + __AtlasAddWhiteRect(ctx, 2, 2) + PushState(ctx) + ClearState(ctx) +} + +__atlasInsertNode :: proc(using ctx: ^FontContext, idx, x, y, w: int) { + // resize is alright here + resize(&nodes, len(nodes) + 1) + + // shift nodes up once to leave space at idx + for i := len(nodes) - 1; i > idx; i -= 1 { + nodes[i] = nodes[i - 1] + } + + // set new inserted one to properties + nodes[idx].x = i16(x) + nodes[idx].y = i16(y) + nodes[idx].width = i16(w) +} + +__atlasRemoveNode :: proc(using ctx: ^FontContext, idx: int) { + if len(nodes) == 0 { + return + } + + // remove node at index, shift elements down + for i in idx.. width { + __atlasInsertNode(ctx, len(nodes), width, 0, w - width) + } + + width = w + height = h +} + +__atlasReset :: proc(using ctx: ^FontContext, w, h: int) { + width = w + height = h + clear(&nodes) + + // init root node + append(&nodes, AtlasNode { + width = i16(w), + }) +} + +__AtlasAddSkylineLevel :: proc(using ctx: ^FontContext, idx, x, y, w, h: int) { + // insert new node + __atlasInsertNode(ctx, idx, x, y + h, w) + + // Delete skyline segments that fall under the shadow of the new segment. + for i := idx + 1; i < len(nodes); i += 1 { + if nodes[i].x < nodes[i - 1].x + nodes[i - 1].width { + shrink := nodes[i-1].x + nodes[i-1].width - nodes[i].x + nodes[i].x += i16(shrink) + nodes[i].width -= i16(shrink) + + if nodes[i].width <= 0 { + __atlasRemoveNode(ctx, i) + i -= 1 + } else { + break + } + } else { + break + } + } + + // Merge same height skyline segments that are next to each other. + for i := 0; i < len(nodes) - 1; i += 1 { + if nodes[i].y == nodes[i + 1].y { + nodes[i].width += nodes[i + 1].width + __atlasRemoveNode(ctx, i + 1) + i -= 1 + } + } +} + +__AtlasRectFits :: proc(using ctx: ^FontContext, i, w, h: int) -> int { + // Checks if there is enough space at the location of skyline span 'i', + // and return the max height of all skyline spans under that at that location, + // (think tetris block being dropped at that position). Or -1 if no space found. + x := int(nodes[i].x) + y := int(nodes[i].y) + + if x + w > width { + return -1 + } + + i := i + space_left := w + for space_left > 0 { + if i == len(nodes) { + return -1 + } + + y = max(y, int(nodes[i].y)) + if y + h > height { + return -1 + } + + space_left -= int(nodes[i].width) + i += 1 + } + + return y +} + +__AtlasAddRect :: proc(using ctx: ^FontContext, rw, rh: int) -> (rx, ry: int, ok: bool) { + besth := height + bestw := width + besti, bestx, besty := -1, -1, -1 + + // Bottom left fit heuristic. + for i in 0.. int { + data, ok := os.read_entire_file(path) + + if !ok { + log.panicf("FONT: failed to read font at %s", path) + } + + return AddFontMem(ctx, name, data) +} + +// push a font to the font stack +// optionally init with ascii characters at a wanted size +AddFontMem :: proc( + ctx: ^FontContext, + name: string, + data: []u8, +) -> int { + append(&ctx.fonts, Font {}) + res := &ctx.fonts[len(ctx.fonts) - 1] + res.loadedData = data + res.name = strings.clone(name) + + stbtt.InitFont(&res.info, &res.loadedData[0], 0) + ascent, descent, line_gap: i32 + stbtt.GetFontVMetrics(&res.info, &ascent, &descent, &line_gap) + fh := f32(ascent - descent) + res.ascender = f32(ascent) / fh + res.descender = f32(descent) / fh + res.lineHeight = (fh + f32(line_gap)) / fh + res.glyphs = make([dynamic]Glyph, 0, INIT_GLYPHS) + + __lutReset(res) + return len(ctx.fonts) - 1 +} + +AddFont :: proc { AddFontPath, AddFontMem } + +AddFallbackFont :: proc(ctx: ^FontContext, base, fallback: int) -> bool { + base_font := __getFont(ctx, base) + + if base_font.nfallbacks < MAX_FALLBACKS { + base_font.fallbacks[base_font.nfallbacks] = fallback + base_font.nfallbacks += 1 + return true + } + + return false +} + +ResetFallbackFont :: proc(ctx: ^FontContext, base: int) { + base_font := __getFont(ctx, base) + base_font.nfallbacks = 0 + clear(&base_font.glyphs) + __lutReset(base_font) +} + +// find font by name +GetFontByName :: proc(ctx: ^FontContext, name: string) -> int { + for font, i in ctx.fonts { + if font.name == name { + return i + } + } + + return INVALID +} + +__lutReset :: proc(font: ^Font) { + // set lookup table + for i in 0.. u32 { + a := a + a += ~(a << 15) + a ~= (a >> 10) + a += (a << 3) + a ~= (a >> 6) + a += (a << 11) + a ~= (a >> 16) + return a +} + +__renderGlyphBitmap :: proc( + font: ^Font, + output: []u8, + outWidth: i32, + outHeight: i32, + outStride: i32, + scaleX: f32, + scaleY: f32, + glyphIndex: Glyph_Index, +) { + stbtt.MakeGlyphBitmap(&font.info, raw_data(output), outWidth, outHeight, outStride, scaleX, scaleY, glyphIndex) +} + +__buildGlyphBitmap :: proc( + font: ^Font, + glyphIndex: Glyph_Index, + pixelSize: f32, + scale: f32, +) -> (advance, lsb, x0, y0, x1, y1: i32) { + stbtt.GetGlyphHMetrics(&font.info, glyphIndex, &advance, &lsb) + stbtt.GetGlyphBitmapBox(&font.info, glyphIndex, scale, scale, &x0, &y0, &x1, &y1) + return +} + +// get glyph and push to atlas if not exists +__getGlyph :: proc( + ctx: ^FontContext, + font: ^Font, + codepoint: rune, + isize: i16, + blurSize: i16 = 0, +) -> (res: ^Glyph) #no_bounds_check { + if isize < 2 { + return + } + + // find code point and size + h := __hashint(u32(codepoint)) & (HASH_LUT_SIZE - 1) + i := font.lut[h] + for i != -1 { + glyph := &font.glyphs[i] + + if + glyph.codepoint == codepoint && + glyph.isize == isize && + glyph.blurSize == blurSize + { + res = glyph + return + } + + i = glyph.next + } + + // could not find glyph, create it. + render_font := font // font used to render + glyph_index := __getGlyph_index(font, codepoint) + if glyph_index == 0 { + // lookout for possible fallbacks + for i in 0.. 0 { + __blur(dst, int(gw), int(gh), ctx.width, blurSize) + } + + ctx.dirtyRect[0] = cast(f32) min(int(ctx.dirtyRect[0]), int(res.x0)) + ctx.dirtyRect[1] = cast(f32) min(int(ctx.dirtyRect[1]), int(res.y0)) + ctx.dirtyRect[2] = cast(f32) max(int(ctx.dirtyRect[2]), int(res.x1)) + ctx.dirtyRect[3] = cast(f32) max(int(ctx.dirtyRect[3]), int(res.y1)) + + return +} + +///////////////////////////////// +// blur +///////////////////////////////// + +// Based on Exponential blur, Jani Huhtanen, 2006 + +BLUR_APREC :: 16 +BLUR_ZPREC :: 7 + +__blurCols :: proc(dst: []u8, w, h, dstStride, alpha: int) { + dst := dst + + for y in 0..> BLUR_APREC + dst[x] = u8(z >> BLUR_ZPREC) + } + + dst[w - 1] = 0 // force zero border + z = 0 + + for x := w - 2; x >= 0; x -= 1 { + z += (alpha * ((int(dst[x]) << BLUR_ZPREC) - z)) >> BLUR_APREC + dst[x] = u8(z >> BLUR_ZPREC) + } + + dst[0] = 0 // force zero border + dst = dst[dstStride:] // advance slice + } +} + +__blurRows :: proc(dst: []u8, w, h, dstStride, alpha: int) { + dst := dst + + for x in 0..> BLUR_APREC + dst[y] = u8(z >> BLUR_ZPREC) + } + + dst[(h - 1) * dstStride] = 0 // force zero border + z = 0 + + for y := (h - 2) * dstStride; y >= 0; y -= dstStride { + z += (alpha * ((int(dst[y]) << BLUR_ZPREC) - z)) >> BLUR_APREC + dst[y] = u8(z >> BLUR_ZPREC) + } + + dst[0] = 0 // force zero border + dst = dst[1:] // advance + } +} + +__blur :: proc(dst: []u8, w, h, dstStride: int, blurSize: i16) { + assert(blurSize != 0) + + // Calculate the alpha such that 90% of the kernel is within the radius. (Kernel extends to infinity) + sigma := f32(blurSize) * 0.57735 // 1 / sqrt(3) + alpha := int((1 << BLUR_APREC) * (1 - math.exp(-2.3 / (sigma + 1)))) + __blurRows(dst, w, h, dstStride, alpha) + __blurCols(dst, w, h, dstStride, alpha) + __blurRows(dst, w, h, dstStride, alpha) + __blurCols(dst, w, h, dstStride, alpha) +} + +///////////////////////////////// +// Texture expansion +///////////////////////////////// + +ExpandAtlas :: proc(ctx: ^FontContext, width, height: int, allocator := context.allocator) -> bool { + width := max(ctx.width, width) + height := max(ctx.height, height) + + if width == ctx.width && height == ctx.height { + return true + } + + if ctx.callbackResize != nil { + ctx.callbackResize(ctx.userData, width, height) + } + + data := make([]byte, width * height, allocator) + + for i in 0.. ctx.width { + mem.set(&data[i * width + ctx.width], 0, width - ctx.width) + } + } + + if height > ctx.height { + mem.set(&data[ctx.height * width], 0, (height - ctx.height) * width) + } + + delete(ctx.textureData) + ctx.textureData = data + + // increase atlas size + __atlasExpand(ctx, width, height) + + // add existing data as dirty + maxy := i16(0) + for node in ctx.nodes { + maxy = max(maxy, node.y) + } + ctx.dirtyRect[0] = 0 + ctx.dirtyRect[1] = 0 + ctx.dirtyRect[2] = f32(ctx.width) + ctx.dirtyRect[3] = f32(maxy) + + ctx.width = width + ctx.height = height + ctx.itw = 1.0 / f32(width) + ctx.ith = 1.0 / f32(height) + + return true +} + +ResetAtlas :: proc(ctx: ^FontContext, width, height: int, allocator := context.allocator) -> bool { + if width == ctx.width && height == ctx.height { + // just clear + mem.zero_slice(ctx.textureData) + } else { + // realloc + ctx.textureData = make([]byte, width * height, allocator) + } + + ctx.dirtyRect[0] = f32(width) + ctx.dirtyRect[1] = f32(height) + ctx.dirtyRect[2] = 0 + ctx.dirtyRect[3] = 0 + + // reset fonts + for font in &ctx.fonts { + clear(&font.glyphs) + __lutReset(&font) + } + + ctx.width = width + ctx.height = height + ctx.itw = 1.0 / f32(width) + ctx.ith = 1.0 / f32(height) + + __AtlasAddWhiteRect(ctx, 2, 2) + return true +} + +__getGlyph_index :: proc(font: ^Font, codepoint: rune) -> Glyph_Index { + return stbtt.FindGlyphIndex(&font.info, codepoint) +} + +__getPixelHeightScale :: proc(font: ^Font, pixel_height: f32) -> f32 { + return stbtt.ScaleForPixelHeight(&font.info, pixel_height) +} + +__getGlyphKernAdvance :: proc(font: ^Font, glyph1, glyph2: Glyph_Index) -> i32 { + return stbtt.GetGlyphKernAdvance(&font.info, glyph1, glyph2) +} + +// get a font with bounds checking +__getFont :: proc(ctx: ^FontContext, index: int, loc := #caller_location) -> ^Font #no_bounds_check { + runtime.bounds_check_error_loc(loc, index, len(ctx.fonts)) + return &ctx.fonts[index] +} + +// only useful for single glyphs where you quickly want the width +CodepointWidth :: proc( + font: ^Font, + codepoint: rune, + scale: f32, +) -> f32 { + glyph_index := __getGlyph_index(font, codepoint) + xadvance, lsb: i32 + stbtt.GetGlyphHMetrics(&font.info, glyph_index, &xadvance, &lsb) + return f32(xadvance) * scale +} + +// get top and bottom line boundary +LineBounds :: proc(ctx: ^FontContext, y: f32) -> (miny, maxy: f32) { + state := __getState(ctx) + font := __getFont(ctx, state.font) + isize := i16(state.size * 10.0) + y := y + y += __getVerticalAlign(ctx, font, state.av, isize) + + if ctx.location == .TOPLEFT { + miny = y - font.ascender * f32(isize) / 10 + maxy = miny + font.lineHeight * f32(isize / 10) + } else if ctx.location == .BOTTOMLEFT { + miny = y + font.ascender * f32(isize) / 10 + maxy = miny - font.lineHeight * f32(isize / 10) + } + + return +} + +// reset dirty rect +__dirtyRectReset :: proc(using ctx: ^FontContext) { + dirtyRect[0] = f32(width) + dirtyRect[1] = f32(height) + dirtyRect[2] = 0 + dirtyRect[3] = 0 +} + +// true when the dirty rectangle is valid and needs a texture update on the gpu +ValidateTexture :: proc(using ctx: ^FontContext, dirty: ^[4]f32) -> bool { + if dirtyRect[0] < dirtyRect[2] && dirtyRect[1] < dirtyRect[3] { + dirty[0] = dirtyRect[0] + dirty[1] = dirtyRect[1] + dirty[2] = dirtyRect[2] + dirty[3] = dirtyRect[3] + __dirtyRectReset(ctx) + return true + } + + return false +} + +// get alignment based on font +__getVerticalAlign :: proc( + ctx: ^FontContext, + font: ^Font, + av: AlignVertical, + pixelSize: i16, +) -> (res: f32) { + switch ctx.location { + case .TOPLEFT: { + switch av { + case .TOP: res = font.ascender * f32(pixelSize) / 10 + case .MIDDLE: res = (font.ascender + font.descender) / 2 * f32(pixelSize) / 10 + case .BASELINE: res = 0 + case .BOTTOM: res = font.descender * f32(pixelSize) / 10 + } + } + + case .BOTTOMLEFT: { + switch av { + case .TOP: res = -font.ascender * f32(pixelSize) / 10 + case .MIDDLE: res = -(font.ascender + font.descender) / 2 * f32(pixelSize) / 10 + case .BASELINE: res = 0 + case .BOTTOM: res = -font.descender * f32(pixelSize) / 10 + } + } + } + + return +} + +@(private) +UTF8_ACCEPT :: 0 + +@(private) +UTF8_REJECT :: 1 + +@(private) +utf8d := [400]u8 { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 00..1f + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 20..3f + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 40..5f + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 60..7f + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, // 80..9f + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, // a0..bf + 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // c0..df + 0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, // e0..ef + 0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, // f0..ff + 0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, // s0..s0 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1, // s1..s2 + 1,2,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1, // s3..s4 + 1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,3,1,1,1,1,1,1, // s5..s6 + 1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // s7..s8 +} + +// decode codepoints from a state +@(private) +__decutf8 :: #force_inline proc(state: ^rune, codep: ^rune, b: byte) -> bool { + b := rune(b) + type := utf8d[b] + codep^ = (state^ != UTF8_ACCEPT) ? ((b & 0x3f) | (codep^ << 6)) : ((0xff >> type) & (b)) + state^ = rune(utf8d[256 + state^ * 16 + rune(type)]) + return state^ == UTF8_ACCEPT +} + +// state used to share font options +State :: struct { + font: int, + size: f32, + color: [4]u8, + spacing: f32, + blur: f32, + + ah: AlignHorizontal, + av: AlignVertical, +} + +// quad that should be used to draw from the texture atlas +Quad :: struct { + x0, y0, s0, t0: f32, + x1, y1, s1, t1: f32, +} + +// text iteration with custom settings +TextIter :: struct { + x, y, nextx, nexty, scale, spacing: f32, + isize, iblur: i16, + + font: ^Font, + previousGlyphIndex: Glyph_Index, + + // unicode iteration + utf8state: rune, // utf8 + codepoint: rune, + text: string, + codepointCount: int, + + // byte indices + str: int, + next: int, + end: int, +} + +// push a state, copies the current one over to the next one +PushState :: proc(using ctx: ^FontContext, loc := #caller_location) #no_bounds_check { + runtime.bounds_check_error_loc(loc, state_count, MAX_STATES) + + if state_count > 0 { + states[state_count] = states[state_count - 1] + } + + state_count += 1 +} + +// pop a state +PopState :: proc(using ctx: ^FontContext) { + if state_count <= 1 { + log.error("FONTSTASH: state underflow! to many pops were called") + } else { + state_count -= 1 + } +} + +// clear current state +ClearState :: proc(ctx: ^FontContext) { + state := __getState(ctx) + state.size = 12 + state.color = 255 + state.blur = 0 + state.spacing = 0 + state.font = 0 + state.ah = .LEFT + state.av = .BASELINE +} + +__getState :: #force_inline proc(ctx: ^FontContext) -> ^State #no_bounds_check { + return &ctx.states[ctx.state_count - 1] +} + +SetSize :: proc(ctx: ^FontContext, size: f32) { + __getState(ctx).size = size +} + +SetColor :: proc(ctx: ^FontContext, color: [4]u8) { + __getState(ctx).color = color +} + +SetSpacing :: proc(ctx: ^FontContext, spacing: f32) { + __getState(ctx).spacing = spacing +} + +SetBlur :: proc(ctx: ^FontContext, blur: f32) { + __getState(ctx).blur = blur +} + +SetFont :: proc(ctx: ^FontContext, font: int) { + __getState(ctx).font = font +} + +SetAH :: SetAlignHorizontal +SetAV :: SetAlignVertical + +SetAlignHorizontal :: proc(ctx: ^FontContext, ah: AlignHorizontal) { + __getState(ctx).ah = ah +} + +SetAlignVertical :: proc(ctx: ^FontContext, av: AlignVertical) { + __getState(ctx).av = av +} + +__getQuad :: proc( + ctx: ^FontContext, + font: ^Font, + + previousGlyphIndex: i32, + glyph: ^Glyph, + + scale: f32, + spacing: f32, + + x, y: ^f32, + quad: ^Quad, +) { + if previousGlyphIndex != -1 { + adv := f32(__getGlyphKernAdvance(font, previousGlyphIndex, glyph.index)) * scale + x^ += f32(int(adv + spacing + 0.5)) + } + + // fill props right + rx, ry, x0, y0, x1, y1, xoff, yoff, glyph_width, glyph_height: f32 + xoff = f32(glyph.xoff + 1) + yoff = f32(glyph.yoff + 1) + x0 = f32(glyph.x0 + 1) + y0 = f32(glyph.y0 + 1) + x1 = f32(glyph.x1 - 1) + y1 = f32(glyph.y1 - 1) + + switch ctx.location { + case .TOPLEFT: { + rx = math.floor(x^ + xoff) + ry = math.floor(y^ + yoff) + + quad.x0 = rx + quad.y0 = ry + quad.x1 = rx + x1 - x0 + quad.y1 = ry + y1 - y0 + + quad.s0 = x0 * ctx.itw + quad.t0 = y0 * ctx.ith + quad.s1 = x1 * ctx.itw + quad.t1 = y1 * ctx.ith + } + + case .BOTTOMLEFT: { + rx = math.floor(x^ + xoff) + ry = math.floor(y^ - yoff) + + quad.x0 = rx + quad.y0 = ry + quad.x1 = rx + x1 - x0 + quad.y1 = ry - y1 + y0 + + quad.s0 = x0 * ctx.itw + quad.t0 = y0 * ctx.ith + quad.s1 = x1 * ctx.itw + quad.t1 = y1 * ctx.ith + } + } + + x^ += f32(int(f32(glyph.xadvance) / 10 + 0.5)) +} + +// init text iter struct with settings +TextIterInit :: proc( + ctx: ^FontContext, + x: f32, + y: f32, + text: string, +) -> (res: TextIter) { + state := __getState(ctx) + res.font = __getFont(ctx, state.font) + res.isize = i16(f32(state.size) * 10) + res.iblur = i16(state.blur) + res.scale = __getPixelHeightScale(res.font, f32(res.isize) / 10) + + // align horizontally + x := x + y := y + switch state.ah { + case .LEFT: {} + case .CENTER: { + width := TextBounds(ctx, text, x, y, nil) + x = math.round(x - width * 0.5) + } + case .RIGHT: { + width := TextBounds(ctx, text, x, y, nil) + x -= width + } + } + + // align vertically + y = math.round(y + __getVerticalAlign(ctx, res.font, state.av, res.isize)) + + // set positions + res.x = x + res.nextx = x + res.y = y + res.nexty = y + res.previousGlyphIndex = -1 + res.spacing = state.spacing + res.text = text + + res.str = 0 + res.next = 0 + res.end = len(text) + + return +} + +// step through each codepoint +TextIterNext :: proc( + ctx: ^FontContext, + iter: ^TextIter, + quad: ^Quad, +) -> (ok: bool) { + str := iter.next + iter.str = iter.next + + for str < iter.end { + defer str += 1 + + if __decutf8(&iter.utf8state, &iter.codepoint, iter.text[str]) { + iter.x = iter.nextx + iter.y = iter.nexty + iter.codepointCount += 1 + glyph := __getGlyph(ctx, iter.font, iter.codepoint, iter.isize, iter.iblur) + + if glyph != nil { + __getQuad(ctx, iter.font, iter.previousGlyphIndex, glyph, iter.scale, iter.spacing, &iter.nextx, &iter.nexty, quad) + } + + iter.previousGlyphIndex = glyph == nil ? -1 : glyph.index + ok = true + break + } + } + + iter.next = str + return +} + +// width of a text line, optionally the full rect +TextBounds :: proc( + ctx: ^FontContext, + text: string, + x: f32 = 0, + y: f32 = 0, + bounds: ^[4]f32 = nil, +) -> f32 { + state := __getState(ctx) + isize := i16(state.size * 10) + iblur := i16(state.blur) + font := __getFont(ctx, state.font) + + // bunch of state + x := x + y := y + minx := x + maxx := x + miny := y + maxy := y + start_x := x + + // iterate + scale := __getPixelHeightScale(font, f32(isize) / 10) + previousGlyphIndex: Glyph_Index = -1 + quad: Quad + utf8state: rune + codepoint: rune + for byte_offset in 0.. maxx { + maxx = quad.x1 + } + + if ctx.location == .TOPLEFT { + if quad.y0 < miny { + miny = quad.y0 + } + if quad.y1 > maxy { + maxy = quad.y1 + } + } else if ctx.location == .BOTTOMLEFT { + if quad.y1 < miny { + miny = quad.y1 + } + if quad.y0 > maxy { + maxy = quad.y0 + } + } + } + + previousGlyphIndex = glyph == nil ? -1 : glyph.index + } + } + + // horizontal alignment + advance := x - start_x + switch state.ah { + case .LEFT: {} + case .CENTER: { + minx -= advance * 0.5 + maxx -= advance * 0.5 + } + case .RIGHT: { + minx -= advance + maxx -= advance + } + } + + if bounds != nil { + bounds^ = { minx, miny, maxx, maxy } + } + + return advance +} + +VerticalMetrics :: proc( + ctx: ^FontContext, +) -> (ascender, descender, lineHeight: f32) { + state := __getState(ctx) + isize := i16(state.size * 10.0) + font := __getFont(ctx, state.font) + ascender = font.ascender * f32(isize / 10) + descender = font.descender * f32(isize / 10) + lineHeight = font.lineHeight * f32(isize / 10) + return +} + +// reset to single state +BeginState :: proc(using ctx: ^FontContext) { + state_count = 0 + PushState(ctx) + ClearState(ctx) +} + +// checks for texture updates after potential __getGlyph calls +EndState :: proc(using ctx: ^FontContext) { + // check for texture update + if dirtyRect[0] < dirtyRect[2] && dirtyRect[1] < dirtyRect[3] { + if callbackUpdate != nil { + callbackUpdate(userData, dirtyRect, raw_data(textureData)) + } + + __dirtyRectReset(ctx) + } +} \ No newline at end of file diff --git a/vendor/nanovg/gl/frag.glsl b/vendor/nanovg/gl/frag.glsl new file mode 100644 index 000000000..423214c9b --- /dev/null +++ b/vendor/nanovg/gl/frag.glsl @@ -0,0 +1,123 @@ +#ifdef GL_ES +#if defined(GL_FRAGMENT_PRECISION_HIGH) || defined(NANOVG_GL3) + precision highp float; +#else + precision mediump float; +#endif +#endif +#ifdef NANOVG_GL3 +#ifdef USE_UNIFORMBUFFER + layout(std140) uniform frag { + mat3 scissorMat; + mat3 paintMat; + vec4 innerCol; + vec4 outerCol; + vec2 scissorExt; + vec2 scissorScale; + vec2 extent; + float radius; + float feather; + float strokeMult; + float strokeThr; + int texType; + int type; + }; +#else // NANOVG_GL3 && !USE_UNIFORMBUFFER + uniform vec4 frag[UNIFORMARRAY_SIZE]; +#endif + uniform sampler2D tex; + in vec2 ftcoord; + in vec2 fpos; + out vec4 outColor; +#else // !NANOVG_GL3 + uniform vec4 frag[UNIFORMARRAY_SIZE]; + uniform sampler2D tex; + varying vec2 ftcoord; + varying vec2 fpos; +#endif +#ifndef USE_UNIFORMBUFFER + #define scissorMat mat3(frag[0].xyz, frag[1].xyz, frag[2].xyz) + #define paintMat mat3(frag[3].xyz, frag[4].xyz, frag[5].xyz) + #define innerCol frag[6] + #define outerCol frag[7] + #define scissorExt frag[8].xy + #define scissorScale frag[8].zw + #define extent frag[9].xy + #define radius frag[9].z + #define feather frag[9].w + #define strokeMult frag[10].x + #define strokeThr frag[10].y + #define texType int(frag[10].z) + #define type int(frag[10].w) +#endif + +float sdroundrect(vec2 pt, vec2 ext, float rad) { + vec2 ext2 = ext - vec2(rad,rad); + vec2 d = abs(pt) - ext2; + return min(max(d.x,d.y),0.0) + length(max(d,0.0)) - rad; +} + +// Scissoring +float scissorMask(vec2 p) { + vec2 sc = (abs((scissorMat * vec3(p,1.0)).xy) - scissorExt); + sc = vec2(0.5,0.5) - sc * scissorScale; + return clamp(sc.x,0.0,1.0) * clamp(sc.y,0.0,1.0); +} +#ifdef EDGE_AA +// Stroke - from [0..1] to clipped pyramid, where the slope is 1px. +float strokeMask() { + return min(1.0, (1.0-abs(ftcoord.x*2.0-1.0))*strokeMult) * min(1.0, ftcoord.y); +} +#endif + +void main(void) { + vec4 result; + float scissor = scissorMask(fpos); +#ifdef EDGE_AA + float strokeAlpha = strokeMask(); + if (strokeAlpha < strokeThr) discard; +#else + float strokeAlpha = 1.0; +#endif + if (type == 0) { // Gradient + // Calculate gradient color using box gradient + vec2 pt = (paintMat * vec3(fpos,1.0)).xy; + float d = clamp((sdroundrect(pt, extent, radius) + feather*0.5) / feather, 0.0, 1.0); + vec4 color = mix(innerCol,outerCol,d); + // Combine alpha + color *= strokeAlpha * scissor; + result = color; + } else if (type == 1) { // Image + // Calculate color fron texture + vec2 pt = (paintMat * vec3(fpos,1.0)).xy / extent; +#ifdef NANOVG_GL3 + vec4 color = texture(tex, pt); +#else + vec4 color = texture2D(tex, pt); +#endif + if (texType == 1) color = vec4(color.xyz*color.w,color.w); + if (texType == 2) color = vec4(color.x); + // Apply color tint and alpha. + color *= innerCol; + // Combine alpha + color *= strokeAlpha * scissor; + result = color; + } else if (type == 2) { // Stencil fill + result = vec4(1,1,1,1); + } else if (type == 3) { // Textured tris +#ifdef NANOVG_GL3 + vec4 color = texture(tex, ftcoord); +#else + vec4 color = texture2D(tex, ftcoord); +#endif + if (texType == 1) color = vec4(color.xyz*color.w,color.w); + if (texType == 2) color = vec4(color.x); + color *= scissor; + result = color * innerCol; + } +#ifdef NANOVG_GL3 + outColor = result; +#else + gl_FragColor = result; +#endif +} \ No newline at end of file diff --git a/vendor/nanovg/gl/gl.odin b/vendor/nanovg/gl/gl.odin new file mode 100644 index 000000000..1f4d8ceb0 --- /dev/null +++ b/vendor/nanovg/gl/gl.odin @@ -0,0 +1,1452 @@ +package nanovg_gl + +import "core:log" +import "core:strings" +import "core:mem" +import "core:math" +import "core:fmt" +import gl "vendor:OpenGL" +import nvg "../../nanovg" + +Color :: nvg.Color +Vertex :: nvg.Vertex +ImageFlags :: nvg.ImageFlags +TextureType :: nvg.Texture +Paint :: nvg.Paint +ScissorT :: nvg.ScissorT + +CreateFlag :: enum { + // Flag indicating if geometry based anti-aliasing is used (may not be needed when using MSAA). + ANTI_ALIAS, + // Flag indicating if strokes should be drawn using stencil buffer. The rendering will be a little + // slower, but path overlaps (i.e. self-intersecting or sharp turns) will be drawn just once. + STENCIL_STROKES, + // additional debug checks + DEBUG, +} +CreateFlags :: bit_set[CreateFlag] + +FRAG_BINDING :: 0 +USE_STATE_FILTER :: true +GL_UNIFORMARRAY_SIZE :: 11 + +UniformLoc :: enum { + VIEW_SIZE, + TEX, + FRAG, +} + +ShaderType :: enum i32 { + FILL_GRAD, + FILL_IMG, + SIMPLE, + IMG, +} + +Shader :: struct { + prog: u32, + frag: u32, + vert: u32, + loc: [UniformLoc]i32, +} + +Texture :: struct { + id: int, + tex: u32, + width, height: int, + type: TextureType, + flags: ImageFlags, +} + +Blend :: struct { + src_RGB: u32, + dst_RGB: u32, + src_alpha: u32, + dst_alpha: u32, +} + +CallType :: enum { + NONE, + FILL, + CONVEX_FILL, + STROKE, + TRIANGLES, +} + +Call :: struct { + type: CallType, + image: int, + pathOffset: int, + pathCount: int, + triangleOffset: int, + triangleCount: int, + uniformOffset: int, + blendFunc: Blend, +} + +Path :: struct { + fillOffset: int, + fillCount: int, + strokeOffset: int, + strokeCount: int, +} + +when GL2_IMPLEMENTATION { + FragUniforms :: struct #raw_union { + using _: struct { + scissorMat: [12]f32, // matrices are actually 3 vec4s + paintMat: [12]f32, + innerColor: Color, + outerColor: Color, + scissorExt: [2]f32, + scissorScale: [2]f32, + extent: [2]f32, + radius: f32, + feather: f32, + strokeMult: f32, + strokeThr: f32, + texType: i32, + type: ShaderType, + }, + uniform_array: [GL_UNIFORMARRAY_SIZE][4]f32, + } +} else { + FragUniforms :: struct #packed { + scissorMat: [12]f32, // matrices are actually 3 vec4s + paintMat: [12]f32, + innerColor: Color, + outerColor: Color, + scissorExt: [2]f32, + scissorScale: [2]f32, + extent: [2]f32, + radius: f32, + feather: f32, + strokeMult: f32, + strokeThr: f32, + texType: i32, + type: ShaderType, + } +} + +GL2_IMPLEMENTATION :: false +GL3_IMPLEMENTATION :: true +GLES2_IMPLEMENTATION :: false +GLES3_IMPLEMENTATION :: false + +when GL2_IMPLEMENTATION { + GL2 :: true + GL3 :: false + GLES2 :: false + GLES3 :: false + GL_IMPLEMENTATION :: true + GL_USE_UNIFORMBUFFER :: false +} else when GL3_IMPLEMENTATION { + GL2 :: false + GL3 :: true + GLES2 :: false + GLES3 :: false + GL_IMPLEMENTATION :: true + GL_USE_UNIFORMBUFFER :: true +} else when GLES2_IMPLEMENTATION { + GL2 :: false + GL3 :: false + GLES2 :: true + GLES3 :: false + GL_IMPLEMENTATION :: true + GL_USE_UNIFORMBUFFER :: false +} else when GLES3_IMPLEMENTATION { + GL2 :: false + GL3 :: false + GLES2 :: false + GLES3 :: true + GL_IMPLEMENTATION :: true + GL_USE_UNIFORMBUFFER :: false +} + +Context :: struct { + shader: Shader, + textures: [dynamic]Texture, + view: [2]f32, + textureId: int, + + vertBuf: u32, + vertArr: u32, // GL3 + fragBuf: u32, // USE_UNIFORMBUFFER + fragSize: int, + flags: CreateFlags, + + // Per frame buffers + calls: [dynamic]Call, + paths: [dynamic]Path, + verts: [dynamic]Vertex, + uniforms: [dynamic]byte, + + // cached state used for state filter + boundTexture: u32, + stencilMask: u32, + stencilFunc: u32, + stencilFuncRef: i32, + stencilFuncMask: u32, + blendFunc: Blend, + + dummyTex: int, +} + +__nearestPow2 :: proc(num: uint) -> uint { + n := num > 0 ? num - 1 : 0 + n |= n >> 1 + n |= n >> 2 + n |= n >> 4 + n |= n >> 8 + n |= n >> 16 + n += 1 + return n +} + +__bindTexture :: proc(ctx: ^Context, tex: u32) { + when USE_STATE_FILTER { + if ctx.boundTexture != tex { + ctx.boundTexture = tex + gl.BindTexture(gl.TEXTURE_2D, tex) + } + } else { + gl.BindTexture(gl.TEXTURE_2D, tex) + } +} + +__stencilMask :: proc(ctx: ^Context, mask: u32) { + when USE_STATE_FILTER { + if ctx.stencilMask != mask { + ctx.stencilMask = mask + gl.StencilMask(mask) + } + } else { + gl.StencilMask(mask) + } +} + +__stencilFunc :: proc(ctx: ^Context, func: u32, ref: i32, mask: u32) { + when USE_STATE_FILTER { + if ctx.stencilFunc != func || + ctx.stencilFuncRef != ref || + ctx.stencilFuncMask != mask { + ctx.stencilFunc = func + ctx.stencilFuncRef = ref + ctx.stencilFuncMask = mask + gl.StencilFunc(func, ref, mask) + } + } else { + gl.StencilFunc(func, ref, mask) + } +} + +__blendFuncSeparate :: proc(ctx: ^Context, blend: ^Blend) { + when USE_STATE_FILTER { + if ctx.blendFunc != blend^ { + ctx.blendFunc = blend^ + gl.BlendFuncSeparate(blend.src_RGB, blend.dst_RGB, blend.src_alpha, blend.dst_alpha) + } + } else { + gl.BlendFuncSeparate(blend.src_RGB, blend.dst_RGB, blend.src_alpha, blend.dst_alpha) + } +} + +__allocTexture :: proc(ctx: ^Context) -> (tex: ^Texture) { + for texture in &ctx.textures { + if texture.id == 0 { + tex = &texture + break + } + } + + if tex == nil { + append(&ctx.textures, Texture {}) + tex = &ctx.textures[len(ctx.textures) - 1] + } + + tex^ = {} + ctx.textureId += 1 + tex.id = ctx.textureId + + return +} + +__findTexture :: proc(ctx: ^Context, id: int) -> ^Texture { + for texture in &ctx.textures { + if texture.id == id { + return &texture + } + } + + return nil +} + +__deleteTexture :: proc(ctx: ^Context, id: int) -> bool { + for texture, i in &ctx.textures { + if texture.id == id { + if texture.tex != 0 && (.NO_DELETE not_in texture.flags) { + gl.DeleteTextures(1, &texture.tex) + } + + ctx.textures[i] = {} + return true + } + } + + return false +} + +__deleteShader :: proc(shader: ^Shader) { + if shader.prog != 0 { + gl.DeleteProgram(shader.prog) + } + + if shader.vert != 0 { + gl.DeleteShader(shader.vert) + } + + if shader.frag != 0 { + gl.DeleteShader(shader.frag) + } +} + +__getUniforms :: proc(shader: ^Shader) { + shader.loc[.VIEW_SIZE] = gl.GetUniformLocation(shader.prog, "viewSize") + shader.loc[.TEX] = gl.GetUniformLocation(shader.prog, "tex") + + when GL_USE_UNIFORMBUFFER { + shader.loc[.FRAG] = i32(gl.GetUniformBlockIndex(shader.prog, "frag")) + } else { + shader.loc[.FRAG] = gl.GetUniformLocation(shader.prog, "frag") + } +} + +vert_shader := #load("vert.glsl") +frag_shader := #load("frag.glsl") + +__renderCreate :: proc(uptr: rawptr) -> bool { + ctx := cast(^Context) uptr + + // just build the string at runtime + builder := strings.builder_make(0, 512, context.temp_allocator) + + when GL2 { + strings.write_string(&builder, "#define NANOVG_GL2 1\n") + } else when GL3 { + strings.write_string(&builder, "#version 150 core\n#define NANOVG_GL3 1\n") + } else when GLES2 { + strings.write_string(&builder, "#version 100\n#define NANOVG_GL2 1\n") + } else when GLES3 { + strings.write_string(&builder, "#version 300 es\n#define NANOVG_GL3 1\n") + } + + when GL_USE_UNIFORMBUFFER { + strings.write_string(&builder, "#define USE_UNIFORMBUFFER 1\n") + } else { + strings.write_string(&builder, "#define UNIFORMARRAY_SIZE 11\n") + } + + __checkError(ctx, "init") + + shader_header := strings.to_string(builder) + anti: string = .ANTI_ALIAS in ctx.flags ? "#define EDGE_AA 1\n" : " " + if !__createShader( + &ctx.shader, + shader_header, + anti, + string(vert_shader), + string(frag_shader), + ) { + return false + } + + __checkError(ctx, "uniform locations") + __getUniforms(&ctx.shader) + + when GL3 { + gl.GenVertexArrays(1, &ctx.vertArr) + } + + gl.GenBuffers(1, &ctx.vertBuf) + align := i32(4) + + when GL_USE_UNIFORMBUFFER { + // Create UBOs + gl.UniformBlockBinding(ctx.shader.prog, u32(ctx.shader.loc[.FRAG]), FRAG_BINDING) + gl.GenBuffers(1, &ctx.fragBuf) + gl.GetIntegerv(gl.UNIFORM_BUFFER_OFFSET_ALIGNMENT, &align) + } + + ctx.fragSize = int(size_of(FragUniforms) + align - size_of(FragUniforms) % align) + // ctx.fragSize = size_of(FragUniforms) + ctx.dummyTex = __renderCreateTexture(ctx, .Alpha, 1, 1, {}, nil) + + __checkError(ctx, "create done") + + gl.Finish() + + return true +} + +__renderCreateTexture :: proc( + uptr: rawptr, + type: TextureType, + w, h: int, + imageFlags: ImageFlags, + data: []byte, +) -> int { + ctx := cast(^Context) uptr + tex := __allocTexture(ctx) + imageFlags := imageFlags + + if tex == nil { + return 0 + } + + when GLES2 { + if __nearestPow2(uint(w)) != uint(w) || __nearestPow2(uint(h)) != uint(h) { + // No repeat + if (.REPEAT_X in imageFlags) || (.REPEAT_Y in imageFlags) { + log.errorf("Repeat X/Y is not supported for non power-of-two textures (%d x %d)\n", w, h) + excl(&imageFlags, ImageFlags { .REPEAT_X, .REPEAT_Y }) + } + + // No mips. + if .GENERATE_MIPMAPS in imageFlags { + log.errorf("Mip-maps is not support for non power-of-two textures (%d x %d)\n", w, h); + excl(&imageFlags, nvg.Image_Flag.GENERATE_MIPMAPS) + } + } + } + + gl.GenTextures(1, &tex.tex) + tex.width = w + tex.height = h + tex.type = type + tex.flags = imageFlags + __bindTexture(ctx, tex.tex) + + gl.PixelStorei(gl.UNPACK_ALIGNMENT,1) + + when GLES2 { + gl.PixelStorei(gl.UNPACK_ROW_LENGTH, i32(tex.width)) + gl.PixelStorei(gl.UNPACK_SKIP_PIXELS, 0) + gl.PixelStorei(gl.UNPACK_SKIP_ROWS, 0) + } + + when GL2 { + if .GENERATE_MIPMAPS in imageFlags { + gl.TexParameteri(gl.TEXTURE_2D, GENERATE_MIPMAP, 1) + } + } + + if type == .RGBA { + gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, i32(w), i32(h), 0, gl.RGBA, gl.UNSIGNED_BYTE, raw_data(data)) + } else { + when GLES2 || GL2 { + // TODO missing in odin + gl.TexImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, w, h, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, data) + } else when GLES3 { + gl.TexImage2D(gl.TEXTURE_2D, 0, gl.R8, i32(w), i32(h), 0, gl.RED, gl.UNSIGNED_BYTE, raw_data(data)) + } else { + gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RED, i32(w), i32(h), 0, gl.RED, gl.UNSIGNED_BYTE, raw_data(data)) + } + } + + if .GENERATE_MIPMAPS in imageFlags { + if .NEAREST in imageFlags { + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST_MIPMAP_NEAREST) + } else { + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR) + } + } else { + if .NEAREST in imageFlags { + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST) + } else { + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) + } + } + + if .NEAREST in imageFlags { + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST) + } else { + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) + } + + if .REPEAT_X in imageFlags { + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT) + } else { + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) + } + + if .REPEAT_Y in imageFlags { + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT) + } else { + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) + } + + gl.PixelStorei(gl.UNPACK_ALIGNMENT, 4) + + when GLES2 { + gl.PixelStorei(gl.UNPACK_ROW_LENGTH, 0) + gl.PixelStorei(gl.UNPACK_SKIP_PIXELS, 0) + gl.PixelStorei(gl.UNPACK_SKIP_ROWS, 0) + } + + // The new way to build mipmaps on GLES and GL3 + when !GL2 { + if .GENERATE_MIPMAPS in imageFlags { + gl.GenerateMipmap(gl.TEXTURE_2D) + } + } + + __checkError(ctx, "create tex") + __bindTexture(ctx, 0) + + return tex.id +} + +__checkError :: proc(ctx: ^Context, str: string) { + if .DEBUG in ctx.flags { + err := gl.GetError() + + if err != gl.NO_ERROR { + log.errorf("FOUND ERROR %08x:\n\t%s\n", err, str) + } + } +} + +__checkProgramError :: proc(prog: u32) { + status: i32 + gl.GetProgramiv(prog, gl.LINK_STATUS, &status) + length: i32 + gl.GetProgramiv(prog, gl.INFO_LOG_LENGTH, &length) + + if status == 0 { + temp := make([]byte, length) + defer delete(temp) + + gl.GetProgramInfoLog(prog, length, nil, raw_data(temp)) + log.errorf("Program Error:\n%s\n", string(temp[:length])) + } +} + +__checkShaderError :: proc(shader: u32, type: string) { + status: i32 + gl.GetShaderiv(shader, gl.COMPILE_STATUS, &status) + length: i32 + gl.GetShaderiv(shader, gl.INFO_LOG_LENGTH, &length) + + if status == 0 { + temp := make([]byte, length) + defer delete(temp) + + gl.GetShaderInfoLog(shader, length, nil, raw_data(temp)) + log.errorf("Shader error:\n%s\n", string(temp[:length])) + } +} + +// TODO good case for or_return +__createShader :: proc( + shader: ^Shader, + header: string, + opts: string, + vshader: string, + fshader: string, +) -> bool { + shader^ = {} + str: [3]cstring + lengths: [3]i32 + str[0] = cstring(raw_data(header)) + str[1] = cstring(raw_data(opts)) + + lengths[0] = i32(len(header)) + lengths[1] = i32(len(opts)) + + prog := gl.CreateProgram() + vert := gl.CreateShader(gl.VERTEX_SHADER) + frag := gl.CreateShader(gl.FRAGMENT_SHADER) + + // vert shader + str[2] = cstring(raw_data(vshader)) + lengths[2] = i32(len(vshader)) + gl.ShaderSource(vert, 3, &str[0], &lengths[0]) + gl.CompileShader(vert) + __checkShaderError(vert, "vert") + + // fragment shader + str[2] = cstring(raw_data(fshader)) + lengths[2] = i32(len(fshader)) + gl.ShaderSource(frag, 3, &str[0], &lengths[0]) + gl.CompileShader(frag) + __checkShaderError(frag, "frag") + + gl.AttachShader(prog, vert) + gl.AttachShader(prog, frag) + + gl.BindAttribLocation(prog, 0, "vertex") + gl.BindAttribLocation(prog, 1, "tcoord") + + gl.LinkProgram(prog) + __checkProgramError(prog) + + shader.prog = prog + shader.vert = vert + shader.frag = frag + return true +} + +__renderDeleteTexture :: proc(uptr: rawptr, image: int) -> bool { + ctx := cast(^Context) uptr + return __deleteTexture(ctx, image) +} + +__renderUpdateTexture :: proc( + uptr: rawptr, + image: int, + x, y: int, + w, h: int, + data: []byte, +) -> bool { + ctx := cast(^Context) uptr + tex := __findTexture(ctx, image) + + if tex == nil { + return false + } + + __bindTexture(ctx, tex.tex) + + gl.PixelStorei(gl.UNPACK_ALIGNMENT,1) + + x := x + w := w + data := data + + when GLES2 { + gl.PixelStorei(gl.UNPACK_ROW_LENGTH, i32(tex.width)) + gl.PixelStorei(gl.UNPACK_SKIP_PIXELS, i32(x)) + gl.PixelStorei(gl.UNPACK_SKIP_ROWS, i32(y)) + } else { + // No support for all of skip, need to update a whole row at a time. + if tex.type == .RGBA { + data = data[y * tex.width * 4:] + } else { + data = data[y * tex.width:] + } + + x = 0 + w = tex.width + } + + if tex.type == .RGBA { + gl.TexSubImage2D(gl.TEXTURE_2D, 0, i32(x), i32(y), i32(w), i32(h), gl.RGBA, gl.UNSIGNED_BYTE, raw_data(data)) + } else { + when GLES2 || GL2 { + // TODO is missing in odin + // gl.TexSubImage2D(gl.TEXTURE_2D, 0, i32(x), i32(y), i32(w), i32(h), gl.LUMINANCE, gl.UNSIGNED_BYTE, raw_data(data)) + } else { + gl.TexSubImage2D(gl.TEXTURE_2D, 0, i32(x), i32(y), i32(w), i32(h), gl.RED, gl.UNSIGNED_BYTE, raw_data(data)) + } + } + + gl.PixelStorei(gl.UNPACK_ALIGNMENT, 4) + + when GLES2 { + gl.PixelStorei(gl.UNPACK_ROW_LENGTH, 0) + gl.PixelStorei(gl.UNPACK_SKIP_PIXELS, 0) + gl.PixelStorei(gl.UNPACK_SKIP_ROWS, 0) + } + + __bindTexture(ctx, 0) + + return true +} + +__renderGetTextureSize :: proc(uptr: rawptr, image: int, w, h: ^int) -> bool { + ctx := cast(^Context) uptr + tex := __findTexture(ctx, image) + + if tex == nil { + return false + } + + w^ = tex.width + h^ = tex.height + return true +} + +__xformToMat3x4 :: proc(m3: ^[12]f32, t: [6]f32) { + m3[0] = t[0] + m3[1] = t[1] + m3[2] = 0 + m3[3] = 0 + m3[4] = t[2] + m3[5] = t[3] + m3[6] = 0 + m3[7] = 0 + m3[8] = t[4] + m3[9] = t[5] + m3[10] = 1 + m3[11] = 0 +} + +__premulColor :: proc(c: Color) -> (res: Color) { + res = c + res.r *= c.a + res.g *= c.a + res.b *= c.a + return +} + +__convertPaint :: proc( + ctx: ^Context, + frag: ^FragUniforms, + paint: ^Paint, + scissor: ^ScissorT, + width: f32, + fringe: f32, + strokeThr: f32, +) -> bool { + invxform: [6]f32 + frag^ = {} + frag.innerColor = __premulColor(paint.innerColor) + frag.outerColor = __premulColor(paint.outerColor) + + if scissor.extent[0] < -0.5 || scissor.extent[1] < -0.5 { + frag.scissorMat = {} + frag.scissorExt[0] = 1.0 + frag.scissorExt[1] = 1.0 + frag.scissorScale[0] = 1.0 + frag.scissorScale[1] = 1.0 + } else { + nvg.TransformInverse(&invxform, scissor.xform) + __xformToMat3x4(&frag.scissorMat, invxform) + frag.scissorExt[0] = scissor.extent[0] + frag.scissorExt[1] = scissor.extent[1] + frag.scissorScale[0] = math.sqrt(scissor.xform[0]*scissor.xform[0] + scissor.xform[2]*scissor.xform[2]) / fringe + frag.scissorScale[1] = math.sqrt(scissor.xform[1]*scissor.xform[1] + scissor.xform[3]*scissor.xform[3]) / fringe + } + + frag.extent = paint.extent + frag.strokeMult = (width * 0.5 + fringe * 0.5) / fringe + frag.strokeThr = strokeThr + + if paint.image != 0 { + tex := __findTexture(ctx, paint.image) + + if tex == nil { + return false + } + + // TODO maybe inversed? + if .FLIP_Y in tex.flags { + m1: [6]f32 + m2: [6]f32 + nvg.TransformTranslate(&m1, 0.0, frag.extent[1] * 0.5) + nvg.TransformMultiply(&m1, paint.xform) + nvg.TransformScale(&m2, 1.0, -1.0) + nvg.TransformMultiply(&m2, m1) + nvg.TransformTranslate(&m1, 0.0, -frag.extent[1] * 0.5) + nvg.TransformMultiply(&m1, m2) + nvg.TransformInverse(&invxform, m1) + } else { + nvg.TransformInverse(&invxform, paint.xform) + } + + frag.type = .FILL_IMG + + when GL_USE_UNIFORMBUFFER { + if tex.type == .RGBA { + frag.texType = (.PREMULTIPLIED in tex.flags) ? 0 : 1 + } else { + frag.texType = 2 + } + } else { + if tex.type == .RGBA { + frag.texType = (.PREMULTIPLIED in tex.flags) ? 0.0 : 1.0 + } else { + frag.texType = 2.0 + } + } + } else { + frag.type = .FILL_GRAD + frag.radius = paint.radius + frag.feather = paint.feather + nvg.TransformInverse(&invxform, paint.xform) + } + + __xformToMat3x4(&frag.paintMat, invxform) + + return true +} + +__setUniforms :: proc(ctx: ^Context, uniformOffset: int, image: int) { + when GL_USE_UNIFORMBUFFER { + gl.BindBufferRange(gl.UNIFORM_BUFFER, FRAG_BINDING, ctx.fragBuf, uniformOffset, size_of(FragUniforms)) + } else { + frag := __fragUniformPtr(ctx, uniformOffset) + gl.Uniform4fv(ctx.shader.loc[.FRAG], GL_UNIFORMARRAY_SIZE, cast(^f32) frag) + } + + __checkError(ctx, "uniform4") + + tex: ^Texture + if image != 0 { + tex = __findTexture(ctx, image) + } + + // If no image is set, use empty texture + if tex == nil { + tex = __findTexture(ctx, ctx.dummyTex) + } + + __bindTexture(ctx, tex != nil ? tex.tex : 0) + __checkError(ctx, "tex paint tex") +} + +__renderViewport :: proc(uptr: rawptr, width, height, devicePixelRatio: f32) { + ctx := cast(^Context) uptr + ctx.view[0] = width + ctx.view[1] = height +} + +__fill :: proc(ctx: ^Context, call: ^Call) { + paths := ctx.paths[call.pathOffset:] + + // Draw shapes + gl.Enable(gl.STENCIL_TEST) + __stencilMask(ctx, 0xff) + __stencilFunc(ctx, gl.ALWAYS, 0, 0xff) + gl.ColorMask(gl.FALSE, gl.FALSE, gl.FALSE, gl.FALSE) + + // set bindpoint for solid loc + __setUniforms(ctx, call.uniformOffset, 0) + __checkError(ctx, "fill simple") + + gl.StencilOpSeparate(gl.FRONT, gl.KEEP, gl.KEEP, gl.INCR_WRAP) + gl.StencilOpSeparate(gl.BACK, gl.KEEP, gl.KEEP, gl.DECR_WRAP) + gl.Disable(gl.CULL_FACE) + for i in 0.. 0 { + gl.DrawArrays(gl.TRIANGLE_STRIP, i32(paths[i].strokeOffset), i32(paths[i].strokeCount)) + } + } +} + +__stroke :: proc(ctx: ^Context, call: ^Call) { + paths := ctx.paths[call.pathOffset:] + + if .STENCIL_STROKES in ctx.flags { + gl.Enable(gl.STENCIL_TEST) + __stencilMask(ctx, 0xff) + + // Fill the stroke base without overlap + __stencilFunc(ctx, gl.EQUAL, 0x0, 0xff) + gl.StencilOp(gl.KEEP, gl.KEEP, gl.INCR) + __setUniforms(ctx, call.uniformOffset + ctx.fragSize, call.image) + __checkError(ctx, "stroke fill 0") + + for i in 0.. Blend { + table := BLEND_FACTOR_TABLE + blend := Blend { + table[op.srcRGB], + table[op.dstRGB], + table[op.srcAlpha], + table[op.dstAlpha], + } + return blend +} + +__renderFlush :: proc(uptr: rawptr) { + ctx := cast(^Context) uptr + + if len(ctx.calls) > 0 { + // Setup require GL state. + gl.UseProgram(ctx.shader.prog) + + gl.Enable(gl.CULL_FACE) + gl.CullFace(gl.BACK) + gl.FrontFace(gl.CCW) + gl.Enable(gl.BLEND) + gl.Disable(gl.DEPTH_TEST) + gl.Disable(gl.SCISSOR_TEST) + gl.ColorMask(gl.TRUE, gl.TRUE, gl.TRUE, gl.TRUE) + gl.StencilMask(0xffffffff) + gl.StencilOp(gl.KEEP, gl.KEEP, gl.KEEP) + gl.StencilFunc(gl.ALWAYS, 0, 0xffffffff) + gl.ActiveTexture(gl.TEXTURE0) + gl.BindTexture(gl.TEXTURE_2D, 0) + + when USE_STATE_FILTER { + ctx.boundTexture = 0 + ctx.stencilMask = 0xffffffff + ctx.stencilFunc = gl.ALWAYS + ctx.stencilFuncRef = 0 + ctx.stencilFuncMask = 0xffffffff + ctx.blendFunc.src_RGB = gl.INVALID_ENUM + ctx.blendFunc.src_alpha = gl.INVALID_ENUM + ctx.blendFunc.dst_RGB = gl.INVALID_ENUM + ctx.blendFunc.dst_alpha = gl.INVALID_ENUM + } + + when GL_USE_UNIFORMBUFFER { + // Upload ubo for frag shaders + gl.BindBuffer(gl.UNIFORM_BUFFER, ctx.fragBuf) + gl.BufferData(gl.UNIFORM_BUFFER, len(ctx.uniforms), raw_data(ctx.uniforms), gl.STREAM_DRAW) + } + + // Upload vertex data + when GL3 { + gl.BindVertexArray(ctx.vertArr) + } + + gl.BindBuffer(gl.ARRAY_BUFFER, ctx.vertBuf) + gl.BufferData(gl.ARRAY_BUFFER, len(ctx.verts) * size_of(Vertex), raw_data(ctx.verts), gl.STREAM_DRAW) + gl.EnableVertexAttribArray(0) + gl.EnableVertexAttribArray(1) + gl.VertexAttribPointer(0, 2, gl.FLOAT, gl.FALSE, size_of(Vertex), 0) + gl.VertexAttribPointer(1, 2, gl.FLOAT, gl.FALSE, size_of(Vertex), 2 * size_of(f32)) + + // Set view and texture just once per frame. + gl.Uniform1i(ctx.shader.loc[.TEX], 0) + gl.Uniform2fv(ctx.shader.loc[.VIEW_SIZE], 1, &ctx.view[0]) + + when GL_USE_UNIFORMBUFFER { + gl.BindBuffer(gl.UNIFORM_BUFFER, ctx.fragBuf) + } + + for i in 0.. (count: int) { + for i in 0.. ^Call { + append(&ctx.calls, Call {}) + return &ctx.calls[len(ctx.calls) - 1] +} + +// alloc paths and return the original start position +__allocPaths :: proc(ctx: ^Context, count: int) -> int { + old := len(ctx.paths) + resize(&ctx.paths, len(ctx.paths) + count) + return old +} + +// alloc verts and return the original start position +__allocVerts :: proc(ctx: ^Context, count: int) -> int { + old := len(ctx.verts) + resize(&ctx.verts, len(ctx.verts) + count) + return old +} + +// alloc uniforms and return the original start position +__allocFragUniforms :: proc(ctx: ^Context, count: int) -> int { + ret := len(ctx.uniforms) + resize(&ctx.uniforms, len(ctx.uniforms) + count * ctx.fragSize) + return ret +} + +// get frag uniforms from byte slice offset +__fragUniformPtr :: proc(ctx: ^Context, offset: int) -> ^FragUniforms { + return cast(^FragUniforms) &ctx.uniforms[offset] +} + +/////////////////////////////////////////////////////////// +// CALLBACKS +/////////////////////////////////////////////////////////// + +__renderFill :: proc( + uptr: rawptr, + paint: ^nvg.Paint, + compositeOperation: nvg.CompositeOperationState, + scissor: ^ScissorT, + fringe: f32, + bounds: [4]f32, + paths: []nvg.Path, +) { + ctx := cast(^Context) uptr + call := __allocCall(ctx) + + call.type = .FILL + call.triangleCount = 4 + call.pathOffset = __allocPaths(ctx, len(paths)) + call.pathCount = len(paths) + call.image = paint.image + call.blendFunc = __blendCompositeOperation(compositeOperation) + + if len(paths) == 1 && paths[0].convex { + call.type = .CONVEX_FILL + call.triangleCount = 0 + } + + // allocate vertices for all the paths + maxverts := __maxVertCount(paths) + call.triangleCount + offset := __allocVerts(ctx, maxverts) + + for i in 0.. 0 { + copy.fillOffset = offset + copy.fillCount = len(path.fill) + mem.copy(&ctx.verts[offset], &path.fill[0], size_of(Vertex) * len(path.fill)) + offset += len(path.fill) + } + + if len(path.stroke) > 0 { + copy.strokeOffset = offset + copy.strokeCount = len(path.stroke) + mem.copy(&ctx.verts[offset], &path.stroke[0], size_of(Vertex) * len(path.stroke)) + offset += len(path.stroke) + } + } + + // setup uniforms for draw calls + if call.type == .FILL { + // quad + call.triangleOffset = offset + quad := ctx.verts[call.triangleOffset:call.triangleOffset+4] + quad[0] = { bounds[2], bounds[3], 0.5, 1 } + quad[1] = { bounds[2], bounds[1], 0.5, 1 } + quad[2] = { bounds[0], bounds[3], 0.5, 1 } + quad[3] = { bounds[0], bounds[1], 0.5, 1 } + + // simple shader for stencil + call.uniformOffset = __allocFragUniforms(ctx, 2) + frag := __fragUniformPtr(ctx, call.uniformOffset) + frag^ = {} + frag.strokeThr = -1 + frag.type = .SIMPLE + + // fill shader + __convertPaint( + ctx, + __fragUniformPtr(ctx, call.uniformOffset + ctx.fragSize), + paint, + scissor, + fringe, + fringe, + -1, + ) + } else { + call.uniformOffset = __allocFragUniforms(ctx, 1) + // fill shader + __convertPaint( + ctx, + __fragUniformPtr(ctx, call.uniformOffset), + paint, + scissor, + fringe, + fringe, + -1, + ) + } +} + +__renderStroke :: proc( + uptr: rawptr, + paint: ^Paint, + compositeOperation: nvg.CompositeOperationState, + scissor: ^ScissorT, + fringe: f32, + strokeWidth: f32, + paths: []nvg.Path, +) { + ctx := cast(^Context) uptr + call := __allocCall(ctx) + + call.type = .STROKE + call.pathOffset = __allocPaths(ctx, len(paths)) + call.pathCount = len(paths) + call.image = paint.image + call.blendFunc = __blendCompositeOperation(compositeOperation) + + // allocate vertices for all the paths + maxverts := __maxVertCount(paths) + offset := __allocVerts(ctx, maxverts) + + for i in 0.. ^nvg.Context { + ctx := new(Context) + params: nvg.Params + params.renderCreate = __renderCreate + params.renderCreateTexture = __renderCreateTexture + params.renderDeleteTexture = __renderDeleteTexture + params.renderUpdateTexture = __renderUpdateTexture + params.renderGetTextureSize = __renderGetTextureSize + params.renderViewport = __renderViewport + params.renderCancel = __renderCancel + params.renderFlush = __renderFlush + params.renderFill = __renderFill + params.renderStroke = __renderStroke + params.renderTriangles = __renderTriangles + params.renderDelete = __renderDelete + params.userPtr = ctx + params.edgeAntiAlias = (.ANTI_ALIAS in flags) + ctx.flags = flags + return nvg.CreateInternal(params) +} + +Destroy :: proc(ctx: ^nvg.Context) { + nvg.DeleteInternal(ctx) +} + +CreateImageFromHandle :: proc(ctx: ^nvg.Context, textureId: u32, w, h: int, imageFlags: ImageFlags) -> int { + gctx := cast(^Context) ctx.params.userPtr + tex := __allocTexture(gctx) + tex.type = .RGBA + tex.tex = textureId + tex.flags = imageFlags + tex.width = w + tex.height = h + return tex.id +} + +ImageHandle :: proc(ctx: ^nvg.Context, textureId: int) -> u32 { + gctx := cast(^Context) ctx.params.userPtr + tex := __findTexture(gctx, textureId) + return tex.tex +} + +// framebuffer additional + +framebuffer :: struct { + ctx: ^nvg.Context, + fbo: u32, + rbo: u32, + texture: u32, + image: int, +} + +DEFAULT_FBO :: 100_000 +defaultFBO := i32(DEFAULT_FBO) + +// helper function to create GL frame buffer to render to +BindFramebuffer :: proc(fb: ^framebuffer) { + if defaultFBO == DEFAULT_FBO { + gl.GetIntegerv(gl.FRAMEBUFFER_BINDING, &defaultFBO) + } + gl.BindFramebuffer(gl.FRAMEBUFFER, fb != nil ? fb.fbo : u32(defaultFBO)) +} + +CreateFramebuffer :: proc(ctx: ^nvg.Context, w, h: int, imageFlags: ImageFlags) -> (fb: framebuffer) { + defaultFBO: i32 + defaultRBO: i32 + gl.GetIntegerv(gl.FRAMEBUFFER_BINDING, &defaultFBO) + gl.GetIntegerv(gl.RENDERBUFFER_BINDING, &defaultRBO) + + imageFlags := imageFlags + incl(&imageFlags, ImageFlags { .FLIP_Y, .PREMULTIPLIED }) + fb.image = nvg.CreateImageRGBA(ctx, w, h, imageFlags, nil) + fb.texture = ImageHandle(ctx, fb.image) + fb.ctx = ctx + + // frame buffer object + gl.GenFramebuffers(1, &fb.fbo) + gl.BindFramebuffer(gl.FRAMEBUFFER, fb.fbo) + + // render buffer object + gl.GenRenderbuffers(1, &fb.rbo) + gl.BindRenderbuffer(gl.RENDERBUFFER, fb.rbo) + gl.RenderbufferStorage(gl.RENDERBUFFER, gl.STENCIL_INDEX8, i32(w), i32(h)) + + // combine all + gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, fb.texture, 0) + gl.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.RENDERBUFFER, fb.rbo) + + if gl.CheckFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE { +// #ifdef gl.DEPTH24_STENCIL8 + // If gl.STENCIL_INDEX8 is not supported, try gl.DEPTH24_STENCIL8 as a fallback. + // Some graphics cards require a depth buffer along with a stencil. + gl.RenderbufferStorage(gl.RENDERBUFFER, gl.DEPTH24_STENCIL8, i32(w), i32(h)) + gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, fb.texture, 0) + gl.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.RENDERBUFFER, fb.rbo) + + if gl.CheckFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE { + fmt.eprintln("ERROR") + } +// #endif // gl.DEPTH24_STENCIL8 +// goto error + } + + gl.BindFramebuffer(gl.FRAMEBUFFER, u32(defaultFBO)) + gl.BindRenderbuffer(gl.RENDERBUFFER, u32(defaultRBO)) + return +} + +DeleteFramebuffer :: proc(fb: ^framebuffer) { + if fb == nil { + return + } + + if fb.fbo != 0 { + gl.DeleteFramebuffers(1, &fb.fbo) + } + + if fb.rbo != 0 { + gl.DeleteRenderbuffers(1, &fb.rbo) + } + + if fb.image >= 0 { + nvg.DeleteImage(fb.ctx, fb.image) + } + + fb.ctx = nil + fb.fbo = 0 + fb.rbo = 0 + fb.texture = 0 + fb.image = -1 +} \ No newline at end of file diff --git a/vendor/nanovg/gl/vert.glsl b/vendor/nanovg/gl/vert.glsl new file mode 100644 index 000000000..f937da09a --- /dev/null +++ b/vendor/nanovg/gl/vert.glsl @@ -0,0 +1,19 @@ +#ifdef NANOVG_GL3 + uniform vec2 viewSize; + in vec2 vertex; + in vec2 tcoord; + out vec2 ftcoord; + out vec2 fpos; +#else + uniform vec2 viewSize; + attribute vec2 vertex; + attribute vec2 tcoord; + varying vec2 ftcoord; + varying vec2 fpos; +#endif + +void main(void) { + ftcoord = tcoord; + fpos = vertex; + gl_Position = vec4(2.0*vertex.x/viewSize.x - 1.0, 1.0 - 2.0*vertex.y/viewSize.y, 0, 1); +} \ No newline at end of file diff --git a/vendor/nanovg/nanovg.odin b/vendor/nanovg/nanovg.odin new file mode 100644 index 000000000..db99ef5a4 --- /dev/null +++ b/vendor/nanovg/nanovg.odin @@ -0,0 +1,3503 @@ +package nanovg + +// TODO rename structs to old nanovg style! +// TODO rename enums to old nanovg style! + +import "core:mem" +import "core:runtime" +import "core:math" +import "core:fmt" +import "../fontstash" +import stbi "vendor:stb/image" + +AlignVertical :: fontstash.AlignVertical +AlignHorizontal :: fontstash.AlignHorizontal + +INIT_FONTIMAGE_SIZE :: 512 +MAX_FONTIMAGE_SIZE :: 2048 +MAX_FONTIMAGES :: 4 + +MAX_STATES :: 32 +INIT_COMMANDS_SIZE :: 256 +INIT_POINTS_SIZE :: 128 +INIT_PATH_SIZE :: 16 +INIT_VERTS_SIZE :: 26 +KAPPA :: 0.5522847493 + +Color :: [4]f32 +Matrix :: [6]f32 +Vertex :: [4]f32 // x,y,u,v + +ImageFlag :: enum { + GENERATE_MIPMAPS, + REPEAT_X, + REPEAT_Y, + FLIP_Y, + PREMULTIPLIED, + NEAREST, + NO_DELETE, +} +ImageFlags :: bit_set[ImageFlag] + +Paint :: struct { + xform: Matrix, + extent: [2]f32, + radius: f32, + feather: f32, + innerColor: Color, + outerColor: Color, + image: int, +} + +Winding :: enum { + CCW = 1, + CW, +} + +Solidity :: enum { + SOLID = 1, // CCW + HOLE, // CW +} + +LineCapType :: enum { + BUTT, + ROUND, + SQUARE, + BEVEL, + MITER, +} + +BlendFactor :: enum { + ZERO, + ONE, + SRC_COLOR, + ONE_MINUS_SRC_COLOR, + DST_COLOR, + ONE_MINUS_DST_COLOR, + SRC_ALPHA, + ONE_MINUS_SRC_ALPHA, + DST_ALPHA, + ONE_MINUS_DST_ALPHA, + SRC_ALPHA_SATURATE, +} + +CompositeOperation :: enum { + SOURCE_OVER, + SOURCE_IN, + SOURCE_OUT, + ATOP, + DESTINATION_OVER, + DESTINATION_IN, + DESTINATION_OUT, + DESTINATION_ATOP, + LIGHTER, + COPY, + XOR, +} + +CompositeOperationState :: struct { + srcRGB: BlendFactor, + dstRGB: BlendFactor, + srcAlpha: BlendFactor, + dstAlpha: BlendFactor, +} + +// render data structures + +Texture :: enum { + Alpha, + RGBA, +} + +ScissorT :: struct { + xform: Matrix, + extent: [2]f32, +} + +Commands :: enum { + MOVE_TO, + LINE_TO, + BEZIER_TO, + CLOSE, + WINDING, +} + +PointFlag :: enum { + CORNER, + LEFT, + BEVEL, + INNER_BEVEL, +} +PointFlags :: bit_set[PointFlag] + +Point :: struct { + x, y: f32, + dx, dy: f32, + len: f32, + dmx, dmy: f32, + flags: PointFlags, +} + +PathCache :: struct { + points: [dynamic]Point, + paths: [dynamic]Path, + verts: [dynamic]Vertex, + bounds: [4]f32, +} + +Path :: struct { + first: int, + count: int, + closed: bool, + nbevel: int, + fill: []Vertex, + stroke: []Vertex, + winding: Winding, + convex: bool, +} + +State :: struct { + compositeOperation: CompositeOperationState, + shapeAntiAlias: bool, + fill: Paint, + stroke: Paint, + strokeWidth: f32, + miterLimit: f32, + lineJoin: LineCapType, + lineCap: LineCapType, + alpha: f32, + xform: Matrix, + scissor: ScissorT, + + // font state + fontSize: f32, + letterSpacing: f32, + lineHeight: f32, + fontBlur: f32, + alignHorizontal: AlignHorizontal, + alignVertical: AlignVertical, + fontId: int, +} + +Context :: struct { + params: Params, + commands: [dynamic]f32, + commandx, commandy: f32, + states: [MAX_STATES]State, + nstates: int, + cache: PathCache, + tessTol: f32, + distTol: f32, + fringeWidth: f32, + devicePxRatio: f32, + + // font + fs: fontstash.FontContext, + fontImages: [MAX_FONTIMAGES]int, + fontImageIdx: int, + + // stats + drawCallCount: int, + fillTriCount: int, + strokeTriCount: int, + textTriCount: int, + + // flush texture + textureDirty: bool, +} + +Params :: struct { + userPtr: rawptr, + edgeAntiAlias: bool, + + // callbacks to fill out + renderCreate: proc(uptr: rawptr) -> bool, + renderDelete: proc(uptr: rawptr), + + // textures calls + renderCreateTexture: proc( + uptr: rawptr, + type: Texture, + w, h: int, + imageFlags: ImageFlags, + data: []byte, + ) -> int, + renderDeleteTexture: proc(uptr: rawptr, image: int) -> bool, + renderUpdateTexture: proc( + uptr: rawptr, + image: int, + x, y: int, + w, h: int, + data: []byte, + ) -> bool, + renderGetTextureSize: proc(uptr: rawptr, image: int, w, h: ^int) -> bool, + + // rendering calls + renderViewport: proc(uptr: rawptr, width, height, devicePixelRatio: f32), + renderCancel: proc(uptr: rawptr), + renderFlush: proc(uptr: rawptr), + renderFill: proc( + uptr: rawptr, + paint: ^Paint, + compositeOperation: CompositeOperationState, + scissor: ^ScissorT, + fringe: f32, + bounds: [4]f32, + paths: []Path, + ), + renderStroke: proc( + uptr: rawptr, + paint: ^Paint, + compositeOperation: CompositeOperationState, + scissor: ^ScissorT, + fringe: f32, + strokeWidth: f32, + paths: []Path, + ), + renderTriangles: proc( + uptr: rawptr, + paint: ^Paint, + compositeOperation: CompositeOperationState, + scissor: ^ScissorT, + verts: []Vertex, + fringe: f32, + ), +} + +__allocPathCache :: proc(c: ^PathCache) { + c.points = make([dynamic]Point, 0, INIT_POINTS_SIZE) + c.paths = make([dynamic]Path, 0, INIT_PATH_SIZE) + c.verts = make([dynamic]Vertex, 0, INIT_VERTS_SIZE) +} + +__deletePathCache :: proc(c: PathCache) { + delete(c.points) + delete(c.paths) + delete(c.verts) +} + +__setDevicePxRatio :: proc(ctx: ^Context, ratio: f32) { + ctx.tessTol = 0.25 / ratio + ctx.distTol = 0.01 / ratio + ctx.fringeWidth = 1.0 / ratio + ctx.devicePxRatio = ratio +} + +__getState :: #force_inline proc(ctx: ^Context) -> ^State #no_bounds_check { + return &ctx.states[ctx.nstates - 1] +} + +CreateInternal :: proc(params: Params) -> (ctx: ^Context) { + ctx = new(Context) + ctx.params = params + ctx.commands = make([dynamic]f32, 0, INIT_COMMANDS_SIZE) + __allocPathCache(&ctx.cache) + + Save(ctx) + Reset(ctx) + __setDevicePxRatio(ctx, 1) + + assert(ctx.params.renderCreate != nil) + if !ctx.params.renderCreate(ctx.params.userPtr) { + DeleteInternal(ctx) + panic("Nanovg - CreateInternal failed") + } + + w := INIT_FONTIMAGE_SIZE + h := INIT_FONTIMAGE_SIZE + fontstash.Init(&ctx.fs, w, h, .TOPLEFT) + assert(ctx.params.renderCreateTexture != nil) + ctx.fs.userData = ctx + + // handle to the image needs to be set to the new generated texture + ctx.fs.callbackResize = proc(data: rawptr, w, h: int) { + ctx := cast(^Context) data + ctx.fontImages[0] = ctx.params.renderCreateTexture(ctx.params.userPtr, .Alpha, w, h, {}, ctx.fs.textureData) + } + + // texture atlas + ctx.fontImages[0] = ctx.params.renderCreateTexture(ctx.params.userPtr, .Alpha, w, h, {}, nil) + ctx.fontImageIdx = 0 + + return +} + +DeleteInternal :: proc(ctx: ^Context) { + __deletePathCache(ctx.cache) + fontstash.Destroy(&ctx.fs) + + for image in &ctx.fontImages { + if image != 0 { + DeleteImage(ctx, image) + } + } + + if ctx.params.renderDelete != nil { + ctx.params.renderDelete(ctx.params.userPtr) + } + + free(ctx) +} + +/* + Begin drawing a new frame + Calls to nanovg drawing API should be wrapped in nvgBeginFrame() & nvgEndFrame() + nvgBeginFrame() defines the size of the window to render to in relation currently + set viewport (i.e. glViewport on GL backends). Device pixel ration allows to + control the rendering on Hi-DPI devices. + For example, GLFW returns two dimension for an opened window: window size and + frame buffer size. In that case you would set windowWidth/Height to the window size + devicePixelRatio to: frameBufferWidth / windowWidth. +*/ +BeginFrame :: proc( + ctx: ^Context, + windowWidth: f32, + windowHeight: f32, + devicePixelRatio: f32, +) { + ctx.nstates = 0 + Save(ctx) + Reset(ctx) + __setDevicePxRatio(ctx, devicePixelRatio) + + assert(ctx.params.renderViewport != nil) + ctx.params.renderViewport(ctx.params.userPtr, windowWidth, windowHeight, devicePixelRatio) + + ctx.drawCallCount = 0 + ctx.fillTriCount = 0 + ctx.strokeTriCount = 0 + ctx.textTriCount = 0 +} + +@(deferred_out=EndFrame) +FrameScoped :: proc( + ctx: ^Context, + windowWidth: f32, + windowHeight: f32, + devicePixelRatio: f32, +) -> ^Context { + BeginFrame(ctx, windowWidth, windowHeight, devicePixelRatio) + return ctx +} + +// Cancels drawing the current frame. +CancelFrame :: proc(ctx: ^Context) { + assert(ctx.params.renderCancel != nil) + ctx.params.renderCancel(ctx.params.userPtr) +} + +// Ends drawing flushing remaining render state. +EndFrame :: proc(ctx: ^Context) { + // flush texture only once + if ctx.textureDirty { + __flushTextTexture(ctx) + ctx.textureDirty = false + } + + assert(ctx.params.renderFlush != nil) + ctx.params.renderFlush(ctx.params.userPtr) + + // delete textures with invalid size + if ctx.fontImageIdx != 0 { + font_image := ctx.fontImages[ctx.fontImageIdx] + ctx.fontImages[ctx.fontImageIdx] = 0 + + if font_image == 0 { + return + } + + iw, ih := ImageSize(ctx, font_image) + j: int + for i in 0.. Color { + return RGBA(r, g, b, 255) +} + +// Returns a color value from red, green, blue and alpha values. +RGBA :: proc(r, g, b, a: u8) -> (res: Color) { + res.r = f32(r) / f32(255) + res.g = f32(g) / f32(255) + res.b = f32(b) / f32(255) + res.a = f32(a) / f32(255) + return +} + +// Linearly interpolates from color c0 to c1, and returns resulting color value. +LerpRGBA :: proc(c0, c1: Color, u: f32) -> (cint: Color) { + u := clamp(u, 0.0, 1.0) + oneminu := 1.0 - u + for i in 0..<4 { + cint[i] = c0[i] * oneminu + c1[i] * u + } + + return +} + +// Returns color value specified by hue, saturation and lightness. +// HSL values are all in range [0..1], alpha will be set to 255. +HSL :: proc(h, s, l: f32) -> Color { + return HSLA(h,s,l,255) +} + +// Returns color value specified by hue, saturation and lightness and alpha. +// HSL values are all in range [0..1], alpha in range [0..255] +HSLA :: proc(h, s, l: f32, a: u8) -> (col: Color) { + hue :: proc(h, m1, m2: f32) -> f32 { + h := h + + if h < 0 { + h += 1 + } + + if h > 1 { + h -= 1 + } + + if h < 1.0 / 6.0 { + return m1 + (m2 - m1) * h * 6.0 + } else if h < 3.0 / 6.0 { + return m2 + } else if h < 4.0 / 6.0 { + return m1 + (m2 - m1) * (2.0 / 3.0 - h) * 6.0 + } + + return m1 + } + + h := math.mod(h, 1.0) + if h < 0.0 { + h += 1.0 + } + s := clamp(s, 0.0, 1.0) + l := clamp(l, 0.0, 1.0) + m2 := l <= 0.5 ? (l * (1 + s)) : (l + s - l * s) + m1 := 2 * l - m2 + col.r = clamp(hue(h + 1.0/3.0, m1, m2), 0.0, 1.0) + col.g = clamp(hue(h, m1, m2), 0.0, 1.0) + col.b = clamp(hue(h - 1.0/3.0, m1, m2), 0.0, 1.0) + col.a = f32(a) / 255.0 + return +} + +// hex to 0xAARRGGBB color +ColorHex :: proc(color: u32) -> (res: Color) { + color := color + res.b = f32(0x000000FF & color) / 255 + color = color >> 8 + res.g = f32(0x000000FF & color) / 255 + color = color >> 8 + res.r = f32(0x000000FF & color) / 255 + color = color >> 8 + res.a = f32(0x000000FF & color) / 255 + return +} + +/////////////////////////////////////////////////////////// +// TRANSFORMS +// +// The following functions can be used to make calculations on 2x3 transformation matrices. +// A 2x3 matrix is represented as float[6]. +/////////////////////////////////////////////////////////// + +// Sets the transform to identity matrix. +TransformIdentity :: proc(t: ^Matrix) { + t[0] = 1 + t[1] = 0 + t[2] = 0 + t[3] = 1 + t[4] = 0 + t[5] = 0 +} + +// Sets the transform to translation matrix matrix. +TransformTranslate :: proc(t: ^Matrix, tx, ty: f32) { + t[0] = 1 + t[1] = 0 + t[2] = 0 + t[3] = 1 + t[4] = tx + t[5] = ty +} + +// Sets the transform to scale matrix. +TransformScale :: proc(t: ^Matrix, sx, sy: f32) { + t[0] = sx + t[1] = 0 + t[2] = 0 + t[3] = sy + t[4] = 0 + t[5] = 0 +} + +// Sets the transform to rotate matrix. Angle is specified in radians. +TransformRotate :: proc(t: ^Matrix, a: f32) { + cs := math.cos(a) + sn := math.sin(a) + t[0] = cs + t[1] = sn + t[2] = -sn + t[3] = cs + t[4] = 0 + t[5] = 0 +} + +// Sets the transform to skew-x matrix. Angle is specified in radians. +TransformSkewX :: proc(t: ^Matrix, a: f32) { + t[0] = 1 + t[1] = 0 + t[2] = math.tan(a) + t[3] = 1 + t[4] = 0 + t[5] = 0 +} + +// Sets the transform to skew-y matrix. Angle is specified in radians. +TransformSkewY :: proc(t: ^Matrix, a: f32) { + t[0] = 1 + t[1] = math.tan(a) + t[2] = 0 + t[3] = 1 + t[4] = 0 + t[5] = 0 +} + +// Sets the transform to the result of multiplication of two transforms, of A = A*B. +TransformMultiply :: proc(t: ^Matrix, s: Matrix) { + t0 := t[0] * s[0] + t[1] * s[2] + t2 := t[2] * s[0] + t[3] * s[2] + t4 := t[4] * s[0] + t[5] * s[2] + s[4] + t[1] = t[0] * s[1] + t[1] * s[3] + t[3] = t[2] * s[1] + t[3] * s[3] + t[5] = t[4] * s[1] + t[5] * s[3] + s[5] + t[0] = t0 + t[2] = t2 + t[4] = t4 +} + +// Sets the transform to the result of multiplication of two transforms, of A = B*A. +TransformPremultiply :: proc(t: ^Matrix, s: Matrix) { + temp := s + TransformMultiply(&temp, t^) + t^ = temp +} + +// Sets the destination to inverse of specified transform. +// Returns true if the inverse could be calculated, else false. +TransformInverse :: proc(inv: ^Matrix, t: Matrix) -> bool { + // TODO could be bad math? due to types + det := f64(t[0]) * f64(t[3]) - f64(t[2]) * f64(t[1]) + + if det > -1e-6 && det < 1e-6 { + TransformIdentity(inv) + return false + } + + invdet := 1.0 / det + inv[0] = f32(f64(t[3]) * invdet) + inv[2] = f32(f64(-t[2]) * invdet) + inv[4] = f32((f64(t[2]) * f64(t[5]) - f64(t[3]) * f64(t[4])) * invdet) + inv[1] = f32(f64(-t[1]) * invdet) + inv[3] = f32(f64(t[0]) * invdet) + inv[5] = f32((f64(t[1]) * f64(t[4]) - f64(t[0]) * f64(t[5])) * invdet) + return true +} + +// Transform a point by given transform. +TransformPoint :: proc( + dx: ^f32, + dy: ^f32, + t: Matrix, + sx: f32, + sy: f32, +) { + dx^ = sx * t[0] + sy * t[2] + t[4] + dy^ = sx * t[1] + sy * t[3] + t[5] +} + +DegToRad :: proc(deg: f32) -> f32 { + return deg / 180.0 * math.PI +} + +RadToDeg :: proc(rad: f32) -> f32 { + return rad / math.PI * 180.0 +} + +/////////////////////////////////////////////////////////// +// STATE MANAGEMENT +// +// NanoVG contains state which represents how paths will be rendered. +// The state contains transform, fill and stroke styles, text and font styles, +// and scissor clipping. +/////////////////////////////////////////////////////////// + +// Pushes and saves the current render state into a state stack. +// A matching nvgRestore() must be used to restore the state. +Save :: proc(ctx: ^Context) { + if ctx.nstates >= MAX_STATES { + return + } + + // copy prior + if ctx.nstates > 0 { + ctx.states[ctx.nstates] = ctx.states[ctx.nstates - 1] + } + + ctx.nstates += 1 +} + +// Pops and restores current render state. +Restore :: proc(ctx: ^Context) { + if ctx.nstates <= 1 { + return + } + + ctx.nstates -= 1 +} + +// NOTE useful helper +@(deferred_in=Restore) +SaveScoped :: #force_inline proc(ctx: ^Context) { + Save(ctx) +} + +__setPaintColor :: proc(p: ^Paint, color: Color) { + p^ = {} + TransformIdentity(&p.xform) + p.radius = 0 + p.feather = 1 + p.innerColor = color + p.outerColor = color +} + +// Resets current render state to default values. Does not affect the render state stack. +Reset :: proc(ctx: ^Context) { + state := __getState(ctx) + state^ = {} + + __setPaintColor(&state.fill, RGBA(255, 255, 255, 255)) + __setPaintColor(&state.stroke, RGBA(0, 0, 0, 255)) + + state.compositeOperation = __compositeOperationState(.SOURCE_OVER) + state.shapeAntiAlias = true + state.strokeWidth = 1 + state.miterLimit = 10 + state.lineCap = .BUTT + state.lineJoin = .MITER + state.alpha = 1 + TransformIdentity(&state.xform) + + state.scissor.extent[0] = -1 + state.scissor.extent[1] = -1 + + // font settings + state.fontSize = 16 + state.letterSpacing = 0 + state.lineHeight = 1 + state.fontBlur = 0 + state.alignHorizontal = .LEFT + state.alignVertical = .BASELINE + state.fontId = 0 +} + +/////////////////////////////////////////////////////////// +// STATE SETTING +/////////////////////////////////////////////////////////// + +// Sets whether to draw antialias for nvgStroke() and nvgFill(). It's enabled by default. +ShapeAntiAlias :: proc(ctx: ^Context, enabled: bool) { + state := __getState(ctx) + state.shapeAntiAlias = enabled +} + +// Sets the stroke width of the stroke style. +StrokeWidth :: proc(ctx: ^Context, width: f32) { + state := __getState(ctx) + state.strokeWidth = width +} + +// Sets the miter limit of the stroke style. +// Miter limit controls when a sharp corner is beveled. +MiterLimit :: proc(ctx: ^Context, limit: f32) { + state := __getState(ctx) + state.miterLimit = limit +} + +// Sets how the end of the line (cap) is drawn, +// Can be one of: NVG_BUTT (default), NVG_ROUND, NVG_SQUARE. +LineCap :: proc(ctx: ^Context, cap: LineCapType) { + state := __getState(ctx) + state.lineCap = cap +} + +// Sets how sharp path corners are drawn. +// Can be one of NVG_MITER (default), NVG_ROUND, NVG_BEVEL. +LineJoin :: proc(ctx: ^Context, join: LineCapType) { + state := __getState(ctx) + state.lineJoin = join +} + +// Sets the transparency applied to all rendered shapes. +// Already transparent paths will get proportionally more transparent as well. +GlobalAlpha :: proc(ctx: ^Context, alpha: f32) { + state := __getState(ctx) + state.alpha = alpha +} + +// Sets current stroke style to a solid color. +StrokeColor :: proc(ctx: ^Context, color: Color) { + state := __getState(ctx) + __setPaintColor(&state.stroke, color) +} + +// Sets current stroke style to a paint, which can be a one of the gradients or a pattern. +StrokePaint :: proc(ctx: ^Context, paint: Paint) { + state := __getState(ctx) + state.stroke = paint + TransformMultiply(&state.stroke.xform, state.xform) +} + +// Sets current fill style to a solid color. +FillColor :: proc(ctx: ^Context, color: Color) { + state := __getState(ctx) + __setPaintColor(&state.fill, color) +} + +// Sets current fill style to a paint, which can be a one of the gradients or a pattern. +FillPaint :: proc(ctx: ^Context, paint: Paint) { + state := __getState(ctx) + state.fill = paint + TransformMultiply(&state.fill.xform, state.xform) +} + +/////////////////////////////////////////////////////////// +// STATE TRANSFORMS +// +// The paths, gradients, patterns and scissor region are transformed by an transformation +// matrix at the time when they are passed to the API. +// The current transformation matrix is a affine matrix: +// [sx kx tx] +// [ky sy ty] +// [ 0 0 1] +// Where: sx,sy define scaling, kx,ky skewing, and tx,ty translation. +// The last row is assumed to be 0,0,1 and is not stored. +// +// Apart from nvgResetTransform(), each transformation function first creates +// specific transformation matrix and pre-multiplies the current transformation by it. +// +// Current coordinate system (transformation) can be saved and restored using nvgSave() and nvgRestore(). +/////////////////////////////////////////////////////////// + +Transform :: proc(ctx: ^Context, a, b, c, d, e, f: f32) { + state := __getState(ctx) + TransformPremultiply(&state.xform, { a, b, c, d, e, f }) +} + +// Resets current transform to a identity matrix. +ResetTransform :: proc(ctx: ^Context) { + state := __getState(ctx) + TransformIdentity(&state.xform) +} + +// Translates current coordinate system. +Translate :: proc(ctx: ^Context, x, y: f32) { + state := __getState(ctx) + temp: Matrix + TransformTranslate(&temp, x, y) + TransformPremultiply(&state.xform, temp) +} + +// Rotates current coordinate system. Angle is specified in radians. +Rotate :: proc(ctx: ^Context, angle: f32) { + state := __getState(ctx) + temp: Matrix + TransformRotate(&temp, angle) + TransformPremultiply(&state.xform, temp) +} + +// Skews the current coordinate system along X axis. Angle is specified in radians. +SkewX :: proc(ctx: ^Context, angle: f32) { + state := __getState(ctx) + temp: Matrix + TransformSkewX(&temp, angle) + TransformPremultiply(&state.xform, temp) +} + +// Skews the current coordinate system along Y axis. Angle is specified in radians. +SkewY :: proc(ctx: ^Context, angle: f32) { + state := __getState(ctx) + temp: Matrix + TransformSkewY(&temp, angle) + TransformPremultiply(&state.xform, temp) +} + +// Scales the current coordinate system. +Scale :: proc(ctx: ^Context, x, y: f32) { + state := __getState(ctx) + temp: Matrix + TransformScale(&temp, x, y) + TransformPremultiply(&state.xform, temp) +} + +/* + Stores the top part (a-f) of the current transformation matrix in to the specified buffer. + [a c e] + [b d f] + [0 0 1] + There should be space for 6 floats in the return buffer for the values a-f. +*/ +CurrentTransform :: proc(ctx: ^Context, xform: ^Matrix) { + state := __getState(ctx) + if xform == nil { + return + } + xform^ = state.xform +} + +/////////////////////////////////////////////////////////// +// IMAGE HANDLING +// +// NanoVG allows you to load jpg, png, psd, tga, pic and gif files to be used for rendering. +// In addition you can upload your own image. The image loading is provided by stb_image. +// The parameter imageFlags is a combination of flags defined in NVGimageFlags. +/////////////////////////////////////////////////////////// + +// Creates image by loading it from the disk from specified file name. +// Returns handle to the image. +CreateImagePath :: proc(ctx: ^Context, filename: cstring, imageFlags: ImageFlags) -> int { + stbi.set_unpremultiply_on_load(1) + stbi.convert_iphone_png_to_rgb(1) + w, h, n: i32 + img := stbi.load(filename, &w, &h, &n, 4) + + if img == nil { + return 0 + } + + data := mem.slice_ptr(img, int(w) * int(h) * int(n)) + image := CreateImageRGBA(ctx, int(w), int(h), imageFlags, data) + stbi.image_free(img) + return image +} + +// Creates image by loading it from the specified chunk of memory. +// Returns handle to the image. +CreateImageMem :: proc(ctx: ^Context, data: []byte, imageFlags: ImageFlags) -> int { + stbi.set_unpremultiply_on_load(1) + stbi.convert_iphone_png_to_rgb(1) + w, h, n: i32 + img := stbi.load_from_memory(raw_data(data), i32(len(data)), &w, &h, &n, 4) + + if img == nil { + return 0 + } + + data := mem.slice_ptr(img, int(w) * int(h) * int(n)) + image := CreateImageRGBA(ctx, int(w), int(h), imageFlags, data) + stbi.image_free(img) + return image +} + +CreateImage :: proc { CreateImagePath, CreateImageMem } + +// Creates image from specified image data. +// Returns handle to the image. +CreateImageRGBA :: proc(ctx: ^Context, w, h: int, imageFlags: ImageFlags, data: []byte) -> int { + assert(ctx.params.renderCreateTexture != nil) + return ctx.params.renderCreateTexture( + ctx.params.userPtr, + .RGBA, + w, h, + imageFlags, + data, + ) +} + +// Updates image data specified by image handle. +UpdateImage :: proc(ctx: ^Context, image: int, data: []byte) { + assert(ctx.params.renderGetTextureSize != nil) + assert(ctx.params.renderUpdateTexture != nil) + + w, h: int + found := ctx.params.renderGetTextureSize(ctx.params.userPtr, image, &w, &h) + if found { + ctx.params.renderUpdateTexture(ctx.params.userPtr, image, 0, 0, w, h, data) + } +} + +// Returns the dimensions of a created image. +ImageSize :: proc(ctx: ^Context, image: int) -> (w, h: int) { + assert(ctx.params.renderGetTextureSize != nil) + ctx.params.renderGetTextureSize(ctx.params.userPtr, image, &w, &h) + return +} + +// Deletes created image. +DeleteImage :: proc(ctx: ^Context, image: int) { + assert(ctx.params.renderDeleteTexture != nil) + ctx.params.renderDeleteTexture(ctx.params.userPtr, image) +} + +/////////////////////////////////////////////////////////// +// PAINT gradients / image +// +// NanoVG supports four types of paints: linear gradient, box gradient, radial gradient and image pattern. +// These can be used as paints for strokes and fills. +/////////////////////////////////////////////////////////// + +/* + Creates and returns a linear gradient. Parameters (sx,sy)-(ex,ey) specify the start and end coordinates + of the linear gradient, icol specifies the start color and ocol the end color. + The gradient is transformed by the current transform when it is passed to nvgFillPaint() or nvgStrokePaint(). +*/ +LinearGradient :: proc( + sx, sy: f32, + ex, ey: f32, + icol: Color, + ocol: Color, +) -> (p: Paint) { + LARGE :: f32(1e5) + + // Calculate transform aligned to the line + dx := ex - sx + dy := ey - sy + d := math.sqrt(dx*dx + dy*dy) + if d > 0.0001 { + dx /= d + dy /= d + } else { + dx = 0 + dy = 1 + } + + p.xform[0] = dy + p.xform[1] = -dx + p.xform[2] = dx + p.xform[3] = dy + p.xform[4] = sx - dx*LARGE + p.xform[5] = sy - dy*LARGE + + p.extent[0] = LARGE + p.extent[1] = LARGE + d*0.5 + + p.feather = max(1.0, d) + + p.innerColor = icol + p.outerColor = ocol + + return +} + +/* + Creates and returns a box gradient. Box gradient is a feathered rounded rectangle, it is useful for rendering + drop shadows or highlights for boxes. Parameters (x,y) define the top-left corner of the rectangle, + (w,h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry + the border of the rectangle is. Parameter icol specifies the inner color and ocol the outer color of the gradient. + The gradient is transformed by the current transform when it is passed to nvgFillPaint() or nvgStrokePaint(). +*/ +RadialGradient :: proc( + cx, cy: f32, + inr: f32, + outr: f32, + icol: Color, + ocol: Color, +) -> (p: Paint) { + r := (inr+outr)*0.5 + f := (outr-inr) + + TransformIdentity(&p.xform) + p.xform[4] = cx + p.xform[5] = cy + + p.extent[0] = r + p.extent[1] = r + + p.radius = r + p.feather = max(1.0, f) + + p.innerColor = icol + p.outerColor = ocol + + return +} + +/* + Creates and returns a radial gradient. Parameters (cx,cy) specify the center, inr and outr specify + the inner and outer radius of the gradient, icol specifies the start color and ocol the end color. + The gradient is transformed by the current transform when it is passed to nvgFillPaint() or nvgStrokePaint(). +*/ +BoxGradient :: proc( + x, y: f32, + w, h: f32, + r: f32, + f: f32, + icol: Color, + ocol: Color, +) -> (p: Paint) { + TransformIdentity(&p.xform) + p.xform[4] = x+w*0.5 + p.xform[5] = y+h*0.5 + + p.extent[0] = w*0.5 + p.extent[1] = h*0.5 + + p.radius = r + p.feather = max(1.0, f) + + p.innerColor = icol + p.outerColor = ocol + + return +} + +/* + Creates and returns an image pattern. Parameters (ox,oy) specify the left-top location of the image pattern, + (ex,ey) the size of one image, angle rotation around the top-left corner, image is handle to the image to render. + The gradient is transformed by the current transform when it is passed to nvgFillPaint() or nvgStrokePaint(). +*/ +ImagePattern :: proc( + cx, cy: f32, + w, h: f32, + angle: f32, + image: int, + alpha: f32, +) -> (p: Paint) { + TransformRotate(&p.xform, angle) + p.xform[4] = cx + p.xform[5] = cy + + p.extent[0] = w + p.extent[1] = h + + p.image = image + p.innerColor = { 1,1,1,alpha } + p.outerColor = p.innerColor + + return +} + +/////////////////////////////////////////////////////////// +// SCISSOR +// +// Scissoring allows you to clip the rendering into a rectangle. This is useful for various +// user interface cases like rendering a text edit or a timeline. +/////////////////////////////////////////////////////////// + +// Sets the current scissor rectangle. +// The scissor rectangle is transformed by the current transform. +Scissor :: proc( + ctx: ^Context, + x, y: f32, + w, h: f32, +) { + state := __getState(ctx) + w := max(w, 0) + h := max(h, 0) + + TransformIdentity(&state.scissor.xform) + state.scissor.xform[4] = x + w * 0.5 + state.scissor.xform[5] = y + h * 0.5 + TransformMultiply(&state.scissor.xform, state.xform) + + state.scissor.extent[0] = w * 0.5 + state.scissor.extent[1] = h * 0.5 +} + +/* + Intersects current scissor rectangle with the specified rectangle. + The scissor rectangle is transformed by the current transform. + Note: in case the rotation of previous scissor rect differs from + the current one, the intersection will be done between the specified + rectangle and the previous scissor rectangle transformed in the current + transform space. The resulting shape is always rectangle. +*/ +IntersectScissor :: proc( + ctx: ^Context, + x, y, w, h: f32, +) { + isect_rects :: proc( + dst: ^[4]f32, + ax, ay, aw, ah: f32, + bx, by, bw, bh: f32, + ) { + minx := max(ax, bx) + miny := max(ay, by) + maxx := min(ax + aw, bx + bw) + maxy := min(ay + ah, by + bh) + dst[0] = minx + dst[1] = miny + dst[2] = max(0.0, maxx - minx) + dst[3] = max(0.0, maxy - miny) + } + + state := __getState(ctx) + pxform: Matrix + invxorm: Matrix + + // If no previous scissor has been set, set the scissor as current scissor. + if state.scissor.extent[0] < 0 { + Scissor(ctx, x, y, w, h) + return + } + + pxform = state.scissor.xform + ex := state.scissor.extent[0] + ey := state.scissor.extent[1] + TransformInverse(&invxorm, state.xform) + TransformMultiply(&pxform, invxorm) + tex := ex * abs(pxform[0]) + ey * abs(pxform[2]) + tey := ex * abs(pxform[1]) + ey * abs(pxform[3]) + + rect: [4]f32 + isect_rects(&rect, pxform[4] - tex, pxform[5] - tey, tex * 2, tey * 2, x,y,w,h) + Scissor(ctx, rect.x, rect.y, rect.z, rect.w) +} + +// Reset and disables scissoring. +ResetScissor :: proc(ctx: ^Context) { + state := __getState(ctx) + state.scissor.xform = 0 + state.scissor.extent[0] = -1 + state.scissor.extent[1] = -1 +} + +/////////////////////////////////////////////////////////// +// Global composite operation +// +// The composite operations in NanoVG are modeled after HTML Canvas API, and +// the blend func is based on OpenGL (see corresponding manuals for more info). +// The colors in the blending state have premultiplied alpha. +/////////////////////////////////////////////////////////// + +// state table instead of if else chains +OP_STATE_TABLE :: [CompositeOperation][2]BlendFactor { + .SOURCE_OVER = { .ONE, .ONE_MINUS_SRC_ALPHA }, + .SOURCE_IN = { .DST_ALPHA, .ZERO }, + .SOURCE_OUT = { .ONE_MINUS_DST_ALPHA, .ZERO }, + .ATOP = { .DST_ALPHA, .ONE_MINUS_SRC_ALPHA }, + + .DESTINATION_OVER = { .ONE_MINUS_DST_ALPHA, .ONE }, + .DESTINATION_IN = { .ZERO, .SRC_ALPHA }, + .DESTINATION_OUT = { .ZERO, .ONE_MINUS_SRC_ALPHA }, + .DESTINATION_ATOP = { .ONE_MINUS_DST_ALPHA, .SRC_ALPHA }, + + .LIGHTER = { .ONE, .ONE }, + .COPY = { .ONE, .ZERO }, + .XOR = { .ONE_MINUS_DST_ALPHA, .ONE_MINUS_SRC_ALPHA }, +} + +__compositeOperationState :: proc(op: CompositeOperation) -> (res: CompositeOperationState) { + table := OP_STATE_TABLE + factors := table[op] + res.srcRGB = factors.x + res.dstRGB = factors.y + res.srcAlpha = factors.x + res.dstAlpha = factors.y + return +} + +// Sets the composite operation. The op parameter should be one of NVGcompositeOperation. +GlobalCompositeOperation :: proc(ctx: ^Context, op: CompositeOperation) { + state := __getState(ctx) + state.compositeOperation = __compositeOperationState(op) +} + +// Sets the composite operation with custom pixel arithmetic. The parameters should be one of NVGblendFactor. +GlobalCompositeBlendFunc :: proc(ctx: ^Context, sfactor, dfactor: BlendFactor) { + GlobalCompositeBlendFuncSeparate(ctx, sfactor, dfactor, sfactor, dfactor) +} + +// Sets the composite operation with custom pixel arithmetic for RGB and alpha components separately. The parameters should be one of NVGblendFactor. +GlobalCompositeBlendFuncSeparate :: proc( + ctx: ^Context, + srcRGB: BlendFactor, + dstRGB: BlendFactor, + srcAlpha: BlendFactor, + dstAlpha: BlendFactor, +) { + op := CompositeOperationState { + srcRGB, + dstRGB, + srcAlpha, + dstAlpha, + } + state := __getState(ctx) + state.compositeOperation = op +} + +/////////////////////////////////////////////////////////// +// Points / Path handling +/////////////////////////////////////////////////////////// + +__cross :: proc(dx0, dy0, dx1, dy1: f32) -> f32 { + return dx1*dy0 - dx0*dy1 +} + +__ptEquals :: proc(x1, y1, x2, y2, tol: f32) -> bool { + dx := x2 - x1 + dy := y2 - y1 + return dx * dx + dy * dy < tol * tol +} + +__distPtSeg :: proc(x, y, px, py, qx, qy: f32) -> f32 { + pqx := qx - px + pqy := qy - py + dx := x - px + dy := y - py + d := pqx * pqx + pqy * pqy + t := pqx * dx + pqy * dy + + if d > 0 { + t /= d + } + + if t < 0 { + t = 0 + } else if t > 1 { + t = 1 + } + + dx = px + t * pqx - x + dy = py + t * pqy - y + return dx * dx + dy * dy +} + +__appendCommands :: proc(ctx: ^Context, values: []f32) { + state := __getState(ctx) + + if Commands(values[0]) != .CLOSE && Commands(values[0]) != .WINDING { + ctx.commandx = values[len(values) - 2] + ctx.commandy = values[len(values) - 1] + } + + i := 0 + for i < len(values) { + cmd := Commands(values[i]) + + switch cmd { + case .MOVE_TO, .LINE_TO: { + TransformPoint(&values[i + 1], &values[i + 2], state.xform, values[i + 1], values[i + 2]) + i += 3 + } + + case .BEZIER_TO: { + TransformPoint(&values[i + 1], &values[i + 2], state.xform, values[i + 1], values[i + 2]) + TransformPoint(&values[i + 3], &values[i + 4], state.xform, values[i + 3], values[i + 4]) + TransformPoint(&values[i + 5], &values[i + 6], state.xform, values[i + 5], values[i + 6]) + i += 7 + } + + case .CLOSE: { + i += 1 + } + + case .WINDING: { + i += 2 + } + + // default + case: { + i += 1 + } + } + } + + // append values + append(&ctx.commands, ..values) +} + +__clearPathCache :: proc(ctx: ^Context) { + clear(&ctx.cache.points) + clear(&ctx.cache.paths) +} + +__lastPath :: proc(ctx: ^Context) -> ^Path { + if len(ctx.cache.paths) > 0 { + return &ctx.cache.paths[len(ctx.cache.paths) - 1] + } + + return nil +} + +__addPath :: proc(ctx: ^Context) { + append(&ctx.cache.paths, Path { + first = len(ctx.cache.points), + winding = .CCW, + }) +} + +__lastPoint :: proc(ctx: ^Context) -> ^Point { + if len(ctx.cache.paths) > 0 { + return &ctx.cache.points[len(ctx.cache.points) - 1] + } + + return nil +} + +__addPoint :: proc(ctx: ^Context, x, y: f32, flags: PointFlags) { + path := __lastPath(ctx) + + if path == nil { + return + } + + if path.count > 0 && len(ctx.cache.points) > 0 { + pt := __lastPoint(ctx) + + if __ptEquals(pt.x, pt.y, x, y, ctx.distTol) { + pt.flags |= flags + return + } + } + + append(&ctx.cache.points, Point { + x = x, + y = y, + flags = flags, + }) + path.count += 1 +} + +__closePath :: proc(ctx: ^Context) { + path := __lastPath(ctx) + if path == nil { + return + } + path.closed = true +} + +__pathWinding :: proc(ctx: ^Context, winding: Winding) { + path := __lastPath(ctx) + if path == nil { + return + } + path.winding = winding +} + +__getAverageScale :: proc(t: []f32) -> f32 { + assert(len(t) > 4) + sx := math.sqrt(f64(t[0]) * f64(t[0]) + f64(t[2]) * f64(t[2])) + sy := math.sqrt(f64(t[1]) * f64(t[1]) + f64(t[3]) * f64(t[3])) + return f32((sx + sy) * 0.5) + // sx := math.sqrt(t[0] * t[0] + t[2] * t[2]) + // sy := math.sqrt(t[1] * t[1] + t[3] * t[3]) + // return (sx + sy) * 0.5 +} + +__triarea2 :: proc(ax, ay, bx, by, cx, cy: f32) -> f32 { + abx := bx - ax + aby := by - ay + acx := cx - ax + acy := cy - ay + return acx * aby - abx * acy +} + +__polyArea :: proc(points: []Point) -> f32 { + area := f32(0) + + for i := 2; i < len(points); i += 1 { + a := &points[0] + b := &points[i - 1] + c := &points[i] + area += __triarea2(a.x, a.y, b.x, b.y, c.x, c.y) + } + + return area * 0.5 +} + +__polyReverse :: proc(points: []Point) { + tmp: Point + i := 0 + j := len(points) - 1 + + for i < j { + tmp = points[i] + points[i] = points[j] + points[j] = tmp + i += 1 + j -= 1 + } +} + +__normalize :: proc(x, y: ^f32) -> f32 { + d := math.sqrt(x^ * x^ + y^ * y^) + if d > 1e-6 { + id := 1.0 / d + x^ *= id + y^ *= id + } + return d +} + +__tesselateBezier :: proc( + ctx: ^Context, + x1, y1: f32, + x2, y2: f32, + x3, y3: f32, + x4, y4: f32, + level: int, + flags: PointFlags, +) { + if level > 10 { + return + } + + x12 := (x1 + x2) * 0.5 + y12 := (y1 + y2) * 0.5 + x23 := (x2 + x3) * 0.5 + y23 := (y2 + y3) * 0.5 + x34 := (x3 + x4) * 0.5 + y34 := (y3 + y4) * 0.5 + x123 := (x12 + x23) * 0.5 + y123 := (y12 + y23) * 0.5 + + dx := x4 - x1 + dy := y4 - y1 + d2 := abs(((x2 - x4) * dy - (y2 - y4) * dx)) + d3 := abs(((x3 - x4) * dy - (y3 - y4) * dx)) + + if (d2 + d3)*(d2 + d3) < ctx.tessTol * (dx*dx + dy*dy) { + __addPoint(ctx, x4, y4, flags) + return + } + + x234 := (x23 + x34) * 0.5 + y234 := (y23 + y34) * 0.5 + x1234 := (x123 + x234) * 0.5 + y1234 := (y123 + y234) * 0.5 + + __tesselateBezier(ctx, x1,y1, x12,y12, x123,y123, x1234,y1234, level+1, {}) + __tesselateBezier(ctx, x1234,y1234, x234,y234, x34,y34, x4,y4, level+1, flags) +} + +__flattenPaths :: proc(ctx: ^Context) { + cache := &ctx.cache + + if len(cache.paths) > 0 { + return + } + + // flatten + i := 0 + for i < len(ctx.commands) { + cmd := Commands(ctx.commands[i]) + + switch cmd { + case .MOVE_TO: { + __addPath(ctx) + p := ctx.commands[i + 1:] + __addPoint(ctx, p[0], p[1], { .CORNER }) + i += 3 + } + + case .LINE_TO: { + p := ctx.commands[i + 1:] + __addPoint(ctx, p[0], p[1], { .CORNER }) + i += 3 + } + + case .BEZIER_TO: { + last := __lastPoint(ctx) + + if last != nil { + cp1 := ctx.commands[i + 1:] + cp2 := ctx.commands[i + 3:] + p := ctx.commands[i + 5:] + __tesselateBezier(ctx, last.x,last.y, cp1[0],cp1[1], cp2[0],cp2[1], p[0],p[1], 0, { .CORNER }) + } + + i += 7 + } + + case .CLOSE: { + __closePath(ctx) + i += 1 + } + + case .WINDING: { + __pathWinding(ctx, Winding(ctx.commands[i + 1])) + i += 2 + } + + case: { + i += 1 + } + } + } + + cache.bounds[0] = 1e6 + cache.bounds[1] = 1e6 + cache.bounds[2] = -1e6 + cache.bounds[3] = -1e6 + + // Calculate the direction and length of line segments. + for j in 0.. 2 { + area := __polyArea(pts[:path.count]) + + if path.winding == .CCW && area < 0 { + __polyReverse(pts[:path.count]) + } + + if path.winding == .CW && area > 0 { + __polyReverse(pts[:path.count]) + } + } + + for k in 0.. f32 { + da := math.acos(r / (r + tol)) * 2 + return max(2, math.ceil(arc / da)) +} + +__chooseBevel :: proc( + bevel: bool, + p0: ^Point, + p1: ^Point, + w: f32, + x0, y0, x1, y1: ^f32, +) { + if bevel { + x0^ = p1.x + p0.dy * w + y0^ = p1.y - p0.dx * w + x1^ = p1.x + p1.dy * w + y1^ = p1.y - p1.dx * w + } else { + x0^ = p1.x + p1.dmx * w + y0^ = p1.y + p1.dmy * w + x1^ = p1.x + p1.dmx * w + y1^ = p1.y + p1.dmy * w + } +} + +/////////////////////////////////////////////////////////// +// Vertice Setting +/////////////////////////////////////////////////////////// + +// set vertex & increase slice position (decreases length) +__vset :: proc(dst: ^[]Vertex, x, y, u, v: f32, loc := #caller_location) { + dst[0] = { x, y, u, v } + dst^ = dst[1:] +} + +__roundJoin :: proc( + dst: ^[]Vertex, + p0: ^Point, + p1: ^Point, + lw: f32, + rw: f32, + lu: f32, + ru: f32, + ncap: int, +) { + dlx0 := p0.dy + dly0 := -p0.dx + dlx1 := p1.dy + dly1 := -p1.dx + + if .LEFT in p1.flags { + lx0,ly0,lx1,ly1: f32 + __chooseBevel(.INNER_BEVEL in p1.flags, p0, p1, lw, &lx0,&ly0, &lx1,&ly1) + a0 := math.atan2(-dly0, -dlx0) + a1 := math.atan2(-dly1, -dlx1) + + if a1 > a0 { + a1 -= math.PI * 2 + } + + __vset(dst, lx0, ly0, lu, 1) + __vset(dst, p1.x - dlx0 * rw, p1.y - dly0 * rw, ru, 1) + + temp := int(math.ceil((a0 - a1) / math.PI * f32(ncap))) + n := clamp(temp, 2, ncap) + + for i := 0; i < n; i += 1 { + u := f32(i) / f32(n - 1) + a := a0 + u * (a1 - a0) + rx := p1.x + math.cos(a) * rw + ry := p1.y + math.sin(a) * rw + __vset(dst, p1.x, p1.y, 0.5, 1) + __vset(dst, rx, ry, ru,1) + } + + __vset(dst, lx1, ly1, lu,1) + __vset(dst, p1.x - dlx1*rw, p1.y - dly1*rw, ru,1) + } else { + rx0,ry0,rx1,ry1: f32 + __chooseBevel(.INNER_BEVEL in p1.flags, p0, p1, -rw, &rx0, &ry0, &rx1, &ry1) + a0 := math.atan2(dly0, dlx0) + a1 := math.atan2(dly1, dlx1) + if a1 < a0 { + a1 += math.PI * 2 + } + + __vset(dst, p1.x + dlx0*rw, p1.y + dly0*rw, lu,1) + __vset(dst, rx0, ry0, ru,1) + + temp := int(math.ceil((a1 - a0) / math.PI * f32(ncap))) + n := clamp(temp, 2, ncap) + + for i := 0; i < n; i += 1 { + u := f32(i) / f32(n - 1) + a := a0 + u*(a1-a0) + lx := p1.x + math.cos(a) * lw + ly := p1.y + math.sin(a) * lw + __vset(dst, lx, ly, lu, 1) + __vset(dst, p1.x, p1.y, 0.5, 1) + } + + __vset(dst, p1.x + dlx1*rw, p1.y + dly1*rw, lu,1) + __vset(dst, rx1, ry1, ru,1) + } +} + +__bevelJoin :: proc( + dst: ^[]Vertex, + p0: ^Point, + p1: ^Point, + lw: f32, + rw: f32, + lu: f32, + ru: f32, +) { + dlx0 := p0.dy + dly0 := -p0.dx + dlx1 := p1.dy + dly1 := -p1.dx + + rx0, ry0, rx1, ry1: f32 + lx0, ly0, lx1, ly1: f32 + + if .LEFT in p1.flags { + __chooseBevel(.INNER_BEVEL in p1.flags, p0, p1, lw, &lx0,&ly0, &lx1,&ly1) + + __vset(dst, lx0, ly0, lu,1) + __vset(dst, p1.x - dlx0*rw, p1.y - dly0*rw, ru,1) + + if .BEVEL in p1.flags { + __vset(dst, lx0, ly0, lu,1) + __vset(dst, p1.x - dlx0*rw, p1.y - dly0*rw, ru,1) + + __vset(dst, lx1, ly1, lu,1) + __vset(dst, p1.x - dlx1*rw, p1.y - dly1*rw, ru,1) + } else { + rx0 = p1.x - p1.dmx * rw + ry0 = p1.y - p1.dmy * rw + + __vset(dst, p1.x, p1.y, 0.5,1) + __vset(dst, p1.x - dlx0*rw, p1.y - dly0*rw, ru,1) + + __vset(dst, rx0, ry0, ru,1) + __vset(dst, rx0, ry0, ru,1) + + __vset(dst, p1.x, p1.y, 0.5,1) + __vset(dst, p1.x - dlx1*rw, p1.y - dly1*rw, ru,1) + } + + __vset(dst, lx1, ly1, lu,1) + __vset(dst, p1.x - dlx1*rw, p1.y - dly1*rw, ru,1) + } else { + __chooseBevel(.INNER_BEVEL in p1.flags, p0, p1, -rw, &rx0,&ry0, &rx1,&ry1) + + __vset(dst, p1.x + dlx0*lw, p1.y + dly0*lw, lu,1) + __vset(dst, rx0, ry0, ru,1) + + if .BEVEL in p1.flags { + __vset(dst, p1.x + dlx0*lw, p1.y + dly0*lw, lu,1) + __vset(dst, rx0, ry0, ru,1) + + __vset(dst, p1.x + dlx1*lw, p1.y + dly1*lw, lu,1) + __vset(dst, rx1, ry1, ru,1) + } else { + lx0 = p1.x + p1.dmx * lw + ly0 = p1.y + p1.dmy * lw + + __vset(dst, p1.x + dlx0*lw, p1.y + dly0*lw, lu,1) + __vset(dst, p1.x, p1.y, 0.5,1) + + __vset(dst, lx0, ly0, lu,1) + __vset(dst, lx0, ly0, lu,1) + + __vset(dst, p1.x + dlx1*lw, p1.y + dly1*lw, lu,1) + __vset(dst, p1.x, p1.y, 0.5,1) + } + + __vset(dst, p1.x + dlx1*lw, p1.y + dly1*lw, lu,1) + __vset(dst, rx1, ry1, ru,1) + } +} + +__buttCapStart :: proc( + dst: ^[]Vertex, + p: ^Point, + dx, dy: f32, + w: f32, + d: f32, + aa: f32, + u0: f32, + u1: f32, +) { + px := p.x - dx * d + py := p.y - dy * d + dlx := dy + dly := -dx + __vset(dst, px + dlx*w - dx*aa, py + dly*w - dy*aa, u0,0) + __vset(dst, px - dlx*w - dx*aa, py - dly*w - dy*aa, u1,0) + __vset(dst, px + dlx*w, py + dly*w, u0,1) + __vset(dst, px - dlx*w, py - dly*w, u1,1) +} + +__buttCapEnd :: proc( + dst: ^[]Vertex, + p: ^Point, + dx, dy: f32, + w: f32, + d: f32, + aa: f32, + u0: f32, + u1: f32, +) { + px := p.x + dx * d + py := p.y + dy * d + dlx := dy + dly := -dx + __vset(dst, px + dlx*w, py + dly*w, u0,1) + __vset(dst, px - dlx*w, py - dly*w, u1,1) + __vset(dst, px + dlx*w + dx*aa, py + dly*w + dy*aa, u0,0) + __vset(dst, px - dlx*w + dx*aa, py - dly*w + dy*aa, u1,0) +} + +__roundCapStart :: proc( + dst: ^[]Vertex, + p: ^Point, + dx, dy: f32, + w: f32, + ncap: int, + u0: f32, + u1: f32, +) { + px := p.x + py := p.y + dlx := dy + dly := -dx + + for i in 0.. 0 { + iw = 1.0 / w + } + + // Calculate which joins needs extra vertices to append, and gather vertex count. + for path, i in &cache.paths { + pts := cache.points[path.first:] + p0 := &pts[path.count-1] + p1 := &pts[0] + nleft := 0 + path.nbevel = 0 + + for j in 0.. 0.000001) { + scale := 1.0 / dmr2 + if (scale > 600.0) { + scale = 600.0 + } + p1.dmx *= scale + p1.dmy *= scale + } + + // Clear flags, but keep the corner. + p1.flags = (.CORNER in p1.flags) ? { .CORNER } : {} + + // Keep track of left turns. + __cross = p1.dx * p0.dy - p0.dx * p1.dy + if __cross > 0.0 { + nleft += 1 + incl(&p1.flags, PointFlag.LEFT) + } + + // Calculate if we should use bevel or miter for inner join. + limit = max(1.01, min(p0.len, p1.len) * iw) + if (dmr2 * limit * limit) < 1.0 { + incl(&p1.flags, PointFlag.INNER_BEVEL) + } + + // Check to see if the corner needs to be beveled. + if .CORNER in p1.flags { + if (dmr2 * miterLimit*miterLimit) < 1.0 || lineJoin == .BEVEL || lineJoin == .ROUND { + incl(&p1.flags, PointFlag.BEVEL) + } + } + + if (.BEVEL in p1.flags) || (.INNER_BEVEL in p1.flags) { + path.nbevel += 1 + } + + p0 = p1 + p1 = mem.ptr_offset(p1, 1) + } + + path.convex = nleft == path.count + } +} + +// TODO could be done better? or not need dynamic +__allocTempVerts :: proc(ctx: ^Context, nverts: int) -> []Vertex { + resize(&ctx.cache.verts, nverts) + return ctx.cache.verts[:] +} + +__expandStroke :: proc( + ctx: ^Context, + w: f32, + fringe: f32, + lineCap: LineCapType, + lineJoin: LineCapType, + miterLimit: f32, +) -> bool { + cache := &ctx.cache + aa := fringe + u0 := f32(0.0) + u1 := f32(1.0) + ncap := __curveDivs(w, math.PI, ctx.tessTol) // Calculate divisions per half circle. + + w := w + w += aa * 0.5 + + // Disable the gradient used for antialiasing when antialiasing is not used. + if aa == 0.0 { + u0 = 0.5 + u1 = 0.5 + } + + __calculateJoins(ctx, w, lineJoin, miterLimit) + + // Calculate max vertex usage. + cverts := 0 + for path in &cache.paths { + loop := path.closed + + // TODO check if f32 calculation necessary? + if lineJoin == .ROUND { + cverts += (path.count + path.nbevel * int(ncap + 2) + 1) * 2 // plus one for loop + } else { + cverts += (path.count + path.nbevel*5 + 1) * 2 // plus one for loop + } + + if !loop { + // space for caps + if lineCap == .ROUND { + cverts += int(ncap*2 + 2)*2 + } else { + cverts += (3 + 3)*2 + } + } + } + + verts := __allocTempVerts(ctx, cverts) + dst_index: int + + for i in 0.. bool { + cache := &ctx.cache + aa := ctx.fringeWidth + fringe := w > 0.0 + __calculateJoins(ctx, w, lineJoin, miterLimit) + + // Calculate max vertex usage. + cverts := 0 + for path in &cache.paths { + cverts += path.count + path.nbevel + 1 + + if fringe { + cverts += (path.count + path.nbevel*5 + 1) * 2 // plus one for loop + } + } + + convex := len(cache.paths) == 1 && cache.paths[0].convex + verts := __allocTempVerts(ctx, cverts) + dst_index: int + + for path in &cache.paths { + pts := cache.points[path.first:] + p0, p1: ^Point + rw, lw, woff: f32 + ru, lu: f32 + + // Calculate shape vertices. + woff = 0.5*aa + dst := verts[dst_index:] + dst_start_length := len(dst) + + if fringe { + // Looping + p0 = &pts[path.count-1] + p1 = &pts[0] + + for j in 0.. f32 { + return f32(cmd) +} + +// Clears the current path and sub-paths. +BeginPath :: proc(ctx: ^Context) { + clear(&ctx.commands) + __clearPathCache(ctx) +} + +@(deferred_in=Fill) +FillScoped :: proc(ctx: ^Context) { + BeginPath(ctx) +} + +@(deferred_in=Stroke) +StrokeScoped :: proc(ctx: ^Context) { + BeginPath(ctx) +} + +@(deferred_in=Stroke) +FillStrokeScoped :: proc(ctx: ^Context) { + BeginPath(ctx) +} + +// Starts new sub-path with specified point as first point. +MoveTo :: proc(ctx: ^Context, x, y: f32) { + values := [3]f32 { __cmdf(.MOVE_TO), x, y } + __appendCommands(ctx, values[:]) +} + +// Adds line segment from the last point in the path to the specified point. +LineTo :: proc(ctx: ^Context, x, y: f32) { + values := [3]f32 { __cmdf(.LINE_TO), x, y } + __appendCommands(ctx, values[:]) +} + +// Adds cubic bezier segment from last point in the path via two control points to the specified point. +BezierTo :: proc( + ctx: ^Context, + c1x, c1y: f32, + c2x, c2y: f32, + x, y: f32, +) { + values := [?]f32 { __cmdf(.BEZIER_TO), c1x, c1y, c2x, c2y, x, y } + __appendCommands(ctx, values[:]) +} + +// Adds quadratic bezier segment from last point in the path via a control point to the specified point. +QuadTo :: proc(ctx: ^Context, cx, cy, x, y: f32) { + x0 := ctx.commandx + y0 := ctx.commandy + values := [?]f32 { + __cmdf(.BEZIER_TO), + x0 + 2 / 3 * (cx - x0), + y0 + 2 / 3 * (cy - y0), + x + 2 / 3 * (cx - x), + y + 2 / 3 * (cy - y), + x, + y, + } + __appendCommands(ctx, values[:]) +} + +// Adds an arc segment at the corner defined by the last path point, and two specified points. +ArcTo :: proc( + ctx: ^Context, + x1, y1: f32, + x2, y2: f32, + radius: f32, +) { + if len(ctx.commands) == 0 { + return + } + + x0 := ctx.commandx + y0 := ctx.commandy + // Handle degenerate cases. + if __ptEquals(x0,y0, x1,y1, ctx.distTol) || + __ptEquals(x1,y1, x2,y2, ctx.distTol) || + __distPtSeg(x1,y1, x0,y0, x2,y2) < ctx.distTol*ctx.distTol || + radius < ctx.distTol { + LineTo(ctx, x1, y1) + return + } + + // Calculate tangential circle to lines (x0,y0)-(x1,y1) and (x1,y1)-(x2,y2). + dx0 := x0-x1 + dy0 := y0-y1 + dx1 := x2-x1 + dy1 := y2-y1 + __normalize(&dx0,&dy0) + __normalize(&dx1,&dy1) + a := math.acos(dx0*dx1 + dy0*dy1) + d := radius / math.tan(a / 2.0) + + if d > 10000 { + LineTo(ctx, x1, y1) + return + } + + a0, a1, cx, cy: f32 + direction: Winding + + if __cross(dx0,dy0, dx1,dy1) > 0.0 { + cx = x1 + dx0*d + dy0*radius + cy = y1 + dy0*d + -dx0*radius + a0 = math.atan2(dx0, -dy0) + a1 = math.atan2(-dx1, dy1) + direction = .CW + } else { + cx = x1 + dx0*d + -dy0*radius + cy = y1 + dy0*d + dx0*radius + a0 = math.atan2(-dx0, dy0) + a1 = math.atan2(dx1, -dy1) + direction = .CCW + } + + Arc(ctx, cx, cy, radius, a0, a1, direction) +} + +// Creates new circle arc shaped sub-path. The arc center is at cx,cy, the arc radius is r, +// and the arc is drawn from angle a0 to a1, and swept in direction dir (NVG_CCW, or NVG_CW). +// Angles are specified in radians. +Arc :: proc(ctx: ^Context, cx, cy, r, a0, a1: f32, dir: Winding) { + move: Commands = .LINE_TO if len(ctx.commands) > 0 else .MOVE_TO + + // Clamp angles + da := a1 - a0 + if dir == .CW { + if abs(da) >= math.PI*2 { + da = math.PI*2 + } else { + for da < 0.0 { + da += math.PI*2 + } + } + } else { + if abs(da) >= math.PI*2 { + da = -math.PI*2 + } else { + for da > 0.0 { + da -= math.PI*2 + } + } + } + + // Split arc into max 90 degree segments. + ndivs := max(1, min((int)(abs(da) / (math.PI*0.5) + 0.5), 5)) + hda := (da / f32(ndivs)) / 2.0 + kappa := abs(4.0 / 3.0 * (1.0 - math.cos(hda)) / math.sin(hda)) + + if dir == .CCW { + kappa = -kappa + } + + values: [3 + 5 * 7 + 100]f32 + nvals := 0 + + px, py, ptanx, ptany: f32 + for i in 0..=ndivs { + a := a0 + da * f32(i) / f32(ndivs) + dx := math.cos(a) + dy := math.sin(a) + x := cx + dx*r + y := cy + dy*r + tanx := -dy*r*kappa + tany := dx*r*kappa + + if i == 0 { + values[nvals] = __cmdf(move); nvals += 1 + values[nvals] = x; nvals += 1 + values[nvals] = y; nvals += 1 + } else { + values[nvals] = __cmdf(.BEZIER_TO); nvals += 1 + values[nvals] = px + ptanx; nvals += 1 + values[nvals] = py + ptany; nvals += 1 + values[nvals] = x-tanx; nvals += 1 + values[nvals] = y-tany; nvals += 1 + values[nvals] = x; nvals += 1 + values[nvals] = y; nvals += 1 + } + px = x + py = y + ptanx = tanx + ptany = tany + } + + // stored internally + __appendCommands(ctx, values[:nvals]) +} + +// Closes current sub-path with a line segment. +ClosePath :: proc(ctx: ^Context) { + values := [1]f32 { __cmdf(.CLOSE) } + __appendCommands(ctx, values[:]) +} + +// Sets the current sub-path winding, see NVGwinding and NVGsolidity. +PathWinding :: proc(ctx: ^Context, direction: Winding) { + values := [2]f32 { __cmdf(.WINDING), f32(direction) } + __appendCommands(ctx, values[:]) +} + +// same as path_winding but with different enum +PathSolidity :: proc(ctx: ^Context, solidity: Solidity) { + values := [2]f32 { __cmdf(.WINDING), f32(solidity) } + __appendCommands(ctx, values[:]) +} + +// Creates new rectangle shaped sub-path. +Rect :: proc(ctx: ^Context, x, y, w, h: f32) { + values := [?]f32 { + __cmdf(.MOVE_TO), x, y, + __cmdf(.LINE_TO), x, y + h, + __cmdf(.LINE_TO), x + w, y + h, + __cmdf(.LINE_TO), x + w, y, + __cmdf(.CLOSE), + } + __appendCommands(ctx, values[:]) +} + +// Creates new rounded rectangle shaped sub-path. +RoundedRect :: proc(ctx: ^Context, x, y, w, h, radius: f32) { + RoundedRectVarying(ctx, x, y, w, h, radius, radius, radius, radius) +} + +// Creates new rounded rectangle shaped sub-path with varying radii for each corner. +RoundedRectVarying :: proc( + ctx: ^Context, + x, y: f32, + w, h: f32, + radius_top_left: f32, + radius_top_right: f32, + radius_bottom_right: f32, + radius_bottom_left: f32, +) { + if radius_top_left < 0.1 && radius_top_right < 0.1 && radius_bottom_right < 0.1 && radius_bottom_left < 0.1 { + Rect(ctx, x, y, w, h) + } else { + halfw := abs(w) * 0.5 + halfh := abs(h) * 0.5 + rxBL := min(radius_bottom_left, halfw) * math.sign(w) + ryBL := min(radius_bottom_left, halfh) * math.sign(h) + rxBR := min(radius_bottom_right, halfw) * math.sign(w) + ryBR := min(radius_bottom_right, halfh) * math.sign(h) + rxTR := min(radius_top_right, halfw) * math.sign(w) + ryTR := min(radius_top_right, halfh) * math.sign(h) + rxTL := min(radius_top_left, halfw) * math.sign(w) + ryTL := min(radius_top_left, halfh) * math.sign(h) + values := [?]f32 { + __cmdf(.MOVE_TO), x, y + ryTL, + __cmdf(.LINE_TO), x, y + h - ryBL, + __cmdf(.BEZIER_TO), x, y + h - ryBL*(1 - KAPPA), x + rxBL*(1 - KAPPA), y + h, x + rxBL, y + h, + __cmdf(.LINE_TO), x + w - rxBR, y + h, + __cmdf(.BEZIER_TO), x + w - rxBR*(1 - KAPPA), y + h, x + w, y + h - ryBR*(1 - KAPPA), x + w, y + h - ryBR, + __cmdf(.LINE_TO), x + w, y + ryTR, + __cmdf(.BEZIER_TO), x + w, y + ryTR*(1 - KAPPA), x + w - rxTR*(1 - KAPPA), y, x + w - rxTR, y, + __cmdf(.LINE_TO), x + rxTL, y, + __cmdf(.BEZIER_TO), x + rxTL*(1 - KAPPA), y, x, y + ryTL*(1 - KAPPA), x, y + ryTL, + __cmdf(.CLOSE), + } + __appendCommands(ctx, values[:]) + } +} + +// Creates new ellipse shaped sub-path. +Ellipse :: proc(ctx: ^Context, cx, cy, rx, ry: f32) { + values := [?]f32 { + __cmdf(.MOVE_TO), cx-rx, cy, + __cmdf(.BEZIER_TO), cx-rx, cy+ry*KAPPA, cx-rx*KAPPA, cy+ry, cx, cy+ry, + __cmdf(.BEZIER_TO), cx+rx*KAPPA, cy+ry, cx+rx, cy+ry*KAPPA, cx+rx, cy, + __cmdf(.BEZIER_TO), cx+rx, cy-ry*KAPPA, cx+rx*KAPPA, cy-ry, cx, cy-ry, + __cmdf(.BEZIER_TO), cx-rx*KAPPA, cy-ry, cx-rx, cy-ry*KAPPA, cx-rx, cy, + __cmdf(.CLOSE) + } + __appendCommands(ctx, values[:]) +} + +// Creates new circle shaped sub-path. +Circle :: #force_inline proc(ctx: ^Context, cx, cy: f32, radius: f32) { + Ellipse(ctx, cx, cy, radius, radius) +} + +// Fills the current path with current fill style. +Fill :: proc(ctx: ^Context) { + state := __getState(ctx) + fill_paint := state.fill + + __flattenPaths(ctx) + + if ctx.params.edgeAntiAlias && state.shapeAntiAlias { + __expandFill(ctx, ctx.fringeWidth, .MITER, 2.4) + } else { + __expandFill(ctx, 0, .MITER, 2.4) + } + + // apply global alpha + fill_paint.innerColor.a *= state.alpha + fill_paint.outerColor.a *= state.alpha + + assert(ctx.params.renderFill != nil) + ctx.params.renderFill( + ctx.params.userPtr, + &fill_paint, + state.compositeOperation, + &state.scissor, + ctx.fringeWidth, + ctx.cache.bounds, + ctx.cache.paths[:], + ) + + for path in &ctx.cache.paths { + ctx.fillTriCount += len(path.fill) - 2 + ctx.fillTriCount += len(path.stroke) - 2 + ctx.drawCallCount += 2 + } +} + +// Fills the current path with current stroke style. +Stroke :: proc(ctx: ^Context) { + state := __getState(ctx) + scale := __getAverageScale(state.xform[:]) + strokeWidth := clamp(state.strokeWidth * scale, 0, 200) + stroke_paint := state.stroke + + if strokeWidth < ctx.fringeWidth { + // If the stroke width is less than pixel size, use alpha to emulate coverage. + // Since coverage is area, scale by alpha*alpha. + alpha := clamp(strokeWidth / ctx.fringeWidth, 0, 1) + stroke_paint.innerColor.a *= alpha * alpha + stroke_paint.outerColor.a *= alpha * alpha + strokeWidth = ctx.fringeWidth + } + + // apply global alpha + stroke_paint.innerColor.a *= state.alpha + stroke_paint.outerColor.a *= state.alpha + + __flattenPaths(ctx) + + if ctx.params.edgeAntiAlias && state.shapeAntiAlias { + __expandStroke(ctx, strokeWidth * 0.5, ctx.fringeWidth, state.lineCap, state.lineJoin, state.miterLimit) + } else { + __expandStroke(ctx, strokeWidth * 0.5, 0, state.lineCap, state.lineJoin, state.miterLimit) + } + + assert(ctx.params.renderStroke != nil) + ctx.params.renderStroke( + ctx.params.userPtr, + &stroke_paint, + state.compositeOperation, + &state.scissor, + ctx.fringeWidth, + strokeWidth, + ctx.cache.paths[:], + ) + + for path in &ctx.cache.paths { + ctx.strokeTriCount += len(path.stroke) - 2 + ctx.drawCallCount += 1 + } +} + +DebugDumpPathCache :: proc(ctx: ^Context) { + fmt.printf("~~~~~~~~~~~~~Dumping %d cached paths\n", len(ctx.cache.paths)) + + for path, i in &ctx.cache.paths { + fmt.printf(" - Path %d\n", i) + + if len(path.fill) != 0 { + fmt.printf(" - fill: %d\n", len(path.fill)) + + for j in 0.. int { + return fontstash.AddFontPath(&ctx.fs, name, filename) +} + +// Creates font by loading it from the specified memory chunk. +// Returns handle to the font. +CreateFontMem :: proc(ctx: ^Context, name: string, slice: []byte) -> int { + return fontstash.AddFontMem(&ctx.fs, name, slice) +} + +// Finds a loaded font of specified name, and returns handle to it, or -1 if the font is not found. +FindFont :: proc(ctx: ^Context, name: string) -> int { + if name == "" { + return -1 + } + + return fontstash.GetFontByName(&ctx.fs, name) +} + +// Adds a fallback font by handle. +AddFallbackFontId :: proc(ctx: ^Context, base_font, fallback_font: int) -> bool { + if base_font == -1 || fallback_font == -1 { + return false + } + + return fontstash.AddFallbackFont(&ctx.fs, base_font, fallback_font) +} + +// Adds a fallback font by name. +AddFallbackFont :: proc(ctx: ^Context, base_font: string, fallback_font: string) -> bool { + return AddFallbackFontId( + ctx, + FindFont(ctx, base_font), + FindFont(ctx, fallback_font), + ) +} + +// Resets fallback fonts by handle. +ResetFallbackFontsId :: proc(ctx: ^Context, base_font: int) { + fontstash.ResetFallbackFont(&ctx.fs, base_font) +} + +// Resets fallback fonts by name. +ResetFallbackFonts :: proc(ctx: ^Context, base_font: string) { + fontstash.ResetFallbackFont(&ctx.fs, FindFont(ctx, base_font)) +} + +// Sets the font size of current text style. +FontSize :: proc(ctx: ^Context, size: f32) { + state := __getState(ctx) + state.fontSize = size +} + +// Sets the blur of current text style. +FontBlur :: proc(ctx: ^Context, blur: f32) { + state := __getState(ctx) + state.fontBlur = blur +} + +// Sets the letter spacing of current text style. +TextLetterSpacing :: proc(ctx: ^Context, spacing: f32) { + state := __getState(ctx) + state.letterSpacing = spacing +} + +// Sets the proportional line height of current text style. The line height is specified as multiple of font size. +TextLineHeight :: proc(ctx: ^Context, lineHeight: f32) { + state := __getState(ctx) + state.lineHeight = lineHeight +} + +// Sets the horizontal text align of current text style +TextAlignHorizontal :: proc(ctx: ^Context, align: AlignHorizontal) { + state := __getState(ctx) + state.alignHorizontal = align +} + +// Sets the vertical text align of current text style +TextAlignVertical :: proc(ctx: ^Context, align: AlignVertical) { + state := __getState(ctx) + state.alignVertical = align +} + +// Sets the text align of current text style, see NVGalign for options. +TextAlign :: proc(ctx: ^Context, ah: AlignHorizontal, av: AlignVertical) { + state := __getState(ctx) + state.alignHorizontal = ah + state.alignVertical = av +} + +// Sets the font face based on specified name of current text style. +FontFaceId :: proc(ctx: ^Context, font: int) { + state := __getState(ctx) + state.fontId = font +} + +// Sets the font face based on specified name of current text style. +FontFace :: proc(ctx: ^Context, font: string) { + state := __getState(ctx) + state.fontId = fontstash.GetFontByName(&ctx.fs, font) +} + +__quantize :: proc(a, d: f32) -> f32 { + return f32(int(a / d + 0.5)) * d +} + +__getFontScale :: proc(state: ^State) -> f32 { + return min(__quantize(__getAverageScale(state.xform[:]), 0.01), 4.0) +} + +__flushTextTexture :: proc(ctx: ^Context) { + dirty: [4]f32 + assert(ctx.params.renderUpdateTexture != nil) + + + if fontstash.ValidateTexture(&ctx.fs, &dirty) { + font_image := ctx.fontImages[ctx.fontImageIdx] + + // Update texture + if font_image != 0 { + data := ctx.fs.textureData + x := dirty[0] + y := dirty[1] + w := dirty[2] - dirty[0] + h := dirty[3] - dirty[1] + ctx.params.renderUpdateTexture(ctx.params.userPtr, font_image, int(x), int(y), int(w), int(h), data) + } + } +} + +__allocTextAtlas :: proc(ctx: ^Context) -> bool { + __flushTextTexture(ctx) + + if ctx.fontImageIdx >= MAX_FONTIMAGES - 1 { + return false + } + + // if next fontImage already have a texture + iw, ih: int + if ctx.fontImages[ctx.fontImageIdx+1] != 0 { + iw, ih = ImageSize(ctx, ctx.fontImages[ctx.fontImageIdx+1]) + } else { // calculate the new font image size and create it. + iw, ih = ImageSize(ctx, ctx.fontImages[ctx.fontImageIdx]) + + if iw > ih { + ih *= 2 + } else { + iw *= 2 + } + + if iw > MAX_FONTIMAGE_SIZE || ih > MAX_FONTIMAGE_SIZE { + iw = MAX_FONTIMAGE_SIZE + ih = MAX_FONTIMAGE_SIZE + } + + ctx.fontImages[ctx.fontImageIdx + 1] = ctx.params.renderCreateTexture(ctx.params.userPtr, .Alpha, iw, ih, {}, nil) + } + + ctx.fontImageIdx += 1 + fontstash.ResetAtlas(&ctx.fs, iw, ih) + + return true +} + +__renderText :: proc(ctx: ^Context, verts: []Vertex) { + // disallow 0 + if len(verts) == 0 { + return + } + + state := __getState(ctx) + paint := state.fill + + // Render triangles. + paint.image = ctx.fontImages[ctx.fontImageIdx] + + // Apply global alpha + paint.innerColor.a *= state.alpha + paint.outerColor.a *= state.alpha + + ctx.params.renderTriangles(ctx.params.userPtr, &paint, state.compositeOperation, &state.scissor, verts, ctx.fringeWidth) + + ctx.drawCallCount += 1 + ctx.textTriCount += len(verts) / 3 +} + +__isTransformFlipped :: proc(xform: []f32) -> bool { + det := xform[0] * xform[3] - xform[2] * xform[1] + return det < 0 +} + +// draw a single codepoint, useful for icons +TextIcon :: proc(ctx: ^Context, x, y: f32, codepoint: rune) -> f32 { + state := __getState(ctx) + scale := __getFontScale(state) * ctx.devicePxRatio + invscale := f32(1.0) / scale + is_flipped := __isTransformFlipped(state.xform[:]) + + if state.fontId == -1 { + return x + } + + fs := &ctx.fs + fontstash.SetSize(fs, state.fontSize * scale) + fontstash.SetSpacing(fs, state.letterSpacing * scale) + fontstash.SetBlur(fs, state.fontBlur * scale) + fontstash.SetAlignHorizontal(fs, state.alignHorizontal) + fontstash.SetAlignVertical(fs, state.alignVertical) + fontstash.SetFont(fs, state.fontId) + + // fontstash internals + fstate := fontstash.__getState(fs) + font := fontstash.__getFont(fs, state.fontId) + isize := i16(fstate.size * 10) + iblur := i16(fstate.blur) + glyph := fontstash.__getGlyph(fs, font, codepoint, isize, iblur) + fscale := fontstash.__getPixelHeightScale(font, f32(isize) / 10) + + // transform x / y + x := x * scale + y := y * scale + switch fstate.ah { + case .LEFT: {} + case .CENTER: { + width := fontstash.CodepointWidth(font, codepoint, fscale) + x = math.round(x - width * 0.5) + } + case .RIGHT: { + width := fontstash.CodepointWidth(font, codepoint, fscale) + x -= width + } + } + + // align vertically + y = math.round(y + fontstash.__getVerticalAlign(fs, font, fstate.av, isize)) + nextx := f32(x) + nexty := f32(y) + + if glyph != nil { + q: fontstash.Quad + fontstash.__getQuad(fs, font, -1, glyph, fscale, fstate.spacing, &nextx, &nexty, &q) + + if is_flipped { + q.y0, q.y1 = q.y1, q.y0 + q.t0, q.t1 = q.t1, q.t0 + } + + // single glyph only + verts := __allocTempVerts(ctx, 6) + c: [4 * 2]f32 + + // Transform corners. + TransformPoint(&c[0], &c[1], state.xform, q.x0 * invscale, q.y0 * invscale) + TransformPoint(&c[2], &c[3], state.xform, q.x1 * invscale, q.y0 * invscale) + TransformPoint(&c[4], &c[5], state.xform, q.x1 * invscale, q.y1 * invscale) + TransformPoint(&c[6], &c[7], state.xform, q.x0 * invscale, q.y1 * invscale) + + // Create triangles + verts[0] = { c[0], c[1], q.s0, q.t0 } + verts[1] = { c[4], c[5], q.s1, q.t1 } + verts[2] = { c[2], c[3], q.s1, q.t0 } + verts[3] = { c[0], c[1], q.s0, q.t0 } + verts[4] = { c[6], c[7], q.s0, q.t1 } + verts[5] = { c[4], c[5], q.s1, q.t1 } + + ctx.textureDirty = true + __renderText(ctx, verts[:]) + } + + return nextx / scale +} + +// Draws text string at specified location. If end is specified only the sub-string up to the end is drawn. +Text :: proc(ctx: ^Context, x, y: f32, text: string) -> f32 { + state := __getState(ctx) + scale := __getFontScale(state) * ctx.devicePxRatio + invscale := f32(1.0) / scale + is_flipped := __isTransformFlipped(state.xform[:]) + + if state.fontId == -1 { + return x + } + + fs := &ctx.fs + fontstash.SetSize(fs, state.fontSize * scale) + fontstash.SetSpacing(fs, state.letterSpacing * scale) + fontstash.SetBlur(fs, state.fontBlur * scale) + fontstash.SetAlignHorizontal(fs, state.alignHorizontal) + fontstash.SetAlignVertical(fs, state.alignVertical) + fontstash.SetFont(fs, state.fontId) + + cverts := max(2, len(text)) * 6 // conservative estimate. + verts := __allocTempVerts(ctx, cverts) + nverts: int + + iter := fontstash.TextIterInit(fs, x * scale, y * scale, text) + prev_iter := iter + q: fontstash.Quad + for fontstash.TextIterNext(&ctx.fs, &iter, &q) { + c: [4 * 2]f32 + + if iter.previousGlyphIndex == -1 { // can not retrieve glyph? + if nverts != 0 { + __renderText(ctx, verts[:]) + nverts = 0 + } + + if !__allocTextAtlas(ctx) { + break // no memory :( + } + + iter = prev_iter + fontstash.TextIterNext(fs, &iter, &q) // try again + + if iter.previousGlyphIndex == -1 { + // still can not find glyph? + break + } + } + + prev_iter = iter + if is_flipped { + q.y0, q.y1 = q.y1, q.y0 + q.t0, q.t1 = q.t1, q.t0 + } + + // Transform corners. + TransformPoint(&c[0], &c[1], state.xform, q.x0 * invscale, q.y0 * invscale) + TransformPoint(&c[2], &c[3], state.xform, q.x1 * invscale, q.y0 * invscale) + TransformPoint(&c[4], &c[5], state.xform, q.x1 * invscale, q.y1 * invscale) + TransformPoint(&c[6], &c[7], state.xform, q.x0 * invscale, q.y1 * invscale) + + // Create triangles + if nverts + 6 <= cverts { + verts[nverts] = { c[0], c[1], q.s0, q.t0 } + verts[nverts + 1] = { c[4], c[5], q.s1, q.t1 } + verts[nverts + 2] = { c[2], c[3], q.s1, q.t0 } + verts[nverts + 3] = { c[0], c[1], q.s0, q.t0 } + verts[nverts + 4] = { c[6], c[7], q.s0, q.t1 } + verts[nverts + 5] = { c[4], c[5], q.s1, q.t1 } + nverts += 6 + } + } + + ctx.textureDirty = true + __renderText(ctx, verts[:nverts]) + + return iter.nextx / scale +} + +// Returns the vertical metrics based on the current text style. +// Measured values are returned in local coordinate space. +TextMetrics :: proc(ctx: ^Context) -> (ascender, descender, lineHeight: f32) { + state := __getState(ctx) + scale := __getFontScale(state) * ctx.devicePxRatio + invscale := f32(1.0) / scale + + if state.fontId == -1 { + return + } + + fs := &ctx.fs + fontstash.SetSize(fs, state.fontSize*scale) + fontstash.SetSpacing(fs, state.letterSpacing*scale) + fontstash.SetBlur(fs, state.fontBlur*scale) + fontstash.SetAlignHorizontal(fs, state.alignHorizontal) + fontstash.SetAlignVertical(fs, state.alignVertical) + fontstash.SetFont(fs, state.fontId) + + ascender, descender, lineHeight = fontstash.VerticalMetrics(fs) + ascender *= invscale + descender *= invscale + lineHeight *= invscale + return +} + +// Measures the specified text string. Parameter bounds should be a pointer to float[4], +// if the bounding box of the text should be returned. The bounds value are [xmin,ymin, xmax,ymax] +// Returns the horizontal advance of the measured text (i.e. where the next character should drawn). +// Measured values are returned in local coordinate space. +TextBounds :: proc( + ctx: ^Context, + x, y: f32, + input: string, + bounds: ^[4]f32 = nil, +) -> (advance: f32) { + state := __getState(ctx) + scale := __getFontScale(state) * ctx.devicePxRatio + invscale := f32(1.0) / scale + + if state.fontId == -1 { + return {} + } + + fs := &ctx.fs + fontstash.SetSize(fs, state.fontSize*scale) + fontstash.SetSpacing(fs, state.letterSpacing*scale) + fontstash.SetBlur(fs, state.fontBlur*scale) + fontstash.SetAlignHorizontal(fs, state.alignHorizontal) + fontstash.SetAlignVertical(fs, state.alignVertical) + fontstash.SetFont(fs, state.fontId) + + width := fontstash.TextBounds(fs, input, x * scale, y * scale, bounds) + + // Use line bounds for height. + one, two := fontstash.LineBounds(fs, y * scale) + + if bounds != nil { + bounds[1] = one + bounds[3] = two + bounds[0] *= invscale + bounds[1] *= invscale + bounds[2] *= invscale + bounds[3] *= invscale + } + + return width * invscale +} + +// text row with relative byte offsets into a string +Text_Row :: struct { + start: int, + end: int, + next: int, + width: f32, + minx, maxx: f32, +} + +Codepoint_Type :: enum { + Space, + Newline, + Char, + CJK, +} + +// Draws multi-line text string at specified location wrapped at the specified width. If end is specified only the sub-string up to the end is drawn. +// White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered. +// Words longer than the max width are slit at nearest character (i.e. no hyphenation). +TextBox :: proc( + ctx: ^Context, + x, y: f32, + break_row_width: f32, + input: string, +) { + state := __getState(ctx) + rows: [2]Text_Row + + if state.fontId == -1 { + return + } + + _, _, lineHeight := TextMetrics(ctx) + old_align := state.alignHorizontal + defer state.alignHorizontal = old_align + state.alignHorizontal = .LEFT + rows_mod := rows[:] + + y := y + input := input + for nrows, input_last in TextBreakLines(ctx, &input, break_row_width, &rows_mod) { + for i in 0.. (nrows: int, last: string, ok: bool) { + state := __getState(ctx) + scale := __getFontScale(state) * ctx.devicePxRatio + invscale := 1.0 / scale + + row_start_x, row_width, row_min_x, row_max_x: f32 + max_rows := len(rows) + + row_start: int = -1 + row_end: int = -1 + word_start: int = -1 + break_end: int = -1 + word_start_x, word_min_x: f32 + + break_width, break_max_x: f32 + type := Codepoint_Type.Space + ptype := Codepoint_Type.Space + pcodepoint: rune + + if max_rows == 0 || state.fontId == -1 || len(text) == 0 { + return + } + + fs := &ctx.fs + fontstash.SetSize(fs, state.fontSize * scale) + fontstash.SetSpacing(fs, state.letterSpacing * scale) + fontstash.SetBlur(fs, state.fontBlur * scale) + fontstash.SetAlignHorizontal(fs, state.alignHorizontal) + fontstash.SetAlignVertical(fs, state.alignVertical) + fontstash.SetFont(fs, state.fontId) + + break_row_width := break_row_width * scale + iter := fontstash.TextIterInit(fs, 0, 0, text^) + prev_iter := iter + q: fontstash.Quad + stopped_early: bool + + for fontstash.TextIterNext(fs, &iter, &q) { + if iter.previousGlyphIndex < 0 && __allocTextAtlas(ctx) { // can not retrieve glyph? + iter = prev_iter + fontstash.TextIterNext(fs, &iter, &q) // try again + } + prev_iter = iter + + switch iter.codepoint { + case '\t', '\v', '\f', ' ', 0x00a0: { + // NBSP + type = .Space + } + + case '\n': { + type = pcodepoint == 13 ? .Space : .Newline + } + + case '\r': { + type = pcodepoint == 10 ? .Space : .Newline + } + + case 0x0085: { + // NEL + type = .Newline + } + + case: { + if (iter.codepoint >= 0x4E00 && iter.codepoint <= 0x9FFF) || + (iter.codepoint >= 0x3000 && iter.codepoint <= 0x30FF) || + (iter.codepoint >= 0xFF00 && iter.codepoint <= 0xFFEF) || + (iter.codepoint >= 0x1100 && iter.codepoint <= 0x11FF) || + (iter.codepoint >= 0x3130 && iter.codepoint <= 0x318F) || + (iter.codepoint >= 0xAC00 && iter.codepoint <= 0xD7AF) { + type = .CJK + } else { + type = .Char + } + } + } + + if type == .Newline { + // Always handle new lines. + rows[nrows].start = row_start != -1 ? row_start : iter.str + rows[nrows].end = row_end != -1 ? row_end : iter.str + rows[nrows].width = row_width * invscale + rows[nrows].minx = row_min_x * invscale + rows[nrows].maxx = row_max_x * invscale + rows[nrows].next = iter.next + nrows += 1 + + if nrows >= max_rows { + stopped_early = true + break + } + + // Set nil break point + break_end = row_start + break_width = 0.0 + break_max_x = 0.0 + // Indicate to skip the white space at the beginning of the row. + row_start = -1 + row_end = -1 + row_width = 0 + row_min_x = 0 + row_max_x = 0 + } else { + if row_start == -1 { + // Skip white space until the beginning of the line + if type == .Char || type == .CJK { + // The current char is the row so far + row_start_x = iter.x + row_start = iter.str + row_end = iter.next + row_width = iter.nextx - row_start_x + row_min_x = q.x0 - row_start_x + row_max_x = q.x1 - row_start_x + word_start = iter.str + word_start_x = iter.x + word_min_x = q.x0 - row_start_x + // Set nil break point + break_end = row_start + break_width = 0.0 + break_max_x = 0.0 + } + } else { + next_width := iter.nextx - row_start_x + + // track last non-white space character + if type == .Char || type == .CJK { + row_end = iter.next + row_width = iter.nextx - row_start_x + row_max_x = q.x1 - row_start_x + } + // track last end of a word + if ((ptype == .Char || ptype == .CJK) && type == .Space) || type == .CJK { + break_end = iter.str + break_width = row_width + break_max_x = row_max_x + } + // track last beginning of a word + if ((ptype == .Space && (type == .Char || type == .CJK)) || type == .CJK) { + word_start = iter.str + word_start_x = iter.x + word_min_x = q.x0 + } + + // Break to new line when a character is beyond break width. + if (type == .Char || type == .CJK) && next_width > break_row_width { + // The run length is too long, need to break to new line. + if (break_end == row_start) { + // The current word is longer than the row length, just break it from here. + rows[nrows].start = row_start + rows[nrows].end = iter.str + rows[nrows].width = row_width * invscale + rows[nrows].minx = row_min_x * invscale + rows[nrows].maxx = row_max_x * invscale + rows[nrows].next = iter.str + nrows += 1 + + if nrows >= max_rows { + stopped_early = true + break + } + + row_start_x = iter.x + row_start = iter.str + row_end = iter.next + row_width = iter.nextx - row_start_x + row_min_x = q.x0 - row_start_x + row_max_x = q.x1 - row_start_x + word_start = iter.str + word_start_x = iter.x + word_min_x = q.x0 - row_start_x + } else { + // Break the line from the end of the last word, and start new line from the beginning of the new. + rows[nrows].start = row_start + rows[nrows].end = break_end + rows[nrows].width = break_width * invscale + rows[nrows].minx = row_min_x * invscale + rows[nrows].maxx = break_max_x * invscale + rows[nrows].next = word_start + nrows += 1 + if nrows >= max_rows { + stopped_early = true + break + } + // Update row + row_start_x = word_start_x + row_start = word_start + row_end = iter.next + row_width = iter.nextx - row_start_x + row_min_x = word_min_x - row_start_x + row_max_x = q.x1 - row_start_x + } + // Set nil break point + break_end = row_start + break_width = 0.0 + break_max_x = 0.0 + } + } + } + + pcodepoint = iter.codepoint + ptype = type + } + + // Break the line from the end of the last word, and start new line from the beginning of the new. + if !stopped_early && row_start != -1 { + rows[nrows].start = row_start + rows[nrows].end = row_end + rows[nrows].width = row_width * invscale + rows[nrows].minx = row_min_x * invscale + rows[nrows].maxx = row_max_x * invscale + rows[nrows].next = iter.end + nrows += 1 + } + + // NOTE a bit hacky, row.start / row.end need to work with last string range + last = text^ + // advance early + next := rows[nrows - 1].next + text^ = text[next:] + // terminate the for loop on non ok + ok = nrows != 0 + + return +} + +// Measures the specified multi-text string. Parameter bounds should be a pointer to float[4], +// if the bounding box of the text should be returned. The bounds value are [xmin,ymin, xmax,ymax] +// Measured values are returned in local coordinate space. +TextBoxBounds :: proc( + ctx: ^Context, + x, y: f32, + breakRowWidth: f32, + input: string, + bounds: ^[4]f32, +) { + state := __getState(ctx) + rows: [2]Text_Row + scale := __getFontScale(state) * ctx.devicePxRatio + invscale := f32(1.0) / scale + + if state.fontId == -1 { + if bounds != nil { + bounds^ = {} + } + + return + } + + // alignment + halign := state.alignHorizontal + valign := state.alignVertical + old_align := state.alignHorizontal + defer state.alignHorizontal = old_align + state.alignHorizontal = .LEFT + + _, _, lineh := TextMetrics(ctx) + minx, maxx := x, x + miny, maxy := y, y + + fs := &ctx.fs + fontstash.SetSize(fs, state.fontSize * scale) + fontstash.SetSpacing(fs, state.letterSpacing * scale) + fontstash.SetBlur(fs, state.fontBlur * scale) + fontstash.SetAlignHorizontal(fs, state.alignHorizontal) + fontstash.SetAlignVertical(fs, state.alignVertical) + fontstash.SetFont(fs, state.fontId) + rminy, rmaxy := fontstash.LineBounds(fs, 0) + rminy *= invscale + rmaxy *= invscale + + input := input + rows_mod := rows[:] + y := y + + for nrows, input_last in TextBreakLines(ctx, &input, breakRowWidth, &rows_mod) { + for i in 0.. int { + state := __getState(ctx) + scale := __getFontScale(state) * ctx.devicePxRatio + + if state.fontId == -1 || len(text) == 0 { + return 0 + } + + fs := &ctx.fs + fontstash.SetSize(fs, state.fontSize*scale) + fontstash.SetSpacing(fs, state.letterSpacing*scale) + fontstash.SetBlur(fs, state.fontBlur*scale) + fontstash.SetAlignHorizontal(fs, state.alignHorizontal) + fontstash.SetAlignVertical(fs, state.alignVertical) + fontstash.SetFont(fs, state.fontId) + + iter := fontstash.TextIterInit(fs, 0, 0, text) + prev_iter := iter + q: fontstash.Quad + npos: int + for fontstash.TextIterNext(fs, &iter, &q) { + if iter.previousGlyphIndex < 0 && __allocTextAtlas(ctx) { // can not retrieve glyph? + iter = prev_iter + fontstash.TextIterNext(fs, &iter, &q) // try again + } + + prev_iter = iter + positions[npos].str = iter.str + positions[npos].x = iter.x + x + positions[npos].minx = min(iter.x, q.x0) + x + positions[npos].maxx = max(iter.nextx, q.x1) + x + npos += 1 + + if npos >= len(positions) { + break + } + } + + return npos +} \ No newline at end of file From 84a7f222ffb77317b7eca4a02e85ffeb8487833e Mon Sep 17 00:00:00 2001 From: Michael Kutowski Date: Tue, 20 Dec 2022 12:27:23 +0100 Subject: [PATCH 02/97] mention fontstash and nanovg in reamde --- vendor/README.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/vendor/README.md b/vendor/README.md index e7d8d4047..bf64933c3 100644 --- a/vendor/README.md +++ b/vendor/README.md @@ -6,9 +6,9 @@ Its use is similar to that of `core:` packages, which would be available in any Presently, the `vendor:` collection comprises the following packages: -## microui +## microui (Port) -A tiny, portable, immediate-mode UI library written in Odin. (Ported from [rxi/microui](https://github.com/rxi/microui).) +A tiny, portable, immediate-mode UI library written in Odin. [rxi/microui](https://github.com/rxi/microui) This package is available under the MIT license. See `LICENSE` for more details. @@ -158,4 +158,12 @@ Includes full bindings. Used in: [bgfx](https://github.com/bkaradzic/bgfx), [Filament](https://github.com/google/filament), [gltfpack](https://github.com/zeux/meshoptimizer/tree/master/gltf), [raylib](https://github.com/raysan5/raylib), [Unigine](https://developer.unigine.com/en/docs/2.14.1/third_party?rlang=cpp#cgltf), and more! -Se also LICENCE in `cgltf` directory itself. \ No newline at end of file +Se also LICENCE in `cgltf` directory itself. + +## nanovg (Port) + +[NanoVG](https://github.com/memononen/nanovg) is a small antialiased vector graphics rendering library for OpenGL. It has lean API modeled after HTML5 canvas API. It is aimed to be a practical and fun toolset for building scalable user interfaces and visualizations. + +## fontstash (Port) + +[Font stash](https://github.com/memononen/fontstash) is a light-weight online font texture atlas builder. It uses stb_truetype to render fonts on demand to a texture atlas. From 9474c997953fc11df1de03997809e9b4ae44760d Mon Sep 17 00:00:00 2001 From: skytrias Date: Wed, 21 Dec 2022 01:36:00 +0100 Subject: [PATCH 03/97] add freeLoadedData flag in case you dont want to remove font memory (e.g. #load) --- vendor/fontstash/fontstash.odin | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/vendor/fontstash/fontstash.odin b/vendor/fontstash/fontstash.odin index ba19d068a..f527d1e9b 100644 --- a/vendor/fontstash/fontstash.odin +++ b/vendor/fontstash/fontstash.odin @@ -50,6 +50,7 @@ Font :: struct { info: stbtt.fontinfo, loadedData: []byte, + freeLoadedData: bool, // in case you dont want loadedData to be removed ascender: f32, descender: f32, @@ -147,7 +148,11 @@ Init :: proc(using ctx: ^FontContext, w, h: int, loc: QuadLocation) { Destroy :: proc(using ctx: ^FontContext) { for font in &fonts { - delete(font.loadedData) + if font.freeLoadedData { + delete(font.loadedData) + } + + delete(font.name) delete(font.glyphs) } @@ -349,7 +354,7 @@ AddFontPath :: proc( log.panicf("FONT: failed to read font at %s", path) } - return AddFontMem(ctx, name, data) + return AddFontMem(ctx, name, data, true) } // push a font to the font stack @@ -358,10 +363,12 @@ AddFontMem :: proc( ctx: ^FontContext, name: string, data: []u8, + freeLoadedData: bool, ) -> int { append(&ctx.fonts, Font {}) res := &ctx.fonts[len(ctx.fonts) - 1] res.loadedData = data + res.freeLoadedData = freeLoadedData res.name = strings.clone(name) stbtt.InitFont(&res.info, &res.loadedData[0], 0) From 2a94b66f4da048adda1710f2e29450c8767be600 Mon Sep 17 00:00:00 2001 From: Skytrias Date: Sat, 24 Dec 2022 11:19:12 +0100 Subject: [PATCH 04/97] test --- vendor/fontstash/fontstash.odin | 110 ++++++++++---------- vendor/nanovg/gl/gl.odin | 10 +- vendor/nanovg/nanovg.odin | 171 ++++++++++++++------------------ 3 files changed, 133 insertions(+), 158 deletions(-) diff --git a/vendor/fontstash/fontstash.odin b/vendor/fontstash/fontstash.odin index f527d1e9b..b4bf86d46 100644 --- a/vendor/fontstash/fontstash.odin +++ b/vendor/fontstash/fontstash.odin @@ -816,22 +816,20 @@ __getVerticalAlign :: proc( pixelSize: i16, ) -> (res: f32) { switch ctx.location { - case .TOPLEFT: { - switch av { - case .TOP: res = font.ascender * f32(pixelSize) / 10 - case .MIDDLE: res = (font.ascender + font.descender) / 2 * f32(pixelSize) / 10 - case .BASELINE: res = 0 - case .BOTTOM: res = font.descender * f32(pixelSize) / 10 - } + case .TOPLEFT: + switch av { + case .TOP: res = font.ascender * f32(pixelSize) / 10 + case .MIDDLE: res = (font.ascender + font.descender) / 2 * f32(pixelSize) / 10 + case .BASELINE: res = 0 + case .BOTTOM: res = font.descender * f32(pixelSize) / 10 } - case .BOTTOMLEFT: { - switch av { - case .TOP: res = -font.ascender * f32(pixelSize) / 10 - case .MIDDLE: res = -(font.ascender + font.descender) / 2 * f32(pixelSize) / 10 - case .BASELINE: res = 0 - case .BOTTOM: res = -font.descender * f32(pixelSize) / 10 - } + case .BOTTOMLEFT: + switch av { + case .TOP: res = -font.ascender * f32(pixelSize) / 10 + case .MIDDLE: res = -(font.ascender + font.descender) / 2 * f32(pixelSize) / 10 + case .BASELINE: res = 0 + case .BOTTOM: res = -font.descender * f32(pixelSize) / 10 } } @@ -1005,35 +1003,33 @@ __getQuad :: proc( y1 = f32(glyph.y1 - 1) switch ctx.location { - case .TOPLEFT: { - rx = math.floor(x^ + xoff) - ry = math.floor(y^ + yoff) - - quad.x0 = rx - quad.y0 = ry - quad.x1 = rx + x1 - x0 - quad.y1 = ry + y1 - y0 + case .TOPLEFT: + rx = math.floor(x^ + xoff) + ry = math.floor(y^ + yoff) + + quad.x0 = rx + quad.y0 = ry + quad.x1 = rx + x1 - x0 + quad.y1 = ry + y1 - y0 - quad.s0 = x0 * ctx.itw - quad.t0 = y0 * ctx.ith - quad.s1 = x1 * ctx.itw - quad.t1 = y1 * ctx.ith - } + quad.s0 = x0 * ctx.itw + quad.t0 = y0 * ctx.ith + quad.s1 = x1 * ctx.itw + quad.t1 = y1 * ctx.ith - case .BOTTOMLEFT: { - rx = math.floor(x^ + xoff) - ry = math.floor(y^ - yoff) + case .BOTTOMLEFT: + rx = math.floor(x^ + xoff) + ry = math.floor(y^ - yoff) - quad.x0 = rx - quad.y0 = ry - quad.x1 = rx + x1 - x0 - quad.y1 = ry - y1 + y0 + quad.x0 = rx + quad.y0 = ry + quad.x1 = rx + x1 - x0 + quad.y1 = ry - y1 + y0 - quad.s0 = x0 * ctx.itw - quad.t0 = y0 * ctx.ith - quad.s1 = x1 * ctx.itw - quad.t1 = y1 * ctx.ith - } + quad.s0 = x0 * ctx.itw + quad.t0 = y0 * ctx.ith + quad.s1 = x1 * ctx.itw + quad.t1 = y1 * ctx.ith } x^ += f32(int(f32(glyph.xadvance) / 10 + 0.5)) @@ -1056,15 +1052,15 @@ TextIterInit :: proc( x := x y := y switch state.ah { - case .LEFT: {} - case .CENTER: { - width := TextBounds(ctx, text, x, y, nil) - x = math.round(x - width * 0.5) - } - case .RIGHT: { - width := TextBounds(ctx, text, x, y, nil) - x -= width - } + case .LEFT: {} + + case .CENTER: + width := TextBounds(ctx, text, x, y, nil) + x = math.round(x - width * 0.5) + + case .RIGHT: + width := TextBounds(ctx, text, x, y, nil) + x -= width } // align vertically @@ -1184,15 +1180,15 @@ TextBounds :: proc( // horizontal alignment advance := x - start_x switch state.ah { - case .LEFT: {} - case .CENTER: { - minx -= advance * 0.5 - maxx -= advance * 0.5 - } - case .RIGHT: { - minx -= advance - maxx -= advance - } + case .LEFT: {} + + case .CENTER: + minx -= advance * 0.5 + maxx -= advance * 0.5 + + case .RIGHT: + minx -= advance + maxx -= advance } if bounds != nil { diff --git a/vendor/nanovg/gl/gl.odin b/vendor/nanovg/gl/gl.odin index 1f4d8ceb0..72b469ddb 100644 --- a/vendor/nanovg/gl/gl.odin +++ b/vendor/nanovg/gl/gl.odin @@ -1020,11 +1020,11 @@ __renderFlush :: proc(uptr: rawptr) { __blendFuncSeparate(ctx, &call.blendFunc) switch call.type { - case .NONE: {} - case .FILL: __fill(ctx, call) - case .CONVEX_FILL: __convexFill(ctx, call) - case .STROKE: __stroke(ctx, call) - case .TRIANGLES: __triangles(ctx, call) + case .NONE: {} + case .FILL: __fill(ctx, call) + case .CONVEX_FILL: __convexFill(ctx, call) + case .STROKE: __stroke(ctx, call) + case .TRIANGLES: __triangles(ctx, call) } } diff --git a/vendor/nanovg/nanovg.odin b/vendor/nanovg/nanovg.odin index db99ef5a4..8beb08cce 100644 --- a/vendor/nanovg/nanovg.odin +++ b/vendor/nanovg/nanovg.odin @@ -1302,30 +1302,21 @@ __appendCommands :: proc(ctx: ^Context, values: []f32) { cmd := Commands(values[i]) switch cmd { - case .MOVE_TO, .LINE_TO: { - TransformPoint(&values[i + 1], &values[i + 2], state.xform, values[i + 1], values[i + 2]) - i += 3 - } + case .MOVE_TO, .LINE_TO: + TransformPoint(&values[i + 1], &values[i + 2], state.xform, values[i + 1], values[i + 2]) + i += 3 - case .BEZIER_TO: { - TransformPoint(&values[i + 1], &values[i + 2], state.xform, values[i + 1], values[i + 2]) - TransformPoint(&values[i + 3], &values[i + 4], state.xform, values[i + 3], values[i + 4]) - TransformPoint(&values[i + 5], &values[i + 6], state.xform, values[i + 5], values[i + 6]) - i += 7 - } + case .BEZIER_TO: + TransformPoint(&values[i + 1], &values[i + 2], state.xform, values[i + 1], values[i + 2]) + TransformPoint(&values[i + 3], &values[i + 4], state.xform, values[i + 3], values[i + 4]) + TransformPoint(&values[i + 5], &values[i + 6], state.xform, values[i + 5], values[i + 6]) + i += 7 - case .CLOSE: { - i += 1 - } + case .CLOSE: i += 1 + case .WINDING: i += 2 - case .WINDING: { - i += 2 - } - - // default - case: { - i += 1 - } + // default + case: i += 1 } } @@ -1510,45 +1501,38 @@ __flattenPaths :: proc(ctx: ^Context) { cmd := Commands(ctx.commands[i]) switch cmd { - case .MOVE_TO: { - __addPath(ctx) - p := ctx.commands[i + 1:] - __addPoint(ctx, p[0], p[1], { .CORNER }) - i += 3 + case .MOVE_TO: + __addPath(ctx) + p := ctx.commands[i + 1:] + __addPoint(ctx, p[0], p[1], { .CORNER }) + i += 3 + + case .LINE_TO: + p := ctx.commands[i + 1:] + __addPoint(ctx, p[0], p[1], { .CORNER }) + i += 3 + + case .BEZIER_TO: + last := __lastPoint(ctx) + + if last != nil { + cp1 := ctx.commands[i + 1:] + cp2 := ctx.commands[i + 3:] + p := ctx.commands[i + 5:] + __tesselateBezier(ctx, last.x,last.y, cp1[0],cp1[1], cp2[0],cp2[1], p[0],p[1], 0, { .CORNER }) } - case .LINE_TO: { - p := ctx.commands[i + 1:] - __addPoint(ctx, p[0], p[1], { .CORNER }) - i += 3 - } + i += 7 - case .BEZIER_TO: { - last := __lastPoint(ctx) - - if last != nil { - cp1 := ctx.commands[i + 1:] - cp2 := ctx.commands[i + 3:] - p := ctx.commands[i + 5:] - __tesselateBezier(ctx, last.x,last.y, cp1[0],cp1[1], cp2[0],cp2[1], p[0],p[1], 0, { .CORNER }) - } + case .CLOSE: + __closePath(ctx) + i += 1 - i += 7 - } + case .WINDING: + __pathWinding(ctx, Winding(ctx.commands[i + 1])) + i += 2 - case .CLOSE: { - __closePath(ctx) - i += 1 - } - - case .WINDING: { - __pathWinding(ctx, Winding(ctx.commands[i + 1])) - i += 2 - } - - case: { - i += 1 - } + case: i += 1 } } @@ -2520,7 +2504,7 @@ Ellipse :: proc(ctx: ^Context, cx, cy, rx, ry: f32) { __cmdf(.BEZIER_TO), cx+rx*KAPPA, cy+ry, cx+rx, cy+ry*KAPPA, cx+rx, cy, __cmdf(.BEZIER_TO), cx+rx, cy-ry*KAPPA, cx+rx*KAPPA, cy-ry, cx, cy-ry, __cmdf(.BEZIER_TO), cx-rx*KAPPA, cy-ry, cx-rx, cy-ry*KAPPA, cx-rx, cy, - __cmdf(.CLOSE) + __cmdf(.CLOSE), } __appendCommands(ctx, values[:]) } @@ -2674,8 +2658,8 @@ CreateFont :: proc(ctx: ^Context, name, filename: string) -> int { // Creates font by loading it from the specified memory chunk. // Returns handle to the font. -CreateFontMem :: proc(ctx: ^Context, name: string, slice: []byte) -> int { - return fontstash.AddFontMem(&ctx.fs, name, slice) +CreateFontMem :: proc(ctx: ^Context, name: string, slice: []byte, free_loaded_data: bool) -> int { + return fontstash.AddFontMem(&ctx.fs, name, slice, free_loaded_data) } // Finds a loaded font of specified name, and returns handle to it, or -1 if the font is not found. @@ -2890,15 +2874,15 @@ TextIcon :: proc(ctx: ^Context, x, y: f32, codepoint: rune) -> f32 { x := x * scale y := y * scale switch fstate.ah { - case .LEFT: {} - case .CENTER: { - width := fontstash.CodepointWidth(font, codepoint, fscale) - x = math.round(x - width * 0.5) - } - case .RIGHT: { - width := fontstash.CodepointWidth(font, codepoint, fscale) - x -= width - } + case .LEFT: {} + + case .CENTER: + width := fontstash.CodepointWidth(font, codepoint, fscale) + x = math.round(x - width * 0.5) + + case .RIGHT: + width := fontstash.CodepointWidth(font, codepoint, fscale) + x -= width } // align vertically @@ -3190,35 +3174,30 @@ TextBreakLines :: proc( prev_iter = iter switch iter.codepoint { - case '\t', '\v', '\f', ' ', 0x00a0: { - // NBSP - type = .Space - } + case '\t', '\v', '\f', ' ', 0x00a0: + // NBSP + type = .Space - case '\n': { - type = pcodepoint == 13 ? .Space : .Newline - } - - case '\r': { - type = pcodepoint == 10 ? .Space : .Newline - } + case '\n': + type = pcodepoint == 13 ? .Space : .Newline + + case '\r': + type = pcodepoint == 10 ? .Space : .Newline - case 0x0085: { - // NEL - type = .Newline - } + case 0x0085: + // NEL + type = .Newline - case: { - if (iter.codepoint >= 0x4E00 && iter.codepoint <= 0x9FFF) || - (iter.codepoint >= 0x3000 && iter.codepoint <= 0x30FF) || - (iter.codepoint >= 0xFF00 && iter.codepoint <= 0xFFEF) || - (iter.codepoint >= 0x1100 && iter.codepoint <= 0x11FF) || - (iter.codepoint >= 0x3130 && iter.codepoint <= 0x318F) || - (iter.codepoint >= 0xAC00 && iter.codepoint <= 0xD7AF) { - type = .CJK - } else { - type = .Char - } + case: + if (iter.codepoint >= 0x4E00 && iter.codepoint <= 0x9FFF) || + (iter.codepoint >= 0x3000 && iter.codepoint <= 0x30FF) || + (iter.codepoint >= 0xFF00 && iter.codepoint <= 0xFFEF) || + (iter.codepoint >= 0x1100 && iter.codepoint <= 0x11FF) || + (iter.codepoint >= 0x3130 && iter.codepoint <= 0x318F) || + (iter.codepoint >= 0xAC00 && iter.codepoint <= 0xD7AF) { + type = .CJK + } else { + type = .Char } } @@ -3426,9 +3405,9 @@ TextBoxBounds :: proc( // Horizontal bounds switch halign { - case .LEFT: dx = 0 - case .CENTER: dx = breakRowWidth*0.5 - row.width*0.5 - case .RIGHT: dx = breakRowWidth - row.width + case .LEFT: dx = 0 + case .CENTER: dx = breakRowWidth*0.5 - row.width*0.5 + case .RIGHT: dx = breakRowWidth - row.width } rminx = x + row.minx + dx From 86fada718ebd8a11f3560f7b9585d74c8840f66f Mon Sep 17 00:00:00 2001 From: Skytrias Date: Sat, 24 Dec 2022 11:30:15 +0100 Subject: [PATCH 05/97] optional constants, starting GL2 fixes --- vendor/nanovg/gl/gl.odin | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/vendor/nanovg/gl/gl.odin b/vendor/nanovg/gl/gl.odin index 72b469ddb..9645c1696 100644 --- a/vendor/nanovg/gl/gl.odin +++ b/vendor/nanovg/gl/gl.odin @@ -26,9 +26,7 @@ CreateFlag :: enum { } CreateFlags :: bit_set[CreateFlag] -FRAG_BINDING :: 0 -USE_STATE_FILTER :: true -GL_UNIFORMARRAY_SIZE :: 11 +USE_STATE_FILTER :: #config(USE_STATE_FILTER, true) UniformLoc :: enum { VIEW_SIZE, @@ -92,6 +90,7 @@ Path :: struct { } when GL2_IMPLEMENTATION { + GL_UNIFORMARRAY_SIZE :: 11 FragUniforms :: struct #raw_union { using _: struct { scissorMat: [12]f32, // matrices are actually 3 vec4s @@ -128,10 +127,11 @@ when GL2_IMPLEMENTATION { } } -GL2_IMPLEMENTATION :: false -GL3_IMPLEMENTATION :: true -GLES2_IMPLEMENTATION :: false -GLES3_IMPLEMENTATION :: false +DEFAULT_IMPLEMENTATION_STRING :: #config(NANOVG_GL_IMPL, "GL3") +GL2_IMPLEMENTATION :: DEFAULT_IMPLEMENTATION_STRING == "GL2" +GL3_IMPLEMENTATION :: DEFAULT_IMPLEMENTATION_STRING == "GL3" +GLES2_IMPLEMENTATION :: DEFAULT_IMPLEMENTATION_STRING == "GLES2" +GLES3_IMPLEMENTATION :: DEFAULT_IMPLEMENTATION_STRING == "GLES3" when GL2_IMPLEMENTATION { GL2 :: true @@ -174,6 +174,7 @@ Context :: struct { fragBuf: u32, // USE_UNIFORMBUFFER fragSize: int, flags: CreateFlags, + frag_binding: u32, // Per frame buffers calls: [dynamic]Call, @@ -372,7 +373,7 @@ __renderCreate :: proc(uptr: rawptr) -> bool { when GL_USE_UNIFORMBUFFER { // Create UBOs - gl.UniformBlockBinding(ctx.shader.prog, u32(ctx.shader.loc[.FRAG]), FRAG_BINDING) + gl.UniformBlockBinding(ctx.shader.prog, u32(ctx.shader.loc[.FRAG]), ctx.frag_binding) gl.GenBuffers(1, &ctx.fragBuf) gl.GetIntegerv(gl.UNIFORM_BUFFER_OFFSET_ALIGNMENT, &align) } @@ -436,7 +437,7 @@ __renderCreateTexture :: proc( when GL2 { if .GENERATE_MIPMAPS in imageFlags { - gl.TexParameteri(gl.TEXTURE_2D, GENERATE_MIPMAP, 1) + gl.TexParameteri(gl.TEXTURE_2D, gl.GENERATE_MIPMAP, 1) } } @@ -444,8 +445,7 @@ __renderCreateTexture :: proc( gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, i32(w), i32(h), 0, gl.RGBA, gl.UNSIGNED_BYTE, raw_data(data)) } else { when GLES2 || GL2 { - // TODO missing in odin - gl.TexImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, w, h, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, data) + gl.TexImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, i32(w), i32(h), 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, raw_data(data)) } else when GLES3 { gl.TexImage2D(gl.TEXTURE_2D, 0, gl.R8, i32(w), i32(h), 0, gl.RED, gl.UNSIGNED_BYTE, raw_data(data)) } else { @@ -783,7 +783,7 @@ __convertPaint :: proc( __setUniforms :: proc(ctx: ^Context, uniformOffset: int, image: int) { when GL_USE_UNIFORMBUFFER { - gl.BindBufferRange(gl.UNIFORM_BUFFER, FRAG_BINDING, ctx.fragBuf, uniformOffset, size_of(FragUniforms)) + gl.BindBufferRange(gl.UNIFORM_BUFFER, ctx.frag_binding, ctx.fragBuf, uniformOffset, size_of(FragUniforms)) } else { frag := __fragUniformPtr(ctx, uniformOffset) gl.Uniform4fv(ctx.shader.loc[.FRAG], GL_UNIFORMARRAY_SIZE, cast(^f32) frag) From f36e19e86fe88198fc1d17426afea577920efbf8 Mon Sep 17 00:00:00 2001 From: Skytrias Date: Sat, 24 Dec 2022 11:47:57 +0100 Subject: [PATCH 06/97] fix GL2 --- vendor/nanovg/gl/gl.odin | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vendor/nanovg/gl/gl.odin b/vendor/nanovg/gl/gl.odin index 9645c1696..698943e2b 100644 --- a/vendor/nanovg/gl/gl.odin +++ b/vendor/nanovg/gl/gl.odin @@ -89,8 +89,9 @@ Path :: struct { strokeCount: int, } +GL_UNIFORMARRAY_SIZE :: 11 + when GL2_IMPLEMENTATION { - GL_UNIFORMARRAY_SIZE :: 11 FragUniforms :: struct #raw_union { using _: struct { scissorMat: [12]f32, // matrices are actually 3 vec4s @@ -415,7 +416,7 @@ __renderCreateTexture :: proc( // No mips. if .GENERATE_MIPMAPS in imageFlags { log.errorf("Mip-maps is not support for non power-of-two textures (%d x %d)\n", w, h); - excl(&imageFlags, nvg.Image_Flag.GENERATE_MIPMAPS) + excl(&imageFlags, ImageFlags { .GENERATE_MIPMAPS }) } } } @@ -643,8 +644,7 @@ __renderUpdateTexture :: proc( gl.TexSubImage2D(gl.TEXTURE_2D, 0, i32(x), i32(y), i32(w), i32(h), gl.RGBA, gl.UNSIGNED_BYTE, raw_data(data)) } else { when GLES2 || GL2 { - // TODO is missing in odin - // gl.TexSubImage2D(gl.TEXTURE_2D, 0, i32(x), i32(y), i32(w), i32(h), gl.LUMINANCE, gl.UNSIGNED_BYTE, raw_data(data)) + gl.TexSubImage2D(gl.TEXTURE_2D, 0, i32(x), i32(y), i32(w), i32(h), gl.LUMINANCE, gl.UNSIGNED_BYTE, raw_data(data)) } else { gl.TexSubImage2D(gl.TEXTURE_2D, 0, i32(x), i32(y), i32(w), i32(h), gl.RED, gl.UNSIGNED_BYTE, raw_data(data)) } From dcf4e51787119d8dcf86ca0195d3a07aea6d47a8 Mon Sep 17 00:00:00 2001 From: hikari Date: Wed, 7 Jun 2023 19:11:16 +0300 Subject: [PATCH 07/97] [core:thread] Added `self_cleanup` flag to properly auto-clean threads --- core/thread/thread.odin | 128 ++++-------------- core/thread/thread_unix.odin | 5 + core/thread/thread_windows.odin | 6 + tests/documentation/documentation_tester.odin | 4 +- 4 files changed, 41 insertions(+), 102 deletions(-) diff --git a/core/thread/thread.odin b/core/thread/thread.odin index fe502c5ae..c84317055 100644 --- a/core/thread/thread.odin +++ b/core/thread/thread.odin @@ -38,7 +38,7 @@ Thread :: struct { IMPORTANT: By default, the thread proc will get the same context as `main()` gets. - In this sitation, the thread will get a new temporary allocator which will be cleaned up when the thread dies. + In this situation, the thread will get a new temporary allocator which will be cleaned up when the thread dies. ***This does NOT happen when you set `init_context`.*** This means that if you set `init_context`, but still have the `temp_allocator` field set to the default temp allocator, then you'll need to call `runtime.default_temp_allocator_destroy(auto_cast the_thread.init_context.temp_allocator.data)` manually, @@ -47,6 +47,8 @@ Thread :: struct { */ init_context: Maybe(runtime.Context), + // Indicates whether the thread will free itself when it completes + self_cleanup: bool, creation_allocator: mem.Allocator, } @@ -101,125 +103,46 @@ yield :: proc() { run :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) { - thread_proc :: proc(t: ^Thread) { - fn := cast(proc())t.data - fn() - destroy(t) - } - t := create(thread_proc, priority) - t.data = rawptr(fn) - t.init_context = init_context - start(t) + create_and_start(fn, init_context, priority, true) } run_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) { - thread_proc :: proc(t: ^Thread) { - fn := cast(proc(rawptr))t.data - assert(t.user_index >= 1) - data := t.user_args[0] - fn(data) - destroy(t) - } - t := create(thread_proc, priority) - t.data = rawptr(fn) - t.user_index = 1 - t.user_args = data - t.init_context = init_context - start(t) + create_and_start_with_data(data, fn, init_context, priority, true) } run_with_poly_data :: proc(data: $T, fn: proc(data: T), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) where size_of(T) <= size_of(rawptr) { - thread_proc :: proc(t: ^Thread) { - fn := cast(proc(T))t.data - assert(t.user_index >= 1) - data := (^T)(&t.user_args[0])^ - fn(data) - destroy(t) - } - t := create(thread_proc, priority) - t.data = rawptr(fn) - t.user_index = 1 - data := data - mem.copy(&t.user_args[0], &data, size_of(data)) - t.init_context = init_context - start(t) + create_and_start_with_poly_data(data, fn, init_context, priority, true) } run_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) where size_of(T1) <= size_of(rawptr), size_of(T2) <= size_of(rawptr) { - thread_proc :: proc(t: ^Thread) { - fn := cast(proc(T1, T2))t.data - assert(t.user_index >= 2) - arg1 := (^T1)(&t.user_args[0])^ - arg2 := (^T2)(&t.user_args[1])^ - fn(arg1, arg2) - destroy(t) - } - t := create(thread_proc, priority) - t.data = rawptr(fn) - t.user_index = 2 - arg1, arg2 := arg1, arg2 - mem.copy(&t.user_args[0], &arg1, size_of(arg1)) - mem.copy(&t.user_args[1], &arg2, size_of(arg2)) - t.init_context = init_context - start(t) + create_and_start_with_poly_data2(arg1, arg2, fn, init_context, priority, true) } run_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: proc(arg1: T1, arg2: T2, arg3: T3), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) where size_of(T1) <= size_of(rawptr), size_of(T2) <= size_of(rawptr), size_of(T3) <= size_of(rawptr) { - thread_proc :: proc(t: ^Thread) { - fn := cast(proc(T1, T2, T3))t.data - assert(t.user_index >= 3) - arg1 := (^T1)(&t.user_args[0])^ - arg2 := (^T2)(&t.user_args[1])^ - arg3 := (^T3)(&t.user_args[2])^ - fn(arg1, arg2, arg3) - destroy(t) - } - t := create(thread_proc, priority) - t.data = rawptr(fn) - t.user_index = 3 - arg1, arg2, arg3 := arg1, arg2, arg3 - mem.copy(&t.user_args[0], &arg1, size_of(arg1)) - mem.copy(&t.user_args[1], &arg2, size_of(arg2)) - mem.copy(&t.user_args[2], &arg3, size_of(arg3)) - t.init_context = init_context - start(t) + create_and_start_with_poly_data3(arg1, arg2, arg3, fn, init_context, priority, true) } run_with_poly_data4 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, arg4: $T4, fn: proc(arg1: T1, arg2: T2, arg3: T3, arg4: T4), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) where size_of(T1) <= size_of(rawptr), size_of(T2) <= size_of(rawptr), size_of(T3) <= size_of(rawptr) { - thread_proc :: proc(t: ^Thread) { - fn := cast(proc(T1, T2, T3, T4))t.data - assert(t.user_index >= 4) - arg1 := (^T1)(&t.user_args[0])^ - arg2 := (^T2)(&t.user_args[1])^ - arg3 := (^T3)(&t.user_args[2])^ - arg4 := (^T4)(&t.user_args[3])^ - fn(arg1, arg2, arg3, arg4) - destroy(t) - } - t := create(thread_proc, priority) - t.data = rawptr(fn) - t.user_index = 4 - arg1, arg2, arg3, arg4 := arg1, arg2, arg3, arg4 - mem.copy(&t.user_args[0], &arg1, size_of(arg1)) - mem.copy(&t.user_args[1], &arg2, size_of(arg2)) - mem.copy(&t.user_args[2], &arg3, size_of(arg3)) - mem.copy(&t.user_args[3], &arg4, size_of(arg4)) - t.init_context = init_context - start(t) + create_and_start_with_poly_data4(arg1, arg2, arg3, arg4, fn, init_context, priority, true) } - -create_and_start :: proc(fn: Thread_Proc, init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) -> ^Thread { - t := create(fn, priority) +create_and_start :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread { + thread_proc :: proc(t: ^Thread) { + fn := cast(proc())t.data + fn() + } + t := create(thread_proc, priority) + t.data = rawptr(fn) + t.self_cleanup = self_cleanup t.init_context = init_context start(t) return t @@ -228,7 +151,7 @@ create_and_start :: proc(fn: Thread_Proc, init_context: Maybe(runtime.Context) = -create_and_start_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) -> ^Thread { +create_and_start_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread { thread_proc :: proc(t: ^Thread) { fn := cast(proc(rawptr))t.data assert(t.user_index >= 1) @@ -239,12 +162,13 @@ create_and_start_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_co t.data = rawptr(fn) t.user_index = 1 t.user_args = data + t.self_cleanup = self_cleanup t.init_context = init_context start(t) return t } -create_and_start_with_poly_data :: proc(data: $T, fn: proc(data: T), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) -> ^Thread +create_and_start_with_poly_data :: proc(data: $T, fn: proc(data: T), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread where size_of(T) <= size_of(rawptr) { thread_proc :: proc(t: ^Thread) { fn := cast(proc(T))t.data @@ -257,12 +181,13 @@ create_and_start_with_poly_data :: proc(data: $T, fn: proc(data: T), init_contex t.user_index = 1 data := data mem.copy(&t.user_args[0], &data, size_of(data)) + t.self_cleanup = self_cleanup t.init_context = init_context start(t) return t } -create_and_start_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) -> ^Thread +create_and_start_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread where size_of(T1) <= size_of(rawptr), size_of(T2) <= size_of(rawptr) { thread_proc :: proc(t: ^Thread) { @@ -278,12 +203,13 @@ create_and_start_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), arg1, arg2 := arg1, arg2 mem.copy(&t.user_args[0], &arg1, size_of(arg1)) mem.copy(&t.user_args[1], &arg2, size_of(arg2)) + t.self_cleanup = self_cleanup t.init_context = init_context start(t) return t } -create_and_start_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: proc(arg1: T1, arg2: T2, arg3: T3), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) -> ^Thread +create_and_start_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: proc(arg1: T1, arg2: T2, arg3: T3), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread where size_of(T1) <= size_of(rawptr), size_of(T2) <= size_of(rawptr), size_of(T3) <= size_of(rawptr) { @@ -302,11 +228,12 @@ create_and_start_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: pr mem.copy(&t.user_args[0], &arg1, size_of(arg1)) mem.copy(&t.user_args[1], &arg2, size_of(arg2)) mem.copy(&t.user_args[2], &arg3, size_of(arg3)) + t.self_cleanup = self_cleanup t.init_context = init_context start(t) return t } -create_and_start_with_poly_data4 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, arg4: $T4, fn: proc(arg1: T1, arg2: T2, arg3: T3, arg4: T4), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) -> ^Thread +create_and_start_with_poly_data4 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, arg4: $T4, fn: proc(arg1: T1, arg2: T2, arg3: T3, arg4: T4), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread where size_of(T1) <= size_of(rawptr), size_of(T2) <= size_of(rawptr), size_of(T3) <= size_of(rawptr) { @@ -327,6 +254,7 @@ create_and_start_with_poly_data4 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, arg4: mem.copy(&t.user_args[1], &arg2, size_of(arg2)) mem.copy(&t.user_args[2], &arg3, size_of(arg3)) mem.copy(&t.user_args[3], &arg4, size_of(arg4)) + t.self_cleanup = self_cleanup t.init_context = init_context start(t) return t @@ -360,4 +288,4 @@ _maybe_destroy_default_temp_allocator :: proc(init_context: Maybe(runtime.Contex if context.temp_allocator.procedure == runtime.default_temp_allocator_proc { runtime.default_temp_allocator_destroy(auto_cast context.temp_allocator.data) } -} \ No newline at end of file +} diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index 45d2bca2e..eac971dd7 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -67,6 +67,11 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { sync.unlock(&t.mutex) + if t.self_cleanup { + t.unix_thread = {} + free(t, t.creation_allocator) + } + return nil } diff --git a/core/thread/thread_windows.odin b/core/thread/thread_windows.odin index e62071a1f..eace9a926 100644 --- a/core/thread/thread_windows.odin +++ b/core/thread/thread_windows.odin @@ -47,6 +47,12 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { intrinsics.atomic_store(&t.flags, t.flags + {.Done}) + if t.self_cleanup { + win32.CloseHandle(t.win32_thread) + t.win32_thread = win32.INVALID_HANDLE + free(t, t.creation_allocator) + } + return 0 } diff --git a/tests/documentation/documentation_tester.odin b/tests/documentation/documentation_tester.odin index 086060000..a4d18d1eb 100644 --- a/tests/documentation/documentation_tester.odin +++ b/tests/documentation/documentation_tester.odin @@ -290,7 +290,7 @@ _bad_test_found: bool @(private="file") _spawn_pipe_reader :: proc() { - thread.create_and_start(proc(^thread.Thread) { + thread.run(proc() { stream := os.stream_from_handle(_read_pipe) reader := io.to_reader(stream) sync.post(&_pipe_reader_semaphore) // notify thread is ready @@ -467,4 +467,4 @@ main :: proc() { run_test_suite :: proc() -> bool { return libc.system(fmt.caprintf("%v run verify", g_path_to_odin)) == 0 -} \ No newline at end of file +} From 7b62b81ebd731a03db919f1df24aee4d37bb2c1d Mon Sep 17 00:00:00 2001 From: hikari Date: Wed, 7 Jun 2023 20:03:19 +0300 Subject: [PATCH 08/97] [core:thread] Fix compilation --- core/thread/thread_unix.odin | 2 ++ core/thread/thread_windows.odin | 2 ++ 2 files changed, 4 insertions(+) diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index eac971dd7..64f2816e0 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -69,6 +69,8 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { if t.self_cleanup { t.unix_thread = {} + // NOTE(ftphikari): It doesn't matter which context 'free' received, right? + context = {} free(t, t.creation_allocator) } diff --git a/core/thread/thread_windows.odin b/core/thread/thread_windows.odin index eace9a926..7a7c8b4a2 100644 --- a/core/thread/thread_windows.odin +++ b/core/thread/thread_windows.odin @@ -50,6 +50,8 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { if t.self_cleanup { win32.CloseHandle(t.win32_thread) t.win32_thread = win32.INVALID_HANDLE + // NOTE(ftphikari): It doesn't matter which context 'free' received, right? + context = {} free(t, t.creation_allocator) } From 3b8515beb0aebc08043d040aa01712f37ef51aac Mon Sep 17 00:00:00 2001 From: hikari Date: Wed, 7 Jun 2023 20:52:41 +0300 Subject: [PATCH 09/97] [core:thread] Seeing if this fixes network tests --- core/thread/thread.odin | 23 ++++++++++++++--------- core/thread/thread_js.odin | 10 +--------- core/thread/thread_unix.odin | 9 +-------- core/thread/thread_windows.odin | 9 +-------- 4 files changed, 17 insertions(+), 34 deletions(-) diff --git a/core/thread/thread.odin b/core/thread/thread.odin index c84317055..fd8e59a5d 100644 --- a/core/thread/thread.odin +++ b/core/thread/thread.odin @@ -10,8 +10,16 @@ Thread_Proc :: #type proc(^Thread) MAX_USER_ARGUMENTS :: 8 +Thread_State :: enum u8 { + Started, + Joined, + Done, + Self_Cleanup, +} + Thread :: struct { using specific: Thread_Os_Specific, + flags: bit_set[Thread_State; u8], id: int, procedure: Thread_Proc, @@ -47,9 +55,6 @@ Thread :: struct { */ init_context: Maybe(runtime.Context), - // Indicates whether the thread will free itself when it completes - self_cleanup: bool, - creation_allocator: mem.Allocator, } @@ -142,7 +147,7 @@ create_and_start :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, } t := create(thread_proc, priority) t.data = rawptr(fn) - t.self_cleanup = self_cleanup + if self_cleanup do t.flags += {.Self_Cleanup} t.init_context = init_context start(t) return t @@ -162,7 +167,7 @@ create_and_start_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_co t.data = rawptr(fn) t.user_index = 1 t.user_args = data - t.self_cleanup = self_cleanup + if self_cleanup do t.flags += {.Self_Cleanup} t.init_context = init_context start(t) return t @@ -181,7 +186,7 @@ create_and_start_with_poly_data :: proc(data: $T, fn: proc(data: T), init_contex t.user_index = 1 data := data mem.copy(&t.user_args[0], &data, size_of(data)) - t.self_cleanup = self_cleanup + if self_cleanup do t.flags += {.Self_Cleanup} t.init_context = init_context start(t) return t @@ -203,7 +208,7 @@ create_and_start_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), arg1, arg2 := arg1, arg2 mem.copy(&t.user_args[0], &arg1, size_of(arg1)) mem.copy(&t.user_args[1], &arg2, size_of(arg2)) - t.self_cleanup = self_cleanup + if self_cleanup do t.flags += {.Self_Cleanup} t.init_context = init_context start(t) return t @@ -228,7 +233,7 @@ create_and_start_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: pr mem.copy(&t.user_args[0], &arg1, size_of(arg1)) mem.copy(&t.user_args[1], &arg2, size_of(arg2)) mem.copy(&t.user_args[2], &arg3, size_of(arg3)) - t.self_cleanup = self_cleanup + if self_cleanup do t.flags += {.Self_Cleanup} t.init_context = init_context start(t) return t @@ -254,7 +259,7 @@ create_and_start_with_poly_data4 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, arg4: mem.copy(&t.user_args[1], &arg2, size_of(arg2)) mem.copy(&t.user_args[2], &arg3, size_of(arg3)) mem.copy(&t.user_args[3], &arg4, size_of(arg4)) - t.self_cleanup = self_cleanup + if self_cleanup do t.flags += {.Self_Cleanup} t.init_context = init_context start(t) return t diff --git a/core/thread/thread_js.odin b/core/thread/thread_js.odin index 5821ab238..3c4935495 100644 --- a/core/thread/thread_js.odin +++ b/core/thread/thread_js.odin @@ -5,15 +5,7 @@ import "core:intrinsics" import "core:sync" import "core:mem" -Thread_State :: enum u8 { - Started, - Joined, - Done, -} - -Thread_Os_Specific :: struct { - flags: bit_set[Thread_State; u8], -} +Thread_Os_Specific :: struct {} _thread_priority_map := [Thread_Priority]i32{ .Normal = 0, diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index 64f2816e0..6e734a03a 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -8,19 +8,12 @@ import "core:sys/unix" CAS :: intrinsics.atomic_compare_exchange_strong -Thread_State :: enum u8 { - Started, - Joined, - Done, -} - // NOTE(tetra): Aligned here because of core/unix/pthread_linux.odin/pthread_t. // Also see core/sys/darwin/mach_darwin.odin/semaphore_t. Thread_Os_Specific :: struct #align 16 { unix_thread: unix.pthread_t, // NOTE: very large on Darwin, small on Linux. cond: sync.Cond, mutex: sync.Mutex, - flags: bit_set[Thread_State; u8], } // // Creates a thread which will run the given procedure. @@ -67,7 +60,7 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { sync.unlock(&t.mutex) - if t.self_cleanup { + if .Self_Cleanup in t.flags { t.unix_thread = {} // NOTE(ftphikari): It doesn't matter which context 'free' received, right? context = {} diff --git a/core/thread/thread_windows.odin b/core/thread/thread_windows.odin index 7a7c8b4a2..0d004c8c3 100644 --- a/core/thread/thread_windows.odin +++ b/core/thread/thread_windows.odin @@ -6,17 +6,10 @@ import "core:intrinsics" import "core:sync" import win32 "core:sys/windows" -Thread_State :: enum u8 { - Started, - Joined, - Done, -} - Thread_Os_Specific :: struct { win32_thread: win32.HANDLE, win32_thread_id: win32.DWORD, mutex: sync.Mutex, - flags: bit_set[Thread_State; u8], } _thread_priority_map := [Thread_Priority]i32{ @@ -47,7 +40,7 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { intrinsics.atomic_store(&t.flags, t.flags + {.Done}) - if t.self_cleanup { + if .Self_Cleanup in t.flags { win32.CloseHandle(t.win32_thread) t.win32_thread = win32.INVALID_HANDLE // NOTE(ftphikari): It doesn't matter which context 'free' received, right? From 3f6775e29b2378d189cca72733cf8953681281e4 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 8 Jun 2023 16:34:36 +0100 Subject: [PATCH 10/97] Update to new io interface --- core/bufio/read_writer.odin | 66 ++-- core/bufio/reader.odin | 58 +--- core/bufio/writer.odin | 59 ++-- core/bytes/buffer.odin | 82 ++--- core/bytes/reader.odin | 59 ++-- core/compress/common.odin | 5 +- core/crypto/blake/blake.odin | 8 +- core/crypto/blake2b/blake2b.odin | 2 +- core/crypto/blake2s/blake2s.odin | 2 +- core/crypto/gost/gost.odin | 2 +- core/crypto/groestl/groestl.odin | 8 +- core/crypto/haval/haval.odin | 30 +- core/crypto/jh/jh.odin | 8 +- core/crypto/keccak/keccak.odin | 8 +- core/crypto/md2/md2.odin | 2 +- core/crypto/md4/md4.odin | 2 +- core/crypto/md5/md5.odin | 2 +- core/crypto/ripemd/ripemd.odin | 8 +- core/crypto/sha1/sha1.odin | 2 +- core/crypto/sha2/sha2.odin | 8 +- core/crypto/sha3/sha3.odin | 8 +- core/crypto/shake/shake.odin | 4 +- core/crypto/sm3/sm3.odin | 2 +- core/crypto/streebog/streebog.odin | 4 +- core/crypto/tiger/tiger.odin | 6 +- core/crypto/tiger2/tiger2.odin | 6 +- core/crypto/whirlpool/whirlpool.odin | 2 +- core/io/conv.odin | 74 +---- core/io/io.odin | 419 +++++++++----------------- core/io/multi.odin | 88 +++--- core/io/util.odin | 79 ++--- core/os/stream.odin | 90 +++--- core/strings/builder.odin | 41 +-- core/strings/reader.odin | 58 ++-- vendor/botan/blake2b/blake2b.odin | 2 +- vendor/botan/gost/gost.odin | 2 +- vendor/botan/keccak/keccak.odin | 2 +- vendor/botan/md4/md4.odin | 2 +- vendor/botan/md5/md5.odin | 2 +- vendor/botan/ripemd/ripemd.odin | 2 +- vendor/botan/sha1/sha1.odin | 2 +- vendor/botan/sha2/sha2.odin | 8 +- vendor/botan/sha3/sha3.odin | 8 +- vendor/botan/shake/shake.odin | 4 +- vendor/botan/skein512/skein512.odin | 6 +- vendor/botan/sm3/sm3.odin | 2 +- vendor/botan/streebog/streebog.odin | 4 +- vendor/botan/tiger/tiger.odin | 6 +- vendor/botan/whirlpool/whirlpool.odin | 2 +- 49 files changed, 504 insertions(+), 852 deletions(-) diff --git a/core/bufio/read_writer.odin b/core/bufio/read_writer.odin index f9ae1ed45..3e6bd3aa0 100644 --- a/core/bufio/read_writer.odin +++ b/core/bufio/read_writer.odin @@ -14,51 +14,29 @@ read_writer_init :: proc(rw: ^Read_Writer, r: ^Reader, w: ^Writer) { } read_writer_to_stream :: proc(rw: ^Read_Writer) -> (s: io.Stream) { - s.stream_data = rw - s.stream_vtable = &_read_writer_vtable + s.procedure = _read_writer_procedure + s.data = rw return } @(private) -_read_writer_vtable := io.Stream_VTable{ - impl_read = proc(s: io.Stream, p: []byte) -> (n: int, err: io.Error) { - b := (^Read_Writer)(s.stream_data).r - return reader_read(b, p) - }, - impl_unread_byte = proc(s: io.Stream) -> io.Error { - b := (^Read_Writer)(s.stream_data).r - return reader_unread_byte(b) - }, - impl_read_rune = proc(s: io.Stream) -> (r: rune, size: int, err: io.Error) { - b := (^Read_Writer)(s.stream_data).r - return reader_read_rune(b) - }, - impl_unread_rune = proc(s: io.Stream) -> io.Error { - b := (^Read_Writer)(s.stream_data).r - return reader_unread_rune(b) - }, - impl_write_to = proc(s: io.Stream, w: io.Writer) -> (n: i64, err: io.Error) { - b := (^Read_Writer)(s.stream_data).r - return reader_write_to(b, w) - }, - impl_flush = proc(s: io.Stream) -> io.Error { - b := (^Read_Writer)(s.stream_data).w - return writer_flush(b) - }, - impl_write = proc(s: io.Stream, p: []byte) -> (n: int, err: io.Error) { - b := (^Read_Writer)(s.stream_data).w - return writer_write(b, p) - }, - impl_write_byte = proc(s: io.Stream, c: byte) -> io.Error { - b := (^Read_Writer)(s.stream_data).w - return writer_write_byte(b, c) - }, - impl_write_rune = proc(s: io.Stream, r: rune) -> (int, io.Error) { - b := (^Read_Writer)(s.stream_data).w - return writer_write_rune(b, r) - }, - impl_read_from = proc(s: io.Stream, r: io.Reader) -> (n: i64, err: io.Error) { - b := (^Read_Writer)(s.stream_data).w - return writer_read_from(b, r) - }, -} +_read_writer_procedure := proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) { + rw := (^Read_Writer)(stream_data) + n_int: int + #partial switch mode { + case .Flush: + err = writer_flush(rw.w) + return + case .Read: + n_int, err = reader_read(rw.r, p) + n = i64(n_int) + return + case .Write: + n_int, err = writer_write(rw.w, p) + n = i64(n_int) + return + case .Query: + return io.query_utility({.Flush, .Read, .Write, .Query}) + } + return 0, .Empty +} \ No newline at end of file diff --git a/core/bufio/reader.odin b/core/bufio/reader.odin index 6bfc4cd9d..dc4e02c02 100644 --- a/core/bufio/reader.odin +++ b/core/bufio/reader.odin @@ -311,18 +311,6 @@ reader_write_to :: proc(b: ^Reader, w: io.Writer) -> (n: i64, err: io.Error) { } m: i64 - if nr, ok := io.to_writer_to(b.rd); ok { - m, err = io.write_to(nr, w) - n += m - return n, err - } - - if nw, ok := io.to_reader_from(w); ok { - m, err = io.read_from(nw, b.rd) - n += m - return n, err - } - if b.w-b.r < len(b.buf) { if err = _reader_read_new_chunk(b); err != nil { return @@ -352,48 +340,28 @@ reader_write_to :: proc(b: ^Reader, w: io.Writer) -> (n: i64, err: io.Error) { // reader_to_stream converts a Reader into an io.Stream reader_to_stream :: proc(b: ^Reader) -> (s: io.Stream) { - s.stream_data = b - s.stream_vtable = &_reader_vtable + s.data = b + s.procedure = _reader_proc return } @(private) -_reader_vtable := io.Stream_VTable{ - impl_destroy = proc(s: io.Stream) -> io.Error { - b := (^Reader)(s.stream_data) +_reader_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) { + b := (^Reader)(stream_data) + #partial switch mode { + case .Read: + return io._i64_err(reader_read(b, p)) + case .Destroy: reader_destroy(b) - return nil - }, - impl_read = proc(s: io.Stream, p: []byte) -> (n: int, err: io.Error) { - b := (^Reader)(s.stream_data) - return reader_read(b, p) - }, - impl_read_byte = proc(s: io.Stream) -> (c: byte, err: io.Error) { - b := (^Reader)(s.stream_data) - return reader_read_byte(b) - }, - impl_unread_byte = proc(s: io.Stream) -> io.Error { - b := (^Reader)(s.stream_data) - return reader_unread_byte(b) - }, - impl_read_rune = proc(s: io.Stream) -> (r: rune, size: int, err: io.Error) { - b := (^Reader)(s.stream_data) - return reader_read_rune(b) - }, - impl_unread_rune = proc(s: io.Stream) -> io.Error { - b := (^Reader)(s.stream_data) - return reader_unread_rune(b) - }, - impl_write_to = proc(s: io.Stream, w: io.Writer) -> (n: i64, err: io.Error) { - b := (^Reader)(s.stream_data) - return reader_write_to(b, w) - }, + return + case .Query: + return io.query_utility({.Read, .Destroy, .Query}) + } + return 0, .Empty } - - // // Utility procedures // diff --git a/core/bufio/writer.odin b/core/bufio/writer.odin index ed0d557c5..b4234af43 100644 --- a/core/bufio/writer.odin +++ b/core/bufio/writer.odin @@ -173,14 +173,6 @@ writer_read_from :: proc(b: ^Writer, r: io.Reader) -> (n: i64, err: io.Error) { if b.err != nil { return 0, b.err } - if writer_buffered(b) == 0 { - if w, ok := io.to_reader_from(b.wr); !ok { - n, err = io.read_from(w, r) - b.err = err - return - } - } - for { if writer_available(b) == 0 { writer_flush(b) or_return @@ -222,46 +214,35 @@ writer_read_from :: proc(b: ^Writer, r: io.Reader) -> (n: i64, err: io.Error) { // writer_to_stream converts a Writer into an io.Stream writer_to_stream :: proc(b: ^Writer) -> (s: io.Stream) { - s.stream_data = b - s.stream_vtable = &_writer_vtable + s.data = b + s.procedure = _writer_proc return } // writer_to_stream converts a Writer into an io.Stream writer_to_writer :: proc(b: ^Writer) -> (s: io.Writer) { - s.stream_data = b - s.stream_vtable = &_writer_vtable - return + return {writer_to_stream(b)} } - @(private) -_writer_vtable := io.Stream_VTable{ - impl_destroy = proc(s: io.Stream) -> io.Error { - b := (^Writer)(s.stream_data) +_writer_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) { + b := (^Writer)(stream_data) + #partial switch mode { + case .Flush: + err = writer_flush(b) + return + case .Write: + n_int: int + n_int, err = writer_write(b, p) + n = i64(n_int) + return + case .Destroy: writer_destroy(b) - return nil - }, - impl_flush = proc(s: io.Stream) -> io.Error { - b := (^Writer)(s.stream_data) - return writer_flush(b) - }, - impl_write = proc(s: io.Stream, p: []byte) -> (n: int, err: io.Error) { - b := (^Writer)(s.stream_data) - return writer_write(b, p) - }, - impl_write_byte = proc(s: io.Stream, c: byte) -> io.Error { - b := (^Writer)(s.stream_data) - return writer_write_byte(b, c) - }, - impl_write_rune = proc(s: io.Stream, r: rune) -> (int, io.Error) { - b := (^Writer)(s.stream_data) - return writer_write_rune(b, r) - }, - impl_read_from = proc(s: io.Stream, r: io.Reader) -> (n: i64, err: io.Error) { - b := (^Writer)(s.stream_data) - return writer_read_from(b, r) - }, + return + case .Query: + return io.query_utility({.Flush, .Write, .Destroy, .Query}) + } + return 0, .Empty } diff --git a/core/bytes/buffer.odin b/core/bytes/buffer.odin index b60a8e877..4375d8195 100644 --- a/core/bytes/buffer.odin +++ b/core/bytes/buffer.odin @@ -375,69 +375,31 @@ buffer_read_from :: proc(b: ^Buffer, r: io.Reader) -> (n: i64, err: io.Error) #n buffer_to_stream :: proc(b: ^Buffer) -> (s: io.Stream) { - s.stream_data = b - s.stream_vtable = &_buffer_vtable + s.data = b + s.procedure = _buffer_proc return } @(private) -_buffer_vtable := io.Stream_VTable{ - impl_size = proc(s: io.Stream) -> i64 { - b := (^Buffer)(s.stream_data) - return i64(buffer_capacity(b)) - }, - impl_read = proc(s: io.Stream, p: []byte) -> (n: int, err: io.Error) { - b := (^Buffer)(s.stream_data) - return buffer_read(b, p) - }, - impl_read_at = proc(s: io.Stream, p: []byte, offset: i64) -> (n: int, err: io.Error) { - b := (^Buffer)(s.stream_data) - return buffer_read_at(b, p, int(offset)) - }, - impl_read_byte = proc(s: io.Stream) -> (byte, io.Error) { - b := (^Buffer)(s.stream_data) - return buffer_read_byte(b) - }, - impl_read_rune = proc(s: io.Stream) -> (r: rune, size: int, err: io.Error) { - b := (^Buffer)(s.stream_data) - return buffer_read_rune(b) - }, - impl_write = proc(s: io.Stream, p: []byte) -> (n: int, err: io.Error) { - b := (^Buffer)(s.stream_data) - return buffer_write(b, p) - }, - impl_write_at = proc(s: io.Stream, p: []byte, offset: i64) -> (n: int, err: io.Error) { - b := (^Buffer)(s.stream_data) - return buffer_write_at(b, p, int(offset)) - }, - impl_write_byte = proc(s: io.Stream, c: byte) -> io.Error { - b := (^Buffer)(s.stream_data) - return buffer_write_byte(b, c) - }, - impl_write_rune = proc(s: io.Stream, r: rune) -> (int, io.Error) { - b := (^Buffer)(s.stream_data) - return buffer_write_rune(b, r) - }, - impl_unread_byte = proc(s: io.Stream) -> io.Error { - b := (^Buffer)(s.stream_data) - return buffer_unread_byte(b) - }, - impl_unread_rune = proc(s: io.Stream) -> io.Error { - b := (^Buffer)(s.stream_data) - return buffer_unread_rune(b) - }, - impl_destroy = proc(s: io.Stream) -> io.Error { - b := (^Buffer)(s.stream_data) +_buffer_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) { + b := (^Buffer)(stream_data) + #partial switch mode { + case .Read: + return io._i64_err(buffer_read(b, p)) + case .Read_At: + return io._i64_err(buffer_read_at(b, p, int(offset))) + case .Write: + return io._i64_err(buffer_write(b, p)) + case .Write_At: + return io._i64_err(buffer_write_at(b, p, int(offset))) + case .Size: + n = i64(buffer_capacity(b)) + return + case .Destroy: buffer_destroy(b) - return nil - }, - impl_write_to = proc(s: io.Stream, w: io.Writer) -> (n: i64, err: io.Error) { - b := (^Buffer)(s.stream_data) - return buffer_write_to(b, w) - }, - impl_read_from = proc(s: io.Stream, r: io.Reader) -> (n: i64, err: io.Error) { - b := (^Buffer)(s.stream_data) - return buffer_read_from(b, r) - }, + return + case .Query: + return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Size, .Destroy}) + } + return 0, .Empty } - diff --git a/core/bytes/reader.odin b/core/bytes/reader.odin index 7c37f3061..4b18345ba 100644 --- a/core/bytes/reader.odin +++ b/core/bytes/reader.odin @@ -16,8 +16,8 @@ reader_init :: proc(r: ^Reader, s: []byte) { } reader_to_stream :: proc(r: ^Reader) -> (s: io.Stream) { - s.stream_data = r - s.stream_vtable = &_reader_vtable + s.data = r + s.procedure = _reader_proc return } @@ -137,41 +137,22 @@ reader_write_to :: proc(r: ^Reader, w: io.Writer) -> (n: i64, err: io.Error) { @(private) -_reader_vtable := io.Stream_VTable{ - impl_size = proc(s: io.Stream) -> i64 { - r := (^Reader)(s.stream_data) - return reader_size(r) - }, - impl_read = proc(s: io.Stream, p: []byte) -> (n: int, err: io.Error) { - r := (^Reader)(s.stream_data) - return reader_read(r, p) - }, - impl_read_at = proc(s: io.Stream, p: []byte, off: i64) -> (n: int, err: io.Error) { - r := (^Reader)(s.stream_data) - return reader_read_at(r, p, off) - }, - impl_read_byte = proc(s: io.Stream) -> (byte, io.Error) { - r := (^Reader)(s.stream_data) - return reader_read_byte(r) - }, - impl_unread_byte = proc(s: io.Stream) -> io.Error { - r := (^Reader)(s.stream_data) - return reader_unread_byte(r) - }, - impl_read_rune = proc(s: io.Stream) -> (ch: rune, size: int, err: io.Error) { - r := (^Reader)(s.stream_data) - return reader_read_rune(r) - }, - impl_unread_rune = proc(s: io.Stream) -> io.Error { - r := (^Reader)(s.stream_data) - return reader_unread_rune(r) - }, - impl_seek = proc(s: io.Stream, offset: i64, whence: io.Seek_From) -> (i64, io.Error) { - r := (^Reader)(s.stream_data) - return reader_seek(r, offset, whence) - }, - impl_write_to = proc(s: io.Stream, w: io.Writer) -> (n: i64, err: io.Error) { - r := (^Reader)(s.stream_data) - return reader_write_to(r, w) - }, +_reader_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) { + r := (^Reader)(stream_data) + #partial switch mode { + case .Read: + return io._i64_err(reader_read(r, p)) + case .Read_At: + return io._i64_err(reader_read_at(r, p, offset)) + case .Seek: + n, err = reader_seek(r, offset, whence) + return + case .Size: + n = reader_size(r) + return + case .Query: + return io.query_utility({.Read, .Read_At, .Seek, .Size, .Query}) + } + return 0, .Empty } + diff --git a/core/compress/common.odin b/core/compress/common.odin index 6bcc46bf2..2d00ed359 100644 --- a/core/compress/common.odin +++ b/core/compress/common.odin @@ -188,7 +188,8 @@ input_size_from_memory :: proc(z: ^Context_Memory_Input) -> (res: i64, err: Erro } input_size_from_stream :: proc(z: ^Context_Stream_Input) -> (res: i64, err: Error) { - return io.size(z.input), nil + res, _ = io.size(z.input) + return } input_size :: proc{input_size_from_memory, input_size_from_stream} @@ -215,7 +216,7 @@ read_slice_from_stream :: #force_inline proc(z: ^Context_Stream_Input, size: int // TODO: REMOVE ALL USE OF context.temp_allocator here // the is literally no need for it b := make([]u8, size, context.temp_allocator) - _, e := z.input->impl_read(b[:]) + _, e := io.read({z.input}, b[:]) if e == .None { return b, .None } diff --git a/core/crypto/blake/blake.odin b/core/crypto/blake/blake.odin index 5fc0a02b9..67f3c0d35 100644 --- a/core/crypto/blake/blake.odin +++ b/core/crypto/blake/blake.odin @@ -70,7 +70,7 @@ hash_stream_224 :: proc(s: io.Stream) -> ([DIGEST_SIZE_224]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { update(&ctx, buf[:read]) } @@ -149,7 +149,7 @@ hash_stream_256 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { update(&ctx, buf[:read]) } @@ -228,7 +228,7 @@ hash_stream_384 :: proc(s: io.Stream) -> ([DIGEST_SIZE_384]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { update(&ctx, buf[:read]) } @@ -307,7 +307,7 @@ hash_stream_512 :: proc(s: io.Stream) -> ([DIGEST_SIZE_512]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { update(&ctx, buf[:read]) } diff --git a/core/crypto/blake2b/blake2b.odin b/core/crypto/blake2b/blake2b.odin index e75d74197..82dabc542 100644 --- a/core/crypto/blake2b/blake2b.odin +++ b/core/crypto/blake2b/blake2b.odin @@ -77,7 +77,7 @@ hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { _blake2.update(&ctx, buf[:read]) } diff --git a/core/crypto/blake2s/blake2s.odin b/core/crypto/blake2s/blake2s.odin index 831335081..a767d4d70 100644 --- a/core/crypto/blake2s/blake2s.odin +++ b/core/crypto/blake2s/blake2s.odin @@ -77,7 +77,7 @@ hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { _blake2.update(&ctx, buf[:read]) } diff --git a/core/crypto/gost/gost.odin b/core/crypto/gost/gost.odin index 1d0274fae..fa2df862f 100644 --- a/core/crypto/gost/gost.odin +++ b/core/crypto/gost/gost.odin @@ -65,7 +65,7 @@ hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { update(&ctx, buf[:read]) } diff --git a/core/crypto/groestl/groestl.odin b/core/crypto/groestl/groestl.odin index 8e5a2440d..604a3eb9b 100644 --- a/core/crypto/groestl/groestl.odin +++ b/core/crypto/groestl/groestl.odin @@ -70,7 +70,7 @@ hash_stream_224 :: proc(s: io.Stream) -> ([DIGEST_SIZE_224]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { update(&ctx, buf[:read]) } @@ -149,7 +149,7 @@ hash_stream_256 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { update(&ctx, buf[:read]) } @@ -228,7 +228,7 @@ hash_stream_384 :: proc(s: io.Stream) -> ([DIGEST_SIZE_384]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { update(&ctx, buf[:read]) } @@ -307,7 +307,7 @@ hash_stream_512 :: proc(s: io.Stream) -> ([DIGEST_SIZE_512]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { update(&ctx, buf[:read]) } diff --git a/core/crypto/haval/haval.odin b/core/crypto/haval/haval.odin index 811ecf95d..76cfbecbe 100644 --- a/core/crypto/haval/haval.odin +++ b/core/crypto/haval/haval.odin @@ -79,7 +79,7 @@ hash_stream_128_3 :: proc(s: io.Stream) -> ([DIGEST_SIZE_128]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) ctx.str_len = u32(len(buf[:read])) if read > 0 { update(&ctx, buf[:read]) @@ -164,7 +164,7 @@ hash_stream_128_4 :: proc(s: io.Stream) -> ([DIGEST_SIZE_128]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) ctx.str_len = u32(len(buf[:read])) if read > 0 { update(&ctx, buf[:read]) @@ -249,7 +249,7 @@ hash_stream_128_5 :: proc(s: io.Stream) -> ([DIGEST_SIZE_128]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) ctx.str_len = u32(len(buf[:read])) if read > 0 { update(&ctx, buf[:read]) @@ -334,7 +334,7 @@ hash_stream_160_3 :: proc(s: io.Stream) -> ([DIGEST_SIZE_160]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) ctx.str_len = u32(len(buf[:read])) if read > 0 { update(&ctx, buf[:read]) @@ -419,7 +419,7 @@ hash_stream_160_4 :: proc(s: io.Stream) -> ([DIGEST_SIZE_160]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) ctx.str_len = u32(len(buf[:read])) if read > 0 { update(&ctx, buf[:read]) @@ -504,7 +504,7 @@ hash_stream_160_5 :: proc(s: io.Stream) -> ([DIGEST_SIZE_160]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) ctx.str_len = u32(len(buf[:read])) if read > 0 { update(&ctx, buf[:read]) @@ -589,7 +589,7 @@ hash_stream_192_3 :: proc(s: io.Stream) -> ([DIGEST_SIZE_192]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) ctx.str_len = u32(len(buf[:read])) if read > 0 { update(&ctx, buf[:read]) @@ -674,7 +674,7 @@ hash_stream_192_4 :: proc(s: io.Stream) -> ([DIGEST_SIZE_192]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) ctx.str_len = u32(len(buf[:read])) if read > 0 { update(&ctx, buf[:read]) @@ -759,7 +759,7 @@ hash_stream_192_5 :: proc(s: io.Stream) -> ([DIGEST_SIZE_192]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) ctx.str_len = u32(len(buf[:read])) if read > 0 { update(&ctx, buf[:read]) @@ -844,7 +844,7 @@ hash_stream_224_3 :: proc(s: io.Stream) -> ([DIGEST_SIZE_224]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) ctx.str_len = u32(len(buf[:read])) if read > 0 { update(&ctx, buf[:read]) @@ -929,7 +929,7 @@ hash_stream_224_4 :: proc(s: io.Stream) -> ([DIGEST_SIZE_224]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) ctx.str_len = u32(len(buf[:read])) if read > 0 { update(&ctx, buf[:read]) @@ -1014,7 +1014,7 @@ hash_stream_224_5 :: proc(s: io.Stream) -> ([DIGEST_SIZE_224]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) ctx.str_len = u32(len(buf[:read])) if read > 0 { update(&ctx, buf[:read]) @@ -1099,7 +1099,7 @@ hash_stream_256_3 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) ctx.str_len = u32(len(buf[:read])) if read > 0 { update(&ctx, buf[:read]) @@ -1184,7 +1184,7 @@ hash_stream_256_4 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) ctx.str_len = u32(len(buf[:read])) if read > 0 { update(&ctx, buf[:read]) @@ -1270,7 +1270,7 @@ hash_stream_256_5 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) ctx.str_len = u32(len(buf[:read])) if read > 0 { update(&ctx, buf[:read]) diff --git a/core/crypto/jh/jh.odin b/core/crypto/jh/jh.odin index 42c2d1d34..6b4a73b46 100644 --- a/core/crypto/jh/jh.odin +++ b/core/crypto/jh/jh.odin @@ -70,7 +70,7 @@ hash_stream_224 :: proc(s: io.Stream) -> ([DIGEST_SIZE_224]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { update(&ctx, buf[:read]) } @@ -149,7 +149,7 @@ hash_stream_256 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { update(&ctx, buf[:read]) } @@ -228,7 +228,7 @@ hash_stream_384 :: proc(s: io.Stream) -> ([DIGEST_SIZE_384]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { update(&ctx, buf[:read]) } @@ -307,7 +307,7 @@ hash_stream_512 :: proc(s: io.Stream) -> ([DIGEST_SIZE_512]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { update(&ctx, buf[:read]) } diff --git a/core/crypto/keccak/keccak.odin b/core/crypto/keccak/keccak.odin index aeb5aac52..c09c9ddbe 100644 --- a/core/crypto/keccak/keccak.odin +++ b/core/crypto/keccak/keccak.odin @@ -77,7 +77,7 @@ hash_stream_224 :: proc(s: io.Stream) -> ([DIGEST_SIZE_224]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { _sha3.update(&ctx, buf[:read]) } @@ -159,7 +159,7 @@ hash_stream_256 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { _sha3.update(&ctx, buf[:read]) } @@ -241,7 +241,7 @@ hash_stream_384 :: proc(s: io.Stream) -> ([DIGEST_SIZE_384]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { _sha3.update(&ctx, buf[:read]) } @@ -323,7 +323,7 @@ hash_stream_512 :: proc(s: io.Stream) -> ([DIGEST_SIZE_512]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { _sha3.update(&ctx, buf[:read]) } diff --git a/core/crypto/md2/md2.odin b/core/crypto/md2/md2.odin index 711e6e9f6..693e2c6b0 100644 --- a/core/crypto/md2/md2.odin +++ b/core/crypto/md2/md2.odin @@ -64,7 +64,7 @@ hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { update(&ctx, buf[:read]) } diff --git a/core/crypto/md4/md4.odin b/core/crypto/md4/md4.odin index b2651225b..1f161e1cb 100644 --- a/core/crypto/md4/md4.odin +++ b/core/crypto/md4/md4.odin @@ -68,7 +68,7 @@ hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { update(&ctx, buf[:read]) } diff --git a/core/crypto/md5/md5.odin b/core/crypto/md5/md5.odin index 30a556102..db04e67df 100644 --- a/core/crypto/md5/md5.odin +++ b/core/crypto/md5/md5.odin @@ -67,7 +67,7 @@ hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { update(&ctx, buf[:read]) } diff --git a/core/crypto/ripemd/ripemd.odin b/core/crypto/ripemd/ripemd.odin index 702d29037..257f4144e 100644 --- a/core/crypto/ripemd/ripemd.odin +++ b/core/crypto/ripemd/ripemd.odin @@ -69,7 +69,7 @@ hash_stream_128 :: proc(s: io.Stream) -> ([DIGEST_SIZE_128]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { update(&ctx, buf[:read]) } @@ -145,7 +145,7 @@ hash_stream_160 :: proc(s: io.Stream) -> ([DIGEST_SIZE_160]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { update(&ctx, buf[:read]) } @@ -221,7 +221,7 @@ hash_stream_256 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { update(&ctx, buf[:read]) } @@ -297,7 +297,7 @@ hash_stream_320 :: proc(s: io.Stream) -> ([DIGEST_SIZE_320]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { update(&ctx, buf[:read]) } diff --git a/core/crypto/sha1/sha1.odin b/core/crypto/sha1/sha1.odin index b0dbd7dc8..2e1991444 100644 --- a/core/crypto/sha1/sha1.odin +++ b/core/crypto/sha1/sha1.odin @@ -67,7 +67,7 @@ hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { update(&ctx, buf[:read]) } diff --git a/core/crypto/sha2/sha2.odin b/core/crypto/sha2/sha2.odin index 9792a4cb8..cbd3a6b39 100644 --- a/core/crypto/sha2/sha2.odin +++ b/core/crypto/sha2/sha2.odin @@ -74,7 +74,7 @@ hash_stream_224 :: proc(s: io.Stream) -> ([DIGEST_SIZE_224]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { update(&ctx, buf[:read]) } @@ -153,7 +153,7 @@ hash_stream_256 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { update(&ctx, buf[:read]) } @@ -232,7 +232,7 @@ hash_stream_384 :: proc(s: io.Stream) -> ([DIGEST_SIZE_384]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { update(&ctx, buf[:read]) } @@ -311,7 +311,7 @@ hash_stream_512 :: proc(s: io.Stream) -> ([DIGEST_SIZE_512]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { update(&ctx, buf[:read]) } diff --git a/core/crypto/sha3/sha3.odin b/core/crypto/sha3/sha3.odin index 1202f8b23..ec6fbc971 100644 --- a/core/crypto/sha3/sha3.odin +++ b/core/crypto/sha3/sha3.odin @@ -73,7 +73,7 @@ hash_stream_224 :: proc(s: io.Stream) -> ([DIGEST_SIZE_224]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { _sha3.update(&ctx, buf[:read]) } @@ -152,7 +152,7 @@ hash_stream_256 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { _sha3.update(&ctx, buf[:read]) } @@ -231,7 +231,7 @@ hash_stream_384 :: proc(s: io.Stream) -> ([DIGEST_SIZE_384]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { _sha3.update(&ctx, buf[:read]) } @@ -310,7 +310,7 @@ hash_stream_512 :: proc(s: io.Stream) -> ([DIGEST_SIZE_512]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { _sha3.update(&ctx, buf[:read]) } diff --git a/core/crypto/shake/shake.odin b/core/crypto/shake/shake.odin index 525dcfbd3..50ef756aa 100644 --- a/core/crypto/shake/shake.odin +++ b/core/crypto/shake/shake.odin @@ -73,7 +73,7 @@ hash_stream_128 :: proc(s: io.Stream) -> ([DIGEST_SIZE_128]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { _sha3.update(&ctx, buf[:read]) } @@ -155,7 +155,7 @@ hash_stream_256 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { _sha3.update(&ctx, buf[:read]) } diff --git a/core/crypto/sm3/sm3.odin b/core/crypto/sm3/sm3.odin index 74c9f22e2..b35e20e37 100644 --- a/core/crypto/sm3/sm3.odin +++ b/core/crypto/sm3/sm3.odin @@ -66,7 +66,7 @@ hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { update(&ctx, buf[:read]) } diff --git a/core/crypto/streebog/streebog.odin b/core/crypto/streebog/streebog.odin index f85977cba..aa4dd7906 100644 --- a/core/crypto/streebog/streebog.odin +++ b/core/crypto/streebog/streebog.odin @@ -70,7 +70,7 @@ hash_stream_256 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { update(&ctx, buf[:read]) } @@ -146,7 +146,7 @@ hash_stream_512 :: proc(s: io.Stream) -> ([DIGEST_SIZE_512]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { update(&ctx, buf[:read]) } diff --git a/core/crypto/tiger/tiger.odin b/core/crypto/tiger/tiger.odin index cf6159fad..a4982d7c4 100644 --- a/core/crypto/tiger/tiger.odin +++ b/core/crypto/tiger/tiger.odin @@ -71,7 +71,7 @@ hash_stream_128 :: proc(s: io.Stream) -> ([DIGEST_SIZE_128]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { _tiger.update(&ctx, buf[:read]) } @@ -150,7 +150,7 @@ hash_stream_160 :: proc(s: io.Stream) -> ([DIGEST_SIZE_160]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { _tiger.update(&ctx, buf[:read]) } @@ -229,7 +229,7 @@ hash_stream_192 :: proc(s: io.Stream) -> ([DIGEST_SIZE_192]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { _tiger.update(&ctx, buf[:read]) } diff --git a/core/crypto/tiger2/tiger2.odin b/core/crypto/tiger2/tiger2.odin index e8f2c4edb..432387bdf 100644 --- a/core/crypto/tiger2/tiger2.odin +++ b/core/crypto/tiger2/tiger2.odin @@ -71,7 +71,7 @@ hash_stream_128 :: proc(s: io.Stream) -> ([DIGEST_SIZE_128]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { _tiger.update(&ctx, buf[:read]) } @@ -150,7 +150,7 @@ hash_stream_160 :: proc(s: io.Stream) -> ([DIGEST_SIZE_160]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { _tiger.update(&ctx, buf[:read]) } @@ -229,7 +229,7 @@ hash_stream_192 :: proc(s: io.Stream) -> ([DIGEST_SIZE_192]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { _tiger.update(&ctx, buf[:read]) } diff --git a/core/crypto/whirlpool/whirlpool.odin b/core/crypto/whirlpool/whirlpool.odin index 0cfef7c6b..8ae6f413e 100644 --- a/core/crypto/whirlpool/whirlpool.odin +++ b/core/crypto/whirlpool/whirlpool.odin @@ -66,7 +66,7 @@ hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = s->impl_read(buf) + read, _ = io.read({s}, buf) if read > 0 { update(&ctx, buf[:read]) } diff --git a/core/io/conv.odin b/core/io/conv.odin index 39a72d69d..2b1351e18 100644 --- a/core/io/conv.odin +++ b/core/io/conv.odin @@ -2,123 +2,79 @@ package io to_reader :: proc(s: Stream) -> (r: Reader, ok: bool = true) #optional_ok { r.stream = s - if s.stream_vtable == nil || s.impl_read == nil { - ok = false - } + ok = .Read in query(s) return } to_writer :: proc(s: Stream) -> (w: Writer, ok: bool = true) #optional_ok { w.stream = s - if s.stream_vtable == nil || s.impl_write == nil { - ok = false - } + ok = .Write in query(s) return } to_closer :: proc(s: Stream) -> (c: Closer, ok: bool = true) #optional_ok { c.stream = s - if s.stream_vtable == nil || s.impl_close == nil { - ok = false - } + ok = .Close in query(s) return } to_flusher :: proc(s: Stream) -> (f: Flusher, ok: bool = true) #optional_ok { f.stream = s - if s.stream_vtable == nil || s.impl_flush == nil { - ok = false - } + ok = .Flush in query(s) return } to_seeker :: proc(s: Stream) -> (seeker: Seeker, ok: bool = true) #optional_ok { seeker.stream = s - if s.stream_vtable == nil || s.impl_seek == nil { - ok = false - } + ok = .Seek in query(s) return } to_read_writer :: proc(s: Stream) -> (r: Read_Writer, ok: bool = true) #optional_ok { r.stream = s - if s.stream_vtable == nil || s.impl_read == nil || s.impl_write == nil { - ok = false - } + ok = query(s) >= {.Read, .Write} return } to_read_closer :: proc(s: Stream) -> (r: Read_Closer, ok: bool = true) #optional_ok { r.stream = s - if s.stream_vtable == nil || s.impl_read == nil || s.impl_close == nil { - ok = false - } + ok = query(s) >= {.Read, .Close} return } to_read_write_closer :: proc(s: Stream) -> (r: Read_Write_Closer, ok: bool = true) #optional_ok { r.stream = s - if s.stream_vtable == nil || s.impl_read == nil || s.impl_write == nil || s.impl_close == nil { - ok = false - } + ok = query(s) >= {.Read, .Write, .Close} return } to_read_write_seeker :: proc(s: Stream) -> (r: Read_Write_Seeker, ok: bool = true) #optional_ok { r.stream = s - if s.stream_vtable == nil || s.impl_read == nil || s.impl_write == nil || s.impl_seek == nil { - ok = false - } + ok = query(s) >= {.Read, .Write, .Seek} return } to_write_flusher :: proc(s: Stream) -> (w: Write_Flusher, ok: bool = true) #optional_ok { w.stream = s - if s.stream_vtable == nil || s.impl_write == nil || s.impl_flush == nil { - ok = false - } + ok = query(s) >= {.Write, .Flush} return } to_write_flush_closer :: proc(s: Stream) -> (w: Write_Flush_Closer, ok: bool = true) #optional_ok { w.stream = s - if s.stream_vtable == nil || s.impl_write == nil || s.impl_flush == nil || s.impl_close == nil { - ok = false - } + ok = query(s) >= {.Write, .Flush, .Close} return } to_reader_at :: proc(s: Stream) -> (r: Reader_At, ok: bool = true) #optional_ok { r.stream = s - if s.stream_vtable == nil || s.impl_read_at == nil { - ok = false - } + ok = query(s) >= {.Read_At} return } to_writer_at :: proc(s: Stream) -> (w: Writer_At, ok: bool = true) #optional_ok { w.stream = s - if s.stream_vtable == nil || s.impl_write_at == nil { - ok = false - } - return -} -to_reader_from :: proc(s: Stream) -> (r: Reader_From, ok: bool = true) #optional_ok { - r.stream = s - if s.stream_vtable == nil || s.impl_read_from == nil { - ok = false - } - return -} -to_writer_to :: proc(s: Stream) -> (w: Writer_To, ok: bool = true) #optional_ok { - w.stream = s - if s.stream_vtable == nil || s.impl_write_to == nil { - ok = false - } + ok = query(s) >= {.Write_At} return } to_write_closer :: proc(s: Stream) -> (w: Write_Closer, ok: bool = true) #optional_ok { w.stream = s - if s.stream_vtable == nil || s.impl_write == nil || s.impl_close == nil { - ok = false - } + ok = query(s) >= {.Write, .Close} return } to_write_seeker :: proc(s: Stream) -> (w: Write_Seeker, ok: bool = true) #optional_ok { w.stream = s - if s.stream_vtable == nil || s.impl_write == nil || s.impl_seek == nil { - ok = false - } + ok = query(s) >= {.Write, .Seek} return } diff --git a/core/io/io.odin b/core/io/io.odin index 7faa500b1..868e16204 100644 --- a/core/io/io.odin +++ b/core/io/io.odin @@ -53,54 +53,27 @@ Error :: enum i32 { Empty = -1, } -Close_Proc :: proc(using s: Stream) -> Error -Flush_Proc :: proc(using s: Stream) -> Error -Seek_Proc :: proc(using s: Stream, offset: i64, whence: Seek_From) -> (n: i64, err: Error) -Size_Proc :: proc(using s: Stream) -> i64 -Read_Proc :: proc(using s: Stream, p: []byte) -> (n: int, err: Error) -Read_At_Proc :: proc(using s: Stream, p: []byte, off: i64) -> (n: int, err: Error) -Read_From_Proc :: proc(using s: Stream, r: Reader) -> (n: i64, err: Error) -Read_Byte_Proc :: proc(using s: Stream) -> (byte, Error) -Read_Rune_Proc :: proc(using s: Stream) -> (ch: rune, size: int, err: Error) -Unread_Byte_Proc :: proc(using s: Stream) -> Error -Unread_Rune_Proc :: proc(using s: Stream) -> Error -Write_Proc :: proc(using s: Stream, p: []byte) -> (n: int, err: Error) -Write_At_Proc :: proc(using s: Stream, p: []byte, off: i64) -> (n: int, err: Error) -Write_To_Proc :: proc(using s: Stream, w: Writer) -> (n: i64, err: Error) -Write_Byte_Proc :: proc(using s: Stream, c: byte) -> Error -Write_Rune_Proc :: proc(using s: Stream, r: rune) -> (size: int, err: Error) -Destroy_Proc :: proc(using s: Stream) -> Error +Stream_Mode :: enum { + Close, + Flush, + Read, + Read_At, + Write, + Write_At, + Seek, + Size, + Destroy, + Query, // query what modes are available +} +Stream_Mode_Set :: distinct bit_set[Stream_Mode; i64] + +Stream_Proc :: #type proc(stream_data: rawptr, mode: Stream_Mode, p: []byte, offset: i64, whence: Seek_From) -> (n: i64, err: Error) Stream :: struct { - using stream_vtable: ^Stream_VTable, - stream_data: rawptr, + procedure: Stream_Proc, + data: rawptr, } -Stream_VTable :: struct { - impl_close: Close_Proc, - impl_flush: Flush_Proc, - - impl_seek: Seek_Proc, - impl_size: Size_Proc, - - impl_read: Read_Proc, - impl_read_at: Read_At_Proc, - impl_read_byte: Read_Byte_Proc, - impl_read_rune: Read_Rune_Proc, - impl_write_to: Write_To_Proc, - - impl_write: Write_Proc, - impl_write_at: Write_At_Proc, - impl_write_byte: Write_Byte_Proc, - impl_write_rune: Write_Rune_Proc, - impl_read_from: Read_From_Proc, - - impl_unread_byte: Unread_Byte_Proc, - impl_unread_rune: Unread_Rune_Proc, - - impl_destroy: Destroy_Proc, -} - Reader :: struct {using stream: Stream} Writer :: struct {using stream: Stream} @@ -120,70 +93,66 @@ Write_Flush_Closer :: struct {using stream: Stream} Reader_At :: struct {using stream: Stream} Writer_At :: struct {using stream: Stream} -Reader_From :: struct {using stream: Stream} -Writer_To :: struct {using stream: Stream} -destroy :: proc(s: Stream) -> Error { - close_err := close({s}) - if s.stream_vtable != nil && s.impl_destroy != nil { - return s->impl_destroy() +destroy :: proc(s: Stream) -> (err: Error) { + _ = flush({s}) + _ = close({s}) + if s.procedure != nil { + _, err = s.procedure(s.data, .Destroy, nil, 0, nil) + } else { + err = .Empty } - if close_err != .None { - return close_err - } - return .Empty + return } +query :: proc(s: Stream) -> (set: Stream_Mode_Set) { + if s.procedure != nil { + n, _ := s.procedure(s.data, .Query, nil, 0, nil) + set = transmute(Stream_Mode_Set)n + if set != nil { + set += {.Query} + } + } + return +} + +query_utility :: #force_inline proc "contextless" (set: Stream_Mode_Set) -> (n: i64, err: Error) { + return transmute(i64)set, nil +} + +_i64_err :: #force_inline proc "contextless" (n: int, err: Error) -> (i64, Error) { + return i64(n), err +} + + // read reads up to len(p) bytes into s. It returns the number of bytes read and any error if occurred. // // When read encounters an .EOF or error after successfully reading n > 0 bytes, it returns the number of // bytes read along with the error. read :: proc(s: Reader, p: []byte, n_read: ^int = nil) -> (n: int, err: Error) { - if s.stream_vtable != nil { - if s.impl_read != nil { - n, err = s->impl_read(p) - if n_read != nil { - n_read^ += n - } - return - } else if s.impl_read_byte != nil { - bytes_read := 0 - defer if n_read != nil { - n_read^ += bytes_read - } - for _, i in p { - p[i] = s->impl_read_byte() or_return - bytes_read += 1 - } - return - } + if s.procedure != nil { + n64: i64 + n64, err = s.procedure(s.data, .Read, p, 0, nil) + n = int(n64) + if n_read != nil { n_read^ += n } + } else { + err = .Empty } - return 0, .Empty + return } // write writes up to len(p) bytes into s. It returns the number of bytes written and any error if occurred. write :: proc(s: Writer, p: []byte, n_written: ^int = nil) -> (n: int, err: Error) { - if s.stream_vtable != nil { - if s.impl_write != nil { - n, err = s->impl_write(p) - if n_written != nil { - n_written^ += n - } - return - } else if s.impl_write_byte != nil { - bytes_written := 0 - defer if n_written != nil { - n_written^ += bytes_written - } - for c in p { - s->impl_write_byte(c) or_return - bytes_written += 1 - } - return - } + if s.procedure != nil { + n64: i64 + n64, err = s.procedure(s.data, .Write, p, 0, nil) + n = int(n64) + if n_written != nil { n_written^ += n } + } else { + err = .Empty } - return 0, .Empty + return } // seek sets the offset of the next read or write to offset. @@ -194,57 +163,46 @@ write :: proc(s: Writer, p: []byte, n_written: ^int = nil) -> (n: int, err: Erro // // seek returns the new offset to the start of the file/stream, and any error if occurred. seek :: proc(s: Seeker, offset: i64, whence: Seek_From) -> (n: i64, err: Error) { - if s.stream_vtable != nil && s.impl_seek != nil { - return s->impl_seek(offset, whence) + if s.procedure != nil { + n, err = s.procedure(s.data, .Seek, nil, offset, whence) + } else { + err = .Empty } - return 0, .Empty + return } // The behaviour of close after the first call is stream implementation defined. // Different streams may document their own behaviour. -close :: proc(s: Closer) -> Error { - if s.stream_vtable != nil && s.impl_close != nil { - return s->impl_close() +close :: proc(s: Closer) -> (err: Error) { + if s.procedure != nil { + _, err = s.procedure(s.data, .Close, nil, 0, nil) } - // Instead of .Empty, .None is fine in this case - return .None + return } -flush :: proc(s: Flusher) -> Error { - if s.stream_vtable != nil && s.impl_flush != nil { - return s->impl_flush() +flush :: proc(s: Flusher) -> (err: Error) { + if s.procedure != nil { + _, err = s.procedure(s.data, .Flush, nil, 0, nil) } - // Instead of .Empty, .None is fine in this case - return .None + return } // size returns the size of the stream. If the stream does not support querying its size, 0 will be returned. -size :: proc(s: Stream) -> i64 { - if s.stream_vtable == nil { - return 0 +size :: proc(s: Stream) -> (n: i64, err: Error) { + if s.procedure != nil { + n, err = s.procedure(s.data, .Size, nil, 0, nil) + if err == .Empty { + seeker := Seeker{s} + n = 0 + curr := seek(seeker, 0, .Current) or_return + end := seek(seeker, 0, .End) or_return + seek(seeker, curr, .Start) or_return + n = end + } + } else { + err = .Empty } - if s.impl_size != nil { - return s->impl_size() - } - if s.impl_seek == nil { - return 0 - } - - curr, end: i64 - err: Error - if curr, err = s->impl_seek(0, .Current); err != nil { - return 0 - } - - if end, err = s->impl_seek(0, .End); err != nil { - return 0 - } - - if _, err = s->impl_seek(curr, .Start); err != nil { - return 0 - } - - return end + return } @@ -256,29 +214,26 @@ size :: proc(s: Stream) -> i64 { // // If n == len(p), err may be either nil or .EOF read_at :: proc(r: Reader_At, p: []byte, offset: i64, n_read: ^int = nil) -> (n: int, err: Error) { - defer if n_read != nil { - n_read^ += n - } - - if r.stream_vtable == nil { - return 0, .Empty - } - if r.impl_read_at != nil { - return r->impl_read_at(p, offset) - } - if r.impl_seek == nil || r.impl_read == nil { - return 0, .Empty - } - - curr_offset := r->impl_seek(offset, .Current) or_return - - n, err = r->impl_read(p) - _, err1 := r->impl_seek(curr_offset, .Start) - if err1 != nil && err == nil { - err = err1 + if r.procedure != nil { + n64: i64 + n64, err = r.procedure(r.data, .Read_At, p, offset, nil) + if err != .Empty { + n = int(n64) + } else { + seeker := Seeker{r} + reader := Reader{r} + curr := seek(seeker, offset, .Current) or_return + n, err = read(reader, p) + _, err1 := seek(seeker, curr, .Start) + if err1 != nil && err == nil { + err = err1 + } + } + if n_read != nil { n_read^ += n } + } else { + err = .Empty } return - } // write_at writes len(p) bytes into p starting with the provided offset in the underlying Writer_At stream w. @@ -287,97 +242,41 @@ read_at :: proc(r: Reader_At, p: []byte, offset: i64, n_read: ^int = nil) -> (n: // If write_at is writing to a Writer_At which has a seek offset, then write_at should not affect the underlying // seek offset. write_at :: proc(w: Writer_At, p: []byte, offset: i64, n_written: ^int = nil) -> (n: int, err: Error) { - defer if n_written != nil { - n_written^ += n - } - - if w.stream_vtable == nil { - return 0, .Empty - } - if w.impl_write_at != nil { - return w->impl_write_at(p, offset) - } - if w.impl_seek == nil || w.impl_write == nil { - return 0, .Empty - } - - curr_offset: i64 - curr_offset, err = w->impl_seek(offset, .Current) - if err != nil { - return 0, err - } - - n, err = w->impl_write(p) - _, err1 := w->impl_seek(curr_offset, .Start) - if err1 != nil && err == nil { - err = err1 + if w.procedure != nil { + n64: i64 + n64, err = w.procedure(w.data, .Write_At, p, offset, nil) + if err != .Empty { + n = int(n64) + } else { + seeker := Seeker{w} + writer := Writer{w} + curr := seek(seeker, offset, .Current) or_return + n, err = write(writer, p) + _, err1 := seek(seeker, curr, .Start) + if err1 != nil && err == nil { + err = err1 + } + } + if n_written != nil { n_written^ += n } + } else { + err = .Empty } return } -write_to :: proc(r: Writer_To, w: Writer) -> (n: i64, err: Error) { - if r.stream_vtable == nil || w.stream_vtable == nil { - return 0, .Empty - } - if r.impl_write_to != nil { - return r->impl_write_to(w) - } - return 0, .Empty -} -read_from :: proc(w: Reader_From, r: Reader) -> (n: i64, err: Error) { - if r.stream_vtable == nil || w.stream_vtable == nil { - return 0, .Empty - } - if r.impl_read_from != nil { - return w->impl_read_from(r) - } - return 0, .Empty -} - - // read_byte reads and returns the next byte from r. read_byte :: proc(r: Reader, n_read: ^int = nil) -> (b: byte, err: Error) { - defer if err == nil && n_read != nil { - n_read^ += 1 - } - - if r.stream_vtable == nil { - return 0, .Empty - } - if r.impl_read_byte != nil { - return r->impl_read_byte() - } - if r.impl_read == nil { - return 0, .Empty - } - buf: [1]byte - _, err = r->impl_read(buf[:]) - return buf[0], err + _, err = read(r, buf[:], n_read) + b = buf[0] + return } write_byte :: proc(w: Writer, c: byte, n_written: ^int = nil) -> Error { - return _write_byte(auto_cast w, c, n_written) -} - -@(private) -_write_byte :: proc(w: Writer, c: byte, n_written: ^int = nil) -> (err: Error) { - defer if err == nil && n_written != nil { - n_written^ += 1 - } - if w.stream_vtable == nil { - return .Empty - } - if w.impl_write_byte != nil { - return w->impl_write_byte(c) - } - if w.impl_write == nil { - return .Empty - } - - b := [1]byte{c} - _, err = w->impl_write(b[:]) - return err + buf: [1]byte + buf[0] = c + write(w, buf[:], n_written) or_return + return nil } // read_rune reads a single UTF-8 encoded Unicode codepoint and returns the rune and its size in bytes. @@ -385,19 +284,9 @@ read_rune :: proc(br: Reader, n_read: ^int = nil) -> (ch: rune, size: int, err: defer if err == nil && n_read != nil { n_read^ += size } - if br.stream_vtable == nil { - return 0, 0, .Empty - } - if br.impl_read_rune != nil { - return br->impl_read_rune() - } - if br.impl_read == nil { - return 0, 0, .Empty - } b: [utf8.UTF_MAX]byte - _, err = br->impl_read(b[:1]) - + _, err = read(br, b[:1]) s0 := b[0] ch = rune(s0) @@ -415,7 +304,7 @@ read_rune :: proc(br: Reader, n_read: ^int = nil) -> (ch: rune, size: int, err: return } sz := int(x&7) - size, err = br->impl_read(b[1:sz]) + size, err = read(br, b[1:sz]) if err != nil || size+1 < sz { ch = utf8.RUNE_ERROR return @@ -425,28 +314,6 @@ read_rune :: proc(br: Reader, n_read: ^int = nil) -> (ch: rune, size: int, err: return } -unread_byte :: proc(s: Stream) -> Error { - if s.stream_vtable == nil { - return .Empty - } - if s.impl_unread_byte != nil { - return s->impl_unread_byte() - } - if s.impl_seek != nil { - _, err := s->impl_seek(-1, .Current) - return err - } - - return .Empty -} -unread_rune :: proc(s: Writer) -> Error { - if s.stream_vtable != nil && s.impl_unread_rune != nil { - return s->impl_unread_rune() - } - return .Empty -} - - // write_string writes the contents of the string s to w. write_string :: proc(s: Writer, str: string, n_written: ^int = nil) -> (n: int, err: Error) { return write(s, transmute([]byte)str, n_written) @@ -457,14 +324,6 @@ write_rune :: proc(s: Writer, r: rune, n_written: ^int = nil) -> (size: int, err defer if err == nil && n_written != nil { n_written^ += size } - - if s.stream_vtable == nil { - return 0, .Empty - } - if s.impl_write_rune != nil { - return s->impl_write_rune(r) - } - if r < utf8.RUNE_SELF { err = write_byte(s, byte(r)) if err == nil { @@ -542,21 +401,15 @@ copy_n :: proc(dst: Writer, src: Reader, n: i64) -> (written: i64, err: Error) { @(private) _copy_buffer :: proc(dst: Writer, src: Reader, buf: []byte) -> (written: i64, err: Error) { - if dst.stream_vtable == nil || src.stream_vtable == nil { + if dst.procedure == nil || src.procedure == nil { return 0, .Empty } - if src.impl_write_to != nil { - return src->impl_write_to(dst) - } - if src.impl_read_from != nil { - return dst->impl_read_from(src) - } buf := buf if buf == nil { DEFAULT_SIZE :: 4 * 1024 size := DEFAULT_SIZE - if src.stream_vtable == _limited_reader_vtable { - l := (^Limited_Reader)(src.stream_data) + if src.procedure == _limited_reader_proc { + l := (^Limited_Reader)(src.data) if i64(size) > l.n { if l.n < 1 { size = 1 diff --git a/core/io/multi.odin b/core/io/multi.odin index 64c533e37..e85114a7a 100644 --- a/core/io/multi.odin +++ b/core/io/multi.odin @@ -5,33 +5,37 @@ Multi_Reader :: struct { } @(private) -_multi_reader_vtable := &Stream_VTable{ - impl_read = proc(s: Stream, p: []byte) -> (n: int, err: Error) { - mr := (^Multi_Reader)(s.stream_data) - for len(mr.readers) > 0 { - r := mr.readers[0] - n, err = read(r, p) - if err == .EOF { - ordered_remove(&mr.readers, 0) - } - if n > 0 || err != .EOF { - if err == .EOF && len(mr.readers) > 0 { - // Don't return EOF yet, more readers remain - err = nil - } - return - } +_multi_reader_proc :: proc(stream_data: rawptr, mode: Stream_Mode, p: []byte, offset: i64, whence: Seek_From) -> (n: i64, err: Error) { + if mode == .Query { + return query_utility({.Read, .Query}) + } else if mode != .Read { + return 0, .Empty + } + mr := (^Multi_Reader)(stream_data) + for len(mr.readers) > 0 { + r := mr.readers[0] + n, err = _i64_err(read(r, p)) + if err == .EOF { + ordered_remove(&mr.readers, 0) } - return 0, .EOF - }, + if n > 0 || err != .EOF { + if err == .EOF && len(mr.readers) > 0 { + // Don't return EOF yet, more readers remain + err = nil + } + return + } + } + return 0, .EOF } + multi_reader_init :: proc(mr: ^Multi_Reader, readers: ..Reader, allocator := context.allocator) -> (r: Reader) { all_readers := make([dynamic]Reader, 0, len(readers), allocator) for w in readers { - if w.stream_vtable == _multi_reader_vtable { - other := (^Multi_Reader)(w.stream_data) + if w.procedure == _multi_reader_proc { + other := (^Multi_Reader)(w.data) append(&all_readers, ..other.readers[:]) } else { append(&all_readers, w) @@ -40,8 +44,8 @@ multi_reader_init :: proc(mr: ^Multi_Reader, readers: ..Reader, allocator := con mr.readers = all_readers - r.stream_vtable = _multi_reader_vtable - r.stream_data = mr + r.procedure = _multi_reader_proc + r.data = mr return } @@ -55,38 +59,42 @@ Multi_Writer :: struct { } @(private) -_multi_writer_vtable := &Stream_VTable{ - impl_write = proc(s: Stream, p: []byte) -> (n: int, err: Error) { - mw := (^Multi_Writer)(s.stream_data) - for w in mw.writers { - n, err = write(w, p) - if err != nil { - return - } - if n != len(p) { - err = .Short_Write - return - } +_multi_writer_proc :: proc(stream_data: rawptr, mode: Stream_Mode, p: []byte, offset: i64, whence: Seek_From) -> (n: i64, err: Error) { + if mode == .Query { + return query_utility({.Write, .Query}) + } else if mode != .Write { + return 0, .Empty + } + mw := (^Multi_Writer)(stream_data) + for w in mw.writers { + n, err = _i64_err(write(w, p)) + if err != nil { + return } + if n != i64(len(p)) { + err = .Short_Write + return + } + } - return len(p), nil - }, + return i64(len(p)), nil } + multi_writer_init :: proc(mw: ^Multi_Writer, writers: ..Writer, allocator := context.allocator) -> (out: Writer) { mw.writers = make([dynamic]Writer, 0, len(writers), allocator) for w in writers { - if w.stream_vtable == _multi_writer_vtable { - other := (^Multi_Writer)(w.stream_data) + if w.procedure == _multi_writer_proc { + other := (^Multi_Writer)(w.data) append(&mw.writers, ..other.writers[:]) } else { append(&mw.writers, w) } } - out.stream_vtable = _multi_writer_vtable - out.stream_data = mw + out.procedure = _multi_writer_proc + out.data = mw return } diff --git a/core/io/util.odin b/core/io/util.odin index cfd7d3608..c77d0be9d 100644 --- a/core/io/util.odin +++ b/core/io/util.odin @@ -292,17 +292,21 @@ Tee_Reader :: struct { } @(private) -_tee_reader_vtable := &Stream_VTable{ - impl_read = proc(s: Stream, p: []byte) -> (n: int, err: Error) { - t := (^Tee_Reader)(s.stream_data) - n, err = read(t.r, p) +_tee_reader_proc :: proc(stream_data: rawptr, mode: Stream_Mode, p: []byte, offset: i64, whence: Seek_From) -> (n: i64, err: Error) { + t := (^Tee_Reader)(stream_data) + #partial switch mode { + case .Read: + n, err = _i64_err(read(t.r, p)) if n > 0 { if wn, werr := write(t.w, p[:n]); werr != nil { - return wn, werr + return i64(wn), werr } } return - }, + case .Query: + return query_utility({.Read, .Query}) + } + return 0, .Empty } // tee_reader_init returns a Reader that writes to 'w' what it reads from 'r' @@ -317,8 +321,8 @@ tee_reader_init :: proc(t: ^Tee_Reader, r: Reader, w: Writer, allocator := conte } tee_reader_to_reader :: proc(t: ^Tee_Reader) -> (r: Reader) { - r.stream_data = t - r.stream_vtable = _tee_reader_vtable + r.data = t + r.procedure = _tee_reader_proc return } @@ -332,9 +336,10 @@ Limited_Reader :: struct { } @(private) -_limited_reader_vtable := &Stream_VTable{ - impl_read = proc(s: Stream, p: []byte) -> (n: int, err: Error) { - l := (^Limited_Reader)(s.stream_data) +_limited_reader_proc :: proc(stream_data: rawptr, mode: Stream_Mode, p: []byte, offset: i64, whence: Seek_From) -> (n: i64, err: Error) { + l := (^Limited_Reader)(stream_data) + #partial switch mode { + case .Read: if l.n <= 0 { return 0, .EOF } @@ -342,10 +347,13 @@ _limited_reader_vtable := &Stream_VTable{ if i64(len(p)) > l.n { p = p[0:l.n] } - n, err = read(l.r, p) + n, err = _i64_err(read(l.r, p)) l.n -= i64(n) return - }, + case .Query: + return query_utility({.Read, .Query}) + } + return 0, .Empty } limited_reader_init :: proc(l: ^Limited_Reader, r: Reader, n: i64) -> Reader { @@ -355,8 +363,8 @@ limited_reader_init :: proc(l: ^Limited_Reader, r: Reader, n: i64) -> Reader { } limited_reader_to_reader :: proc(l: ^Limited_Reader) -> (r: Reader) { - r.stream_vtable = _limited_reader_vtable - r.stream_data = l + r.procedure = _limited_reader_proc + r.data = l return } @@ -375,15 +383,16 @@ section_reader_init :: proc(s: ^Section_Reader, r: Reader_At, off: i64, n: i64) return } section_reader_to_stream :: proc(s: ^Section_Reader) -> (out: Stream) { - out.stream_data = s - out.stream_vtable = _section_reader_vtable + out.data = s + out.procedure = _section_reader_proc return } @(private) -_section_reader_vtable := &Stream_VTable{ - impl_read = proc(stream: Stream, p: []byte) -> (n: int, err: Error) { - s := (^Section_Reader)(stream.stream_data) +_section_reader_proc :: proc(stream_data: rawptr, mode: Stream_Mode, p: []byte, offset: i64, whence: Seek_From) -> (n: i64, err: Error) { + s := (^Section_Reader)(stream_data) + #partial switch mode { + case .Read: if s.off >= s.limit { return 0, .EOF } @@ -391,13 +400,11 @@ _section_reader_vtable := &Stream_VTable{ if max := s.limit - s.off; i64(len(p)) > max { p = p[0:max] } - n, err = read_at(s.r, p, s.off) + n, err = _i64_err(read_at(s.r, p, s.off)) s.off += i64(n) return - }, - impl_read_at = proc(stream: Stream, p: []byte, off: i64) -> (n: int, err: Error) { - s := (^Section_Reader)(stream.stream_data) - p, off := p, off + case .Read_At: + p, off := p, offset if off < 0 || off >= s.limit - s.base { return 0, .EOF @@ -405,17 +412,15 @@ _section_reader_vtable := &Stream_VTable{ off += s.base if max := s.limit - off; i64(len(p)) > max { p = p[0:max] - n, err = read_at(s.r, p, off) + n, err = _i64_err(read_at(s.r, p, off)) if err == nil { err = .EOF } return } - return read_at(s.r, p, off) - }, - impl_seek = proc(stream: Stream, offset: i64, whence: Seek_From) -> (n: i64, err: Error) { - s := (^Section_Reader)(stream.stream_data) + return _i64_err(read_at(s.r, p, off)) + case .Seek: offset := offset switch whence { case: @@ -433,10 +438,12 @@ _section_reader_vtable := &Stream_VTable{ s.off = offset n = offset - s.base return - }, - impl_size = proc(stream: Stream) -> i64 { - s := (^Section_Reader)(stream.stream_data) - return s.limit - s.base - }, -} + case .Size: + n = s.limit - s.base + return + case .Query: + return query_utility({.Read, .Read_At, .Seek, .Size, .Query}) + } + return 0, nil +} diff --git a/core/os/stream.odin b/core/os/stream.odin index 9506ed3a3..946854cc6 100644 --- a/core/os/stream.odin +++ b/core/os/stream.odin @@ -4,66 +4,52 @@ import "core:io" stream_from_handle :: proc(fd: Handle) -> io.Stream { s: io.Stream - s.stream_data = rawptr(uintptr(fd)) - s.stream_vtable = &_file_stream_vtable + s.data = rawptr(uintptr(fd)) + s.procedure = _file_stream_proc return s } @(private) -_file_stream_vtable := io.Stream_VTable{ - impl_read = proc(s: io.Stream, p: []byte) -> (n: int, err: io.Error) { - fd := Handle(uintptr(s.stream_data)) - os_err: Errno - n, os_err = read(fd, p) - return - }, - impl_read_at = proc(s: io.Stream, p: []byte, offset: i64) -> (n: int, err: io.Error) { - when ODIN_OS == .Windows || ODIN_OS == .WASI { - fd := Handle(uintptr(s.stream_data)) - os_err: Errno - n, os_err = read_at(fd, p, offset) - } - return - }, - impl_write = proc(s: io.Stream, p: []byte) -> (n: int, err: io.Error) { - fd := Handle(uintptr(s.stream_data)) - os_err: Errno - n, os_err = write(fd, p) - return - }, - impl_write_at = proc(s: io.Stream, p: []byte, offset: i64) -> (n: int, err: io.Error) { - when ODIN_OS == .Windows || ODIN_OS == .WASI { - fd := Handle(uintptr(s.stream_data)) - os_err: Errno - n, os_err = write_at(fd, p, offset) - _ = os_err - } - return - }, - impl_seek = proc(s: io.Stream, offset: i64, whence: io.Seek_From) -> (i64, io.Error) { - fd := Handle(uintptr(s.stream_data)) - n, os_err := seek(fd, offset, int(whence)) - _ = os_err - return n, nil - }, - impl_size = proc(s: io.Stream) -> i64 { - fd := Handle(uintptr(s.stream_data)) - sz, _ := file_size(fd) - return sz - }, - impl_flush = proc(s: io.Stream) -> io.Error { +_file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) { + fd := Handle(uintptr(stream_data)) + n_int: int + os_err: Errno + switch mode { + case .Close: + close(fd) + case .Flush: when ODIN_OS == .Windows { - fd := Handle(uintptr(s.stream_data)) flush(fd) } else { // TOOD(bill): other operating systems } - return nil - }, - impl_close = proc(s: io.Stream) -> io.Error { - fd := Handle(uintptr(s.stream_data)) - close(fd) - return nil - }, + case .Read: + n_int, os_err = read(fd, p) + n = i64(n_int) + if os_err != 0 { + err = .Unknown + } + case .Read_At: + n_int, os_err = read_at(fd, p, offset) + n = i64(n_int) + case .Write: + n_int, os_err = write(fd, p) + n = i64(n_int) + case .Write_At: + n_int, os_err = write_at(fd, p, offset) + n = i64(n_int) + case .Seek: + n, os_err = seek(fd, offset, int(whence)) + case .Size: + n, os_err = file_size(fd) + case .Destroy: + err = .Empty + case .Query: + return io.query_utility({.Close, .Flush, .Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Query}) + } + if err == nil && os_err != 0 { + err = .Unknown + } + return } diff --git a/core/strings/builder.odin b/core/strings/builder.odin index edde4b297..28c56f6f9 100644 --- a/core/strings/builder.odin +++ b/core/strings/builder.odin @@ -164,36 +164,27 @@ builder_init :: proc{ builder_init_len_cap, } @(private) -_builder_stream_vtable_obj := io.Stream_VTable{ - impl_write = proc(s: io.Stream, p: []byte) -> (n: int, err: io.Error) { - b := (^Builder)(s.stream_data) - n = write_bytes(b, p) - if n < len(p) { +_builder_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) { + b := (^Builder)(stream_data) + #partial switch mode { + case .Write: + n = i64(write_bytes(b, p)) + if n < i64(len(p)) { err = .EOF } return - }, - impl_write_byte = proc(s: io.Stream, c: byte) -> (err: io.Error) { - b := (^Builder)(s.stream_data) - n := write_byte(b, c) - if n == 0 { - err = .EOF - } + case .Size: + n = i64(len(b.buf)) return - }, - impl_size = proc(s: io.Stream) -> i64 { - b := (^Builder)(s.stream_data) - return i64(len(b.buf)) - }, - impl_destroy = proc(s: io.Stream) -> io.Error { - b := (^Builder)(s.stream_data) + case .Destroy: builder_destroy(b) - return .None - }, + return + case .Query: + return io.query_utility({.Write, .Size, .Destroy, .Query}) + } + return 0, .Empty } -// NOTE(dweiler): Work around a miscompilation bug on Linux still. -@(private) -_builder_stream_vtable := &_builder_stream_vtable_obj + /* Returns an io.Stream from a Builder @@ -204,7 +195,7 @@ Returns: - res: the io.Stream */ to_stream :: proc(b: ^Builder) -> (res: io.Stream) { - return io.Stream{stream_vtable=_builder_stream_vtable, stream_data=b} + return io.Stream{procedure=_builder_stream_proc, data=b} } /* Returns an io.Writer from a Builder diff --git a/core/strings/reader.odin b/core/strings/reader.odin index 081e59b4b..bb49bf917 100644 --- a/core/strings/reader.odin +++ b/core/strings/reader.odin @@ -35,8 +35,8 @@ Returns: - s: An io.Stream for the given Reader */ reader_to_stream :: proc(r: ^Reader) -> (s: io.Stream) { - s.stream_data = r - s.stream_vtable = &_reader_vtable + s.data = r + s.procedure = _reader_proc return } /* @@ -294,41 +294,21 @@ This VTable is used by the Reader struct to provide its functionality as an `io.Stream`. */ @(private) -_reader_vtable := io.Stream_VTable{ - impl_size = proc(s: io.Stream) -> i64 { - r := (^Reader)(s.stream_data) - return reader_size(r) - }, - impl_read = proc(s: io.Stream, p: []byte) -> (n: int, err: io.Error) { - r := (^Reader)(s.stream_data) - return reader_read(r, p) - }, - impl_read_at = proc(s: io.Stream, p: []byte, off: i64) -> (n: int, err: io.Error) { - r := (^Reader)(s.stream_data) - return reader_read_at(r, p, off) - }, - impl_read_byte = proc(s: io.Stream) -> (byte, io.Error) { - r := (^Reader)(s.stream_data) - return reader_read_byte(r) - }, - impl_unread_byte = proc(s: io.Stream) -> io.Error { - r := (^Reader)(s.stream_data) - return reader_unread_byte(r) - }, - impl_read_rune = proc(s: io.Stream) -> (ch: rune, size: int, err: io.Error) { - r := (^Reader)(s.stream_data) - return reader_read_rune(r) - }, - impl_unread_rune = proc(s: io.Stream) -> io.Error { - r := (^Reader)(s.stream_data) - return reader_unread_rune(r) - }, - impl_seek = proc(s: io.Stream, offset: i64, whence: io.Seek_From) -> (i64, io.Error) { - r := (^Reader)(s.stream_data) - return reader_seek(r, offset, whence) - }, - impl_write_to = proc(s: io.Stream, w: io.Writer) -> (n: i64, err: io.Error) { - r := (^Reader)(s.stream_data) - return reader_write_to(r, w) - }, +_reader_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) { + r := (^Reader)(stream_data) + #partial switch mode { + case .Size: + n = reader_size(r) + return + case .Read: + return io._i64_err(reader_read(r, p)) + case .Read_At: + return io._i64_err(reader_read_at(r, p, offset)) + case .Seek: + n, err = reader_seek(r, offset, whence) + return + case .Query: + return io.query_utility({.Size, .Read, .Read_At, .Seek, .Query}) + } + return 0, .Empty } diff --git a/vendor/botan/blake2b/blake2b.odin b/vendor/botan/blake2b/blake2b.odin index 18fd89bd8..f0b3fdfa8 100644 --- a/vendor/botan/blake2b/blake2b.odin +++ b/vendor/botan/blake2b/blake2b.odin @@ -69,7 +69,7 @@ hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = s->impl_read(buf) + i, _ = io.read({s}, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } diff --git a/vendor/botan/gost/gost.odin b/vendor/botan/gost/gost.odin index bccc4d463..cf14ead15 100644 --- a/vendor/botan/gost/gost.odin +++ b/vendor/botan/gost/gost.odin @@ -69,7 +69,7 @@ hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = s->impl_read(buf) + i, _ = io.read({s}, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } diff --git a/vendor/botan/keccak/keccak.odin b/vendor/botan/keccak/keccak.odin index 4c82edc92..40d563d58 100644 --- a/vendor/botan/keccak/keccak.odin +++ b/vendor/botan/keccak/keccak.odin @@ -69,7 +69,7 @@ hash_stream_512 :: proc(s: io.Stream) -> ([DIGEST_SIZE_512]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = s->impl_read(buf) + i, _ = io.read({s}, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } diff --git a/vendor/botan/md4/md4.odin b/vendor/botan/md4/md4.odin index ddb7d5940..424756fae 100644 --- a/vendor/botan/md4/md4.odin +++ b/vendor/botan/md4/md4.odin @@ -69,7 +69,7 @@ hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = s->impl_read(buf) + i, _ = io.read({s}, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } diff --git a/vendor/botan/md5/md5.odin b/vendor/botan/md5/md5.odin index 9ea489669..a23c4fd01 100644 --- a/vendor/botan/md5/md5.odin +++ b/vendor/botan/md5/md5.odin @@ -69,7 +69,7 @@ hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = s->impl_read(buf) + i, _ = io.read({s}, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } diff --git a/vendor/botan/ripemd/ripemd.odin b/vendor/botan/ripemd/ripemd.odin index 33f0ba692..cf08aebcf 100644 --- a/vendor/botan/ripemd/ripemd.odin +++ b/vendor/botan/ripemd/ripemd.odin @@ -69,7 +69,7 @@ hash_stream_160 :: proc(s: io.Stream) -> ([DIGEST_SIZE_160]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = s->impl_read(buf) + i, _ = io.read({s}, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } diff --git a/vendor/botan/sha1/sha1.odin b/vendor/botan/sha1/sha1.odin index 96520f09e..e33e5a51a 100644 --- a/vendor/botan/sha1/sha1.odin +++ b/vendor/botan/sha1/sha1.odin @@ -69,7 +69,7 @@ hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = s->impl_read(buf) + i, _ = io.read({s}, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } diff --git a/vendor/botan/sha2/sha2.odin b/vendor/botan/sha2/sha2.odin index d583298ee..8283dde03 100644 --- a/vendor/botan/sha2/sha2.odin +++ b/vendor/botan/sha2/sha2.odin @@ -72,7 +72,7 @@ hash_stream_224 :: proc(s: io.Stream) -> ([DIGEST_SIZE_224]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = s->impl_read(buf) + i, _ = io.read({s}, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } @@ -151,7 +151,7 @@ hash_stream_256 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = s->impl_read(buf) + i, _ = io.read({s}, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } @@ -230,7 +230,7 @@ hash_stream_384 :: proc(s: io.Stream) -> ([DIGEST_SIZE_384]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = s->impl_read(buf) + i, _ = io.read({s}, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } @@ -309,7 +309,7 @@ hash_stream_512 :: proc(s: io.Stream) -> ([DIGEST_SIZE_512]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = s->impl_read(buf) + i, _ = io.read({s}, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } diff --git a/vendor/botan/sha3/sha3.odin b/vendor/botan/sha3/sha3.odin index 5f82be49c..f736d6665 100644 --- a/vendor/botan/sha3/sha3.odin +++ b/vendor/botan/sha3/sha3.odin @@ -72,7 +72,7 @@ hash_stream_224 :: proc(s: io.Stream) -> ([DIGEST_SIZE_224]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = s->impl_read(buf) + i, _ = io.read({s}, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } @@ -151,7 +151,7 @@ hash_stream_256 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = s->impl_read(buf) + i, _ = io.read({s}, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } @@ -230,7 +230,7 @@ hash_stream_384 :: proc(s: io.Stream) -> ([DIGEST_SIZE_384]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = s->impl_read(buf) + i, _ = io.read({s}, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } @@ -309,7 +309,7 @@ hash_stream_512 :: proc(s: io.Stream) -> ([DIGEST_SIZE_512]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = s->impl_read(buf) + i, _ = io.read({s}, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } diff --git a/vendor/botan/shake/shake.odin b/vendor/botan/shake/shake.odin index b973fee24..0a286c72e 100644 --- a/vendor/botan/shake/shake.odin +++ b/vendor/botan/shake/shake.odin @@ -70,7 +70,7 @@ hash_stream_128 :: proc(s: io.Stream) -> ([DIGEST_SIZE_128]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = s->impl_read(buf) + i, _ = io.read({s}, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } @@ -149,7 +149,7 @@ hash_stream_256 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = s->impl_read(buf) + i, _ = io.read({s}, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } diff --git a/vendor/botan/skein512/skein512.odin b/vendor/botan/skein512/skein512.odin index 41ffaefff..7dbfaf438 100644 --- a/vendor/botan/skein512/skein512.odin +++ b/vendor/botan/skein512/skein512.odin @@ -72,7 +72,7 @@ hash_stream_256 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = s->impl_read(buf) + i, _ = io.read({s}, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } @@ -151,7 +151,7 @@ hash_stream_512 :: proc(s: io.Stream) -> ([DIGEST_SIZE_512]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = s->impl_read(buf) + i, _ = io.read({s}, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } @@ -230,7 +230,7 @@ hash_stream_slice :: proc(s: io.Stream, bit_size: int, allocator := context.allo defer delete(buf) i := 1 for i > 0 { - i, _ = s->impl_read(buf) + i, _ = io.read({s}, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } diff --git a/vendor/botan/sm3/sm3.odin b/vendor/botan/sm3/sm3.odin index 52fe9a488..c23885445 100644 --- a/vendor/botan/sm3/sm3.odin +++ b/vendor/botan/sm3/sm3.odin @@ -69,7 +69,7 @@ hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = s->impl_read(buf) + i, _ = io.read({s}, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } diff --git a/vendor/botan/streebog/streebog.odin b/vendor/botan/streebog/streebog.odin index fdc07923f..45d535192 100644 --- a/vendor/botan/streebog/streebog.odin +++ b/vendor/botan/streebog/streebog.odin @@ -70,7 +70,7 @@ hash_stream_256 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = s->impl_read(buf) + i, _ = io.read({s}, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } @@ -149,7 +149,7 @@ hash_stream_512 :: proc(s: io.Stream) -> ([DIGEST_SIZE_512]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = s->impl_read(buf) + i, _ = io.read({s}, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } diff --git a/vendor/botan/tiger/tiger.odin b/vendor/botan/tiger/tiger.odin index 3d7e064d0..90efd8142 100644 --- a/vendor/botan/tiger/tiger.odin +++ b/vendor/botan/tiger/tiger.odin @@ -71,7 +71,7 @@ hash_stream_128 :: proc(s: io.Stream) -> ([DIGEST_SIZE_128]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = s->impl_read(buf) + i, _ = io.read({s}, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } @@ -150,7 +150,7 @@ hash_stream_160 :: proc(s: io.Stream) -> ([DIGEST_SIZE_160]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = s->impl_read(buf) + i, _ = io.read({s}, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } @@ -229,7 +229,7 @@ hash_stream_192 :: proc(s: io.Stream) -> ([DIGEST_SIZE_192]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = s->impl_read(buf) + i, _ = io.read({s}, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } diff --git a/vendor/botan/whirlpool/whirlpool.odin b/vendor/botan/whirlpool/whirlpool.odin index c32ff20c0..15f5109d9 100644 --- a/vendor/botan/whirlpool/whirlpool.odin +++ b/vendor/botan/whirlpool/whirlpool.odin @@ -69,7 +69,7 @@ hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = s->impl_read(buf) + i, _ = io.read({s}, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } From 9ee4b76cd950e5eef9d480831670ae7bcff64775 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 8 Jun 2023 16:38:57 +0100 Subject: [PATCH 11/97] Just make the `io.Reader` etc aliases --- core/bufio/writer.odin | 2 +- core/compress/common.odin | 2 +- core/crypto/blake/blake.odin | 8 ++-- core/crypto/blake2b/blake2b.odin | 2 +- core/crypto/blake2s/blake2s.odin | 2 +- core/crypto/gost/gost.odin | 2 +- core/crypto/groestl/groestl.odin | 8 ++-- core/crypto/haval/haval.odin | 30 +++++++------- core/crypto/jh/jh.odin | 8 ++-- core/crypto/keccak/keccak.odin | 8 ++-- core/crypto/md2/md2.odin | 2 +- core/crypto/md4/md4.odin | 2 +- core/crypto/md5/md5.odin | 2 +- core/crypto/ripemd/ripemd.odin | 8 ++-- core/crypto/sha1/sha1.odin | 2 +- core/crypto/sha2/sha2.odin | 8 ++-- core/crypto/sha3/sha3.odin | 8 ++-- core/crypto/shake/shake.odin | 4 +- core/crypto/sm3/sm3.odin | 2 +- core/crypto/streebog/streebog.odin | 4 +- core/crypto/tiger/tiger.odin | 6 +-- core/crypto/tiger2/tiger2.odin | 6 +-- core/crypto/whirlpool/whirlpool.odin | 2 +- core/fmt/fmt_os.odin | 10 ++--- core/io/conv.odin | 30 +++++++------- core/io/io.odin | 57 ++++++++++++--------------- vendor/botan/blake2b/blake2b.odin | 2 +- vendor/botan/gost/gost.odin | 2 +- vendor/botan/keccak/keccak.odin | 2 +- vendor/botan/md4/md4.odin | 2 +- vendor/botan/md5/md5.odin | 2 +- vendor/botan/ripemd/ripemd.odin | 2 +- vendor/botan/sha1/sha1.odin | 2 +- vendor/botan/sha2/sha2.odin | 8 ++-- vendor/botan/sha3/sha3.odin | 8 ++-- vendor/botan/shake/shake.odin | 4 +- vendor/botan/skein512/skein512.odin | 6 +-- vendor/botan/sm3/sm3.odin | 2 +- vendor/botan/streebog/streebog.odin | 4 +- vendor/botan/tiger/tiger.odin | 6 +-- vendor/botan/whirlpool/whirlpool.odin | 2 +- 41 files changed, 137 insertions(+), 142 deletions(-) diff --git a/core/bufio/writer.odin b/core/bufio/writer.odin index b4234af43..bfa8b804f 100644 --- a/core/bufio/writer.odin +++ b/core/bufio/writer.odin @@ -221,7 +221,7 @@ writer_to_stream :: proc(b: ^Writer) -> (s: io.Stream) { // writer_to_stream converts a Writer into an io.Stream writer_to_writer :: proc(b: ^Writer) -> (s: io.Writer) { - return {writer_to_stream(b)} + return writer_to_stream(b) } diff --git a/core/compress/common.odin b/core/compress/common.odin index 2d00ed359..bc56229c2 100644 --- a/core/compress/common.odin +++ b/core/compress/common.odin @@ -216,7 +216,7 @@ read_slice_from_stream :: #force_inline proc(z: ^Context_Stream_Input, size: int // TODO: REMOVE ALL USE OF context.temp_allocator here // the is literally no need for it b := make([]u8, size, context.temp_allocator) - _, e := io.read({z.input}, b[:]) + _, e := io.read(z.input, b[:]) if e == .None { return b, .None } diff --git a/core/crypto/blake/blake.odin b/core/crypto/blake/blake.odin index 67f3c0d35..3685109e4 100644 --- a/core/crypto/blake/blake.odin +++ b/core/crypto/blake/blake.odin @@ -70,7 +70,7 @@ hash_stream_224 :: proc(s: io.Stream) -> ([DIGEST_SIZE_224]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { update(&ctx, buf[:read]) } @@ -149,7 +149,7 @@ hash_stream_256 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { update(&ctx, buf[:read]) } @@ -228,7 +228,7 @@ hash_stream_384 :: proc(s: io.Stream) -> ([DIGEST_SIZE_384]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { update(&ctx, buf[:read]) } @@ -307,7 +307,7 @@ hash_stream_512 :: proc(s: io.Stream) -> ([DIGEST_SIZE_512]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { update(&ctx, buf[:read]) } diff --git a/core/crypto/blake2b/blake2b.odin b/core/crypto/blake2b/blake2b.odin index 82dabc542..8f0770f82 100644 --- a/core/crypto/blake2b/blake2b.odin +++ b/core/crypto/blake2b/blake2b.odin @@ -77,7 +77,7 @@ hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { _blake2.update(&ctx, buf[:read]) } diff --git a/core/crypto/blake2s/blake2s.odin b/core/crypto/blake2s/blake2s.odin index a767d4d70..6a2d4ab9b 100644 --- a/core/crypto/blake2s/blake2s.odin +++ b/core/crypto/blake2s/blake2s.odin @@ -77,7 +77,7 @@ hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { _blake2.update(&ctx, buf[:read]) } diff --git a/core/crypto/gost/gost.odin b/core/crypto/gost/gost.odin index fa2df862f..5aca8ce95 100644 --- a/core/crypto/gost/gost.odin +++ b/core/crypto/gost/gost.odin @@ -65,7 +65,7 @@ hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { update(&ctx, buf[:read]) } diff --git a/core/crypto/groestl/groestl.odin b/core/crypto/groestl/groestl.odin index 604a3eb9b..61460808f 100644 --- a/core/crypto/groestl/groestl.odin +++ b/core/crypto/groestl/groestl.odin @@ -70,7 +70,7 @@ hash_stream_224 :: proc(s: io.Stream) -> ([DIGEST_SIZE_224]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { update(&ctx, buf[:read]) } @@ -149,7 +149,7 @@ hash_stream_256 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { update(&ctx, buf[:read]) } @@ -228,7 +228,7 @@ hash_stream_384 :: proc(s: io.Stream) -> ([DIGEST_SIZE_384]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { update(&ctx, buf[:read]) } @@ -307,7 +307,7 @@ hash_stream_512 :: proc(s: io.Stream) -> ([DIGEST_SIZE_512]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { update(&ctx, buf[:read]) } diff --git a/core/crypto/haval/haval.odin b/core/crypto/haval/haval.odin index 76cfbecbe..b98facb33 100644 --- a/core/crypto/haval/haval.odin +++ b/core/crypto/haval/haval.odin @@ -79,7 +79,7 @@ hash_stream_128_3 :: proc(s: io.Stream) -> ([DIGEST_SIZE_128]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) ctx.str_len = u32(len(buf[:read])) if read > 0 { update(&ctx, buf[:read]) @@ -164,7 +164,7 @@ hash_stream_128_4 :: proc(s: io.Stream) -> ([DIGEST_SIZE_128]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) ctx.str_len = u32(len(buf[:read])) if read > 0 { update(&ctx, buf[:read]) @@ -249,7 +249,7 @@ hash_stream_128_5 :: proc(s: io.Stream) -> ([DIGEST_SIZE_128]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) ctx.str_len = u32(len(buf[:read])) if read > 0 { update(&ctx, buf[:read]) @@ -334,7 +334,7 @@ hash_stream_160_3 :: proc(s: io.Stream) -> ([DIGEST_SIZE_160]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) ctx.str_len = u32(len(buf[:read])) if read > 0 { update(&ctx, buf[:read]) @@ -419,7 +419,7 @@ hash_stream_160_4 :: proc(s: io.Stream) -> ([DIGEST_SIZE_160]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) ctx.str_len = u32(len(buf[:read])) if read > 0 { update(&ctx, buf[:read]) @@ -504,7 +504,7 @@ hash_stream_160_5 :: proc(s: io.Stream) -> ([DIGEST_SIZE_160]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) ctx.str_len = u32(len(buf[:read])) if read > 0 { update(&ctx, buf[:read]) @@ -589,7 +589,7 @@ hash_stream_192_3 :: proc(s: io.Stream) -> ([DIGEST_SIZE_192]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) ctx.str_len = u32(len(buf[:read])) if read > 0 { update(&ctx, buf[:read]) @@ -674,7 +674,7 @@ hash_stream_192_4 :: proc(s: io.Stream) -> ([DIGEST_SIZE_192]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) ctx.str_len = u32(len(buf[:read])) if read > 0 { update(&ctx, buf[:read]) @@ -759,7 +759,7 @@ hash_stream_192_5 :: proc(s: io.Stream) -> ([DIGEST_SIZE_192]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) ctx.str_len = u32(len(buf[:read])) if read > 0 { update(&ctx, buf[:read]) @@ -844,7 +844,7 @@ hash_stream_224_3 :: proc(s: io.Stream) -> ([DIGEST_SIZE_224]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) ctx.str_len = u32(len(buf[:read])) if read > 0 { update(&ctx, buf[:read]) @@ -929,7 +929,7 @@ hash_stream_224_4 :: proc(s: io.Stream) -> ([DIGEST_SIZE_224]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) ctx.str_len = u32(len(buf[:read])) if read > 0 { update(&ctx, buf[:read]) @@ -1014,7 +1014,7 @@ hash_stream_224_5 :: proc(s: io.Stream) -> ([DIGEST_SIZE_224]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) ctx.str_len = u32(len(buf[:read])) if read > 0 { update(&ctx, buf[:read]) @@ -1099,7 +1099,7 @@ hash_stream_256_3 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) ctx.str_len = u32(len(buf[:read])) if read > 0 { update(&ctx, buf[:read]) @@ -1184,7 +1184,7 @@ hash_stream_256_4 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) ctx.str_len = u32(len(buf[:read])) if read > 0 { update(&ctx, buf[:read]) @@ -1270,7 +1270,7 @@ hash_stream_256_5 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) ctx.str_len = u32(len(buf[:read])) if read > 0 { update(&ctx, buf[:read]) diff --git a/core/crypto/jh/jh.odin b/core/crypto/jh/jh.odin index 6b4a73b46..5dc6c4e6b 100644 --- a/core/crypto/jh/jh.odin +++ b/core/crypto/jh/jh.odin @@ -70,7 +70,7 @@ hash_stream_224 :: proc(s: io.Stream) -> ([DIGEST_SIZE_224]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { update(&ctx, buf[:read]) } @@ -149,7 +149,7 @@ hash_stream_256 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { update(&ctx, buf[:read]) } @@ -228,7 +228,7 @@ hash_stream_384 :: proc(s: io.Stream) -> ([DIGEST_SIZE_384]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { update(&ctx, buf[:read]) } @@ -307,7 +307,7 @@ hash_stream_512 :: proc(s: io.Stream) -> ([DIGEST_SIZE_512]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { update(&ctx, buf[:read]) } diff --git a/core/crypto/keccak/keccak.odin b/core/crypto/keccak/keccak.odin index c09c9ddbe..4c74858d2 100644 --- a/core/crypto/keccak/keccak.odin +++ b/core/crypto/keccak/keccak.odin @@ -77,7 +77,7 @@ hash_stream_224 :: proc(s: io.Stream) -> ([DIGEST_SIZE_224]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { _sha3.update(&ctx, buf[:read]) } @@ -159,7 +159,7 @@ hash_stream_256 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { _sha3.update(&ctx, buf[:read]) } @@ -241,7 +241,7 @@ hash_stream_384 :: proc(s: io.Stream) -> ([DIGEST_SIZE_384]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { _sha3.update(&ctx, buf[:read]) } @@ -323,7 +323,7 @@ hash_stream_512 :: proc(s: io.Stream) -> ([DIGEST_SIZE_512]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { _sha3.update(&ctx, buf[:read]) } diff --git a/core/crypto/md2/md2.odin b/core/crypto/md2/md2.odin index 693e2c6b0..4942183e1 100644 --- a/core/crypto/md2/md2.odin +++ b/core/crypto/md2/md2.odin @@ -64,7 +64,7 @@ hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { update(&ctx, buf[:read]) } diff --git a/core/crypto/md4/md4.odin b/core/crypto/md4/md4.odin index 1f161e1cb..8efdbb5a5 100644 --- a/core/crypto/md4/md4.odin +++ b/core/crypto/md4/md4.odin @@ -68,7 +68,7 @@ hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { update(&ctx, buf[:read]) } diff --git a/core/crypto/md5/md5.odin b/core/crypto/md5/md5.odin index db04e67df..858480b04 100644 --- a/core/crypto/md5/md5.odin +++ b/core/crypto/md5/md5.odin @@ -67,7 +67,7 @@ hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { update(&ctx, buf[:read]) } diff --git a/core/crypto/ripemd/ripemd.odin b/core/crypto/ripemd/ripemd.odin index 257f4144e..f9edb121b 100644 --- a/core/crypto/ripemd/ripemd.odin +++ b/core/crypto/ripemd/ripemd.odin @@ -69,7 +69,7 @@ hash_stream_128 :: proc(s: io.Stream) -> ([DIGEST_SIZE_128]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { update(&ctx, buf[:read]) } @@ -145,7 +145,7 @@ hash_stream_160 :: proc(s: io.Stream) -> ([DIGEST_SIZE_160]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { update(&ctx, buf[:read]) } @@ -221,7 +221,7 @@ hash_stream_256 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { update(&ctx, buf[:read]) } @@ -297,7 +297,7 @@ hash_stream_320 :: proc(s: io.Stream) -> ([DIGEST_SIZE_320]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { update(&ctx, buf[:read]) } diff --git a/core/crypto/sha1/sha1.odin b/core/crypto/sha1/sha1.odin index 2e1991444..599d1791e 100644 --- a/core/crypto/sha1/sha1.odin +++ b/core/crypto/sha1/sha1.odin @@ -67,7 +67,7 @@ hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { update(&ctx, buf[:read]) } diff --git a/core/crypto/sha2/sha2.odin b/core/crypto/sha2/sha2.odin index cbd3a6b39..0f55c4be1 100644 --- a/core/crypto/sha2/sha2.odin +++ b/core/crypto/sha2/sha2.odin @@ -74,7 +74,7 @@ hash_stream_224 :: proc(s: io.Stream) -> ([DIGEST_SIZE_224]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { update(&ctx, buf[:read]) } @@ -153,7 +153,7 @@ hash_stream_256 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { update(&ctx, buf[:read]) } @@ -232,7 +232,7 @@ hash_stream_384 :: proc(s: io.Stream) -> ([DIGEST_SIZE_384]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { update(&ctx, buf[:read]) } @@ -311,7 +311,7 @@ hash_stream_512 :: proc(s: io.Stream) -> ([DIGEST_SIZE_512]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { update(&ctx, buf[:read]) } diff --git a/core/crypto/sha3/sha3.odin b/core/crypto/sha3/sha3.odin index ec6fbc971..5d8ad2106 100644 --- a/core/crypto/sha3/sha3.odin +++ b/core/crypto/sha3/sha3.odin @@ -73,7 +73,7 @@ hash_stream_224 :: proc(s: io.Stream) -> ([DIGEST_SIZE_224]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { _sha3.update(&ctx, buf[:read]) } @@ -152,7 +152,7 @@ hash_stream_256 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { _sha3.update(&ctx, buf[:read]) } @@ -231,7 +231,7 @@ hash_stream_384 :: proc(s: io.Stream) -> ([DIGEST_SIZE_384]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { _sha3.update(&ctx, buf[:read]) } @@ -310,7 +310,7 @@ hash_stream_512 :: proc(s: io.Stream) -> ([DIGEST_SIZE_512]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { _sha3.update(&ctx, buf[:read]) } diff --git a/core/crypto/shake/shake.odin b/core/crypto/shake/shake.odin index 50ef756aa..020ba68f3 100644 --- a/core/crypto/shake/shake.odin +++ b/core/crypto/shake/shake.odin @@ -73,7 +73,7 @@ hash_stream_128 :: proc(s: io.Stream) -> ([DIGEST_SIZE_128]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { _sha3.update(&ctx, buf[:read]) } @@ -155,7 +155,7 @@ hash_stream_256 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { _sha3.update(&ctx, buf[:read]) } diff --git a/core/crypto/sm3/sm3.odin b/core/crypto/sm3/sm3.odin index b35e20e37..9e684ff08 100644 --- a/core/crypto/sm3/sm3.odin +++ b/core/crypto/sm3/sm3.odin @@ -66,7 +66,7 @@ hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { update(&ctx, buf[:read]) } diff --git a/core/crypto/streebog/streebog.odin b/core/crypto/streebog/streebog.odin index aa4dd7906..42da1e695 100644 --- a/core/crypto/streebog/streebog.odin +++ b/core/crypto/streebog/streebog.odin @@ -70,7 +70,7 @@ hash_stream_256 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { update(&ctx, buf[:read]) } @@ -146,7 +146,7 @@ hash_stream_512 :: proc(s: io.Stream) -> ([DIGEST_SIZE_512]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { update(&ctx, buf[:read]) } diff --git a/core/crypto/tiger/tiger.odin b/core/crypto/tiger/tiger.odin index a4982d7c4..614926129 100644 --- a/core/crypto/tiger/tiger.odin +++ b/core/crypto/tiger/tiger.odin @@ -71,7 +71,7 @@ hash_stream_128 :: proc(s: io.Stream) -> ([DIGEST_SIZE_128]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { _tiger.update(&ctx, buf[:read]) } @@ -150,7 +150,7 @@ hash_stream_160 :: proc(s: io.Stream) -> ([DIGEST_SIZE_160]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { _tiger.update(&ctx, buf[:read]) } @@ -229,7 +229,7 @@ hash_stream_192 :: proc(s: io.Stream) -> ([DIGEST_SIZE_192]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { _tiger.update(&ctx, buf[:read]) } diff --git a/core/crypto/tiger2/tiger2.odin b/core/crypto/tiger2/tiger2.odin index 432387bdf..ead874d56 100644 --- a/core/crypto/tiger2/tiger2.odin +++ b/core/crypto/tiger2/tiger2.odin @@ -71,7 +71,7 @@ hash_stream_128 :: proc(s: io.Stream) -> ([DIGEST_SIZE_128]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { _tiger.update(&ctx, buf[:read]) } @@ -150,7 +150,7 @@ hash_stream_160 :: proc(s: io.Stream) -> ([DIGEST_SIZE_160]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { _tiger.update(&ctx, buf[:read]) } @@ -229,7 +229,7 @@ hash_stream_192 :: proc(s: io.Stream) -> ([DIGEST_SIZE_192]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { _tiger.update(&ctx, buf[:read]) } diff --git a/core/crypto/whirlpool/whirlpool.odin b/core/crypto/whirlpool/whirlpool.odin index 8ae6f413e..cf0bf6490 100644 --- a/core/crypto/whirlpool/whirlpool.odin +++ b/core/crypto/whirlpool/whirlpool.odin @@ -66,7 +66,7 @@ hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) { defer delete(buf) read := 1 for read > 0 { - read, _ = io.read({s}, buf) + read, _ = io.read(s, buf) if read > 0 { update(&ctx, buf[:read]) } diff --git a/core/fmt/fmt_os.odin b/core/fmt/fmt_os.odin index 861b0c3b9..299d8f017 100644 --- a/core/fmt/fmt_os.odin +++ b/core/fmt/fmt_os.odin @@ -12,7 +12,7 @@ fprint :: proc(fd: os.Handle, args: ..any, sep := " ") -> int { b: bufio.Writer defer bufio.writer_flush(&b) - bufio.writer_init_with_buf(&b, {os.stream_from_handle(fd)}, buf[:]) + bufio.writer_init_with_buf(&b, os.stream_from_handle(fd), buf[:]) w := bufio.writer_to_writer(&b) return wprint(w=w, args=args, sep=sep) } @@ -23,7 +23,7 @@ fprintln :: proc(fd: os.Handle, args: ..any, sep := " ") -> int { b: bufio.Writer defer bufio.writer_flush(&b) - bufio.writer_init_with_buf(&b, {os.stream_from_handle(fd)}, buf[:]) + bufio.writer_init_with_buf(&b, os.stream_from_handle(fd), buf[:]) w := bufio.writer_to_writer(&b) return wprintln(w=w, args=args, sep=sep) @@ -34,7 +34,7 @@ fprintf :: proc(fd: os.Handle, fmt: string, args: ..any) -> int { b: bufio.Writer defer bufio.writer_flush(&b) - bufio.writer_init_with_buf(&b, {os.stream_from_handle(fd)}, buf[:]) + bufio.writer_init_with_buf(&b, os.stream_from_handle(fd), buf[:]) w := bufio.writer_to_writer(&b) return wprintf(w, fmt, ..args) @@ -44,7 +44,7 @@ fprint_type :: proc(fd: os.Handle, info: ^runtime.Type_Info) -> (n: int, err: io b: bufio.Writer defer bufio.writer_flush(&b) - bufio.writer_init_with_buf(&b, {os.stream_from_handle(fd)}, buf[:]) + bufio.writer_init_with_buf(&b, os.stream_from_handle(fd), buf[:]) w := bufio.writer_to_writer(&b) return wprint_type(w, info) @@ -54,7 +54,7 @@ fprint_typeid :: proc(fd: os.Handle, id: typeid) -> (n: int, err: io.Error) { b: bufio.Writer defer bufio.writer_flush(&b) - bufio.writer_init_with_buf(&b, {os.stream_from_handle(fd)}, buf[:]) + bufio.writer_init_with_buf(&b, os.stream_from_handle(fd), buf[:]) w := bufio.writer_to_writer(&b) return wprint_typeid(w, id) diff --git a/core/io/conv.odin b/core/io/conv.odin index 2b1351e18..e3286baca 100644 --- a/core/io/conv.odin +++ b/core/io/conv.odin @@ -1,80 +1,80 @@ package io to_reader :: proc(s: Stream) -> (r: Reader, ok: bool = true) #optional_ok { - r.stream = s + r = s ok = .Read in query(s) return } to_writer :: proc(s: Stream) -> (w: Writer, ok: bool = true) #optional_ok { - w.stream = s + w = s ok = .Write in query(s) return } to_closer :: proc(s: Stream) -> (c: Closer, ok: bool = true) #optional_ok { - c.stream = s + c = s ok = .Close in query(s) return } to_flusher :: proc(s: Stream) -> (f: Flusher, ok: bool = true) #optional_ok { - f.stream = s + f = s ok = .Flush in query(s) return } to_seeker :: proc(s: Stream) -> (seeker: Seeker, ok: bool = true) #optional_ok { - seeker.stream = s + seeker = s ok = .Seek in query(s) return } to_read_writer :: proc(s: Stream) -> (r: Read_Writer, ok: bool = true) #optional_ok { - r.stream = s + r = s ok = query(s) >= {.Read, .Write} return } to_read_closer :: proc(s: Stream) -> (r: Read_Closer, ok: bool = true) #optional_ok { - r.stream = s + r = s ok = query(s) >= {.Read, .Close} return } to_read_write_closer :: proc(s: Stream) -> (r: Read_Write_Closer, ok: bool = true) #optional_ok { - r.stream = s + r = s ok = query(s) >= {.Read, .Write, .Close} return } to_read_write_seeker :: proc(s: Stream) -> (r: Read_Write_Seeker, ok: bool = true) #optional_ok { - r.stream = s + r = s ok = query(s) >= {.Read, .Write, .Seek} return } to_write_flusher :: proc(s: Stream) -> (w: Write_Flusher, ok: bool = true) #optional_ok { - w.stream = s + w = s ok = query(s) >= {.Write, .Flush} return } to_write_flush_closer :: proc(s: Stream) -> (w: Write_Flush_Closer, ok: bool = true) #optional_ok { - w.stream = s + w = s ok = query(s) >= {.Write, .Flush, .Close} return } to_reader_at :: proc(s: Stream) -> (r: Reader_At, ok: bool = true) #optional_ok { - r.stream = s + r = s ok = query(s) >= {.Read_At} return } to_writer_at :: proc(s: Stream) -> (w: Writer_At, ok: bool = true) #optional_ok { - w.stream = s + w = s ok = query(s) >= {.Write_At} return } to_write_closer :: proc(s: Stream) -> (w: Write_Closer, ok: bool = true) #optional_ok { - w.stream = s + w = s ok = query(s) >= {.Write, .Close} return } to_write_seeker :: proc(s: Stream) -> (w: Write_Seeker, ok: bool = true) #optional_ok { - w.stream = s + w = s ok = query(s) >= {.Write, .Seek} return } diff --git a/core/io/io.odin b/core/io/io.odin index 868e16204..566e13c54 100644 --- a/core/io/io.odin +++ b/core/io/io.odin @@ -75,29 +75,29 @@ Stream :: struct { data: rawptr, } -Reader :: struct {using stream: Stream} -Writer :: struct {using stream: Stream} -Closer :: struct {using stream: Stream} -Flusher :: struct {using stream: Stream} -Seeker :: struct {using stream: Stream} +Reader :: Stream +Writer :: Stream +Closer :: Stream +Flusher :: Stream +Seeker :: Stream -Read_Writer :: struct {using stream: Stream} -Read_Closer :: struct {using stream: Stream} -Read_Write_Closer :: struct {using stream: Stream} -Read_Write_Seeker :: struct {using stream: Stream} +Read_Writer :: Stream +Read_Closer :: Stream +Read_Write_Closer :: Stream +Read_Write_Seeker :: Stream -Write_Closer :: struct {using stream: Stream} -Write_Seeker :: struct {using stream: Stream} -Write_Flusher :: struct {using stream: Stream} -Write_Flush_Closer :: struct {using stream: Stream} +Write_Closer :: Stream +Write_Seeker :: Stream +Write_Flusher :: Stream +Write_Flush_Closer :: Stream -Reader_At :: struct {using stream: Stream} -Writer_At :: struct {using stream: Stream} +Reader_At :: Stream +Writer_At :: Stream destroy :: proc(s: Stream) -> (err: Error) { - _ = flush({s}) - _ = close({s}) + _ = flush(s) + _ = close(s) if s.procedure != nil { _, err = s.procedure(s.data, .Destroy, nil, 0, nil) } else { @@ -192,11 +192,10 @@ size :: proc(s: Stream) -> (n: i64, err: Error) { if s.procedure != nil { n, err = s.procedure(s.data, .Size, nil, 0, nil) if err == .Empty { - seeker := Seeker{s} n = 0 - curr := seek(seeker, 0, .Current) or_return - end := seek(seeker, 0, .End) or_return - seek(seeker, curr, .Start) or_return + curr := seek(s, 0, .Current) or_return + end := seek(s, 0, .End) or_return + seek(s, curr, .Start) or_return n = end } } else { @@ -220,11 +219,9 @@ read_at :: proc(r: Reader_At, p: []byte, offset: i64, n_read: ^int = nil) -> (n: if err != .Empty { n = int(n64) } else { - seeker := Seeker{r} - reader := Reader{r} - curr := seek(seeker, offset, .Current) or_return - n, err = read(reader, p) - _, err1 := seek(seeker, curr, .Start) + curr := seek(r, offset, .Current) or_return + n, err = read(r, p) + _, err1 := seek(r, curr, .Start) if err1 != nil && err == nil { err = err1 } @@ -248,11 +245,9 @@ write_at :: proc(w: Writer_At, p: []byte, offset: i64, n_written: ^int = nil) -> if err != .Empty { n = int(n64) } else { - seeker := Seeker{w} - writer := Writer{w} - curr := seek(seeker, offset, .Current) or_return - n, err = write(writer, p) - _, err1 := seek(seeker, curr, .Start) + curr := seek(w, offset, .Current) or_return + n, err = write(w, p) + _, err1 := seek(w, curr, .Start) if err1 != nil && err == nil { err = err1 } diff --git a/vendor/botan/blake2b/blake2b.odin b/vendor/botan/blake2b/blake2b.odin index f0b3fdfa8..6cc828caf 100644 --- a/vendor/botan/blake2b/blake2b.odin +++ b/vendor/botan/blake2b/blake2b.odin @@ -69,7 +69,7 @@ hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = io.read({s}, buf) + i, _ = io.read(s, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } diff --git a/vendor/botan/gost/gost.odin b/vendor/botan/gost/gost.odin index cf14ead15..5b3db31fe 100644 --- a/vendor/botan/gost/gost.odin +++ b/vendor/botan/gost/gost.odin @@ -69,7 +69,7 @@ hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = io.read({s}, buf) + i, _ = io.read(s, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } diff --git a/vendor/botan/keccak/keccak.odin b/vendor/botan/keccak/keccak.odin index 40d563d58..c08eaf598 100644 --- a/vendor/botan/keccak/keccak.odin +++ b/vendor/botan/keccak/keccak.odin @@ -69,7 +69,7 @@ hash_stream_512 :: proc(s: io.Stream) -> ([DIGEST_SIZE_512]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = io.read({s}, buf) + i, _ = io.read(s, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } diff --git a/vendor/botan/md4/md4.odin b/vendor/botan/md4/md4.odin index 424756fae..02c33dde9 100644 --- a/vendor/botan/md4/md4.odin +++ b/vendor/botan/md4/md4.odin @@ -69,7 +69,7 @@ hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = io.read({s}, buf) + i, _ = io.read(s, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } diff --git a/vendor/botan/md5/md5.odin b/vendor/botan/md5/md5.odin index a23c4fd01..9aaf96d27 100644 --- a/vendor/botan/md5/md5.odin +++ b/vendor/botan/md5/md5.odin @@ -69,7 +69,7 @@ hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = io.read({s}, buf) + i, _ = io.read(s, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } diff --git a/vendor/botan/ripemd/ripemd.odin b/vendor/botan/ripemd/ripemd.odin index cf08aebcf..ddb549350 100644 --- a/vendor/botan/ripemd/ripemd.odin +++ b/vendor/botan/ripemd/ripemd.odin @@ -69,7 +69,7 @@ hash_stream_160 :: proc(s: io.Stream) -> ([DIGEST_SIZE_160]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = io.read({s}, buf) + i, _ = io.read(s, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } diff --git a/vendor/botan/sha1/sha1.odin b/vendor/botan/sha1/sha1.odin index e33e5a51a..c39a41d0a 100644 --- a/vendor/botan/sha1/sha1.odin +++ b/vendor/botan/sha1/sha1.odin @@ -69,7 +69,7 @@ hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = io.read({s}, buf) + i, _ = io.read(s, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } diff --git a/vendor/botan/sha2/sha2.odin b/vendor/botan/sha2/sha2.odin index 8283dde03..4ce001a75 100644 --- a/vendor/botan/sha2/sha2.odin +++ b/vendor/botan/sha2/sha2.odin @@ -72,7 +72,7 @@ hash_stream_224 :: proc(s: io.Stream) -> ([DIGEST_SIZE_224]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = io.read({s}, buf) + i, _ = io.read(s, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } @@ -151,7 +151,7 @@ hash_stream_256 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = io.read({s}, buf) + i, _ = io.read(s, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } @@ -230,7 +230,7 @@ hash_stream_384 :: proc(s: io.Stream) -> ([DIGEST_SIZE_384]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = io.read({s}, buf) + i, _ = io.read(s, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } @@ -309,7 +309,7 @@ hash_stream_512 :: proc(s: io.Stream) -> ([DIGEST_SIZE_512]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = io.read({s}, buf) + i, _ = io.read(s, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } diff --git a/vendor/botan/sha3/sha3.odin b/vendor/botan/sha3/sha3.odin index f736d6665..5dcb008ce 100644 --- a/vendor/botan/sha3/sha3.odin +++ b/vendor/botan/sha3/sha3.odin @@ -72,7 +72,7 @@ hash_stream_224 :: proc(s: io.Stream) -> ([DIGEST_SIZE_224]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = io.read({s}, buf) + i, _ = io.read(s, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } @@ -151,7 +151,7 @@ hash_stream_256 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = io.read({s}, buf) + i, _ = io.read(s, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } @@ -230,7 +230,7 @@ hash_stream_384 :: proc(s: io.Stream) -> ([DIGEST_SIZE_384]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = io.read({s}, buf) + i, _ = io.read(s, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } @@ -309,7 +309,7 @@ hash_stream_512 :: proc(s: io.Stream) -> ([DIGEST_SIZE_512]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = io.read({s}, buf) + i, _ = io.read(s, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } diff --git a/vendor/botan/shake/shake.odin b/vendor/botan/shake/shake.odin index 0a286c72e..af577f316 100644 --- a/vendor/botan/shake/shake.odin +++ b/vendor/botan/shake/shake.odin @@ -70,7 +70,7 @@ hash_stream_128 :: proc(s: io.Stream) -> ([DIGEST_SIZE_128]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = io.read({s}, buf) + i, _ = io.read(s, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } @@ -149,7 +149,7 @@ hash_stream_256 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = io.read({s}, buf) + i, _ = io.read(s, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } diff --git a/vendor/botan/skein512/skein512.odin b/vendor/botan/skein512/skein512.odin index 7dbfaf438..47529bc44 100644 --- a/vendor/botan/skein512/skein512.odin +++ b/vendor/botan/skein512/skein512.odin @@ -72,7 +72,7 @@ hash_stream_256 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = io.read({s}, buf) + i, _ = io.read(s, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } @@ -151,7 +151,7 @@ hash_stream_512 :: proc(s: io.Stream) -> ([DIGEST_SIZE_512]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = io.read({s}, buf) + i, _ = io.read(s, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } @@ -230,7 +230,7 @@ hash_stream_slice :: proc(s: io.Stream, bit_size: int, allocator := context.allo defer delete(buf) i := 1 for i > 0 { - i, _ = io.read({s}, buf) + i, _ = io.read(s, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } diff --git a/vendor/botan/sm3/sm3.odin b/vendor/botan/sm3/sm3.odin index c23885445..dd6da9e63 100644 --- a/vendor/botan/sm3/sm3.odin +++ b/vendor/botan/sm3/sm3.odin @@ -69,7 +69,7 @@ hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = io.read({s}, buf) + i, _ = io.read(s, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } diff --git a/vendor/botan/streebog/streebog.odin b/vendor/botan/streebog/streebog.odin index 45d535192..07c39684a 100644 --- a/vendor/botan/streebog/streebog.odin +++ b/vendor/botan/streebog/streebog.odin @@ -70,7 +70,7 @@ hash_stream_256 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = io.read({s}, buf) + i, _ = io.read(s, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } @@ -149,7 +149,7 @@ hash_stream_512 :: proc(s: io.Stream) -> ([DIGEST_SIZE_512]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = io.read({s}, buf) + i, _ = io.read(s, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } diff --git a/vendor/botan/tiger/tiger.odin b/vendor/botan/tiger/tiger.odin index 90efd8142..960d4694b 100644 --- a/vendor/botan/tiger/tiger.odin +++ b/vendor/botan/tiger/tiger.odin @@ -71,7 +71,7 @@ hash_stream_128 :: proc(s: io.Stream) -> ([DIGEST_SIZE_128]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = io.read({s}, buf) + i, _ = io.read(s, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } @@ -150,7 +150,7 @@ hash_stream_160 :: proc(s: io.Stream) -> ([DIGEST_SIZE_160]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = io.read({s}, buf) + i, _ = io.read(s, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } @@ -229,7 +229,7 @@ hash_stream_192 :: proc(s: io.Stream) -> ([DIGEST_SIZE_192]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = io.read({s}, buf) + i, _ = io.read(s, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } diff --git a/vendor/botan/whirlpool/whirlpool.odin b/vendor/botan/whirlpool/whirlpool.odin index 15f5109d9..76d4d25d4 100644 --- a/vendor/botan/whirlpool/whirlpool.odin +++ b/vendor/botan/whirlpool/whirlpool.odin @@ -69,7 +69,7 @@ hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) { defer delete(buf) i := 1 for i > 0 { - i, _ = io.read({s}, buf) + i, _ = io.read(s, buf) if i > 0 { botan.hash_update(ctx, len(buf) == 0 ? nil : &buf[0], uint(i)) } From 6c040497efc1fafbdf5eae77454e7f1190217162 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 8 Jun 2023 16:45:13 +0100 Subject: [PATCH 12/97] Update os2 to new `io.Stream` --- core/os/os2/file_stream.odin | 100 ++++++++++++++-------------------- core/os/os2/path_windows.odin | 2 +- 2 files changed, 41 insertions(+), 61 deletions(-) diff --git a/core/os/os2/file_stream.odin b/core/os/os2/file_stream.odin index 7edbd68fa..91cab3d1c 100644 --- a/core/os/os2/file_stream.odin +++ b/core/os/os2/file_stream.odin @@ -3,17 +3,13 @@ package os2 import "core:io" to_stream :: proc(f: ^File) -> (s: io.Stream) { - s.stream_data = f - s.stream_vtable = &_file_stream_vtable + s.data = f + s.procedure = _file_stream_proc return } -to_writer :: proc(f: ^File) -> (s: io.Writer) { - return {to_stream(f)} -} -to_reader :: proc(f: ^File) -> (s: io.Reader) { - return {to_stream(f)} -} +to_writer :: to_stream +to_reader :: to_stream @(private) @@ -26,68 +22,52 @@ error_to_io_error :: proc(ferr: Error) -> io.Error { @(private) -_file_stream_vtable := io.Stream_VTable{ - impl_read = proc(s: io.Stream, p: []byte) -> (n: int, err: io.Error) { - f := (^File)(s.stream_data) - ferr: Error - n, ferr = read(f, p) +_file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) { + f := (^File)(stream_data) + ferr: Error + i: int + switch mode { + case .Read: + i, ferr = read(f, p) + n = i64(i) err = error_to_io_error(ferr) return - }, - impl_read_at = proc(s: io.Stream, p: []byte, offset: i64) -> (n: int, err: io.Error) { - f := (^File)(s.stream_data) - ferr: Error - n, ferr = read_at(f, p, offset) + case .Read_At: + i, ferr = read_at(f, p, offset) + n = i64(i) err = error_to_io_error(ferr) return - }, - impl_write_to = proc(s: io.Stream, w: io.Writer) -> (n: i64, err: io.Error) { - f := (^File)(s.stream_data) - ferr: Error - n, ferr = write_to(f, w) + case .Write: + i, ferr = write(f, p) + n = i64(i) err = error_to_io_error(ferr) return - }, - impl_write = proc(s: io.Stream, p: []byte) -> (n: int, err: io.Error) { - f := (^File)(s.stream_data) - ferr: Error - n, ferr = write(f, p) + case .Write_At: + i, ferr = write_at(f, p, offset) + n = i64(i) err = error_to_io_error(ferr) return - }, - impl_write_at = proc(s: io.Stream, p: []byte, offset: i64) -> (n: int, err: io.Error) { - f := (^File)(s.stream_data) - ferr: Error - n, ferr = write_at(f, p, offset) + case .Seek: + n, ferr = seek(f, offset, Seek_From(whence)) err = error_to_io_error(ferr) return - }, - impl_read_from = proc(s: io.Stream, r: io.Reader) -> (n: i64, err: io.Error) { - f := (^File)(s.stream_data) - ferr: Error - n, ferr = read_from(f, r) + case .Size: + n, ferr = file_size(f) err = error_to_io_error(ferr) return - }, - impl_seek = proc(s: io.Stream, offset: i64, whence: io.Seek_From) -> (i64, io.Error) { - f := (^File)(s.stream_data) - n, ferr := seek(f, offset, Seek_From(whence)) - err := error_to_io_error(ferr) - return n, err - }, - impl_size = proc(s: io.Stream) -> i64 { - f := (^File)(s.stream_data) - sz, _ := file_size(f) - return sz - }, - impl_flush = proc(s: io.Stream) -> io.Error { - f := (^File)(s.stream_data) - ferr := flush(f) - return error_to_io_error(ferr) - }, - impl_close = proc(s: io.Stream) -> io.Error { - f := (^File)(s.stream_data) - ferr := close(f) - return error_to_io_error(ferr) - }, + case .Flush: + ferr = flush(f) + err = error_to_io_error(ferr) + return + case .Close: + ferr = close(f) + err = error_to_io_error(ferr) + return + case .Query: + return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Flush, .Close, .Query}) + case .Destroy: + return 0, .Empty + } + return 0, .Empty } + diff --git a/core/os/os2/path_windows.odin b/core/os/os2/path_windows.odin index 2dc667822..a2306784e 100644 --- a/core/os/os2/path_windows.odin +++ b/core/os/os2/path_windows.odin @@ -23,7 +23,7 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error { fix_root_directory :: proc(p: string) -> (s: string, allocated: bool, err: runtime.Allocator_Error) { if len(p) == len(`\\?\c:`) { if is_path_separator(p[0]) && is_path_separator(p[1]) && p[2] == '?' && is_path_separator(p[3]) && p[5] == ':' { - s = strings.concatenate_safe({p, `\`}, _file_allocator()) or_return + s = strings.concatenate({p, `\`}, _file_allocator()) or_return allocated = true return } From 145a7a24e8710a428f48db64c23544ac813a6814 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 8 Jun 2023 16:55:01 +0100 Subject: [PATCH 13/97] Use `io.Stream` as the internal interface for `os2.File` --- core/os/os2/file.odin | 42 +++++++-------------- core/os/os2/file_linux.odin | 66 +++++++++++++++++++++++++++------ core/os/os2/file_stream.odin | 58 ++--------------------------- core/os/os2/file_windows.odin | 69 +++++++++++++++++++++++++++++------ 4 files changed, 131 insertions(+), 104 deletions(-) diff --git a/core/os/os2/file.odin b/core/os/os2/file.odin index eb6d9e366..6c1cef89f 100644 --- a/core/os/os2/file.odin +++ b/core/os/os2/file.odin @@ -5,13 +5,8 @@ import "core:time" import "core:runtime" File :: struct { - impl: _File, -} - -Seek_From :: enum { - Start = 0, // seek relative to the origin of the file - Current = 1, // seek relative to the current offset - End = 2, // seek relative to the end + impl: _File, + stream: io.Stream, } File_Mode :: distinct u32 @@ -72,45 +67,36 @@ fd :: proc(f: ^File) -> uintptr { return _fd(f) } - -close :: proc(f: ^File) -> Error { - return _close(f) -} - name :: proc(f: ^File) -> string { return _name(f) } -seek :: proc(f: ^File, offset: i64, whence: Seek_From) -> (ret: i64, err: Error) { - return _seek(f, offset, whence) +close :: proc(f: ^File) -> Error { + return io.close(f.stream) +} + +seek :: proc(f: ^File, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) { + return io.seek(f.stream, offset, whence) } read :: proc(f: ^File, p: []byte) -> (n: int, err: Error) { - return _read(f, p) + return io.read(f.stream, p) } read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) { - return _read_at(f, p, offset) -} - -read_from :: proc(f: ^File, r: io.Reader) -> (n: i64, err: Error) { - return _read_from(f, r) + return io.read_at(f.stream, p, offset) } write :: proc(f: ^File, p: []byte) -> (n: int, err: Error) { - return _write(f, p) + return io.write(f.stream, p) } write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) { - return _write_at(f, p, offset) -} - -write_to :: proc(f: ^File, w: io.Writer) -> (n: i64, err: Error) { - return _write_to(f, w) + return io.write_at(f.stream, p, offset) } file_size :: proc(f: ^File) -> (n: i64, err: Error) { - return _file_size(f) + return io.size(f.stream) } @@ -119,7 +105,7 @@ sync :: proc(f: ^File) -> Error { } flush :: proc(f: ^File) -> Error { - return _flush(f) + return io.flush(f.stream) } truncate :: proc(f: ^File, size: i64) -> Error { diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index 890bbfc43..a43c1aadf 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -73,6 +73,10 @@ _new_file :: proc(fd: uintptr, _: string) -> ^File { file.impl.fd = int(fd) file.impl.allocator = _file_allocator() file.impl.name = _get_full_path(file.impl.fd, file.impl.allocator) + file.stream = { + data = file, + procedure = _file_stream_proc, + } return file } @@ -102,7 +106,7 @@ _name :: proc(f: ^File) -> string { return f.impl.name if f != nil else "" } -_seek :: proc(f: ^File, offset: i64, whence: Seek_From) -> (ret: i64, err: Error) { +_seek :: proc(f: ^File, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) { res := unix.sys_lseek(f.impl.fd, offset, int(whence)) if res < 0 { return -1, _get_platform_error(int(res)) @@ -139,11 +143,6 @@ _read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) { return } -_read_from :: proc(f: ^File, r: io.Reader) -> (n: i64, err: Error) { - //TODO - return -} - _write :: proc(f: ^File, p: []byte) -> (n: int, err: Error) { if len(p) == 0 { return 0, nil @@ -173,11 +172,6 @@ _write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) { return } -_write_to :: proc(f: ^File, w: io.Writer) -> (n: i64, err: Error) { - //TODO - return -} - _file_size :: proc(f: ^File) -> (n: i64, err: Error) { s: _Stat = --- res := unix.sys_fstat(f.impl.fd, &s) @@ -366,3 +360,53 @@ _is_dir_fd :: proc(fd: int) -> bool { _temp_name_to_cstring :: proc(name: string) -> (cname: cstring) { return strings.clone_to_cstring(name, context.temp_allocator) } + + +@(private="package") +_file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) { + f := (^File)(stream_data) + ferr: Error + i: int + switch mode { + case .Read: + i, ferr = _read(f, p) + n = i64(i) + err = error_to_io_error(ferr) + return + case .Read_At: + i, ferr = _read_at(f, p, offset) + n = i64(i) + err = error_to_io_error(ferr) + return + case .Write: + i, ferr = _write(f, p) + n = i64(i) + err = error_to_io_error(ferr) + return + case .Write_At: + i, ferr = _write_at(f, p, offset) + n = i64(i) + err = error_to_io_error(ferr) + return + case .Seek: + n, ferr = _seek(f, offset, whence) + err = error_to_io_error(ferr) + return + case .Size: + n, ferr = _file_size(f) + err = error_to_io_error(ferr) + return + case .Flush: + ferr = _flush(f) + err = error_to_io_error(ferr) + return + case .Close, .Destroy: + ferr = _close(f) + err = error_to_io_error(ferr) + return + case .Query: + return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Flush, .Close, .Destroy, .Query}) + } + return 0, .Empty +} + diff --git a/core/os/os2/file_stream.odin b/core/os/os2/file_stream.odin index 91cab3d1c..84176928d 100644 --- a/core/os/os2/file_stream.odin +++ b/core/os/os2/file_stream.odin @@ -3,8 +3,10 @@ package os2 import "core:io" to_stream :: proc(f: ^File) -> (s: io.Stream) { - s.data = f - s.procedure = _file_stream_proc + if f != nil { + assert(f.stream.procedure != nil) + s = f.stream + } return } @@ -19,55 +21,3 @@ error_to_io_error :: proc(ferr: Error) -> io.Error { } return ferr.(io.Error) or_else .Unknown } - - -@(private) -_file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) { - f := (^File)(stream_data) - ferr: Error - i: int - switch mode { - case .Read: - i, ferr = read(f, p) - n = i64(i) - err = error_to_io_error(ferr) - return - case .Read_At: - i, ferr = read_at(f, p, offset) - n = i64(i) - err = error_to_io_error(ferr) - return - case .Write: - i, ferr = write(f, p) - n = i64(i) - err = error_to_io_error(ferr) - return - case .Write_At: - i, ferr = write_at(f, p, offset) - n = i64(i) - err = error_to_io_error(ferr) - return - case .Seek: - n, ferr = seek(f, offset, Seek_From(whence)) - err = error_to_io_error(ferr) - return - case .Size: - n, ferr = file_size(f) - err = error_to_io_error(ferr) - return - case .Flush: - ferr = flush(f) - err = error_to_io_error(ferr) - return - case .Close: - ferr = close(f) - err = error_to_io_error(ferr) - return - case .Query: - return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Flush, .Close, .Query}) - case .Destroy: - return 0, .Empty - } - return 0, .Empty -} - diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index e4ae4856a..50938d92a 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -144,6 +144,11 @@ _new_file :: proc(handle: uintptr, name: string) -> ^File { } f.impl.kind = kind + f.stream = { + data = f, + procedure = _file_stream_proc, + } + return f } @@ -181,7 +186,7 @@ _name :: proc(f: ^File) -> string { return f.impl.name if f != nil else "" } -_seek :: proc(f: ^File, offset: i64, whence: Seek_From) -> (ret: i64, err: Error) { +_seek :: proc(f: ^File, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) { handle := _handle(f) if handle == win32.INVALID_HANDLE { return 0, .Invalid_File @@ -329,11 +334,6 @@ _read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) { return } -_read_from :: proc(f: ^File, r: io.Reader) -> (n: i64, err: Error) { - // TODO(bill) - return -} - _write :: proc(f: ^File, p: []byte) -> (n: int, err: Error) { if len(p) == 0 { return @@ -397,11 +397,6 @@ _write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) { return } -_write_to :: proc(f: ^File, w: io.Writer) -> (n: i64, err: Error) { - // TODO(bill) - return -} - _file_size :: proc(f: ^File) -> (n: i64, err: Error) { length: win32.LARGE_INTEGER handle := _handle(f) @@ -727,3 +722,55 @@ _is_dir :: proc(path: string) -> bool { } return false } + + +@(private="package") +_file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) { + f := (^File)(stream_data) + ferr: Error + i: int + switch mode { + case .Read: + i, ferr = _read(f, p) + n = i64(i) + err = error_to_io_error(ferr) + return + case .Read_At: + i, ferr = _read_at(f, p, offset) + n = i64(i) + err = error_to_io_error(ferr) + return + case .Write: + i, ferr = _write(f, p) + n = i64(i) + err = error_to_io_error(ferr) + return + case .Write_At: + i, ferr = _write_at(f, p, offset) + n = i64(i) + err = error_to_io_error(ferr) + return + case .Seek: + n, ferr = _seek(f, offset, whence) + err = error_to_io_error(ferr) + return + case .Size: + n, ferr = _file_size(f) + err = error_to_io_error(ferr) + return + case .Flush: + ferr = _flush(f) + err = error_to_io_error(ferr) + return + case .Close: + ferr = _close(f) + err = error_to_io_error(ferr) + return + case .Query: + return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Flush, .Close, .Query}) + case .Destroy: + return 0, .Empty + } + return 0, .Empty +} + From 2a212a755659459c945ce0aec80ad73c690e76d1 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 8 Jun 2023 17:00:38 +0100 Subject: [PATCH 14/97] Put stream into the impl --- core/os/os2/file.odin | 28 +++++++++++++--------------- core/os/os2/file_linux.odin | 4 +++- core/os/os2/file_stream.odin | 4 ++-- core/os/os2/file_windows.odin | 4 +++- 4 files changed, 21 insertions(+), 19 deletions(-) diff --git a/core/os/os2/file.odin b/core/os/os2/file.odin index 6c1cef89f..4113675ca 100644 --- a/core/os/os2/file.odin +++ b/core/os/os2/file.odin @@ -5,8 +5,7 @@ import "core:time" import "core:runtime" File :: struct { - impl: _File, - stream: io.Stream, + impl: _File, } File_Mode :: distinct u32 @@ -72,40 +71,39 @@ name :: proc(f: ^File) -> string { } close :: proc(f: ^File) -> Error { - return io.close(f.stream) + return io.close(f.impl.stream) } seek :: proc(f: ^File, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) { - return io.seek(f.stream, offset, whence) + return io.seek(f.impl.stream, offset, whence) } read :: proc(f: ^File, p: []byte) -> (n: int, err: Error) { - return io.read(f.stream, p) + return io.read(f.impl.stream, p) } read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) { - return io.read_at(f.stream, p, offset) + return io.read_at(f.impl.stream, p, offset) } write :: proc(f: ^File, p: []byte) -> (n: int, err: Error) { - return io.write(f.stream, p) + return io.write(f.impl.stream, p) } write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) { - return io.write_at(f.stream, p, offset) + return io.write_at(f.impl.stream, p, offset) } file_size :: proc(f: ^File) -> (n: i64, err: Error) { - return io.size(f.stream) -} - - -sync :: proc(f: ^File) -> Error { - return _sync(f) + return io.size(f.impl.stream) } flush :: proc(f: ^File) -> Error { - return io.flush(f.stream) + return io.flush(f.impl.stream) +} + +sync :: proc(f: ^File) -> Error { + return _sync(f) } truncate :: proc(f: ^File, size: i64) -> Error { diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index a43c1aadf..8df19614f 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -33,6 +33,8 @@ _File :: struct { name: string, fd: int, allocator: runtime.Allocator, + + stream: io.Stream, } _file_allocator :: proc() -> runtime.Allocator { @@ -73,7 +75,7 @@ _new_file :: proc(fd: uintptr, _: string) -> ^File { file.impl.fd = int(fd) file.impl.allocator = _file_allocator() file.impl.name = _get_full_path(file.impl.fd, file.impl.allocator) - file.stream = { + file.impl.stream = { data = file, procedure = _file_stream_proc, } diff --git a/core/os/os2/file_stream.odin b/core/os/os2/file_stream.odin index 84176928d..da1e3344f 100644 --- a/core/os/os2/file_stream.odin +++ b/core/os/os2/file_stream.odin @@ -4,8 +4,8 @@ import "core:io" to_stream :: proc(f: ^File) -> (s: io.Stream) { if f != nil { - assert(f.stream.procedure != nil) - s = f.stream + assert(f.impl.stream.procedure != nil) + s = f.impl.stream } return } diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index 50938d92a..5ca3eded8 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -38,6 +38,8 @@ _File :: struct { wname: win32.wstring, kind: _File_Kind, + stream: io.Stream, + allocator: runtime.Allocator, rw_mutex: sync.RW_Mutex, // read write calls @@ -144,7 +146,7 @@ _new_file :: proc(handle: uintptr, name: string) -> ^File { } f.impl.kind = kind - f.stream = { + f.impl.stream = { data = f, procedure = _file_stream_proc, } From 420f93ce7853902bff8c69707efafb67626c585b Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 8 Jun 2023 22:38:15 +0100 Subject: [PATCH 15/97] Ignore BSD's for stream.odin --- core/os/stream.odin | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/core/os/stream.odin b/core/os/stream.odin index 946854cc6..2b4c83663 100644 --- a/core/os/stream.odin +++ b/core/os/stream.odin @@ -31,14 +31,18 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, err = .Unknown } case .Read_At: - n_int, os_err = read_at(fd, p, offset) - n = i64(n_int) + when !(ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD) { + n_int, os_err = read_at(fd, p, offset) + n = i64(n_int) + } case .Write: n_int, os_err = write(fd, p) n = i64(n_int) case .Write_At: - n_int, os_err = write_at(fd, p, offset) - n = i64(n_int) + when !(ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD) { + n_int, os_err = write_at(fd, p, offset) + n = i64(n_int) + } case .Seek: n, os_err = seek(fd, offset, int(whence)) case .Size: @@ -46,7 +50,11 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, case .Destroy: err = .Empty case .Query: - return io.query_utility({.Close, .Flush, .Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Query}) + when ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD { + return io.query_utility({.Close, .Flush, .Read, .Write, .Seek, .Size, .Query}) + } else { + return io.query_utility({.Close, .Flush, .Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Query}) + } } if err == nil && os_err != 0 { err = .Unknown From c3933cead49c1d4b8b91b899f2b7739ba2493027 Mon Sep 17 00:00:00 2001 From: Damian Tarnawski Date: Sun, 11 Jun 2023 23:41:26 +0200 Subject: [PATCH 16/97] Replace `0` with `os.ERROR_NONE` in demo.odin --- examples/demo/demo.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/demo/demo.odin b/examples/demo/demo.odin index 3b9aa73ca..4b02bcd53 100644 --- a/examples/demo/demo.odin +++ b/examples/demo/demo.odin @@ -352,7 +352,7 @@ control_flow :: proc() { if false { f, err := os.open("my_file.txt") - if err != 0 { + if err != os.ERROR_NONE { // handle error } defer os.close(f) From fc4a5e61c27467b30a421bceb7d21ac29ef8468e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 13 Jun 2023 13:21:15 +0100 Subject: [PATCH 17/97] Add `ODIN_IGNORE_MSVC_CHECK` check to build.bat --- build.bat | 20 +++++++++++--------- misc/shell.bat | 2 ++ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/build.bat b/build.bat index 99c3ad2ee..b7537fba6 100644 --- a/build.bat +++ b/build.bat @@ -3,18 +3,20 @@ setlocal EnableDelayedExpansion where /Q cl.exe || ( - set __VSCMD_ARG_NO_LOGO=1 - for /f "tokens=*" %%i in ('"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -latest -requires Microsoft.VisualStudio.Workload.NativeDesktop -property installationPath') do set VS=%%i - if "!VS!" equ "" ( - echo ERROR: Visual Studio installation not found - exit /b 1 - ) - call "!VS!\VC\Auxiliary\Build\vcvarsall.bat" amd64 || exit /b 1 + set __VSCMD_ARG_NO_LOGO=1 + for /f "tokens=*" %%i in ('"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -latest -requires Microsoft.VisualStudio.Workload.NativeDesktop -property installationPath') do set VS=%%i + if "!VS!" equ "" ( + echo ERROR: Visual Studio installation not found + exit /b 1 + ) + call "!VS!\VC\Auxiliary\Build\vcvarsall.bat" amd64 || exit /b 1 ) if "%VSCMD_ARG_TGT_ARCH%" neq "x64" ( - echo ERROR: please run this from MSVC x64 native tools command prompt, 32-bit target is not supported! - exit /b 1 + if "%ODIN_IGNORE_MSVC_CHECK%" == "" ( + echo ERROR: please run this from MSVC x64 native tools command prompt, 32-bit target is not supported! + exit /b 1 + ) ) for /f "usebackq tokens=1,2 delims=,=- " %%i in (`wmic os get LocalDateTime /value`) do @if %%i==LocalDateTime ( diff --git a/misc/shell.bat b/misc/shell.bat index 60f603bc1..bfb444396 100644 --- a/misc/shell.bat +++ b/misc/shell.bat @@ -7,5 +7,7 @@ rem call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxil rem call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat" x64 1> NUL set _NO_DEBUG_HEAP=1 +set ODIN_IGNORE_MSVC_CHECK=1 + rem set path=w:\Odin\misc;%path% cls From ec32967daab5136fc2518f4068e7007e265c4e92 Mon Sep 17 00:00:00 2001 From: finn Date: Tue, 13 Jun 2023 22:07:01 +0200 Subject: [PATCH 18/97] [check-type] fix faulty #no_nil variants error - when checking the variants of a union type we will skip adding the variants if we have an unspecialized polymorphic, hence our union_type variants will be empty and have a count of 0 - so when checking if we violate the #no_nil error, if we are in the unspecialized polymorphic case and there exists at least one variant in the original variants then we should not raise this error - test checks that we do not raise the error anymore, and that we still detect the #no_nil error in the described circumstances --- src/check_type.cpp | 6 ++++++ tests/issues/run.sh | 7 +++++++ tests/issues/test_issue_2395.odin | 29 +++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 tests/issues/test_issue_2395.odin diff --git a/src/check_type.cpp b/src/check_type.cpp index bbfc25a12..a69dcdadc 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -729,6 +729,12 @@ gb_internal void check_union_type(CheckerContext *ctx, Type *union_type, Ast *no union_type->Union.kind = ut->kind; switch (ut->kind) { case UnionType_no_nil: + if (union_type->Union.is_polymorphic && poly_operands == nullptr) { + GB_ASSERT(variants.count == 0); + if (ut->variants.count != 1) { + break; + } + } if (variants.count < 2) { error(ut->align, "A union with #no_nil must have at least 2 variants"); } diff --git a/tests/issues/run.sh b/tests/issues/run.sh index bbcd6fb28..b0c43572f 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -6,6 +6,8 @@ pushd build ODIN=../../../odin COMMON="-collection:tests=../.." +NO_NIL_ERR="Error: " + set -x $ODIN test ../test_issue_829.odin $COMMON -file @@ -14,6 +16,11 @@ $ODIN test ../test_issue_2056.odin $COMMON -file $ODIN test ../test_issue_2087.odin $COMMON -file $ODIN build ../test_issue_2113.odin $COMMON -file -debug $ODIN test ../test_issue_2466.odin $COMMON -file +if [[ $($ODIN build ../test_issue_2395.odin $COMMON -file 2>&1 >/dev/null | grep -c "$NO_NIL_ERR") -eq 2 ]] ; then + echo "SUCCESSFUL 1/1" +else + echo "SUCCESSFUL 0/1" +fi set +x diff --git a/tests/issues/test_issue_2395.odin b/tests/issues/test_issue_2395.odin new file mode 100644 index 000000000..48e1ee516 --- /dev/null +++ b/tests/issues/test_issue_2395.odin @@ -0,0 +1,29 @@ +// Tests issue #2395 https://github.com/odin-lang/Odin/issues/2395 + +// Ensures that we no longer raise the faulty error for #no_nil unions when +// then are 2 variants with the polymorphic type. Also ensure that we raise +// exactly 2 errors from the invalid unions +package test_issues + +import "core:testing" + +ValidUnion :: union($T: typeid) #no_nil { + T, + f32, +} + +OtherValidUnion :: union($T: typeid, $S: typeid) #no_nil { + T, + S, +} + +InvalidUnion :: union($T: typeid) #no_nil { + T, +} + +OtherInvalidUnion :: union($T: typeid) #no_nil { + u8, +} + +main :: proc() { +} From a78d6fe0b39d57f002a397d54bb506df7a3d2f1b Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 14 Jun 2023 12:31:57 +0100 Subject: [PATCH 19/97] Use `i64` instead of `int` for internal procedures --- core/os/os2/errors_windows.odin | 8 +++++-- core/os/os2/file.odin | 40 ++++++++++++++++++++++++++------- core/os/os2/file_linux.odin | 32 ++++++++++++-------------- core/os/os2/file_windows.odin | 36 +++++++++++++---------------- 4 files changed, 68 insertions(+), 48 deletions(-) diff --git a/core/os/os2/errors_windows.odin b/core/os/os2/errors_windows.odin index 27c16e72e..6500e7ccc 100644 --- a/core/os/os2/errors_windows.odin +++ b/core/os/os2/errors_windows.odin @@ -37,14 +37,18 @@ _get_platform_error :: proc() -> Error { case win32.ERROR_NOT_SUPPORTED: return .Unsupported + case win32.ERROR_HANDLE_EOF: + return .EOF + + case win32.ERROR_INVALID_HANDLE: + return .Invalid_File + case win32.ERROR_BAD_ARGUMENTS, win32.ERROR_INVALID_PARAMETER, win32.ERROR_NOT_ENOUGH_MEMORY, - win32.ERROR_INVALID_HANDLE, win32.ERROR_NO_MORE_FILES, win32.ERROR_LOCK_VIOLATION, - win32.ERROR_HANDLE_EOF, win32.ERROR_BROKEN_PIPE, win32.ERROR_CALL_NOT_IMPLEMENTED, win32.ERROR_INSUFFICIENT_BUFFER, diff --git a/core/os/os2/file.odin b/core/os/os2/file.odin index 4113675ca..da822374a 100644 --- a/core/os/os2/file.odin +++ b/core/os/os2/file.odin @@ -71,35 +71,59 @@ name :: proc(f: ^File) -> string { } close :: proc(f: ^File) -> Error { - return io.close(f.impl.stream) + if f != nil { + return io.close(f.impl.stream) + } + return nil } seek :: proc(f: ^File, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) { - return io.seek(f.impl.stream, offset, whence) + if f != nil { + return io.seek(f.impl.stream, offset, whence) + } + return 0, .Invalid_File } read :: proc(f: ^File, p: []byte) -> (n: int, err: Error) { - return io.read(f.impl.stream, p) + if f != nil { + return io.read(f.impl.stream, p) + } + return 0, .Invalid_File } read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) { - return io.read_at(f.impl.stream, p, offset) + if f != nil { + return io.read_at(f.impl.stream, p, offset) + } + return 0, .Invalid_File } write :: proc(f: ^File, p: []byte) -> (n: int, err: Error) { - return io.write(f.impl.stream, p) + if f != nil { + return io.write(f.impl.stream, p) + } + return 0, .Invalid_File } write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) { - return io.write_at(f.impl.stream, p, offset) + if f != nil { + return io.write_at(f.impl.stream, p, offset) + } + return 0, .Invalid_File } file_size :: proc(f: ^File) -> (n: i64, err: Error) { - return io.size(f.impl.stream) + if f != nil { + return io.size(f.impl.stream) + } + return 0, .Invalid_File } flush :: proc(f: ^File) -> Error { - return io.flush(f.impl.stream) + if f != nil { + return io.flush(f.impl.stream) + } + return nil } sync :: proc(f: ^File) -> Error { diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index 8df19614f..ddd827bce 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -116,18 +116,18 @@ _seek :: proc(f: ^File, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Er return res, nil } -_read :: proc(f: ^File, p: []byte) -> (n: int, err: Error) { +_read :: proc(f: ^File, p: []byte) -> (i64, Error) { if len(p) == 0 { return 0, nil } - n = unix.sys_read(f.impl.fd, &p[0], len(p)) + n := unix.sys_read(f.impl.fd, &p[0], len(p)) if n < 0 { return -1, _get_platform_error(n) } - return n, nil + return i64(n), nil } -_read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) { +_read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) { if offset < 0 { return 0, .Invalid_Offset } @@ -138,25 +138,25 @@ _read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) { if m < 0 { return -1, _get_platform_error(m) } - n += m + n += i64(m) b = b[m:] offset += i64(m) } return } -_write :: proc(f: ^File, p: []byte) -> (n: int, err: Error) { +_write :: proc(f: ^File, p: []byte) -> (i64, Error) { if len(p) == 0 { return 0, nil } - n = unix.sys_write(f.impl.fd, &p[0], uint(len(p))) + n := unix.sys_write(f.impl.fd, &p[0], uint(len(p))) if n < 0 { return -1, _get_platform_error(n) } - return int(n), nil + return i64(n), nil } -_write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) { +_write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) { if offset < 0 { return 0, .Invalid_Offset } @@ -167,7 +167,7 @@ _write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) { if m < 0 { return -1, _get_platform_error(m) } - n += m + n += i64(m) b = b[m:] offset += i64(m) } @@ -371,23 +371,19 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, i: int switch mode { case .Read: - i, ferr = _read(f, p) - n = i64(i) + n, ferr = _read(f, p) err = error_to_io_error(ferr) return case .Read_At: - i, ferr = _read_at(f, p, offset) - n = i64(i) + n, ferr = _read_at(f, p, offset) err = error_to_io_error(ferr) return case .Write: - i, ferr = _write(f, p) - n = i64(i) + n, ferr = _write(f, p) err = error_to_io_error(ferr) return case .Write_At: - i, ferr = _write_at(f, p, offset) - n = i64(i) + n, ferr = _write_at(f, p, offset) err = error_to_io_error(ferr) return case .Seek: diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index bb9d983b9..600ecde21 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -215,7 +215,7 @@ _seek :: proc(f: ^File, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Er return i64(hi)<<32 + i64(dw_ptr), nil } -_read :: proc(f: ^File, p: []byte) -> (n: int, err: Error) { +_read :: proc(f: ^File, p: []byte) -> (n: i64, err: Error) { read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Error) { if len(b) == 0 { return 0, nil @@ -281,7 +281,7 @@ _read :: proc(f: ^File, p: []byte) -> (n: int, err: Error) { n, err := read_console(handle, p[total_read:][:to_read]) total_read += n if err != nil { - return int(total_read), err + return i64(total_read), err } } else { ok = win32.ReadFile(handle, &p[total_read], to_read, &single_read_length, nil) @@ -294,11 +294,11 @@ _read :: proc(f: ^File, p: []byte) -> (n: int, err: Error) { } } - return int(total_read), err + return i64(total_read), err } -_read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) { - pread :: proc(f: ^File, data: []byte, offset: i64) -> (n: int, err: Error) { +_read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) { + pread :: proc(f: ^File, data: []byte, offset: i64) -> (n: i64, err: Error) { buf := data if len(buf) > MAX_RW { buf = buf[:MAX_RW] @@ -320,7 +320,7 @@ _read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) { err = _get_platform_error() done = 0 } - n = int(done) + n = i64(done) return } @@ -336,7 +336,7 @@ _read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) { return } -_write :: proc(f: ^File, p: []byte) -> (n: int, err: Error) { +_write :: proc(f: ^File, p: []byte) -> (n: i64, err: Error) { if len(p) == 0 { return } @@ -354,17 +354,17 @@ _write :: proc(f: ^File, p: []byte) -> (n: int, err: Error) { e := win32.WriteFile(handle, &p[total_write], to_write, &single_write_length, nil) if single_write_length <= 0 || !e { - n = int(total_write) + n = i64(total_write) err = _get_platform_error() return } total_write += i64(single_write_length) } - return int(total_write), nil + return i64(total_write), nil } -_write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) { - pwrite :: proc(f: ^File, data: []byte, offset: i64) -> (n: int, err: Error) { +_write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) { + pwrite :: proc(f: ^File, data: []byte, offset: i64) -> (n: i64, err: Error) { buf := data if len(buf) > MAX_RW { buf = buf[:MAX_RW] @@ -384,7 +384,7 @@ _write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) { err = _get_platform_error() done = 0 } - n = int(done) + n = i64(done) return } @@ -733,23 +733,19 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, i: int switch mode { case .Read: - i, ferr = _read(f, p) - n = i64(i) + n, ferr = _read(f, p) err = error_to_io_error(ferr) return case .Read_At: - i, ferr = _read_at(f, p, offset) - n = i64(i) + n, ferr = _read_at(f, p, offset) err = error_to_io_error(ferr) return case .Write: - i, ferr = _write(f, p) - n = i64(i) + n, ferr = _write(f, p) err = error_to_io_error(ferr) return case .Write_At: - i, ferr = _write_at(f, p, offset) - n = i64(i) + n, ferr = _write_at(f, p, offset) err = error_to_io_error(ferr) return case .Seek: From 3a761395bee4f14c81682d4c21a5aa1e9565959a Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 14 Jun 2023 12:49:33 +0100 Subject: [PATCH 20/97] Add basic optimization for comparisons against the empty string `""` --- src/llvm_backend_expr.cpp | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index 619b40fda..68ad9e6e0 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -1325,6 +1325,15 @@ handle_op:; return {}; } +gb_internal bool lb_is_empty_string_constant(Ast *expr) { + if (expr->tav.value.kind == ExactValue_String && + is_type_string(expr->tav.type)) { + String s = expr->tav.value.value_string; + return s.len == 0; + } + return false; +} + gb_internal lbValue lb_build_binary_expr(lbProcedure *p, Ast *expr) { ast_node(be, BinaryExpr, expr); @@ -1373,15 +1382,29 @@ gb_internal lbValue lb_build_binary_expr(lbProcedure *p, Ast *expr) { case Token_CmpEq: case Token_NotEq: if (is_type_untyped_nil(be->right->tav.type)) { + // `x == nil` or `x != nil` lbValue left = lb_build_expr(p, be->left); lbValue cmp = lb_emit_comp_against_nil(p, be->op.kind, left); Type *type = default_type(tv.type); return lb_emit_conv(p, cmp, type); } else if (is_type_untyped_nil(be->left->tav.type)) { + // `nil == x` or `nil != x` lbValue right = lb_build_expr(p, be->right); lbValue cmp = lb_emit_comp_against_nil(p, be->op.kind, right); Type *type = default_type(tv.type); return lb_emit_conv(p, cmp, type); + } else if (lb_is_empty_string_constant(be->right)) { + // `x == ""` or `x != ""` + lbValue len = lb_string_len(p, lb_build_expr(p, be->left)); + lbValue cmp = lb_emit_comp(p, be->op.kind, len, lb_const_int(p->module, t_int, 0)); + Type *type = default_type(tv.type); + return lb_emit_conv(p, cmp, type); + } else if (lb_is_empty_string_constant(be->left)) { + // `"" == x` or `"" != x` + lbValue len = lb_string_len(p, lb_build_expr(p, be->right)); + lbValue cmp = lb_emit_comp(p, be->op.kind, len, lb_const_int(p->module, t_int, 0)); + Type *type = default_type(tv.type); + return lb_emit_conv(p, cmp, type); } /*fallthrough*/ case Token_Lt: @@ -2246,7 +2269,6 @@ gb_internal lbValue lb_compare_records(lbProcedure *p, TokenKind op_kind, lbValu - gb_internal lbValue lb_emit_comp(lbProcedure *p, TokenKind op_kind, lbValue left, lbValue right) { Type *a = core_type(left.type); Type *b = core_type(right.type); From feacc5cd1176f93fc4b52c081d8ad93ed391df00 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 14 Jun 2023 14:03:08 +0100 Subject: [PATCH 21/97] Basic enforcement of ordered named arguments/parameters for procedures --- examples/demo/demo.odin | 4 +- src/check_expr.cpp | 131 ++++++++++++++++++++++++++++++++++++---- src/parser_pos.cpp | 6 +- 3 files changed, 127 insertions(+), 14 deletions(-) diff --git a/examples/demo/demo.odin b/examples/demo/demo.odin index 4b02bcd53..6863a7768 100644 --- a/examples/demo/demo.odin +++ b/examples/demo/demo.odin @@ -1175,13 +1175,13 @@ threading_example :: proc() { N :: 3 pool: thread.Pool - thread.pool_init(pool=&pool, thread_count=N, allocator=context.allocator) + thread.pool_init(pool=&pool, allocator=context.allocator, thread_count=N) defer thread.pool_destroy(&pool) for i in 0..<30 { // be mindful of the allocator used for tasks. The allocator needs to be thread safe, or be owned by the task for exclusive use - thread.pool_add_task(pool=&pool, procedure=task_proc, data=nil, user_index=i, allocator=context.allocator) + thread.pool_add_task(pool=&pool, allocator=context.allocator, procedure=task_proc, data=nil, user_index=i) } thread.pool_start(&pool) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 830b5315d..c6b332cde 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -14,6 +14,7 @@ enum CallArgumentError { CallArgumentError_ParameterMissing, CallArgumentError_DuplicateParameter, CallArgumentError_NoneConstantParameter, + CallArgumentError_OutOfOrderParameters, CallArgumentError_MAX, }; @@ -121,6 +122,7 @@ gb_internal void check_or_return_split_types(CheckerContext *c, Operand *x, Stri gb_internal bool is_diverging_expr(Ast *expr); +gb_internal CallArgumentError check_order_of_call_arguments(CheckerContext *c, Type *proc_type, Ast *call, Slice const &args, bool show_error); enum LoadDirectiveResult { LoadDirective_Success = 0, @@ -5604,6 +5606,10 @@ gb_internal CALL_ARGUMENT_CHECKER(check_named_call_arguments) { } }); + if (check_order_of_call_arguments(c, proc_type, call, ce->args, show_error)) { + return CallArgumentError_OutOfOrderParameters; + } + for_array(i, ce->args) { Ast *arg = ce->args[i]; ast_node(fv, FieldValue, arg); @@ -5867,6 +5873,99 @@ gb_internal bool evaluate_where_clauses(CheckerContext *ctx, Ast *call_expr, Sco return true; } +gb_internal CallArgumentError check_order_of_call_arguments(CheckerContext *c, Type *proc_type, Ast *call, Slice const &args, bool show_error) { + CallArgumentError err = CallArgumentError_None; + + if (proc_type == nullptr || !is_type_proc(proc_type)) { + // ignore for the time being + return err; + } + + ast_node(ce, CallExpr, call); + if (!is_call_expr_field_value(ce)) { + return err; + } + + TypeProc *pt = &base_type(proc_type)->Proc; + isize param_count = pt->param_count; + + TEMPORARY_ALLOCATOR_GUARD(); + + auto arg_order_indices = slice_make(temporary_allocator(), args.count); + auto visited = slice_make(temporary_allocator(), param_count); + + for_array(i, args) { + Ast *arg = args[i]; + ast_node(fv, FieldValue, arg); + + Ast *field = fv->field; + String name = {}; + if (field != nullptr && field->kind == Ast_Ident) { + name = field->Ident.token.string; + } else { + err = CallArgumentError_InvalidFieldValue; + if (show_error) { + gbString s = expr_to_string(arg); + error(arg, "Invalid parameter name '%s' in procedure call", s); + gb_string_free(s); + } + } + + isize index = lookup_procedure_parameter(pt, name); + if (index < 0) { + err = CallArgumentError_InvalidFieldValue; + if (show_error) { + error(arg, "No parameter named '%.*s' for this procedure type", LIT(name)); + } + } + if (visited[index]) { + err = CallArgumentError_DuplicateParameter; + if (show_error) { + error(arg, "Duplicate parameter '%.*s' in procedure call", LIT(name)); + } + } + arg_order_indices[i] = index; + } + + if (err) { + return err; + } + + for (isize i = 0; i < arg_order_indices.count-1; i++) { + isize a = arg_order_indices[i+0]; + isize b = arg_order_indices[i+1]; + GB_ASSERT(a != b); + if (a > b) { + err = CallArgumentError_OutOfOrderParameters; + if (show_error) { + Ast *arg_a = args[i+0]; + Ast *arg_b = args[i+1]; + + isize curr_a_order = a; + for (isize j = i; j >= 0; j--) { + isize j_order = arg_order_indices[j]; + if (b < j_order && j_order < curr_a_order) { + curr_a_order = j_order; + arg_a = args[j]; + } + } + + + ast_node(fv_a, FieldValue, arg_a); + ast_node(fv_b, FieldValue, arg_b); + + gbString s_a = expr_to_string(fv_a->field); + gbString s_b = expr_to_string(fv_b->field); + error(arg_b, "Parameter names out of order, expected '%s' to be called before '%s'", s_b, s_a); + gb_string_free(s_b); + gb_string_free(s_a); + } + } + } + + return err; +} + gb_internal CallArgumentData check_call_arguments(CheckerContext *c, Operand *operand, Type *proc_type, Ast *call, Slice const &args) { ast_node(ce, CallExpr, call); @@ -5878,17 +5977,6 @@ gb_internal CallArgumentData check_call_arguments(CheckerContext *c, Operand *op Type *result_type = t_invalid; if (is_call_expr_field_value(ce)) { - call_checker = check_named_call_arguments; - - operands = array_make(heap_allocator(), args.count); - - // NOTE(bill): This is give type hints for the named parameters - // in order to improve the type inference system - - StringMap type_hint_map = {}; // Key: String - string_map_init(&type_hint_map, 2*args.count); - defer (string_map_destroy(&type_hint_map)); - Type *ptype = nullptr; bool single_case = true; @@ -5903,6 +5991,27 @@ gb_internal CallArgumentData check_call_arguments(CheckerContext *c, Operand *op ptype = proc_type; } + if (check_order_of_call_arguments(c, ptype, call, args, true)) { + CallArgumentData data = {}; + data.result_type = t_invalid; + if (ptype && is_type_proc(ptype) && !is_type_polymorphic(ptype)) { + data.result_type = reduce_tuple_to_single_type(ptype->Proc.results); + } + return data; + } + + call_checker = check_named_call_arguments; + + operands = array_make(heap_allocator(), args.count); + + // NOTE(bill): This is give type hints for the named parameters + // in order to improve the type inference system + + StringMap type_hint_map = {}; // Key: String + string_map_init(&type_hint_map, 2*args.count); + defer (string_map_destroy(&type_hint_map)); + + if (single_case) { Type *bptype = base_type(ptype); if (is_type_proc(bptype)) { diff --git a/src/parser_pos.cpp b/src/parser_pos.cpp index 52d49e897..2f22a85d3 100644 --- a/src/parser_pos.cpp +++ b/src/parser_pos.cpp @@ -41,7 +41,11 @@ gb_internal Token ast_token(Ast *node) { case Ast_MatrixIndexExpr: return node->MatrixIndexExpr.open; case Ast_SliceExpr: return node->SliceExpr.open; case Ast_Ellipsis: return node->Ellipsis.token; - case Ast_FieldValue: return node->FieldValue.eq; + case Ast_FieldValue: + if (node->FieldValue.field) { + return ast_token(node->FieldValue.field); + } + return node->FieldValue.eq; case Ast_EnumFieldValue: return ast_token(node->EnumFieldValue.name); case Ast_DerefExpr: return node->DerefExpr.op; case Ast_TernaryIfExpr: return ast_token(node->TernaryIfExpr.x); From c66ac9725ee89df82b70a1109ee1988360d5ab6d Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 14 Jun 2023 14:56:33 +0100 Subject: [PATCH 22/97] Separate out logic for checking mixture of named and unnamed parameters --- src/check_expr.cpp | 67 ++++++++++++++++++++++++++-------------------- src/parser.cpp | 2 -- 2 files changed, 38 insertions(+), 31 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index c6b332cde..d3e287ba4 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -6849,6 +6849,34 @@ gb_internal CallArgumentError check_polymorphic_record_type(CheckerContext *c, O +// returns true on failure +gb_internal bool check_call_parameter_mixture(Operand *operand, Slice const &args, char const *context) { + bool failure = false; + if (args.count > 0) { + bool first_is_field_value = (args[0]->kind == Ast_FieldValue); + for (Ast *arg : args) { + bool mix = false; + if (first_is_field_value) { + mix = arg->kind != Ast_FieldValue; + } else { + mix = arg->kind == Ast_FieldValue; + } + if (mix) { + error(arg, "Mixture of 'field = value' and value elements in a %s is not allowed", context); + failure = true; + } + } + + } + return failure; +} + +#define CHECK_CALL_PARAMETER_MIXTURE_OR_RETURN(context_) if (check_call_parameter_mixture(operand, args, "procedure call")) { \ + operand->mode = Addressing_Invalid; \ + operand->expr = call; \ + return Expr_Stmt; \ +} + gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *call, Ast *proc, Slice const &args, ProcInlining inlining, Type *type_hint) { if (proc != nullptr && @@ -6888,30 +6916,8 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c } } - if (args.count > 0) { - bool fail = false; - bool first_is_field_value = (args[0]->kind == Ast_FieldValue); - for (Ast *arg : args) { - bool mix = false; - if (first_is_field_value) { - mix = arg->kind != Ast_FieldValue; - } else { - mix = arg->kind == Ast_FieldValue; - } - if (mix) { - error(arg, "Mixture of 'field = value' and value elements in a procedure call is not allowed"); - fail = true; - } - } - - if (fail) { - operand->mode = Addressing_Invalid; - operand->expr = call; - return Expr_Stmt; - } - } - if (operand->mode == Addressing_Invalid) { + CHECK_CALL_PARAMETER_MIXTURE_OR_RETURN("procedure call"); for (Ast *arg : args) { if (arg->kind == Ast_FieldValue) { arg = arg->FieldValue.value; @@ -6926,6 +6932,8 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c if (operand->mode == Addressing_Type) { Type *t = operand->type; if (is_type_polymorphic_record(t)) { + CHECK_CALL_PARAMETER_MIXTURE_OR_RETURN("polymorphic type construction"); + if (!is_type_named(t)) { gbString s = expr_to_string(operand->expr); error(call, "Illegal use of an unnamed polymorphic record, %s", s); @@ -6951,6 +6959,8 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c operand->type = t_invalid; } } else { + CHECK_CALL_PARAMETER_MIXTURE_OR_RETURN("type conversion"); + operand->mode = Addressing_Invalid; isize arg_count = args.count; switch (arg_count) { @@ -6996,6 +7006,8 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c } if (operand->mode == Addressing_Builtin) { + CHECK_CALL_PARAMETER_MIXTURE_OR_RETURN("builtin call"); + i32 id = operand->builtin_id; Entity *e = entity_of_node(operand->expr); if (e != nullptr && e->token.string == "expand_to_tuple") { @@ -7009,6 +7021,8 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c return builtin_procs[id].kind; } + CHECK_CALL_PARAMETER_MIXTURE_OR_RETURN("procedure call"); + Entity *initial_entity = entity_of_node(operand->expr); if (initial_entity != nullptr && initial_entity->kind == Entity_Procedure) { @@ -7095,7 +7109,7 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c if (decl->proc_lit) { ast_node(pl, ProcLit, decl->proc_lit); if (pl->inlining == ProcInlining_no_inline) { - error(call, "'inline' cannot be applied to a procedure that has be marked as 'no_inline'"); + error(call, "'#force_inline' cannot be applied to a procedure that has be marked as '#force_no_inline'"); } } } @@ -7108,9 +7122,6 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c operand->expr = call; { - if (proc_type == t_invalid) { - // gb_printf_err("%s\n", expr_to_string(operand->expr)); - } Type *type = nullptr; if (operand->expr != nullptr && operand->expr->kind == Ast_CallExpr) { type = type_of_expr(operand->expr->CallExpr.proc); @@ -7128,8 +7139,6 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c } } - // add_type_and_value(c, operand->expr, operand->mode, operand->type, operand->value); - return Expr_Expr; } diff --git a/src/parser.cpp b/src/parser.cpp index 883342b21..96b7c96ea 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -2777,8 +2777,6 @@ gb_internal Ast *parse_call_expr(AstFile *f, Ast *operand) { Ast *value = parse_value(f); arg = ast_field_value(f, arg, value, eq); - - } array_add(&args, arg); From 242d3b3c4d7e269b04c96a81dbf6e5f5fbeb5e8b Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 14 Jun 2023 15:40:52 +0100 Subject: [PATCH 23/97] Begin work allowing mixture of named and unnamed --- src/check_expr.cpp | 75 ++++++++++++++++++++++++++++------------------ 1 file changed, 46 insertions(+), 29 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index d3e287ba4..52ab5092e 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -101,8 +101,7 @@ gb_internal void check_struct_type (CheckerContext *c, Type *st gb_internal void check_union_type (CheckerContext *c, Type *union_type, Ast *node, Array *poly_operands, Type *named_type = nullptr, Type *original_type_for_poly = nullptr); -gb_internal CallArgumentData check_call_arguments (CheckerContext *c, Operand *operand, Type *proc_type, Ast *call); -gb_internal Type * check_init_variable (CheckerContext *c, Entity *e, Operand *operand, String context_name); +gb_internal Type * check_init_variable (CheckerContext *c, Entity *e, Operand *operand, String context_name); gb_internal void check_assignment_error_suggestion(CheckerContext *c, Operand *o, Type *type); @@ -122,7 +121,7 @@ gb_internal void check_or_return_split_types(CheckerContext *c, Operand *x, Stri gb_internal bool is_diverging_expr(Ast *expr); -gb_internal CallArgumentError check_order_of_call_arguments(CheckerContext *c, Type *proc_type, Ast *call, Slice const &args, bool show_error); +gb_internal CallArgumentError check_order_of_call_arguments(CheckerContext *c, Type *proc_type, Ast *call, bool show_error); enum LoadDirectiveResult { LoadDirective_Success = 0, @@ -5606,7 +5605,7 @@ gb_internal CALL_ARGUMENT_CHECKER(check_named_call_arguments) { } }); - if (check_order_of_call_arguments(c, proc_type, call, ce->args, show_error)) { + if (check_order_of_call_arguments(c, proc_type, call, show_error)) { return CallArgumentError_OutOfOrderParameters; } @@ -5873,7 +5872,7 @@ gb_internal bool evaluate_where_clauses(CheckerContext *ctx, Ast *call_expr, Sco return true; } -gb_internal CallArgumentError check_order_of_call_arguments(CheckerContext *c, Type *proc_type, Ast *call, Slice const &args, bool show_error) { +gb_internal CallArgumentError check_order_of_call_arguments(CheckerContext *c, Type *proc_type, Ast *call, bool show_error) { CallArgumentError err = CallArgumentError_None; if (proc_type == nullptr || !is_type_proc(proc_type)) { @@ -5891,11 +5890,11 @@ gb_internal CallArgumentError check_order_of_call_arguments(CheckerContext *c, T TEMPORARY_ALLOCATOR_GUARD(); - auto arg_order_indices = slice_make(temporary_allocator(), args.count); + auto arg_order_indices = slice_make(temporary_allocator(), ce->args.count); auto visited = slice_make(temporary_allocator(), param_count); - for_array(i, args) { - Ast *arg = args[i]; + for_array(i, ce->args) { + Ast *arg = ce->args[i]; ast_node(fv, FieldValue, arg); Ast *field = fv->field; @@ -5938,15 +5937,15 @@ gb_internal CallArgumentError check_order_of_call_arguments(CheckerContext *c, T if (a > b) { err = CallArgumentError_OutOfOrderParameters; if (show_error) { - Ast *arg_a = args[i+0]; - Ast *arg_b = args[i+1]; + Ast *arg_a = ce->args[i+0]; + Ast *arg_b = ce->args[i+1]; isize curr_a_order = a; for (isize j = i; j >= 0; j--) { isize j_order = arg_order_indices[j]; if (b < j_order && j_order < curr_a_order) { curr_a_order = j_order; - arg_a = args[j]; + arg_a = ce->args[j]; } } @@ -5967,15 +5966,18 @@ gb_internal CallArgumentError check_order_of_call_arguments(CheckerContext *c, T } -gb_internal CallArgumentData check_call_arguments(CheckerContext *c, Operand *operand, Type *proc_type, Ast *call, Slice const &args) { +gb_internal CallArgumentData check_call_arguments(CheckerContext *c, Operand *operand, Ast *call) { ast_node(ce, CallExpr, call); CallArgumentCheckerType *call_checker = check_call_arguments_internal; Array operands = {}; defer (array_free(&operands)); - Type *result_type = t_invalid; + Type *result_type = t_invalid; + Type *proc_type = base_type(operand->type); + + Slice const &args = ce->args; if (is_call_expr_field_value(ce)) { Type *ptype = nullptr; bool single_case = true; @@ -5991,7 +5993,7 @@ gb_internal CallArgumentData check_call_arguments(CheckerContext *c, Operand *op ptype = proc_type; } - if (check_order_of_call_arguments(c, ptype, call, args, true)) { + if (check_order_of_call_arguments(c, ptype, call, true)) { CallArgumentData data = {}; data.result_type = t_invalid; if (ptype && is_type_proc(ptype) && !is_type_polymorphic(ptype)) { @@ -6850,20 +6852,31 @@ gb_internal CallArgumentError check_polymorphic_record_type(CheckerContext *c, O // returns true on failure -gb_internal bool check_call_parameter_mixture(Operand *operand, Slice const &args, char const *context) { +gb_internal bool check_call_parameter_mixture(Operand *operand, Slice const &args, char const *context, bool allow_mixed=false) { bool failure = false; if (args.count > 0) { - bool first_is_field_value = (args[0]->kind == Ast_FieldValue); - for (Ast *arg : args) { - bool mix = false; - if (first_is_field_value) { - mix = arg->kind != Ast_FieldValue; - } else { - mix = arg->kind == Ast_FieldValue; + if (allow_mixed) { + bool was_named = false; + for (Ast *arg : args) { + if (was_named && arg->kind != Ast_FieldValue) { + error(arg, "Non-named parameter is not allowed to follow named parameter i.e. 'field = value' in a %s", context); + failure = true; + } + was_named = was_named || arg->kind == Ast_FieldValue; } - if (mix) { - error(arg, "Mixture of 'field = value' and value elements in a %s is not allowed", context); - failure = true; + } else { + bool first_is_field_value = (args[0]->kind == Ast_FieldValue); + for (Ast *arg : args) { + bool mix = false; + if (first_is_field_value) { + mix = arg->kind != Ast_FieldValue; + } else { + mix = arg->kind == Ast_FieldValue; + } + if (mix) { + error(arg, "Mixture of 'field = value' and value elements in a %s is not allowed", context); + failure = true; + } } } @@ -6871,7 +6884,7 @@ gb_internal bool check_call_parameter_mixture(Operand *operand, Slice con return failure; } -#define CHECK_CALL_PARAMETER_MIXTURE_OR_RETURN(context_) if (check_call_parameter_mixture(operand, args, "procedure call")) { \ +#define CHECK_CALL_PARAMETER_MIXTURE_OR_RETURN(context_, ...) if (check_call_parameter_mixture(operand, args, context_, ##__VA_ARGS__)) { \ operand->mode = Addressing_Invalid; \ operand->expr = call; \ return Expr_Stmt; \ @@ -7021,6 +7034,7 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c return builtin_procs[id].kind; } + // CHECK_CALL_PARAMETER_MIXTURE_OR_RETURN("procedure call", true); CHECK_CALL_PARAMETER_MIXTURE_OR_RETURN("procedure call"); Entity *initial_entity = entity_of_node(operand->expr); @@ -7034,8 +7048,8 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c } } - Type *proc_type = base_type(operand->type); if (operand->mode != Addressing_ProcGroup) { + Type *proc_type = base_type(operand->type); bool valid_type = (proc_type != nullptr) && is_type_proc(proc_type); bool valid_mode = is_operand_value(*operand); if (!valid_type || !valid_mode) { @@ -7053,7 +7067,7 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c } } - CallArgumentData data = check_call_arguments(c, operand, proc_type, call, args); + CallArgumentData data = check_call_arguments(c, operand, call); Type *result_type = data.result_type; gb_zero_item(operand); operand->expr = call; @@ -7064,7 +7078,10 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c return Expr_Stmt; } - Type *pt = base_type(proc_type); + Type *pt = base_type(operand->type); + if (pt == nullptr) { + pt = t_invalid; + } if (pt == t_invalid) { if (operand->expr != nullptr && operand->expr->kind == Ast_CallExpr) { pt = type_of_expr(operand->expr->CallExpr.proc); From 4f2b9835f5f6966c5f11fe20f9f56855c54e4ce5 Mon Sep 17 00:00:00 2001 From: Rickard Andersson Date: Wed, 14 Jun 2023 22:13:36 +0300 Subject: [PATCH 24/97] feat(unix): add `poll` --- core/os/os_linux.odin | 36 +++++++++++++++++++++++++++++++ core/sys/unix/syscalls_linux.odin | 29 +++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/core/os/os_linux.odin b/core/os/os_linux.odin index 7a0a4b1dd..beb4acec5 100644 --- a/core/os/os_linux.odin +++ b/core/os/os_linux.odin @@ -432,6 +432,18 @@ AT_FDCWD :: ~uintptr(99) /* -100 */ AT_REMOVEDIR :: uintptr(0x200) AT_SYMLINK_NOFOLLOW :: uintptr(0x100) +pollfd :: struct { + fd: c.int, + events: c.short, + revents: c.short, +} + +nfds_t :: distinct c.uint + +sigset_t :: struct { + __val: [16]c.ulong, +} + foreign libc { @(link_name="__errno_location") __errno_location :: proc() -> ^int --- @@ -1087,3 +1099,27 @@ fcntl :: proc(fd: int, cmd: int, arg: int) -> (int, Errno) { } return result, ERROR_NONE } + +// select :: proc(nfds: int, readfds: ^fd_set, writefds: ^fd_set, exceptfds: ^fd_set, timeout: ^timeval) -> (int, Errno) { +// result := unix.sys_select(nfds, readfds, writefds, exceptfds, timeout) +// if result < 0 { +// return 0, _get_errno(result) +// } +// return result, ERROR_NONE +// } + +poll :: proc(fds: [^]pollfd, nfds: nfds_t, timeout: int) -> (int, Errno) { + result := unix.sys_poll(fds, uint(nfds), timeout) + if result < 0 { + return 0, _get_errno(result) + } + return result, ERROR_NONE +} + +ppoll :: proc(fds: [^]pollfd, nfds: nfds_t, timeout: ^unix.timespec, sigmask: ^sigset_t) -> (int, Errno) { + result := unix.sys_ppoll(fds, uint(nfds), timeout, sigmask, size_of(sigset_t)) + if result < 0 { + return 0, _get_errno(result) + } + return result, ERROR_NONE +} diff --git a/core/sys/unix/syscalls_linux.odin b/core/sys/unix/syscalls_linux.odin index abdcf0b92..54f8bcc25 100644 --- a/core/sys/unix/syscalls_linux.odin +++ b/core/sys/unix/syscalls_linux.odin @@ -1567,6 +1567,23 @@ MADV_HWPOISON :: 100 // pipe2 flags O_CLOEXEC :: 0o2000000 +// poll events +POLLIN :: 0x0001 +POLLPRI :: 0x0002 +POLLOUT :: 0x0004 +POLLERR :: 0x0008 +POLLHUP :: 0x0010 +POLLNVAL :: 0x0020 +POLLRDNORM :: 0x0040 +POLLRDBAND :: 0x0080 +POLLWRNORM :: 0x0100 +POLLWRBAND :: 0x0200 +POLLMSG :: 0x0400 +POLLREMOVE :: 0x1000 +POLLRDHUP :: 0x2000 +POLLFREE :: 0x4000 +POLL_BUSY_LOOP :: 0x8000 + // perf event data Perf_Sample :: struct #raw_union { period: u64, @@ -2057,6 +2074,18 @@ sys_fcntl :: proc "contextless" (fd: int, cmd: int, arg: int) -> int { return int(intrinsics.syscall(SYS_fcntl, uintptr(fd), uintptr(cmd), uintptr(arg))) } +sys_select :: proc "contextless" (nfds: int, readfds, writefds, exceptfds: rawptr, timeout: rawptr) -> int { + return int(intrinsics.syscall(SYS_select, uintptr(nfds), uintptr(readfds), uintptr(writefds), uintptr(exceptfds), uintptr(timeout))) +} + +sys_poll :: proc "contextless" (fds: rawptr, nfds: uint, timeout: int) -> int { + return int(intrinsics.syscall(SYS_poll, uintptr(fds), uintptr(nfds), uintptr(timeout))) +} + +sys_ppoll :: proc "contextless" (fds: rawptr, nfds: uint, timeout: rawptr, sigmask: rawptr, sigsetsize: uint) -> int { + return int(intrinsics.syscall(SYS_ppoll, uintptr(fds), uintptr(nfds), uintptr(timeout), uintptr(sigmask), uintptr(sigsetsize))) +} + get_errno :: proc "contextless" (res: int) -> i32 { if res < 0 && res > -4096 { return i32(-res) From 091c515fea784b639b4a907a2fff4a6c6eef53de Mon Sep 17 00:00:00 2001 From: Rickard Andersson Date: Wed, 14 Jun 2023 22:34:57 +0300 Subject: [PATCH 25/97] cleanup(os_linux): remove `select` --- core/os/os_linux.odin | 8 -------- 1 file changed, 8 deletions(-) diff --git a/core/os/os_linux.odin b/core/os/os_linux.odin index beb4acec5..70e842aa4 100644 --- a/core/os/os_linux.odin +++ b/core/os/os_linux.odin @@ -1100,14 +1100,6 @@ fcntl :: proc(fd: int, cmd: int, arg: int) -> (int, Errno) { return result, ERROR_NONE } -// select :: proc(nfds: int, readfds: ^fd_set, writefds: ^fd_set, exceptfds: ^fd_set, timeout: ^timeval) -> (int, Errno) { -// result := unix.sys_select(nfds, readfds, writefds, exceptfds, timeout) -// if result < 0 { -// return 0, _get_errno(result) -// } -// return result, ERROR_NONE -// } - poll :: proc(fds: [^]pollfd, nfds: nfds_t, timeout: int) -> (int, Errno) { result := unix.sys_poll(fds, uint(nfds), timeout) if result < 0 { From d6540d9077cbfe18f0d6804ec2a301c04758c820 Mon Sep 17 00:00:00 2001 From: Rickard Andersson Date: Wed, 14 Jun 2023 23:26:43 +0300 Subject: [PATCH 26/97] fix(os_linux): call `ppoll` instead on `arm64` --- core/os/os_linux.odin | 4 +--- core/sys/unix/syscalls_linux.odin | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/core/os/os_linux.odin b/core/os/os_linux.odin index 70e842aa4..3e5258af3 100644 --- a/core/os/os_linux.odin +++ b/core/os/os_linux.odin @@ -440,9 +440,7 @@ pollfd :: struct { nfds_t :: distinct c.uint -sigset_t :: struct { - __val: [16]c.ulong, -} +sigset_t :: distinct u64 foreign libc { @(link_name="__errno_location") __errno_location :: proc() -> ^int --- diff --git a/core/sys/unix/syscalls_linux.odin b/core/sys/unix/syscalls_linux.odin index 54f8bcc25..2e5bad153 100644 --- a/core/sys/unix/syscalls_linux.odin +++ b/core/sys/unix/syscalls_linux.odin @@ -2079,7 +2079,22 @@ sys_select :: proc "contextless" (nfds: int, readfds, writefds, exceptfds: rawpt } sys_poll :: proc "contextless" (fds: rawptr, nfds: uint, timeout: int) -> int { - return int(intrinsics.syscall(SYS_poll, uintptr(fds), uintptr(nfds), uintptr(timeout))) + // NOTE: specialcased here because `arm64` does not have `poll` + when ODIN_ARCH != .arm64 { + // redefined because we can't depend on the `unix` module here + timespec :: struct { + tv_sec: i64, + tv_nsec: i64, + } + seconds := i64(timeout / 1_000) + nanoseconds := i64((timeout % 1000) * 1_000_000) + timeout_spec := timespec{seconds, nanoseconds} + + return int(intrinsics.syscall(SYS_ppoll, uintptr(fds), uintptr(nfds), uintptr(&timeout_spec), uintptr(0), uintptr(8))) + } else { + return int(intrinsics.syscall(SYS_poll, uintptr(fds), uintptr(nfds), uintptr(timeout))) + } + } sys_ppoll :: proc "contextless" (fds: rawptr, nfds: uint, timeout: rawptr, sigmask: rawptr, sigsetsize: uint) -> int { From 003c470a4df12fe2e82b13fde532f95b2ac85637 Mon Sep 17 00:00:00 2001 From: Rickard Andersson Date: Wed, 14 Jun 2023 23:38:00 +0300 Subject: [PATCH 27/97] fix(os_linux): make `when` for `arm64` check correct order Also remove `sys_select` since we aren't using it anyway --- core/sys/unix/syscalls_linux.odin | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/core/sys/unix/syscalls_linux.odin b/core/sys/unix/syscalls_linux.odin index 2e5bad153..0accc9d53 100644 --- a/core/sys/unix/syscalls_linux.odin +++ b/core/sys/unix/syscalls_linux.odin @@ -2074,13 +2074,9 @@ sys_fcntl :: proc "contextless" (fd: int, cmd: int, arg: int) -> int { return int(intrinsics.syscall(SYS_fcntl, uintptr(fd), uintptr(cmd), uintptr(arg))) } -sys_select :: proc "contextless" (nfds: int, readfds, writefds, exceptfds: rawptr, timeout: rawptr) -> int { - return int(intrinsics.syscall(SYS_select, uintptr(nfds), uintptr(readfds), uintptr(writefds), uintptr(exceptfds), uintptr(timeout))) -} - sys_poll :: proc "contextless" (fds: rawptr, nfds: uint, timeout: int) -> int { // NOTE: specialcased here because `arm64` does not have `poll` - when ODIN_ARCH != .arm64 { + when ODIN_ARCH == .arm64 { // redefined because we can't depend on the `unix` module here timespec :: struct { tv_sec: i64, From 0036509f74592e6921e0dbb994e0df02e6d673ba Mon Sep 17 00:00:00 2001 From: Rickard Andersson Date: Wed, 14 Jun 2023 23:51:33 +0300 Subject: [PATCH 28/97] cleanup(os_linux/poll): use tabs everywhere :( --- core/sys/unix/syscalls_linux.odin | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/core/sys/unix/syscalls_linux.odin b/core/sys/unix/syscalls_linux.odin index 0accc9d53..fdfc27187 100644 --- a/core/sys/unix/syscalls_linux.odin +++ b/core/sys/unix/syscalls_linux.odin @@ -2075,22 +2075,21 @@ sys_fcntl :: proc "contextless" (fd: int, cmd: int, arg: int) -> int { } sys_poll :: proc "contextless" (fds: rawptr, nfds: uint, timeout: int) -> int { - // NOTE: specialcased here because `arm64` does not have `poll` + // NOTE: specialcased here because `arm64` does not have `poll` when ODIN_ARCH == .arm64 { - // redefined because we can't depend on the `unix` module here - timespec :: struct { - tv_sec: i64, - tv_nsec: i64, - } - seconds := i64(timeout / 1_000) - nanoseconds := i64((timeout % 1000) * 1_000_000) - timeout_spec := timespec{seconds, nanoseconds} - + // redefined because we can't depend on the `unix` module here + timespec :: struct { + tv_sec: i64, + tv_nsec: i64, + } + seconds := i64(timeout / 1_000) + nanoseconds := i64((timeout % 1000) * 1_000_000) + timeout_spec := timespec{seconds, nanoseconds} + return int(intrinsics.syscall(SYS_ppoll, uintptr(fds), uintptr(nfds), uintptr(&timeout_spec), uintptr(0), uintptr(8))) } else { - return int(intrinsics.syscall(SYS_poll, uintptr(fds), uintptr(nfds), uintptr(timeout))) + return int(intrinsics.syscall(SYS_poll, uintptr(fds), uintptr(nfds), uintptr(timeout))) } - } sys_ppoll :: proc "contextless" (fds: rawptr, nfds: uint, timeout: rawptr, sigmask: rawptr, sigsetsize: uint) -> int { From a5ed5883c71282b2454e3e320fc7c8703b9fa952 Mon Sep 17 00:00:00 2001 From: Rickard Andersson Date: Wed, 14 Jun 2023 23:59:09 +0300 Subject: [PATCH 29/97] cleanup: more tabs --- core/os/os_linux.odin | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/core/os/os_linux.odin b/core/os/os_linux.odin index 3e5258af3..ff3ff700c 100644 --- a/core/os/os_linux.odin +++ b/core/os/os_linux.odin @@ -1099,17 +1099,17 @@ fcntl :: proc(fd: int, cmd: int, arg: int) -> (int, Errno) { } poll :: proc(fds: [^]pollfd, nfds: nfds_t, timeout: int) -> (int, Errno) { - result := unix.sys_poll(fds, uint(nfds), timeout) - if result < 0 { - return 0, _get_errno(result) - } - return result, ERROR_NONE + result := unix.sys_poll(fds, uint(nfds), timeout) + if result < 0 { + return 0, _get_errno(result) + } + return result, ERROR_NONE } ppoll :: proc(fds: [^]pollfd, nfds: nfds_t, timeout: ^unix.timespec, sigmask: ^sigset_t) -> (int, Errno) { - result := unix.sys_ppoll(fds, uint(nfds), timeout, sigmask, size_of(sigset_t)) - if result < 0 { - return 0, _get_errno(result) - } - return result, ERROR_NONE + result := unix.sys_ppoll(fds, uint(nfds), timeout, sigmask, size_of(sigset_t)) + if result < 0 { + return 0, _get_errno(result) + } + return result, ERROR_NONE } From 2992ca5df122e2f20113d5b357413c7fff606879 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 15 Jun 2023 01:37:55 +0100 Subject: [PATCH 30/97] Basic support for new procedure code (non-polymorphic, non-proc-group) --- src/check_expr.cpp | 388 ++++++++++++++++++++++++++++++++++++-- src/llvm_backend_proc.cpp | 119 ++++++++++++ src/parser.hpp | 6 + src/parser_pos.cpp | 6 +- 4 files changed, 501 insertions(+), 18 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 52ab5092e..cc3f268c3 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -123,6 +123,8 @@ gb_internal bool is_diverging_expr(Ast *expr); gb_internal CallArgumentError check_order_of_call_arguments(CheckerContext *c, Type *proc_type, Ast *call, bool show_error); +gb_internal isize get_procedure_param_count_excluding_defaults(Type *pt, isize *param_count_); + enum LoadDirectiveResult { LoadDirective_Success = 0, LoadDirective_Error = 1, @@ -5151,14 +5153,19 @@ enum UnpackFlag : u32 { }; -gb_internal bool check_unpack_arguments(CheckerContext *ctx, Entity **lhs, isize lhs_count, Array *operands, Slice const &rhs, UnpackFlags flags) { +gb_internal bool check_unpack_arguments(CheckerContext *ctx, Entity **lhs, isize lhs_count, Array *operands, Slice const &rhs_arguments, UnpackFlags flags) { bool allow_ok = (flags & UnpackFlag_AllowOk) != 0; bool is_variadic = (flags & UnpackFlag_IsVariadic) != 0; bool allow_undef = (flags & UnpackFlag_AllowUndef) != 0; bool optional_ok = false; isize tuple_index = 0; - for_array(i, rhs) { + for (Ast *rhs : rhs_arguments) { + if (rhs->kind == Ast_FieldValue) { + error(rhs, "Invalid use of 'field = value'"); + rhs = rhs->FieldValue.value; + } + CheckerContext c_ = *ctx; CheckerContext *c = &c_; @@ -5166,12 +5173,11 @@ gb_internal bool check_unpack_arguments(CheckerContext *ctx, Entity **lhs, isize Type *type_hint = nullptr; + if (lhs != nullptr && tuple_index < lhs_count) { // NOTE(bill): override DeclInfo for dependency Entity *e = lhs[tuple_index]; if (e != nullptr) { - // DeclInfo *decl = decl_info_of_entity(e); - // if (decl) c->decl = decl; type_hint = e->type; if (e->flags & EntityFlag_Ellipsis) { GB_ASSERT(is_type_slice(e->type)); @@ -5183,8 +5189,6 @@ gb_internal bool check_unpack_arguments(CheckerContext *ctx, Entity **lhs, isize // NOTE(bill): override DeclInfo for dependency Entity *e = lhs[lhs_count-1]; if (e != nullptr) { - // DeclInfo *decl = decl_info_of_entity(e); - // if (decl) c->decl = decl; type_hint = e->type; if (e->flags & EntityFlag_Ellipsis) { GB_ASSERT(is_type_slice(e->type)); @@ -5194,15 +5198,15 @@ gb_internal bool check_unpack_arguments(CheckerContext *ctx, Entity **lhs, isize } } - Ast *rhs_expr = unparen_expr(rhs[i]); + Ast *rhs_expr = unparen_expr(rhs); if (allow_undef && rhs_expr != nullptr && rhs_expr->kind == Ast_Uninit) { // NOTE(bill): Just handle this very specific logic here o.type = t_untyped_uninit; o.mode = Addressing_Value; - o.expr = rhs[i]; - add_type_and_value(c, rhs[i], o.mode, o.type, o.value); + o.expr = rhs; + add_type_and_value(c, rhs, o.mode, o.type, o.value); } else { - check_expr_base(c, &o, rhs[i], type_hint); + check_expr_base(c, &o, rhs, type_hint); } if (o.mode == Addressing_NoValue) { error_operand_no_value(&o); @@ -5210,7 +5214,7 @@ gb_internal bool check_unpack_arguments(CheckerContext *ctx, Entity **lhs, isize } if (o.type == nullptr || o.type->kind != Type_Tuple) { - if (allow_ok && lhs_count == 2 && rhs.count == 1 && + if (allow_ok && lhs_count == 2 && rhs_arguments.count == 1 && (o.mode == Addressing_MapIndex || o.mode == Addressing_OptionalOk || o.mode == Addressing_OptionalOkPtr)) { Ast *expr = unparen_expr(o.expr); @@ -5966,14 +5970,364 @@ gb_internal CallArgumentError check_order_of_call_arguments(CheckerContext *c, T } +gb_internal CallArgumentData check_call_arguments_single_procedure(CheckerContext *c, Operand *operand, Ast *call) { + Type *proc_type = nullptr; + + CallArgumentData data = {}; + data.result_type = t_invalid; + + + GB_ASSERT(operand->mode != Addressing_ProcGroup); + + proc_type = base_type(operand->type); + + if (proc_type == nullptr || proc_type->kind != Type_Proc) { + gbString s = type_to_string(proc_type); + error(call, "Expected a procedure to call, got %s", s); + gb_string_free(s); + + return data; + } + + if (is_type_polymorphic(proc_type)) { + error(call, "Polymorphic procedures not yet supported"); + return data; + } + + TypeProc *pt = &proc_type->Proc; + + TEMPORARY_ALLOCATOR_GUARD(); + ast_node(ce, CallExpr, call); + + bool any_failure = false; + + bool vari_expand = (ce->ellipsis.pos.line != 0); + + auto positional_args = ce->args; + for (isize i = 0; i < ce->args.count; i++) { + Ast *arg = ce->args.data[i]; + if (arg->kind == Ast_FieldValue) { + positional_args.count = i; + break; + } + } + auto named_args = slice(ce->args, positional_args.count, ce->args.count); + + auto positional_operands = array_make(heap_allocator(), 0, positional_args.count); + auto variadic_operands = array_make(heap_allocator(), 0, 0); + + defer (array_free(&positional_operands)); + defer (array_free(&variadic_operands)); + + isize positional_operand_count = 0; + + auto visited = slice_make(temporary_allocator(), pt->param_count); + + if (positional_args.count > 0) { + isize lhs_count = -1; + bool is_variadic = false; + Entity **lhs = populate_proc_parameter_list(c, proc_type, &lhs_count, &is_variadic); + check_unpack_arguments(c, lhs, lhs_count, &positional_operands, positional_args, is_variadic ? UnpackFlag_IsVariadic : UnpackFlag_None); + + if (!is_variadic) { + if (pt->param_count < positional_operands.count) { + char const *err_fmt = "Too many arguments for '%s', expected a maximum of %td arguments, got %td"; + gbString c = expr_to_string(ce->proc); + error(call, err_fmt, c, pt->param_count, positional_operands.count); + gb_string_free(c); + return data; + } + } + + for (Operand const &o : positional_operands) { + check_no_copy_assignment(o, str_lit("procedure call expression")); + } + + positional_operand_count = gb_min(pt->param_count, positional_operands.count); + if (is_variadic) { + positional_operand_count = gb_min(pt->variadic_index, positional_operand_count); + } + + for (isize i = 0; i < positional_operand_count; i++) { + visited[i] = true; + } + if (is_variadic) { + visited[pt->variadic_index] = true; + } + + for (isize i = positional_operand_count; i < positional_operands.count; i++) { + array_add(&variadic_operands, positional_operands[i]); + } + if (vari_expand) { + if (variadic_operands.count > 1) { + error(variadic_operands[1].expr, "Unexpected position arguments after variadic expansion with '..'"); + return data; + } + } + + array_resize(&positional_operands, positional_operand_count); + } + + isize param_count = 0; + isize param_count_excluding_defaults = get_procedure_param_count_excluding_defaults(proc_type, ¶m_count); + + if (named_args.count == 0 && positional_operand_count < param_count_excluding_defaults) { + char const *err_fmt = "Too few arguments for '%s', expected a minimum of %td arguments, got %td"; + gbString c = expr_to_string(ce->proc); + error(call, err_fmt, c, param_count_excluding_defaults, positional_operands.count); + gb_string_free(c); + return data; + } + + for_array(i, positional_operands) { + Operand &o = positional_operands[i]; + isize param_index = i; + + Entity *e = pt->params->Tuple.variables[param_index]; + GB_ASSERT(e->kind == Entity_Variable); + + check_assignment(c, &o, e->type, str_lit("procedure call expression")); + } + + if (pt->variadic) { + isize param_index = pt->variadic_index; + + Entity *e = pt->params->Tuple.variables[param_index]; + GB_ASSERT(e->kind == Entity_Variable); + GB_ASSERT(e->flags & EntityFlag_Ellipsis); + GB_ASSERT(is_type_slice(e->type)); + + if (vari_expand) { + if (variadic_operands.count != 0) { + GB_ASSERT(variadic_operands.count == 1); + check_assignment(c, &variadic_operands[0], e->type, str_lit("procedure call expression")); + } + } else { + Type *elem = e->type->Slice.elem; + for (Operand &o : variadic_operands) { + check_assignment(c, &o, elem, str_lit("procedure call expression")); + } + } + } + + array_resize(&positional_operands, pt->param_count); + + for_array(i, named_args) { + Ast *arg = named_args[i]; + if (arg->kind != Ast_FieldValue) { + error(arg, "Expected a 'field = value'"); + return data; + } + ast_node(fv, FieldValue, arg); + if (fv->field->kind != Ast_Ident) { + gbString expr_str = expr_to_string(fv->field); + error(arg, "Invalid parameter name '%s' in procedure call", expr_str); + any_failure = true; + gb_string_free(expr_str); + continue; + } + String name = fv->field->Ident.token.string; + isize param_index = lookup_procedure_parameter(pt, name); + if (param_index < 0) { + error(arg, "No parameter named '%.*s' for this procedure type", LIT(name)); + any_failure = true; + continue; + } + + bool prev_visited = visited[param_index]; + visited[param_index] = true; + + if (prev_visited) { + error(arg, "Duplicate parameter named '%.*s' in procedure call", LIT(name)); + } + + Entity *e = pt->params->Tuple.variables[param_index]; + GB_ASSERT(e->kind == Entity_Variable); + + Operand o = {}; + check_expr_with_type_hint(c, &o, fv->value, e->type); + if (o.mode == Addressing_Invalid) { + any_failure = true; + } + + check_assignment(c, &o, e->type, str_lit("procedure call expression")); + check_no_copy_assignment(o, str_lit("procedure call expression")); + + if (!prev_visited) { + positional_operands[param_index] = o; + } + } + + for_array(i, visited) { + if (!visited[i]) { + if (pt->variadic && i == pt->variadic_index) { + visited[i] = true; + continue; + } + + Entity *e = pt->params->Tuple.variables[i]; + if (e->kind == Entity_Variable) { + if (has_parameter_value(e->Variable.param_value)) { + visited[i] = true; + continue; + } + } + gbString t = type_to_string(e->type); + error(call, "Missing parameter of type '%s' at index %td", t, i); + gb_string_free(t); + } + } + + if (any_failure) { + return data; + } + + + auto ordered_args = gb_alloc_item(permanent_allocator(), AstOrderedArgs); + ordered_args->positional = positional_args; + ordered_args->named = named_args; + + ce->ordered_args = ordered_args; + + data.result_type = pt->results; + return data; +#if 0 + + + + /* + auto visited = slice_make(temporary_allocator(), pt->param_count); + auto ordered_values = slice_make(temporary_allocator(), pt->param_count); + + + for_array(i, ce->args) { + Ast *arg = ce->args[i]; + + isize param_index = i; + String param_name = {}; + + if (arg->kind == Ast_FieldValue) { + ast_node(fv, FieldValue, arg); + if (fv->field->kind != Ast_Ident) { + gbString expr_str = expr_to_string(fv->field); + error(arg, "Invalid parameter name '%s' in procedure call", expr_str); + any_failure = true; + gb_string_free(expr_str); + continue; + } + param_name = fv->field->Ident.token.string; + param_index = lookup_procedure_parameter(pt, param_name); + if (param_index < 0) { + error(arg, "No parameter named '%.*s' for this procedure type", LIT(param_name)); + any_failure = true; + continue; + } + arg = fv->value; + } else { + if (param_index >= pt->param_count) { + error(arg, "Too many parameters in procedure call, expected a maximum of %td, got %td", pt->param_count, ce->args.count); + any_failure = true; + break; + } + + param_name = pt->params->Tuple.variables[param_index]->token.string; + } + if (visited[param_index]) { + error(arg, "Duplicate parameter '%.*s' in procedure call", LIT(param_name)); + any_failure = true; + } else { + ParameterValue value = {}; + value.kind = ParameterValue_Value; + value.original_ast_expr = arg; + value.ast_value = arg; + ordered_values[param_index] = value; + } + visited[param_index] = true; + } + + for (isize i = 0; i < pt->param_count; i++) { + if (!visited[i]) { + Entity *e = pt->params->Tuple.variables[i]; + if (e->kind == Entity_Variable && + has_parameter_value(e->Variable.param_value)) { + ordered_values[i] = e->Variable.param_value; + visited[i] = true; + } + } + } + + for (isize i = 0; i < visited.count; i++) { + if (!visited[i]) { + Entity *e = pt->params->Tuple.variables[i]; + gbString t = type_to_string(e->type); + defer (gb_string_free(t)); + if (is_blank_ident(e->token)) { + error(call, "Missing parameter of type '%s' at index %td", t, i); + } else { + error(call, "Missing parameter '%.*s' of type '%s'", LIT(e->token.string), t, i); + } + return data; + } + } + */ + if (any_failure) { + return data; + } + + error(call, "TODO: Implement check_call_arguments_single_procedure for %s", type_to_string(proc_type)); + + Array operands = {}; + defer (array_free(&operands)); + + Ast *ident = unparen_expr(operand->expr); + while (ident->kind == Ast_SelectorExpr) { + Ast *s = ident->SelectorExpr.selector; + ident = unparen_expr(s); + } + + Entity *e = entity_of_node(ident); + + CallArgumentError err = check_call_arguments_internal(c, call, proc_type, e, operands, CallArgumentMode_ShowErrors, &data); + gb_unused(err); + Entity *entity_to_use = data.gen_entity != nullptr ? data.gen_entity : e; + add_entity_use(c, ident, entity_to_use); + if (entity_to_use != nullptr) { + update_untyped_expr_type(c, operand->expr, entity_to_use->type, true); + } + if (data.gen_entity != nullptr) { + Entity *e = data.gen_entity; + DeclInfo *decl = data.gen_entity->decl_info; + CheckerContext ctx = *c; + ctx.scope = decl->scope; + ctx.decl = decl; + ctx.proc_name = e->token.string; + ctx.curr_proc_decl = decl; + ctx.curr_proc_sig = e->type; + + GB_ASSERT(decl->proc_lit->kind == Ast_ProcLit); + bool ok = evaluate_where_clauses(&ctx, call, decl->scope, &decl->proc_lit->ProcLit.where_clauses, true); + decl->where_clauses_evaluated = true; + + if (ok && (data.gen_entity->flags & EntityFlag_ProcBodyChecked) == 0) { + check_procedure_later(c->checker, e->file, e->token, decl, e->type, decl->proc_lit->ProcLit.body, decl->proc_lit->ProcLit.tags); + } + } + return data; +#endif +} + + gb_internal CallArgumentData check_call_arguments(CheckerContext *c, Operand *operand, Ast *call) { + if (operand->mode != Addressing_ProcGroup && is_type_proc(operand->type) && !is_type_polymorphic(operand->type)) { + return check_call_arguments_single_procedure(c, operand, call); + } + ast_node(ce, CallExpr, call); CallArgumentCheckerType *call_checker = check_call_arguments_internal; Array operands = {}; defer (array_free(&operands)); - Type *result_type = t_invalid; Type *proc_type = base_type(operand->type); @@ -6102,7 +6456,7 @@ gb_internal CallArgumentData check_call_arguments(CheckerContext *c, Operand *op } for (Operand const &o : operands) { - check_no_copy_assignment(o, str_lit("call expression")); + check_no_copy_assignment(o, str_lit("procedure call expression")); } if (operand->mode == Addressing_ProcGroup) { @@ -6861,6 +7215,7 @@ gb_internal bool check_call_parameter_mixture(Operand *operand, Slice con if (was_named && arg->kind != Ast_FieldValue) { error(arg, "Non-named parameter is not allowed to follow named parameter i.e. 'field = value' in a %s", context); failure = true; + break; } was_named = was_named || arg->kind == Ast_FieldValue; } @@ -7034,8 +7389,11 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c return builtin_procs[id].kind; } - // CHECK_CALL_PARAMETER_MIXTURE_OR_RETURN("procedure call", true); - CHECK_CALL_PARAMETER_MIXTURE_OR_RETURN("procedure call"); + if (operand->mode == Addressing_ProcGroup) { + CHECK_CALL_PARAMETER_MIXTURE_OR_RETURN("procedure group call"); + } else { + CHECK_CALL_PARAMETER_MIXTURE_OR_RETURN("procedure call", true); + } Entity *initial_entity = entity_of_node(operand->expr); diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 8b9f8b249..3c1cfe47c 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -3145,6 +3145,118 @@ gb_internal lbValue lb_build_call_expr(lbProcedure *p, Ast *expr) { } return res; } + +gb_internal void lb_add_values_to_array(lbProcedure *p, Array *args, lbValue value) { + if (is_type_tuple(value.type)) { + for_array(i, value.type->Tuple.variables) { + lbValue sub_value = lb_emit_struct_ev(p, value, cast(i32)i); + array_add(args, sub_value); + } + } else { + array_add(args, value); + } +} +gb_internal lbValue lb_build_call_expr_internal_with_arg_ordered_args(lbProcedure *p, lbValue value, TypeProc *pt, Ast *call, AstOrderedArgs *ordered_args) { + ast_node(ce, CallExpr, call); + + auto args = array_make(permanent_allocator(), 0, pt->param_count); + + bool vari_expand = (ce->ellipsis.pos.line != 0); + + for_array(i, ordered_args->positional) { + Entity *e = pt->params->Tuple.variables[i]; + GB_ASSERT(e->kind == Entity_Variable); + + if (pt->variadic && pt->variadic_index == i) { + lbValue variadic_args = lb_const_nil(p->module, e->type); + auto variadic = slice(ordered_args->positional, pt->variadic_index, ordered_args->positional.count); + if (variadic.count != 0) { + // variadic call argument generation + Type *slice_type = e->type; + GB_ASSERT(slice_type->kind == Type_Slice); + if (vari_expand) { + GB_ASSERT(variadic.count == 1); + variadic_args = lb_build_expr(p, variadic[0]); + variadic_args = lb_emit_conv(p, variadic_args, slice_type); + } else { + Type *elem_type = slice_type->Slice.elem; + + auto var_args = array_make(heap_allocator(), 0, variadic.count); + defer (array_free(&var_args)); + for (Ast *var_arg : variadic) { + lbValue v = lb_build_expr(p, var_arg); + lb_add_values_to_array(p, &var_args, v); + } + isize slice_len = var_args.count; + if (slice_len > 0) { + lbAddr slice = lb_add_local_generated(p, slice_type, true); + lbAddr base_array = lb_add_local_generated(p, alloc_type_array(elem_type, slice_len), true); + + for (isize i = 0; i < var_args.count; i++) { + lbValue addr = lb_emit_array_epi(p, base_array.addr, cast(i32)i); + lbValue var_arg = var_args[i]; + var_arg = lb_emit_conv(p, var_arg, elem_type); + lb_emit_store(p, addr, var_arg); + } + + lbValue base_elem = lb_emit_array_epi(p, base_array.addr, 0); + lbValue len = lb_const_int(p->module, t_int, slice_len); + lb_fill_slice(p, slice, base_elem, len); + + variadic_args = lb_addr_load(p, slice); + } + } + } + array_add(&args, variadic_args); + + break; + } else { + lbValue value = lb_build_expr(p, ordered_args->positional[i]); + lb_add_values_to_array(p, &args, value); + } + } + + array_resize(&args, pt->param_count); + + for (Ast *arg : ordered_args->named) { + ast_node(fv, FieldValue, arg); + GB_ASSERT(fv->field->kind == Ast_Ident); + String name = fv->field->Ident.token.string; + gb_unused(name); + isize param_index = lookup_procedure_parameter(pt, name); + GB_ASSERT(param_index >= 0); + + lbValue value = lb_build_expr(p, fv->value); + GB_ASSERT(!is_type_tuple(value.type)); + args[param_index] = value; + } + + TokenPos pos = ast_token(ce->proc).pos; + + for_array(i, args) { + Entity *e = pt->params->Tuple.variables[i]; + lbValue arg = args[i]; + if (arg.value == nullptr) { + switch (e->kind) { + case Entity_TypeName: + args[i] = lb_const_nil(p->module, e->type); + break; + case Entity_Variable: + args[i] = lb_handle_param_value(p, e->type, e->Variable.param_value, pos); + break; + case Entity_Constant: + GB_PANIC("TODO constant parameter"); + break; + } + } else { + args[i] = lb_emit_conv(p, arg, e->type); + } + } + + + return lb_emit_call(p, value, args, ce->inlining); +} + gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { lbModule *m = p->module; @@ -3219,6 +3331,10 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { GB_ASSERT(proc_type_->kind == Type_Proc); TypeProc *pt = &proc_type_->Proc; + if (ce->ordered_args) { + return lb_build_call_expr_internal_with_arg_ordered_args(p, value, pt, expr, ce->ordered_args); + } + if (is_call_expr_field_value(ce)) { auto args = array_make(permanent_allocator(), pt->param_count); @@ -3274,6 +3390,9 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { isize arg_count = 0; for_array(i, ce->args) { Ast *arg = ce->args[i]; + if (arg->kind == Ast_FieldValue) { + arg = arg->FieldValue.value; + } TypeAndValue tav = type_and_value_of_expr(arg); GB_ASSERT_MSG(tav.mode != Addressing_Invalid, "%s %s %d", expr_to_string(arg), expr_to_string(expr), tav.mode); GB_ASSERT_MSG(tav.mode != Addressing_ProcGroup, "%s", expr_to_string(arg)); diff --git a/src/parser.hpp b/src/parser.hpp index 6ba4ef6d6..e0e78fa1d 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -367,6 +367,11 @@ gb_global char const *union_type_kind_strings[UnionType_COUNT] = { "#shared_nil", }; +struct AstOrderedArgs { + Slice positional; + Slice named; +}; + #define AST_KINDS \ AST_KIND(Ident, "identifier", struct { \ Token token; \ @@ -442,6 +447,7 @@ AST_KIND(_ExprBegin, "", bool) \ ProcInlining inlining; \ bool optional_ok_one; \ bool was_selector; \ + AstOrderedArgs *ordered_args; \ }) \ AST_KIND(FieldValue, "field value", struct { Token eq; Ast *field, *value; }) \ AST_KIND(EnumFieldValue, "enum field value", struct { \ diff --git a/src/parser_pos.cpp b/src/parser_pos.cpp index 2f22a85d3..3d2e8f27d 100644 --- a/src/parser_pos.cpp +++ b/src/parser_pos.cpp @@ -37,9 +37,9 @@ gb_internal Token ast_token(Ast *node) { return ast_token(node->ImplicitSelectorExpr.selector); } return node->ImplicitSelectorExpr.token; - case Ast_IndexExpr: return node->IndexExpr.open; - case Ast_MatrixIndexExpr: return node->MatrixIndexExpr.open; - case Ast_SliceExpr: return node->SliceExpr.open; + case Ast_IndexExpr: return ast_token(node->IndexExpr.expr); + case Ast_MatrixIndexExpr: return ast_token(node->MatrixIndexExpr.expr); + case Ast_SliceExpr: return ast_token(node->SliceExpr.expr); case Ast_Ellipsis: return node->Ellipsis.token; case Ast_FieldValue: if (node->FieldValue.field) { From dce57627c972276be8398ec0e2efcb7782b650d8 Mon Sep 17 00:00:00 2001 From: Rickard Andersson Date: Thu, 15 Jun 2023 08:54:42 +0300 Subject: [PATCH 31/97] fix: remove redefinition of `timespec` They are in the same package, so it's accessible anyway. --- core/sys/unix/syscalls_linux.odin | 5 ----- 1 file changed, 5 deletions(-) diff --git a/core/sys/unix/syscalls_linux.odin b/core/sys/unix/syscalls_linux.odin index fdfc27187..92b551041 100644 --- a/core/sys/unix/syscalls_linux.odin +++ b/core/sys/unix/syscalls_linux.odin @@ -2077,11 +2077,6 @@ sys_fcntl :: proc "contextless" (fd: int, cmd: int, arg: int) -> int { sys_poll :: proc "contextless" (fds: rawptr, nfds: uint, timeout: int) -> int { // NOTE: specialcased here because `arm64` does not have `poll` when ODIN_ARCH == .arm64 { - // redefined because we can't depend on the `unix` module here - timespec :: struct { - tv_sec: i64, - tv_nsec: i64, - } seconds := i64(timeout / 1_000) nanoseconds := i64((timeout % 1000) * 1_000_000) timeout_spec := timespec{seconds, nanoseconds} From 37469dc9c2eb2df30995599b80b234526c05f4d1 Mon Sep 17 00:00:00 2001 From: Rickard Andersson Date: Thu, 15 Jun 2023 16:10:00 +0300 Subject: [PATCH 32/97] fix(poll): make interface more odinary We take `fds` as a normal slice and get the length from it instead of bothering with a second parameter. --- core/os/os_linux.odin | 16 +++++++--------- core/sys/unix/syscalls_linux.odin | 2 +- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/core/os/os_linux.odin b/core/os/os_linux.odin index ff3ff700c..e0b60fd36 100644 --- a/core/os/os_linux.odin +++ b/core/os/os_linux.odin @@ -433,13 +433,11 @@ AT_REMOVEDIR :: uintptr(0x200) AT_SYMLINK_NOFOLLOW :: uintptr(0x100) pollfd :: struct { - fd: c.int, - events: c.short, - revents: c.short, + fd: c.int, + events: c.short, + revents: c.short, } -nfds_t :: distinct c.uint - sigset_t :: distinct u64 foreign libc { @@ -1098,16 +1096,16 @@ fcntl :: proc(fd: int, cmd: int, arg: int) -> (int, Errno) { return result, ERROR_NONE } -poll :: proc(fds: [^]pollfd, nfds: nfds_t, timeout: int) -> (int, Errno) { - result := unix.sys_poll(fds, uint(nfds), timeout) +poll :: proc(fds: []pollfd, timeout: int) -> (int, Errno) { + result := unix.sys_poll(raw_data(fds), uint(len(fds)), timeout) if result < 0 { return 0, _get_errno(result) } return result, ERROR_NONE } -ppoll :: proc(fds: [^]pollfd, nfds: nfds_t, timeout: ^unix.timespec, sigmask: ^sigset_t) -> (int, Errno) { - result := unix.sys_ppoll(fds, uint(nfds), timeout, sigmask, size_of(sigset_t)) +ppoll :: proc(fds: []pollfd, timeout: ^unix.timespec, sigmask: ^sigset_t) -> (int, Errno) { + result := unix.sys_ppoll(raw_data(fds), uint(len(fds)), timeout, sigmask, size_of(sigset_t)) if result < 0 { return 0, _get_errno(result) } diff --git a/core/sys/unix/syscalls_linux.odin b/core/sys/unix/syscalls_linux.odin index 92b551041..3083c084b 100644 --- a/core/sys/unix/syscalls_linux.odin +++ b/core/sys/unix/syscalls_linux.odin @@ -2088,7 +2088,7 @@ sys_poll :: proc "contextless" (fds: rawptr, nfds: uint, timeout: int) -> int { } sys_ppoll :: proc "contextless" (fds: rawptr, nfds: uint, timeout: rawptr, sigmask: rawptr, sigsetsize: uint) -> int { - return int(intrinsics.syscall(SYS_ppoll, uintptr(fds), uintptr(nfds), uintptr(timeout), uintptr(sigmask), uintptr(sigsetsize))) + return int(intrinsics.syscall(SYS_ppoll, uintptr(fds), uintptr(nfds), uintptr(timeout), uintptr(sigmask), uintptr(sigsetsize))) } get_errno :: proc "contextless" (res: int) -> i32 { From 427f212170114b006fcb6ff1366231f99acff13e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 19 Jun 2023 13:56:00 +0100 Subject: [PATCH 33/97] Begin work in procedure calls --- src/check_expr.cpp | 357 ++++++++++++++++++++++++++++++--------------- 1 file changed, 239 insertions(+), 118 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index cc3f268c3..c3f6002c7 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -5976,11 +5976,17 @@ gb_internal CallArgumentData check_call_arguments_single_procedure(CheckerContex CallArgumentData data = {}; data.result_type = t_invalid; - - GB_ASSERT(operand->mode != Addressing_ProcGroup); - proc_type = base_type(operand->type); + if (operand->mode == Addressing_ProcGroup) { + Array procs = proc_group_entities(c, *operand); + if (procs.count == 1) { + proc_type = base_type(procs[0]->type); + } else { + error(call, "Procedure groups >1 not yet supported"); + return data; + } + } if (proc_type == nullptr || proc_type->kind != Type_Proc) { gbString s = type_to_string(proc_type); error(call, "Expected a procedure to call, got %s", s); @@ -5989,12 +5995,15 @@ gb_internal CallArgumentData check_call_arguments_single_procedure(CheckerContex return data; } - if (is_type_polymorphic(proc_type)) { + if (proc_type != nullptr && is_type_polymorphic(proc_type)) { error(call, "Polymorphic procedures not yet supported"); return data; } - TypeProc *pt = &proc_type->Proc; + TypeProc *pt = nullptr; + if (proc_type) { + pt = &proc_type->Proc; + } TEMPORARY_ALLOCATOR_GUARD(); ast_node(ce, CallExpr, call); @@ -6015,21 +6024,31 @@ gb_internal CallArgumentData check_call_arguments_single_procedure(CheckerContex auto positional_operands = array_make(heap_allocator(), 0, positional_args.count); auto variadic_operands = array_make(heap_allocator(), 0, 0); + auto named_operands = array_make(heap_allocator(), 0, 0); defer (array_free(&positional_operands)); defer (array_free(&variadic_operands)); + defer (array_free(&named_operands)); isize positional_operand_count = 0; - auto visited = slice_make(temporary_allocator(), pt->param_count); + Slice visited = {}; if (positional_args.count > 0) { isize lhs_count = -1; bool is_variadic = false; - Entity **lhs = populate_proc_parameter_list(c, proc_type, &lhs_count, &is_variadic); + Entity **lhs = nullptr; + if (proc_type != nullptr && is_type_proc(proc_type)) { + lhs = populate_proc_parameter_list(c, proc_type, &lhs_count, &is_variadic); + } check_unpack_arguments(c, lhs, lhs_count, &positional_operands, positional_args, is_variadic ? UnpackFlag_IsVariadic : UnpackFlag_None); + } + positional_operand_count = positional_operands.count; - if (!is_variadic) { + if (proc_type != nullptr) { + visited = slice_make(temporary_allocator(), pt->param_count); + + if (!pt->variadic) { if (pt->param_count < positional_operands.count) { char const *err_fmt = "Too many arguments for '%s', expected a maximum of %td arguments, got %td"; gbString c = expr_to_string(ce->proc); @@ -6037,27 +6056,24 @@ gb_internal CallArgumentData check_call_arguments_single_procedure(CheckerContex gb_string_free(c); return data; } - } + positional_operand_count = positional_operands.count; + } else { + // only state it is visited if it was set later on + visited[pt->variadic_index] = positional_operand_count > pt->variadic_index; - for (Operand const &o : positional_operands) { - check_no_copy_assignment(o, str_lit("procedure call expression")); - } - - positional_operand_count = gb_min(pt->param_count, positional_operands.count); - if (is_variadic) { positional_operand_count = gb_min(pt->variadic_index, positional_operand_count); } + for (isize i = 0; i < positional_operand_count; i++) { visited[i] = true; } - if (is_variadic) { - visited[pt->variadic_index] = true; - } for (isize i = positional_operand_count; i < positional_operands.count; i++) { array_add(&variadic_operands, positional_operands[i]); } + array_resize(&positional_operands, positional_operand_count); + if (vari_expand) { if (variadic_operands.count > 1) { error(variadic_operands[1].expr, "Unexpected position arguments after variadic expansion with '..'"); @@ -6065,119 +6081,214 @@ gb_internal CallArgumentData check_call_arguments_single_procedure(CheckerContex } } - array_resize(&positional_operands, positional_operand_count); - } + isize param_count = 0; + isize param_count_excluding_defaults = get_procedure_param_count_excluding_defaults(proc_type, ¶m_count); - isize param_count = 0; - isize param_count_excluding_defaults = get_procedure_param_count_excluding_defaults(proc_type, ¶m_count); - - if (named_args.count == 0 && positional_operand_count < param_count_excluding_defaults) { - char const *err_fmt = "Too few arguments for '%s', expected a minimum of %td arguments, got %td"; - gbString c = expr_to_string(ce->proc); - error(call, err_fmt, c, param_count_excluding_defaults, positional_operands.count); - gb_string_free(c); - return data; - } - - for_array(i, positional_operands) { - Operand &o = positional_operands[i]; - isize param_index = i; - - Entity *e = pt->params->Tuple.variables[param_index]; - GB_ASSERT(e->kind == Entity_Variable); - - check_assignment(c, &o, e->type, str_lit("procedure call expression")); - } - - if (pt->variadic) { - isize param_index = pt->variadic_index; - - Entity *e = pt->params->Tuple.variables[param_index]; - GB_ASSERT(e->kind == Entity_Variable); - GB_ASSERT(e->flags & EntityFlag_Ellipsis); - GB_ASSERT(is_type_slice(e->type)); - - if (vari_expand) { - if (variadic_operands.count != 0) { - GB_ASSERT(variadic_operands.count == 1); - check_assignment(c, &variadic_operands[0], e->type, str_lit("procedure call expression")); - } - } else { - Type *elem = e->type->Slice.elem; - for (Operand &o : variadic_operands) { - check_assignment(c, &o, elem, str_lit("procedure call expression")); - } - } - } - - array_resize(&positional_operands, pt->param_count); - - for_array(i, named_args) { - Ast *arg = named_args[i]; - if (arg->kind != Ast_FieldValue) { - error(arg, "Expected a 'field = value'"); + if (named_args.count == 0 && positional_operands.count < param_count_excluding_defaults) { + char const *err_fmt = "Too few arguments for '%s', expected a minimum of %td arguments, got %td"; + gbString c = expr_to_string(ce->proc); + error(call, err_fmt, c, param_count_excluding_defaults, positional_operands.count); + gb_string_free(c); return data; } - ast_node(fv, FieldValue, arg); - if (fv->field->kind != Ast_Ident) { - gbString expr_str = expr_to_string(fv->field); - error(arg, "Invalid parameter name '%s' in procedure call", expr_str); - any_failure = true; - gb_string_free(expr_str); - continue; - } - String name = fv->field->Ident.token.string; - isize param_index = lookup_procedure_parameter(pt, name); - if (param_index < 0) { - error(arg, "No parameter named '%.*s' for this procedure type", LIT(name)); - any_failure = true; - continue; + + + for_array(i, positional_operands) { + Operand &o = positional_operands[i]; + isize param_index = i; + + Entity *e = pt->params->Tuple.variables[param_index]; + GB_ASSERT(e->kind == Entity_Variable); + + check_assignment(c, &o, e->type, str_lit("procedure call expression")); } - bool prev_visited = visited[param_index]; - visited[param_index] = true; + if (pt->variadic) { + isize param_index = pt->variadic_index; - if (prev_visited) { - error(arg, "Duplicate parameter named '%.*s' in procedure call", LIT(name)); + Entity *e = pt->params->Tuple.variables[param_index]; + GB_ASSERT(e->kind == Entity_Variable); + GB_ASSERT(e->flags & EntityFlag_Ellipsis); + GB_ASSERT(is_type_slice(e->type)); + + if (vari_expand) { + if (variadic_operands.count != 0) { + GB_ASSERT(variadic_operands.count == 1); + check_assignment(c, &variadic_operands[0], e->type, str_lit("procedure call expression")); + } + } else { + Type *elem = e->type->Slice.elem; + for (Operand &o : variadic_operands) { + check_assignment(c, &o, elem, str_lit("procedure call expression")); + } + } } - Entity *e = pt->params->Tuple.variables[param_index]; - GB_ASSERT(e->kind == Entity_Variable); + array_resize(&positional_operands, pt->param_count); - Operand o = {}; - check_expr_with_type_hint(c, &o, fv->value, e->type); - if (o.mode == Addressing_Invalid) { - any_failure = true; - } - - check_assignment(c, &o, e->type, str_lit("procedure call expression")); - check_no_copy_assignment(o, str_lit("procedure call expression")); - - if (!prev_visited) { - positional_operands[param_index] = o; - } } - for_array(i, visited) { - if (!visited[i]) { - if (pt->variadic && i == pt->variadic_index) { - visited[i] = true; + + if (named_args.count > 0) { + struct NamedParameter { + String key; + Ast * value; + }; + + auto named_parameters = array_make(heap_allocator(), 0, named_args.count); + defer (array_free(&named_parameters)); + + for_array(i, named_args) { + Ast *arg = named_args[i]; + if (arg->kind != Ast_FieldValue) { + error(arg, "Expected a 'field = value'"); + return data; + } + ast_node(fv, FieldValue, arg); + if (fv->field->kind != Ast_Ident) { + gbString expr_str = expr_to_string(fv->field); + error(arg, "Invalid parameter name '%s' in procedure call", expr_str); + any_failure = true; + gb_string_free(expr_str); + continue; + } + String name = fv->field->Ident.token.string; + + array_add(&named_parameters, NamedParameter{name, fv->value}); + } + + StringMap type_hint_map = {}; // Key: String + string_map_init(&type_hint_map, 2*named_args.count); + defer (string_map_destroy(&type_hint_map)); + + if (proc_type != nullptr) { + TypeTuple *param_tuple = &pt->params->Tuple; + for (Entity *e : param_tuple->variables) { + if (is_blank_ident(e->token)) { + continue; + } + string_map_set(&type_hint_map, e->token.string, e->type); + } + } else if (operand->mode == Addressing_ProcGroup) { + Array procs = proc_group_entities(c, *operand); + for (Entity *proc : procs) { + Type *proc_type = base_type(proc->type); + if (is_type_proc(proc_type)) { + TypeProc *pt = &proc_type->Proc; + TypeTuple *param_tuple = nullptr; + if (pt->params != nullptr) { + param_tuple = &pt->params->Tuple; + } + if (param_tuple == nullptr) { + continue; + } + for (Entity *e : param_tuple->variables) { + if (is_blank_ident(e->token)) { + continue; + } + StringHashKey key = string_hash_string(e->token.string); + Type **found = string_map_get(&type_hint_map, key); + if (found) { + Type *t = *found; + if (t == nullptr) { + // NOTE(bill): Ambiguous named parameter across all types + continue; + } + if (are_types_identical(t, e->type)) { + // NOTE(bill): No need to set again + } else { + // NOTE(bill): Ambiguous named parameter across all types so set it to a nullptr + string_map_set(&type_hint_map, key, cast(Type *)nullptr); + } + } else { + string_map_set(&type_hint_map, key, e->type); + } + } + } + } + } + + for (NamedParameter ¶m : named_parameters) { + Type *type_hint = nullptr; + if (Type **found = string_map_get(&type_hint_map, param.key)) { + type_hint = *found; + } + + Operand operand = {}; + check_expr_or_type(c, &operand, param.value, type_hint); + array_add(&named_operands, operand); + } + + for (NamedParameter const ¶m : named_parameters) { + isize param_index = lookup_procedure_parameter(pt, param.key); + if (param_index < 0) { + error(param.value, "No parameter named '%.*s' for this procedure type", LIT(param.key)); + any_failure = true; continue; } - Entity *e = pt->params->Tuple.variables[i]; - if (e->kind == Entity_Variable) { - if (has_parameter_value(e->Variable.param_value)) { + bool prev_visited = visited[param_index]; + visited[param_index] = true; + + if (prev_visited) { + error(param.value, "Duplicate parameter named '%.*s' in procedure call", LIT(param.key)); + } + + Entity *e = pt->params->Tuple.variables[param_index]; + GB_ASSERT(e->kind == Entity_Variable); + + Operand o = {}; + check_expr_with_type_hint(c, &o, param.value, e->type); + if (o.mode == Addressing_Invalid) { + any_failure = true; + } + + check_assignment(c, &o, e->type, str_lit("procedure call expression")); + + if (!prev_visited) { + positional_operands[param_index] = o; + } + } + + } + + if (proc_type != nullptr) { + if (pt->variadic) { + visited[pt->variadic_index] = true; + } + + for_array(i, visited) { + if (!visited[i]) { + if (pt->variadic && i == pt->variadic_index) { visited[i] = true; continue; } + + Entity *e = pt->params->Tuple.variables[i]; + if (e->kind == Entity_Variable) { + if (has_parameter_value(e->Variable.param_value)) { + visited[i] = true; + continue; + } + } + gbString t = type_to_string(e->type); + error(call, "Missing parameter of type '%s' at index %td", t, i); + gb_string_free(t); } - gbString t = type_to_string(e->type); - error(call, "Missing parameter of type '%s' at index %td", t, i); - gb_string_free(t); } } + for (Operand const &o : positional_operands) { + check_no_copy_assignment(o, str_lit("procedure call expression")); + } + for (Operand const &o : variadic_operands) { + check_no_copy_assignment(o, str_lit("procedure call expression")); + } + for (Operand const &o : named_operands) { + check_no_copy_assignment(o, str_lit("procedure call expression")); + } + + if (any_failure) { return data; } @@ -6189,7 +6300,9 @@ gb_internal CallArgumentData check_call_arguments_single_procedure(CheckerContex ce->ordered_args = ordered_args; - data.result_type = pt->results; + if (proc_type != nullptr) { + data.result_type = pt->results; + } return data; #if 0 @@ -6274,8 +6387,6 @@ gb_internal CallArgumentData check_call_arguments_single_procedure(CheckerContex return data; } - error(call, "TODO: Implement check_call_arguments_single_procedure for %s", type_to_string(proc_type)); - Array operands = {}; defer (array_free(&operands)); @@ -6318,6 +6429,20 @@ gb_internal CallArgumentData check_call_arguments_single_procedure(CheckerContex gb_internal CallArgumentData check_call_arguments(CheckerContext *c, Operand *operand, Ast *call) { + if (operand->mode == Addressing_ProcGroup) { + Array procs = proc_group_entities(c, *operand); + bool all_non_poly = true; + for (Entity *e : procs) { + if (is_type_polymorphic(e->type)) { + all_non_poly = false; + break; + } + } + if (all_non_poly && (procs.count == 1 || c->pkg->name == "bug")) { + return check_call_arguments_single_procedure(c, operand, call); + } + } + if (operand->mode != Addressing_ProcGroup && is_type_proc(operand->type) && !is_type_polymorphic(operand->type)) { return check_call_arguments_single_procedure(c, operand, call); } @@ -7389,11 +7514,7 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c return builtin_procs[id].kind; } - if (operand->mode == Addressing_ProcGroup) { - CHECK_CALL_PARAMETER_MIXTURE_OR_RETURN("procedure group call"); - } else { - CHECK_CALL_PARAMETER_MIXTURE_OR_RETURN("procedure call", true); - } + CHECK_CALL_PARAMETER_MIXTURE_OR_RETURN(operand->mode == Addressing_ProcGroup ? "procedure group call": "procedure call", true); Entity *initial_entity = entity_of_node(operand->expr); From 6568625dea679b4622024f62fc14725aa49b2106 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 19 Jun 2023 22:12:47 +0100 Subject: [PATCH 34/97] Fix line error printing for error messages --- src/array.cpp | 4 +- src/check_expr.cpp | 760 +++++++++++++++++++++++++++++++++++--- src/error.cpp | 31 +- src/gb/gb.h | 39 +- src/llvm_backend_proc.cpp | 14 +- src/parser.hpp | 4 +- src/types.cpp | 6 + 7 files changed, 770 insertions(+), 88 deletions(-) diff --git a/src/array.cpp b/src/array.cpp index f1a1f93e2..d8e25d25d 100644 --- a/src/array.cpp +++ b/src/array.cpp @@ -80,7 +80,9 @@ gb_internal Slice slice_make(gbAllocator const &allocator, isize count) { GB_ASSERT(count >= 0); Slice s = {}; s.data = gb_alloc_array(allocator, T, count); - GB_ASSERT(s.data != nullptr); + if (count > 0) { + GB_ASSERT(s.data != nullptr); + } s.count = count; return s; } diff --git a/src/check_expr.cpp b/src/check_expr.cpp index c3f6002c7..e0c6be1d1 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -66,7 +66,7 @@ gb_internal int valid_index_and_score_cmp(void const *a, void const *b) { -#define CALL_ARGUMENT_CHECKER(name) CallArgumentError name(CheckerContext *c, Ast *call, Type *proc_type, Entity *entity, Array operands, CallArgumentErrorMode show_error_mode, CallArgumentData *data) +#define CALL_ARGUMENT_CHECKER(name) CallArgumentError name(CheckerContext *c, Ast *call, Type *proc_type, Entity *entity, Array operands, Array const &named_operands, CallArgumentErrorMode show_error_mode, CallArgumentData *data) typedef CALL_ARGUMENT_CHECKER(CallArgumentCheckerType); @@ -5314,7 +5314,24 @@ gb_internal isize get_procedure_param_count_excluding_defaults(Type *pt, isize * } +gb_internal isize lookup_procedure_parameter(TypeProc *pt, String parameter_name) { + isize param_count = pt->param_count; + for (isize i = 0; i < param_count; i++) { + Entity *e = pt->params->Tuple.variables[i]; + String name = e->token.string; + if (is_blank_ident(name)) { + continue; + } + if (name == parameter_name) { + return i; + } + } + return -1; +} + gb_internal CALL_ARGUMENT_CHECKER(check_call_arguments_internal) { + CallArgumentError err = CallArgumentError_None; + ast_node(ce, CallExpr, call); GB_ASSERT(is_type_proc(proc_type)); proc_type = base_type(proc_type); @@ -5327,6 +5344,52 @@ gb_internal CALL_ARGUMENT_CHECKER(check_call_arguments_internal) { i64 score = 0; bool show_error = show_error_mode == CallArgumentMode_ShowErrors; + auto ordered_operands = array_make(temporary_allocator(), pt->param_count); + bool *visited = gb_alloc_array(temporary_allocator(), bool, pt->param_count); + + if (ce->split_args) { + GB_ASSERT(ce->split_args->named.count == named_operands.count); + for_array(i, ce->split_args->named) { + Ast *arg = ce->split_args->named[i]; + Operand operand = named_operands[i]; + + ast_node(fv, FieldValue, arg); + if (fv->field->kind != Ast_Ident) { + if (show_error) { + gbString expr_str = expr_to_string(fv->field); + error(arg, "Invalid parameter name '%s' in procedure call", expr_str); + gb_string_free(expr_str); + } + err = CallArgumentError_InvalidFieldValue; + continue; + } + String name = fv->field->Ident.token.string; + isize param_index = lookup_procedure_parameter(pt, name); + if (param_index < 0) { + if (show_error) { + error(arg, "No parameter named '%.*s' for this procedure type", LIT(name)); + } + err = CallArgumentError_ParameterNotFound; + continue; + } + if (visited[param_index]) { + if (show_error) { + error(arg, "Duplicate parameter '%.*s' in procedure call", LIT(name)); + } + err = CallArgumentError_DuplicateParameter; + continue; + } + + visited[param_index] = true; + ordered_operands[param_index] = operand; + + err = CallArgumentError_DuplicateParameter; + } + if (err) { + return err; + } + } + TypeTuple *param_tuple = nullptr; if (pt->params != nullptr) { @@ -5334,7 +5397,6 @@ gb_internal CALL_ARGUMENT_CHECKER(check_call_arguments_internal) { } - CallArgumentError err = CallArgumentError_None; Type *final_proc_type = proc_type; Entity *gen_entity = nullptr; @@ -5571,21 +5633,6 @@ gb_internal bool is_call_expr_field_value(AstCallExpr *ce) { return ce->args[0]->kind == Ast_FieldValue; } -gb_internal isize lookup_procedure_parameter(TypeProc *pt, String parameter_name) { - isize param_count = pt->param_count; - for (isize i = 0; i < param_count; i++) { - Entity *e = pt->params->Tuple.variables[i]; - String name = e->token.string; - if (is_blank_ident(name)) { - continue; - } - if (name == parameter_name) { - return i; - } - } - return -1; -} - gb_internal CALL_ARGUMENT_CHECKER(check_named_call_arguments) { ast_node(ce, CallExpr, call); GB_ASSERT(is_type_proc(proc_type)); @@ -5969,8 +6016,561 @@ gb_internal CallArgumentError check_order_of_call_arguments(CheckerContext *c, T return err; } +gb_internal bool check_named_arguments(CheckerContext *c, Type *type, Slice const &named_args, Array *named_operands, bool show_error) { + bool success = true; -gb_internal CallArgumentData check_call_arguments_single_procedure(CheckerContext *c, Operand *operand, Ast *call) { + type = base_type(type); + if (named_args.count > 0) { + TypeProc *pt = nullptr; + if (is_type_proc(type)) { + pt = &type->Proc; + } + + for_array(i, named_args) { + Ast *arg = named_args[i]; + if (arg->kind != Ast_FieldValue) { + if (show_error) { + error(arg, "Expected a 'field = value'"); + } + return false; + } + ast_node(fv, FieldValue, arg); + if (fv->field->kind != Ast_Ident) { + if (show_error) { + gbString expr_str = expr_to_string(fv->field); + error(arg, "Invalid parameter name '%s' in procedure call", expr_str); + gb_string_free(expr_str); + } + success = false; + continue; + } + String key = fv->field->Ident.token.string; + Ast *value = fv->value; + + Type *type_hint = nullptr; + if (pt) { + isize param_index = lookup_procedure_parameter(pt, key); + if (param_index < 0) { + if (show_error) { + error(value, "No parameter named '%.*s' for this procedure type %s", LIT(key), type_to_string(type)); + } + success = false; + continue; + } + + // bool prev_visited = visited[param_index]; + // if (prev_visited) { + // error(value, "Duplicate parameter named '%.*s' in procedure call", LIT(key)); + // success = false; + // } + + Entity *e = pt->params->Tuple.variables[param_index]; + if (!is_type_polymorphic(e->type)) { + type_hint = e->type; + } + + } + Operand o = {}; + check_expr_with_type_hint(c, &o, value, type_hint); + if (o.mode == Addressing_Invalid) { + success = false; + } + array_add(named_operands, o); + } + + } + return success; +} + + +gb_internal CallArgumentData check_call_arguments_new_and_improved_proc_group(CheckerContext *c, Operand *operand, Ast *call) { + ast_node(ce, CallExpr, call); + GB_ASSERT(ce->split_args != nullptr); + + Slice const &positional_args = ce->split_args->positional; + Slice const &named_args = ce->split_args->named; + + CallArgumentData data = {}; + data.result_type = t_invalid; + + GB_ASSERT(operand->mode == Addressing_ProcGroup); + auto procs = proc_group_entities_cloned(c, *operand); + + if (procs.count > 1) { + isize max_arg_count = positional_args.count + named_args.count; + for (Ast *arg : positional_args) { + // NOTE(bill): The only thing that may have multiple values + // will be a call expression (assuming `or_return` and `()` will be stripped) + arg = strip_or_return_expr(arg); + if (arg && arg->kind == Ast_CallExpr) { + max_arg_count = ISIZE_MAX; + break; + } + } + if (max_arg_count != ISIZE_MAX) for (Ast *arg : named_args) { + // NOTE(bill): The only thing that may have multiple values + // will be a call expression (assuming `or_return` and `()` will be stripped) + if (arg->kind == Ast_FieldValue) { + arg = strip_or_return_expr(arg->FieldValue.value); + if (arg && arg->kind == Ast_CallExpr) { + max_arg_count = ISIZE_MAX; + break; + } + } + } + + for (isize proc_index = 0; proc_index < procs.count; /**/) { + Entity *proc = procs[proc_index]; + Type *pt = base_type(proc->type); + if (!(pt != nullptr && is_type_proc(pt))) { + proc_index++; + continue; + } + + isize param_count = 0; + isize param_count_excluding_defaults = get_procedure_param_count_excluding_defaults(pt, ¶m_count); + + if (param_count_excluding_defaults > max_arg_count) { + array_unordered_remove(&procs, proc_index); + continue; + } + + // for (Ast *arg : named_args) { + // if (arg->kind != Ast_FieldValue) { + // continue; + // } + // ast_node(fv, FieldValue, arg); + // if (fv->field->kind != Ast_Ident) { + // continue; + // } + // bool ok = false; + // String key = fv->field->Ident.token.string; + // if (param_count) for (Entity *e : pt->Proc.params->Tuple.variables) { + // if (e->token.string == key) { + // ok = true; + // break; + // } + // } + // if (!ok) { + // if (proc_index < procs.count) { + // array_unordered_remove(&procs, proc_index); + // continue; + // } else { + // break; + // } + // } + // } + + proc_index++; + } + } + + Entity **lhs = nullptr; + isize lhs_count = -1; + bool is_variadic = false; + + auto positional_operands = array_make(heap_allocator(), 0, 0); + auto named_operands = array_make(heap_allocator(), 0, 0); + defer (array_free(&positional_operands)); + defer (array_free(&named_operands)); + + if (procs.count == 1) { + Ast *ident = operand->expr; + while (ident->kind == Ast_SelectorExpr) { + Ast *s = ident->SelectorExpr.selector; + ident = s; + } + + + Entity *e = procs[0]; + + lhs = populate_proc_parameter_list(c, e->type, &lhs_count, &is_variadic); + check_unpack_arguments(c, lhs, lhs_count, &positional_operands, positional_args, is_variadic ? UnpackFlag_IsVariadic : UnpackFlag_None); + + if (!check_named_arguments(c, e->type, named_args, &named_operands, true)) { + return data; + } + + CallArgumentError err = check_call_arguments_internal(c, call, e->type, e, positional_operands, named_operands, CallArgumentMode_ShowErrors, &data); + if (err != CallArgumentError_None) { + // handle error + } + + Type *proc_type = base_type(operand->type); + + Entity *entity_to_use = data.gen_entity != nullptr ? data.gen_entity : e; + add_entity_use(c, ident, entity_to_use); + if (entity_to_use != nullptr) { + update_untyped_expr_type(c, operand->expr, entity_to_use->type, true); + + proc_type = base_type(entity_to_use->type); + } + + if (proc_type && proc_type->kind == Type_Proc) { + data.result_type = proc_type->Proc.results; + add_type_and_value(c, operand->expr, operand->mode, proc_type, operand->value); + } else if (err == CallArgumentError_None) { + data.result_type = nullptr; + } + + if (data.gen_entity != nullptr) { + Entity *e = data.gen_entity; + DeclInfo *decl = data.gen_entity->decl_info; + CheckerContext ctx = *c; + ctx.scope = decl->scope; + ctx.decl = decl; + ctx.proc_name = e->token.string; + ctx.curr_proc_decl = decl; + ctx.curr_proc_sig = e->type; + + GB_ASSERT(decl->proc_lit->kind == Ast_ProcLit); + bool ok = evaluate_where_clauses(&ctx, call, decl->scope, &decl->proc_lit->ProcLit.where_clauses, true); + decl->where_clauses_evaluated = true; + + if (ok && (data.gen_entity->flags & EntityFlag_ProcBodyChecked) == 0) { + check_procedure_later(c->checker, e->file, e->token, decl, e->type, decl->proc_lit->ProcLit.body, decl->proc_lit->ProcLit.tags); + } + } + return data; + } + + { + // NOTE(bill, 2019-07-13): This code is used to improve the type inference for procedure groups + // where the same positional parameter has the same type value (and ellipsis) + bool proc_arg_count_all_equal = true; + isize proc_arg_count = -1; + for (Entity *p : procs) { + Type *pt = base_type(p->type); + if (pt != nullptr && is_type_proc(pt)) { + if (proc_arg_count < 0) { + proc_arg_count = pt->Proc.param_count; + } else { + if (proc_arg_count != pt->Proc.param_count) { + proc_arg_count_all_equal = false; + break; + } + } + } + } + + if (proc_arg_count >= 0 && proc_arg_count_all_equal) { + lhs_count = proc_arg_count; + if (lhs_count > 0) { + lhs = gb_alloc_array(heap_allocator(), Entity *, lhs_count); + for (isize param_index = 0; param_index < lhs_count; param_index++) { + Entity *e = nullptr; + for (Entity *p : procs) { + Type *pt = base_type(p->type); + if (!(pt != nullptr && is_type_proc(pt))) { + continue; + } + + if (e == nullptr) { + e = pt->Proc.params->Tuple.variables[param_index]; + } else { + Entity *f = pt->Proc.params->Tuple.variables[param_index]; + if (e == f) { + continue; + } + if (are_types_identical(e->type, f->type)) { + bool ee = (e->flags & EntityFlag_Ellipsis) != 0; + bool fe = (f->flags & EntityFlag_Ellipsis) != 0; + if (ee == fe) { + continue; + } + } + // NOTE(bill): Entities are not close enough to be used + e = nullptr; + break; + } + } + lhs[param_index] = e; + } + } + } + } + + check_unpack_arguments(c, lhs, lhs_count, &positional_operands, positional_args, is_variadic ? UnpackFlag_IsVariadic : UnpackFlag_None); + + for_array(i, named_args) { + Ast *arg = named_args[i]; + if (arg->kind != Ast_FieldValue) { + error(arg, "Expected a 'field = value'"); + return data; + } + ast_node(fv, FieldValue, arg); + if (fv->field->kind != Ast_Ident) { + gbString expr_str = expr_to_string(fv->field); + error(arg, "Invalid parameter name '%s' in procedure call", expr_str); + gb_string_free(expr_str); + return data; + } + String key = fv->field->Ident.token.string; + Ast *value = fv->value; + + Type *type_hint = nullptr; + + for (isize lhs_idx = 0; lhs_idx < lhs_count; lhs_idx++) { + Entity *e = lhs[lhs_idx]; + if (e != nullptr && e->token.string == key && + !is_type_polymorphic(e->type)) { + type_hint = e->type; + break; + } + } + Operand o = {}; + check_expr_with_type_hint(c, &o, value, type_hint); + array_add(&named_operands, o); + } + + gb_free(heap_allocator(), lhs); + + auto valids = array_make(heap_allocator(), 0, procs.count); + defer (array_free(&valids)); + + auto proc_entities = array_make(heap_allocator(), 0, procs.count*2 + 1); + defer (array_free(&proc_entities)); + for (Entity *proc : procs) { + array_add(&proc_entities, proc); + } + + + gbString expr_name = expr_to_string(operand->expr); + defer (gb_string_free(expr_name)); + + for_array(i, procs) { + Entity *p = procs[i]; + Type *pt = base_type(p->type); + if (pt != nullptr && is_type_proc(pt)) { + CallArgumentError err = CallArgumentError_None; + CallArgumentData data = {}; + CheckerContext ctx = *c; + + ctx.no_polymorphic_errors = true; + ctx.allow_polymorphic_types = is_type_polymorphic(pt); + ctx.hide_polymorphic_errors = true; + + err = check_call_arguments_internal(&ctx, call, pt, p, positional_operands, named_operands, CallArgumentMode_NoErrors, &data); + if (err != CallArgumentError_None) { + continue; + } + isize index = i; + + if (data.gen_entity != nullptr) { + Entity *e = data.gen_entity; + DeclInfo *decl = data.gen_entity->decl_info; + ctx.scope = decl->scope; + ctx.decl = decl; + ctx.proc_name = e->token.string; + ctx.curr_proc_decl = decl; + ctx.curr_proc_sig = e->type; + + GB_ASSERT(decl->proc_lit->kind == Ast_ProcLit); + if (!evaluate_where_clauses(&ctx, call, decl->scope, &decl->proc_lit->ProcLit.where_clauses, false)) { + continue; + } + + array_add(&proc_entities, data.gen_entity); + index = proc_entities.count-1; + } + + ValidIndexAndScore item = {}; + item.index = index; + item.score = data.score; + array_add(&valids, item); + } + } + + if (valids.count > 1) { + gb_sort_array(valids.data, valids.count, valid_index_and_score_cmp); + i64 best_score = valids[0].score; + Entity *best_entity = proc_entities[valids[0].index]; + GB_ASSERT(best_entity != nullptr); + for (isize i = 1; i < valids.count; i++) { + if (best_score > valids[i].score) { + valids.count = i; + break; + } + if (best_entity == proc_entities[valids[i].index]) { + valids.count = i; + break; + } + } + } + + auto print_argument_types = [&]() { + error_line("\tGiven argument types: ("); + isize i = 0; + for (Operand const &o : positional_operands) { + if (i++ > 0) error_line(", "); + gbString type = type_to_string(o.type); + defer (gb_string_free(type)); + error_line("%s", type); + } + for (Operand const &o : named_operands) { + if (i++ > 0) error_line(", "); + + gbString type = type_to_string(o.type); + defer (gb_string_free(type)); + + if (i < ce->split_args->named.count) { + Ast *named_field = ce->split_args->named[i]; + ast_node(fv, FieldValue, named_field); + + gbString field = expr_to_string(fv->field); + defer (gb_string_free(field)); + + error_line("%s = %s", field, type); + } else { + error_line("%s", type); + } + } + error_line(")\n"); + }; + + if (valids.count == 0) { + begin_error_block(); + defer (end_error_block()); + + error(operand->expr, "No procedures or ambiguous call for procedure group '%s' that match with the given arguments", expr_name); + if (positional_operands.count == 0 && named_operands.count == 0) { + error_line("\tNo given arguments\n"); + } else { + print_argument_types(); + } + + if (procs.count > 0) { + error_line("Did you mean to use one of the following:\n"); + } + for (Entity *proc : procs) { + TokenPos pos = proc->token.pos; + Type *t = base_type(proc->type); + if (t == t_invalid) continue; + GB_ASSERT(t->kind == Type_Proc); + gbString pt; + defer (gb_string_free(pt)); + if (t->Proc.node != nullptr) { + pt = expr_to_string(t->Proc.node); + } else { + pt = type_to_string(t); + } + String prefix = {}; + String prefix_sep = {}; + if (proc->pkg) { + prefix = proc->pkg->name; + prefix_sep = str_lit("."); + } + String name = proc->token.string; + + char const *sep = "::"; + if (proc->kind == Entity_Variable) { + sep = ":="; + } + error_line("\t%.*s%.*s%.*s %s %s at %s\n", LIT(prefix), LIT(prefix_sep), LIT(name), sep, pt, token_pos_to_string(pos)); + } + if (procs.count > 0) { + error_line("\n"); + } + + data.result_type = t_invalid; + } else if (valids.count > 1) { + begin_error_block(); + defer (end_error_block()); + + error(operand->expr, "Ambiguous procedure group call '%s' that match with the given arguments", expr_name); + print_argument_types(); + + for (isize i = 0; i < valids.count; i++) { + Entity *proc = proc_entities[valids[i].index]; + GB_ASSERT(proc != nullptr); + TokenPos pos = proc->token.pos; + Type *t = base_type(proc->type); GB_ASSERT(t->kind == Type_Proc); + gbString pt = nullptr; + defer (gb_string_free(pt)); + if (t->Proc.node != nullptr) { + pt = expr_to_string(t->Proc.node); + } else { + pt = type_to_string(t); + } + String name = proc->token.string; + char const *sep = "::"; + if (proc->kind == Entity_Variable) { + sep = ":="; + } + error_line("\t%.*s %s %s ", LIT(name), sep, pt); + if (proc->decl_info->proc_lit != nullptr) { + GB_ASSERT(proc->decl_info->proc_lit->kind == Ast_ProcLit); + auto *pl = &proc->decl_info->proc_lit->ProcLit; + if (pl->where_token.kind != Token_Invalid) { + error_line("\n\t\twhere "); + for_array(j, pl->where_clauses) { + Ast *clause = pl->where_clauses[j]; + if (j != 0) { + error_line("\t\t "); + } + gbString str = expr_to_string(clause); + error_line("%s", str); + gb_string_free(str); + + if (j != pl->where_clauses.count-1) { + error_line(","); + } + } + error_line("\n\t"); + } + } + error_line("at %s\n", token_pos_to_string(pos)); + } + data.result_type = t_invalid; + } else { + GB_ASSERT(valids.count == 1); + Ast *ident = operand->expr; + while (ident->kind == Ast_SelectorExpr) { + Ast *s = ident->SelectorExpr.selector; + ident = s; + } + + Entity *e = proc_entities[valids[0].index]; + GB_ASSERT(e != nullptr); + + Array named_operands = {}; + + Type *proc_type = e->type; + CallArgumentData data = {}; + CallArgumentError err = check_call_arguments_internal(c, call, proc_type, e, positional_operands, named_operands, CallArgumentMode_ShowErrors, &data); + gb_unused(err); + Entity *entity_to_use = data.gen_entity != nullptr ? data.gen_entity : e; + add_entity_use(c, ident, entity_to_use); + if (entity_to_use != nullptr) { + update_untyped_expr_type(c, operand->expr, entity_to_use->type, true); + } + + if (data.gen_entity != nullptr) { + Entity *e = data.gen_entity; + DeclInfo *decl = data.gen_entity->decl_info; + CheckerContext ctx = *c; + ctx.scope = decl->scope; + ctx.decl = decl; + ctx.proc_name = e->token.string; + ctx.curr_proc_decl = decl; + ctx.curr_proc_sig = e->type; + + GB_ASSERT(decl->proc_lit->kind == Ast_ProcLit); + bool ok = evaluate_where_clauses(&ctx, call, decl->scope, &decl->proc_lit->ProcLit.where_clauses, true); + decl->where_clauses_evaluated = true; + + if (ok && (data.gen_entity->flags & EntityFlag_ProcBodyChecked) == 0) { + check_procedure_later(c->checker, e->file, e->token, decl, e->type, decl->proc_lit->ProcLit.body, decl->proc_lit->ProcLit.tags); + } + } + return data; + } + + return data; +} + + +gb_internal CallArgumentData check_call_arguments_new_and_improved(CheckerContext *c, Operand *operand, Ast *call) { Type *proc_type = nullptr; CallArgumentData data = {}; @@ -5978,23 +6578,6 @@ gb_internal CallArgumentData check_call_arguments_single_procedure(CheckerContex proc_type = base_type(operand->type); - if (operand->mode == Addressing_ProcGroup) { - Array procs = proc_group_entities(c, *operand); - if (procs.count == 1) { - proc_type = base_type(procs[0]->type); - } else { - error(call, "Procedure groups >1 not yet supported"); - return data; - } - } - if (proc_type == nullptr || proc_type->kind != Type_Proc) { - gbString s = type_to_string(proc_type); - error(call, "Expected a procedure to call, got %s", s); - gb_string_free(s); - - return data; - } - if (proc_type != nullptr && is_type_polymorphic(proc_type)) { error(call, "Polymorphic procedures not yet supported"); return data; @@ -6012,19 +6595,38 @@ gb_internal CallArgumentData check_call_arguments_single_procedure(CheckerContex bool vari_expand = (ce->ellipsis.pos.line != 0); - auto positional_args = ce->args; - for (isize i = 0; i < ce->args.count; i++) { - Ast *arg = ce->args.data[i]; - if (arg->kind == Ast_FieldValue) { - positional_args.count = i; - break; + // Split positional and named args into separate arrays/slices + Slice positional_args = {}; + Slice named_args = {}; + + if (ce->split_args == nullptr) { + positional_args = ce->args; + for (isize i = 0; i < ce->args.count; i++) { + Ast *arg = ce->args.data[i]; + if (arg->kind == Ast_FieldValue) { + positional_args.count = i; + break; + } } + named_args = slice(ce->args, positional_args.count, ce->args.count); + + auto split_args = gb_alloc_item(permanent_allocator(), AstSplitArgs); + split_args->positional = positional_args; + split_args->named = named_args; + ce->split_args = split_args; + } else { + positional_args = ce->split_args->positional; + named_args = ce->split_args->named; } - auto named_args = slice(ce->args, positional_args.count, ce->args.count); + + if (operand->mode == Addressing_ProcGroup) { + return check_call_arguments_new_and_improved_proc_group(c, operand, call); + } + auto positional_operands = array_make(heap_allocator(), 0, positional_args.count); - auto variadic_operands = array_make(heap_allocator(), 0, 0); - auto named_operands = array_make(heap_allocator(), 0, 0); + auto variadic_operands = array_make(heap_allocator(), 0, 0); + auto named_operands = array_make(heap_allocator(), 0, 0); defer (array_free(&positional_operands)); defer (array_free(&variadic_operands)); @@ -6038,14 +6640,16 @@ gb_internal CallArgumentData check_call_arguments_single_procedure(CheckerContex isize lhs_count = -1; bool is_variadic = false; Entity **lhs = nullptr; - if (proc_type != nullptr && is_type_proc(proc_type)) { + if (pt != nullptr) { lhs = populate_proc_parameter_list(c, proc_type, &lhs_count, &is_variadic); + } else if (operand->mode == Addressing_ProcGroup) { + // TODO } check_unpack_arguments(c, lhs, lhs_count, &positional_operands, positional_args, is_variadic ? UnpackFlag_IsVariadic : UnpackFlag_None); } positional_operand_count = positional_operands.count; - if (proc_type != nullptr) { + if (pt != nullptr) { visited = slice_make(temporary_allocator(), pt->param_count); if (!pt->variadic) { @@ -6161,7 +6765,7 @@ gb_internal CallArgumentData check_call_arguments_single_procedure(CheckerContex string_map_init(&type_hint_map, 2*named_args.count); defer (string_map_destroy(&type_hint_map)); - if (proc_type != nullptr) { + if (pt != nullptr) { TypeTuple *param_tuple = &pt->params->Tuple; for (Entity *e : param_tuple->variables) { if (is_blank_ident(e->token)) { @@ -6252,7 +6856,7 @@ gb_internal CallArgumentData check_call_arguments_single_procedure(CheckerContex } - if (proc_type != nullptr) { + if (pt != nullptr) { if (pt->variadic) { visited[pt->variadic_index] = true; } @@ -6293,14 +6897,45 @@ gb_internal CallArgumentData check_call_arguments_single_procedure(CheckerContex return data; } + { + Ast *ident = operand->expr; + while (ident->kind == Ast_SelectorExpr) { + Ast *s = ident->SelectorExpr.selector; + ident = s; + } - auto ordered_args = gb_alloc_item(permanent_allocator(), AstOrderedArgs); - ordered_args->positional = positional_args; - ordered_args->named = named_args; + Entity *e = entity_of_node(ident); - ce->ordered_args = ordered_args; + Array operands = array_clone(heap_allocator(), positional_operands); - if (proc_type != nullptr) { + CallArgumentError err = check_call_arguments_internal(c, call, proc_type, e, operands, named_operands, CallArgumentMode_ShowErrors, &data); + gb_unused(err); + Entity *entity_to_use = data.gen_entity != nullptr ? data.gen_entity : e; + add_entity_use(c, ident, entity_to_use); + if (entity_to_use != nullptr) { + update_untyped_expr_type(c, operand->expr, entity_to_use->type, true); + } + if (data.gen_entity != nullptr) { + Entity *e = data.gen_entity; + DeclInfo *decl = data.gen_entity->decl_info; + CheckerContext ctx = *c; + ctx.scope = decl->scope; + ctx.decl = decl; + ctx.proc_name = e->token.string; + ctx.curr_proc_decl = decl; + ctx.curr_proc_sig = e->type; + + GB_ASSERT(decl->proc_lit->kind == Ast_ProcLit); + bool ok = evaluate_where_clauses(&ctx, call, decl->scope, &decl->proc_lit->ProcLit.where_clauses, true); + decl->where_clauses_evaluated = true; + + if (ok && (data.gen_entity->flags & EntityFlag_ProcBodyChecked) == 0) { + check_procedure_later(c->checker, e->file, e->token, decl, e->type, decl->proc_lit->ProcLit.body, decl->proc_lit->ProcLit.tags); + } + } + } + + if (pt != nullptr) { data.result_type = pt->results; } return data; @@ -6438,13 +7073,13 @@ gb_internal CallArgumentData check_call_arguments(CheckerContext *c, Operand *op break; } } - if (all_non_poly && (procs.count == 1 || c->pkg->name == "bug")) { - return check_call_arguments_single_procedure(c, operand, call); + if (all_non_poly) { + return check_call_arguments_new_and_improved(c, operand, call); } } if (operand->mode != Addressing_ProcGroup && is_type_proc(operand->type) && !is_type_polymorphic(operand->type)) { - return check_call_arguments_single_procedure(c, operand, call); + return check_call_arguments_new_and_improved(c, operand, call); } ast_node(ce, CallExpr, call); @@ -6635,8 +7270,9 @@ gb_internal CallArgumentData check_call_arguments(CheckerContext *c, Operand *op lhs = populate_proc_parameter_list(c, e->type, &lhs_count, &is_variadic); check_unpack_arguments(c, lhs, lhs_count, &operands, args, is_variadic ? UnpackFlag_IsVariadic : UnpackFlag_None); + Array named_operands = {}; CallArgumentData data = {}; - CallArgumentError err = call_checker(c, call, e->type, e, operands, CallArgumentMode_ShowErrors, &data); + CallArgumentError err = call_checker(c, call, e->type, e, operands, named_operands, CallArgumentMode_ShowErrors, &data); if (err != CallArgumentError_None) { // handle error } @@ -6740,7 +7376,8 @@ gb_internal CallArgumentData check_call_arguments(CheckerContext *c, Operand *op ctx.allow_polymorphic_types = is_type_polymorphic(pt); ctx.hide_polymorphic_errors = true; - err = call_checker(&ctx, call, pt, p, operands, CallArgumentMode_NoErrors, &data); + Array named_operands = {}; + err = call_checker(&ctx, call, pt, p, operands, named_operands, CallArgumentMode_NoErrors, &data); if (err != CallArgumentError_None) { continue; } @@ -6910,9 +7547,11 @@ gb_internal CallArgumentData check_call_arguments(CheckerContext *c, Operand *op Entity *e = proc_entities[valids[0].index]; GB_ASSERT(e != nullptr); + Array named_operands = {}; + proc_type = e->type; CallArgumentData data = {}; - CallArgumentError err = call_checker(c, call, proc_type, e, operands, CallArgumentMode_ShowErrors, &data); + CallArgumentError err = call_checker(c, call, proc_type, e, operands, named_operands, CallArgumentMode_ShowErrors, &data); gb_unused(err); Entity *entity_to_use = data.gen_entity != nullptr ? data.gen_entity : e; add_entity_use(c, ident, entity_to_use); @@ -6949,9 +7588,10 @@ gb_internal CallArgumentData check_call_arguments(CheckerContext *c, Operand *op Entity *e = entity_of_node(ident); + Array named_operands = {}; CallArgumentData data = {}; - CallArgumentError err = call_checker(c, call, proc_type, e, operands, CallArgumentMode_ShowErrors, &data); + CallArgumentError err = call_checker(c, call, proc_type, e, operands, named_operands, CallArgumentMode_ShowErrors, &data); gb_unused(err); Entity *entity_to_use = data.gen_entity != nullptr ? data.gen_entity : e; add_entity_use(c, ident, entity_to_use); diff --git a/src/error.cpp b/src/error.cpp index defc2593f..eb010eb36 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -265,7 +265,8 @@ gb_internal bool show_error_on_line(TokenPos const &pos, TokenPos end) { defer (gb_string_free(the_line)); if (the_line != nullptr) { - String line = make_string(cast(u8 const *)the_line, gb_string_length(the_line)); + char const *line_text = the_line; + isize line_len = gb_string_length(the_line); // TODO(bill): This assumes ASCII @@ -285,21 +286,27 @@ gb_internal bool show_error_on_line(TokenPos const &pos, TokenPos end) { isize squiggle_extra = 0; - if (line.len > MAX_LINE_LENGTH_PADDED) { + if (line_len > MAX_LINE_LENGTH_PADDED) { i32 left = MAX_TAB_WIDTH; - line.text += offset-left; - line.len -= offset-left; - offset = left+MAX_TAB_WIDTH/2; - if (line.len > MAX_LINE_LENGTH_PADDED) { - line.len = MAX_LINE_LENGTH_PADDED; - if (error_length > line.len-left) { - error_length = cast(i32)line.len - left; + if (offset > 0) { + line_text += offset-left; + line_len -= offset-left; + offset = left+MAX_TAB_WIDTH/2; + } + if (line_len > MAX_LINE_LENGTH_PADDED) { + line_len = MAX_LINE_LENGTH_PADDED; + if (error_length > line_len-left) { + error_length = cast(i32)line_len - left; squiggle_extra = 1; } } - error_out("... %.*s ...", LIT(line)); + if (offset > 0) { + error_out("... %.*s ...", cast(i32)line_len, line_text); + } else { + error_out("%.*s ...", cast(i32)line_len, line_text); + } } else { - error_out("%.*s", LIT(line)); + error_out("%.*s", cast(i32)line_len, line_text); } error_out("\n\t"); @@ -312,7 +319,7 @@ gb_internal bool show_error_on_line(TokenPos const &pos, TokenPos end) { error_out("^"); if (end.file_id == pos.file_id) { if (end.line > pos.line) { - for (i32 i = offset; i < line.len; i++) { + for (i32 i = offset; i < line_len; i++) { error_out("~"); } } else if (end.line == pos.line && end.column > pos.column) { diff --git a/src/gb/gb.h b/src/gb/gb.h index bc4c1f27d..3d4bff9b4 100644 --- a/src/gb/gb.h +++ b/src/gb/gb.h @@ -3299,12 +3299,39 @@ void const *gb_memrchr(void const *data, u8 c, isize n) { -gb_inline void *gb_alloc_align (gbAllocator a, isize size, isize alignment) { return a.proc(a.data, gbAllocation_Alloc, size, alignment, NULL, 0, GB_DEFAULT_ALLOCATOR_FLAGS); } -gb_inline void *gb_alloc (gbAllocator a, isize size) { return gb_alloc_align(a, size, GB_DEFAULT_MEMORY_ALIGNMENT); } -gb_inline void gb_free (gbAllocator a, void *ptr) { if (ptr != NULL) a.proc(a.data, gbAllocation_Free, 0, 0, ptr, 0, GB_DEFAULT_ALLOCATOR_FLAGS); } -gb_inline void gb_free_all (gbAllocator a) { a.proc(a.data, gbAllocation_FreeAll, 0, 0, NULL, 0, GB_DEFAULT_ALLOCATOR_FLAGS); } -gb_inline void *gb_resize (gbAllocator a, void *ptr, isize old_size, isize new_size) { return gb_resize_align(a, ptr, old_size, new_size, GB_DEFAULT_MEMORY_ALIGNMENT); } -gb_inline void *gb_resize_align(gbAllocator a, void *ptr, isize old_size, isize new_size, isize alignment) { return a.proc(a.data, gbAllocation_Resize, new_size, alignment, ptr, old_size, GB_DEFAULT_ALLOCATOR_FLAGS); } +gb_inline void *gb_alloc_align (gbAllocator a, isize size, isize alignment) { + if (size == 0) { + return NULL; + } + return a.proc(a.data, gbAllocation_Alloc, size, alignment, NULL, 0, GB_DEFAULT_ALLOCATOR_FLAGS); +} +gb_inline void *gb_alloc(gbAllocator a, isize size) { + return gb_alloc_align(a, size, GB_DEFAULT_MEMORY_ALIGNMENT); +} +gb_inline void gb_free(gbAllocator a, void *ptr) { + if (ptr != NULL) { + a.proc(a.data, gbAllocation_Free, 0, 0, ptr, 0, GB_DEFAULT_ALLOCATOR_FLAGS); + } +} +gb_inline void gb_free_all(gbAllocator a) { + a.proc(a.data, gbAllocation_FreeAll, 0, 0, NULL, 0, GB_DEFAULT_ALLOCATOR_FLAGS); +} +gb_inline void *gb_resize(gbAllocator a, void *ptr, isize old_size, isize new_size) { + return gb_resize_align(a, ptr, old_size, new_size, GB_DEFAULT_MEMORY_ALIGNMENT); +} +gb_inline void *gb_resize_align(gbAllocator a, void *ptr, isize old_size, isize new_size, isize alignment) { + if (new_size == 0) { + if (ptr != NULL) { + return a.proc(a.data, gbAllocation_Free, 0, 0, ptr, old_size, GB_DEFAULT_ALLOCATOR_FLAGS); + } + return NULL; + } else if (ptr == NULL) { + return a.proc(a.data, gbAllocation_Alloc, new_size, alignment, NULL, 0, GB_DEFAULT_ALLOCATOR_FLAGS); + } else if (old_size == new_size && ((uintptr)ptr % (uintptr)alignment) == 0) { + return ptr; + } + return a.proc(a.data, gbAllocation_Resize, new_size, alignment, ptr, old_size, GB_DEFAULT_ALLOCATOR_FLAGS); +} gb_inline void *gb_alloc_copy (gbAllocator a, void const *src, isize size) { return gb_memcopy(gb_alloc(a, size), src, size); diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 3c1cfe47c..046016c75 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -3156,20 +3156,20 @@ gb_internal void lb_add_values_to_array(lbProcedure *p, Array *args, lb array_add(args, value); } } -gb_internal lbValue lb_build_call_expr_internal_with_arg_ordered_args(lbProcedure *p, lbValue value, TypeProc *pt, Ast *call, AstOrderedArgs *ordered_args) { +gb_internal lbValue lb_build_call_expr_internal_with_arg_split_args(lbProcedure *p, lbValue value, TypeProc *pt, Ast *call, AstSplitArgs *split_args) { ast_node(ce, CallExpr, call); auto args = array_make(permanent_allocator(), 0, pt->param_count); bool vari_expand = (ce->ellipsis.pos.line != 0); - for_array(i, ordered_args->positional) { + for_array(i, split_args->positional) { Entity *e = pt->params->Tuple.variables[i]; GB_ASSERT(e->kind == Entity_Variable); if (pt->variadic && pt->variadic_index == i) { lbValue variadic_args = lb_const_nil(p->module, e->type); - auto variadic = slice(ordered_args->positional, pt->variadic_index, ordered_args->positional.count); + auto variadic = slice(split_args->positional, pt->variadic_index, split_args->positional.count); if (variadic.count != 0) { // variadic call argument generation Type *slice_type = e->type; @@ -3211,14 +3211,14 @@ gb_internal lbValue lb_build_call_expr_internal_with_arg_ordered_args(lbProcedur break; } else { - lbValue value = lb_build_expr(p, ordered_args->positional[i]); + lbValue value = lb_build_expr(p, split_args->positional[i]); lb_add_values_to_array(p, &args, value); } } array_resize(&args, pt->param_count); - for (Ast *arg : ordered_args->named) { + for (Ast *arg : split_args->named) { ast_node(fv, FieldValue, arg); GB_ASSERT(fv->field->kind == Ast_Ident); String name = fv->field->Ident.token.string; @@ -3331,8 +3331,8 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { GB_ASSERT(proc_type_->kind == Type_Proc); TypeProc *pt = &proc_type_->Proc; - if (ce->ordered_args) { - return lb_build_call_expr_internal_with_arg_ordered_args(p, value, pt, expr, ce->ordered_args); + if (ce->split_args) { + return lb_build_call_expr_internal_with_arg_split_args(p, value, pt, expr, ce->split_args); } if (is_call_expr_field_value(ce)) { diff --git a/src/parser.hpp b/src/parser.hpp index e0e78fa1d..900fddbab 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -367,7 +367,7 @@ gb_global char const *union_type_kind_strings[UnionType_COUNT] = { "#shared_nil", }; -struct AstOrderedArgs { +struct AstSplitArgs { Slice positional; Slice named; }; @@ -447,7 +447,7 @@ AST_KIND(_ExprBegin, "", bool) \ ProcInlining inlining; \ bool optional_ok_one; \ bool was_selector; \ - AstOrderedArgs *ordered_args; \ + AstSplitArgs *split_args; \ }) \ AST_KIND(FieldValue, "field value", struct { Token eq; Ast *field, *value; }) \ AST_KIND(EnumFieldValue, "enum field value", struct { \ diff --git a/src/types.cpp b/src/types.cpp index 3cc077f84..f3b7f5bab 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -3326,6 +3326,9 @@ gb_internal bool are_struct_fields_reordered(Type *type) { type = base_type(type); GB_ASSERT(type->kind == Type_Struct); type_set_offsets(type); + if (type->Struct.fields.count == 0) { + return false; + } GB_ASSERT(type->Struct.offsets != nullptr); i64 prev_offset = 0; @@ -3344,6 +3347,9 @@ gb_internal Slice struct_fields_index_by_increasing_offset(gbAllocator allo type = base_type(type); GB_ASSERT(type->kind == Type_Struct); type_set_offsets(type); + if (type->Struct.fields.count == 0) { + return {}; + } GB_ASSERT(type->Struct.offsets != nullptr); auto indices = slice_make(allocator, type->Struct.fields.count); From f26e3c65098e5c95ac7d446f2a6db5f136c509f2 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 19 Jun 2023 22:26:43 +0100 Subject: [PATCH 35/97] Improve proc group selection with named arguments --- src/check_expr.cpp | 67 ++++++++++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 29 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index e0c6be1d1..eb3713d82 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -5314,7 +5314,7 @@ gb_internal isize get_procedure_param_count_excluding_defaults(Type *pt, isize * } -gb_internal isize lookup_procedure_parameter(TypeProc *pt, String parameter_name) { +gb_internal isize lookup_procedure_parameter(TypeProc *pt, String const ¶meter_name) { isize param_count = pt->param_count; for (isize i = 0; i < param_count; i++) { Entity *e = pt->params->Tuple.variables[i]; @@ -5329,6 +5329,13 @@ gb_internal isize lookup_procedure_parameter(TypeProc *pt, String parameter_name return -1; } +gb_internal isize lookup_procedure_parameter(Type *type, String const ¶meter_name) { + type = base_type(type); + GB_ASSERT(type->kind == Type_Proc); + return lookup_procedure_parameter(&type->Proc, parameter_name); +} + + gb_internal CALL_ARGUMENT_CHECKER(check_call_arguments_internal) { CallArgumentError err = CallArgumentError_None; @@ -6052,7 +6059,7 @@ gb_internal bool check_named_arguments(CheckerContext *c, Type *type, Slicekind != Ast_FieldValue) { + continue; + } + ast_node(fv, FieldValue, arg); + if (fv->field->kind != Ast_Ident) { + continue; + } + String key = fv->field->Ident.token.string; + for (isize proc_index = procs.count-1; proc_index >= 0; proc_index--) { + Type *t = procs[proc_index]->type; + if (is_type_proc(t)) { + isize param_index = lookup_procedure_parameter(t, key); + if (param_index < 0) { + array_unordered_remove(&procs, proc_index); + } + } + } + } + + if (procs.count == 0) { + // if any of the named arguments are wrong, the `procs` will be empty + // just start from scratch + array_free(&procs); + procs = proc_group_entities_cloned(c, *operand); + } + + // filter by positional argument length for (isize proc_index = 0; proc_index < procs.count; /**/) { Entity *proc = procs[proc_index]; Type *pt = base_type(proc->type); @@ -6134,33 +6170,6 @@ gb_internal CallArgumentData check_call_arguments_new_and_improved_proc_group(Ch array_unordered_remove(&procs, proc_index); continue; } - - // for (Ast *arg : named_args) { - // if (arg->kind != Ast_FieldValue) { - // continue; - // } - // ast_node(fv, FieldValue, arg); - // if (fv->field->kind != Ast_Ident) { - // continue; - // } - // bool ok = false; - // String key = fv->field->Ident.token.string; - // if (param_count) for (Entity *e : pt->Proc.params->Tuple.variables) { - // if (e->token.string == key) { - // ok = true; - // break; - // } - // } - // if (!ok) { - // if (proc_index < procs.count) { - // array_unordered_remove(&procs, proc_index); - // continue; - // } else { - // break; - // } - // } - // } - proc_index++; } } From 15a0d9f9008362882aed152ba3fecd09f4ce7b28 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 19 Jun 2023 23:46:06 +0100 Subject: [PATCH 36/97] Begin to handle split arguments correctly --- src/check_expr.cpp | 560 ++++++++++++++++++++++----------------------- 1 file changed, 279 insertions(+), 281 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index eb3713d82..92b572352 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -66,7 +66,7 @@ gb_internal int valid_index_and_score_cmp(void const *a, void const *b) { -#define CALL_ARGUMENT_CHECKER(name) CallArgumentError name(CheckerContext *c, Ast *call, Type *proc_type, Entity *entity, Array operands, Array const &named_operands, CallArgumentErrorMode show_error_mode, CallArgumentData *data) +#define CALL_ARGUMENT_CHECKER(name) CallArgumentError name(CheckerContext *c, Ast *call, Type *proc_type, Entity *entity, Array positional_operands, Array const &named_operands, CallArgumentErrorMode show_error_mode, CallArgumentData *data) typedef CALL_ARGUMENT_CHECKER(CallArgumentCheckerType); @@ -5351,59 +5351,6 @@ gb_internal CALL_ARGUMENT_CHECKER(check_call_arguments_internal) { i64 score = 0; bool show_error = show_error_mode == CallArgumentMode_ShowErrors; - auto ordered_operands = array_make(temporary_allocator(), pt->param_count); - bool *visited = gb_alloc_array(temporary_allocator(), bool, pt->param_count); - - if (ce->split_args) { - GB_ASSERT(ce->split_args->named.count == named_operands.count); - for_array(i, ce->split_args->named) { - Ast *arg = ce->split_args->named[i]; - Operand operand = named_operands[i]; - - ast_node(fv, FieldValue, arg); - if (fv->field->kind != Ast_Ident) { - if (show_error) { - gbString expr_str = expr_to_string(fv->field); - error(arg, "Invalid parameter name '%s' in procedure call", expr_str); - gb_string_free(expr_str); - } - err = CallArgumentError_InvalidFieldValue; - continue; - } - String name = fv->field->Ident.token.string; - isize param_index = lookup_procedure_parameter(pt, name); - if (param_index < 0) { - if (show_error) { - error(arg, "No parameter named '%.*s' for this procedure type", LIT(name)); - } - err = CallArgumentError_ParameterNotFound; - continue; - } - if (visited[param_index]) { - if (show_error) { - error(arg, "Duplicate parameter '%.*s' in procedure call", LIT(name)); - } - err = CallArgumentError_DuplicateParameter; - continue; - } - - visited[param_index] = true; - ordered_operands[param_index] = operand; - - err = CallArgumentError_DuplicateParameter; - } - if (err) { - return err; - } - } - - - TypeTuple *param_tuple = nullptr; - if (pt->params != nullptr) { - param_tuple = &pt->params->Tuple; - } - - Type *final_proc_type = proc_type; Entity *gen_entity = nullptr; @@ -5421,7 +5368,263 @@ gb_internal CALL_ARGUMENT_CHECKER(check_call_arguments_internal) { LIT(ce->proc->Ident.token.string)); } err = CallArgumentError_NonVariadicExpand; - } else if (operands.count == 0 && param_count_excluding_defaults == 0) { + } + + if (ce->split_args) { + auto ordered_operands = array_make(temporary_allocator(), pt->param_count); + auto visited = slice_make(temporary_allocator(), pt->param_count); + + isize positional_operand_count = positional_operands.count; + if (variadic) { + positional_operand_count = gb_min(positional_operands.count, pt->variadic_index); + } else if (positional_operand_count > pt->param_count) { + err = CallArgumentError_TooManyArguments; + char const *err_fmt = "Too many arguments for '%s', expected %td arguments, got %td"; + if (show_error) { + gbString proc_str = expr_to_string(ce->proc); + defer (gb_string_free(proc_str)); + error(call, err_fmt, proc_str, param_count_excluding_defaults, positional_operands.count); + } + return err; + } + positional_operand_count = gb_min(positional_operand_count, pt->param_count); + + for (isize i = 0; i < positional_operand_count; i++) { + ordered_operands[i] = positional_operands[i]; + visited[i] = true; + } + + auto variadic_operands = slice(slice_from_array(positional_operands), positional_operand_count, positional_operands.count); + + if (named_operands.count != 0) { + GB_ASSERT(ce->split_args->named.count == named_operands.count); + for_array(i, ce->split_args->named) { + Ast *arg = ce->split_args->named[i]; + Operand operand = named_operands[i]; + + ast_node(fv, FieldValue, arg); + if (fv->field->kind != Ast_Ident) { + if (show_error) { + gbString expr_str = expr_to_string(fv->field); + error(arg, "Invalid parameter name '%s' in procedure call", expr_str); + gb_string_free(expr_str); + } + err = CallArgumentError_InvalidFieldValue; + continue; + } + String name = fv->field->Ident.token.string; + isize param_index = lookup_procedure_parameter(pt, name); + if (param_index < 0) { + if (show_error) { + error(arg, "No parameter named '%.*s' for this procedure type", LIT(name)); + } + err = CallArgumentError_ParameterNotFound; + continue; + } + if (visited[param_index]) { + if (show_error) { + error(arg, "Duplicate parameter '%.*s' in procedure call", LIT(name)); + } + err = CallArgumentError_DuplicateParameter; + continue; + } + Entity *e = pt->params->Tuple.variables[param_index]; + + check_assignment(c, &operand, e->type, str_lit("named argument")); + + visited[param_index] = true; + ordered_operands[param_index] = operand; + + err = CallArgumentError_DuplicateParameter; + } + } + + if (variadic) { + if (visited[pt->variadic_index] && + positional_operand_count < positional_operands.count) { + if (show_error) { + String name = pt->params->Tuple.variables[pt->variadic_index]->token.string; + error(call, "Variadic parameters already handled with a named argument '%.*s' in procedure call", LIT(name)); + } + err = CallArgumentError_DuplicateParameter; + } else { + visited[pt->variadic_index] = true; + } + + } + + + for (Operand const &o : ordered_operands) { + if (o.mode != Addressing_Invalid) { + check_no_copy_assignment(o, str_lit("procedure call expression")); + } + } + + for (isize i = 0; i < pt->param_count; i++) { + if (!visited[i]) { + Entity *e = pt->params->Tuple.variables[i]; + if (is_blank_ident(e->token)) { + continue; + } + if (e->kind == Entity_Variable) { + if (e->Variable.param_value.kind != ParameterValue_Invalid) { + score += assign_score_function(1); + continue; + } + } + + if (show_error) { + if (e->kind == Entity_TypeName) { + error(call, "Type parameter '%.*s' is missing in procedure call", + LIT(e->token.string)); + } else if (e->kind == Entity_Constant && e->Constant.value.kind != ExactValue_Invalid) { + // Ignore + } else { + gbString str = type_to_string(e->type); + error(call, "Parameter '%.*s' of type '%s' is missing in procedure call", + LIT(e->token.string), str); + gb_string_free(str); + } + } + err = CallArgumentError_ParameterMissing; + } + } + + if (ordered_operands.count == 0 && param_count_excluding_defaults == 0) { + err = CallArgumentError_None; + + if (variadic) { + GB_ASSERT(pt->params != nullptr && pt->params->Tuple.variables.count > 0); + Type *t = pt->params->Tuple.variables[0]->type; + if (is_type_polymorphic(t)) { + error(call, "Ambiguous call to a polymorphic variadic procedure with no variadic input"); + err = CallArgumentError_AmbiguousPolymorphicVariadic; + } + } + } else { + if (pt->is_polymorphic && !pt->is_poly_specialized && err == CallArgumentError_None) { + PolyProcData poly_proc_data = {}; + if (find_or_generate_polymorphic_procedure_from_parameters(c, entity, &ordered_operands, call, &poly_proc_data)) { + gen_entity = poly_proc_data.gen_entity; + Type *gept = base_type(gen_entity->type); + GB_ASSERT(is_type_proc(gept)); + proc_type = gept; + pt = &gept->Proc; + } else { + err = CallArgumentError_WrongTypes; + } + } + + for (isize i = 0; i < pt->param_count; i++) { + Entity *e = pt->params->Tuple.variables[i]; + Operand *o = &ordered_operands[i]; + bool param_is_variadic = pt->variadic && pt->variadic_index == i; + + if (o->mode == Addressing_Invalid) { + if (param_is_variadic) { + Type *slice = e->type; + GB_ASSERT(is_type_slice(slice)); + Type *elem = base_type(slice)->Slice.elem; + + if (variadic_operands.count == 0) { + if (is_type_polymorphic(elem)) { + error(call, "Ambiguous call to a polymorphic variadic procedure with no variadic input"); + err = CallArgumentError_AmbiguousPolymorphicVariadic; + return err; + } + } else { + if (vari_expand) { + GB_ASSERT(variadic_operands.count == 1); + check_assignment(c, &variadic_operands[0], slice, str_lit("variadic expanded argument")); + } else { + for (Operand &vo : variadic_operands) { + check_assignment(c, &vo, elem, str_lit("variadic argument")); + } + } + } + } + continue; + } + + if (e->kind == Entity_TypeName) { + GB_ASSERT(pt->is_polymorphic); + if (o->mode != Addressing_Type) { + if (show_error) { + error(o->expr, "Expected a type for the argument '%.*s'", LIT(e->token.string)); + } + err = CallArgumentError_WrongTypes; + } + if (are_types_identical(e->type, o->type)) { + score += assign_score_function(1); + } else { + score += assign_score_function(MAXIMUM_TYPE_DISTANCE); + } + } else { + i64 s = 0; + if (!check_is_assignable_to_with_score(c, o, e->type, &s, param_is_variadic)) { + bool ok = false; + if (ok) { + s = assign_score_function(MAXIMUM_TYPE_DISTANCE); + } else { + if (show_error) { + check_assignment(c, o, e->type, str_lit("procedure argument")); + } + err = CallArgumentError_WrongTypes; + } + + if (e->flags & EntityFlag_ConstInput) { + if (o->mode != Addressing_Constant) { + if (show_error) { + error(o->expr, "Expected a constant value for the argument '%.*s'", LIT(e->token.string)); + } + err = CallArgumentError_NoneConstantParameter; + } + } + } else if (show_error) { + check_assignment(c, o, e->type, str_lit("procedure argument")); + } + score += s; + } + + if (o->mode == Addressing_Type && is_type_typeid(e->type)) { + add_type_info_type(c, o->type); + add_type_and_value(c, o->expr, Addressing_Value, e->type, exact_value_typeid(o->type)); + } + } + } + + if (data) { + data->score = score; + data->result_type = final_proc_type->Proc.results; + data->gen_entity = gen_entity; + + + Ast *proc_lit = nullptr; + if (ce->proc->tav.value.kind == ExactValue_Procedure) { + Ast *vp = unparen_expr(ce->proc->tav.value.value_procedure); + if (vp && vp->kind == Ast_ProcLit) { + proc_lit = vp; + } + } + if (proc_lit == nullptr) { + add_type_and_value(c, ce->proc, Addressing_Value, final_proc_type, {}); + } + } + + return err; + } + + if (err) { + return err; + } + + auto operands = positional_operands; + TypeTuple *param_tuple = nullptr; + if (pt->params != nullptr) { + param_tuple = &pt->params->Tuple; + } + + if (operands.count == 0 && param_count_excluding_defaults == 0) { err = CallArgumentError_None; if (variadic) { @@ -5641,6 +5844,7 @@ gb_internal bool is_call_expr_field_value(AstCallExpr *ce) { } gb_internal CALL_ARGUMENT_CHECKER(check_named_call_arguments) { + auto operands = positional_operands; ast_node(ce, CallExpr, call); GB_ASSERT(is_type_proc(proc_type)); proc_type = base_type(proc_type); @@ -6705,37 +6909,26 @@ gb_internal CallArgumentData check_call_arguments_new_and_improved(CheckerContex return data; } + // if (pt->variadic) { + // isize param_index = pt->variadic_index; - for_array(i, positional_operands) { - Operand &o = positional_operands[i]; - isize param_index = i; + // Entity *e = pt->params->Tuple.variables[param_index]; + // GB_ASSERT(e->kind == Entity_Variable); + // GB_ASSERT(e->flags & EntityFlag_Ellipsis); + // GB_ASSERT(is_type_slice(e->type)); - Entity *e = pt->params->Tuple.variables[param_index]; - GB_ASSERT(e->kind == Entity_Variable); - - check_assignment(c, &o, e->type, str_lit("procedure call expression")); - } - - if (pt->variadic) { - isize param_index = pt->variadic_index; - - Entity *e = pt->params->Tuple.variables[param_index]; - GB_ASSERT(e->kind == Entity_Variable); - GB_ASSERT(e->flags & EntityFlag_Ellipsis); - GB_ASSERT(is_type_slice(e->type)); - - if (vari_expand) { - if (variadic_operands.count != 0) { - GB_ASSERT(variadic_operands.count == 1); - check_assignment(c, &variadic_operands[0], e->type, str_lit("procedure call expression")); - } - } else { - Type *elem = e->type->Slice.elem; - for (Operand &o : variadic_operands) { - check_assignment(c, &o, elem, str_lit("procedure call expression")); - } - } - } + // if (vari_expand) { + // if (variadic_operands.count != 0) { + // GB_ASSERT(variadic_operands.count == 1); + // check_assignment(c, &variadic_operands[0], e->type, str_lit("procedure call expression")); + // } + // } else { + // Type *elem = e->type->Slice.elem; + // for (Operand &o : variadic_operands) { + // check_assignment(c, &o, elem, str_lit("procedure call expression")); + // } + // } + // } array_resize(&positional_operands, pt->param_count); @@ -6770,68 +6963,6 @@ gb_internal CallArgumentData check_call_arguments_new_and_improved(CheckerContex array_add(&named_parameters, NamedParameter{name, fv->value}); } - StringMap type_hint_map = {}; // Key: String - string_map_init(&type_hint_map, 2*named_args.count); - defer (string_map_destroy(&type_hint_map)); - - if (pt != nullptr) { - TypeTuple *param_tuple = &pt->params->Tuple; - for (Entity *e : param_tuple->variables) { - if (is_blank_ident(e->token)) { - continue; - } - string_map_set(&type_hint_map, e->token.string, e->type); - } - } else if (operand->mode == Addressing_ProcGroup) { - Array procs = proc_group_entities(c, *operand); - for (Entity *proc : procs) { - Type *proc_type = base_type(proc->type); - if (is_type_proc(proc_type)) { - TypeProc *pt = &proc_type->Proc; - TypeTuple *param_tuple = nullptr; - if (pt->params != nullptr) { - param_tuple = &pt->params->Tuple; - } - if (param_tuple == nullptr) { - continue; - } - for (Entity *e : param_tuple->variables) { - if (is_blank_ident(e->token)) { - continue; - } - StringHashKey key = string_hash_string(e->token.string); - Type **found = string_map_get(&type_hint_map, key); - if (found) { - Type *t = *found; - if (t == nullptr) { - // NOTE(bill): Ambiguous named parameter across all types - continue; - } - if (are_types_identical(t, e->type)) { - // NOTE(bill): No need to set again - } else { - // NOTE(bill): Ambiguous named parameter across all types so set it to a nullptr - string_map_set(&type_hint_map, key, cast(Type *)nullptr); - } - } else { - string_map_set(&type_hint_map, key, e->type); - } - } - } - } - } - - for (NamedParameter ¶m : named_parameters) { - Type *type_hint = nullptr; - if (Type **found = string_map_get(&type_hint_map, param.key)) { - type_hint = *found; - } - - Operand operand = {}; - check_expr_or_type(c, &operand, param.value, type_hint); - array_add(&named_operands, operand); - } - for (NamedParameter const ¶m : named_parameters) { isize param_index = lookup_procedure_parameter(pt, param.key); if (param_index < 0) { @@ -6856,11 +6987,10 @@ gb_internal CallArgumentData check_call_arguments_new_and_improved(CheckerContex any_failure = true; } - check_assignment(c, &o, e->type, str_lit("procedure call expression")); - if (!prev_visited) { positional_operands[param_index] = o; } + array_add(&named_operands, o); } } @@ -6891,17 +7021,6 @@ gb_internal CallArgumentData check_call_arguments_new_and_improved(CheckerContex } } - for (Operand const &o : positional_operands) { - check_no_copy_assignment(o, str_lit("procedure call expression")); - } - for (Operand const &o : variadic_operands) { - check_no_copy_assignment(o, str_lit("procedure call expression")); - } - for (Operand const &o : named_operands) { - check_no_copy_assignment(o, str_lit("procedure call expression")); - } - - if (any_failure) { return data; } @@ -6917,7 +7036,7 @@ gb_internal CallArgumentData check_call_arguments_new_and_improved(CheckerContex Array operands = array_clone(heap_allocator(), positional_operands); - CallArgumentError err = check_call_arguments_internal(c, call, proc_type, e, operands, named_operands, CallArgumentMode_ShowErrors, &data); + CallArgumentError err = check_call_arguments_internal(c, call, proc_type, e, operands, {}, CallArgumentMode_ShowErrors, &data); gb_unused(err); Entity *entity_to_use = data.gen_entity != nullptr ? data.gen_entity : e; add_entity_use(c, ident, entity_to_use); @@ -6948,127 +7067,6 @@ gb_internal CallArgumentData check_call_arguments_new_and_improved(CheckerContex data.result_type = pt->results; } return data; -#if 0 - - - - /* - auto visited = slice_make(temporary_allocator(), pt->param_count); - auto ordered_values = slice_make(temporary_allocator(), pt->param_count); - - - for_array(i, ce->args) { - Ast *arg = ce->args[i]; - - isize param_index = i; - String param_name = {}; - - if (arg->kind == Ast_FieldValue) { - ast_node(fv, FieldValue, arg); - if (fv->field->kind != Ast_Ident) { - gbString expr_str = expr_to_string(fv->field); - error(arg, "Invalid parameter name '%s' in procedure call", expr_str); - any_failure = true; - gb_string_free(expr_str); - continue; - } - param_name = fv->field->Ident.token.string; - param_index = lookup_procedure_parameter(pt, param_name); - if (param_index < 0) { - error(arg, "No parameter named '%.*s' for this procedure type", LIT(param_name)); - any_failure = true; - continue; - } - arg = fv->value; - } else { - if (param_index >= pt->param_count) { - error(arg, "Too many parameters in procedure call, expected a maximum of %td, got %td", pt->param_count, ce->args.count); - any_failure = true; - break; - } - - param_name = pt->params->Tuple.variables[param_index]->token.string; - } - if (visited[param_index]) { - error(arg, "Duplicate parameter '%.*s' in procedure call", LIT(param_name)); - any_failure = true; - } else { - ParameterValue value = {}; - value.kind = ParameterValue_Value; - value.original_ast_expr = arg; - value.ast_value = arg; - ordered_values[param_index] = value; - } - visited[param_index] = true; - } - - for (isize i = 0; i < pt->param_count; i++) { - if (!visited[i]) { - Entity *e = pt->params->Tuple.variables[i]; - if (e->kind == Entity_Variable && - has_parameter_value(e->Variable.param_value)) { - ordered_values[i] = e->Variable.param_value; - visited[i] = true; - } - } - } - - for (isize i = 0; i < visited.count; i++) { - if (!visited[i]) { - Entity *e = pt->params->Tuple.variables[i]; - gbString t = type_to_string(e->type); - defer (gb_string_free(t)); - if (is_blank_ident(e->token)) { - error(call, "Missing parameter of type '%s' at index %td", t, i); - } else { - error(call, "Missing parameter '%.*s' of type '%s'", LIT(e->token.string), t, i); - } - return data; - } - } - */ - if (any_failure) { - return data; - } - - Array operands = {}; - defer (array_free(&operands)); - - Ast *ident = unparen_expr(operand->expr); - while (ident->kind == Ast_SelectorExpr) { - Ast *s = ident->SelectorExpr.selector; - ident = unparen_expr(s); - } - - Entity *e = entity_of_node(ident); - - CallArgumentError err = check_call_arguments_internal(c, call, proc_type, e, operands, CallArgumentMode_ShowErrors, &data); - gb_unused(err); - Entity *entity_to_use = data.gen_entity != nullptr ? data.gen_entity : e; - add_entity_use(c, ident, entity_to_use); - if (entity_to_use != nullptr) { - update_untyped_expr_type(c, operand->expr, entity_to_use->type, true); - } - if (data.gen_entity != nullptr) { - Entity *e = data.gen_entity; - DeclInfo *decl = data.gen_entity->decl_info; - CheckerContext ctx = *c; - ctx.scope = decl->scope; - ctx.decl = decl; - ctx.proc_name = e->token.string; - ctx.curr_proc_decl = decl; - ctx.curr_proc_sig = e->type; - - GB_ASSERT(decl->proc_lit->kind == Ast_ProcLit); - bool ok = evaluate_where_clauses(&ctx, call, decl->scope, &decl->proc_lit->ProcLit.where_clauses, true); - decl->where_clauses_evaluated = true; - - if (ok && (data.gen_entity->flags & EntityFlag_ProcBodyChecked) == 0) { - check_procedure_later(c->checker, e->file, e->token, decl, e->type, decl->proc_lit->ProcLit.body, decl->proc_lit->ProcLit.tags); - } - } - return data; -#endif } From 9ec927b9e952cb1e82ec543c95547321273a812b Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 20 Jun 2023 00:30:29 +0100 Subject: [PATCH 37/97] Try to get make everything work with parapoly --- src/check_expr.cpp | 186 +++++++------------------------------- src/llvm_backend_proc.cpp | 7 +- src/types.cpp | 16 ++-- 3 files changed, 48 insertions(+), 161 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 92b572352..8242d38e9 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -6791,11 +6791,6 @@ gb_internal CallArgumentData check_call_arguments_new_and_improved(CheckerContex proc_type = base_type(operand->type); - if (proc_type != nullptr && is_type_polymorphic(proc_type)) { - error(call, "Polymorphic procedures not yet supported"); - return data; - } - TypeProc *pt = nullptr; if (proc_type) { pt = &proc_type->Proc; @@ -6806,7 +6801,7 @@ gb_internal CallArgumentData check_call_arguments_new_and_improved(CheckerContex bool any_failure = false; - bool vari_expand = (ce->ellipsis.pos.line != 0); + // bool vari_expand = (ce->ellipsis.pos.line != 0); // Split positional and named args into separate arrays/slices Slice positional_args = {}; @@ -6838,14 +6833,11 @@ gb_internal CallArgumentData check_call_arguments_new_and_improved(CheckerContex auto positional_operands = array_make(heap_allocator(), 0, positional_args.count); - auto variadic_operands = array_make(heap_allocator(), 0, 0); auto named_operands = array_make(heap_allocator(), 0, 0); defer (array_free(&positional_operands)); - defer (array_free(&variadic_operands)); defer (array_free(&named_operands)); - isize positional_operand_count = 0; Slice visited = {}; @@ -6860,90 +6852,8 @@ gb_internal CallArgumentData check_call_arguments_new_and_improved(CheckerContex } check_unpack_arguments(c, lhs, lhs_count, &positional_operands, positional_args, is_variadic ? UnpackFlag_IsVariadic : UnpackFlag_None); } - positional_operand_count = positional_operands.count; - - if (pt != nullptr) { - visited = slice_make(temporary_allocator(), pt->param_count); - - if (!pt->variadic) { - if (pt->param_count < positional_operands.count) { - char const *err_fmt = "Too many arguments for '%s', expected a maximum of %td arguments, got %td"; - gbString c = expr_to_string(ce->proc); - error(call, err_fmt, c, pt->param_count, positional_operands.count); - gb_string_free(c); - return data; - } - positional_operand_count = positional_operands.count; - } else { - // only state it is visited if it was set later on - visited[pt->variadic_index] = positional_operand_count > pt->variadic_index; - - positional_operand_count = gb_min(pt->variadic_index, positional_operand_count); - } - - - for (isize i = 0; i < positional_operand_count; i++) { - visited[i] = true; - } - - for (isize i = positional_operand_count; i < positional_operands.count; i++) { - array_add(&variadic_operands, positional_operands[i]); - } - array_resize(&positional_operands, positional_operand_count); - - if (vari_expand) { - if (variadic_operands.count > 1) { - error(variadic_operands[1].expr, "Unexpected position arguments after variadic expansion with '..'"); - return data; - } - } - - isize param_count = 0; - isize param_count_excluding_defaults = get_procedure_param_count_excluding_defaults(proc_type, ¶m_count); - - if (named_args.count == 0 && positional_operands.count < param_count_excluding_defaults) { - char const *err_fmt = "Too few arguments for '%s', expected a minimum of %td arguments, got %td"; - gbString c = expr_to_string(ce->proc); - error(call, err_fmt, c, param_count_excluding_defaults, positional_operands.count); - gb_string_free(c); - return data; - } - - // if (pt->variadic) { - // isize param_index = pt->variadic_index; - - // Entity *e = pt->params->Tuple.variables[param_index]; - // GB_ASSERT(e->kind == Entity_Variable); - // GB_ASSERT(e->flags & EntityFlag_Ellipsis); - // GB_ASSERT(is_type_slice(e->type)); - - // if (vari_expand) { - // if (variadic_operands.count != 0) { - // GB_ASSERT(variadic_operands.count == 1); - // check_assignment(c, &variadic_operands[0], e->type, str_lit("procedure call expression")); - // } - // } else { - // Type *elem = e->type->Slice.elem; - // for (Operand &o : variadic_operands) { - // check_assignment(c, &o, elem, str_lit("procedure call expression")); - // } - // } - // } - - array_resize(&positional_operands, pt->param_count); - - } - if (named_args.count > 0) { - struct NamedParameter { - String key; - Ast * value; - }; - - auto named_parameters = array_make(heap_allocator(), 0, named_args.count); - defer (array_free(&named_parameters)); - for_array(i, named_args) { Ast *arg = named_args[i]; if (arg->kind != Ast_FieldValue) { @@ -6958,74 +6868,27 @@ gb_internal CallArgumentData check_call_arguments_new_and_improved(CheckerContex gb_string_free(expr_str); continue; } - String name = fv->field->Ident.token.string; + String key = fv->field->Ident.token.string; + Ast *value = fv->value; - array_add(&named_parameters, NamedParameter{name, fv->value}); - } - - for (NamedParameter const ¶m : named_parameters) { - isize param_index = lookup_procedure_parameter(pt, param.key); - if (param_index < 0) { - error(param.value, "No parameter named '%.*s' for this procedure type", LIT(param.key)); - any_failure = true; - continue; + isize param_index = lookup_procedure_parameter(pt, key); + Type *type_hint = nullptr; + if (param_index >= 0) { + Entity *e = pt->params->Tuple.variables[param_index]; + type_hint = e->type; } - bool prev_visited = visited[param_index]; - visited[param_index] = true; - - if (prev_visited) { - error(param.value, "Duplicate parameter named '%.*s' in procedure call", LIT(param.key)); - } - - Entity *e = pt->params->Tuple.variables[param_index]; - GB_ASSERT(e->kind == Entity_Variable); - Operand o = {}; - check_expr_with_type_hint(c, &o, param.value, e->type); + check_expr_with_type_hint(c, &o, value, type_hint); if (o.mode == Addressing_Invalid) { any_failure = true; } - - if (!prev_visited) { - positional_operands[param_index] = o; - } array_add(&named_operands, o); } } - if (pt != nullptr) { - if (pt->variadic) { - visited[pt->variadic_index] = true; - } - - for_array(i, visited) { - if (!visited[i]) { - if (pt->variadic && i == pt->variadic_index) { - visited[i] = true; - continue; - } - - Entity *e = pt->params->Tuple.variables[i]; - if (e->kind == Entity_Variable) { - if (has_parameter_value(e->Variable.param_value)) { - visited[i] = true; - continue; - } - } - gbString t = type_to_string(e->type); - error(call, "Missing parameter of type '%s' at index %td", t, i); - gb_string_free(t); - } - } - } - - if (any_failure) { - return data; - } - - { + if (!any_failure) { Ast *ident = operand->expr; while (ident->kind == Ast_SelectorExpr) { Ast *s = ident->SelectorExpr.selector; @@ -7034,9 +6897,12 @@ gb_internal CallArgumentData check_call_arguments_new_and_improved(CheckerContex Entity *e = entity_of_node(ident); - Array operands = array_clone(heap_allocator(), positional_operands); + TokenPos pos = ast_token(call).pos; + if (pos.line == 282 && pos.column == 9 && is_type_polymorphic(proc_type)) { + // GB_PANIC("HERE! %s\n", expr_to_string(call)); + } - CallArgumentError err = check_call_arguments_internal(c, call, proc_type, e, operands, {}, CallArgumentMode_ShowErrors, &data); + CallArgumentError err = check_call_arguments_internal(c, call, proc_type, e, positional_operands, named_operands, CallArgumentMode_ShowErrors, &data); gb_unused(err); Entity *entity_to_use = data.gen_entity != nullptr ? data.gen_entity : e; add_entity_use(c, ident, entity_to_use); @@ -7060,12 +6926,17 @@ gb_internal CallArgumentData check_call_arguments_new_and_improved(CheckerContex if (ok && (data.gen_entity->flags & EntityFlag_ProcBodyChecked) == 0) { check_procedure_later(c->checker, e->file, e->token, decl, e->type, decl->proc_lit->ProcLit.body, decl->proc_lit->ProcLit.tags); } - } - } - if (pt != nullptr) { + if (is_type_proc(data.gen_entity->type)) { + Type *t = base_type(entity_to_use->type); + // GB_ASSERT_MSG(!is_type_polymorphic(t), "%s", expr_to_string(call)); + data.result_type = t->Proc.results; + } + } + } else if (pt) { data.result_type = pt->results; } + return data; } @@ -7085,8 +6956,15 @@ gb_internal CallArgumentData check_call_arguments(CheckerContext *c, Operand *op } } - if (operand->mode != Addressing_ProcGroup && is_type_proc(operand->type) && !is_type_polymorphic(operand->type)) { - return check_call_arguments_new_and_improved(c, operand, call); + if (operand->mode != Addressing_ProcGroup) { + bool is_poly = is_type_polymorphic(operand->type); + bool ok = !is_poly; + if (!ok && c->pkg->name == "bug") { + ok = true; + } + if (ok) { + return check_call_arguments_new_and_improved(c, operand, call); + } } ast_node(ce, CallExpr, call); diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 046016c75..c6a9438ea 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -3165,7 +3165,12 @@ gb_internal lbValue lb_build_call_expr_internal_with_arg_split_args(lbProcedure for_array(i, split_args->positional) { Entity *e = pt->params->Tuple.variables[i]; - GB_ASSERT(e->kind == Entity_Variable); + if (e->kind == Entity_TypeName) { + array_add(&args, lb_const_nil(p->module, e->type)); + continue; + } else if (e->kind != Entity_Variable) { + continue; + } if (pt->variadic && pt->variadic_index == i) { lbValue variadic_args = lb_const_nil(p->module, e->type); diff --git a/src/types.cpp b/src/types.cpp index f3b7f5bab..1223132e3 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -2108,8 +2108,12 @@ gb_internal bool is_type_polymorphic(Type *t, bool or_specialized=false) { return is_type_polymorphic(t->Matrix.elem, or_specialized); case Type_Tuple: - for_array(i, t->Tuple.variables) { - if (is_type_polymorphic(t->Tuple.variables[i]->type, or_specialized)) { + for (Entity *e : t->Tuple.variables) { + if (e->kind == Entity_Constant) { + if (e->Constant.value.kind != ExactValue_Invalid) { + return or_specialized; + } + } else if (is_type_polymorphic(e->type, or_specialized)) { return true; } } @@ -4279,6 +4283,10 @@ gb_internal gbString write_type_to_string(gbString str, Type *type, bool shortha if (var == nullptr) { continue; } + if (comma_index++ > 0) { + str = gb_string_appendc(str, ", "); + } + String name = var->token.string; if (var->kind == Entity_Constant) { str = gb_string_appendc(str, "$"); @@ -4295,10 +4303,6 @@ gb_internal gbString write_type_to_string(gbString str, Type *type, bool shortha continue; } - if (comma_index++ > 0) { - str = gb_string_appendc(str, ", "); - } - if (var->kind == Entity_Variable) { if (var->flags&EntityFlag_CVarArg) { str = gb_string_appendc(str, "#c_vararg "); From 7c57dde2556b95a0ef206efd93fc23f8fd0bdf89 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 20 Jun 2023 13:47:10 +0100 Subject: [PATCH 38/97] Start work on parapoly args for new and improved --- src/check_expr.cpp | 18 ++++-------------- src/llvm_backend_proc.cpp | 2 +- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 8242d38e9..5574827d5 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -6898,9 +6898,6 @@ gb_internal CallArgumentData check_call_arguments_new_and_improved(CheckerContex Entity *e = entity_of_node(ident); TokenPos pos = ast_token(call).pos; - if (pos.line == 282 && pos.column == 9 && is_type_polymorphic(proc_type)) { - // GB_PANIC("HERE! %s\n", expr_to_string(call)); - } CallArgumentError err = check_call_arguments_internal(c, call, proc_type, e, positional_operands, named_operands, CallArgumentMode_ShowErrors, &data); gb_unused(err); @@ -6908,6 +6905,7 @@ gb_internal CallArgumentData check_call_arguments_new_and_improved(CheckerContex add_entity_use(c, ident, entity_to_use); if (entity_to_use != nullptr) { update_untyped_expr_type(c, operand->expr, entity_to_use->type, true); + add_type_and_value(c, operand->expr, operand->mode, entity_to_use->type, operand->value); } if (data.gen_entity != nullptr) { Entity *e = data.gen_entity; @@ -6931,6 +6929,7 @@ gb_internal CallArgumentData check_call_arguments_new_and_improved(CheckerContex Type *t = base_type(entity_to_use->type); // GB_ASSERT_MSG(!is_type_polymorphic(t), "%s", expr_to_string(call)); data.result_type = t->Proc.results; + } } } else if (pt) { @@ -6954,17 +6953,8 @@ gb_internal CallArgumentData check_call_arguments(CheckerContext *c, Operand *op if (all_non_poly) { return check_call_arguments_new_and_improved(c, operand, call); } - } - - if (operand->mode != Addressing_ProcGroup) { - bool is_poly = is_type_polymorphic(operand->type); - bool ok = !is_poly; - if (!ok && c->pkg->name == "bug") { - ok = true; - } - if (ok) { - return check_call_arguments_new_and_improved(c, operand, call); - } + } else { + return check_call_arguments_new_and_improved(c, operand, call); } ast_node(ce, CallExpr, call); diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index c6a9438ea..b7ee88804 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -3250,7 +3250,7 @@ gb_internal lbValue lb_build_call_expr_internal_with_arg_split_args(lbProcedure args[i] = lb_handle_param_value(p, e->type, e->Variable.param_value, pos); break; case Entity_Constant: - GB_PANIC("TODO constant parameter"); + args[i] = lb_handle_param_value(p, e->type, e->Constant.param_value, pos); break; } } else { From 26e06ba6a68bf9812b92d59629aaa426f2659a5f Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 20 Jun 2023 16:08:26 +0100 Subject: [PATCH 39/97] Correct `check_call_arguments_new_and_improved` logic --- src/check_expr.cpp | 139 ++++++++++++++++++++++++++++++++++----------- src/check_stmt.cpp | 8 ++- src/check_type.cpp | 133 ++++++++++++++++++++++--------------------- src/types.cpp | 2 - 4 files changed, 181 insertions(+), 101 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 5574827d5..ac3886268 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -34,6 +34,7 @@ gb_global char const *CallArgumentError_strings[CallArgumentError_MAX] = { "ParameterMissing", "DuplicateParameter", "NoneConstantParameter", + "OutOfOrderParameters", }; @@ -95,7 +96,7 @@ gb_internal void check_stmt (CheckerContext *c, Ast *nod gb_internal void check_stmt_list (CheckerContext *c, Slice const &stmts, u32 flags); gb_internal void check_init_constant (CheckerContext *c, Entity *e, Operand *operand); gb_internal bool check_representable_as_constant(CheckerContext *c, ExactValue in_value, Type *type, ExactValue *out_value); -gb_internal bool check_procedure_type (CheckerContext *c, Type *type, Ast *proc_type_node, Array *operands = nullptr); +gb_internal bool check_procedure_type (CheckerContext *c, Type *type, Ast *proc_type_node, Array const *operands = nullptr); gb_internal void check_struct_type (CheckerContext *c, Type *struct_type, Ast *node, Array *poly_operands, Type *named_type = nullptr, Type *original_type_for_poly = nullptr); gb_internal void check_union_type (CheckerContext *c, Type *union_type, Ast *node, Array *poly_operands, @@ -338,7 +339,7 @@ gb_internal void check_scope_decls(CheckerContext *c, Slice const &nodes, } gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, Entity *base_entity, Type *type, - Array *param_operands, Ast *poly_def_node, PolyProcData *poly_proc_data) { + Array const *param_operands, Ast *poly_def_node, PolyProcData *poly_proc_data) { /////////////////////////////////////////////////////////////////////////////// // // // TODO CLEANUP(bill): This procedure is very messy and hacky. Clean this!!! // @@ -605,7 +606,7 @@ gb_internal bool check_polymorphic_procedure_assignment(CheckerContext *c, Opera return find_or_generate_polymorphic_procedure(c, base_entity, type, nullptr, poly_def_node, poly_proc_data); } -gb_internal bool find_or_generate_polymorphic_procedure_from_parameters(CheckerContext *c, Entity *base_entity, Array *operands, Ast *poly_def_node, PolyProcData *poly_proc_data) { +gb_internal bool find_or_generate_polymorphic_procedure_from_parameters(CheckerContext *c, Entity *base_entity, Array const *operands, Ast *poly_def_node, PolyProcData *poly_proc_data) { return find_or_generate_polymorphic_procedure(c, base_entity, nullptr, operands, poly_def_node, poly_proc_data); } @@ -5337,6 +5338,8 @@ gb_internal isize lookup_procedure_parameter(Type *type, String const ¶meter gb_internal CALL_ARGUMENT_CHECKER(check_call_arguments_internal) { + TEMPORARY_ALLOCATOR_GUARD(); + CallArgumentError err = CallArgumentError_None; ast_node(ce, CallExpr, call); @@ -5371,8 +5374,19 @@ gb_internal CALL_ARGUMENT_CHECKER(check_call_arguments_internal) { } if (ce->split_args) { - auto ordered_operands = array_make(temporary_allocator(), pt->param_count); auto visited = slice_make(temporary_allocator(), pt->param_count); + auto ordered_operands = array_make(temporary_allocator(), pt->param_count); + defer ({ + for (Operand const &o : ordered_operands) { + if (o.expr != nullptr) { + call->viral_state_flags |= o.expr->viral_state_flags; + } + } + }); + + if (check_order_of_call_arguments(c, proc_type, call, show_error)) { + return CallArgumentError_OutOfOrderParameters; + } isize positional_operand_count = positional_operands.count; if (variadic) { @@ -5449,11 +5463,36 @@ gb_internal CALL_ARGUMENT_CHECKER(check_call_arguments_internal) { err = CallArgumentError_DuplicateParameter; } else { visited[pt->variadic_index] = true; + + if (vari_expand) { + GB_ASSERT(variadic_operands.count != 0); + ordered_operands[pt->variadic_index] = variadic_operands[0]; + } else { + AstFile *f = call->file(); + + // HACK(bill): this is an awful hack + Operand o = {}; + o.mode = Addressing_Value; + o.expr = ast_ident(f, make_token_ident("nil")); + o.expr->Ident.token.pos = ast_token(call).pos; + if (variadic_operands.count != 0) { + o.expr->Ident.token.pos = ast_token(variadic_operands[0].expr).pos; + + Entity *vt = pt->params->Tuple.variables[pt->variadic_index]; + if (is_type_polymorphic(vt->type)) { + o.type = alloc_type_slice(default_type(variadic_operands[0].type)); + } else { + o.type = vt->type; + } + } else { + o.type = t_untyped_nil; + } + ordered_operands[pt->variadic_index] = o; + } } } - for (Operand const &o : ordered_operands) { if (o.mode != Addressing_Invalid) { check_no_copy_assignment(o, str_lit("procedure call expression")); @@ -5508,44 +5547,23 @@ gb_internal CALL_ARGUMENT_CHECKER(check_call_arguments_internal) { gen_entity = poly_proc_data.gen_entity; Type *gept = base_type(gen_entity->type); GB_ASSERT(is_type_proc(gept)); - proc_type = gept; + final_proc_type = gen_entity->type; pt = &gept->Proc; + } else { err = CallArgumentError_WrongTypes; } } for (isize i = 0; i < pt->param_count; i++) { - Entity *e = pt->params->Tuple.variables[i]; Operand *o = &ordered_operands[i]; - bool param_is_variadic = pt->variadic && pt->variadic_index == i; - if (o->mode == Addressing_Invalid) { - if (param_is_variadic) { - Type *slice = e->type; - GB_ASSERT(is_type_slice(slice)); - Type *elem = base_type(slice)->Slice.elem; - - if (variadic_operands.count == 0) { - if (is_type_polymorphic(elem)) { - error(call, "Ambiguous call to a polymorphic variadic procedure with no variadic input"); - err = CallArgumentError_AmbiguousPolymorphicVariadic; - return err; - } - } else { - if (vari_expand) { - GB_ASSERT(variadic_operands.count == 1); - check_assignment(c, &variadic_operands[0], slice, str_lit("variadic expanded argument")); - } else { - for (Operand &vo : variadic_operands) { - check_assignment(c, &vo, elem, str_lit("variadic argument")); - } - } - } - } continue; } + Entity *e = pt->params->Tuple.variables[i]; + bool param_is_variadic = pt->variadic && pt->variadic_index == i; + if (e->kind == Entity_TypeName) { GB_ASSERT(pt->is_polymorphic); if (o->mode != Addressing_Type) { @@ -5563,6 +5581,11 @@ gb_internal CALL_ARGUMENT_CHECKER(check_call_arguments_internal) { i64 s = 0; if (!check_is_assignable_to_with_score(c, o, e->type, &s, param_is_variadic)) { bool ok = false; + if (e->flags & EntityFlag_AnyInt) { + if (is_type_integer(e->type)) { + ok = check_is_castable_to(c, o, e->type); + } + } if (ok) { s = assign_score_function(MAXIMUM_TYPE_DISTANCE); } else { @@ -5589,6 +5612,57 @@ gb_internal CALL_ARGUMENT_CHECKER(check_call_arguments_internal) { if (o->mode == Addressing_Type && is_type_typeid(e->type)) { add_type_info_type(c, o->type); add_type_and_value(c, o->expr, Addressing_Value, e->type, exact_value_typeid(o->type)); + } else if (show_error && is_type_untyped(o->type)) { + update_untyped_expr_type(c, o->expr, e->type, true); + } + } + } + + if (variadic) { + Type *slice = pt->params->Tuple.variables[pt->variadic_index]->type; + GB_ASSERT(is_type_slice(slice)); + Type *elem = base_type(slice)->Slice.elem; + Type *t = elem; + + if (is_type_polymorphic(t)) { + error(call, "Ambiguous call to a polymorphic variadic procedure with no variadic input %s", type_to_string(final_proc_type)); + err = CallArgumentError_AmbiguousPolymorphicVariadic; + } + + for_array(operand_index, variadic_operands) { + Operand &o = variadic_operands[operand_index]; + if (vari_expand) { + t = slice; + if (operand_index > 0) { + if (show_error) { + error(o.expr, "'..' in a variadic procedure can only have one variadic argument at the end"); + } + if (data) { + data->score = score; + data->result_type = final_proc_type->Proc.results; + data->gen_entity = gen_entity; + } + return CallArgumentError_MultipleVariadicExpand; + } + } + i64 s = 0; + if (!check_is_assignable_to_with_score(c, &o, t, &s, true)) { + if (show_error) { + check_assignment(c, &o, t, str_lit("variadic argument")); + } + err = CallArgumentError_WrongTypes; + } else if (show_error) { + check_assignment(c, &o, t, str_lit("variadic argument")); + } + score += s; + if (is_type_any(elem)) { + add_type_info_type(c, o.type); + } + if (o.mode == Addressing_Type && is_type_typeid(t)) { + add_type_info_type(c, o.type); + add_type_and_value(c, o.expr, Addressing_Value, t, exact_value_typeid(o.type)); + } else if (show_error && is_type_untyped(o.type)) { + update_untyped_expr_type(c, o.expr, t, true); } } } @@ -6756,6 +6830,7 @@ gb_internal CallArgumentData check_call_arguments_new_and_improved_proc_group(Ch add_entity_use(c, ident, entity_to_use); if (entity_to_use != nullptr) { update_untyped_expr_type(c, operand->expr, entity_to_use->type, true); + add_type_and_value(c, operand->expr, operand->mode, entity_to_use->type, operand->value); } if (data.gen_entity != nullptr) { @@ -6950,7 +7025,7 @@ gb_internal CallArgumentData check_call_arguments(CheckerContext *c, Operand *op break; } } - if (all_non_poly) { + if (true) { return check_call_arguments_new_and_improved(c, operand, call); } } else { diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index 09af496ab..cf6f998e5 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -2207,7 +2207,13 @@ gb_internal void check_return_stmt(CheckerContext *ctx, Ast *node) { } else if (operands.count != result_count) { // Ignore error message as it has most likely already been reported if (all_operands_valid(operands)) { - error(node, "Expected %td return values, got %td", result_count, operands.count); + if (operands.count == 1) { + gbString t = type_to_string(operands[0].type); + error(node, "Expected %td return values, got %td (%s)", result_count, operands.count, t); + gb_string_free(t); + } else { + error(node, "Expected %td return values, got %td", result_count, operands.count); + } } } else { for (isize i = 0; i < result_count; i++) { diff --git a/src/check_type.cpp b/src/check_type.cpp index a69dcdadc..f241f35ee 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -1416,7 +1416,7 @@ gb_internal ParameterValue handle_parameter_value(CheckerContext *ctx, Type *in_ } -gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_params, bool *is_variadic_, isize *variadic_index_, bool *success_, isize *specialization_count_, Array *operands) { +gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_params, bool *is_variadic_, isize *variadic_index_, bool *success_, isize *specialization_count_, Array const *operands) { if (_params == nullptr) { return nullptr; } @@ -1664,80 +1664,81 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para ExactValue poly_const = {}; if (operands != nullptr && variables.count < operands->count) { - Operand op = (*operands)[variables.count]; - if (op.expr == nullptr) { - // NOTE(bill): 2019-03-30 - // This is just to add the error message to determine_type_from_polymorphic which - // depends on valid position information - op.expr = _params; - op.mode = Addressing_Invalid; - op.type = t_invalid; - } - if (is_type_polymorphic_type) { - type = determine_type_from_polymorphic(ctx, type, op); - if (type == t_invalid) { - success = false; - } else if (!ctx->no_polymorphic_errors) { - // NOTE(bill): The type should be determined now and thus, no need to determine the type any more - is_type_polymorphic_type = false; - Entity *proc_entity = entity_from_expr(op.expr); - if ((proc_entity != nullptr) && (op.value.kind == ExactValue_Procedure)) { - if (is_type_polymorphic(proc_entity->type, false)) { - error(op.expr, "Cannot determine complete type of partial polymorphic procedure"); + { + if (op.expr == nullptr) { + // NOTE(bill): 2019-03-30 + // This is just to add the error message to determine_type_from_polymorphic which + // depends on valid position information + op.expr = _params; + op.mode = Addressing_Invalid; + op.type = t_invalid; + } + if (is_type_polymorphic_type) { + type = determine_type_from_polymorphic(ctx, type, op); + if (type == t_invalid) { + success = false; + } else if (!ctx->no_polymorphic_errors) { + // NOTE(bill): The type should be determined now and thus, no need to determine the type any more + is_type_polymorphic_type = false; + Entity *proc_entity = entity_from_expr(op.expr); + if ((proc_entity != nullptr) && (op.value.kind == ExactValue_Procedure)) { + if (is_type_polymorphic(proc_entity->type, false)) { + error(op.expr, "Cannot determine complete type of partial polymorphic procedure"); + } } } } - } - if (is_poly_name) { - bool valid = false; - if (is_type_proc(op.type)) { - Ast *expr = unparen_expr(op.expr); - Entity *proc_entity = entity_from_expr(expr); - if (proc_entity) { - poly_const = exact_value_procedure(proc_entity->identifier.load() ? proc_entity->identifier.load() : op.expr); - valid = true; - } else if (expr->kind == Ast_ProcLit) { - poly_const = exact_value_procedure(expr); - valid = true; + if (is_poly_name) { + bool valid = false; + if (is_type_proc(op.type)) { + Ast *expr = unparen_expr(op.expr); + Entity *proc_entity = entity_from_expr(expr); + if (proc_entity) { + poly_const = exact_value_procedure(proc_entity->identifier.load() ? proc_entity->identifier.load() : op.expr); + valid = true; + } else if (expr->kind == Ast_ProcLit) { + poly_const = exact_value_procedure(expr); + valid = true; + } + } + if (!valid) { + if (op.mode == Addressing_Constant) { + poly_const = op.value; + } else { + error(op.expr, "Expected a constant value for this polymorphic name parameter, got %s", expr_to_string(op.expr)); + success = false; + } } } - if (!valid) { - if (op.mode == Addressing_Constant) { - poly_const = op.value; - } else { - error(op.expr, "Expected a constant value for this polymorphic name parameter, got %s", expr_to_string(op.expr)); + if (type != t_invalid && !check_is_assignable_to(ctx, &op, type)) { + bool ok = true; + if (p->flags&FieldFlag_any_int) { + if ((!is_type_integer(op.type) && !is_type_enum(op.type)) || (!is_type_integer(type) && !is_type_enum(type))) { + ok = false; + } else if (!check_is_castable_to(ctx, &op, type)) { + ok = false; + } + } + if (!ok) { success = false; + #if 0 + gbString got = type_to_string(op.type); + gbString expected = type_to_string(type); + error(op.expr, "Cannot assigned type to parameter, got type '%s', expected '%s'", got, expected); + gb_string_free(expected); + gb_string_free(got); + #endif } } - } - if (type != t_invalid && !check_is_assignable_to(ctx, &op, type)) { - bool ok = true; - if (p->flags&FieldFlag_any_int) { - if ((!is_type_integer(op.type) && !is_type_enum(op.type)) || (!is_type_integer(type) && !is_type_enum(type))) { - ok = false; - } else if (!check_is_castable_to(ctx, &op, type)) { - ok = false; - } - } - if (!ok) { - success = false; - #if 0 - gbString got = type_to_string(op.type); - gbString expected = type_to_string(type); - error(op.expr, "Cannot assigned type to parameter, got type '%s', expected '%s'", got, expected); - gb_string_free(expected); - gb_string_free(got); - #endif - } - } - if (is_type_untyped(default_type(type))) { - gbString str = type_to_string(type); - error(op.expr, "Cannot determine type from the parameter, got '%s'", str); - gb_string_free(str); - success = false; - type = t_invalid; + if (is_type_untyped(default_type(type))) { + gbString str = type_to_string(type); + error(op.expr, "Cannot determine type from the parameter, got '%s'", str); + gb_string_free(str); + success = false; + type = t_invalid; + } } } @@ -1967,7 +1968,7 @@ gb_internal Type *check_get_results(CheckerContext *ctx, Scope *scope, Ast *_res // NOTE(bill): 'operands' is for generating non generic procedure type -gb_internal bool check_procedure_type(CheckerContext *ctx, Type *type, Ast *proc_type_node, Array *operands) { +gb_internal bool check_procedure_type(CheckerContext *ctx, Type *type, Ast *proc_type_node, Array const *operands) { ast_node(pt, ProcType, proc_type_node); if (ctx->polymorphic_scope == nullptr && ctx->allow_polymorphic_types) { diff --git a/src/types.cpp b/src/types.cpp index 1223132e3..385ca926d 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -2123,7 +2123,6 @@ gb_internal bool is_type_polymorphic(Type *t, bool or_specialized=false) { if (t->Proc.is_polymorphic) { return true; } - #if 1 if (t->Proc.param_count > 0 && is_type_polymorphic(t->Proc.params, or_specialized)) { return true; @@ -2132,7 +2131,6 @@ gb_internal bool is_type_polymorphic(Type *t, bool or_specialized=false) { is_type_polymorphic(t->Proc.results, or_specialized)) { return true; } - #endif break; case Type_Enum: From 3c5661b51b9da1d3017bf9d0082418627d832884 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 20 Jun 2023 20:29:40 +0100 Subject: [PATCH 40/97] Allow for positional and named arguments in procedure calls --- src/check_expr.cpp | 1440 +++++++------------------------------------- 1 file changed, 234 insertions(+), 1206 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index ac3886268..05fd51119 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -67,11 +67,6 @@ gb_internal int valid_index_and_score_cmp(void const *a, void const *b) { -#define CALL_ARGUMENT_CHECKER(name) CallArgumentError name(CheckerContext *c, Ast *call, Type *proc_type, Entity *entity, Array positional_operands, Array const &named_operands, CallArgumentErrorMode show_error_mode, CallArgumentData *data) -typedef CALL_ARGUMENT_CHECKER(CallArgumentCheckerType); - - - gb_internal void check_expr (CheckerContext *c, Operand *operand, Ast *expression); gb_internal void check_multi_expr (CheckerContext *c, Operand *operand, Ast *expression); gb_internal void check_multi_expr_or_type (CheckerContext *c, Operand *operand, Ast *expression); @@ -5336,8 +5331,7 @@ gb_internal isize lookup_procedure_parameter(Type *type, String const ¶meter return lookup_procedure_parameter(&type->Proc, parameter_name); } - -gb_internal CALL_ARGUMENT_CHECKER(check_call_arguments_internal) { +gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, Ast *call, Type *proc_type, Entity *entity, Array positional_operands, Array const &named_operands, CallArgumentErrorMode show_error_mode, CallArgumentData *data) { TEMPORARY_ALLOCATOR_GUARD(); CallArgumentError err = CallArgumentError_None; @@ -5373,517 +5367,300 @@ gb_internal CALL_ARGUMENT_CHECKER(check_call_arguments_internal) { err = CallArgumentError_NonVariadicExpand; } - if (ce->split_args) { - auto visited = slice_make(temporary_allocator(), pt->param_count); - auto ordered_operands = array_make(temporary_allocator(), pt->param_count); - defer ({ - for (Operand const &o : ordered_operands) { - if (o.expr != nullptr) { - call->viral_state_flags |= o.expr->viral_state_flags; - } - } - }); - - if (check_order_of_call_arguments(c, proc_type, call, show_error)) { - return CallArgumentError_OutOfOrderParameters; - } - - isize positional_operand_count = positional_operands.count; - if (variadic) { - positional_operand_count = gb_min(positional_operands.count, pt->variadic_index); - } else if (positional_operand_count > pt->param_count) { - err = CallArgumentError_TooManyArguments; - char const *err_fmt = "Too many arguments for '%s', expected %td arguments, got %td"; - if (show_error) { - gbString proc_str = expr_to_string(ce->proc); - defer (gb_string_free(proc_str)); - error(call, err_fmt, proc_str, param_count_excluding_defaults, positional_operands.count); - } - return err; - } - positional_operand_count = gb_min(positional_operand_count, pt->param_count); - - for (isize i = 0; i < positional_operand_count; i++) { - ordered_operands[i] = positional_operands[i]; - visited[i] = true; - } - - auto variadic_operands = slice(slice_from_array(positional_operands), positional_operand_count, positional_operands.count); - - if (named_operands.count != 0) { - GB_ASSERT(ce->split_args->named.count == named_operands.count); - for_array(i, ce->split_args->named) { - Ast *arg = ce->split_args->named[i]; - Operand operand = named_operands[i]; - - ast_node(fv, FieldValue, arg); - if (fv->field->kind != Ast_Ident) { - if (show_error) { - gbString expr_str = expr_to_string(fv->field); - error(arg, "Invalid parameter name '%s' in procedure call", expr_str); - gb_string_free(expr_str); - } - err = CallArgumentError_InvalidFieldValue; - continue; - } - String name = fv->field->Ident.token.string; - isize param_index = lookup_procedure_parameter(pt, name); - if (param_index < 0) { - if (show_error) { - error(arg, "No parameter named '%.*s' for this procedure type", LIT(name)); - } - err = CallArgumentError_ParameterNotFound; - continue; - } - if (visited[param_index]) { - if (show_error) { - error(arg, "Duplicate parameter '%.*s' in procedure call", LIT(name)); - } - err = CallArgumentError_DuplicateParameter; - continue; - } - Entity *e = pt->params->Tuple.variables[param_index]; - - check_assignment(c, &operand, e->type, str_lit("named argument")); - - visited[param_index] = true; - ordered_operands[param_index] = operand; - - err = CallArgumentError_DuplicateParameter; - } - } - - if (variadic) { - if (visited[pt->variadic_index] && - positional_operand_count < positional_operands.count) { - if (show_error) { - String name = pt->params->Tuple.variables[pt->variadic_index]->token.string; - error(call, "Variadic parameters already handled with a named argument '%.*s' in procedure call", LIT(name)); - } - err = CallArgumentError_DuplicateParameter; - } else { - visited[pt->variadic_index] = true; - - if (vari_expand) { - GB_ASSERT(variadic_operands.count != 0); - ordered_operands[pt->variadic_index] = variadic_operands[0]; - } else { - AstFile *f = call->file(); - - // HACK(bill): this is an awful hack - Operand o = {}; - o.mode = Addressing_Value; - o.expr = ast_ident(f, make_token_ident("nil")); - o.expr->Ident.token.pos = ast_token(call).pos; - if (variadic_operands.count != 0) { - o.expr->Ident.token.pos = ast_token(variadic_operands[0].expr).pos; - - Entity *vt = pt->params->Tuple.variables[pt->variadic_index]; - if (is_type_polymorphic(vt->type)) { - o.type = alloc_type_slice(default_type(variadic_operands[0].type)); - } else { - o.type = vt->type; - } - } else { - o.type = t_untyped_nil; - } - ordered_operands[pt->variadic_index] = o; - } - } - - } - + GB_ASSERT(ce->split_args); + auto visited = slice_make(temporary_allocator(), pt->param_count); + auto ordered_operands = array_make(temporary_allocator(), pt->param_count); + defer ({ for (Operand const &o : ordered_operands) { - if (o.mode != Addressing_Invalid) { - check_no_copy_assignment(o, str_lit("procedure call expression")); + if (o.expr != nullptr) { + call->viral_state_flags |= o.expr->viral_state_flags; } } + }); - for (isize i = 0; i < pt->param_count; i++) { - if (!visited[i]) { - Entity *e = pt->params->Tuple.variables[i]; - if (is_blank_ident(e->token)) { - continue; - } - if (e->kind == Entity_Variable) { - if (e->Variable.param_value.kind != ParameterValue_Invalid) { - score += assign_score_function(1); - continue; - } - } + if (check_order_of_call_arguments(c, proc_type, call, show_error)) { + return CallArgumentError_OutOfOrderParameters; + } + isize positional_operand_count = positional_operands.count; + if (variadic) { + positional_operand_count = gb_min(positional_operands.count, pt->variadic_index); + } else if (positional_operand_count > pt->param_count) { + err = CallArgumentError_TooManyArguments; + char const *err_fmt = "Too many arguments for '%s', expected %td arguments, got %td"; + if (show_error) { + gbString proc_str = expr_to_string(ce->proc); + defer (gb_string_free(proc_str)); + error(call, err_fmt, proc_str, param_count_excluding_defaults, positional_operands.count); + } + return err; + } + positional_operand_count = gb_min(positional_operand_count, pt->param_count); + + for (isize i = 0; i < positional_operand_count; i++) { + ordered_operands[i] = positional_operands[i]; + visited[i] = true; + } + + auto variadic_operands = slice(slice_from_array(positional_operands), positional_operand_count, positional_operands.count); + + if (named_operands.count != 0) { + GB_ASSERT(ce->split_args->named.count == named_operands.count); + for_array(i, ce->split_args->named) { + Ast *arg = ce->split_args->named[i]; + Operand operand = named_operands[i]; + + ast_node(fv, FieldValue, arg); + if (fv->field->kind != Ast_Ident) { if (show_error) { - if (e->kind == Entity_TypeName) { - error(call, "Type parameter '%.*s' is missing in procedure call", - LIT(e->token.string)); - } else if (e->kind == Entity_Constant && e->Constant.value.kind != ExactValue_Invalid) { - // Ignore - } else { - gbString str = type_to_string(e->type); - error(call, "Parameter '%.*s' of type '%s' is missing in procedure call", - LIT(e->token.string), str); - gb_string_free(str); - } + gbString expr_str = expr_to_string(fv->field); + error(arg, "Invalid parameter name '%s' in procedure call", expr_str); + gb_string_free(expr_str); } - err = CallArgumentError_ParameterMissing; + err = CallArgumentError_InvalidFieldValue; + continue; + } + String name = fv->field->Ident.token.string; + isize param_index = lookup_procedure_parameter(pt, name); + if (param_index < 0) { + if (show_error) { + error(arg, "No parameter named '%.*s' for this procedure type", LIT(name)); + } + err = CallArgumentError_ParameterNotFound; + continue; + } + if (visited[param_index]) { + if (show_error) { + error(arg, "Duplicate parameter '%.*s' in procedure call", LIT(name)); + } + err = CallArgumentError_DuplicateParameter; + continue; + } + Entity *e = pt->params->Tuple.variables[param_index]; + + check_assignment(c, &operand, e->type, str_lit("named argument")); + + visited[param_index] = true; + ordered_operands[param_index] = operand; + + err = CallArgumentError_DuplicateParameter; + } + } + + if (variadic) { + if (visited[pt->variadic_index] && + positional_operand_count < positional_operands.count) { + if (show_error) { + String name = pt->params->Tuple.variables[pt->variadic_index]->token.string; + error(call, "Variadic parameters already handled with a named argument '%.*s' in procedure call", LIT(name)); + } + err = CallArgumentError_DuplicateParameter; + } else { + visited[pt->variadic_index] = true; + + if (vari_expand) { + GB_ASSERT(variadic_operands.count != 0); + ordered_operands[pt->variadic_index] = variadic_operands[0]; + } else { + AstFile *f = call->file(); + + // HACK(bill): this is an awful hack + Operand o = {}; + o.mode = Addressing_Value; + o.expr = ast_ident(f, make_token_ident("nil")); + o.expr->Ident.token.pos = ast_token(call).pos; + if (variadic_operands.count != 0) { + o.expr->Ident.token.pos = ast_token(variadic_operands[0].expr).pos; + + Entity *vt = pt->params->Tuple.variables[pt->variadic_index]; + if (is_type_polymorphic(vt->type)) { + o.type = alloc_type_slice(default_type(variadic_operands[0].type)); + } else { + o.type = vt->type; + } + } else { + o.type = t_untyped_nil; + } + ordered_operands[pt->variadic_index] = o; } } - if (ordered_operands.count == 0 && param_count_excluding_defaults == 0) { - err = CallArgumentError_None; + } - if (variadic) { - GB_ASSERT(pt->params != nullptr && pt->params->Tuple.variables.count > 0); - Type *t = pt->params->Tuple.variables[0]->type; - if (is_type_polymorphic(t)) { - error(call, "Ambiguous call to a polymorphic variadic procedure with no variadic input"); - err = CallArgumentError_AmbiguousPolymorphicVariadic; - } + for (Operand const &o : ordered_operands) { + if (o.mode != Addressing_Invalid) { + check_no_copy_assignment(o, str_lit("procedure call expression")); + } + } + + for (isize i = 0; i < pt->param_count; i++) { + if (!visited[i]) { + Entity *e = pt->params->Tuple.variables[i]; + if (is_blank_ident(e->token)) { + continue; } - } else { - if (pt->is_polymorphic && !pt->is_poly_specialized && err == CallArgumentError_None) { - PolyProcData poly_proc_data = {}; - if (find_or_generate_polymorphic_procedure_from_parameters(c, entity, &ordered_operands, call, &poly_proc_data)) { - gen_entity = poly_proc_data.gen_entity; - Type *gept = base_type(gen_entity->type); - GB_ASSERT(is_type_proc(gept)); - final_proc_type = gen_entity->type; - pt = &gept->Proc; - - } else { - err = CallArgumentError_WrongTypes; - } - } - - for (isize i = 0; i < pt->param_count; i++) { - Operand *o = &ordered_operands[i]; - if (o->mode == Addressing_Invalid) { + if (e->kind == Entity_Variable) { + if (e->Variable.param_value.kind != ParameterValue_Invalid) { + ordered_operands[i].mode = Addressing_Value; + ordered_operands[i].type = e->type; + ordered_operands[i].expr = e->Variable.param_value.original_ast_expr; + score += assign_score_function(1); continue; } + } - Entity *e = pt->params->Tuple.variables[i]; - bool param_is_variadic = pt->variadic && pt->variadic_index == i; - + if (show_error) { if (e->kind == Entity_TypeName) { - GB_ASSERT(pt->is_polymorphic); - if (o->mode != Addressing_Type) { - if (show_error) { - error(o->expr, "Expected a type for the argument '%.*s'", LIT(e->token.string)); - } - err = CallArgumentError_WrongTypes; - } - if (are_types_identical(e->type, o->type)) { - score += assign_score_function(1); - } else { - score += assign_score_function(MAXIMUM_TYPE_DISTANCE); - } + error(call, "Type parameter '%.*s' is missing in procedure call", + LIT(e->token.string)); + } else if (e->kind == Entity_Constant && e->Constant.value.kind != ExactValue_Invalid) { + // Ignore } else { - i64 s = 0; - if (!check_is_assignable_to_with_score(c, o, e->type, &s, param_is_variadic)) { - bool ok = false; - if (e->flags & EntityFlag_AnyInt) { - if (is_type_integer(e->type)) { - ok = check_is_castable_to(c, o, e->type); - } - } - if (ok) { - s = assign_score_function(MAXIMUM_TYPE_DISTANCE); - } else { - if (show_error) { - check_assignment(c, o, e->type, str_lit("procedure argument")); - } - err = CallArgumentError_WrongTypes; - } - - if (e->flags & EntityFlag_ConstInput) { - if (o->mode != Addressing_Constant) { - if (show_error) { - error(o->expr, "Expected a constant value for the argument '%.*s'", LIT(e->token.string)); - } - err = CallArgumentError_NoneConstantParameter; - } - } - } else if (show_error) { - check_assignment(c, o, e->type, str_lit("procedure argument")); - } - score += s; - } - - if (o->mode == Addressing_Type && is_type_typeid(e->type)) { - add_type_info_type(c, o->type); - add_type_and_value(c, o->expr, Addressing_Value, e->type, exact_value_typeid(o->type)); - } else if (show_error && is_type_untyped(o->type)) { - update_untyped_expr_type(c, o->expr, e->type, true); + gbString str = type_to_string(e->type); + error(call, "Parameter '%.*s' of type '%s' is missing in procedure call", + LIT(e->token.string), str); + gb_string_free(str); } } + err = CallArgumentError_ParameterMissing; } - - if (variadic) { - Type *slice = pt->params->Tuple.variables[pt->variadic_index]->type; - GB_ASSERT(is_type_slice(slice)); - Type *elem = base_type(slice)->Slice.elem; - Type *t = elem; - - if (is_type_polymorphic(t)) { - error(call, "Ambiguous call to a polymorphic variadic procedure with no variadic input %s", type_to_string(final_proc_type)); - err = CallArgumentError_AmbiguousPolymorphicVariadic; - } - - for_array(operand_index, variadic_operands) { - Operand &o = variadic_operands[operand_index]; - if (vari_expand) { - t = slice; - if (operand_index > 0) { - if (show_error) { - error(o.expr, "'..' in a variadic procedure can only have one variadic argument at the end"); - } - if (data) { - data->score = score; - data->result_type = final_proc_type->Proc.results; - data->gen_entity = gen_entity; - } - return CallArgumentError_MultipleVariadicExpand; - } - } - i64 s = 0; - if (!check_is_assignable_to_with_score(c, &o, t, &s, true)) { - if (show_error) { - check_assignment(c, &o, t, str_lit("variadic argument")); - } - err = CallArgumentError_WrongTypes; - } else if (show_error) { - check_assignment(c, &o, t, str_lit("variadic argument")); - } - score += s; - if (is_type_any(elem)) { - add_type_info_type(c, o.type); - } - if (o.mode == Addressing_Type && is_type_typeid(t)) { - add_type_info_type(c, o.type); - add_type_and_value(c, o.expr, Addressing_Value, t, exact_value_typeid(o.type)); - } else if (show_error && is_type_untyped(o.type)) { - update_untyped_expr_type(c, o.expr, t, true); - } - } - } - - if (data) { - data->score = score; - data->result_type = final_proc_type->Proc.results; - data->gen_entity = gen_entity; - - - Ast *proc_lit = nullptr; - if (ce->proc->tav.value.kind == ExactValue_Procedure) { - Ast *vp = unparen_expr(ce->proc->tav.value.value_procedure); - if (vp && vp->kind == Ast_ProcLit) { - proc_lit = vp; - } - } - if (proc_lit == nullptr) { - add_type_and_value(c, ce->proc, Addressing_Value, final_proc_type, {}); - } - } - - return err; } - if (err) { - return err; - } - - auto operands = positional_operands; - TypeTuple *param_tuple = nullptr; - if (pt->params != nullptr) { - param_tuple = &pt->params->Tuple; - } - - if (operands.count == 0 && param_count_excluding_defaults == 0) { + if (ordered_operands.count == 0 && param_count_excluding_defaults == 0) { err = CallArgumentError_None; if (variadic) { - GB_ASSERT(param_tuple != nullptr && param_tuple->variables.count > 0); - Type *t = param_tuple->variables[0]->type; + GB_ASSERT(pt->params != nullptr && pt->params->Tuple.variables.count > 0); + Type *t = pt->params->Tuple.variables[0]->type; if (is_type_polymorphic(t)) { error(call, "Ambiguous call to a polymorphic variadic procedure with no variadic input"); err = CallArgumentError_AmbiguousPolymorphicVariadic; } } } else { - i32 error_code = 0; - if (operands.count < param_count_excluding_defaults) { - error_code = -1; - } else if (!variadic && operands.count > param_count) { - error_code = +1; + if (pt->is_polymorphic && !pt->is_poly_specialized && err == CallArgumentError_None) { + PolyProcData poly_proc_data = {}; + if (find_or_generate_polymorphic_procedure_from_parameters(c, entity, &ordered_operands, call, &poly_proc_data)) { + gen_entity = poly_proc_data.gen_entity; + Type *gept = base_type(gen_entity->type); + GB_ASSERT(is_type_proc(gept)); + final_proc_type = gen_entity->type; + pt = &gept->Proc; + + } else { + err = CallArgumentError_WrongTypes; + } } - if (error_code != 0) { - err = CallArgumentError_TooManyArguments; - char const *err_fmt = "Too many arguments for '%s', expected %td arguments, got %td"; - if (error_code < 0) { - err = CallArgumentError_TooFewArguments; - err_fmt = "Too few arguments for '%s', expected %td arguments, got %td"; + + for (isize i = 0; i < pt->param_count; i++) { + Operand *o = &ordered_operands[i]; + if (o->mode == Addressing_Invalid) { + continue; } - if (show_error) { - gbString proc_str = expr_to_string(ce->proc); - defer (gb_string_free(proc_str)); - error(call, err_fmt, proc_str, param_count_excluding_defaults, operands.count); + Entity *e = pt->params->Tuple.variables[i]; + bool param_is_variadic = pt->variadic && pt->variadic_index == i; - #if 0 - error_line("\t"); - for_array(i, operands) { - if (i > 0) { - error_line(", "); + if (e->kind == Entity_TypeName) { + GB_ASSERT(pt->is_polymorphic); + if (o->mode != Addressing_Type) { + if (show_error) { + error(o->expr, "Expected a type for the argument '%.*s'", LIT(e->token.string)); } - gbString s = expr_to_string(operands[i].expr); - error_line("%s", s); - gb_string_free(s); - } - error_line("\n"); - #endif - } - } else { - // NOTE(bill): Generate the procedure type for this generic instance - if (pt->is_polymorphic && !pt->is_poly_specialized) { - PolyProcData poly_proc_data = {}; - if (find_or_generate_polymorphic_procedure_from_parameters(c, entity, &operands, call, &poly_proc_data)) { - gen_entity = poly_proc_data.gen_entity; - GB_ASSERT(is_type_proc(gen_entity->type)); - final_proc_type = gen_entity->type; - } else { err = CallArgumentError_WrongTypes; } - } - - GB_ASSERT(is_type_proc(final_proc_type)); - TypeProc *pt = &final_proc_type->Proc; - - GB_ASSERT(pt->params != nullptr); - auto sig_params = pt->params->Tuple.variables; - isize operand_index = 0; - isize max_operand_count = gb_min(param_count, operands.count); - for (; operand_index < max_operand_count; operand_index++) { - Entity *e = sig_params[operand_index]; - Type *t = e->type; - Operand o = operands[operand_index]; - if (o.expr != nullptr) { - call->viral_state_flags |= o.expr->viral_state_flags; + if (are_types_identical(e->type, o->type)) { + score += assign_score_function(1); + } else { + score += assign_score_function(MAXIMUM_TYPE_DISTANCE); } - - if (e->kind == Entity_TypeName) { - // GB_ASSERT(!variadic); - if (o.mode == Addressing_Invalid) { - continue; - } else if (o.mode != Addressing_Type) { - if (show_error) { - error(o.expr, "Expected a type for the argument '%.*s'", LIT(e->token.string)); - } - err = CallArgumentError_WrongTypes; - } - - if (are_types_identical(e->type, o.type)) { - score += assign_score_function(1); - } else { - score += assign_score_function(MAXIMUM_TYPE_DISTANCE); - } - - continue; - } - - bool param_is_variadic = pt->variadic && pt->variadic_index == operand_index; - + } else { i64 s = 0; - if (!check_is_assignable_to_with_score(c, &o, t, &s, param_is_variadic)) { + if (!check_is_assignable_to_with_score(c, o, e->type, &s, param_is_variadic)) { bool ok = false; if (e->flags & EntityFlag_AnyInt) { - if (is_type_integer(t)) { - ok = check_is_castable_to(c, &o, t); + if (is_type_integer(e->type)) { + ok = check_is_castable_to(c, o, e->type); } } if (ok) { s = assign_score_function(MAXIMUM_TYPE_DISTANCE); } else { if (show_error) { - check_assignment(c, &o, t, str_lit("argument")); + check_assignment(c, o, e->type, str_lit("procedure argument")); } - // TODO(bill, 2021-05-05): Is this incorrect logic to only fail if there is ambiguity for definite? - if (o.mode == Addressing_Invalid) { - err = CallArgumentError_WrongTypes; + err = CallArgumentError_WrongTypes; + } + + if (e->flags & EntityFlag_ConstInput) { + if (o->mode != Addressing_Constant) { + if (show_error) { + error(o->expr, "Expected a constant value for the argument '%.*s'", LIT(e->token.string)); + } + err = CallArgumentError_NoneConstantParameter; } } } else if (show_error) { - check_assignment(c, &o, t, str_lit("argument")); + check_assignment(c, o, e->type, str_lit("procedure argument")); } score += s; - - if (e->flags & EntityFlag_ConstInput) { - if (o.mode != Addressing_Constant) { - if (show_error) { - error(o.expr, "Expected a constant value for the argument '%.*s'", LIT(e->token.string)); - } - err = CallArgumentError_NoneConstantParameter; - } - } - - if (o.mode == Addressing_Type && is_type_typeid(e->type)) { - add_type_info_type(c, o.type); - add_type_and_value(c, o.expr, Addressing_Value, e->type, exact_value_typeid(o.type)); - } else if (show_error && is_type_untyped(o.type)) { - update_untyped_expr_type(c, o.expr, t, true); - } - } - if (variadic) { - bool variadic_expand = false; - Type *slice = sig_params[param_count]->type; - GB_ASSERT(is_type_slice(slice)); - Type *elem = base_type(slice)->Slice.elem; - Type *t = elem; + if (o->mode == Addressing_Type && is_type_typeid(e->type)) { + add_type_info_type(c, o->type); + add_type_and_value(c, o->expr, Addressing_Value, e->type, exact_value_typeid(o->type)); + } else if (show_error && is_type_untyped(o->type)) { + update_untyped_expr_type(c, o->expr, e->type, true); + } + } + } - if (is_type_polymorphic(t)) { - error(call, "Ambiguous call to a polymorphic variadic procedure with no variadic input"); - err = CallArgumentError_AmbiguousPolymorphicVariadic; - } + if (variadic) { + Type *slice = pt->params->Tuple.variables[pt->variadic_index]->type; + GB_ASSERT(is_type_slice(slice)); + Type *elem = base_type(slice)->Slice.elem; + Type *t = elem; - for (; operand_index < operands.count; operand_index++) { - Operand o = operands[operand_index]; - if (vari_expand) { - variadic_expand = true; - t = slice; - if (operand_index != param_count) { - if (show_error) { - error(o.expr, "'..' in a variadic procedure can only have one variadic argument at the end"); - } - if (data) { - data->score = score; - data->result_type = final_proc_type->Proc.results; - data->gen_entity = gen_entity; - } - return CallArgumentError_MultipleVariadicExpand; - } + if (is_type_polymorphic(t)) { + error(call, "Ambiguous call to a polymorphic variadic procedure with no variadic input %s", type_to_string(final_proc_type)); + err = CallArgumentError_AmbiguousPolymorphicVariadic; + } + + for_array(operand_index, variadic_operands) { + Operand &o = variadic_operands[operand_index]; + if (vari_expand) { + t = slice; + if (operand_index > 0) { + if (show_error) { + error(o.expr, "'..' in a variadic procedure can only have one variadic argument at the end"); } - i64 s = 0; - if (!check_is_assignable_to_with_score(c, &o, t, &s, true)) { - if (show_error) { - check_assignment(c, &o, t, str_lit("argument")); - } - err = CallArgumentError_WrongTypes; - } else if (show_error) { - check_assignment(c, &o, t, str_lit("argument")); - } - score += s; - if (is_type_any(elem)) { - add_type_info_type(c, o.type); - } - if (o.mode == Addressing_Type && is_type_typeid(t)) { - add_type_info_type(c, o.type); - add_type_and_value(c, o.expr, Addressing_Value, t, exact_value_typeid(o.type)); - } else if (show_error && is_type_untyped(o.type)) { - update_untyped_expr_type(c, o.expr, t, true); + if (data) { + data->score = score; + data->result_type = final_proc_type->Proc.results; + data->gen_entity = gen_entity; } + return CallArgumentError_MultipleVariadicExpand; } } + i64 s = 0; + if (!check_is_assignable_to_with_score(c, &o, t, &s, true)) { + if (show_error) { + check_assignment(c, &o, t, str_lit("variadic argument")); + } + err = CallArgumentError_WrongTypes; + } else if (show_error) { + check_assignment(c, &o, t, str_lit("variadic argument")); + } + score += s; + if (is_type_any(elem)) { + add_type_info_type(c, o.type); + } + if (o.mode == Addressing_Type && is_type_typeid(t)) { + add_type_info_type(c, o.type); + add_type_and_value(c, o.expr, Addressing_Value, t, exact_value_typeid(o.type)); + } else if (show_error && is_type_untyped(o.type)) { + update_untyped_expr_type(c, o.expr, t, true); + } } } @@ -5917,193 +5694,6 @@ gb_internal bool is_call_expr_field_value(AstCallExpr *ce) { return ce->args[0]->kind == Ast_FieldValue; } -gb_internal CALL_ARGUMENT_CHECKER(check_named_call_arguments) { - auto operands = positional_operands; - ast_node(ce, CallExpr, call); - GB_ASSERT(is_type_proc(proc_type)); - proc_type = base_type(proc_type); - TypeProc *pt = &proc_type->Proc; - - i64 score = 0; - bool show_error = show_error_mode == CallArgumentMode_ShowErrors; - CallArgumentError err = CallArgumentError_None; - - TEMPORARY_ALLOCATOR_GUARD(); - - isize param_count = pt->param_count; - bool *visited = gb_alloc_array(temporary_allocator(), bool, param_count); - auto ordered_operands = array_make(temporary_allocator(), param_count); - defer ({ - for (Operand const &o : ordered_operands) { - if (o.expr != nullptr) { - call->viral_state_flags |= o.expr->viral_state_flags; - } - } - }); - - if (check_order_of_call_arguments(c, proc_type, call, show_error)) { - return CallArgumentError_OutOfOrderParameters; - } - - for_array(i, ce->args) { - Ast *arg = ce->args[i]; - ast_node(fv, FieldValue, arg); - if (fv->field->kind != Ast_Ident) { - if (show_error) { - gbString expr_str = expr_to_string(fv->field); - error(arg, "Invalid parameter name '%s' in procedure call", expr_str); - gb_string_free(expr_str); - } - err = CallArgumentError_InvalidFieldValue; - continue; - } - String name = fv->field->Ident.token.string; - isize index = lookup_procedure_parameter(pt, name); - if (index < 0) { - if (show_error) { - error(arg, "No parameter named '%.*s' for this procedure type", LIT(name)); - } - err = CallArgumentError_ParameterNotFound; - continue; - } - if (visited[index]) { - if (show_error) { - error(arg, "Duplicate parameter '%.*s' in procedure call", LIT(name)); - } - err = CallArgumentError_DuplicateParameter; - continue; - } - - visited[index] = true; - ordered_operands[index] = operands[i]; - } - - // NOTE(bill): Check for default values and missing parameters - isize param_count_to_check = param_count; - if (pt->variadic) { - param_count_to_check--; - } - for (isize i = 0; i < param_count_to_check; i++) { - if (!visited[i]) { - Entity *e = pt->params->Tuple.variables[i]; - if (is_blank_ident(e->token)) { - continue; - } - if (e->kind == Entity_Variable) { - if (e->Variable.param_value.kind != ParameterValue_Invalid) { - score += assign_score_function(1); - continue; - } - } - - if (show_error) { - if (e->kind == Entity_TypeName) { - error(call, "Type parameter '%.*s' is missing in procedure call", - LIT(e->token.string)); - } else if (e->kind == Entity_Constant && e->Constant.value.kind != ExactValue_Invalid) { - // Ignore - } else { - gbString str = type_to_string(e->type); - error(call, "Parameter '%.*s' of type '%s' is missing in procedure call", - LIT(e->token.string), str); - gb_string_free(str); - } - } - err = CallArgumentError_ParameterMissing; - } - } - - Entity *gen_entity = nullptr; - if (pt->is_polymorphic && !pt->is_poly_specialized && err == CallArgumentError_None) { - PolyProcData poly_proc_data = {}; - if (find_or_generate_polymorphic_procedure_from_parameters(c, entity, &ordered_operands, call, &poly_proc_data)) { - gen_entity = poly_proc_data.gen_entity; - Type *gept = base_type(gen_entity->type); - GB_ASSERT(is_type_proc(gept)); - proc_type = gept; - pt = &gept->Proc; - } else { - err = CallArgumentError_WrongTypes; - } - } - - - for (isize i = 0; i < param_count; i++) { - Entity *e = pt->params->Tuple.variables[i]; - Operand *o = &ordered_operands[i]; - bool param_is_variadic = pt->variadic && pt->variadic_index == i; - - - if (o->mode == Addressing_Invalid) { - if (param_is_variadic) { - Type *slice = e->type; - GB_ASSERT(is_type_slice(slice)); - Type *elem = base_type(slice)->Slice.elem; - if (is_type_polymorphic(elem)) { - error(call, "Ambiguous call to a polymorphic variadic procedure with no variadic input"); - err = CallArgumentError_AmbiguousPolymorphicVariadic; - return err; - } - } - continue; - } - - if (e->kind == Entity_TypeName) { - GB_ASSERT(pt->is_polymorphic); - if (o->mode != Addressing_Type) { - if (show_error) { - error(o->expr, "Expected a type for the argument '%.*s'", LIT(e->token.string)); - } - err = CallArgumentError_WrongTypes; - } - if (are_types_identical(e->type, o->type)) { - score += assign_score_function(1); - } else { - score += assign_score_function(MAXIMUM_TYPE_DISTANCE); - } - } else { - i64 s = 0; - if (!check_is_assignable_to_with_score(c, o, e->type, &s, param_is_variadic)) { - bool ok = false; - if (ok) { - s = assign_score_function(MAXIMUM_TYPE_DISTANCE); - } else { - if (show_error) { - check_assignment(c, o, e->type, str_lit("procedure argument")); - } - err = CallArgumentError_WrongTypes; - } - - if (e->flags & EntityFlag_ConstInput) { - if (o->mode != Addressing_Constant) { - if (show_error) { - error(o->expr, "Expected a constant value for the argument '%.*s'", LIT(e->token.string)); - } - err = CallArgumentError_NoneConstantParameter; - } - } - } else if (show_error) { - check_assignment(c, o, e->type, str_lit("procedure argument")); - } - score += s; - } - - if (o->mode == Addressing_Type && is_type_typeid(e->type)) { - add_type_info_type(c, o->type); - add_type_and_value(c, o->expr, Addressing_Value, e->type, exact_value_typeid(o->type)); - } - } - - if (data) { - data->score = score; - data->result_type = pt->results; - data->gen_entity = gen_entity; - add_type_and_value(c, ce->proc, Addressing_Value, proc_type, {}); - } - - return err; -} - gb_internal Entity **populate_proc_parameter_list(CheckerContext *c, Type *proc_type, isize *lhs_count_, bool *is_variadic) { Entity **lhs = nullptr; isize lhs_count = -1; @@ -7014,568 +6604,6 @@ gb_internal CallArgumentData check_call_arguments_new_and_improved(CheckerContex return data; } - -gb_internal CallArgumentData check_call_arguments(CheckerContext *c, Operand *operand, Ast *call) { - if (operand->mode == Addressing_ProcGroup) { - Array procs = proc_group_entities(c, *operand); - bool all_non_poly = true; - for (Entity *e : procs) { - if (is_type_polymorphic(e->type)) { - all_non_poly = false; - break; - } - } - if (true) { - return check_call_arguments_new_and_improved(c, operand, call); - } - } else { - return check_call_arguments_new_and_improved(c, operand, call); - } - - ast_node(ce, CallExpr, call); - - CallArgumentCheckerType *call_checker = check_call_arguments_internal; - Array operands = {}; - defer (array_free(&operands)); - - Type *result_type = t_invalid; - Type *proc_type = base_type(operand->type); - - Slice const &args = ce->args; - if (is_call_expr_field_value(ce)) { - Type *ptype = nullptr; - bool single_case = true; - - if (operand->mode == Addressing_ProcGroup) { - single_case = false; - Array procs = proc_group_entities(c, *operand); - if (procs.count == 1) { - ptype = procs[0]->type; - single_case = true; - } - } else { - ptype = proc_type; - } - - if (check_order_of_call_arguments(c, ptype, call, true)) { - CallArgumentData data = {}; - data.result_type = t_invalid; - if (ptype && is_type_proc(ptype) && !is_type_polymorphic(ptype)) { - data.result_type = reduce_tuple_to_single_type(ptype->Proc.results); - } - return data; - } - - call_checker = check_named_call_arguments; - - operands = array_make(heap_allocator(), args.count); - - // NOTE(bill): This is give type hints for the named parameters - // in order to improve the type inference system - - StringMap type_hint_map = {}; // Key: String - string_map_init(&type_hint_map, 2*args.count); - defer (string_map_destroy(&type_hint_map)); - - - if (single_case) { - Type *bptype = base_type(ptype); - if (is_type_proc(bptype)) { - TypeProc *pt = &bptype->Proc; - TypeTuple *param_tuple = nullptr; - if (pt->params != nullptr) { - param_tuple = &pt->params->Tuple; - } - if (param_tuple != nullptr) { - for (Entity *e : param_tuple->variables) { - if (is_blank_ident(e->token)) { - continue; - } - string_map_set(&type_hint_map, e->token.string, e->type); - } - } - } - } else { - Array procs = proc_group_entities(c, *operand); - for (Entity *proc : procs) { - Type *proc_type = base_type(proc->type); - if (is_type_proc(proc_type)) { - TypeProc *pt = &proc_type->Proc; - TypeTuple *param_tuple = nullptr; - if (pt->params != nullptr) { - param_tuple = &pt->params->Tuple; - } - if (param_tuple == nullptr) { - continue; - } - for (Entity *e : param_tuple->variables) { - if (is_blank_ident(e->token)) { - continue; - } - StringHashKey key = string_hash_string(e->token.string); - Type **found = string_map_get(&type_hint_map, key); - if (found) { - Type *t = *found; - if (t == nullptr) { - // NOTE(bill): Ambiguous named parameter across all types - continue; - } - if (are_types_identical(t, e->type)) { - // NOTE(bill): No need to set again - } else { - // NOTE(bill): Ambiguous named parameter across all types so set it to a nullptr - string_map_set(&type_hint_map, key, cast(Type *)nullptr); - } - } else { - string_map_set(&type_hint_map, key, e->type); - } - } - } - } - - } - - - for_array(i, args) { - Ast *arg = args[i]; - ast_node(fv, FieldValue, arg); - Ast *field = fv->field; - - Type *type_hint = nullptr; - - if (field != nullptr && field->kind == Ast_Ident) { - String key = field->Ident.token.string; - Type **found = string_map_get(&type_hint_map, key); - if (found) { - type_hint = *found; - } - } - check_expr_or_type(c, &operands[i], fv->value, type_hint); - } - } else { - operands = array_make(heap_allocator(), 0, 2*args.count); - Entity **lhs = nullptr; - isize lhs_count = -1; - bool is_variadic = false; - if (proc_type != nullptr && is_type_proc(proc_type)) { - lhs = populate_proc_parameter_list(c, proc_type, &lhs_count, &is_variadic); - } - if (operand->mode != Addressing_ProcGroup) { - check_unpack_arguments(c, lhs, lhs_count, &operands, args, is_variadic ? UnpackFlag_IsVariadic : UnpackFlag_None); - } - } - - for (Operand const &o : operands) { - check_no_copy_assignment(o, str_lit("procedure call expression")); - } - - if (operand->mode == Addressing_ProcGroup) { - check_entity_decl(c, operand->proc_group, nullptr, nullptr); - - auto procs = proc_group_entities_cloned(c, *operand); - - if (procs.count > 1) { - isize max_arg_count = args.count; - for (Ast *arg : args) { - // NOTE(bill): The only thing that may have multiple values - // will be a call expression (assuming `or_return` and `()` will be stripped) - arg = strip_or_return_expr(arg); - if (arg && arg->kind == Ast_CallExpr) { - max_arg_count = ISIZE_MAX; - break; - } - } - - for (isize proc_index = 0; proc_index < procs.count; /**/) { - Entity *proc = procs[proc_index]; - Type *pt = base_type(proc->type); - if (!(pt != nullptr && is_type_proc(pt))) { - proc_index++; - continue; - } - - isize param_count = 0; - isize param_count_excluding_defaults = get_procedure_param_count_excluding_defaults(pt, ¶m_count); - - if (param_count_excluding_defaults > max_arg_count) { - array_unordered_remove(&procs, proc_index); - } else { - proc_index++; - } - } - } - - if (procs.count == 1) { - Ast *ident = operand->expr; - while (ident->kind == Ast_SelectorExpr) { - Ast *s = ident->SelectorExpr.selector; - ident = s; - } - - Entity *e = procs[0]; - - Entity **lhs = nullptr; - isize lhs_count = -1; - bool is_variadic = false; - lhs = populate_proc_parameter_list(c, e->type, &lhs_count, &is_variadic); - check_unpack_arguments(c, lhs, lhs_count, &operands, args, is_variadic ? UnpackFlag_IsVariadic : UnpackFlag_None); - - Array named_operands = {}; - CallArgumentData data = {}; - CallArgumentError err = call_checker(c, call, e->type, e, operands, named_operands, CallArgumentMode_ShowErrors, &data); - if (err != CallArgumentError_None) { - // handle error - } - Entity *entity_to_use = data.gen_entity != nullptr ? data.gen_entity : e; - add_entity_use(c, ident, entity_to_use); - if (entity_to_use != nullptr) { - update_untyped_expr_type(c, operand->expr, entity_to_use->type, true); - } - return data; - } - - - Entity **lhs = nullptr; - isize lhs_count = -1; - - { - // NOTE(bill, 2019-07-13): This code is used to improve the type inference for procedure groups - // where the same positional parameter has the same type value (and ellipsis) - bool proc_arg_count_all_equal = true; - isize proc_arg_count = -1; - for (Entity *p : procs) { - Type *pt = base_type(p->type); - if (pt != nullptr && is_type_proc(pt)) { - if (proc_arg_count < 0) { - proc_arg_count = pt->Proc.param_count; - } else { - if (proc_arg_count != pt->Proc.param_count) { - proc_arg_count_all_equal = false; - break; - } - } - } - } - - - - if (proc_arg_count >= 0 && proc_arg_count_all_equal) { - lhs_count = proc_arg_count; - if (lhs_count > 0) { - lhs = gb_alloc_array(heap_allocator(), Entity *, lhs_count); - for (isize param_index = 0; param_index < lhs_count; param_index++) { - Entity *e = nullptr; - for (Entity *p : procs) { - Type *pt = base_type(p->type); - if (pt != nullptr && is_type_proc(pt)) { - if (e == nullptr) { - e = pt->Proc.params->Tuple.variables[param_index]; - } else { - Entity *f = pt->Proc.params->Tuple.variables[param_index]; - if (e == f) { - continue; - } - if (are_types_identical(e->type, f->type)) { - bool ee = (e->flags & EntityFlag_Ellipsis) != 0; - bool fe = (f->flags & EntityFlag_Ellipsis) != 0; - if (ee == fe) { - continue; - } - } - // NOTE(bill): Entities are not close enough to be used - e = nullptr; - break; - } - } - } - lhs[param_index] = e; - } - } - } - } - - - check_unpack_arguments(c, lhs, lhs_count, &operands, args, UnpackFlag_None); - - if (lhs != nullptr) { - gb_free(heap_allocator(), lhs); - } - - auto valids = array_make(heap_allocator(), 0, procs.count); - defer (array_free(&valids)); - - auto proc_entities = array_make(heap_allocator(), 0, procs.count*2 + 1); - defer (array_free(&proc_entities)); - for (Entity *proc : procs) { - array_add(&proc_entities, proc); - } - - - gbString expr_name = expr_to_string(operand->expr); - defer (gb_string_free(expr_name)); - - for_array(i, procs) { - Entity *p = procs[i]; - Type *pt = base_type(p->type); - if (pt != nullptr && is_type_proc(pt)) { - CallArgumentError err = CallArgumentError_None; - CallArgumentData data = {}; - CheckerContext ctx = *c; - - ctx.no_polymorphic_errors = true; - ctx.allow_polymorphic_types = is_type_polymorphic(pt); - ctx.hide_polymorphic_errors = true; - - Array named_operands = {}; - err = call_checker(&ctx, call, pt, p, operands, named_operands, CallArgumentMode_NoErrors, &data); - if (err != CallArgumentError_None) { - continue; - } - isize index = i; - - if (data.gen_entity != nullptr) { - Entity *e = data.gen_entity; - DeclInfo *decl = data.gen_entity->decl_info; - ctx.scope = decl->scope; - ctx.decl = decl; - ctx.proc_name = e->token.string; - ctx.curr_proc_decl = decl; - ctx.curr_proc_sig = e->type; - - GB_ASSERT(decl->proc_lit->kind == Ast_ProcLit); - if (!evaluate_where_clauses(&ctx, call, decl->scope, &decl->proc_lit->ProcLit.where_clauses, false)) { - continue; - } - - array_add(&proc_entities, data.gen_entity); - index = proc_entities.count-1; - } - - ValidIndexAndScore item = {}; - item.index = index; - item.score = data.score; - array_add(&valids, item); - } - } - - if (valids.count > 1) { - gb_sort_array(valids.data, valids.count, valid_index_and_score_cmp); - i64 best_score = valids[0].score; - Entity *best_entity = proc_entities[valids[0].index]; - GB_ASSERT(best_entity != nullptr); - for (isize i = 1; i < valids.count; i++) { - if (best_score > valids[i].score) { - valids.count = i; - break; - } - if (best_entity == proc_entities[valids[i].index]) { - valids.count = i; - break; - } - } - } - - - if (valids.count == 0) { - begin_error_block(); - defer (end_error_block()); - - error(operand->expr, "No procedures or ambiguous call for procedure group '%s' that match with the given arguments", expr_name); - if (operands.count == 0) { - error_line("\tNo given arguments\n"); - } else { - error_line("\tGiven argument types: ("); - for_array(i, operands) { - Operand o = operands[i]; - if (i > 0) error_line(", "); - gbString type = type_to_string(o.type); - defer (gb_string_free(type)); - error_line("%s", type); - } - error_line(")\n"); - } - - if (procs.count > 0) { - error_line("Did you mean to use one of the following:\n"); - } - for (Entity *proc : procs) { - TokenPos pos = proc->token.pos; - Type *t = base_type(proc->type); - if (t == t_invalid) continue; - GB_ASSERT(t->kind == Type_Proc); - gbString pt; - defer (gb_string_free(pt)); - if (t->Proc.node != nullptr) { - pt = expr_to_string(t->Proc.node); - } else { - pt = type_to_string(t); - } - String prefix = {}; - String prefix_sep = {}; - if (proc->pkg) { - prefix = proc->pkg->name; - prefix_sep = str_lit("."); - } - String name = proc->token.string; - - char const *sep = "::"; - if (proc->kind == Entity_Variable) { - sep = ":="; - } - error_line("\t%.*s%.*s%.*s %s %s at %s\n", LIT(prefix), LIT(prefix_sep), LIT(name), sep, pt, token_pos_to_string(pos)); - } - if (procs.count > 0) { - error_line("\n"); - } - - result_type = t_invalid; - } else if (valids.count > 1) { - begin_error_block(); - defer (end_error_block()); - - error(operand->expr, "Ambiguous procedure group call '%s' that match with the given arguments", expr_name); - error_line("\tGiven argument types: ("); - for_array(i, operands) { - Operand o = operands[i]; - if (i > 0) error_line(", "); - gbString type = type_to_string(o.type); - defer (gb_string_free(type)); - error_line("%s", type); - } - error_line(")\n"); - - for (isize i = 0; i < valids.count; i++) { - Entity *proc = proc_entities[valids[i].index]; - GB_ASSERT(proc != nullptr); - TokenPos pos = proc->token.pos; - Type *t = base_type(proc->type); GB_ASSERT(t->kind == Type_Proc); - gbString pt = nullptr; - defer (gb_string_free(pt)); - if (t->Proc.node != nullptr) { - pt = expr_to_string(t->Proc.node); - } else { - pt = type_to_string(t); - } - String name = proc->token.string; - char const *sep = "::"; - if (proc->kind == Entity_Variable) { - sep = ":="; - } - error_line("\t%.*s %s %s ", LIT(name), sep, pt); - if (proc->decl_info->proc_lit != nullptr) { - GB_ASSERT(proc->decl_info->proc_lit->kind == Ast_ProcLit); - auto *pl = &proc->decl_info->proc_lit->ProcLit; - if (pl->where_token.kind != Token_Invalid) { - error_line("\n\t\twhere "); - for_array(j, pl->where_clauses) { - Ast *clause = pl->where_clauses[j]; - if (j != 0) { - error_line("\t\t "); - } - gbString str = expr_to_string(clause); - error_line("%s", str); - gb_string_free(str); - - if (j != pl->where_clauses.count-1) { - error_line(","); - } - } - error_line("\n\t"); - } - } - error_line("at %s\n", token_pos_to_string(pos)); - } - result_type = t_invalid; - } else { - GB_ASSERT(valids.count == 1); - Ast *ident = operand->expr; - while (ident->kind == Ast_SelectorExpr) { - Ast *s = ident->SelectorExpr.selector; - ident = s; - } - - Entity *e = proc_entities[valids[0].index]; - GB_ASSERT(e != nullptr); - - Array named_operands = {}; - - proc_type = e->type; - CallArgumentData data = {}; - CallArgumentError err = call_checker(c, call, proc_type, e, operands, named_operands, CallArgumentMode_ShowErrors, &data); - gb_unused(err); - Entity *entity_to_use = data.gen_entity != nullptr ? data.gen_entity : e; - add_entity_use(c, ident, entity_to_use); - if (entity_to_use != nullptr) { - update_untyped_expr_type(c, operand->expr, entity_to_use->type, true); - } - - if (data.gen_entity != nullptr) { - Entity *e = data.gen_entity; - DeclInfo *decl = data.gen_entity->decl_info; - CheckerContext ctx = *c; - ctx.scope = decl->scope; - ctx.decl = decl; - ctx.proc_name = e->token.string; - ctx.curr_proc_decl = decl; - ctx.curr_proc_sig = e->type; - - GB_ASSERT(decl->proc_lit->kind == Ast_ProcLit); - bool ok = evaluate_where_clauses(&ctx, call, decl->scope, &decl->proc_lit->ProcLit.where_clauses, true); - decl->where_clauses_evaluated = true; - - if (ok && (data.gen_entity->flags & EntityFlag_ProcBodyChecked) == 0) { - check_procedure_later(c->checker, e->file, e->token, decl, e->type, decl->proc_lit->ProcLit.body, decl->proc_lit->ProcLit.tags); - } - } - return data; - } - } else { - Ast *ident = operand->expr; - while (ident->kind == Ast_SelectorExpr) { - Ast *s = ident->SelectorExpr.selector; - ident = s; - } - - Entity *e = entity_of_node(ident); - - Array named_operands = {}; - - CallArgumentData data = {}; - CallArgumentError err = call_checker(c, call, proc_type, e, operands, named_operands, CallArgumentMode_ShowErrors, &data); - gb_unused(err); - Entity *entity_to_use = data.gen_entity != nullptr ? data.gen_entity : e; - add_entity_use(c, ident, entity_to_use); - if (entity_to_use != nullptr) { - update_untyped_expr_type(c, operand->expr, entity_to_use->type, true); - } - if (data.gen_entity != nullptr) { - Entity *e = data.gen_entity; - DeclInfo *decl = data.gen_entity->decl_info; - CheckerContext ctx = *c; - ctx.scope = decl->scope; - ctx.decl = decl; - ctx.proc_name = e->token.string; - ctx.curr_proc_decl = decl; - ctx.curr_proc_sig = e->type; - - GB_ASSERT(decl->proc_lit->kind == Ast_ProcLit); - bool ok = evaluate_where_clauses(&ctx, call, decl->scope, &decl->proc_lit->ProcLit.where_clauses, true); - decl->where_clauses_evaluated = true; - - if (ok && (data.gen_entity->flags & EntityFlag_ProcBodyChecked) == 0) { - check_procedure_later(c->checker, e->file, e->token, decl, e->type, decl->proc_lit->ProcLit.body, decl->proc_lit->ProcLit.tags); - } - } - return data; - } - - - CallArgumentData data = {}; - data.result_type = t_invalid; - return data; -} - - gb_internal isize lookup_polymorphic_record_parameter(Type *t, String parameter_name) { if (!is_type_polymorphic_record(t)) { return -1; @@ -8136,7 +7164,7 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c } } - CallArgumentData data = check_call_arguments(c, operand, call); + CallArgumentData data = check_call_arguments_new_and_improved(c, operand, call); Type *result_type = data.result_type; gb_zero_item(operand); operand->expr = call; From fb756e346380288ba2a1e14150d9f99619c4c6e8 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 20 Jun 2023 21:02:57 +0100 Subject: [PATCH 41/97] Correct procedure group handling --- src/check_expr.cpp | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 05fd51119..48fdfe871 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -5447,6 +5447,9 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A } } + isize dummy_argument_count = 0; + bool actually_variadic = false; + if (variadic) { if (visited[pt->variadic_index] && positional_operand_count < positional_operands.count) { @@ -5461,6 +5464,7 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A if (vari_expand) { GB_ASSERT(variadic_operands.count != 0); ordered_operands[pt->variadic_index] = variadic_operands[0]; + actually_variadic = true; } else { AstFile *f = call->file(); @@ -5470,6 +5474,7 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A o.expr = ast_ident(f, make_token_ident("nil")); o.expr->Ident.token.pos = ast_token(call).pos; if (variadic_operands.count != 0) { + actually_variadic = true; o.expr->Ident.token.pos = ast_token(variadic_operands[0].expr).pos; Entity *vt = pt->params->Tuple.variables[pt->variadic_index]; @@ -5479,6 +5484,7 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A o.type = vt->type; } } else { + dummy_argument_count += 1; o.type = t_untyped_nil; } ordered_operands[pt->variadic_index] = o; @@ -5504,6 +5510,8 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A ordered_operands[i].mode = Addressing_Value; ordered_operands[i].type = e->type; ordered_operands[i].expr = e->Variable.param_value.original_ast_expr; + + dummy_argument_count += 1; score += assign_score_function(1); continue; } @@ -5603,7 +5611,9 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A } else if (show_error) { check_assignment(c, o, e->type, str_lit("procedure argument")); } - score += s; + if (!param_is_variadic) { + score += s; + } } if (o->mode == Addressing_Type && is_type_typeid(e->type)) { @@ -6070,9 +6080,10 @@ gb_internal CallArgumentData check_call_arguments_new_and_improved_proc_group(Ch CallArgumentError err = check_call_arguments_internal(c, call, e->type, e, positional_operands, named_operands, CallArgumentMode_ShowErrors, &data); if (err != CallArgumentError_None) { - // handle error + } + Type *proc_type = base_type(operand->type); Entity *entity_to_use = data.gen_entity != nullptr ? data.gen_entity : e; @@ -6086,8 +6097,6 @@ gb_internal CallArgumentData check_call_arguments_new_and_improved_proc_group(Ch if (proc_type && proc_type->kind == Type_Proc) { data.result_type = proc_type->Proc.results; add_type_and_value(c, operand->expr, operand->mode, proc_type, operand->value); - } else if (err == CallArgumentError_None) { - data.result_type = nullptr; } if (data.gen_entity != nullptr) { @@ -6108,6 +6117,7 @@ gb_internal CallArgumentData check_call_arguments_new_and_improved_proc_group(Ch check_procedure_later(c->checker, e->file, e->token, decl, e->type, decl->proc_lit->ProcLit.body, decl->proc_lit->ProcLit.tags); } } + return data; } @@ -6419,6 +6429,7 @@ gb_internal CallArgumentData check_call_arguments_new_and_improved_proc_group(Ch Entity *entity_to_use = data.gen_entity != nullptr ? data.gen_entity : e; add_entity_use(c, ident, entity_to_use); if (entity_to_use != nullptr) { + proc_type = base_type(entity_to_use->type); update_untyped_expr_type(c, operand->expr, entity_to_use->type, true); add_type_and_value(c, operand->expr, operand->mode, entity_to_use->type, operand->value); } @@ -6441,6 +6452,11 @@ gb_internal CallArgumentData check_call_arguments_new_and_improved_proc_group(Ch check_procedure_later(c->checker, e->file, e->token, decl, e->type, decl->proc_lit->ProcLit.body, decl->proc_lit->ProcLit.tags); } } + + if (is_type_proc(proc_type)) { + data.result_type = proc_type->Proc.results; + } + return data; } From 2a002c3882ab0c82d5d9553da7926532e832ef4e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 20 Jun 2023 22:55:33 +0100 Subject: [PATCH 42/97] Fix scoring for untyped ternary expressions --- src/check_expr.cpp | 46 ++++++++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 48fdfe871..2e5b23e78 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -700,23 +700,34 @@ gb_internal i64 check_distance_between_types(CheckerContext *c, Operand *operand } return -1; } - if (src->kind == Type_Basic && src->Basic.kind == Basic_UntypedRune) { - if (is_type_integer(dst) || is_type_rune(dst)) { - if (is_type_typed(type)) { - return 2; + if (src->kind == Type_Basic) { + switch (src->Basic.kind) { + case Basic_UntypedRune: + if (is_type_integer(dst) || is_type_rune(dst)) { + if (is_type_typed(type)) { + return 2; + } + return 1; } - return 1; - } - return -1; - } - if (src->kind == Type_Basic && src->Basic.kind == Basic_UntypedBool) { - if (is_type_boolean(dst)) { - if (is_type_typed(type)) { - return 2; + return -1; + case Basic_UntypedBool: + if (is_type_boolean(dst)) { + if (is_type_typed(type)) { + return 2; + } + return 1; } - return 1; + return -1; + case Basic_UntypedString: + if (is_type_string(dst) || is_type_cstring(dst)) { + if (is_type_typed(type)) { + return 2; + } + return 1; + } + return -1; } - return -1; + } } } @@ -5461,9 +5472,12 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A } else { visited[pt->variadic_index] = true; + Operand *variadic_operand = &ordered_operands[pt->variadic_index]; + if (vari_expand) { GB_ASSERT(variadic_operands.count != 0); - ordered_operands[pt->variadic_index] = variadic_operands[0]; + *variadic_operand = variadic_operands[0]; + variadic_operand->type = default_type(variadic_operand->type); actually_variadic = true; } else { AstFile *f = call->file(); @@ -5487,7 +5501,7 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A dummy_argument_count += 1; o.type = t_untyped_nil; } - ordered_operands[pt->variadic_index] = o; + *variadic_operand = o; } } From 3998d0c81e2d6aa408b6fd143af1b569d8e69d71 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 20 Jun 2023 22:55:47 +0100 Subject: [PATCH 43/97] Make error checks diverging where possible --- core/runtime/error_checks.odin | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/core/runtime/error_checks.odin b/core/runtime/error_checks.odin index a667b9065..c189642af 100644 --- a/core/runtime/error_checks.odin +++ b/core/runtime/error_checks.odin @@ -22,7 +22,7 @@ bounds_check_error :: proc "contextless" (file: string, line, column: i32, index return } @(cold) - handle_error :: proc "contextless" (file: string, line, column: i32, index, count: int) { + handle_error :: proc "contextless" (file: string, line, column: i32, index, count: int) -> ! { print_caller_location(Source_Code_Location{file, line, column, ""}) print_string(" Index ") print_i64(i64(index)) @@ -83,7 +83,7 @@ dynamic_array_expr_error :: proc "contextless" (file: string, line, column: i32, return } @(cold) - handle_error :: proc "contextless" (file: string, line, column: i32, low, high, max: int) { + handle_error :: proc "contextless" (file: string, line, column: i32, low, high, max: int) -> ! { print_caller_location(Source_Code_Location{file, line, column, ""}) print_string(" Invalid dynamic array indices ") print_i64(i64(low)) @@ -104,7 +104,7 @@ matrix_bounds_check_error :: proc "contextless" (file: string, line, column: i32 return } @(cold) - handle_error :: proc "contextless" (file: string, line, column: i32, row_index, column_index, row_count, column_count: int) { + handle_error :: proc "contextless" (file: string, line, column: i32, row_index, column_index, row_count, column_count: int) -> ! { print_caller_location(Source_Code_Location{file, line, column, ""}) print_string(" Matrix indices [") print_i64(i64(row_index)) @@ -128,7 +128,7 @@ when ODIN_NO_RTTI { return } @(cold) - handle_error :: proc "contextless" (file: string, line, column: i32) { + handle_error :: proc "contextless" (file: string, line, column: i32) -> ! { print_caller_location(Source_Code_Location{file, line, column, ""}) print_string(" Invalid type assertion\n") type_assertion_trap() @@ -141,7 +141,7 @@ when ODIN_NO_RTTI { return } @(cold) - handle_error :: proc "contextless" (file: string, line, column: i32) { + handle_error :: proc "contextless" (file: string, line, column: i32) -> ! { print_caller_location(Source_Code_Location{file, line, column, ""}) print_string(" Invalid type assertion\n") type_assertion_trap() @@ -154,7 +154,7 @@ when ODIN_NO_RTTI { return } @(cold) - handle_error :: proc "contextless" (file: string, line, column: i32, from, to: typeid) { + handle_error :: proc "contextless" (file: string, line, column: i32, from, to: typeid) -> ! { print_caller_location(Source_Code_Location{file, line, column, ""}) print_string(" Invalid type assertion from ") print_typeid(from) @@ -199,7 +199,7 @@ when ODIN_NO_RTTI { } @(cold) - handle_error :: proc "contextless" (file: string, line, column: i32, from, to: typeid, from_data: rawptr) { + handle_error :: proc "contextless" (file: string, line, column: i32, from, to: typeid, from_data: rawptr) -> ! { actual := variant_type(from, from_data) @@ -225,7 +225,7 @@ make_slice_error_loc :: #force_inline proc "contextless" (loc := #caller_locatio return } @(cold) - handle_error :: proc "contextless" (loc: Source_Code_Location, len: int) { + handle_error :: proc "contextless" (loc: Source_Code_Location, len: int) -> ! { print_caller_location(loc) print_string(" Invalid slice length for make: ") print_i64(i64(len)) @@ -240,7 +240,7 @@ make_dynamic_array_error_loc :: #force_inline proc "contextless" (using loc := # return } @(cold) - handle_error :: proc "contextless" (loc: Source_Code_Location, len, cap: int) { + handle_error :: proc "contextless" (loc: Source_Code_Location, len, cap: int) -> ! { print_caller_location(loc) print_string(" Invalid dynamic array parameters for make: ") print_i64(i64(len)) @@ -257,7 +257,7 @@ make_map_expr_error_loc :: #force_inline proc "contextless" (loc := #caller_loca return } @(cold) - handle_error :: proc "contextless" (loc: Source_Code_Location, cap: int) { + handle_error :: proc "contextless" (loc: Source_Code_Location, cap: int) -> ! { print_caller_location(loc) print_string(" Invalid map capacity for make: ") print_i64(i64(cap)) From 09f366bec72087fb8ed835cda6d2b8cc115627e4 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 21 Jun 2023 00:03:56 +0100 Subject: [PATCH 44/97] Correct purely named argument handling --- src/check_expr.cpp | 181 ++++++++++++++------------- src/llvm_backend_proc.cpp | 250 +------------------------------------- 2 files changed, 99 insertions(+), 332 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 2e5b23e78..5b1f1a73c 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -676,6 +676,11 @@ gb_internal i64 check_distance_between_types(CheckerContext *c, Operand *operand return 1; } break; + case Basic_UntypedString: + if (is_type_string(dst)) { + return 1; + } + break; case Basic_UntypedFloat: if (is_type_float(dst)) { return 1; @@ -701,33 +706,48 @@ gb_internal i64 check_distance_between_types(CheckerContext *c, Operand *operand return -1; } if (src->kind == Type_Basic) { + i64 score = -1; switch (src->Basic.kind) { case Basic_UntypedRune: if (is_type_integer(dst) || is_type_rune(dst)) { - if (is_type_typed(type)) { - return 2; - } - return 1; + score = 1; } - return -1; - case Basic_UntypedBool: - if (is_type_boolean(dst)) { - if (is_type_typed(type)) { - return 2; - } - return 1; + break; + case Basic_UntypedInteger: + if (is_type_integer(dst) || is_type_rune(dst)) { + score = 1; } - return -1; + break; case Basic_UntypedString: - if (is_type_string(dst) || is_type_cstring(dst)) { - if (is_type_typed(type)) { - return 2; - } - return 1; + if (is_type_string(dst)) { + score = 1; } - return -1; + break; + case Basic_UntypedFloat: + if (is_type_float(dst)) { + score = 1; + } + break; + case Basic_UntypedComplex: + if (is_type_complex(dst)) { + score = 1; + } + if (is_type_quaternion(dst)) { + score = 2; + } + break; + case Basic_UntypedQuaternion: + if (is_type_quaternion(dst)) { + score = 1; + } + break; } - + if (score > 0) { + if (is_type_typed(dst)) { + score += 1; + } + } + return score; } } } @@ -5447,14 +5467,9 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A err = CallArgumentError_DuplicateParameter; continue; } - Entity *e = pt->params->Tuple.variables[param_index]; - - check_assignment(c, &operand, e->type, str_lit("named argument")); visited[param_index] = true; ordered_operands[param_index] = operand; - - err = CallArgumentError_DuplicateParameter; } } @@ -5469,7 +5484,7 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A error(call, "Variadic parameters already handled with a named argument '%.*s' in procedure call", LIT(name)); } err = CallArgumentError_DuplicateParameter; - } else { + } else if (!visited[pt->variadic_index]) { visited[pt->variadic_index] = true; Operand *variadic_operand = &ordered_operands[pt->variadic_index]; @@ -5548,6 +5563,52 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A } } + auto eval_param_and_score = [](CheckerContext *c, Operand *o, Type *param_type, CallArgumentError &err, bool param_is_variadic, Entity *e, bool show_error) -> i64 { + i64 s = 0; + if (!check_is_assignable_to_with_score(c, o, param_type, &s, param_is_variadic)) { + bool ok = false; + if (e && e->flags & EntityFlag_AnyInt) { + if (is_type_integer(param_type)) { + ok = check_is_castable_to(c, o, param_type); + } + } + if (ok) { + s = assign_score_function(MAXIMUM_TYPE_DISTANCE); + } else { + if (show_error) { + check_assignment(c, o, param_type, str_lit("procedure argument")); + } + err = CallArgumentError_WrongTypes; + } + + } else if (show_error) { + check_assignment(c, o, param_type, str_lit("procedure argument")); + } + + if (e && e->flags & EntityFlag_ConstInput) { + if (o->mode != Addressing_Constant) { + if (show_error) { + error(o->expr, "Expected a constant value for the argument '%.*s'", LIT(e->token.string)); + } + err = CallArgumentError_NoneConstantParameter; + } + } + + + if (!err && is_type_any(param_type)) { + add_type_info_type(c, o->type); + } + if (o->mode == Addressing_Type && is_type_typeid(param_type)) { + add_type_info_type(c, o->type); + add_type_and_value(c, o->expr, Addressing_Value, param_type, exact_value_typeid(o->type)); + } else if (show_error && is_type_untyped(o->type)) { + update_untyped_expr_type(c, o->expr, param_type, true); + } + + return s; + }; + + if (ordered_operands.count == 0 && param_count_excluding_defaults == 0) { err = CallArgumentError_None; @@ -5555,7 +5616,9 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A GB_ASSERT(pt->params != nullptr && pt->params->Tuple.variables.count > 0); Type *t = pt->params->Tuple.variables[0]->type; if (is_type_polymorphic(t)) { - error(call, "Ambiguous call to a polymorphic variadic procedure with no variadic input"); + if (show_error) { + error(call, "Ambiguous call to a polymorphic variadic procedure with no variadic input"); + } err = CallArgumentError_AmbiguousPolymorphicVariadic; } } @@ -5596,46 +5659,14 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A } else { score += assign_score_function(MAXIMUM_TYPE_DISTANCE); } - } else { - i64 s = 0; - if (!check_is_assignable_to_with_score(c, o, e->type, &s, param_is_variadic)) { - bool ok = false; - if (e->flags & EntityFlag_AnyInt) { - if (is_type_integer(e->type)) { - ok = check_is_castable_to(c, o, e->type); - } - } - if (ok) { - s = assign_score_function(MAXIMUM_TYPE_DISTANCE); - } else { - if (show_error) { - check_assignment(c, o, e->type, str_lit("procedure argument")); - } - err = CallArgumentError_WrongTypes; - } - - if (e->flags & EntityFlag_ConstInput) { - if (o->mode != Addressing_Constant) { - if (show_error) { - error(o->expr, "Expected a constant value for the argument '%.*s'", LIT(e->token.string)); - } - err = CallArgumentError_NoneConstantParameter; - } - } - } else if (show_error) { - check_assignment(c, o, e->type, str_lit("procedure argument")); - } - if (!param_is_variadic) { - score += s; - } + continue; } - if (o->mode == Addressing_Type && is_type_typeid(e->type)) { - add_type_info_type(c, o->type); - add_type_and_value(c, o->expr, Addressing_Value, e->type, exact_value_typeid(o->type)); - } else if (show_error && is_type_untyped(o->type)) { - update_untyped_expr_type(c, o->expr, e->type, true); + if (param_is_variadic) { + continue; } + + score += eval_param_and_score(c, o, e->type, err, param_is_variadic, e, show_error); } } @@ -5651,12 +5682,12 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A } for_array(operand_index, variadic_operands) { - Operand &o = variadic_operands[operand_index]; + Operand *o = &variadic_operands[operand_index]; if (vari_expand) { t = slice; if (operand_index > 0) { if (show_error) { - error(o.expr, "'..' in a variadic procedure can only have one variadic argument at the end"); + error(o->expr, "'..' in a variadic procedure can only have one variadic argument at the end"); } if (data) { data->score = score; @@ -5666,25 +5697,7 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A return CallArgumentError_MultipleVariadicExpand; } } - i64 s = 0; - if (!check_is_assignable_to_with_score(c, &o, t, &s, true)) { - if (show_error) { - check_assignment(c, &o, t, str_lit("variadic argument")); - } - err = CallArgumentError_WrongTypes; - } else if (show_error) { - check_assignment(c, &o, t, str_lit("variadic argument")); - } - score += s; - if (is_type_any(elem)) { - add_type_info_type(c, o.type); - } - if (o.mode == Addressing_Type && is_type_typeid(t)) { - add_type_info_type(c, o.type); - add_type_and_value(c, o.expr, Addressing_Value, t, exact_value_typeid(o.type)); - } else if (show_error && is_type_untyped(o.type)) { - update_untyped_expr_type(c, o.expr, t, true); - } + score += eval_param_and_score(c, o, t, err, true, nullptr, show_error); } } diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index b7ee88804..fc58d584e 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -3336,253 +3336,7 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { GB_ASSERT(proc_type_->kind == Type_Proc); TypeProc *pt = &proc_type_->Proc; - if (ce->split_args) { - return lb_build_call_expr_internal_with_arg_split_args(p, value, pt, expr, ce->split_args); - } - - if (is_call_expr_field_value(ce)) { - auto args = array_make(permanent_allocator(), pt->param_count); - - for_array(arg_index, ce->args) { - Ast *arg = ce->args[arg_index]; - ast_node(fv, FieldValue, arg); - GB_ASSERT(fv->field->kind == Ast_Ident); - String name = fv->field->Ident.token.string; - isize index = lookup_procedure_parameter(pt, name); - GB_ASSERT(index >= 0); - TypeAndValue tav = type_and_value_of_expr(fv->value); - if (tav.mode == Addressing_Type) { - args[index] = lb_const_nil(m, tav.type); - } else { - args[index] = lb_build_expr(p, fv->value); - } - } - TypeTuple *params = &pt->params->Tuple; - for (isize i = 0; i < args.count; i++) { - Entity *e = params->variables[i]; - if (e->kind == Entity_TypeName) { - args[i] = lb_const_nil(m, e->type); - } else if (e->kind == Entity_Constant) { - continue; - } else { - GB_ASSERT(e->kind == Entity_Variable); - if (args[i].value == nullptr) { - args[i] = lb_handle_param_value(p, e->type, e->Variable.param_value, ast_token(expr).pos); - } else if (is_type_typeid(e->type) && !is_type_typeid(args[i].type)) { - args[i] = lb_typeid(p->module, args[i].type); - } else { - args[i] = lb_emit_conv(p, args[i], e->type); - } - } - } - - for (isize i = 0; i < args.count; i++) { - Entity *e = params->variables[i]; - if (args[i].type == nullptr) { - continue; - } else if (is_type_untyped_uninit(args[i].type)) { - args[i] = lb_const_undef(m, e->type); - } else if (is_type_untyped_nil(args[i].type)) { - args[i] = lb_const_nil(m, e->type); - } - } - - return lb_emit_call(p, value, args, ce->inlining); - } - - isize arg_index = 0; - - isize arg_count = 0; - for_array(i, ce->args) { - Ast *arg = ce->args[i]; - if (arg->kind == Ast_FieldValue) { - arg = arg->FieldValue.value; - } - TypeAndValue tav = type_and_value_of_expr(arg); - GB_ASSERT_MSG(tav.mode != Addressing_Invalid, "%s %s %d", expr_to_string(arg), expr_to_string(expr), tav.mode); - GB_ASSERT_MSG(tav.mode != Addressing_ProcGroup, "%s", expr_to_string(arg)); - Type *at = tav.type; - if (is_type_tuple(at)) { - arg_count += at->Tuple.variables.count; - } else { - arg_count++; - } - } - - isize param_count = 0; - if (pt->params) { - GB_ASSERT(pt->params->kind == Type_Tuple); - param_count = pt->params->Tuple.variables.count; - } - - auto args = array_make(permanent_allocator(), cast(isize)gb_max(param_count, arg_count)); - isize variadic_index = pt->variadic_index; - bool variadic = pt->variadic && variadic_index >= 0; - bool vari_expand = ce->ellipsis.pos.line != 0; - bool is_c_vararg = pt->c_vararg; - - String proc_name = {}; - if (p->entity != nullptr) { - proc_name = p->entity->token.string; - } - TokenPos pos = ast_token(ce->proc).pos; - - TypeTuple *param_tuple = nullptr; - if (pt->params) { - GB_ASSERT(pt->params->kind == Type_Tuple); - param_tuple = &pt->params->Tuple; - } - - for_array(i, ce->args) { - Ast *arg = ce->args[i]; - TypeAndValue arg_tv = type_and_value_of_expr(arg); - if (arg_tv.mode == Addressing_Type) { - args[arg_index++] = lb_const_nil(m, arg_tv.type); - } else { - lbValue a = lb_build_expr(p, arg); - Type *at = a.type; - if (at->kind == Type_Tuple) { - lbTupleFix *tf = map_get(&p->tuple_fix_map, a.value); - if (tf) { - for_array(j, tf->values) { - args[arg_index++] = tf->values[j]; - } - } else { - for_array(j, at->Tuple.variables) { - lbValue v = lb_emit_struct_ev(p, a, cast(i32)j); - args[arg_index++] = v; - } - } - } else { - args[arg_index++] = a; - } - } - } - - - if (param_count > 0) { - GB_ASSERT_MSG(pt->params != nullptr, "%s %td", expr_to_string(expr), pt->param_count); - GB_ASSERT(param_count < 1000000); - - if (arg_count < param_count) { - isize end = cast(isize)param_count; - if (variadic) { - end = variadic_index; - } - while (arg_index < end) { - Entity *e = param_tuple->variables[arg_index]; - GB_ASSERT(e->kind == Entity_Variable); - args[arg_index++] = lb_handle_param_value(p, e->type, e->Variable.param_value, ast_token(expr).pos); - } - } - - if (is_c_vararg) { - GB_ASSERT(variadic); - GB_ASSERT(!vari_expand); - isize i = 0; - for (; i < variadic_index; i++) { - Entity *e = param_tuple->variables[i]; - if (e->kind == Entity_Variable) { - args[i] = lb_emit_conv(p, args[i], e->type); - } - } - Type *variadic_type = param_tuple->variables[i]->type; - GB_ASSERT(is_type_slice(variadic_type)); - variadic_type = base_type(variadic_type)->Slice.elem; - if (!is_type_any(variadic_type)) { - for (; i < arg_count; i++) { - args[i] = lb_emit_conv(p, args[i], variadic_type); - } - } else { - for (; i < arg_count; i++) { - args[i] = lb_emit_conv(p, args[i], default_type(args[i].type)); - } - } - } else if (variadic) { - isize i = 0; - for (; i < variadic_index; i++) { - Entity *e = param_tuple->variables[i]; - if (e->kind == Entity_Variable) { - args[i] = lb_emit_conv(p, args[i], e->type); - } - } - if (!vari_expand) { - Type *variadic_type = param_tuple->variables[i]->type; - GB_ASSERT(is_type_slice(variadic_type)); - variadic_type = base_type(variadic_type)->Slice.elem; - for (; i < arg_count; i++) { - args[i] = lb_emit_conv(p, args[i], variadic_type); - } - } - } else { - for (isize i = 0; i < param_count; i++) { - Entity *e = param_tuple->variables[i]; - if (e->kind == Entity_Variable) { - if (args[i].value == nullptr) { - continue; - } - GB_ASSERT_MSG(args[i].value != nullptr, "%.*s", LIT(e->token.string)); - if (is_type_typeid(e->type) && !is_type_typeid(args[i].type)) { - GB_ASSERT(LLVMIsNull(args[i].value)); - args[i] = lb_typeid(p->module, args[i].type); - } else { - args[i] = lb_emit_conv(p, args[i], e->type); - } - } - } - } - - if (variadic && !vari_expand && !is_c_vararg) { - // variadic call argument generation - Type *slice_type = param_tuple->variables[variadic_index]->type; - Type *elem_type = base_type(slice_type)->Slice.elem; - lbAddr slice = lb_add_local_generated(p, slice_type, true); - isize slice_len = arg_count+1 - (variadic_index+1); - - if (slice_len > 0) { - lbAddr base_array = lb_add_local_generated(p, alloc_type_array(elem_type, slice_len), true); - - for (isize i = variadic_index, j = 0; i < arg_count; i++, j++) { - lbValue addr = lb_emit_array_epi(p, base_array.addr, cast(i32)j); - lb_emit_store(p, addr, args[i]); - } - - lbValue base_elem = lb_emit_array_epi(p, base_array.addr, 0); - lbValue len = lb_const_int(m, t_int, slice_len); - lb_fill_slice(p, slice, base_elem, len); - } - - arg_count = param_count; - args[variadic_index] = lb_addr_load(p, slice); - } - } - - if (variadic && variadic_index+1 < param_count) { - for (isize i = variadic_index+1; i < param_count; i++) { - Entity *e = param_tuple->variables[i]; - args[i] = lb_handle_param_value(p, e->type, e->Variable.param_value, ast_token(expr).pos); - } - } - - isize final_count = param_count; - if (is_c_vararg) { - final_count = arg_count; - } - - if (param_tuple != nullptr) { - for (isize i = 0; i < gb_min(args.count, param_tuple->variables.count); i++) { - Entity *e = param_tuple->variables[i]; - if (args[i].type == nullptr) { - continue; - } else if (is_type_untyped_uninit(args[i].type)) { - args[i] = lb_const_undef(m, e->type); - } else if (is_type_untyped_nil(args[i].type)) { - args[i] = lb_const_nil(m, e->type); - } - } - } - - auto call_args = array_slice(args, 0, final_count); - return lb_emit_call(p, value, call_args, ce->inlining); + GB_ASSERT(ce->split_args != nullptr); + return lb_build_call_expr_internal_with_arg_split_args(p, value, pt, expr, ce->split_args); } From 18746c1444c8137fc0d78659a66c8f63079963e8 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 21 Jun 2023 00:40:02 +0100 Subject: [PATCH 45/97] Refactor call argument checking to a single procedure --- src/check_expr.cpp | 249 ++++++++++++++++++--------------------------- 1 file changed, 97 insertions(+), 152 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 5b1f1a73c..b9cc311a8 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -5994,6 +5994,75 @@ gb_internal bool check_named_arguments(CheckerContext *c, Type *type, Slice const &positional_operands, Array const &named_operands, + CallArgumentErrorMode show_error_mode, + CallArgumentData *data) { + + bool return_on_failure = show_error_mode == CallArgumentMode_NoErrors; + + Ast *ident = operand->expr; + while (ident->kind == Ast_SelectorExpr) { + Ast *s = ident->SelectorExpr.selector; + ident = s; + } + + if (e == nullptr) { + GB_ASSERT(proc_type == nullptr); + e = entity_of_node(ident); + proc_type = e->type; + } + + proc_type = base_type(proc_type); + GB_ASSERT(proc_type->kind == Type_Proc); + + CallArgumentError err = check_call_arguments_internal(c, call, proc_type, e, positional_operands, named_operands, show_error_mode, data); + if (return_on_failure && err != CallArgumentError_None) { + return false; + } + + Entity *entity_to_use = data->gen_entity != nullptr ? data->gen_entity : e; + if (!return_on_failure) { + add_entity_use(c, ident, entity_to_use); + if (entity_to_use != nullptr) { + update_untyped_expr_type(c, operand->expr, entity_to_use->type, true); + add_type_and_value(c, operand->expr, operand->mode, entity_to_use->type, operand->value); + } + } + + if (data->gen_entity != nullptr) { + Entity *e = data->gen_entity; + DeclInfo *decl = data->gen_entity->decl_info; + CheckerContext ctx = *c; + ctx.scope = decl->scope; + ctx.decl = decl; + ctx.proc_name = e->token.string; + ctx.curr_proc_decl = decl; + ctx.curr_proc_sig = e->type; + + GB_ASSERT(decl->proc_lit->kind == Ast_ProcLit); + bool ok = evaluate_where_clauses(&ctx, call, decl->scope, &decl->proc_lit->ProcLit.where_clauses, !return_on_failure); + if (return_on_failure) { + if (!ok) { + return false; + } + + } else { + decl->where_clauses_evaluated = true; + if (ok && (data->gen_entity->flags & EntityFlag_ProcBodyChecked) == 0) { + check_procedure_later(c->checker, e->file, e->token, decl, e->type, decl->proc_lit->ProcLit.body, decl->proc_lit->ProcLit.tags); + } + if (is_type_proc(data->gen_entity->type)) { + Type *t = base_type(entity_to_use->type); + data->result_type = t->Proc.results; + } + } + } + + return true; +} + gb_internal CallArgumentData check_call_arguments_new_and_improved_proc_group(CheckerContext *c, Operand *operand, Ast *call) { ast_node(ce, CallExpr, call); @@ -6089,11 +6158,6 @@ gb_internal CallArgumentData check_call_arguments_new_and_improved_proc_group(Ch defer (array_free(&named_operands)); if (procs.count == 1) { - Ast *ident = operand->expr; - while (ident->kind == Ast_SelectorExpr) { - Ast *s = ident->SelectorExpr.selector; - ident = s; - } Entity *e = procs[0]; @@ -6105,46 +6169,11 @@ gb_internal CallArgumentData check_call_arguments_new_and_improved_proc_group(Ch return data; } - CallArgumentError err = check_call_arguments_internal(c, call, e->type, e, positional_operands, named_operands, CallArgumentMode_ShowErrors, &data); - if (err != CallArgumentError_None) { - - } - - - Type *proc_type = base_type(operand->type); - - Entity *entity_to_use = data.gen_entity != nullptr ? data.gen_entity : e; - add_entity_use(c, ident, entity_to_use); - if (entity_to_use != nullptr) { - update_untyped_expr_type(c, operand->expr, entity_to_use->type, true); - - proc_type = base_type(entity_to_use->type); - } - - if (proc_type && proc_type->kind == Type_Proc) { - data.result_type = proc_type->Proc.results; - add_type_and_value(c, operand->expr, operand->mode, proc_type, operand->value); - } - - if (data.gen_entity != nullptr) { - Entity *e = data.gen_entity; - DeclInfo *decl = data.gen_entity->decl_info; - CheckerContext ctx = *c; - ctx.scope = decl->scope; - ctx.decl = decl; - ctx.proc_name = e->token.string; - ctx.curr_proc_decl = decl; - ctx.curr_proc_sig = e->type; - - GB_ASSERT(decl->proc_lit->kind == Ast_ProcLit); - bool ok = evaluate_where_clauses(&ctx, call, decl->scope, &decl->proc_lit->ProcLit.where_clauses, true); - decl->where_clauses_evaluated = true; - - if (ok && (data.gen_entity->flags & EntityFlag_ProcBodyChecked) == 0) { - check_procedure_later(c->checker, e->file, e->token, decl, e->type, decl->proc_lit->ProcLit.body, decl->proc_lit->ProcLit.tags); - } - } - + check_call_arguments_new_and_improved_single(c, call, operand, + e, e->type, + positional_operands, named_operands, + CallArgumentMode_ShowErrors, + &data); return data; } @@ -6256,7 +6285,6 @@ gb_internal CallArgumentData check_call_arguments_new_and_improved_proc_group(Ch Entity *p = procs[i]; Type *pt = base_type(p->type); if (pt != nullptr && is_type_proc(pt)) { - CallArgumentError err = CallArgumentError_None; CallArgumentData data = {}; CheckerContext ctx = *c; @@ -6264,26 +6292,17 @@ gb_internal CallArgumentData check_call_arguments_new_and_improved_proc_group(Ch ctx.allow_polymorphic_types = is_type_polymorphic(pt); ctx.hide_polymorphic_errors = true; - err = check_call_arguments_internal(&ctx, call, pt, p, positional_operands, named_operands, CallArgumentMode_NoErrors, &data); - if (err != CallArgumentError_None) { + bool is_a_candidate = check_call_arguments_new_and_improved_single(&ctx, call, operand, + p, pt, + positional_operands, named_operands, + CallArgumentMode_NoErrors, + &data); + if (!is_a_candidate) { continue; } isize index = i; if (data.gen_entity != nullptr) { - Entity *e = data.gen_entity; - DeclInfo *decl = data.gen_entity->decl_info; - ctx.scope = decl->scope; - ctx.decl = decl; - ctx.proc_name = e->token.string; - ctx.curr_proc_decl = decl; - ctx.curr_proc_sig = e->type; - - GB_ASSERT(decl->proc_lit->kind == Ast_ProcLit); - if (!evaluate_where_clauses(&ctx, call, decl->scope, &decl->proc_lit->ProcLit.where_clauses, false)) { - continue; - } - array_add(&proc_entities, data.gen_entity); index = proc_entities.count-1; } @@ -6449,41 +6468,11 @@ gb_internal CallArgumentData check_call_arguments_new_and_improved_proc_group(Ch Array named_operands = {}; - Type *proc_type = e->type; - CallArgumentData data = {}; - CallArgumentError err = check_call_arguments_internal(c, call, proc_type, e, positional_operands, named_operands, CallArgumentMode_ShowErrors, &data); - gb_unused(err); - Entity *entity_to_use = data.gen_entity != nullptr ? data.gen_entity : e; - add_entity_use(c, ident, entity_to_use); - if (entity_to_use != nullptr) { - proc_type = base_type(entity_to_use->type); - update_untyped_expr_type(c, operand->expr, entity_to_use->type, true); - add_type_and_value(c, operand->expr, operand->mode, entity_to_use->type, operand->value); - } - - if (data.gen_entity != nullptr) { - Entity *e = data.gen_entity; - DeclInfo *decl = data.gen_entity->decl_info; - CheckerContext ctx = *c; - ctx.scope = decl->scope; - ctx.decl = decl; - ctx.proc_name = e->token.string; - ctx.curr_proc_decl = decl; - ctx.curr_proc_sig = e->type; - - GB_ASSERT(decl->proc_lit->kind == Ast_ProcLit); - bool ok = evaluate_where_clauses(&ctx, call, decl->scope, &decl->proc_lit->ProcLit.where_clauses, true); - decl->where_clauses_evaluated = true; - - if (ok && (data.gen_entity->flags & EntityFlag_ProcBodyChecked) == 0) { - check_procedure_later(c->checker, e->file, e->token, decl, e->type, decl->proc_lit->ProcLit.body, decl->proc_lit->ProcLit.tags); - } - } - - if (is_type_proc(proc_type)) { - data.result_type = proc_type->Proc.results; - } - + check_call_arguments_new_and_improved_single(c, call, operand, + e, e->type, + positional_operands, named_operands, + CallArgumentMode_ShowErrors, + &data); return data; } @@ -6539,24 +6528,18 @@ gb_internal CallArgumentData check_call_arguments_new_and_improved(CheckerContex return check_call_arguments_new_and_improved_proc_group(c, operand, call); } - auto positional_operands = array_make(heap_allocator(), 0, positional_args.count); auto named_operands = array_make(heap_allocator(), 0, 0); defer (array_free(&positional_operands)); defer (array_free(&named_operands)); - - Slice visited = {}; - if (positional_args.count > 0) { isize lhs_count = -1; bool is_variadic = false; Entity **lhs = nullptr; if (pt != nullptr) { lhs = populate_proc_parameter_list(c, proc_type, &lhs_count, &is_variadic); - } else if (operand->mode == Addressing_ProcGroup) { - // TODO } check_unpack_arguments(c, lhs, lhs_count, &positional_operands, positional_args, is_variadic ? UnpackFlag_IsVariadic : UnpackFlag_None); } @@ -6597,49 +6580,11 @@ gb_internal CallArgumentData check_call_arguments_new_and_improved(CheckerContex } if (!any_failure) { - Ast *ident = operand->expr; - while (ident->kind == Ast_SelectorExpr) { - Ast *s = ident->SelectorExpr.selector; - ident = s; - } - - Entity *e = entity_of_node(ident); - - TokenPos pos = ast_token(call).pos; - - CallArgumentError err = check_call_arguments_internal(c, call, proc_type, e, positional_operands, named_operands, CallArgumentMode_ShowErrors, &data); - gb_unused(err); - Entity *entity_to_use = data.gen_entity != nullptr ? data.gen_entity : e; - add_entity_use(c, ident, entity_to_use); - if (entity_to_use != nullptr) { - update_untyped_expr_type(c, operand->expr, entity_to_use->type, true); - add_type_and_value(c, operand->expr, operand->mode, entity_to_use->type, operand->value); - } - if (data.gen_entity != nullptr) { - Entity *e = data.gen_entity; - DeclInfo *decl = data.gen_entity->decl_info; - CheckerContext ctx = *c; - ctx.scope = decl->scope; - ctx.decl = decl; - ctx.proc_name = e->token.string; - ctx.curr_proc_decl = decl; - ctx.curr_proc_sig = e->type; - - GB_ASSERT(decl->proc_lit->kind == Ast_ProcLit); - bool ok = evaluate_where_clauses(&ctx, call, decl->scope, &decl->proc_lit->ProcLit.where_clauses, true); - decl->where_clauses_evaluated = true; - - if (ok && (data.gen_entity->flags & EntityFlag_ProcBodyChecked) == 0) { - check_procedure_later(c->checker, e->file, e->token, decl, e->type, decl->proc_lit->ProcLit.body, decl->proc_lit->ProcLit.tags); - } - - if (is_type_proc(data.gen_entity->type)) { - Type *t = base_type(entity_to_use->type); - // GB_ASSERT_MSG(!is_type_polymorphic(t), "%s", expr_to_string(call)); - data.result_type = t->Proc.results; - - } - } + check_call_arguments_new_and_improved_single(c, call, operand, + nullptr, nullptr, + positional_operands, named_operands, + CallArgumentMode_ShowErrors, + &data); } else if (pt) { data.result_type = pt->results; } @@ -6991,16 +6936,16 @@ gb_internal CallArgumentError check_polymorphic_record_type(CheckerContext *c, O -// returns true on failure -gb_internal bool check_call_parameter_mixture(Operand *operand, Slice const &args, char const *context, bool allow_mixed=false) { - bool failure = false; +// returns true on success +gb_internal bool check_call_parameter_mixture(Slice const &args, char const *context, bool allow_mixed=false) { + bool success = true; if (args.count > 0) { if (allow_mixed) { bool was_named = false; for (Ast *arg : args) { if (was_named && arg->kind != Ast_FieldValue) { error(arg, "Non-named parameter is not allowed to follow named parameter i.e. 'field = value' in a %s", context); - failure = true; + success = false; break; } was_named = was_named || arg->kind == Ast_FieldValue; @@ -7016,16 +6961,16 @@ gb_internal bool check_call_parameter_mixture(Operand *operand, Slice con } if (mix) { error(arg, "Mixture of 'field = value' and value elements in a %s is not allowed", context); - failure = true; + success = false; } } } } - return failure; + return success; } -#define CHECK_CALL_PARAMETER_MIXTURE_OR_RETURN(context_, ...) if (check_call_parameter_mixture(operand, args, context_, ##__VA_ARGS__)) { \ +#define CHECK_CALL_PARAMETER_MIXTURE_OR_RETURN(context_, ...) if (!check_call_parameter_mixture(args, context_, ##__VA_ARGS__)) { \ operand->mode = Addressing_Invalid; \ operand->expr = call; \ return Expr_Stmt; \ From b2ced834ba01c61c8ec842af2de5f480d1e93462 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 21 Jun 2023 00:43:51 +0100 Subject: [PATCH 46/97] Minor code reshuffle --- src/check_expr.cpp | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index b9cc311a8..8731a0caf 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -38,9 +38,9 @@ gb_global char const *CallArgumentError_strings[CallArgumentError_MAX] = { }; -enum CallArgumentErrorMode { - CallArgumentMode_NoErrors, - CallArgumentMode_ShowErrors, +enum struct CallArgumentErrorMode { + NoErrors, + ShowErrors, }; struct CallArgumentData { @@ -5362,7 +5362,11 @@ gb_internal isize lookup_procedure_parameter(Type *type, String const ¶meter return lookup_procedure_parameter(&type->Proc, parameter_name); } -gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, Ast *call, Type *proc_type, Entity *entity, Array positional_operands, Array const &named_operands, CallArgumentErrorMode show_error_mode, CallArgumentData *data) { +gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, Ast *call, + Entity *entity, Type *proc_type, + Array positional_operands, Array const &named_operands, + CallArgumentErrorMode show_error_mode, + CallArgumentData *data) { TEMPORARY_ALLOCATOR_GUARD(); CallArgumentError err = CallArgumentError_None; @@ -5377,7 +5381,7 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A bool variadic = pt->variadic; bool vari_expand = (ce->ellipsis.pos.line != 0); i64 score = 0; - bool show_error = show_error_mode == CallArgumentMode_ShowErrors; + bool show_error = show_error_mode == CallArgumentErrorMode::ShowErrors; Type *final_proc_type = proc_type; Entity *gen_entity = nullptr; @@ -6000,7 +6004,7 @@ gb_internal bool check_call_arguments_new_and_improved_single(CheckerContext *c, CallArgumentErrorMode show_error_mode, CallArgumentData *data) { - bool return_on_failure = show_error_mode == CallArgumentMode_NoErrors; + bool return_on_failure = show_error_mode == CallArgumentErrorMode::NoErrors; Ast *ident = operand->expr; while (ident->kind == Ast_SelectorExpr) { @@ -6017,7 +6021,7 @@ gb_internal bool check_call_arguments_new_and_improved_single(CheckerContext *c, proc_type = base_type(proc_type); GB_ASSERT(proc_type->kind == Type_Proc); - CallArgumentError err = check_call_arguments_internal(c, call, proc_type, e, positional_operands, named_operands, show_error_mode, data); + CallArgumentError err = check_call_arguments_internal(c, call, e, proc_type, positional_operands, named_operands, show_error_mode, data); if (return_on_failure && err != CallArgumentError_None) { return false; } @@ -6158,22 +6162,18 @@ gb_internal CallArgumentData check_call_arguments_new_and_improved_proc_group(Ch defer (array_free(&named_operands)); if (procs.count == 1) { - - Entity *e = procs[0]; lhs = populate_proc_parameter_list(c, e->type, &lhs_count, &is_variadic); check_unpack_arguments(c, lhs, lhs_count, &positional_operands, positional_args, is_variadic ? UnpackFlag_IsVariadic : UnpackFlag_None); - if (!check_named_arguments(c, e->type, named_args, &named_operands, true)) { - return data; + if (check_named_arguments(c, e->type, named_args, &named_operands, true)) { + check_call_arguments_new_and_improved_single(c, call, operand, + e, e->type, + positional_operands, named_operands, + CallArgumentErrorMode::ShowErrors, + &data); } - - check_call_arguments_new_and_improved_single(c, call, operand, - e, e->type, - positional_operands, named_operands, - CallArgumentMode_ShowErrors, - &data); return data; } @@ -6295,7 +6295,7 @@ gb_internal CallArgumentData check_call_arguments_new_and_improved_proc_group(Ch bool is_a_candidate = check_call_arguments_new_and_improved_single(&ctx, call, operand, p, pt, positional_operands, named_operands, - CallArgumentMode_NoErrors, + CallArgumentErrorMode::NoErrors, &data); if (!is_a_candidate) { continue; @@ -6471,7 +6471,7 @@ gb_internal CallArgumentData check_call_arguments_new_and_improved_proc_group(Ch check_call_arguments_new_and_improved_single(c, call, operand, e, e->type, positional_operands, named_operands, - CallArgumentMode_ShowErrors, + CallArgumentErrorMode::ShowErrors, &data); return data; } @@ -6583,7 +6583,7 @@ gb_internal CallArgumentData check_call_arguments_new_and_improved(CheckerContex check_call_arguments_new_and_improved_single(c, call, operand, nullptr, nullptr, positional_operands, named_operands, - CallArgumentMode_ShowErrors, + CallArgumentErrorMode::ShowErrors, &data); } else if (pt) { data.result_type = pt->results; From 67ca9166d36375fd42725c16ca3933d4c91ebfee Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 21 Jun 2023 01:03:21 +0100 Subject: [PATCH 47/97] Allow named arguments variadic expansion `..` --- src/parser.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/parser.cpp b/src/parser.cpp index 96b7c96ea..b756412ff 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -2752,9 +2752,9 @@ gb_internal Ast *parse_call_expr(AstFile *f, Ast *operand) { open_paren = expect_token(f, Token_OpenParen); + bool seen_ellipsis = false; while (f->curr_token.kind != Token_CloseParen && - f->curr_token.kind != Token_EOF && - ellipsis.pos.line == 0) { + f->curr_token.kind != Token_EOF) { if (f->curr_token.kind == Token_Comma) { syntax_error(f->curr_token, "Expected an expression not ,"); } else if (f->curr_token.kind == Token_Eq) { @@ -2777,9 +2777,15 @@ gb_internal Ast *parse_call_expr(AstFile *f, Ast *operand) { Ast *value = parse_value(f); arg = ast_field_value(f, arg, value, eq); + } else if (seen_ellipsis) { + syntax_error(arg, "Positional arguments are not allowed after '..'"); } array_add(&args, arg); + if (ellipsis.pos.line != 0) { + seen_ellipsis = true; + } + if (!allow_field_separator(f)) { break; } From 9b54b99bf696a65bc5c8358b2a5ace1f6dc4fdcc Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 21 Jun 2023 01:17:05 +0100 Subject: [PATCH 48/97] Use positional and named arguments within the core library --- core/c/frontend/preprocessor/preprocess.odin | 4 +- core/compress/gzip/gzip.odin | 2 +- core/compress/zlib/zlib.odin | 6 +-- core/fmt/fmt.odin | 18 +++---- core/fmt/fmt_os.odin | 12 ++--- core/image/png/helpers.odin | 8 +-- core/log/log.odin | 26 +++++----- core/log/log_allocator.odin | 54 ++++++++++---------- core/math/big/radix.odin | 2 +- core/mem/virtual/arena.odin | 2 +- core/runtime/core_builtin.odin | 12 ++--- core/testing/runner_windows.odin | 2 +- core/testing/testing.odin | 10 ++-- core/text/table/table.odin | 2 +- examples/demo/demo.odin | 4 +- 15 files changed, 82 insertions(+), 82 deletions(-) diff --git a/core/c/frontend/preprocessor/preprocess.odin b/core/c/frontend/preprocessor/preprocess.odin index 4cfad2c1c..b5eab0bb3 100644 --- a/core/c/frontend/preprocessor/preprocess.odin +++ b/core/c/frontend/preprocessor/preprocess.odin @@ -1118,7 +1118,7 @@ expand_macro :: proc(cpp: ^Preprocessor, rest: ^^Token, tok: ^Token) -> bool { search_include_next :: proc(cpp: ^Preprocessor, filename: string) -> (path: string, ok: bool) { for ; cpp.include_next_index < len(cpp.include_paths); cpp.include_next_index += 1 { - tpath := filepath.join(elems={cpp.include_paths[cpp.include_next_index], filename}, allocator=context.temp_allocator) + tpath := filepath.join({cpp.include_paths[cpp.include_next_index], filename}, allocator=context.temp_allocator) if os.exists(tpath) { return strings.clone(tpath), true } @@ -1136,7 +1136,7 @@ search_include_paths :: proc(cpp: ^Preprocessor, filename: string) -> (path: str } for include_path in cpp.include_paths { - tpath := filepath.join(elems={include_path, filename}, allocator=context.temp_allocator) + tpath := filepath.join({include_path, filename}, allocator=context.temp_allocator) if os.exists(tpath) { path, ok = strings.clone(tpath), true cpp.filepath_cache[filename] = path diff --git a/core/compress/gzip/gzip.odin b/core/compress/gzip/gzip.odin index 4de4d1b63..b0ca4491b 100644 --- a/core/compress/gzip/gzip.odin +++ b/core/compress/gzip/gzip.odin @@ -335,7 +335,7 @@ load_from_context :: proc(z: ^$C, buf: ^bytes.Buffer, known_gzip_size := -1, exp // fmt.printf("GZIP: Expected Payload Size: %v\n", expected_output_size); - zlib_error := zlib.inflate_raw(z=z, expected_output_size=expected_output_size) + zlib_error := zlib.inflate_raw(z, expected_output_size=expected_output_size) if zlib_error != nil { return zlib_error } diff --git a/core/compress/zlib/zlib.odin b/core/compress/zlib/zlib.odin index 855eef7a8..21172e8e8 100644 --- a/core/compress/zlib/zlib.odin +++ b/core/compress/zlib/zlib.odin @@ -471,7 +471,7 @@ inflate_from_context :: proc(using ctx: ^compress.Context_Memory_Input, raw := f } // Parse ZLIB stream without header. - inflate_raw(z=ctx, expected_output_size=expected_output_size) or_return + inflate_raw(ctx, expected_output_size=expected_output_size) or_return if !raw { compress.discard_to_next_byte_lsb(ctx) @@ -665,7 +665,7 @@ inflate_from_byte_array :: proc(input: []u8, buf: ^bytes.Buffer, raw := false, e ctx.input_data = input ctx.output = buf - return inflate_from_context(ctx=&ctx, raw=raw, expected_output_size=expected_output_size) + return inflate_from_context(&ctx, raw=raw, expected_output_size=expected_output_size) } inflate_from_byte_array_raw :: proc(input: []u8, buf: ^bytes.Buffer, raw := false, expected_output_size := -1) -> (err: Error) { @@ -674,7 +674,7 @@ inflate_from_byte_array_raw :: proc(input: []u8, buf: ^bytes.Buffer, raw := fals ctx.input_data = input ctx.output = buf - return inflate_raw(z=&ctx, expected_output_size=expected_output_size) + return inflate_raw(&ctx, expected_output_size=expected_output_size) } inflate :: proc{inflate_from_context, inflate_from_byte_array} diff --git a/core/fmt/fmt.odin b/core/fmt/fmt.odin index 61f7b5d99..f1f94b1b3 100644 --- a/core/fmt/fmt.odin +++ b/core/fmt/fmt.odin @@ -123,7 +123,7 @@ register_user_formatter :: proc(id: typeid, formatter: User_Formatter) -> Regist aprint :: proc(args: ..any, sep := " ") -> string { str: strings.Builder strings.builder_init(&str) - sbprint(buf=&str, args=args, sep=sep) + sbprint(&str, ..args, sep=sep) return strings.to_string(str) } // Creates a formatted string with a newline character at the end @@ -139,7 +139,7 @@ aprint :: proc(args: ..any, sep := " ") -> string { aprintln :: proc(args: ..any, sep := " ") -> string { str: strings.Builder strings.builder_init(&str) - sbprintln(buf=&str, args=args, sep=sep) + sbprintln(&str, ..args, sep=sep) return strings.to_string(str) } // Creates a formatted string using a format string and arguments @@ -171,7 +171,7 @@ aprintf :: proc(fmt: string, args: ..any) -> string { tprint :: proc(args: ..any, sep := " ") -> string { str: strings.Builder strings.builder_init(&str, context.temp_allocator) - sbprint(buf=&str, args=args, sep=sep) + sbprint(&str, ..args, sep=sep) return strings.to_string(str) } // Creates a formatted string with a newline character at the end @@ -187,7 +187,7 @@ tprint :: proc(args: ..any, sep := " ") -> string { tprintln :: proc(args: ..any, sep := " ") -> string { str: strings.Builder strings.builder_init(&str, context.temp_allocator) - sbprintln(buf=&str, args=args, sep=sep) + sbprintln(&str, ..args, sep=sep) return strings.to_string(str) } // Creates a formatted string using a format string and arguments @@ -217,7 +217,7 @@ tprintf :: proc(fmt: string, args: ..any) -> string { // bprint :: proc(buf: []byte, args: ..any, sep := " ") -> string { sb := strings.builder_from_bytes(buf[0:len(buf)]) - return sbprint(buf=&sb, args=args, sep=sep) + return sbprint(&sb, ..args, sep=sep) } // Creates a formatted string using a supplied buffer as the backing array, appends newline. Writes into the buffer. // @@ -230,7 +230,7 @@ bprint :: proc(buf: []byte, args: ..any, sep := " ") -> string { // bprintln :: proc(buf: []byte, args: ..any, sep := " ") -> string { sb := strings.builder_from_bytes(buf[0:len(buf)]) - return sbprintln(buf=&sb, args=args, sep=sep) + return sbprintln(&sb, ..args, sep=sep) } // Creates a formatted string using a supplied buffer as the backing array. Writes into the buffer. // @@ -327,7 +327,7 @@ ctprintf :: proc(format: string, args: ..any) -> cstring { // Returns: A formatted string // sbprint :: proc(buf: ^strings.Builder, args: ..any, sep := " ") -> string { - wprint(w=strings.to_writer(buf), args=args, sep=sep) + wprint(strings.to_writer(buf), ..args, sep=sep) return strings.to_string(buf^) } // Formats and writes to a strings.Builder buffer using the default print settings @@ -340,7 +340,7 @@ sbprint :: proc(buf: ^strings.Builder, args: ..any, sep := " ") -> string { // Returns: The resulting formatted string // sbprintln :: proc(buf: ^strings.Builder, args: ..any, sep := " ") -> string { - wprintln(w=strings.to_writer(buf), args=args, sep=sep) + wprintln(strings.to_writer(buf), ..args, sep=sep) return strings.to_string(buf^) } // Formats and writes to a strings.Builder buffer according to the specified format string @@ -353,7 +353,7 @@ sbprintln :: proc(buf: ^strings.Builder, args: ..any, sep := " ") -> string { // Returns: The resulting formatted string // sbprintf :: proc(buf: ^strings.Builder, fmt: string, args: ..any) -> string { - wprintf(w=strings.to_writer(buf), fmt=fmt, args=args) + wprintf(strings.to_writer(buf), fmt, ..args) return strings.to_string(buf^) } // Formats and writes to an io.Writer using the default print settings diff --git a/core/fmt/fmt_os.odin b/core/fmt/fmt_os.odin index 861b0c3b9..5b3a68502 100644 --- a/core/fmt/fmt_os.odin +++ b/core/fmt/fmt_os.odin @@ -14,7 +14,7 @@ fprint :: proc(fd: os.Handle, args: ..any, sep := " ") -> int { bufio.writer_init_with_buf(&b, {os.stream_from_handle(fd)}, buf[:]) w := bufio.writer_to_writer(&b) - return wprint(w=w, args=args, sep=sep) + return wprint(w, ..args, sep=sep) } // fprintln formats using the default print settings and writes to fd @@ -26,7 +26,7 @@ fprintln :: proc(fd: os.Handle, args: ..any, sep := " ") -> int { bufio.writer_init_with_buf(&b, {os.stream_from_handle(fd)}, buf[:]) w := bufio.writer_to_writer(&b) - return wprintln(w=w, args=args, sep=sep) + return wprintln(w, ..args, sep=sep) } // fprintf formats according to the specified format string and writes to fd fprintf :: proc(fd: os.Handle, fmt: string, args: ..any) -> int { @@ -61,15 +61,15 @@ fprint_typeid :: proc(fd: os.Handle, id: typeid) -> (n: int, err: io.Error) { } // print formats using the default print settings and writes to os.stdout -print :: proc(args: ..any, sep := " ") -> int { return fprint(fd=os.stdout, args=args, sep=sep) } +print :: proc(args: ..any, sep := " ") -> int { return fprint(os.stdout, ..args, sep=sep) } // println formats using the default print settings and writes to os.stdout -println :: proc(args: ..any, sep := " ") -> int { return fprintln(fd=os.stdout, args=args, sep=sep) } +println :: proc(args: ..any, sep := " ") -> int { return fprintln(os.stdout, ..args, sep=sep) } // printf formats according to the specified format string and writes to os.stdout printf :: proc(fmt: string, args: ..any) -> int { return fprintf(os.stdout, fmt, ..args) } // eprint formats using the default print settings and writes to os.stderr -eprint :: proc(args: ..any, sep := " ") -> int { return fprint(fd=os.stderr, args=args, sep=sep) } +eprint :: proc(args: ..any, sep := " ") -> int { return fprint(os.stderr, ..args, sep=sep) } // eprintln formats using the default print settings and writes to os.stderr -eprintln :: proc(args: ..any, sep := " ") -> int { return fprintln(fd=os.stderr, args=args, sep=sep) } +eprintln :: proc(args: ..any, sep := " ") -> int { return fprintln(os.stderr, ..args, sep=sep) } // eprintf formats according to the specified format string and writes to os.stderr eprintf :: proc(fmt: string, args: ..any) -> int { return fprintf(os.stderr, fmt, ..args) } diff --git a/core/image/png/helpers.odin b/core/image/png/helpers.odin index bfe56d62e..025c2b866 100644 --- a/core/image/png/helpers.odin +++ b/core/image/png/helpers.odin @@ -99,7 +99,7 @@ text :: proc(c: image.PNG_Chunk) -> (res: Text, ok: bool) { case .tEXt: ok = true - fields := bytes.split(s=c.data, sep=[]u8{0}, allocator=context.temp_allocator) + fields := bytes.split(c.data, sep=[]u8{0}, allocator=context.temp_allocator) if len(fields) == 2 { res.keyword = strings.clone(string(fields[0])) res.text = strings.clone(string(fields[1])) @@ -110,7 +110,7 @@ text :: proc(c: image.PNG_Chunk) -> (res: Text, ok: bool) { case .zTXt: ok = true - fields := bytes.split_n(s=c.data, sep=[]u8{0}, n=3, allocator=context.temp_allocator) + fields := bytes.split_n(c.data, sep=[]u8{0}, n=3, allocator=context.temp_allocator) if len(fields) != 3 || len(fields[1]) != 0 { // Compression method must be 0=Deflate, which thanks to the split above turns // into an empty slice @@ -199,7 +199,7 @@ text_destroy :: proc(text: Text) { iccp :: proc(c: image.PNG_Chunk) -> (res: iCCP, ok: bool) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) - fields := bytes.split_n(s=c.data, sep=[]u8{0}, n=3, allocator=context.temp_allocator) + fields := bytes.split_n(c.data, sep=[]u8{0}, n=3, allocator=context.temp_allocator) if len(fields[0]) < 1 || len(fields[0]) > 79 { // Invalid profile name @@ -263,7 +263,7 @@ splt :: proc(c: image.PNG_Chunk) -> (res: sPLT, ok: bool) { } runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) - fields := bytes.split_n(s=c.data, sep=[]u8{0}, n=2, allocator=context.temp_allocator) + fields := bytes.split_n(c.data, sep=[]u8{0}, n=2, allocator=context.temp_allocator) if len(fields) != 2 { return } diff --git a/core/log/log.odin b/core/log/log.odin index a699247b8..f3554791b 100644 --- a/core/log/log.odin +++ b/core/log/log.odin @@ -76,43 +76,43 @@ nil_logger :: proc() -> Logger { } debugf :: proc(fmt_str: string, args: ..any, location := #caller_location) { - logf(level=.Debug, fmt_str=fmt_str, args=args, location=location) + logf(.Debug, fmt_str, ..args, location=location) } infof :: proc(fmt_str: string, args: ..any, location := #caller_location) { - logf(level=.Info, fmt_str=fmt_str, args=args, location=location) + logf(.Info, fmt_str, ..args, location=location) } warnf :: proc(fmt_str: string, args: ..any, location := #caller_location) { - logf(level=.Warning, fmt_str=fmt_str, args=args, location=location) + logf(.Warning, fmt_str, ..args, location=location) } errorf :: proc(fmt_str: string, args: ..any, location := #caller_location) { - logf(level=.Error, fmt_str=fmt_str, args=args, location=location) + logf(.Error, fmt_str, ..args, location=location) } fatalf :: proc(fmt_str: string, args: ..any, location := #caller_location) { - logf(level=.Fatal, fmt_str=fmt_str, args=args, location=location) + logf(.Fatal, fmt_str, ..args, location=location) } debug :: proc(args: ..any, sep := " ", location := #caller_location) { - log(level=.Debug, args=args, sep=sep, location=location) + log(.Debug, ..args, sep=sep, location=location) } info :: proc(args: ..any, sep := " ", location := #caller_location) { - log(level=.Info, args=args, sep=sep, location=location) + log(.Info, ..args, sep=sep, location=location) } warn :: proc(args: ..any, sep := " ", location := #caller_location) { - log(level=.Warning, args=args, sep=sep, location=location) + log(.Warning, ..args, sep=sep, location=location) } error :: proc(args: ..any, sep := " ", location := #caller_location) { - log(level=.Error, args=args, sep=sep, location=location) + log(.Error, ..args, sep=sep, location=location) } fatal :: proc(args: ..any, sep := " ", location := #caller_location) { - log(level=.Fatal, args=args, sep=sep, location=location) + log(.Fatal, ..args, sep=sep, location=location) } panic :: proc(args: ..any, location := #caller_location) -> ! { - log(level=.Fatal, args=args, location=location) + log(.Fatal, ..args, location=location) runtime.panic("log.panic", location) } panicf :: proc(fmt_str: string, args: ..any, location := #caller_location) -> ! { - logf(level=.Fatal, fmt_str=fmt_str, args=args, location=location) + logf(.Fatal, fmt_str, ..args, location=location) runtime.panic("log.panicf", location) } @@ -127,7 +127,7 @@ log :: proc(level: Level, args: ..any, sep := " ", location := #caller_location) if level < logger.lowest_level { return } - str := fmt.tprint(args=args, sep=sep) //NOTE(Hoej): While tprint isn't thread-safe, no logging is. + str := fmt.tprint(..args, sep=sep) //NOTE(Hoej): While tprint isn't thread-safe, no logging is. logger.procedure(logger.data, level, str, logger.options, location) } diff --git a/core/log/log_allocator.odin b/core/log/log_allocator.odin index f4d1841db..934f0d643 100644 --- a/core/log/log_allocator.odin +++ b/core/log/log_allocator.odin @@ -38,60 +38,60 @@ log_allocator_proc :: proc(allocator_data: rawptr, mode: runtime.Allocator_Mode, switch mode { case .Alloc: logf( - level=la.level, - fmt_str = "%s%s>>> ALLOCATOR(mode=.Alloc, size=%d, alignment=%d)", - args = {la.prefix, padding, size, alignment}, + la.level, + "%s%s>>> ALLOCATOR(mode=.Alloc, size=%d, alignment=%d)", + la.prefix, padding, size, alignment, location = location, ) case .Alloc_Non_Zeroed: logf( - level=la.level, - fmt_str = "%s%s>>> ALLOCATOR(mode=.Alloc_Non_Zeroed, size=%d, alignment=%d)", - args = {la.prefix, padding, size, alignment}, + la.level, + "%s%s>>> ALLOCATOR(mode=.Alloc_Non_Zeroed, size=%d, alignment=%d)", + la.prefix, padding, size, alignment, location = location, ) case .Free: if old_size != 0 { logf( - level=la.level, - fmt_str = "%s%s<<< ALLOCATOR(mode=.Free, ptr=%p, size=%d)", - args = {la.prefix, padding, old_memory, old_size}, + la.level, + "%s%s<<< ALLOCATOR(mode=.Free, ptr=%p, size=%d)", + la.prefix, padding, old_memory, old_size, location = location, ) } else { logf( - level=la.level, - fmt_str = "%s%s<<< ALLOCATOR(mode=.Free, ptr=%p)", - args = {la.prefix, padding, old_memory}, + la.level, + "%s%s<<< ALLOCATOR(mode=.Free, ptr=%p)", + la.prefix, padding, old_memory, location = location, ) } case .Free_All: logf( - level=la.level, - fmt_str = "%s%s<<< ALLOCATOR(mode=.Free_All)", - args = {la.prefix, padding}, + la.level, + "%s%s<<< ALLOCATOR(mode=.Free_All)", + la.prefix, padding, location = location, ) case .Resize: logf( - level=la.level, - fmt_str = "%s%s>>> ALLOCATOR(mode=.Resize, ptr=%p, old_size=%d, size=%d, alignment=%d)", - args = {la.prefix, padding, old_memory, old_size, size, alignment}, + la.level, + "%s%s>>> ALLOCATOR(mode=.Resize, ptr=%p, old_size=%d, size=%d, alignment=%d)", + la.prefix, padding, old_memory, old_size, size, alignment, location = location, ) case .Query_Features: logf( - level=la.level, - fmt_str = "%s%ALLOCATOR(mode=.Query_Features)", - args = {la.prefix, padding}, + la.level, + "%s%ALLOCATOR(mode=.Query_Features)", + la.prefix, padding, location = location, ) case .Query_Info: logf( - level=la.level, - fmt_str = "%s%ALLOCATOR(mode=.Query_Info)", - args = {la.prefix, padding}, + la.level, + "%s%ALLOCATOR(mode=.Query_Info)", + la.prefix, padding, location = location, ) } @@ -103,9 +103,9 @@ log_allocator_proc :: proc(allocator_data: rawptr, mode: runtime.Allocator_Mode, defer la.locked = false if err != nil { logf( - level=la.level, - fmt_str = "%s%ALLOCATOR ERROR=%v", - args = {la.prefix, padding, error}, + la.level, + "%s%ALLOCATOR ERROR=%v", + la.prefix, padding, error, location = location, ) } diff --git a/core/math/big/radix.odin b/core/math/big/radix.odin index 2b758dc35..d15ce0e98 100644 --- a/core/math/big/radix.odin +++ b/core/math/big/radix.odin @@ -429,7 +429,7 @@ internal_int_write_to_ascii_file :: proc(a: ^Int, filename: string, radix := i8( len = l, } - ok := os.write_entire_file(name=filename, data=data, truncate=true) + ok := os.write_entire_file(filename, data, truncate=true) return nil if ok else .Cannot_Write_File } diff --git a/core/mem/virtual/arena.odin b/core/mem/virtual/arena.odin index 027a6ce6e..e73a39660 100644 --- a/core/mem/virtual/arena.odin +++ b/core/mem/virtual/arena.odin @@ -120,7 +120,7 @@ arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_l if arena.minimum_block_size == 0 { arena.minimum_block_size = DEFAULT_ARENA_STATIC_RESERVE_SIZE } - arena_init_static(arena=arena, reserved=arena.minimum_block_size, commit_size=DEFAULT_ARENA_STATIC_COMMIT_SIZE) or_return + arena_init_static(arena, reserved=arena.minimum_block_size, commit_size=DEFAULT_ARENA_STATIC_COMMIT_SIZE) or_return } fallthrough case .Buffer: diff --git a/core/runtime/core_builtin.odin b/core/runtime/core_builtin.odin index c5cb8cc07..9f2899bcc 100644 --- a/core/runtime/core_builtin.odin +++ b/core/runtime/core_builtin.odin @@ -112,7 +112,7 @@ remove_range :: proc(array: ^$D/[dynamic]$T, lo, hi: int, loc := #caller_locatio // Note: If the dynamic array as no elements (`len(array) == 0`), this procedure will panic. @builtin pop :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (res: E) #no_bounds_check { - assert(len(array) > 0, "", loc) + assert(len(array) > 0, loc=loc) res = array[len(array)-1] (^Raw_Dynamic_Array)(array).len -= 1 return res @@ -136,7 +136,7 @@ pop_safe :: proc(array: ^$T/[dynamic]$E) -> (res: E, ok: bool) #no_bounds_check // Note: If the dynamic array as no elements (`len(array) == 0`), this procedure will panic. @builtin pop_front :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (res: E) #no_bounds_check { - assert(len(array) > 0, "", loc) + assert(len(array) > 0, loc=loc) res = array[0] if len(array) > 1 { copy(array[0:], array[1:]) @@ -424,7 +424,7 @@ append_elem :: proc(array: ^$T/[dynamic]$E, arg: E, loc := #caller_location) -> a := (^Raw_Dynamic_Array)(array) when size_of(E) != 0 { data := ([^]E)(a.data) - assert(condition=data != nil, loc=loc) + assert(data != nil, loc=loc) data[a.len] = arg } a.len += 1 @@ -459,7 +459,7 @@ append_elems :: proc(array: ^$T/[dynamic]$E, args: ..E, loc := #caller_location) a := (^Raw_Dynamic_Array)(array) when size_of(E) != 0 { data := ([^]E)(a.data) - assert(condition=data != nil, loc=loc) + assert(data != nil, loc=loc) intrinsics.mem_copy(&data[a.len], raw_data(args), size_of(E) * arg_len) } a.len += arg_len @@ -472,7 +472,7 @@ append_elems :: proc(array: ^$T/[dynamic]$E, args: ..E, loc := #caller_location) @builtin append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { args := transmute([]E)arg - return append_elems(array=array, args=args, loc=loc) + return append_elems(array, ..args, loc=loc) } @@ -481,7 +481,7 @@ append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, loc := #ca append_string :: proc(array: ^$T/[dynamic]$E/u8, args: ..string, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { n_arg: int for arg in args { - n_arg, err = append(array = array, args = transmute([]E)(arg), loc = loc) + n_arg, err = append(array, ..transmute([]E)(arg), loc=loc) n += n_arg if err != nil { return diff --git a/core/testing/runner_windows.odin b/core/testing/runner_windows.odin index 525eae685..17bcfce26 100644 --- a/core/testing/runner_windows.odin +++ b/core/testing/runner_windows.odin @@ -191,7 +191,7 @@ run_internal_test :: proc(t: ^T, it: Internal_Test) { global_exception_handler = win32.AddVectoredExceptionHandler(0, exception_handler_proc) context.assertion_failure_proc = proc(prefix, message: string, loc: runtime.Source_Code_Location) -> ! { - errorf(t=global_current_t, format="%s %s", args={prefix, message}, loc=loc) + errorf(global_current_t, "%s %s", prefix, message, loc=loc) intrinsics.trap() } diff --git a/core/testing/testing.odin b/core/testing/testing.odin index 37f9fe4d9..0ceb58e33 100644 --- a/core/testing/testing.odin +++ b/core/testing/testing.odin @@ -46,15 +46,15 @@ errorf :: proc(t: ^T, format: string, args: ..any, loc := #caller_location) { } fail :: proc(t: ^T, loc := #caller_location) { - error(t=t, args={"FAIL"}, loc=loc) + error(t, "FAIL", loc=loc) t.error_count += 1 } fail_now :: proc(t: ^T, msg := "", loc := #caller_location) { if msg != "" { - error(t=t, args={"FAIL:", msg}, loc=loc) + error(t, "FAIL:", msg, loc=loc) } else { - error(t=t, args={"FAIL"}, loc=loc) + error(t, "FAIL", loc=loc) } t.error_count += 1 if t._fail_now != nil { @@ -84,14 +84,14 @@ cleanup :: proc(t: ^T, procedure: proc(rawptr), user_data: rawptr) { expect :: proc(t: ^T, ok: bool, msg: string = "", loc := #caller_location) -> bool { if !ok { - error(t=t, args={msg}, loc=loc) + error(t, msg, loc=loc) } return ok } expect_value :: proc(t: ^T, value, expected: $T, loc := #caller_location) -> bool where intrinsics.type_is_comparable(T) { ok := value == expected if !ok { - errorf(t=t, format="expected %v, got %v", args={expected, value}, loc=loc) + errorf(t, "expected %v, got %v", expected, value, loc=loc) } return ok } diff --git a/core/text/table/table.odin b/core/text/table/table.odin index 2b60df98f..8d96cb26f 100644 --- a/core/text/table/table.odin +++ b/core/text/table/table.odin @@ -114,7 +114,7 @@ set_cell_alignment :: proc(tbl: ^Table, row, col: int, alignment: Cell_Alignment format :: proc(tbl: ^Table, _fmt: string, args: ..any, loc := #caller_location) -> string { context.allocator = tbl.format_allocator - return fmt.aprintf(fmt = _fmt, args = args) + return fmt.aprintf(_fmt, ..args) } header :: proc(tbl: ^Table, values: ..any, loc := #caller_location) { diff --git a/examples/demo/demo.odin b/examples/demo/demo.odin index 6863a7768..7c98ca728 100644 --- a/examples/demo/demo.odin +++ b/examples/demo/demo.odin @@ -1175,13 +1175,13 @@ threading_example :: proc() { N :: 3 pool: thread.Pool - thread.pool_init(pool=&pool, allocator=context.allocator, thread_count=N) + thread.pool_init(&pool, allocator=context.allocator, thread_count=N) defer thread.pool_destroy(&pool) for i in 0..<30 { // be mindful of the allocator used for tasks. The allocator needs to be thread safe, or be owned by the task for exclusive use - thread.pool_add_task(pool=&pool, allocator=context.allocator, procedure=task_proc, data=nil, user_index=i) + thread.pool_add_task(&pool, allocator=context.allocator, procedure=task_proc, data=nil, user_index=i) } thread.pool_start(&pool) From 15e31e47fa73fdf9d71321c4a3021c37c063cca8 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 21 Jun 2023 01:24:57 +0100 Subject: [PATCH 49/97] Remove in order requirement for named parameters --- src/check_expr.cpp | 107 --------------------------------------------- 1 file changed, 107 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 8731a0caf..9d87ffdea 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -117,8 +117,6 @@ gb_internal void check_or_return_split_types(CheckerContext *c, Operand *x, Stri gb_internal bool is_diverging_expr(Ast *expr); -gb_internal CallArgumentError check_order_of_call_arguments(CheckerContext *c, Type *proc_type, Ast *call, bool show_error); - gb_internal isize get_procedure_param_count_excluding_defaults(Type *pt, isize *param_count_); enum LoadDirectiveResult { @@ -5413,10 +5411,6 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A } }); - if (check_order_of_call_arguments(c, proc_type, call, show_error)) { - return CallArgumentError_OutOfOrderParameters; - } - isize positional_operand_count = positional_operands.count; if (variadic) { positional_operand_count = gb_min(positional_operands.count, pt->variadic_index); @@ -5839,99 +5833,6 @@ gb_internal bool evaluate_where_clauses(CheckerContext *ctx, Ast *call_expr, Sco return true; } -gb_internal CallArgumentError check_order_of_call_arguments(CheckerContext *c, Type *proc_type, Ast *call, bool show_error) { - CallArgumentError err = CallArgumentError_None; - - if (proc_type == nullptr || !is_type_proc(proc_type)) { - // ignore for the time being - return err; - } - - ast_node(ce, CallExpr, call); - if (!is_call_expr_field_value(ce)) { - return err; - } - - TypeProc *pt = &base_type(proc_type)->Proc; - isize param_count = pt->param_count; - - TEMPORARY_ALLOCATOR_GUARD(); - - auto arg_order_indices = slice_make(temporary_allocator(), ce->args.count); - auto visited = slice_make(temporary_allocator(), param_count); - - for_array(i, ce->args) { - Ast *arg = ce->args[i]; - ast_node(fv, FieldValue, arg); - - Ast *field = fv->field; - String name = {}; - if (field != nullptr && field->kind == Ast_Ident) { - name = field->Ident.token.string; - } else { - err = CallArgumentError_InvalidFieldValue; - if (show_error) { - gbString s = expr_to_string(arg); - error(arg, "Invalid parameter name '%s' in procedure call", s); - gb_string_free(s); - } - } - - isize index = lookup_procedure_parameter(pt, name); - if (index < 0) { - err = CallArgumentError_InvalidFieldValue; - if (show_error) { - error(arg, "No parameter named '%.*s' for this procedure type", LIT(name)); - } - } - if (visited[index]) { - err = CallArgumentError_DuplicateParameter; - if (show_error) { - error(arg, "Duplicate parameter '%.*s' in procedure call", LIT(name)); - } - } - arg_order_indices[i] = index; - } - - if (err) { - return err; - } - - for (isize i = 0; i < arg_order_indices.count-1; i++) { - isize a = arg_order_indices[i+0]; - isize b = arg_order_indices[i+1]; - GB_ASSERT(a != b); - if (a > b) { - err = CallArgumentError_OutOfOrderParameters; - if (show_error) { - Ast *arg_a = ce->args[i+0]; - Ast *arg_b = ce->args[i+1]; - - isize curr_a_order = a; - for (isize j = i; j >= 0; j--) { - isize j_order = arg_order_indices[j]; - if (b < j_order && j_order < curr_a_order) { - curr_a_order = j_order; - arg_a = ce->args[j]; - } - } - - - ast_node(fv_a, FieldValue, arg_a); - ast_node(fv_b, FieldValue, arg_b); - - gbString s_a = expr_to_string(fv_a->field); - gbString s_b = expr_to_string(fv_b->field); - error(arg_b, "Parameter names out of order, expected '%s' to be called before '%s'", s_b, s_a); - gb_string_free(s_b); - gb_string_free(s_a); - } - } - } - - return err; -} - gb_internal bool check_named_arguments(CheckerContext *c, Type *type, Slice const &named_args, Array *named_operands, bool show_error) { bool success = true; @@ -5974,12 +5875,6 @@ gb_internal bool check_named_arguments(CheckerContext *c, Type *type, Sliceparams->Tuple.variables[param_index]; if (!is_type_polymorphic(e->type)) { type_hint = e->type; @@ -6498,8 +6393,6 @@ gb_internal CallArgumentData check_call_arguments_new_and_improved(CheckerContex bool any_failure = false; - // bool vari_expand = (ce->ellipsis.pos.line != 0); - // Split positional and named args into separate arrays/slices Slice positional_args = {}; Slice named_args = {}; From 8a890fd3d3cc1d11417d1da37bced9ba5715e612 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 21 Jun 2023 01:32:35 +0100 Subject: [PATCH 50/97] Remove `new_and_improved` in the name --- src/check_expr.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 9d87ffdea..99017f434 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -5893,7 +5893,7 @@ gb_internal bool check_named_arguments(CheckerContext *c, Type *type, Slice const &positional_operands, Array const &named_operands, CallArgumentErrorMode show_error_mode, @@ -5963,7 +5963,7 @@ gb_internal bool check_call_arguments_new_and_improved_single(CheckerContext *c, } -gb_internal CallArgumentData check_call_arguments_new_and_improved_proc_group(CheckerContext *c, Operand *operand, Ast *call) { +gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c, Operand *operand, Ast *call) { ast_node(ce, CallExpr, call); GB_ASSERT(ce->split_args != nullptr); @@ -6063,7 +6063,7 @@ gb_internal CallArgumentData check_call_arguments_new_and_improved_proc_group(Ch check_unpack_arguments(c, lhs, lhs_count, &positional_operands, positional_args, is_variadic ? UnpackFlag_IsVariadic : UnpackFlag_None); if (check_named_arguments(c, e->type, named_args, &named_operands, true)) { - check_call_arguments_new_and_improved_single(c, call, operand, + check_call_arguments_single(c, call, operand, e, e->type, positional_operands, named_operands, CallArgumentErrorMode::ShowErrors, @@ -6187,7 +6187,7 @@ gb_internal CallArgumentData check_call_arguments_new_and_improved_proc_group(Ch ctx.allow_polymorphic_types = is_type_polymorphic(pt); ctx.hide_polymorphic_errors = true; - bool is_a_candidate = check_call_arguments_new_and_improved_single(&ctx, call, operand, + bool is_a_candidate = check_call_arguments_single(&ctx, call, operand, p, pt, positional_operands, named_operands, CallArgumentErrorMode::NoErrors, @@ -6363,7 +6363,7 @@ gb_internal CallArgumentData check_call_arguments_new_and_improved_proc_group(Ch Array named_operands = {}; - check_call_arguments_new_and_improved_single(c, call, operand, + check_call_arguments_single(c, call, operand, e, e->type, positional_operands, named_operands, CallArgumentErrorMode::ShowErrors, @@ -6375,7 +6375,7 @@ gb_internal CallArgumentData check_call_arguments_new_and_improved_proc_group(Ch } -gb_internal CallArgumentData check_call_arguments_new_and_improved(CheckerContext *c, Operand *operand, Ast *call) { +gb_internal CallArgumentData check_call_arguments(CheckerContext *c, Operand *operand, Ast *call) { Type *proc_type = nullptr; CallArgumentData data = {}; @@ -6418,7 +6418,7 @@ gb_internal CallArgumentData check_call_arguments_new_and_improved(CheckerContex } if (operand->mode == Addressing_ProcGroup) { - return check_call_arguments_new_and_improved_proc_group(c, operand, call); + return check_call_arguments_proc_group(c, operand, call); } auto positional_operands = array_make(heap_allocator(), 0, positional_args.count); @@ -6473,7 +6473,7 @@ gb_internal CallArgumentData check_call_arguments_new_and_improved(CheckerContex } if (!any_failure) { - check_call_arguments_new_and_improved_single(c, call, operand, + check_call_arguments_single(c, call, operand, nullptr, nullptr, positional_operands, named_operands, CallArgumentErrorMode::ShowErrors, @@ -7045,7 +7045,7 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c } } - CallArgumentData data = check_call_arguments_new_and_improved(c, operand, call); + CallArgumentData data = check_call_arguments(c, operand, call); Type *result_type = data.result_type; gb_zero_item(operand); operand->expr = call; From 735181dc0ec11bdd7293bae01cc07450e5dd788b Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 21 Jun 2023 01:33:37 +0100 Subject: [PATCH 51/97] Remove unnecessary indent --- src/check_type.cpp | 142 ++++++++++++++++++++++----------------------- 1 file changed, 70 insertions(+), 72 deletions(-) diff --git a/src/check_type.cpp b/src/check_type.cpp index f241f35ee..a68f83ba9 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -1665,81 +1665,79 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para if (operands != nullptr && variables.count < operands->count) { Operand op = (*operands)[variables.count]; - { - if (op.expr == nullptr) { - // NOTE(bill): 2019-03-30 - // This is just to add the error message to determine_type_from_polymorphic which - // depends on valid position information - op.expr = _params; - op.mode = Addressing_Invalid; - op.type = t_invalid; - } - if (is_type_polymorphic_type) { - type = determine_type_from_polymorphic(ctx, type, op); - if (type == t_invalid) { - success = false; - } else if (!ctx->no_polymorphic_errors) { - // NOTE(bill): The type should be determined now and thus, no need to determine the type any more - is_type_polymorphic_type = false; - Entity *proc_entity = entity_from_expr(op.expr); - if ((proc_entity != nullptr) && (op.value.kind == ExactValue_Procedure)) { - if (is_type_polymorphic(proc_entity->type, false)) { - error(op.expr, "Cannot determine complete type of partial polymorphic procedure"); - } - } - } - } - if (is_poly_name) { - bool valid = false; - if (is_type_proc(op.type)) { - Ast *expr = unparen_expr(op.expr); - Entity *proc_entity = entity_from_expr(expr); - if (proc_entity) { - poly_const = exact_value_procedure(proc_entity->identifier.load() ? proc_entity->identifier.load() : op.expr); - valid = true; - } else if (expr->kind == Ast_ProcLit) { - poly_const = exact_value_procedure(expr); - valid = true; - } - } - if (!valid) { - if (op.mode == Addressing_Constant) { - poly_const = op.value; - } else { - error(op.expr, "Expected a constant value for this polymorphic name parameter, got %s", expr_to_string(op.expr)); - success = false; - } - } - } - if (type != t_invalid && !check_is_assignable_to(ctx, &op, type)) { - bool ok = true; - if (p->flags&FieldFlag_any_int) { - if ((!is_type_integer(op.type) && !is_type_enum(op.type)) || (!is_type_integer(type) && !is_type_enum(type))) { - ok = false; - } else if (!check_is_castable_to(ctx, &op, type)) { - ok = false; - } - } - if (!ok) { - success = false; - #if 0 - gbString got = type_to_string(op.type); - gbString expected = type_to_string(type); - error(op.expr, "Cannot assigned type to parameter, got type '%s', expected '%s'", got, expected); - gb_string_free(expected); - gb_string_free(got); - #endif - } - } - - if (is_type_untyped(default_type(type))) { - gbString str = type_to_string(type); - error(op.expr, "Cannot determine type from the parameter, got '%s'", str); - gb_string_free(str); + if (op.expr == nullptr) { + // NOTE(bill): 2019-03-30 + // This is just to add the error message to determine_type_from_polymorphic which + // depends on valid position information + op.expr = _params; + op.mode = Addressing_Invalid; + op.type = t_invalid; + } + if (is_type_polymorphic_type) { + type = determine_type_from_polymorphic(ctx, type, op); + if (type == t_invalid) { success = false; - type = t_invalid; + } else if (!ctx->no_polymorphic_errors) { + // NOTE(bill): The type should be determined now and thus, no need to determine the type any more + is_type_polymorphic_type = false; + Entity *proc_entity = entity_from_expr(op.expr); + if ((proc_entity != nullptr) && (op.value.kind == ExactValue_Procedure)) { + if (is_type_polymorphic(proc_entity->type, false)) { + error(op.expr, "Cannot determine complete type of partial polymorphic procedure"); + } + } } } + if (is_poly_name) { + bool valid = false; + if (is_type_proc(op.type)) { + Ast *expr = unparen_expr(op.expr); + Entity *proc_entity = entity_from_expr(expr); + if (proc_entity) { + poly_const = exact_value_procedure(proc_entity->identifier.load() ? proc_entity->identifier.load() : op.expr); + valid = true; + } else if (expr->kind == Ast_ProcLit) { + poly_const = exact_value_procedure(expr); + valid = true; + } + } + if (!valid) { + if (op.mode == Addressing_Constant) { + poly_const = op.value; + } else { + error(op.expr, "Expected a constant value for this polymorphic name parameter, got %s", expr_to_string(op.expr)); + success = false; + } + } + } + if (type != t_invalid && !check_is_assignable_to(ctx, &op, type)) { + bool ok = true; + if (p->flags&FieldFlag_any_int) { + if ((!is_type_integer(op.type) && !is_type_enum(op.type)) || (!is_type_integer(type) && !is_type_enum(type))) { + ok = false; + } else if (!check_is_castable_to(ctx, &op, type)) { + ok = false; + } + } + if (!ok) { + success = false; + #if 0 + gbString got = type_to_string(op.type); + gbString expected = type_to_string(type); + error(op.expr, "Cannot assigned type to parameter, got type '%s', expected '%s'", got, expected); + gb_string_free(expected); + gb_string_free(got); + #endif + } + } + + if (is_type_untyped(default_type(type))) { + gbString str = type_to_string(type); + error(op.expr, "Cannot determine type from the parameter, got '%s'", str); + gb_string_free(str); + success = false; + type = t_invalid; + } } if (p->flags&FieldFlag_no_alias) { From 43ba2c6226dc71754c7aa74ed8ecb150231a9624 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 21 Jun 2023 12:10:07 +0100 Subject: [PATCH 52/97] Fix constant parameter passing --- src/llvm_backend_proc.cpp | 44 ++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index fc58d584e..0beaf376b 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -3168,10 +3168,13 @@ gb_internal lbValue lb_build_call_expr_internal_with_arg_split_args(lbProcedure if (e->kind == Entity_TypeName) { array_add(&args, lb_const_nil(p->module, e->type)); continue; - } else if (e->kind != Entity_Variable) { + } else if (e->kind == Entity_Constant) { + array_add(&args, lb_const_value(p->module, e->type, e->Constant.value)); continue; } + GB_ASSERT(e->kind == Entity_Variable); + if (pt->variadic && pt->variadic_index == i) { lbValue variadic_args = lb_const_nil(p->module, e->type); auto variadic = slice(split_args->positional, pt->variadic_index, split_args->positional.count); @@ -3238,23 +3241,30 @@ gb_internal lbValue lb_build_call_expr_internal_with_arg_split_args(lbProcedure TokenPos pos = ast_token(ce->proc).pos; - for_array(i, args) { - Entity *e = pt->params->Tuple.variables[i]; - lbValue arg = args[i]; - if (arg.value == nullptr) { - switch (e->kind) { - case Entity_TypeName: - args[i] = lb_const_nil(p->module, e->type); - break; - case Entity_Variable: - args[i] = lb_handle_param_value(p, e->type, e->Variable.param_value, pos); - break; - case Entity_Constant: - args[i] = lb_handle_param_value(p, e->type, e->Constant.param_value, pos); - break; + + if (pt->params != nullptr) { + for_array(arg_index, pt->params->Tuple.variables) { + Entity *e = pt->params->Tuple.variables[arg_index]; + + lbValue arg = args[arg_index]; + if (arg.value == nullptr) { + switch (e->kind) { + case Entity_TypeName: + args[arg_index] = lb_const_nil(p->module, e->type); + break; + case Entity_Variable: + args[arg_index] = lb_handle_param_value(p, e->type, e->Variable.param_value, pos); + break; + + case Entity_Constant: + args[arg_index] = lb_const_value(p->module, e->type, e->Constant.value); + break; + default: + GB_PANIC("Unknown entity kind %.*s\n", LIT(entity_strings[e->kind])); + } + } else { + args[arg_index] = lb_emit_conv(p, arg, e->type); } - } else { - args[i] = lb_emit_conv(p, arg, e->type); } } From 180003035659d56a898ddf7de59e8630493c2147 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 21 Jun 2023 14:01:46 +0100 Subject: [PATCH 53/97] Correct deferred procedures --- src/llvm_backend_proc.cpp | 256 +++++++++++++++++++++----------------- 1 file changed, 139 insertions(+), 117 deletions(-) diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 0beaf376b..48b4b9330 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -1160,7 +1160,13 @@ gb_internal lbValue lb_emit_call(lbProcedure *p, lbValue value, Array c } - Entity **found = map_get(&p->module->procedure_values, value.value); + LLVMValueRef the_proc_value = value.value; + + if (LLVMIsAConstantExpr(the_proc_value)) { + // NOTE(bill): it's a bit cast + the_proc_value = LLVMGetOperand(the_proc_value, 0); + } + Entity **found = map_get(&p->module->procedure_values, the_proc_value); if (found != nullptr) { Entity *e = *found; if (e != nullptr && entity_has_deferred_procedure(e)) { @@ -3156,121 +3162,6 @@ gb_internal void lb_add_values_to_array(lbProcedure *p, Array *args, lb array_add(args, value); } } -gb_internal lbValue lb_build_call_expr_internal_with_arg_split_args(lbProcedure *p, lbValue value, TypeProc *pt, Ast *call, AstSplitArgs *split_args) { - ast_node(ce, CallExpr, call); - - auto args = array_make(permanent_allocator(), 0, pt->param_count); - - bool vari_expand = (ce->ellipsis.pos.line != 0); - - for_array(i, split_args->positional) { - Entity *e = pt->params->Tuple.variables[i]; - if (e->kind == Entity_TypeName) { - array_add(&args, lb_const_nil(p->module, e->type)); - continue; - } else if (e->kind == Entity_Constant) { - array_add(&args, lb_const_value(p->module, e->type, e->Constant.value)); - continue; - } - - GB_ASSERT(e->kind == Entity_Variable); - - if (pt->variadic && pt->variadic_index == i) { - lbValue variadic_args = lb_const_nil(p->module, e->type); - auto variadic = slice(split_args->positional, pt->variadic_index, split_args->positional.count); - if (variadic.count != 0) { - // variadic call argument generation - Type *slice_type = e->type; - GB_ASSERT(slice_type->kind == Type_Slice); - if (vari_expand) { - GB_ASSERT(variadic.count == 1); - variadic_args = lb_build_expr(p, variadic[0]); - variadic_args = lb_emit_conv(p, variadic_args, slice_type); - } else { - Type *elem_type = slice_type->Slice.elem; - - auto var_args = array_make(heap_allocator(), 0, variadic.count); - defer (array_free(&var_args)); - for (Ast *var_arg : variadic) { - lbValue v = lb_build_expr(p, var_arg); - lb_add_values_to_array(p, &var_args, v); - } - isize slice_len = var_args.count; - if (slice_len > 0) { - lbAddr slice = lb_add_local_generated(p, slice_type, true); - lbAddr base_array = lb_add_local_generated(p, alloc_type_array(elem_type, slice_len), true); - - for (isize i = 0; i < var_args.count; i++) { - lbValue addr = lb_emit_array_epi(p, base_array.addr, cast(i32)i); - lbValue var_arg = var_args[i]; - var_arg = lb_emit_conv(p, var_arg, elem_type); - lb_emit_store(p, addr, var_arg); - } - - lbValue base_elem = lb_emit_array_epi(p, base_array.addr, 0); - lbValue len = lb_const_int(p->module, t_int, slice_len); - lb_fill_slice(p, slice, base_elem, len); - - variadic_args = lb_addr_load(p, slice); - } - } - } - array_add(&args, variadic_args); - - break; - } else { - lbValue value = lb_build_expr(p, split_args->positional[i]); - lb_add_values_to_array(p, &args, value); - } - } - - array_resize(&args, pt->param_count); - - for (Ast *arg : split_args->named) { - ast_node(fv, FieldValue, arg); - GB_ASSERT(fv->field->kind == Ast_Ident); - String name = fv->field->Ident.token.string; - gb_unused(name); - isize param_index = lookup_procedure_parameter(pt, name); - GB_ASSERT(param_index >= 0); - - lbValue value = lb_build_expr(p, fv->value); - GB_ASSERT(!is_type_tuple(value.type)); - args[param_index] = value; - } - - TokenPos pos = ast_token(ce->proc).pos; - - - if (pt->params != nullptr) { - for_array(arg_index, pt->params->Tuple.variables) { - Entity *e = pt->params->Tuple.variables[arg_index]; - - lbValue arg = args[arg_index]; - if (arg.value == nullptr) { - switch (e->kind) { - case Entity_TypeName: - args[arg_index] = lb_const_nil(p->module, e->type); - break; - case Entity_Variable: - args[arg_index] = lb_handle_param_value(p, e->type, e->Variable.param_value, pos); - break; - - case Entity_Constant: - args[arg_index] = lb_const_value(p->module, e->type, e->Constant.value); - break; - default: - GB_PANIC("Unknown entity kind %.*s\n", LIT(entity_strings[e->kind])); - } - } else { - args[arg_index] = lb_emit_conv(p, arg, e->type); - } - } - } - - - return lb_emit_call(p, value, args, ce->inlining); -} gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { lbModule *m = p->module; @@ -3347,6 +3238,137 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { TypeProc *pt = &proc_type_->Proc; GB_ASSERT(ce->split_args != nullptr); - return lb_build_call_expr_internal_with_arg_split_args(p, value, pt, expr, ce->split_args); + + auto args = array_make(permanent_allocator(), 0, pt->param_count); + + bool vari_expand = (ce->ellipsis.pos.line != 0); + bool is_c_vararg = pt->c_vararg; + + for_array(i, ce->split_args->positional) { + Entity *e = pt->params->Tuple.variables[i]; + if (e->kind == Entity_TypeName) { + array_add(&args, lb_const_nil(p->module, e->type)); + continue; + } else if (e->kind == Entity_Constant) { + array_add(&args, lb_const_value(p->module, e->type, e->Constant.value)); + continue; + } + + GB_ASSERT(e->kind == Entity_Variable); + + if (pt->variadic && pt->variadic_index == i) { + lbValue variadic_args = lb_const_nil(p->module, e->type); + auto variadic = slice(ce->split_args->positional, pt->variadic_index, ce->split_args->positional.count); + if (variadic.count != 0) { + // variadic call argument generation + Type *slice_type = e->type; + GB_ASSERT(slice_type->kind == Type_Slice); + + if (is_c_vararg) { + GB_PANIC("TODO #c_vararg"); + GB_ASSERT(!vari_expand); + + Type *elem_type = slice_type->Slice.elem; + + for (Ast *var_arg : variadic) { + lbValue arg = lb_build_expr(p, var_arg); + if (!is_type_any(elem_type)) { + array_add(&args, lb_emit_conv(p, arg, elem_type)); + } else { + array_add(&args, lb_emit_conv(p, arg, default_type(arg.type))); + } + } + break; + } else if (vari_expand) { + GB_ASSERT(variadic.count == 1); + variadic_args = lb_build_expr(p, variadic[0]); + variadic_args = lb_emit_conv(p, variadic_args, slice_type); + } else { + Type *elem_type = slice_type->Slice.elem; + + auto var_args = array_make(heap_allocator(), 0, variadic.count); + defer (array_free(&var_args)); + for (Ast *var_arg : variadic) { + lbValue v = lb_build_expr(p, var_arg); + lb_add_values_to_array(p, &var_args, v); + } + isize slice_len = var_args.count; + if (slice_len > 0) { + lbAddr slice = lb_add_local_generated(p, slice_type, true); + lbAddr base_array = lb_add_local_generated(p, alloc_type_array(elem_type, slice_len), true); + + for (isize i = 0; i < var_args.count; i++) { + lbValue addr = lb_emit_array_epi(p, base_array.addr, cast(i32)i); + lbValue var_arg = var_args[i]; + var_arg = lb_emit_conv(p, var_arg, elem_type); + lb_emit_store(p, addr, var_arg); + } + + lbValue base_elem = lb_emit_array_epi(p, base_array.addr, 0); + lbValue len = lb_const_int(p->module, t_int, slice_len); + lb_fill_slice(p, slice, base_elem, len); + + variadic_args = lb_addr_load(p, slice); + } + } + } + array_add(&args, variadic_args); + + break; + } else { + lbValue value = lb_build_expr(p, ce->split_args->positional[i]); + lb_add_values_to_array(p, &args, value); + } + } + + array_resize(&args, pt->param_count); + + for (Ast *arg : ce->split_args->named) { + ast_node(fv, FieldValue, arg); + GB_ASSERT(fv->field->kind == Ast_Ident); + String name = fv->field->Ident.token.string; + gb_unused(name); + isize param_index = lookup_procedure_parameter(pt, name); + GB_ASSERT(param_index >= 0); + + lbValue value = lb_build_expr(p, fv->value); + GB_ASSERT(!is_type_tuple(value.type)); + args[param_index] = value; + } + + TokenPos pos = ast_token(ce->proc).pos; + + + if (pt->params != nullptr) { + GB_ASSERT(args.count >= pt->params->Tuple.variables.count); + for_array(arg_index, pt->params->Tuple.variables) { + Entity *e = pt->params->Tuple.variables[arg_index]; + + lbValue arg = args[arg_index]; + if (arg.value == nullptr) { + switch (e->kind) { + case Entity_TypeName: + args[arg_index] = lb_const_nil(p->module, e->type); + break; + case Entity_Variable: + args[arg_index] = lb_handle_param_value(p, e->type, e->Variable.param_value, pos); + break; + + case Entity_Constant: + args[arg_index] = lb_const_value(p->module, e->type, e->Constant.value); + break; + default: + GB_PANIC("Unknown entity kind %.*s\n", LIT(entity_strings[e->kind])); + } + } else { + args[arg_index] = lb_emit_conv(p, arg, e->type); + } + } + } + + + isize final_count = is_c_vararg ? args.count : pt->param_count; + auto call_args = array_slice(args, 0, final_count); + return lb_emit_call(p, value, call_args, ce->inlining); } From c9fb078c0f1e09edcc023dce30dd0ca04f20cc24 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 21 Jun 2023 14:07:14 +0100 Subject: [PATCH 54/97] Handle `#c_vararg` --- src/llvm_backend_proc.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 48b4b9330..7cf82b208 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -3265,17 +3265,16 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { GB_ASSERT(slice_type->kind == Type_Slice); if (is_c_vararg) { - GB_PANIC("TODO #c_vararg"); GB_ASSERT(!vari_expand); Type *elem_type = slice_type->Slice.elem; for (Ast *var_arg : variadic) { lbValue arg = lb_build_expr(p, var_arg); - if (!is_type_any(elem_type)) { - array_add(&args, lb_emit_conv(p, arg, elem_type)); - } else { + if (is_type_any(elem_type)) { array_add(&args, lb_emit_conv(p, arg, default_type(arg.type))); + } else { + array_add(&args, lb_emit_conv(p, arg, elem_type)); } } break; @@ -3321,7 +3320,9 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { } } - array_resize(&args, pt->param_count); + if (!is_c_vararg) { + array_resize(&args, pt->param_count); + } for (Ast *arg : ce->split_args->named) { ast_node(fv, FieldValue, arg); @@ -3343,6 +3344,9 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { GB_ASSERT(args.count >= pt->params->Tuple.variables.count); for_array(arg_index, pt->params->Tuple.variables) { Entity *e = pt->params->Tuple.variables[arg_index]; + if (pt->variadic && arg_index == pt->variadic_index) { + continue; + } lbValue arg = args[arg_index]; if (arg.value == nullptr) { @@ -3366,7 +3370,6 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { } } - isize final_count = is_c_vararg ? args.count : pt->param_count; auto call_args = array_slice(args, 0, final_count); return lb_emit_call(p, value, call_args, ce->inlining); From ea76e09ea741f0bf090d967cf3aaded59079d001 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 21 Jun 2023 14:30:39 +0100 Subject: [PATCH 55/97] Fix empty varargs --- src/llvm_backend_proc.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 7cf82b208..47137c540 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -3345,6 +3345,9 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { for_array(arg_index, pt->params->Tuple.variables) { Entity *e = pt->params->Tuple.variables[arg_index]; if (pt->variadic && arg_index == pt->variadic_index) { + if (!is_c_vararg && args[arg_index].value == 0) { + args[arg_index] = lb_const_nil(p->module, e->type); + } continue; } From c48057081e451c81524c7727ec3ccf434a45726f Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 21 Jun 2023 14:39:23 +0100 Subject: [PATCH 56/97] Fix nullptr entity case --- src/check_expr.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 99017f434..83e44b39f 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -5908,11 +5908,13 @@ gb_internal bool check_call_arguments_single(CheckerContext *c, Ast *call, Opera } if (e == nullptr) { - GB_ASSERT(proc_type == nullptr); e = entity_of_node(ident); - proc_type = e->type; + if (e != nullptr) { + proc_type = e->type; + } } + GB_ASSERT(proc_type != nullptr); proc_type = base_type(proc_type); GB_ASSERT(proc_type->kind == Type_Proc); @@ -5922,12 +5924,10 @@ gb_internal bool check_call_arguments_single(CheckerContext *c, Ast *call, Opera } Entity *entity_to_use = data->gen_entity != nullptr ? data->gen_entity : e; - if (!return_on_failure) { + if (!return_on_failure && entity_to_use != nullptr) { add_entity_use(c, ident, entity_to_use); - if (entity_to_use != nullptr) { - update_untyped_expr_type(c, operand->expr, entity_to_use->type, true); - add_type_and_value(c, operand->expr, operand->mode, entity_to_use->type, operand->value); - } + update_untyped_expr_type(c, operand->expr, entity_to_use->type, true); + add_type_and_value(c, operand->expr, operand->mode, entity_to_use->type, operand->value); } if (data->gen_entity != nullptr) { @@ -6474,7 +6474,7 @@ gb_internal CallArgumentData check_call_arguments(CheckerContext *c, Operand *op if (!any_failure) { check_call_arguments_single(c, call, operand, - nullptr, nullptr, + nullptr, proc_type, positional_operands, named_operands, CallArgumentErrorMode::ShowErrors, &data); From e3b43b6e1c5ddb459a7bfda46daa232c874c5771 Mon Sep 17 00:00:00 2001 From: Rehkitzdev Date: Thu, 22 Jun 2023 13:35:22 +0200 Subject: [PATCH 57/97] fixed dynlib wasm stub --- core/dynlib/lib_js.odin | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/dynlib/lib_js.odin b/core/dynlib/lib_js.odin index 37dab8758..ace1b0939 100644 --- a/core/dynlib/lib_js.odin +++ b/core/dynlib/lib_js.odin @@ -3,13 +3,13 @@ package dynlib _load_library :: proc(path: string, global_symbols := false) -> (Library, bool) { - return + return nil, false } _unload_library :: proc(library: Library) -> bool { - return + return false } _symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found: bool) { - return + return nil, false } From 5a6d5374d780e726be82f3576b4f647d9096d012 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 22 Jun 2023 14:30:02 +0100 Subject: [PATCH 58/97] Update WebGL procedures to `contextless` calling convention --- vendor/wasm/WebGL/webgl.odin | 67 +++++++++++++++++------------------ vendor/wasm/WebGL/webgl2.odin | 42 +++++++++++----------- 2 files changed, 53 insertions(+), 56 deletions(-) diff --git a/vendor/wasm/WebGL/webgl.odin b/vendor/wasm/WebGL/webgl.odin index 4fa6c4c1d..7337e2a11 100644 --- a/vendor/wasm/WebGL/webgl.odin +++ b/vendor/wasm/WebGL/webgl.odin @@ -27,7 +27,7 @@ ContextAttributes :: distinct bit_set[ContextAttribute; u32] DEFAULT_CONTEXT_ATTRIBUTES :: ContextAttributes{} -@(default_calling_convention="c") +@(default_calling_convention="contextless") foreign webgl { // CreateCurrentContextById must be called before `GetCurrentContextAttributes` if the user wants to // set specific attributes, otherwise the default attributes will be set for the WebGL context @@ -168,61 +168,59 @@ foreign webgl { Viewport :: proc(x, y, w, h: i32) --- } -Uniform1fv :: proc "c" (location: i32, v: f32) { Uniform1f(location, v) } -Uniform2fv :: proc "c" (location: i32, v: glm.vec2) { Uniform2f(location, v.x, v.y) } -Uniform3fv :: proc "c" (location: i32, v: glm.vec3) { Uniform3f(location, v.x, v.y, v.z) } -Uniform4fv :: proc "c" (location: i32, v: glm.vec4) { Uniform4f(location, v.x, v.y, v.z, v.w) } -Uniform1iv :: proc "c" (location: i32, v: i32) { Uniform1i(location, v) } -Uniform2iv :: proc "c" (location: i32, v: glm.ivec2) { Uniform2i(location, v.x, v.y) } -Uniform3iv :: proc "c" (location: i32, v: glm.ivec3) { Uniform3i(location, v.x, v.y, v.z) } -Uniform4iv :: proc "c" (location: i32, v: glm.ivec4) { Uniform4i(location, v.x, v.y, v.z, v.w) } +Uniform1fv :: proc "contextless" (location: i32, v: f32) { Uniform1f(location, v) } +Uniform2fv :: proc "contextless" (location: i32, v: glm.vec2) { Uniform2f(location, v.x, v.y) } +Uniform3fv :: proc "contextless" (location: i32, v: glm.vec3) { Uniform3f(location, v.x, v.y, v.z) } +Uniform4fv :: proc "contextless" (location: i32, v: glm.vec4) { Uniform4f(location, v.x, v.y, v.z, v.w) } +Uniform1iv :: proc "contextless" (location: i32, v: i32) { Uniform1i(location, v) } +Uniform2iv :: proc "contextless" (location: i32, v: glm.ivec2) { Uniform2i(location, v.x, v.y) } +Uniform3iv :: proc "contextless" (location: i32, v: glm.ivec3) { Uniform3i(location, v.x, v.y, v.z) } +Uniform4iv :: proc "contextless" (location: i32, v: glm.ivec4) { Uniform4i(location, v.x, v.y, v.z, v.w) } -VertexAttrib1fv :: proc "c" (index: i32, v: f32) { VertexAttrib1f(index, v) } -VertexAttrib2fv :: proc "c" (index: i32, v: glm.vec2){ VertexAttrib2f(index, v.x, v.y) } -VertexAttrib3fv :: proc "c" (index: i32, v: glm.vec3){ VertexAttrib3f(index, v.x, v.y, v.z) } -VertexAttrib4fv :: proc "c" (index: i32, v: glm.vec4){ VertexAttrib4f(index, v.x, v.y, v.z, v.w) } +VertexAttrib1fv :: proc "contextless" (index: i32, v: f32) { VertexAttrib1f(index, v) } +VertexAttrib2fv :: proc "contextless" (index: i32, v: glm.vec2){ VertexAttrib2f(index, v.x, v.y) } +VertexAttrib3fv :: proc "contextless" (index: i32, v: glm.vec3){ VertexAttrib3f(index, v.x, v.y, v.z) } +VertexAttrib4fv :: proc "contextless" (index: i32, v: glm.vec4){ VertexAttrib4f(index, v.x, v.y, v.z, v.w) } -UniformMatrix2fv :: proc "c" (location: i32, m: glm.mat2) { +UniformMatrix2fv :: proc "contextless" (location: i32, m: glm.mat2) { foreign webgl { @(link_name="UniformMatrix2fv") - _UniformMatrix2fv :: proc "c" (location: i32, value: [^]f32) --- + _UniformMatrix2fv :: proc "contextless" (location: i32, value: [^]f32) --- } value := transmute([2*2]f32)m _UniformMatrix2fv(location, &value[0]) } -UniformMatrix3fv :: proc "c" (location: i32, m: glm.mat3) { +UniformMatrix3fv :: proc "contextless" (location: i32, m: glm.mat3) { foreign webgl { @(link_name="UniformMatrix3fv") - _UniformMatrix3fv :: proc "c" (location: i32, value: [^]f32) --- + _UniformMatrix3fv :: proc "contextless" (location: i32, value: [^]f32) --- } value := transmute([3*3]f32)m _UniformMatrix3fv(location, &value[0]) } -UniformMatrix4fv :: proc "c" (location: i32, m: glm.mat4) { +UniformMatrix4fv :: proc "contextless" (location: i32, m: glm.mat4) { foreign webgl { @(link_name="UniformMatrix4fv") - _UniformMatrix4fv :: proc "c" (location: i32, value: [^]f32) --- + _UniformMatrix4fv :: proc "contextless" (location: i32, value: [^]f32) --- } value := transmute([4*4]f32)m _UniformMatrix4fv(location, &value[0]) } -GetShaderiv :: proc "c" (shader: Shader, pname: Enum) -> (p: i32) { - @(default_calling_convention="c") +GetShaderiv :: proc "contextless" (shader: Shader, pname: Enum) -> (p: i32) { foreign webgl { @(link_name="GetShaderiv") - _GetShaderiv :: proc "c" (shader: Shader, pname: Enum, p: ^i32) --- + _GetShaderiv :: proc "contextless" (shader: Shader, pname: Enum, p: ^i32) --- } _GetShaderiv(shader, pname, &p) return } -GetProgramInfoLog :: proc "c" (program: Program, buf: []byte) -> string { - @(default_calling_convention="c") +GetProgramInfoLog :: proc "contextless" (program: Program, buf: []byte) -> string { foreign webgl { @(link_name="GetProgramInfoLog") - _GetProgramInfoLog :: proc "c" (program: Program, buf: []byte, length: ^int) --- + _GetProgramInfoLog :: proc "contextless" (program: Program, buf: []byte, length: ^int) --- } length: int @@ -230,11 +228,10 @@ GetProgramInfoLog :: proc "c" (program: Program, buf: []byte) -> string { return string(buf[:length]) } -GetShaderInfoLog :: proc "c" (shader: Shader, buf: []byte) -> string { - @(default_calling_convention="c") +GetShaderInfoLog :: proc "contextless" (shader: Shader, buf: []byte) -> string { foreign webgl { @(link_name="GetShaderInfoLog") - _GetShaderInfoLog :: proc "c" (shader: Shader, buf: []byte, length: ^int) --- + _GetShaderInfoLog :: proc "contextless" (shader: Shader, buf: []byte, length: ^int) --- } length: int @@ -244,27 +241,27 @@ GetShaderInfoLog :: proc "c" (shader: Shader, buf: []byte) -> string { -BufferDataSlice :: proc "c" (target: Enum, slice: $S/[]$E, usage: Enum) { +BufferDataSlice :: proc "contextless" (target: Enum, slice: $S/[]$E, usage: Enum) { BufferData(target, len(slice)*size_of(E), raw_data(slice), usage) } -BufferSubDataSlice :: proc "c" (target: Enum, offset: uintptr, slice: $S/[]$E) { +BufferSubDataSlice :: proc "contextless" (target: Enum, offset: uintptr, slice: $S/[]$E) { BufferSubData(target, offset, len(slice)*size_of(E), raw_data(slice), usage) } -CompressedTexImage2DSlice :: proc "c" (target: Enum, level: i32, internalformat: Enum, width, height: i32, border: i32, slice: $S/[]$E) { +CompressedTexImage2DSlice :: proc "contextless" (target: Enum, level: i32, internalformat: Enum, width, height: i32, border: i32, slice: $S/[]$E) { CompressedTexImage2DSlice(target, level, internalformat, width, height, border, len(slice)*size_of(E), raw_data(slice)) } -CompressedTexSubImage2DSlice :: proc "c" (target: Enum, level: i32, xoffset, yoffset, width, height: i32, format: Enum, slice: $S/[]$E) { +CompressedTexSubImage2DSlice :: proc "contextless" (target: Enum, level: i32, xoffset, yoffset, width, height: i32, format: Enum, slice: $S/[]$E) { CompressedTexSubImage2DSlice(target, level, level, xoffset, yoffset, width, height, format, len(slice)*size_of(E), raw_data(slice)) } -ReadPixelsSlice :: proc "c" (x, y, width, height: i32, format: Enum, type: Enum, slice: $S/[]$E) { +ReadPixelsSlice :: proc "contextless" (x, y, width, height: i32, format: Enum, type: Enum, slice: $S/[]$E) { ReadnPixels(x, y, width, height, format, type, len(slice)*size_of(E), raw_data(slice)) } -TexImage2DSlice :: proc "c" (target: Enum, level: i32, internalformat: Enum, width, height: i32, border: i32, format, type: Enum, slice: $S/[]$E) { +TexImage2DSlice :: proc "contextless" (target: Enum, level: i32, internalformat: Enum, width, height: i32, border: i32, format, type: Enum, slice: $S/[]$E) { TexImage2D(target, level, internalformat, width, height, border, format, type, len(slice)*size_of(E), raw_data(slice)) } -TexSubImage2DSlice :: proc "c" (target: Enum, level: i32, xoffset, yoffset, width, height: i32, format, type: Enum, slice: $S/[]$E) { +TexSubImage2DSlice :: proc "contextless" (target: Enum, level: i32, xoffset, yoffset, width, height: i32, format, type: Enum, slice: $S/[]$E) { TexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, len(slice)*size_of(E), raw_data(slice)) } \ No newline at end of file diff --git a/vendor/wasm/WebGL/webgl2.odin b/vendor/wasm/WebGL/webgl2.odin index a1d55b703..d360bc396 100644 --- a/vendor/wasm/WebGL/webgl2.odin +++ b/vendor/wasm/WebGL/webgl2.odin @@ -10,13 +10,13 @@ Sync :: distinct u32 TransformFeedback :: distinct u32 VertexArrayObject :: distinct u32 -IsWebGL2Supported :: proc "c" () -> bool { +IsWebGL2Supported :: proc "contextless" () -> bool { major, minor: i32 GetWebGLVersion(&major, &minor) return major >= 2 } -@(default_calling_convention="c") +@(default_calling_convention="contextless") foreign webgl2 { /* Buffer objects */ CopyBufferSubData :: proc(readTarget, writeTarget: Enum, readOffset, writeOffset: int, size: int) --- @@ -110,7 +110,7 @@ foreign webgl2 { GetActiveUniformBlockName :: proc(program: Program, uniformBlockIndex: i32, buf: []byte) -> string { foreign webgl2 { - _GetActiveUniformBlockName :: proc(program: Program, uniformBlockIndex: i32, buf: []byte, length: ^int) --- + _GetActiveUniformBlockName :: proc "contextless" (program: Program, uniformBlockIndex: i32, buf: []byte, length: ^int) --- } n: int _GetActiveUniformBlockName(program, uniformBlockIndex, buf, &n) @@ -118,65 +118,65 @@ GetActiveUniformBlockName :: proc(program: Program, uniformBlockIndex: i32, buf: } -Uniform1uiv :: proc "c" (location: i32, v: u32) { +Uniform1uiv :: proc "contextless" (location: i32, v: u32) { Uniform1ui(location, v) } -Uniform2uiv :: proc "c" (location: i32, v: glm.uvec2) { +Uniform2uiv :: proc "contextless" (location: i32, v: glm.uvec2) { Uniform2ui(location, v.x, v.y) } -Uniform3uiv :: proc "c" (location: i32, v: glm.uvec3) { +Uniform3uiv :: proc "contextless" (location: i32, v: glm.uvec3) { Uniform3ui(location, v.x, v.y, v.z) } -Uniform4uiv :: proc "c" (location: i32, v: glm.uvec4) { +Uniform4uiv :: proc "contextless" (location: i32, v: glm.uvec4) { Uniform4ui(location, v.x, v.y, v.z, v.w) } -UniformMatrix3x2fv :: proc "c" (location: i32, m: glm.mat3x2) { +UniformMatrix3x2fv :: proc "contextless" (location: i32, m: glm.mat3x2) { foreign webgl2 { - _UniformMatrix3x2fv :: proc "c" (location: i32, addr: [^]f32) --- + _UniformMatrix3x2fv :: proc "contextless" (location: i32, addr: [^]f32) --- } array := matrix_flatten(m) _UniformMatrix3x2fv(location, &array[0]) } -UniformMatrix4x2fv :: proc "c" (location: i32, m: glm.mat4x2) { +UniformMatrix4x2fv :: proc "contextless" (location: i32, m: glm.mat4x2) { foreign webgl2 { - _UniformMatrix4x2fv :: proc "c" (location: i32, addr: [^]f32) --- + _UniformMatrix4x2fv :: proc "contextless" (location: i32, addr: [^]f32) --- } array := matrix_flatten(m) _UniformMatrix4x2fv(location, &array[0]) } -UniformMatrix2x3fv :: proc "c" (location: i32, m: glm.mat2x3) { +UniformMatrix2x3fv :: proc "contextless" (location: i32, m: glm.mat2x3) { foreign webgl2 { - _UniformMatrix2x3fv :: proc "c" (location: i32, addr: [^]f32) --- + _UniformMatrix2x3fv :: proc "contextless" (location: i32, addr: [^]f32) --- } array := matrix_flatten(m) _UniformMatrix2x3fv(location, &array[0]) } -UniformMatrix4x3fv :: proc "c" (location: i32, m: glm.mat4x3) { +UniformMatrix4x3fv :: proc "contextless" (location: i32, m: glm.mat4x3) { foreign webgl2 { - _UniformMatrix4x3fv :: proc "c" (location: i32, addr: [^]f32) --- + _UniformMatrix4x3fv :: proc "contextless" (location: i32, addr: [^]f32) --- } array := matrix_flatten(m) _UniformMatrix4x3fv(location, &array[0]) } -UniformMatrix2x4fv :: proc "c" (location: i32, m: glm.mat2x4) { +UniformMatrix2x4fv :: proc "contextless" (location: i32, m: glm.mat2x4) { foreign webgl2 { - _UniformMatrix2x4fv :: proc "c" (location: i32, addr: [^]f32) --- + _UniformMatrix2x4fv :: proc "contextless" (location: i32, addr: [^]f32) --- } array := matrix_flatten(m) _UniformMatrix2x4fv(location, &array[0]) } -UniformMatrix3x4fv :: proc "c" (location: i32, m: glm.mat3x4) { +UniformMatrix3x4fv :: proc "contextless" (location: i32, m: glm.mat3x4) { foreign webgl2 { - _UniformMatrix3x4fv :: proc "c" (location: i32, addr: [^]f32) --- + _UniformMatrix3x4fv :: proc "contextless" (location: i32, addr: [^]f32) --- } array := matrix_flatten(m) _UniformMatrix3x4fv(location, &array[0]) } -VertexAttribI4iv :: proc "c" (index: i32, v: glm.ivec4) { +VertexAttribI4iv :: proc "contextless" (index: i32, v: glm.ivec4) { VertexAttribI4i(index, v.x, v.y, v.z, v.w) } -VertexAttribI4uiv :: proc "c" (index: i32, v: glm.uvec4) { +VertexAttribI4uiv :: proc "contextless" (index: i32, v: glm.uvec4) { VertexAttribI4ui(index, v.x, v.y, v.z, v.w) } From 6b59aee336665d16a3c1dfc295301f0d9c92f4b1 Mon Sep 17 00:00:00 2001 From: skytrias Date: Fri, 23 Jun 2023 16:18:40 +0200 Subject: [PATCH 59/97] cleanup with -vet and add to all_vendor --- examples/all/all_vendor.odin | 10 ++++++- vendor/fontstash/fontstash.odin | 48 +++++++++++++++--------------- vendor/nanovg/gl/gl.odin | 12 ++++---- vendor/nanovg/nanovg.odin | 52 ++++++++++++++++----------------- 4 files changed, 63 insertions(+), 59 deletions(-) diff --git a/examples/all/all_vendor.odin b/examples/all/all_vendor.odin index fe1080e20..fa1e8d995 100644 --- a/examples/all/all_vendor.odin +++ b/examples/all/all_vendor.odin @@ -47,6 +47,10 @@ import CA "vendor:darwin/QuartzCore" // NOTE(bill): only one can be checked at a time import lua_5_4 "vendor:lua/5.4" +import nvg "vendor:nanovg" +import nvg_gl "vendor:nanovg/gl" +import fontstash "vendor:fontstash" + _ :: botan_bindings _ :: botan_blake2b _ :: gost @@ -92,4 +96,8 @@ _ :: MTL _ :: MTK _ :: CA -_ :: lua_5_4 \ No newline at end of file +_ :: lua_5_4 + +_ :: nvg +_ :: nvg_gl +_ :: fontstash \ No newline at end of file diff --git a/vendor/fontstash/fontstash.odin b/vendor/fontstash/fontstash.odin index b4bf86d46..64c1ce7cd 100644 --- a/vendor/fontstash/fontstash.odin +++ b/vendor/fontstash/fontstash.odin @@ -1,12 +1,10 @@ package fontstash import "core:runtime" -import "core:fmt" import "core:log" import "core:os" import "core:mem" import "core:math" -import "core:unicode" import "core:strings" import stbtt "vendor:stb/truetype" @@ -329,7 +327,7 @@ __AtlasAddWhiteRect :: proc(ctx: ^FontContext, w, h: int) { // Rasterize dst := ctx.textureData[gx + gy * ctx.width:] - for y in 0.. (res: ^Glyph) #no_bounds_check { if isize < 2 { return @@ -479,7 +477,7 @@ __getGlyph :: proc( if glyph.codepoint == codepoint && glyph.isize == isize && - glyph.blurSize == blurSize + glyph.blurSize == blur { res = glyph return @@ -506,10 +504,10 @@ __getGlyph :: proc( } pixel_size := f32(isize) / 10 - blurSize := min(blurSize, 20) + blurSize := min(blur, 20) padding := i16(blurSize + 2) // 2 minimum padding scale := __getPixelHeightScale(render_font, pixel_size) - advance, lsb, x0, y0, x1, y1 := __buildGlyphBitmap(render_font, glyph_index, pixel_size, scale) + advance, _, x0, y0, x1, y1 := __buildGlyphBitmap(render_font, glyph_index, pixel_size, scale) gw := (x1 - x0) + i32(padding) * 2 gh := (y1 - y0) + i32(padding) * 2 @@ -596,7 +594,7 @@ BLUR_ZPREC :: 7 __blurCols :: proc(dst: []u8, w, h, dstStride, alpha: int) { dst := dst - for y in 0..> BLUR_APREC @@ -657,38 +655,38 @@ __blur :: proc(dst: []u8, w, h, dstStride: int, blurSize: i16) { ///////////////////////////////// ExpandAtlas :: proc(ctx: ^FontContext, width, height: int, allocator := context.allocator) -> bool { - width := max(ctx.width, width) - height := max(ctx.height, height) + w := max(ctx.width, width) + h := max(ctx.height, height) - if width == ctx.width && height == ctx.height { + if w == ctx.width && h == ctx.height { return true } if ctx.callbackResize != nil { - ctx.callbackResize(ctx.userData, width, height) + ctx.callbackResize(ctx.userData, w, h) } - data := make([]byte, width * height, allocator) + data := make([]byte, w * h, allocator) for i in 0.. ctx.width { - mem.set(&data[i * width + ctx.width], 0, width - ctx.width) + if w > ctx.width { + mem.set(&data[i * w + ctx.width], 0, w - ctx.width) } } - if height > ctx.height { - mem.set(&data[ctx.height * width], 0, (height - ctx.height) * width) + if h > ctx.height { + mem.set(&data[ctx.height * w], 0, (h - ctx.height) * w) } delete(ctx.textureData) ctx.textureData = data // increase atlas size - __atlasExpand(ctx, width, height) + __atlasExpand(ctx, w, h) // add existing data as dirty maxy := i16(0) @@ -700,10 +698,10 @@ ExpandAtlas :: proc(ctx: ^FontContext, width, height: int, allocator := context. ctx.dirtyRect[2] = f32(ctx.width) ctx.dirtyRect[3] = f32(maxy) - ctx.width = width - ctx.height = height - ctx.itw = 1.0 / f32(width) - ctx.ith = 1.0 / f32(height) + ctx.width = w + ctx.height = h + ctx.itw = 1.0 / f32(w) + ctx.ith = 1.0 / f32(h) return true } @@ -994,7 +992,7 @@ __getQuad :: proc( } // fill props right - rx, ry, x0, y0, x1, y1, xoff, yoff, glyph_width, glyph_height: f32 + rx, ry, x0, y0, x1, y1, xoff, yoff: f32 xoff = f32(glyph.xoff + 1) yoff = f32(glyph.yoff + 1) x0 = f32(glyph.x0 + 1) diff --git a/vendor/nanovg/gl/gl.odin b/vendor/nanovg/gl/gl.odin index 698943e2b..fb1c063f7 100644 --- a/vendor/nanovg/gl/gl.odin +++ b/vendor/nanovg/gl/gl.odin @@ -1383,10 +1383,10 @@ BindFramebuffer :: proc(fb: ^framebuffer) { } CreateFramebuffer :: proc(ctx: ^nvg.Context, w, h: int, imageFlags: ImageFlags) -> (fb: framebuffer) { - defaultFBO: i32 - defaultRBO: i32 - gl.GetIntegerv(gl.FRAMEBUFFER_BINDING, &defaultFBO) - gl.GetIntegerv(gl.RENDERBUFFER_BINDING, &defaultRBO) + tempFBO: i32 + tempRBO: i32 + gl.GetIntegerv(gl.FRAMEBUFFER_BINDING, &tempFBO) + gl.GetIntegerv(gl.RENDERBUFFER_BINDING, &tempRBO) imageFlags := imageFlags incl(&imageFlags, ImageFlags { .FLIP_Y, .PREMULTIPLIED }) @@ -1422,8 +1422,8 @@ CreateFramebuffer :: proc(ctx: ^nvg.Context, w, h: int, imageFlags: ImageFlags) // goto error } - gl.BindFramebuffer(gl.FRAMEBUFFER, u32(defaultFBO)) - gl.BindRenderbuffer(gl.RENDERBUFFER, u32(defaultRBO)) + gl.BindFramebuffer(gl.FRAMEBUFFER, u32(tempFBO)) + gl.BindRenderbuffer(gl.RENDERBUFFER, u32(tempRBO)) return } diff --git a/vendor/nanovg/nanovg.odin b/vendor/nanovg/nanovg.odin index 8beb08cce..27054f049 100644 --- a/vendor/nanovg/nanovg.odin +++ b/vendor/nanovg/nanovg.odin @@ -4,7 +4,6 @@ package nanovg // TODO rename enums to old nanovg style! import "core:mem" -import "core:runtime" import "core:math" import "core:fmt" import "../fontstash" @@ -452,10 +451,10 @@ RGBA :: proc(r, g, b, a: u8) -> (res: Color) { // Linearly interpolates from color c0 to c1, and returns resulting color value. LerpRGBA :: proc(c0, c1: Color, u: f32) -> (cint: Color) { - u := clamp(u, 0.0, 1.0) - oneminu := 1.0 - u + clamped := clamp(u, 0.0, 1.0) + oneminu := 1.0 - clamped for i in 0..<4 { - cint[i] = c0[i] * oneminu + c1[i] * u + cint[i] = c0[i] * oneminu + c1[i] * clamped } return @@ -469,8 +468,8 @@ HSL :: proc(h, s, l: f32) -> Color { // Returns color value specified by hue, saturation and lightness and alpha. // HSL values are all in range [0..1], alpha in range [0..255] -HSLA :: proc(h, s, l: f32, a: u8) -> (col: Color) { - hue :: proc(h, m1, m2: f32) -> f32 { +HSLA :: proc(hue, saturation, lightness: f32, a: u8) -> (col: Color) { + hue_get :: proc(h, m1, m2: f32) -> f32 { h := h if h < 0 { @@ -492,17 +491,17 @@ HSLA :: proc(h, s, l: f32, a: u8) -> (col: Color) { return m1 } - h := math.mod(h, 1.0) + h := math.mod(hue, 1.0) if h < 0.0 { h += 1.0 } - s := clamp(s, 0.0, 1.0) - l := clamp(l, 0.0, 1.0) + s := clamp(saturation, 0.0, 1.0) + l := clamp(lightness, 0.0, 1.0) m2 := l <= 0.5 ? (l * (1 + s)) : (l + s - l * s) m1 := 2 * l - m2 - col.r = clamp(hue(h + 1.0/3.0, m1, m2), 0.0, 1.0) - col.g = clamp(hue(h, m1, m2), 0.0, 1.0) - col.b = clamp(hue(h - 1.0/3.0, m1, m2), 0.0, 1.0) + col.r = clamp(hue_get(h + 1.0/3.0, m1, m2), 0.0, 1.0) + col.g = clamp(hue_get(h, m1, m2), 0.0, 1.0) + col.b = clamp(hue_get(h - 1.0/3.0, m1, m2), 0.0, 1.0) col.a = f32(a) / 255.0 return } @@ -919,8 +918,8 @@ CreateImageMem :: proc(ctx: ^Context, data: []byte, imageFlags: ImageFlags) -> i return 0 } - data := mem.slice_ptr(img, int(w) * int(h) * int(n)) - image := CreateImageRGBA(ctx, int(w), int(h), imageFlags, data) + pixel_data := mem.slice_ptr(img, int(w) * int(h) * int(n)) + image := CreateImageRGBA(ctx, int(w), int(h), imageFlags, pixel_data) stbi.image_free(img) return image } @@ -1115,11 +1114,11 @@ ImagePattern :: proc( Scissor :: proc( ctx: ^Context, x, y: f32, - w, h: f32, + width, height: f32, ) { state := __getState(ctx) - w := max(w, 0) - h := max(h, 0) + w := max(width, 0) + h := max(height, 0) TransformIdentity(&state.scissor.xform) state.scissor.xform[4] = x + w * 0.5 @@ -1568,7 +1567,7 @@ __flattenPaths :: proc(ctx: ^Context) { } } - for k in 0.. bool { } // draw a single codepoint, useful for icons -TextIcon :: proc(ctx: ^Context, x, y: f32, codepoint: rune) -> f32 { +TextIcon :: proc(ctx: ^Context, xpos, ypos: f32, codepoint: rune) -> f32 { state := __getState(ctx) scale := __getFontScale(state) * ctx.devicePxRatio invscale := f32(1.0) / scale is_flipped := __isTransformFlipped(state.xform[:]) if state.fontId == -1 { - return x + return xpos } fs := &ctx.fs @@ -2871,8 +2870,8 @@ TextIcon :: proc(ctx: ^Context, x, y: f32, codepoint: rune) -> f32 { fscale := fontstash.__getPixelHeightScale(font, f32(isize) / 10) // transform x / y - x := x * scale - y := y * scale + x := xpos * scale + y := ypos * scale switch fstate.ah { case .LEFT: {} @@ -3160,7 +3159,7 @@ TextBreakLines :: proc( fontstash.SetAlignVertical(fs, state.alignVertical) fontstash.SetFont(fs, state.fontId) - break_row_width := break_row_width * scale + break_x := break_row_width * scale iter := fontstash.TextIterInit(fs, 0, 0, text^) prev_iter := iter q: fontstash.Quad @@ -3268,7 +3267,7 @@ TextBreakLines :: proc( } // Break to new line when a character is beyond break width. - if (type == .Char || type == .CJK) && next_width > break_row_width { + if (type == .Char || type == .CJK) && next_width > break_x { // The run length is too long, need to break to new line. if (break_end == row_start) { // The current word is longer than the row length, just break it from here. @@ -3374,7 +3373,6 @@ TextBoxBounds :: proc( // alignment halign := state.alignHorizontal - valign := state.alignVertical old_align := state.alignHorizontal defer state.alignHorizontal = old_align state.alignHorizontal = .LEFT From bbe50bdaf1dcebd1e6627929704f231c2a02bbcb Mon Sep 17 00:00:00 2001 From: skytrias Date: Fri, 23 Jun 2023 16:21:52 +0200 Subject: [PATCH 60/97] -strict-style fix --- vendor/nanovg/gl/gl.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/nanovg/gl/gl.odin b/vendor/nanovg/gl/gl.odin index fb1c063f7..630e36dd4 100644 --- a/vendor/nanovg/gl/gl.odin +++ b/vendor/nanovg/gl/gl.odin @@ -415,7 +415,7 @@ __renderCreateTexture :: proc( // No mips. if .GENERATE_MIPMAPS in imageFlags { - log.errorf("Mip-maps is not support for non power-of-two textures (%d x %d)\n", w, h); + log.errorf("Mip-maps is not support for non power-of-two textures (%d x %d)\n", w, h) excl(&imageFlags, ImageFlags { .GENERATE_MIPMAPS }) } } From ea00619c3bc1baacd249552397c445fd86dd7500 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 26 Jun 2023 15:20:40 +0100 Subject: [PATCH 61/97] `for &e, i in array` and `for k, &v in map` (alternative to passing the iterable by pointer) --- src/check_expr.cpp | 10 ++++- src/check_stmt.cpp | 33 +++++++++++++-- src/entity.cpp | 2 + src/llvm_backend_stmt.cpp | 88 ++++++++++++++++++++++++--------------- 4 files changed, 94 insertions(+), 39 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 83e44b39f..532555b0a 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -2273,7 +2273,15 @@ gb_internal void check_unary_expr(CheckerContext *c, Operand *o, Token op, Ast * defer (end_error_block()); error(op, "Cannot take the pointer address of '%s'", str); if (e != nullptr && (e->flags & EntityFlag_ForValue) != 0) { - error_line("\tSuggestion: Did you want to pass the iterable value to the for statement by pointer to get addressable semantics?\n"); + Type *parent_type = type_deref(e->Variable.for_loop_parent_type); + + if (parent_type != nullptr && is_type_string(parent_type)) { + error_line("\tSuggestion: Iterating over a string produces an intermediate 'rune' value which cannot be addressed.\n"); + } else if (parent_type != nullptr && is_type_tuple(parent_type)) { + error_line("\tSuggestion: Iterating over a procedure does not produce values which are addressable.\n"); + } else { + error_line("\tSuggestion: Did you want to pass the iterable value to the for statement by pointer to get addressable semantics?\n"); + } } if (e != nullptr && (e->flags & EntityFlag_SwitchValue) != 0) { error_line("\tSuggestion: Did you want to pass the value to the switch statement by pointer to get addressable semantics?\n"); diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index cf6f998e5..30eef4f25 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -1469,12 +1469,15 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) Ast *expr = unparen_expr(rs->expr); + bool is_possibly_addressable = true; isize max_val_count = 2; if (is_ast_range(expr)) { ast_node(ie, BinaryExpr, expr); Operand x = {}; Operand y = {}; + is_possibly_addressable = false; + bool ok = check_range(ctx, expr, true, &x, &y, nullptr); if (!ok) { goto skip_expr_range_stmt; @@ -1497,6 +1500,8 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) gb_string_free(t); goto skip_expr_range_stmt; } else { + is_possibly_addressable = false; + if (is_reverse) { error(node, "#reverse for is not supported for enum types"); } @@ -1510,7 +1515,8 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) Type *t = base_type(type_deref(operand.type)); switch (t->kind) { case Type_Basic: - if (is_type_string(t) && t->Basic.kind != Basic_cstring) { + if (t->Basic.kind == Basic_string) { + is_possibly_addressable = false; array_add(&vals, t_rune); array_add(&vals, t_int); if (is_reverse) { @@ -1529,6 +1535,7 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) case Type_Array: if (is_ptr) use_by_reference_for_value = true; + if (!is_ptr) is_possibly_addressable = operand.mode == Addressing_Variable; array_add(&vals, t->Array.elem); array_add(&vals, t_int); break; @@ -1575,6 +1582,8 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) array_add(&vals, e->type); } + is_possibly_addressable = false; + if (rs->vals.count > 1 && rs->vals[1] != nullptr && count < 3) { gbString s = type_to_string(t); error(operand.expr, "Expected a 3-valued expression on the rhs, got (%s)", s); @@ -1644,8 +1653,13 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) } Ast * name = lhs[i]; Type *type = rhs[i]; - Entity *entity = nullptr; + + bool is_addressed = false; + if (name->kind == Ast_UnaryExpr && name->UnaryExpr.op.kind == Token_And) { + is_addressed = true; + name = name->UnaryExpr.expr; + } if (name->kind == Ast_Ident) { Token token = name->Ident.token; String str = token.string; @@ -1659,7 +1673,16 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) entity->flags |= EntityFlag_ForValue; entity->flags |= EntityFlag_Value; entity->identifier = name; - if (i == addressable_index && use_by_reference_for_value) { + entity->Variable.for_loop_parent_type = type_of_expr(expr); + + if (is_addressed) { + if (is_possibly_addressable && i == addressable_index) { + entity->flags &= ~EntityFlag_Value; + } else { + char const *idx_name = is_map ? "key" : "index"; + error(token, "The %s variable '%.*s' cannot be made addressable", idx_name, LIT(str)); + } + } else if (i == addressable_index && use_by_reference_for_value) { entity->flags &= ~EntityFlag_Value; } if (is_soa) { @@ -1678,7 +1701,9 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) entity = found; } } else { - error(name, "A variable declaration must be an identifier"); + gbString s = expr_to_string(lhs[i]); + error(name, "A variable declaration must be an identifier, got %s", s); + gb_string_free(s); } if (entity == nullptr) { diff --git a/src/entity.cpp b/src/entity.cpp index d6f4edece..2dcb76482 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -209,6 +209,8 @@ struct Entity { ParameterValue param_value; + Type *for_loop_parent_type; + String thread_local_model; Entity * foreign_library; Ast * foreign_library_ident; diff --git a/src/llvm_backend_stmt.cpp b/src/llvm_backend_stmt.cpp index 0702af42e..ae6adb0e2 100644 --- a/src/llvm_backend_stmt.cpp +++ b/src/llvm_backend_stmt.cpp @@ -619,6 +619,18 @@ gb_internal void lb_build_range_string(lbProcedure *p, lbValue expr, Type *val_t } +gb_internal Ast *lb_strip_and_prefix(Ast *ident) { + if (ident != nullptr) { + if (ident->kind == Ast_UnaryExpr && ident->UnaryExpr.op.kind == Token_And) { + ident = ident->UnaryExpr.expr; + } + GB_ASSERT(ident->kind == Ast_Ident); + } + return ident; +} + + + gb_internal void lb_build_range_interval(lbProcedure *p, AstBinaryExpr *node, AstRangeStmt *rs, Scope *scope) { bool ADD_EXTRA_WRAPPING_CHECK = true; @@ -627,13 +639,15 @@ gb_internal void lb_build_range_interval(lbProcedure *p, AstBinaryExpr *node, lb_open_scope(p, scope); + Ast *val0 = rs->vals.count > 0 ? lb_strip_and_prefix(rs->vals[0]) : nullptr; + Ast *val1 = rs->vals.count > 1 ? lb_strip_and_prefix(rs->vals[1]) : nullptr; Type *val0_type = nullptr; Type *val1_type = nullptr; - if (rs->vals.count > 0 && rs->vals[0] != nullptr && !is_blank_ident(rs->vals[0])) { - val0_type = type_of_expr(rs->vals[0]); + if (val0 != nullptr && !is_blank_ident(val0)) { + val0_type = type_of_expr(val0); } - if (rs->vals.count > 1 && rs->vals[1] != nullptr && !is_blank_ident(rs->vals[1])) { - val1_type = type_of_expr(rs->vals[1]); + if (val1 != nullptr && !is_blank_ident(val1)) { + val1_type = type_of_expr(val1); } TokenKind op = Token_Lt; @@ -649,7 +663,7 @@ gb_internal void lb_build_range_interval(lbProcedure *p, AstBinaryExpr *node, lbAddr value; if (val0_type != nullptr) { - Entity *e = entity_of_node(rs->vals[0]); + Entity *e = entity_of_node(val0); value = lb_add_local(p, val0_type, e, false); } else { value = lb_add_local_generated(p, lower.type, false); @@ -658,7 +672,7 @@ gb_internal void lb_build_range_interval(lbProcedure *p, AstBinaryExpr *node, lbAddr index; if (val1_type != nullptr) { - Entity *e = entity_of_node(rs->vals[1]); + Entity *e = entity_of_node(val1); index = lb_add_local(p, val1_type, e, false); } else { index = lb_add_local_generated(p, t_int, false); @@ -680,8 +694,8 @@ gb_internal void lb_build_range_interval(lbProcedure *p, AstBinaryExpr *node, lbValue val = lb_addr_load(p, value); lbValue idx = lb_addr_load(p, index); - if (val0_type) lb_store_range_stmt_val(p, rs->vals[0], val); - if (val1_type) lb_store_range_stmt_val(p, rs->vals[1], idx); + if (val0_type) lb_store_range_stmt_val(p, val0, val); + if (val1_type) lb_store_range_stmt_val(p, val1, idx); { // NOTE: this check block will most likely be optimized out, and is here @@ -815,12 +829,14 @@ gb_internal void lb_build_range_stmt_struct_soa(lbProcedure *p, AstRangeStmt *rs lb_open_scope(p, scope); + Ast *val0 = rs->vals.count > 0 ? lb_strip_and_prefix(rs->vals[0]) : nullptr; + Ast *val1 = rs->vals.count > 1 ? lb_strip_and_prefix(rs->vals[1]) : nullptr; Type *val_types[2] = {}; - if (rs->vals.count > 0 && rs->vals[0] != nullptr && !is_blank_ident(rs->vals[0])) { - val_types[0] = type_of_expr(rs->vals[0]); + if (val0 != nullptr && !is_blank_ident(val0)) { + val_types[0] = type_of_expr(val0); } - if (rs->vals.count > 1 && rs->vals[1] != nullptr && !is_blank_ident(rs->vals[1])) { - val_types[1] = type_of_expr(rs->vals[1]); + if (val1 != nullptr && !is_blank_ident(val1)) { + val_types[1] = type_of_expr(val1); } @@ -901,14 +917,14 @@ gb_internal void lb_build_range_stmt_struct_soa(lbProcedure *p, AstRangeStmt *rs if (val_types[0]) { - Entity *e = entity_of_node(rs->vals[0]); + Entity *e = entity_of_node(val0); if (e != nullptr) { lbAddr soa_val = lb_addr_soa_variable(array.addr, lb_addr_load(p, index), nullptr); map_set(&p->module->soa_values, e, soa_val); } } if (val_types[1]) { - lb_store_range_stmt_val(p, rs->vals[1], lb_addr_load(p, index)); + lb_store_range_stmt_val(p, val1, lb_addr_load(p, index)); } @@ -942,13 +958,15 @@ gb_internal void lb_build_range_stmt(lbProcedure *p, AstRangeStmt *rs, Scope *sc lb_open_scope(p, scope); + Ast *val0 = rs->vals.count > 0 ? lb_strip_and_prefix(rs->vals[0]) : nullptr; + Ast *val1 = rs->vals.count > 1 ? lb_strip_and_prefix(rs->vals[1]) : nullptr; Type *val0_type = nullptr; Type *val1_type = nullptr; - if (rs->vals.count > 0 && rs->vals[0] != nullptr && !is_blank_ident(rs->vals[0])) { - val0_type = type_of_expr(rs->vals[0]); + if (val0 != nullptr && !is_blank_ident(val0)) { + val0_type = type_of_expr(val0); } - if (rs->vals.count > 1 && rs->vals[1] != nullptr && !is_blank_ident(rs->vals[1])) { - val1_type = type_of_expr(rs->vals[1]); + if (val1 != nullptr && !is_blank_ident(val1)) { + val1_type = type_of_expr(val1); } lbValue val = {}; @@ -1042,11 +1060,11 @@ gb_internal void lb_build_range_stmt(lbProcedure *p, AstRangeStmt *rs, Scope *sc if (is_map) { - if (val0_type) lb_store_range_stmt_val(p, rs->vals[0], key); - if (val1_type) lb_store_range_stmt_val(p, rs->vals[1], val); + if (val0_type) lb_store_range_stmt_val(p, val0, key); + if (val1_type) lb_store_range_stmt_val(p, val1, val); } else { - if (val0_type) lb_store_range_stmt_val(p, rs->vals[0], val); - if (val1_type) lb_store_range_stmt_val(p, rs->vals[1], key); + if (val0_type) lb_store_range_stmt_val(p, val0, val); + if (val1_type) lb_store_range_stmt_val(p, val1, key); } lb_push_target_list(p, rs->label, done, loop, nullptr); @@ -1064,21 +1082,23 @@ gb_internal void lb_build_unroll_range_stmt(lbProcedure *p, AstUnrollRangeStmt * lb_open_scope(p, scope); // Open scope here + Ast *val0 = lb_strip_and_prefix(rs->val0); + Ast *val1 = lb_strip_and_prefix(rs->val1); Type *val0_type = nullptr; Type *val1_type = nullptr; - if (rs->val0 != nullptr && !is_blank_ident(rs->val0)) { - val0_type = type_of_expr(rs->val0); + if (val0 != nullptr && !is_blank_ident(val0)) { + val0_type = type_of_expr(val0); } - if (rs->val1 != nullptr && !is_blank_ident(rs->val1)) { - val1_type = type_of_expr(rs->val1); + if (val1 != nullptr && !is_blank_ident(val1)) { + val1_type = type_of_expr(val1); } if (val0_type != nullptr) { - Entity *e = entity_of_node(rs->val0); + Entity *e = entity_of_node(val0); lb_add_local(p, e->type, e, true); } if (val1_type != nullptr) { - Entity *e = entity_of_node(rs->val1); + Entity *e = entity_of_node(val1); lb_add_local(p, e->type, e, true); } @@ -1092,8 +1112,8 @@ gb_internal void lb_build_unroll_range_stmt(lbProcedure *p, AstUnrollRangeStmt * lbAddr val0_addr = {}; lbAddr val1_addr = {}; - if (val0_type) val0_addr = lb_build_addr(p, rs->val0); - if (val1_type) val1_addr = lb_build_addr(p, rs->val1); + if (val0_type) val0_addr = lb_build_addr(p, val0); + if (val1_type) val1_addr = lb_build_addr(p, val1); TokenKind op = expr->BinaryExpr.op.kind; Ast *start_expr = expr->BinaryExpr.left; @@ -1135,8 +1155,8 @@ gb_internal void lb_build_unroll_range_stmt(lbProcedure *p, AstUnrollRangeStmt * lbAddr val0_addr = {}; lbAddr val1_addr = {}; - if (val0_type) val0_addr = lb_build_addr(p, rs->val0); - if (val1_type) val1_addr = lb_build_addr(p, rs->val1); + if (val0_type) val0_addr = lb_build_addr(p, val0); + if (val1_type) val1_addr = lb_build_addr(p, val1); for_array(i, bet->Enum.fields) { Entity *field = bet->Enum.fields[i]; @@ -1149,8 +1169,8 @@ gb_internal void lb_build_unroll_range_stmt(lbProcedure *p, AstUnrollRangeStmt * } else { lbAddr val0_addr = {}; lbAddr val1_addr = {}; - if (val0_type) val0_addr = lb_build_addr(p, rs->val0); - if (val1_type) val1_addr = lb_build_addr(p, rs->val1); + if (val0_type) val0_addr = lb_build_addr(p, val0); + if (val1_type) val1_addr = lb_build_addr(p, val1); GB_ASSERT(expr->tav.mode == Addressing_Constant); From 00d60e28c2a3e3e3a2e8bf7617bd62c0f9b1aae8 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 26 Jun 2023 15:41:53 +0100 Subject: [PATCH 62/97] Allow `switch &v in ...` work to be consistent with `for &e in ...` --- src/check_expr.cpp | 34 +++++++++++++++++++++++++++++++++- src/check_stmt.cpp | 20 +++++++++++++++++--- src/entity.cpp | 4 +++- 3 files changed, 53 insertions(+), 5 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 532555b0a..e0e78b441 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -2246,6 +2246,33 @@ gb_internal bool check_is_not_addressable(CheckerContext *c, Operand *o) { return o->mode != Addressing_Variable && o->mode != Addressing_SoaVariable; } +gb_internal void check_old_for_or_switch_value_usage(Ast *expr) { + Entity *e = entity_of_node(expr); + if (e != nullptr && (e->flags & EntityFlag_OldForOrSwitchValue) != 0) { + GB_ASSERT(e->kind == Entity_Variable); + + begin_error_block(); + defer (end_error_block()); + + if ((e->flags & EntityFlag_ForValue) != 0) { + Type *parent_type = type_deref(e->Variable.for_loop_parent_type); + + error(expr, "Assuming a for-in defined value is addressable as the iterable is passed by value has been disallowed with '-strict-style'."); + + if (is_type_map(parent_type)) { + error_line("\tSuggestion: Prefer doing 'for key, &%.*s in ...'\n", LIT(e->token.string)); + } else { + error_line("\tSuggestion: Prefer doing 'for &%.*s in ...'\n", LIT(e->token.string)); + } + } else { + GB_ASSERT((e->flags & EntityFlag_SwitchValue) != 0); + + error(expr, "Assuming a switch-in defined value is addressable as the iterable is passed by value has been disallowed with '-strict-style'."); + error_line("\tSuggestion: Prefer doing 'switch &%.*s in ...'\n", LIT(e->token.string)); + } + } +} + gb_internal void check_unary_expr(CheckerContext *c, Operand *o, Token op, Ast *node) { switch (op.kind) { case Token_And: { // Pointer address @@ -2255,7 +2282,7 @@ gb_internal void check_unary_expr(CheckerContext *c, Operand *o, Token op, Ast * gbString str = expr_to_string(ue->expr); defer (gb_string_free(str)); - Entity *e = entity_of_node(o->expr); + Entity *e = entity_of_node(ue->expr); if (e != nullptr && (e->flags & EntityFlag_Param) != 0) { error(op, "Cannot take the pointer address of '%s' which is a procedure parameter", str); } else { @@ -2306,6 +2333,11 @@ gb_internal void check_unary_expr(CheckerContext *c, Operand *o, Token op, Ast * o->type = alloc_type_pointer(o->type); } } else { + if (build_context.strict_style && ast_node_expect(node, Ast_UnaryExpr)) { + ast_node(ue, UnaryExpr, node); + check_old_for_or_switch_value_usage(ue->expr); + } + o->type = alloc_type_pointer(o->type); } diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index 30eef4f25..964ff1842 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -417,6 +417,7 @@ gb_internal Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, O return nullptr; case Addressing_Variable: + check_old_for_or_switch_value_usage(lhs->expr); break; case Addressing_MapIndex: { @@ -1141,8 +1142,14 @@ gb_internal void check_type_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_ syntax_error(as_token, "Expected 1 expression after 'in'"); return; } + bool is_addressed = false; + Ast *lhs = as->lhs[0]; Ast *rhs = as->rhs[0]; + if (lhs->kind == Ast_UnaryExpr && lhs->UnaryExpr.op.kind == Token_And) { + is_addressed = true; + lhs = lhs->UnaryExpr.expr; + } check_expr(ctx, &x, rhs); check_assignment(ctx, &x, nullptr, str_lit("type switch expression")); @@ -1281,12 +1288,15 @@ gb_internal void check_type_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_ } } - bool is_reference = false; + bool is_reference = is_addressed; + bool old_style = false; - if (is_ptr && + if (!is_reference && + is_ptr && cc->list.count == 1 && case_type != nullptr) { is_reference = true; + old_style = true; } if (cc->list.count > 1 || saw_nil) { @@ -1305,9 +1315,12 @@ gb_internal void check_type_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_ { Entity *tag_var = alloc_entity_variable(ctx->scope, lhs->Ident.token, case_type, EntityState_Resolved); tag_var->flags |= EntityFlag_Used; + tag_var->flags |= EntityFlag_SwitchValue; if (!is_reference) { tag_var->flags |= EntityFlag_Value; - tag_var->flags |= EntityFlag_SwitchValue; + } + if (old_style) { + tag_var->flags |= EntityFlag_OldForOrSwitchValue; } add_entity(ctx, ctx->scope, lhs, tag_var); add_entity_use(ctx, lhs, tag_var); @@ -1683,6 +1696,7 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) error(token, "The %s variable '%.*s' cannot be made addressable", idx_name, LIT(str)); } } else if (i == addressable_index && use_by_reference_for_value) { + entity->flags |= EntityFlag_OldForOrSwitchValue; entity->flags &= ~EntityFlag_Value; } if (is_soa) { diff --git a/src/entity.cpp b/src/entity.cpp index 2dcb76482..649dd900d 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -84,7 +84,9 @@ enum EntityFlag : u64 { EntityFlag_CustomLinkage_LinkOnce = 1ull<<44, EntityFlag_Require = 1ull<<50, - EntityFlag_ByPtr = 1ull<<51, // enforce parameter is passed by pointer + EntityFlag_ByPtr = 1ull<<51, // enforce parameter is passed by pointer + + EntityFlag_OldForOrSwitchValue = 1ull<<52, EntityFlag_Overridden = 1ull<<63, }; From 3dec55f009da4293aca870d50f7b15668c4bba7c Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 26 Jun 2023 15:42:57 +0100 Subject: [PATCH 63/97] Replace `x in &y` Use `&v in y` syntax through core & vendor for `switch`/`for` statements --- .../container/topological_sort/topological_sort.odin | 4 ++-- core/encoding/hxa/read.odin | 4 ++-- core/encoding/json/unmarshal.odin | 10 +++++----- core/image/netpbm/netpbm.odin | 12 ++++++------ core/image/png/helpers.odin | 2 +- core/math/big/helpers.odin | 6 +++--- core/math/big/internal.odin | 4 ++-- core/math/big/rat.odin | 2 +- core/math/ease/ease.odin | 2 +- core/reflect/reflect.odin | 4 ++-- core/strings/strings.odin | 8 ++++---- core/text/i18n/i18n.odin | 4 ++-- core/thread/thread_pool.odin | 2 +- core/unicode/tools/generate_entity_table.odin | 2 +- 14 files changed, 33 insertions(+), 33 deletions(-) diff --git a/core/container/topological_sort/topological_sort.odin b/core/container/topological_sort/topological_sort.odin index 4b69930d5..314e3e070 100644 --- a/core/container/topological_sort/topological_sort.odin +++ b/core/container/topological_sort/topological_sort.odin @@ -32,7 +32,7 @@ init :: proc(sorter: ^$S/Sorter($K)) { } destroy :: proc(sorter: ^$S/Sorter($K)) { - for _, v in &sorter.relations { + for _, v in sorter.relations { delete(v.dependents) } delete(sorter.relations) @@ -80,7 +80,7 @@ sort :: proc(sorter: ^$S/Sorter($K)) -> (sorted, cycled: [dynamic]K) { } } - for root in &sorted do for k, _ in relations[root].dependents { + for root in sorted do for k, _ in relations[root].dependents { relation := &relations[k] relation.dependencies -= 1 if relation.dependencies == 0 { diff --git a/core/encoding/hxa/read.odin b/core/encoding/hxa/read.odin index abe295530..8a8636f19 100644 --- a/core/encoding/hxa/read.odin +++ b/core/encoding/hxa/read.odin @@ -83,7 +83,7 @@ read :: proc(data: []byte, filename := "", print_error := false, allocato meta_data = make([]Meta, int(capacity)) count := 0 defer meta_data = meta_data[:count] - for m in &meta_data { + for &m in meta_data { m.name = read_name(r) or_return type := read_value(r, Meta_Value_Type) or_return @@ -116,7 +116,7 @@ read :: proc(data: []byte, filename := "", print_error := false, allocato layer_count := 0 layers = make(Layer_Stack, stack_count) defer layers = layers[:layer_count] - for layer in &layers { + for &layer in layers { layer.name = read_name(r) or_return layer.components = read_value(r, u8) or_return type := read_value(r, Layer_Data_Type) or_return diff --git a/core/encoding/json/unmarshal.odin b/core/encoding/json/unmarshal.odin index e6c61d8fa..678f2dcfa 100644 --- a/core/encoding/json/unmarshal.odin +++ b/core/encoding/json/unmarshal.odin @@ -72,7 +72,7 @@ unmarshal_string :: proc(data: string, ptr: ^$T, spec := DEFAULT_SPECIFICATION, @(private) assign_bool :: proc(val: any, b: bool) -> bool { v := reflect.any_core(val) - switch dst in &v { + switch &dst in v { case bool: dst = bool(b) case b8: dst = b8 (b) case b16: dst = b16 (b) @@ -85,7 +85,7 @@ assign_bool :: proc(val: any, b: bool) -> bool { @(private) assign_int :: proc(val: any, i: $T) -> bool { v := reflect.any_core(val) - switch dst in &v { + switch &dst in v { case i8: dst = i8 (i) case i16: dst = i16 (i) case i16le: dst = i16le (i) @@ -122,7 +122,7 @@ assign_int :: proc(val: any, i: $T) -> bool { @(private) assign_float :: proc(val: any, f: $T) -> bool { v := reflect.any_core(val) - switch dst in &v { + switch &dst in v { case f16: dst = f16 (f) case f16le: dst = f16le(f) case f16be: dst = f16be(f) @@ -150,7 +150,7 @@ assign_float :: proc(val: any, f: $T) -> bool { @(private) unmarshal_string_token :: proc(p: ^Parser, val: any, str: string, ti: ^reflect.Type_Info) -> bool { val := val - switch dst in &val { + switch &dst in val { case string: dst = str return true @@ -215,7 +215,7 @@ unmarshal_value :: proc(p: ^Parser, v: any) -> (err: Unmarshal_Error) { } } - switch dst in &v { + switch &dst in v { // Handle json.Value as an unknown type case Value: dst = parse_value(p) or_return diff --git a/core/image/netpbm/netpbm.odin b/core/image/netpbm/netpbm.odin index cb07b1e3a..74e482cb4 100644 --- a/core/image/netpbm/netpbm.odin +++ b/core/image/netpbm/netpbm.odin @@ -161,18 +161,18 @@ save_to_buffer :: proc(img: ^Image, custom_info: Info = {}, allocator := context // convert from native endianness if img.depth == 16 { pixels := mem.slice_data_cast([]u16be, data.buf[len(header_buf):]) - for p in &pixels { + for &p in pixels { p = u16be(transmute(u16) p) } } else if header.format in PFM { if header.little_endian { pixels := mem.slice_data_cast([]f32le, data.buf[len(header_buf):]) - for p in &pixels { + for &p in pixels { p = f32le(transmute(f32) p) } } else { pixels := mem.slice_data_cast([]f32be, data.buf[len(header_buf):]) - for p in &pixels { + for &p in pixels { p = f32be(transmute(f32) p) } } @@ -578,18 +578,18 @@ decode_image :: proc(img: ^Image, header: Header, data: []byte, allocator := con if header.format in PFM { pixels := mem.slice_data_cast([]f32, img.pixels.buf[:]) if header.little_endian { - for p in &pixels { + for &p in pixels { p = f32(transmute(f32le) p) } } else { - for p in &pixels { + for &p in pixels { p = f32(transmute(f32be) p) } } } else { if img.depth == 16 { pixels := mem.slice_data_cast([]u16, img.pixels.buf[:]) - for p in &pixels { + for &p in pixels { p = u16(transmute(u16be) p) } } diff --git a/core/image/png/helpers.odin b/core/image/png/helpers.odin index 025c2b866..889b3cb6b 100644 --- a/core/image/png/helpers.odin +++ b/core/image/png/helpers.odin @@ -36,7 +36,7 @@ destroy :: proc(img: ^Image) { bytes.buffer_destroy(&img.pixels) if v, ok := img.metadata.(^image.PNG_Info); ok { - for chunk in &v.chunks { + for chunk in v.chunks { delete(chunk.data) } delete(v.chunks) diff --git a/core/math/big/helpers.odin b/core/math/big/helpers.odin index 6c4b5dd01..a4313a244 100644 --- a/core/math/big/helpers.odin +++ b/core/math/big/helpers.odin @@ -19,7 +19,7 @@ import rnd "core:math/rand" int_destroy :: proc(integers: ..^Int) { integers := integers - for a in &integers { + for a in integers { assert_if_nil(a) } #force_inline internal_int_destroy(..integers) @@ -408,7 +408,7 @@ clear_if_uninitialized_multi :: proc(args: ..^Int, allocator := context.allocato args := args assert_if_nil(..args) - for i in &args { + for i in args { #force_inline internal_clear_if_uninitialized_single(i, allocator) or_return } return err @@ -435,7 +435,7 @@ int_init_multi :: proc(integers: ..^Int, allocator := context.allocator) -> (err assert_if_nil(..integers) integers := integers - for a in &integers { + for a in integers { #force_inline internal_clear(a, true, allocator) or_return } return nil diff --git a/core/math/big/internal.odin b/core/math/big/internal.odin index 13aa96bef..968a26f8f 100644 --- a/core/math/big/internal.odin +++ b/core/math/big/internal.odin @@ -1857,7 +1857,7 @@ internal_root_n :: proc { internal_int_root_n, } internal_int_destroy :: proc(integers: ..^Int) { integers := integers - for a in &integers { + for &a in integers { if internal_int_allocated_cap(a) > 0 { mem.zero_slice(a.digit[:]) free(&a.digit[0]) @@ -2909,7 +2909,7 @@ internal_int_init_multi :: proc(integers: ..^Int, allocator := context.allocator context.allocator = allocator integers := integers - for a in &integers { + for a in integers { internal_clear(a) or_return } return nil diff --git a/core/math/big/rat.odin b/core/math/big/rat.odin index c3efc30aa..35618affb 100644 --- a/core/math/big/rat.odin +++ b/core/math/big/rat.odin @@ -137,7 +137,7 @@ rat_copy :: proc(dst, src: ^Rat, minimize := false, allocator := context.allocat internal_rat_destroy :: proc(rationals: ..^Rat) { rationals := rationals - for z in &rationals { + for &z in rationals { internal_int_destroy(&z.a, &z.b) } } diff --git a/core/math/ease/ease.odin b/core/math/ease/ease.odin index d5cb85dd8..0e6569bca 100644 --- a/core/math/ease/ease.odin +++ b/core/math/ease/ease.odin @@ -450,7 +450,7 @@ flux_tween_init :: proc(tween: ^Flux_Tween($T), duration: time.Duration) where i flux_update :: proc(flux: ^Flux_Map($T), dt: f64) where intrinsics.type_is_float(T) { clear(&flux.keys_to_be_deleted) - for key, tween in &flux.values { + for key, &tween in flux.values { delay_remainder := f64(0) // Update delay if necessary. diff --git a/core/reflect/reflect.odin b/core/reflect/reflect.odin index 2eb31c21b..a88557e0e 100644 --- a/core/reflect/reflect.odin +++ b/core/reflect/reflect.odin @@ -781,7 +781,7 @@ set_union_variant_raw_tag :: proc(a: any, tag: i64) { tag_ptr := uintptr(a.data) + info.tag_offset tag_any := any{rawptr(tag_ptr), info.tag_type.id} - switch i in &tag_any { + switch &i in tag_any { case u8: i = u8(tag) case i8: i = i8(tag) case u16: i = u16(tag) @@ -1312,7 +1312,7 @@ relative_pointer_to_absolute_raw :: proc(data: rawptr, base_integer_id: typeid) ptr_any := any{data, base_integer_id} ptr: rawptr - switch i in &ptr_any { + switch &i in ptr_any { case u8: ptr = _handle(&i) case u16: ptr = _handle(&i) case u32: ptr = _handle(&i) diff --git a/core/strings/strings.odin b/core/strings/strings.odin index 6daa5f9c9..66a75f96a 100644 --- a/core/strings/strings.odin +++ b/core/strings/strings.odin @@ -1194,7 +1194,7 @@ Output: split_lines :: proc(s: string, allocator := context.allocator) -> (res: []string, err: mem.Allocator_Error) #optional_allocator_error { sep :: "\n" lines := _split(s, sep, 0, -1, allocator) or_return - for line in &lines { + for &line in lines { line = _trim_cr(line) } return lines, nil @@ -1234,7 +1234,7 @@ Output: split_lines_n :: proc(s: string, n: int, allocator := context.allocator) -> (res: []string, err: mem.Allocator_Error) #optional_allocator_error { sep :: "\n" lines := _split(s, sep, 0, n, allocator) or_return - for line in &lines { + for &line in lines { line = _trim_cr(line) } return lines, nil @@ -1273,7 +1273,7 @@ Output: split_lines_after :: proc(s: string, allocator := context.allocator) -> (res: []string, err: mem.Allocator_Error) #optional_allocator_error { sep :: "\n" lines := _split(s, sep, len(sep), -1, allocator) or_return - for line in &lines { + for &line in lines { line = _trim_cr(line) } return lines, nil @@ -1314,7 +1314,7 @@ Output: split_lines_after_n :: proc(s: string, n: int, allocator := context.allocator) -> (res: []string, err: mem.Allocator_Error) #optional_allocator_error { sep :: "\n" lines := _split(s, sep, len(sep), n, allocator) or_return - for line in &lines { + for &line in lines { line = _trim_cr(line) } return lines, nil diff --git a/core/text/i18n/i18n.odin b/core/text/i18n/i18n.odin index 9d030db16..8513f30c8 100644 --- a/core/text/i18n/i18n.odin +++ b/core/text/i18n/i18n.odin @@ -170,8 +170,8 @@ destroy :: proc(catalog: ^Translation = ACTIVE, allocator := context.allocator) return } - for section in &catalog.k_v { - for key in &catalog.k_v[section] { + for section in catalog.k_v { + for key in catalog.k_v[section] { delete(catalog.k_v[section][key]) } delete(catalog.k_v[section]) diff --git a/core/thread/thread_pool.odin b/core/thread/thread_pool.odin index 820de8ad4..1a4119e5f 100644 --- a/core/thread/thread_pool.odin +++ b/core/thread/thread_pool.odin @@ -81,7 +81,7 @@ pool_destroy :: proc(pool: ^Pool) { delete(pool.tasks) delete(pool.tasks_done) - for t in &pool.threads { + for &t in pool.threads { destroy(t) } diff --git a/core/unicode/tools/generate_entity_table.odin b/core/unicode/tools/generate_entity_table.odin index 328ba9091..fb4e4c2a4 100644 --- a/core/unicode/tools/generate_entity_table.odin +++ b/core/unicode/tools/generate_entity_table.odin @@ -221,7 +221,7 @@ named_xml_entity_to_rune :: proc(name: string) -> (decoded: rune, ok: bool) { delete(entity_map) delete(names) - for name in &names { + for &name in names { free(&name) } } From c8f475174e4cc0acd9255386f9f6aff27c7aaa38 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 26 Jun 2023 15:51:08 +0100 Subject: [PATCH 64/97] Fix tests for `-strict-style` --- tests/documentation/documentation_tester.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/documentation/documentation_tester.odin b/tests/documentation/documentation_tester.odin index a4d18d1eb..1f0f8ca97 100644 --- a/tests/documentation/documentation_tester.odin +++ b/tests/documentation/documentation_tester.odin @@ -238,10 +238,10 @@ find_and_add_examples :: proc(docs: string, package_name: string, entity_name: s } } // Remove first layer of tabs which are always present - for line in &example_block.lines { + for &line in example_block.lines { line = strings.trim_prefix(line, "\t") } - for line in &output_block.lines { + for &line in output_block.lines { line = strings.trim_prefix(line, "\t") } append(&g_examples_to_verify, Example_Test { From 6c6f9f7d25c2c4fd6af95dd2bbe91eb597fe1830 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 26 Jun 2023 15:55:35 +0100 Subject: [PATCH 65/97] Fix fmt implementation for `js` --- core/fmt/fmt_js.odin | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/core/fmt/fmt_js.odin b/core/fmt/fmt_js.odin index 5e06041f5..881cde867 100644 --- a/core/fmt/fmt_js.odin +++ b/core/fmt/fmt_js.odin @@ -11,27 +11,24 @@ foreign odin_env { } @(private="file") -write_vtable := io.Stream_VTable{ - impl_write = proc(s: io.Stream, p: []byte) -> (n: int, err: io.Error) { - fd := u32(uintptr(s.stream_data)) +write_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) { + if mode == .Write { + fd := u32(uintptr(stream_data)) write(fd, p) - return len(p), nil - }, + return i64(len(p)), nil + } + return 0, .Empty } @(private="file") stdout := io.Writer{ - stream = { - stream_vtable = &write_vtable, - stream_data = rawptr(uintptr(1)), - }, + procedure = write_stream_proc, + data = rawptr(uintptr(1)), } @(private="file") stderr := io.Writer{ - stream = { - stream_vtable = &write_vtable, - stream_data = rawptr(uintptr(2)), - }, + procedure = write_stream_proc, + data = rawptr(uintptr(2)), } // print formats using the default print settings and writes to stdout From 3d9328fd79f45c85f1ecc513b2c05071f9c1d1a3 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 26 Jun 2023 15:55:52 +0100 Subject: [PATCH 66/97] Default to `panic` allocator for wasm targets --- core/mem/allocators.odin | 12 +++--- core/runtime/default_allocators_js.odin | 4 +- core/runtime/default_allocators_nil.odin | 50 ++++++++++++++++++++++- core/runtime/default_allocators_wasi.odin | 4 +- 4 files changed, 59 insertions(+), 11 deletions(-) diff --git a/core/mem/allocators.odin b/core/mem/allocators.odin index 603c2a6c7..7767740c9 100644 --- a/core/mem/allocators.odin +++ b/core/mem/allocators.odin @@ -813,22 +813,22 @@ panic_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, switch mode { case .Alloc: if size > 0 { - panic("mem: panic allocator, .Alloc called") + panic("mem: panic allocator, .Alloc called", loc=loc) } case .Alloc_Non_Zeroed: if size > 0 { - panic("mem: panic allocator, .Alloc_Non_Zeroed called") + panic("mem: panic allocator, .Alloc_Non_Zeroed called", loc=loc) } case .Resize: if size > 0 { - panic("mem: panic allocator, .Resize called") + panic("mem: panic allocator, .Resize called", loc=loc) } case .Free: if old_memory != nil { - panic("mem: panic allocator, .Free called") + panic("mem: panic allocator, .Free called", loc=loc) } case .Free_All: - panic("mem: panic allocator, .Free_All called") + panic("mem: panic allocator, .Free_All called", loc=loc) case .Query_Features: set := (^Allocator_Mode_Set)(old_memory) @@ -838,7 +838,7 @@ panic_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, return nil, nil case .Query_Info: - panic("mem: panic allocator, .Query_Info called") + panic("mem: panic allocator, .Query_Info called", loc=loc) } return nil, nil diff --git a/core/runtime/default_allocators_js.odin b/core/runtime/default_allocators_js.odin index cc70b963a..715073f08 100644 --- a/core/runtime/default_allocators_js.odin +++ b/core/runtime/default_allocators_js.odin @@ -1,5 +1,5 @@ //+build js package runtime -default_allocator_proc :: nil_allocator_proc -default_allocator :: nil_allocator +default_allocator_proc :: panic_allocator_proc +default_allocator :: panic_allocator diff --git a/core/runtime/default_allocators_nil.odin b/core/runtime/default_allocators_nil.odin index f86990581..a340050eb 100644 --- a/core/runtime/default_allocators_nil.odin +++ b/core/runtime/default_allocators_nil.odin @@ -35,4 +35,52 @@ nil_allocator :: proc() -> Allocator { when ODIN_OS == .Freestanding { default_allocator_proc :: nil_allocator_proc default_allocator :: nil_allocator -} \ No newline at end of file +} + + + +panic_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, + size, alignment: int, + old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, Allocator_Error) { + switch mode { + case .Alloc: + if size > 0 { + panic("panic allocator, .Alloc called", loc=loc) + } + case .Alloc_Non_Zeroed: + if size > 0 { + panic("panic allocator, .Alloc_Non_Zeroed called", loc=loc) + } + case .Resize: + if size > 0 { + panic("panic allocator, .Resize called", loc=loc) + } + case .Free: + if old_memory != nil { + panic("panic allocator, .Free called", loc=loc) + } + case .Free_All: + panic("panic allocator, .Free_All called", loc=loc) + + case .Query_Features: + set := (^Allocator_Mode_Set)(old_memory) + if set != nil { + set^ = {.Query_Features} + } + return nil, nil + + case .Query_Info: + panic("panic allocator, .Query_Info called", loc=loc) + } + + return nil, nil +} + +panic_allocator :: proc() -> Allocator { + return Allocator{ + procedure = nil_allocator_proc, + data = nil, + } +} + + diff --git a/core/runtime/default_allocators_wasi.odin b/core/runtime/default_allocators_wasi.odin index 2e475e055..a7e6842a6 100644 --- a/core/runtime/default_allocators_wasi.odin +++ b/core/runtime/default_allocators_wasi.odin @@ -1,5 +1,5 @@ //+build wasi package runtime -default_allocator_proc :: nil_allocator_proc -default_allocator :: nil_allocator +default_allocator_proc :: panic_allocator_proc +default_allocator :: panic_allocator From 8182ba4ee05473d13c6619500457da9a1e0845ca Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 26 Jun 2023 16:48:43 +0100 Subject: [PATCH 67/97] Improve internal names for parapoly records --- src/check_expr.cpp | 49 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index e0e78b441..b12c6d114 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -6828,15 +6828,15 @@ gb_internal CallArgumentError check_polymorphic_record_type(CheckerContext *c, O return err; } - String generated_name = make_string_c(expr_to_string(call)); - CheckerContext ctx = *c; // NOTE(bill): We need to make sure the lookup scope for the record is the same as where it was created ctx.scope = polymorphic_record_parent_scope(original_type); GB_ASSERT(ctx.scope != nullptr); - Type *named_type = alloc_type_named(generated_name, nullptr, nullptr); Type *bt = base_type(original_type); + String generated_name = make_string_c(expr_to_string(call)); + + Type *named_type = alloc_type_named(generated_name, nullptr, nullptr); if (bt->kind == Type_Struct) { Ast *node = clone_ast(bt->Struct.node); Type *struct_type = alloc_type_struct(); @@ -6861,6 +6861,49 @@ gb_internal CallArgumentError check_polymorphic_record_type(CheckerContext *c, O GB_PANIC("Unsupported parametric polymorphic record type"); } + + bt = base_type(named_type); + if (bt->kind == Type_Struct || bt->kind == Type_Union) { + GB_ASSERT(original_type->kind == Type_Named); + Entity *e = original_type->Named.type_name; + GB_ASSERT(e->kind == Entity_TypeName); + + gbString s = gb_string_make_reserve(heap_allocator(), e->token.string.len+3); + s = gb_string_append_fmt(s, "%.*s(", LIT(e->token.string)); + + Type *params = nullptr; + switch (bt->kind) { + case Type_Struct: params = bt->Struct.polymorphic_params; break; + case Type_Union: params = bt->Union.polymorphic_params; break; + } + + if (params != nullptr) for_array(i, params->Tuple.variables) { + Entity *v = params->Tuple.variables[i]; + String name = v->token.string; + if (i > 0) { + s = gb_string_append_fmt(s, ", "); + } + s = gb_string_append_fmt(s, "$%.*s", LIT(name)); + + if (v->kind == Entity_TypeName) { + if (v->type->kind != Type_Generic) { + s = gb_string_append_fmt(s, "="); + s = write_type_to_string(s, v->type, false); + } + } else if (v->kind == Entity_Constant) { + s = gb_string_append_fmt(s, "="); + s = write_exact_value_to_string(s, v->Constant.value); + } + } + s = gb_string_append_fmt(s, ")"); + + String new_name = make_string_c(s); + named_type->Named.name = new_name; + if (named_type->Named.type_name) { + named_type->Named.type_name->token.string = new_name; + } + } + operand->mode = Addressing_Type; operand->type = named_type; } From 3c0f1caa41e758e5568ed4d32b3f8951548c8672 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 26 Jun 2023 17:03:40 +0100 Subject: [PATCH 68/97] Fix #2606 --- src/llvm_backend_proc.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 47137c540..42ca90828 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -3341,7 +3341,11 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { if (pt->params != nullptr) { - GB_ASSERT(args.count >= pt->params->Tuple.variables.count); + isize min_count = pt->params->Tuple.variables.count; + if (is_c_vararg) { + min_count -= 1; + } + GB_ASSERT(args.count >= min_count); for_array(arg_index, pt->params->Tuple.variables) { Entity *e = pt->params->Tuple.variables[arg_index]; if (pt->variadic && arg_index == pt->variadic_index) { From cdcb64b0d08be85c3ea579270eeb4d193b4cb495 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 26 Jun 2023 17:06:42 +0100 Subject: [PATCH 69/97] Add missing `-strict-style` check --- src/check_expr.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index b12c6d114..7b3ddfc73 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -2247,6 +2247,10 @@ gb_internal bool check_is_not_addressable(CheckerContext *c, Operand *o) { } gb_internal void check_old_for_or_switch_value_usage(Ast *expr) { + if (!build_context.strict_style) { + return; + } + Entity *e = entity_of_node(expr); if (e != nullptr && (e->flags & EntityFlag_OldForOrSwitchValue) != 0) { GB_ASSERT(e->kind == Entity_Variable); From 8b8310711e4924678e605278fca1f8dbf517b903 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 26 Jun 2023 17:36:27 +0100 Subject: [PATCH 70/97] Fix #2594 zero sized union code generation --- src/llvm_backend.cpp | 4 +++- src/llvm_backend_general.cpp | 1 + src/llvm_backend_type.cpp | 5 ++--- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 730610ad9..12abe7b16 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -218,7 +218,9 @@ gb_internal lbValue lb_equal_proc_for_type(lbModule *m, Type *type) { LLVMBuildRet(p->builder, LLVMConstInt(lb_type(m, t_bool), 0, false)); } else if (type->kind == Type_Union) { - if (is_type_union_maybe_pointer(type)) { + if (type_size_of(type) == 0) { + LLVMBuildRet(p->builder, LLVMConstInt(lb_type(m, t_bool), 1, false)); + } else if (is_type_union_maybe_pointer(type)) { Type *v = type->Union.variants[0]; Type *pv = alloc_type_pointer(v); diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 9333f13a4..017eeca2e 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -1323,6 +1323,7 @@ gb_internal lbValue lb_emit_union_tag_value(lbProcedure *p, lbValue u) { gb_internal void lb_emit_store_union_variant_tag(lbProcedure *p, lbValue parent, Type *variant_type) { Type *t = type_deref(parent.type); + GB_ASSERT(is_type_union(t)); if (is_type_union_maybe_pointer(t) || type_size_of(t) == 0) { // No tag needed! diff --git a/src/llvm_backend_type.cpp b/src/llvm_backend_type.cpp index a54fde748..4716733cc 100644 --- a/src/llvm_backend_type.cpp +++ b/src/llvm_backend_type.cpp @@ -654,10 +654,9 @@ gb_internal void lb_setup_type_info_data(lbProcedure *p) { // NOTE(bill): Setup lbValue count = lb_const_int(m, t_int, variant_count); vals[0] = llvm_const_slice(m, memory_types, count); - i64 tag_size = union_tag_size(t); - i64 tag_offset = align_formula(t->Union.variant_block_size, tag_size); - + i64 tag_size = union_tag_size(t); if (tag_size > 0) { + i64 tag_offset = align_formula(t->Union.variant_block_size, tag_size); vals[1] = lb_const_int(m, t_uintptr, tag_offset).value; vals[2] = lb_type_info(m, union_tag_type(t)).value; } else { From ca1d4374359907f4862f4958860f1106c76657da Mon Sep 17 00:00:00 2001 From: skytrias Date: Mon, 26 Jun 2023 21:28:54 +0200 Subject: [PATCH 71/97] add build flags restrictions --- vendor/fontstash/fontstash.odin | 1 + vendor/nanovg/gl/gl.odin | 1 + vendor/nanovg/nanovg.odin | 1 + 3 files changed, 3 insertions(+) diff --git a/vendor/fontstash/fontstash.odin b/vendor/fontstash/fontstash.odin index 64c1ce7cd..7d9926cb1 100644 --- a/vendor/fontstash/fontstash.odin +++ b/vendor/fontstash/fontstash.odin @@ -1,3 +1,4 @@ +//+build windows, linux, darwin package fontstash import "core:runtime" diff --git a/vendor/nanovg/gl/gl.odin b/vendor/nanovg/gl/gl.odin index 630e36dd4..34b9f99fb 100644 --- a/vendor/nanovg/gl/gl.odin +++ b/vendor/nanovg/gl/gl.odin @@ -1,3 +1,4 @@ +//+build windows, linux, darwin package nanovg_gl import "core:log" diff --git a/vendor/nanovg/nanovg.odin b/vendor/nanovg/nanovg.odin index 27054f049..f22f9c425 100644 --- a/vendor/nanovg/nanovg.odin +++ b/vendor/nanovg/nanovg.odin @@ -1,3 +1,4 @@ +//+build windows, linux, darwin package nanovg // TODO rename structs to old nanovg style! From a820246f64b4c84bd82714551ae46c0e61e5cbe3 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 26 Jun 2023 23:00:39 +0100 Subject: [PATCH 72/97] Keep -vet happy --- vendor/fontstash/fontstash.odin | 6 +++--- vendor/nanovg/gl/gl.odin | 8 ++++---- vendor/nanovg/nanovg.odin | 16 ++++++++-------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/vendor/fontstash/fontstash.odin b/vendor/fontstash/fontstash.odin index 7d9926cb1..67b2c781c 100644 --- a/vendor/fontstash/fontstash.odin +++ b/vendor/fontstash/fontstash.odin @@ -146,7 +146,7 @@ Init :: proc(using ctx: ^FontContext, w, h: int, loc: QuadLocation) { } Destroy :: proc(using ctx: ^FontContext) { - for font in &fonts { + for font in fonts { if font.freeLoadedData { delete(font.loadedData) } @@ -166,7 +166,7 @@ Reset :: proc(using ctx: ^FontContext) { __dirtyRectReset(ctx) mem.zero_slice(textureData) - for font in &fonts { + for &font in fonts { __lutReset(&font) } @@ -722,7 +722,7 @@ ResetAtlas :: proc(ctx: ^FontContext, width, height: int, allocator := context.a ctx.dirtyRect[3] = 0 // reset fonts - for font in &ctx.fonts { + for &font in ctx.fonts { clear(&font.glyphs) __lutReset(&font) } diff --git a/vendor/nanovg/gl/gl.odin b/vendor/nanovg/gl/gl.odin index 34b9f99fb..50df6e1b5 100644 --- a/vendor/nanovg/gl/gl.odin +++ b/vendor/nanovg/gl/gl.odin @@ -255,7 +255,7 @@ __blendFuncSeparate :: proc(ctx: ^Context, blend: ^Blend) { } __allocTexture :: proc(ctx: ^Context) -> (tex: ^Texture) { - for texture in &ctx.textures { + for &texture in ctx.textures { if texture.id == 0 { tex = &texture break @@ -275,7 +275,7 @@ __allocTexture :: proc(ctx: ^Context) -> (tex: ^Texture) { } __findTexture :: proc(ctx: ^Context, id: int) -> ^Texture { - for texture in &ctx.textures { + for &texture in ctx.textures { if texture.id == id { return &texture } @@ -285,7 +285,7 @@ __findTexture :: proc(ctx: ^Context, id: int) -> ^Texture { } __deleteTexture :: proc(ctx: ^Context, id: int) -> bool { - for texture, i in &ctx.textures { + for &texture, i in ctx.textures { if texture.id == id { if texture.tex != 0 && (.NO_DELETE not_in texture.flags) { gl.DeleteTextures(1, &texture.tex) @@ -1302,7 +1302,7 @@ __renderDelete :: proc(uptr: rawptr) { gl.DeleteBuffers(1, &ctx.vertBuf) } - for texture in &ctx.textures { + for &texture in ctx.textures { if texture.tex != 0 && (.NO_DELETE not_in texture.flags) { gl.DeleteTextures(1, &texture.tex) } diff --git a/vendor/nanovg/nanovg.odin b/vendor/nanovg/nanovg.odin index f22f9c425..bc0335f6a 100644 --- a/vendor/nanovg/nanovg.odin +++ b/vendor/nanovg/nanovg.odin @@ -326,7 +326,7 @@ DeleteInternal :: proc(ctx: ^Context) { __deletePathCache(ctx.cache) fontstash.Destroy(&ctx.fs) - for image in &ctx.fontImages { + for &image in ctx.fontImages { if image != 0 { DeleteImage(ctx, image) } @@ -1874,7 +1874,7 @@ __calculateJoins :: proc( } // Calculate which joins needs extra vertices to append, and gather vertex count. - for path in &cache.paths { + for &path in cache.paths { pts := cache.points[path.first:] p0 := &pts[path.count-1] p1 := &pts[0] @@ -1968,7 +1968,7 @@ __expandStroke :: proc( // Calculate max vertex usage. cverts := 0 - for path in &cache.paths { + for &path in cache.paths { loop := path.closed // TODO check if f32 calculation necessary? @@ -2097,7 +2097,7 @@ __expandFill :: proc( // Calculate max vertex usage. cverts := 0 - for path in &cache.paths { + for &path in cache.paths { cverts += path.count + path.nbevel + 1 if fringe { @@ -2109,7 +2109,7 @@ __expandFill :: proc( verts := __allocTempVerts(ctx, cverts) dst_index: int - for path in &cache.paths { + for &path in cache.paths { pts := cache.points[path.first:] p0, p1: ^Point rw, lw, woff: f32 @@ -2542,7 +2542,7 @@ Fill :: proc(ctx: ^Context) { ctx.cache.paths[:], ) - for path in &ctx.cache.paths { + for &path in ctx.cache.paths { ctx.fillTriCount += len(path.fill) - 2 ctx.fillTriCount += len(path.stroke) - 2 ctx.drawCallCount += 2 @@ -2588,7 +2588,7 @@ Stroke :: proc(ctx: ^Context) { ctx.cache.paths[:], ) - for path in &ctx.cache.paths { + for &path in ctx.cache.paths { ctx.strokeTriCount += len(path.stroke) - 2 ctx.drawCallCount += 1 } @@ -2597,7 +2597,7 @@ Stroke :: proc(ctx: ^Context) { DebugDumpPathCache :: proc(ctx: ^Context) { fmt.printf("~~~~~~~~~~~~~Dumping %d cached paths\n", len(ctx.cache.paths)) - for path, i in &ctx.cache.paths { + for &path, i in ctx.cache.paths { fmt.printf(" - Path %d\n", i) if len(path.fill) != 0 { From 330b393e1604b9a41fb8751b6446f73091c9f85c Mon Sep 17 00:00:00 2001 From: Rickard Andersson Date: Tue, 27 Jun 2023 21:37:10 +0300 Subject: [PATCH 73/97] fix(os): use `setenv` instead of `putenv` `setenv` doesn't copy the value that is put, which means that the previous code had a bug where we free'd the temporary memory and the environment was accidentally cleared right after the function finished. --- core/os/os_linux.odin | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/os/os_linux.odin b/core/os/os_linux.odin index e0b60fd36..aaff01165 100644 --- a/core/os/os_linux.odin +++ b/core/os/os_linux.odin @@ -458,6 +458,7 @@ foreign libc { @(link_name="execvp") _unix_execvp :: proc(path: cstring, argv: [^]cstring) -> int --- @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring --- @(link_name="putenv") _unix_putenv :: proc(cstring) -> c.int --- + @(link_name="setenv") _unix_setenv :: proc(key: cstring, value: cstring, overwrite: c.int) -> c.int --- @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: rawptr) -> rawptr --- @(link_name="exit") _unix_exit :: proc(status: c.int) -> ! --- @@ -894,7 +895,10 @@ get_env :: proc(key: string, allocator := context.allocator) -> (value: string) set_env :: proc(key, value: string) -> Errno { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() s := strings.concatenate({key, "=", value, "\x00"}, context.temp_allocator) - res := _unix_putenv(strings.unsafe_string_to_cstring(s)) + key_cstring := strings.unsafe_string_to_cstring(strings.concatenate({key, "\x00"}, context.temp_allocator)) + value_cstring := strings.unsafe_string_to_cstring(strings.concatenate({value, "\x00"}, context.temp_allocator)) + // NOTE(GoNZooo): `setenv` instead of `putenv` because it copies both key and value more commonly + res := _unix_setenv(key_cstring, value_cstring, 1) if res < 0 { return Errno(get_last_error()) } From 6ff0ce15e76019c28707a416f60852110b16d4fc Mon Sep 17 00:00:00 2001 From: Rickard Andersson Date: Tue, 27 Jun 2023 21:42:20 +0300 Subject: [PATCH 74/97] cleanup: remove leftover line --- core/os/os_linux.odin | 1 - 1 file changed, 1 deletion(-) diff --git a/core/os/os_linux.odin b/core/os/os_linux.odin index aaff01165..a2c07d98e 100644 --- a/core/os/os_linux.odin +++ b/core/os/os_linux.odin @@ -894,7 +894,6 @@ get_env :: proc(key: string, allocator := context.allocator) -> (value: string) set_env :: proc(key, value: string) -> Errno { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - s := strings.concatenate({key, "=", value, "\x00"}, context.temp_allocator) key_cstring := strings.unsafe_string_to_cstring(strings.concatenate({key, "\x00"}, context.temp_allocator)) value_cstring := strings.unsafe_string_to_cstring(strings.concatenate({value, "\x00"}, context.temp_allocator)) // NOTE(GoNZooo): `setenv` instead of `putenv` because it copies both key and value more commonly From d03d5d8f038ee5f964edd9e40441b9027c91f180 Mon Sep 17 00:00:00 2001 From: Rickard Andersson Date: Tue, 27 Jun 2023 21:46:00 +0300 Subject: [PATCH 75/97] style: use tabs :[ --- core/os/os_linux.odin | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/os/os_linux.odin b/core/os/os_linux.odin index a2c07d98e..0a3bdbb9e 100644 --- a/core/os/os_linux.odin +++ b/core/os/os_linux.odin @@ -894,9 +894,9 @@ get_env :: proc(key: string, allocator := context.allocator) -> (value: string) set_env :: proc(key, value: string) -> Errno { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - key_cstring := strings.unsafe_string_to_cstring(strings.concatenate({key, "\x00"}, context.temp_allocator)) - value_cstring := strings.unsafe_string_to_cstring(strings.concatenate({value, "\x00"}, context.temp_allocator)) - // NOTE(GoNZooo): `setenv` instead of `putenv` because it copies both key and value more commonly + key_cstring := strings.unsafe_string_to_cstring(strings.concatenate({key, "\x00"}, context.temp_allocator)) + value_cstring := strings.unsafe_string_to_cstring(strings.concatenate({value, "\x00"}, context.temp_allocator)) + // NOTE(GoNZooo): `setenv` instead of `putenv` because it copies both key and value more commonly res := _unix_setenv(key_cstring, value_cstring, 1) if res < 0 { return Errno(get_last_error()) From f048ad13b52d3c2be2fcd975f1039eb88604360f Mon Sep 17 00:00:00 2001 From: Rickard Andersson Date: Tue, 27 Jun 2023 21:48:53 +0300 Subject: [PATCH 76/97] fix(set_env): use `clone_to_cstring` instead of `unsafe_to_cstring` --- core/os/os_linux.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/os/os_linux.odin b/core/os/os_linux.odin index 0a3bdbb9e..1a4c1fddb 100644 --- a/core/os/os_linux.odin +++ b/core/os/os_linux.odin @@ -894,8 +894,8 @@ get_env :: proc(key: string, allocator := context.allocator) -> (value: string) set_env :: proc(key, value: string) -> Errno { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - key_cstring := strings.unsafe_string_to_cstring(strings.concatenate({key, "\x00"}, context.temp_allocator)) - value_cstring := strings.unsafe_string_to_cstring(strings.concatenate({value, "\x00"}, context.temp_allocator)) + key_cstring := strings.clone_to_cstring(key, context.temp_allocator) + value_cstring := strings.clone_to_cstring(value, context.temp_allocator) // NOTE(GoNZooo): `setenv` instead of `putenv` because it copies both key and value more commonly res := _unix_setenv(key_cstring, value_cstring, 1) if res < 0 { From d352e2fa31a36e5666cfc1e99983a0e04b36be29 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Tue, 27 Jun 2023 23:30:33 +0200 Subject: [PATCH 77/97] only run nightly upload if workflow is ran on main repo --- .github/workflows/nightly.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 67d0396c1..0bfaa11e9 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -7,6 +7,7 @@ on: jobs: build_windows: + if: github.repository == 'odin-lang/Odin' runs-on: windows-2022 steps: - uses: actions/checkout@v1 @@ -37,6 +38,7 @@ jobs: name: windows_artifacts path: dist build_ubuntu: + if: github.repository == 'odin-lang/Odin' runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 @@ -61,6 +63,7 @@ jobs: name: ubuntu_artifacts path: dist build_macos: + if: github.repository == 'odin-lang/Odin' runs-on: macOS-latest steps: - uses: actions/checkout@v1 From d50a8447209ef4e79a03a2a006c70987448d7d65 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 28 Jun 2023 11:04:51 +0100 Subject: [PATCH 78/97] Replace `mem` with `runtime` in `core:slice` --- core/slice/ptr.odin | 20 ++++++++++---------- core/slice/slice.odin | 30 ++++++++++++++++++------------ 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/core/slice/ptr.odin b/core/slice/ptr.odin index e2f1c3e7b..b17a27dc8 100644 --- a/core/slice/ptr.odin +++ b/core/slice/ptr.odin @@ -1,7 +1,7 @@ package slice import "core:builtin" -import "core:mem" +import "core:runtime" ptr_add :: proc(p: $P/^$T, x: int) -> ^T { return ([^]T)(p)[x:] @@ -27,9 +27,9 @@ ptr_swap_non_overlapping :: proc(x, y: rawptr, len: int) { a := rawptr(uintptr(x) + uintptr(i)) b := rawptr(uintptr(y) + uintptr(i)) - mem.copy(t, a, BLOCK_SIZE) - mem.copy(a, b, BLOCK_SIZE) - mem.copy(b, t, BLOCK_SIZE) + runtime.mem_copy(t, a, BLOCK_SIZE) + runtime.mem_copy(a, b, BLOCK_SIZE) + runtime.mem_copy(b, t, BLOCK_SIZE) } if i < len { @@ -38,9 +38,9 @@ ptr_swap_non_overlapping :: proc(x, y: rawptr, len: int) { a := rawptr(uintptr(x) + uintptr(i)) b := rawptr(uintptr(y) + uintptr(i)) - mem.copy(t, a, rem) - mem.copy(a, b, rem) - mem.copy(b, t, rem) + runtime.mem_copy(t, a, rem) + runtime.mem_copy(a, b, rem) + runtime.mem_copy(b, t, rem) } } @@ -59,9 +59,9 @@ ptr_swap_overlapping :: proc(x, y: rawptr, len: int) { for n := len; n > 0; n -= N { m := builtin.min(n, N) - mem.copy(&buffer, a, m) - mem.copy(a, b, m) - mem.copy(b, &buffer, m) + runtime.mem_copy(&buffer, a, m) + runtime.mem_copy(a, b, m) + runtime.mem_copy(b, &buffer, m) a, b = a[N:], b[N:] } diff --git a/core/slice/slice.odin b/core/slice/slice.odin index 412c90fc8..9a810141d 100644 --- a/core/slice/slice.odin +++ b/core/slice/slice.odin @@ -3,12 +3,12 @@ package slice import "core:intrinsics" import "core:builtin" import "core:math/bits" -import "core:mem" +import "core:runtime" _ :: intrinsics _ :: builtin _ :: bits -_ :: mem +_ :: runtime /* Turn a pointer and a length into a slice. @@ -164,7 +164,7 @@ equal :: proc(a, b: $T/[]$E) -> bool where intrinsics.type_is_comparable(E) { return false } when intrinsics.type_is_simple_compare(E) { - return mem.compare_ptrs(raw_data(a), raw_data(b), len(a)*size_of(E)) == 0 + return runtime.memory_compare(raw_data(a), raw_data(b), len(a)*size_of(E)) == 0 } else { for i in 0.. bool where intrinsics.type_is_simple_comp if len(a) != len(b) { return false } - return mem.compare_ptrs(raw_data(a), raw_data(b), len(a)*size_of(E)) == 0 + return runtime.memory_compare(raw_data(a), raw_data(b), len(a)*size_of(E)) == 0 } /* @@ -220,6 +220,12 @@ has_suffix :: proc(array: $T/[]$E, needle: E) -> bool where intrinsics.type_is_c return false } +zero :: proc(array: $T/[]$E) #no_bounds_check { + if len(array) > 0 { + intrinsics.mem_zero(raw_data(array), size_of(E)*len(array)) + } +} + fill :: proc(array: $T/[]$E, value: E) #no_bounds_check { if len(array) <= 0 { return @@ -250,7 +256,7 @@ swap_with_slice :: proc(a, b: $T/[]$E, loc := #caller_location) { } @(require_results) -concatenate :: proc(a: []$T/[]$E, allocator := context.allocator) -> (res: T, err: mem.Allocator_Error) #optional_allocator_error { +concatenate :: proc(a: []$T/[]$E, allocator := context.allocator) -> (res: T, err: runtime.Allocator_Error) #optional_allocator_error { if len(a) == 0 { return } @@ -268,7 +274,7 @@ concatenate :: proc(a: []$T/[]$E, allocator := context.allocator) -> (res: T, er // copies a slice into a new slice @(require_results) -clone :: proc(a: $T/[]$E, allocator := context.allocator) -> ([]E, mem.Allocator_Error) #optional_allocator_error { +clone :: proc(a: $T/[]$E, allocator := context.allocator) -> ([]E, runtime.Allocator_Error) #optional_allocator_error { d, err := make([]E, len(a), allocator) copy(d[:], a) return d, err @@ -276,7 +282,7 @@ clone :: proc(a: $T/[]$E, allocator := context.allocator) -> ([]E, mem.Allocator // copies slice into a new dynamic array -clone_to_dynamic :: proc(a: $T/[]$E, allocator := context.allocator) -> ([dynamic]E, mem.Allocator_Error) #optional_allocator_error { +clone_to_dynamic :: proc(a: $T/[]$E, allocator := context.allocator) -> ([dynamic]E, runtime.Allocator_Error) #optional_allocator_error { d, err := make([dynamic]E, len(a), allocator) copy(d[:], a) return d, err @@ -286,12 +292,12 @@ to_dynamic :: clone_to_dynamic // Converts slice into a dynamic array without cloning or allocating memory @(require_results) into_dynamic :: proc(a: $T/[]$E) -> [dynamic]E { - s := transmute(mem.Raw_Slice)a - d := mem.Raw_Dynamic_Array{ + s := transmute(runtime.Raw_Slice)a + d := runtime.Raw_Dynamic_Array{ data = s.data, len = 0, cap = s.len, - allocator = mem.nil_allocator(), + allocator = runtime.nil_allocator(), } return transmute([dynamic]E)d } @@ -373,7 +379,7 @@ as_ptr :: proc(array: $T/[]$E) -> [^]E { @(require_results) -mapper :: proc(s: $S/[]$U, f: proc(U) -> $V, allocator := context.allocator) -> (r: []V, err: mem.Allocator_Error) #optional_allocator_error { +mapper :: proc(s: $S/[]$U, f: proc(U) -> $V, allocator := context.allocator) -> (r: []V, err: runtime.Allocator_Error) #optional_allocator_error { r = make([]V, len(s), allocator) or_return for v, i in s { r[i] = f(v) @@ -402,7 +408,7 @@ filter :: proc(s: $S/[]$U, f: proc(U) -> bool, allocator := context.allocator) - } @(require_results) -scanner :: proc (s: $S/[]$U, initializer: $V, f: proc(V, U) -> V, allocator := context.allocator) -> (res: []V, err: mem.Allocator_Error) #optional_allocator_error { +scanner :: proc (s: $S/[]$U, initializer: $V, f: proc(V, U) -> V, allocator := context.allocator) -> (res: []V, err: runtime.Allocator_Error) #optional_allocator_error { if len(s) == 0 { return } res = make([]V, len(s), allocator) or_return From 9e9f3c485bfd283cda92c788a921e521e9c0a66b Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 28 Jun 2023 11:36:27 +0100 Subject: [PATCH 79/97] General code style clean up for `vendor:fontstash` --- vendor/fontstash/fontstash.odin | 495 +++++++++++++++----------------- 1 file changed, 230 insertions(+), 265 deletions(-) diff --git a/vendor/fontstash/fontstash.odin b/vendor/fontstash/fontstash.odin index 67b2c781c..93a48bc0d 100644 --- a/vendor/fontstash/fontstash.odin +++ b/vendor/fontstash/fontstash.odin @@ -7,6 +7,7 @@ import "core:os" import "core:mem" import "core:math" import "core:strings" +import "core:slice" import stbtt "vendor:stb/truetype" // This is a port from Fontstash into odin - specialized for nanovg @@ -23,13 +24,13 @@ import stbtt "vendor:stb/truetype" // leaves GPU vertex creation & texture management up to the user // texture atlas expands by default -INVALID :: -1 -MAX_STATES :: 20 -HASH_LUT_SIZE :: 256 -INIT_GLYPHS :: 256 +INVALID :: -1 +MAX_STATES :: 20 +HASH_LUT_SIZE :: 256 +INIT_GLYPHS :: 256 INIT_ATLAS_NODES :: 256 -MAX_FALLBACKS :: 20 -Glyph_Index :: i32 // in case you want to change the handle for glyph indices +MAX_FALLBACKS :: 20 +Glyph_Index :: i32 // in case you want to change the handle for glyph indices AlignHorizontal :: enum { LEFT, @@ -47,30 +48,30 @@ AlignVertical :: enum { Font :: struct { name: string, // allocated - info: stbtt.fontinfo, - loadedData: []byte, + info: stbtt.fontinfo, + loadedData: []byte, freeLoadedData: bool, // in case you dont want loadedData to be removed - ascender: f32, - descender: f32, + ascender: f32, + descender: f32, lineHeight: f32, glyphs: [dynamic]Glyph, - lut: [HASH_LUT_SIZE]int, + lut: [HASH_LUT_SIZE]int, - fallbacks: [MAX_FALLBACKS]int, + fallbacks: [MAX_FALLBACKS]int, nfallbacks: int, } Glyph :: struct { - codepoint: rune, - index: Glyph_Index, - next: int, - isize: i16, - blurSize: i16, + codepoint: rune, + index: Glyph_Index, + next: int, + isize: i16, + blurSize: i16, x0, y0, x1, y1: i16, - xoff, yoff: i16, - xadvance: i16, + xoff, yoff: i16, + xadvance: i16, } AtlasNode :: struct { @@ -78,8 +79,8 @@ AtlasNode :: struct { } Vertex :: struct #packed { - x, y: f32, - u, v: f32, + x, y: f32, + u, v: f32, color: [4]u8, } @@ -95,13 +96,13 @@ FontContext :: struct { nodes: [dynamic]AtlasNode, // actual pixels - textureData: []byte, // allocated using context.allocator + textureData: []byte, // allocated using context.allocator width, height: int, // 1 / texture_atlas_width, 1 / texture_atlas_height itw, ith: f32, // state - states: []State, + states: []State, state_count: int, // used states location: QuadLocation, @@ -118,24 +119,24 @@ FontContext :: struct { callbackUpdate: proc(data: rawptr, dirtyRect: [4]f32, textureData: rawptr), } -Init :: proc(using ctx: ^FontContext, w, h: int, loc: QuadLocation) { - userData = ctx - location = loc - fonts = make([dynamic]Font, 0, 8) +Init :: proc(ctx: ^FontContext, w, h: int, loc: QuadLocation) { + ctx.userData = ctx + ctx.location = loc + ctx.fonts = make([dynamic]Font, 0, 8) - itw = f32(1) / f32(w) - ith = f32(1) / f32(h) - textureData = make([]byte, w * h) + ctx.itw, ctx.ith = 1.0 / f32(w), 1.0 / f32(h) + + ctx.textureData = make([]byte, w * h) - width = w - height = h - nodes = make([dynamic]AtlasNode, 0, INIT_ATLAS_NODES) + ctx.width = w + ctx.height = h + ctx.nodes = make([dynamic]AtlasNode, 0, INIT_ATLAS_NODES) __dirtyRectReset(ctx) - states = make([]State, MAX_STATES) + ctx.states = make([]State, MAX_STATES) // NOTE NECESSARY - append(&nodes, AtlasNode { + append(&ctx.nodes, AtlasNode{ width = i16(w), }) @@ -145,8 +146,8 @@ Init :: proc(using ctx: ^FontContext, w, h: int, loc: QuadLocation) { ClearState(ctx) } -Destroy :: proc(using ctx: ^FontContext) { - for font in fonts { +Destroy :: proc(ctx: ^FontContext) { + for font in ctx.fonts { if font.freeLoadedData { delete(font.loadedData) } @@ -155,18 +156,18 @@ Destroy :: proc(using ctx: ^FontContext) { delete(font.glyphs) } - delete(states) - delete(textureData) - delete(fonts) - delete(nodes) + delete(ctx.states) + delete(ctx.textureData) + delete(ctx.fonts) + delete(ctx.nodes) } -Reset :: proc(using ctx: ^FontContext) { - __atlasReset(ctx, width, height) +Reset :: proc(ctx: ^FontContext) { + __atlasReset(ctx, ctx.width, ctx.height) __dirtyRectReset(ctx) - mem.zero_slice(textureData) + slice.zero(ctx.textureData) - for &font in fonts { + for &font in ctx.fonts { __lutReset(&font) } @@ -175,84 +176,67 @@ Reset :: proc(using ctx: ^FontContext) { ClearState(ctx) } -__atlasInsertNode :: proc(using ctx: ^FontContext, idx, x, y, w: int) { - // resize is alright here - resize(&nodes, len(nodes) + 1) - - // shift nodes up once to leave space at idx - for i := len(nodes) - 1; i > idx; i -= 1 { - nodes[i] = nodes[i - 1] - } - - // set new inserted one to properties - nodes[idx].x = i16(x) - nodes[idx].y = i16(y) - nodes[idx].width = i16(w) -} - -__atlasRemoveNode :: proc(using ctx: ^FontContext, idx: int) { - if len(nodes) == 0 { - return - } - - // remove node at index, shift elements down - for i in idx.. width { - __atlasInsertNode(ctx, len(nodes), width, 0, w - width) - } - - width = w - height = h -} - -__atlasReset :: proc(using ctx: ^FontContext, w, h: int) { - width = w - height = h - clear(&nodes) - - // init root node - append(&nodes, AtlasNode { +__atlasInsertNode :: proc(ctx: ^FontContext, idx: int, x, y, w: int) { + inject_at(&ctx.nodes, idx, AtlasNode{ + x = i16(x), + y = i16(y), width = i16(w), }) } -__AtlasAddSkylineLevel :: proc(using ctx: ^FontContext, idx, x, y, w, h: int) { +__atlasRemoveNode :: proc(ctx: ^FontContext, idx: int) { + if len(ctx.nodes) == 0 { + return + } + + ordered_remove(&ctx.nodes, idx) +} + +__atlasExpand :: proc(ctx: ^FontContext, w, h: int) { + if w > ctx.width { + __atlasInsertNode(ctx, len(ctx.nodes), ctx.width, 0, w - ctx.width) + } + + ctx.width, ctx.height = w, h +} + +__atlasReset :: proc(ctx: ^FontContext, w, h: int) { + ctx.width, ctx.height = w, h + clear(&ctx.nodes) + + // init root node + append(&ctx.nodes, AtlasNode{ + width = i16(w), + }) +} + +__AtlasAddSkylineLevel :: proc(using ctx: ^FontContext, idx: int, x, y, w, h: int) { // insert new node __atlasInsertNode(ctx, idx, x, y + h, w) // Delete skyline segments that fall under the shadow of the new segment. for i := idx + 1; i < len(nodes); i += 1 { - if nodes[i].x < nodes[i - 1].x + nodes[i - 1].width { - shrink := nodes[i-1].x + nodes[i-1].width - nodes[i].x - nodes[i].x += i16(shrink) - nodes[i].width -= i16(shrink) - - if nodes[i].width <= 0 { - __atlasRemoveNode(ctx, i) - i -= 1 - } else { - break - } - } else { + if nodes[i].x >= nodes[i-1].x + nodes[i-1].width { break } + shrink := nodes[i-1].x + nodes[i-1].width - nodes[i].x + nodes[i].x += i16(shrink) + nodes[i].width -= i16(shrink) + + if nodes[i].width > 0 { + break + } + __atlasRemoveNode(ctx, i) + i -= 1 } // Merge same height skyline segments that are next to each other. - for i := 0; i < len(nodes) - 1; i += 1 { - if nodes[i].y == nodes[i + 1].y { - nodes[i].width += nodes[i + 1].width - __atlasRemoveNode(ctx, i + 1) - i -= 1 + for i := 0; i < len(nodes) - 1; /**/ { + if nodes[i].y == nodes[i+1].y { + nodes[i].width += nodes[i+1].width + __atlasRemoveNode(ctx, i+1) + } else { + i += 1 } } } @@ -261,14 +245,14 @@ __AtlasRectFits :: proc(using ctx: ^FontContext, i, w, h: int) -> int { // Checks if there is enough space at the location of skyline span 'i', // and return the max height of all skyline spans under that at that location, // (think tetris block being dropped at that position). Or -1 if no space found. - x := int(nodes[i].x) - y := int(nodes[i].y) - + + i := i + x, y := int(nodes[i].x), int(nodes[i].y) + if x + w > width { return -1 } - i := i space_left := w for space_left > 0 { if i == len(nodes) { @@ -288,8 +272,7 @@ __AtlasRectFits :: proc(using ctx: ^FontContext, i, w, h: int) -> int { } __AtlasAddRect :: proc(using ctx: ^FontContext, rw, rh: int) -> (rx, ry: int, ok: bool) { - besth := height - bestw := width + bestw, besth := width, height besti, bestx, besty := -1, -1, -1 // Bottom left fit heuristic. @@ -313,18 +296,11 @@ __AtlasAddRect :: proc(using ctx: ^FontContext, rw, rh: int) -> (rx, ry: int, ok // Perform the actual packing. __AtlasAddSkylineLevel(ctx, besti, bestx, besty, rw, rh) - ok = true - rx = bestx - ry = besty - return + return bestx, besty, true } -__AtlasAddWhiteRect :: proc(ctx: ^FontContext, w, h: int) { - gx, gy, ok := __AtlasAddRect(ctx, w, h) - - if !ok { - return - } +__AtlasAddWhiteRect :: proc(ctx: ^FontContext, w, h: int) -> bool { + gx, gy := __AtlasAddRect(ctx, w, h) or_return // Rasterize dst := ctx.textureData[gx + gy * ctx.width:] @@ -336,10 +312,12 @@ __AtlasAddWhiteRect :: proc(ctx: ^FontContext, w, h: int) { dst = dst[ctx.width:] } - ctx.dirtyRect[0] = cast(f32) min(int(ctx.dirtyRect[0]), gx) - ctx.dirtyRect[1] = cast(f32) min(int(ctx.dirtyRect[1]), gy) - ctx.dirtyRect[2] = cast(f32) max(int(ctx.dirtyRect[2]), gx + w) - ctx.dirtyRect[3] = cast(f32) max(int(ctx.dirtyRect[3]), gy + h) + ctx.dirtyRect[0] = f32(min(int(ctx.dirtyRect[0]), gx)) + ctx.dirtyRect[1] = f32(min(int(ctx.dirtyRect[1]), gy)) + ctx.dirtyRect[2] = f32(max(int(ctx.dirtyRect[2]), gx + w)) + ctx.dirtyRect[3] = f32(max(int(ctx.dirtyRect[3]), gy + h)) + + return true } AddFontPath :: proc( @@ -359,25 +337,26 @@ AddFontPath :: proc( // push a font to the font stack // optionally init with ascii characters at a wanted size AddFontMem :: proc( - ctx: ^FontContext, - name: string, - data: []u8, + ctx: ^FontContext, + name: string, + data: []u8, freeLoadedData: bool, ) -> int { - append(&ctx.fonts, Font {}) + append(&ctx.fonts, Font{}) res := &ctx.fonts[len(ctx.fonts) - 1] - res.loadedData = data + res.loadedData = data res.freeLoadedData = freeLoadedData - res.name = strings.clone(name) + res.name = strings.clone(name) stbtt.InitFont(&res.info, &res.loadedData[0], 0) ascent, descent, line_gap: i32 + stbtt.GetFontVMetrics(&res.info, &ascent, &descent, &line_gap) fh := f32(ascent - descent) - res.ascender = f32(ascent) / fh - res.descender = f32(descent) / fh + res.ascender = f32(ascent) / fh + res.descender = f32(descent) / fh res.lineHeight = (fh + f32(line_gap)) / fh - res.glyphs = make([dynamic]Glyph, 0, INIT_GLYPHS) + res.glyphs = make([dynamic]Glyph, 0, INIT_GLYPHS) __lutReset(res) return len(ctx.fonts) - 1 @@ -417,9 +396,7 @@ GetFontByName :: proc(ctx: ^FontContext, name: string) -> int { __lutReset :: proc(font: ^Font) { // set lookup table - for i in 0.. u32 { @@ -434,23 +411,23 @@ __hashint :: proc(a: u32) -> u32 { } __renderGlyphBitmap :: proc( - font: ^Font, - output: []u8, - outWidth: i32, - outHeight: i32, - outStride: i32, - scaleX: f32, - scaleY: f32, + font: ^Font, + output: []u8, + outWidth: i32, + outHeight: i32, + outStride: i32, + scaleX: f32, + scaleY: f32, glyphIndex: Glyph_Index, ) { stbtt.MakeGlyphBitmap(&font.info, raw_data(output), outWidth, outHeight, outStride, scaleX, scaleY, glyphIndex) } __buildGlyphBitmap :: proc( - font: ^Font, + font: ^Font, glyphIndex: Glyph_Index, - pixelSize: f32, - scale: f32, + pixelSize: f32, + scale: f32, ) -> (advance, lsb, x0, y0, x1, y1: i32) { stbtt.GetGlyphHMetrics(&font.info, glyphIndex, &advance, &lsb) stbtt.GetGlyphBitmapBox(&font.info, glyphIndex, scale, scale, &x0, &y0, &x1, &y1) @@ -459,28 +436,26 @@ __buildGlyphBitmap :: proc( // get glyph and push to atlas if not exists __getGlyph :: proc( - ctx: ^FontContext, - font: ^Font, + ctx: ^FontContext, + font: ^Font, codepoint: rune, - isize: i16, - blur: i16 = 0, -) -> (res: ^Glyph) #no_bounds_check { + isize: i16, + blur: i16 = 0, +) -> (res: ^Glyph, ok: bool) #no_bounds_check { if isize < 2 { return } // find code point and size h := __hashint(u32(codepoint)) & (HASH_LUT_SIZE - 1) - i := font.lut[h] - for i != -1 { + for i := font.lut[h]; i != -1; /**/ { glyph := &font.glyphs[i] - if - glyph.codepoint == codepoint && - glyph.isize == isize && - glyph.blurSize == blur - { + if glyph.codepoint == codepoint && + glyph.isize == isize && + glyph.blurSize == blur { res = glyph + ok = true return } @@ -505,39 +480,34 @@ __getGlyph :: proc( } pixel_size := f32(isize) / 10 - blurSize := min(blur, 20) - padding := i16(blurSize + 2) // 2 minimum padding - scale := __getPixelHeightScale(render_font, pixel_size) + blurSize := min(blur, 20) + padding := i16(blurSize + 2) // 2 minimum padding + scale := __getPixelHeightScale(render_font, pixel_size) advance, _, x0, y0, x1, y1 := __buildGlyphBitmap(render_font, glyph_index, pixel_size, scale) gw := (x1 - x0) + i32(padding) * 2 gh := (y1 - y0) + i32(padding) * 2 // Find free spot for the rect in the atlas - gx, gy, ok := __AtlasAddRect(ctx, int(gw), int(gh)) - if !ok { + gx, gy, rect_ok := __AtlasAddRect(ctx, int(gw), int(gh)) + if !rect_ok { // try again with expanded ExpandAtlas(ctx, ctx.width * 2, ctx.height * 2) - gx, gy, ok = __AtlasAddRect(ctx, int(gw), int(gh)) - } - - // still not ok? - if !ok { - return + gx, gy = __AtlasAddRect(ctx, int(gw), int(gh)) or_return } // Init glyph. - append(&font.glyphs, Glyph { + append(&font.glyphs, Glyph{ codepoint = codepoint, - isize = isize, - blurSize = blurSize, - index = glyph_index, - x0 = i16(gx), - y0 = i16(gy), - x1 = i16(i32(gx) + gw), - y1 = i16(i32(gy) + gh), - xadvance = i16(scale * f32(advance) * 10), - xoff = i16(x0 - i32(padding)), - yoff = i16(y0 - i32(padding)), + isize = isize, + blurSize = blurSize, + index = glyph_index, + x0 = i16(gx), + y0 = i16(gy), + x1 = i16(i32(gx) + gw), + y1 = i16(i32(gy) + gh), + xadvance = i16(scale * f32(advance) * 10), + xoff = i16(x0 - i32(padding)), + yoff = i16(y0 - i32(padding)), // insert char to hash lookup. next = font.lut[h], @@ -575,11 +545,12 @@ __getGlyph :: proc( __blur(dst, int(gw), int(gh), ctx.width, blurSize) } - ctx.dirtyRect[0] = cast(f32) min(int(ctx.dirtyRect[0]), int(res.x0)) - ctx.dirtyRect[1] = cast(f32) min(int(ctx.dirtyRect[1]), int(res.y0)) - ctx.dirtyRect[2] = cast(f32) max(int(ctx.dirtyRect[2]), int(res.x1)) - ctx.dirtyRect[3] = cast(f32) max(int(ctx.dirtyRect[3]), int(res.y1)) + ctx.dirtyRect[0] = f32(min(int(ctx.dirtyRect[0]), int(res.x0))) + ctx.dirtyRect[1] = f32(min(int(ctx.dirtyRect[1]), int(res.y0))) + ctx.dirtyRect[2] = f32(max(int(ctx.dirtyRect[2]), int(res.x1))) + ctx.dirtyRect[3] = f32(max(int(ctx.dirtyRect[3]), int(res.y1))) + ok = true return } @@ -640,7 +611,7 @@ __blurRows :: proc(dst: []u8, w, h, dstStride, alpha: int) { } __blur :: proc(dst: []u8, w, h, dstStride: int, blurSize: i16) { - assert(blurSize != 0) + assert(blurSize > 0) // Calculate the alpha such that 90% of the kernel is within the radius. (Kernel extends to infinity) sigma := f32(blurSize) * 0.57735 // 1 / sqrt(3) @@ -710,9 +681,10 @@ ExpandAtlas :: proc(ctx: ^FontContext, width, height: int, allocator := context. ResetAtlas :: proc(ctx: ^FontContext, width, height: int, allocator := context.allocator) -> bool { if width == ctx.width && height == ctx.height { // just clear - mem.zero_slice(ctx.textureData) + slice.zero(ctx.textureData) } else { // realloc + delete(ctx.textureData, allocator) ctx.textureData = make([]byte, width * height, allocator) } @@ -732,7 +704,7 @@ ResetAtlas :: proc(ctx: ^FontContext, width, height: int, allocator := context.a ctx.itw = 1.0 / f32(width) ctx.ith = 1.0 / f32(height) - __AtlasAddWhiteRect(ctx, 2, 2) + _ = __AtlasAddWhiteRect(ctx, 2, 2) return true } @@ -769,16 +741,16 @@ CodepointWidth :: proc( // get top and bottom line boundary LineBounds :: proc(ctx: ^FontContext, y: f32) -> (miny, maxy: f32) { state := __getState(ctx) - font := __getFont(ctx, state.font) + font := __getFont(ctx, state.font) isize := i16(state.size * 10.0) y := y y += __getVerticalAlign(ctx, font, state.av, isize) if ctx.location == .TOPLEFT { - miny = y - font.ascender * f32(isize) / 10 + miny = y - font.ascender * f32(isize) / 10 maxy = miny + font.lineHeight * f32(isize / 10) } else if ctx.location == .BOTTOMLEFT { - miny = y + font.ascender * f32(isize) / 10 + miny = y + font.ascender * f32(isize) / 10 maxy = miny - font.lineHeight * f32(isize / 10) } @@ -809,9 +781,9 @@ ValidateTexture :: proc(using ctx: ^FontContext, dirty: ^[4]f32) -> bool { // get alignment based on font __getVerticalAlign :: proc( - ctx: ^FontContext, - font: ^Font, - av: AlignVertical, + ctx: ^FontContext, + font: ^Font, + av: AlignVertical, pixelSize: i16, ) -> (res: f32) { switch ctx.location { @@ -842,7 +814,7 @@ UTF8_ACCEPT :: 0 UTF8_REJECT :: 1 @(private) -utf8d := [400]u8 { +utf8d := [400]u8{ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 00..1f 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 20..3f 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 40..5f @@ -871,11 +843,11 @@ __decutf8 :: #force_inline proc(state: ^rune, codep: ^rune, b: byte) -> bool { // state used to share font options State :: struct { - font: int, - size: f32, - color: [4]u8, + font: int, + size: f32, + color: [4]u8, spacing: f32, - blur: f32, + blur: f32, ah: AlignHorizontal, av: AlignVertical, @@ -890,21 +862,21 @@ Quad :: struct { // text iteration with custom settings TextIter :: struct { x, y, nextx, nexty, scale, spacing: f32, - isize, iblur: i16, + isize, iblur: i16, - font: ^Font, + font: ^Font, previousGlyphIndex: Glyph_Index, // unicode iteration - utf8state: rune, // utf8 - codepoint: rune, - text: string, + utf8state: rune, // utf8 + codepoint: rune, + text: string, codepointCount: int, // byte indices - str: int, + str: int, next: int, - end: int, + end: int, } // push a state, copies the current one over to the next one @@ -930,13 +902,13 @@ PopState :: proc(using ctx: ^FontContext) { // clear current state ClearState :: proc(ctx: ^FontContext) { state := __getState(ctx) - state.size = 12 - state.color = 255 - state.blur = 0 + state.size = 12 + state.color = 255 + state.blur = 0 state.spacing = 0 - state.font = 0 - state.ah = .LEFT - state.av = .BASELINE + state.font = 0 + state.ah = .LEFT + state.av = .BASELINE } __getState :: #force_inline proc(ctx: ^FontContext) -> ^State #no_bounds_check { @@ -975,13 +947,13 @@ SetAlignVertical :: proc(ctx: ^FontContext, av: AlignVertical) { } __getQuad :: proc( - ctx: ^FontContext, + ctx: ^FontContext, font: ^Font, previousGlyphIndex: i32, - glyph: ^Glyph, + glyph: ^Glyph, - scale: f32, + scale: f32, spacing: f32, x, y: ^f32, @@ -1036,27 +1008,27 @@ __getQuad :: proc( // init text iter struct with settings TextIterInit :: proc( - ctx: ^FontContext, - x: f32, - y: f32, + ctx: ^FontContext, + x: f32, + y: f32, text: string, ) -> (res: TextIter) { + + x, y := x, y + state := __getState(ctx) - res.font = __getFont(ctx, state.font) + res.font = __getFont(ctx, state.font) res.isize = i16(f32(state.size) * 10) res.iblur = i16(state.blur) res.scale = __getPixelHeightScale(res.font, f32(res.isize) / 10) // align horizontally - x := x - y := y switch state.ah { - case .LEFT: {} - + case .LEFT: + /**/ case .CENTER: width := TextBounds(ctx, text, x, y, nil) x = math.round(x - width * 0.5) - case .RIGHT: width := TextBounds(ctx, text, x, y, nil) x -= width @@ -1066,24 +1038,22 @@ TextIterInit :: proc( y = math.round(y + __getVerticalAlign(ctx, res.font, state.av, res.isize)) // set positions - res.x = x - res.nextx = x - res.y = y - res.nexty = y + res.x, res.nextx = x, x + res.y, res.nexty = y, y res.previousGlyphIndex = -1 res.spacing = state.spacing - res.text = text + res.text = text - res.str = 0 + res.str = 0 res.next = 0 - res.end = len(text) + res.end = len(text) return } // step through each codepoint TextIterNext :: proc( - ctx: ^FontContext, + ctx: ^FontContext, iter: ^TextIter, quad: ^Quad, ) -> (ok: bool) { @@ -1097,13 +1067,12 @@ TextIterNext :: proc( iter.x = iter.nextx iter.y = iter.nexty iter.codepointCount += 1 - glyph := __getGlyph(ctx, iter.font, iter.codepoint, iter.isize, iter.iblur) - - if glyph != nil { + if glyph, ok := __getGlyph(ctx, iter.font, iter.codepoint, iter.isize, iter.iblur); ok { __getQuad(ctx, iter.font, iter.previousGlyphIndex, glyph, iter.scale, iter.spacing, &iter.nextx, &iter.nexty, quad) + iter.previousGlyphIndex = glyph.index + } else { + iter.previousGlyphIndex = -1 } - - iter.previousGlyphIndex = glyph == nil ? -1 : glyph.index ok = true break } @@ -1115,24 +1084,21 @@ TextIterNext :: proc( // width of a text line, optionally the full rect TextBounds :: proc( - ctx: ^FontContext, - text: string, - x: f32 = 0, - y: f32 = 0, + ctx: ^FontContext, + text: string, + x: f32 = 0, + y: f32 = 0, bounds: ^[4]f32 = nil, ) -> f32 { state := __getState(ctx) isize := i16(state.size * 10) iblur := i16(state.blur) - font := __getFont(ctx, state.font) + font := __getFont(ctx, state.font) // bunch of state - x := x - y := y - minx := x - maxx := x - miny := y - maxy := y + x, y := x, y + minx, maxx := x, x + miny, maxy := y, y start_x := x // iterate @@ -1143,9 +1109,7 @@ TextBounds :: proc( codepoint: rune for byte_offset in 0.. (ascender, descender, lineHeight: f32) { state := __getState(ctx) isize := i16(state.size * 10.0) - font := __getFont(ctx, state.font) - ascender = font.ascender * f32(isize / 10) - descender = font.descender * f32(isize / 10) + font := __getFont(ctx, state.font) + ascender = font.ascender * f32(isize / 10) + descender = font.descender * f32(isize / 10) lineHeight = font.lineHeight * f32(isize / 10) return } // reset to single state -BeginState :: proc(using ctx: ^FontContext) { - state_count = 0 +BeginState :: proc(ctx: ^FontContext) { + ctx.state_count = 0 PushState(ctx) ClearState(ctx) } @@ -1223,7 +1189,6 @@ EndState :: proc(using ctx: ^FontContext) { if callbackUpdate != nil { callbackUpdate(userData, dirtyRect, raw_data(textureData)) } - __dirtyRectReset(ctx) } } \ No newline at end of file From ebed66d4ceee206998500b604a664e72a3ba9539 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 28 Jun 2023 11:57:37 +0100 Subject: [PATCH 80/97] General code style clean up for `vendor:nanovg` --- vendor/nanovg/nanovg.odin | 864 ++++++++++++++++++-------------------- 1 file changed, 413 insertions(+), 451 deletions(-) diff --git a/vendor/nanovg/nanovg.odin b/vendor/nanovg/nanovg.odin index bc0335f6a..6b06e3534 100644 --- a/vendor/nanovg/nanovg.odin +++ b/vendor/nanovg/nanovg.odin @@ -10,21 +10,21 @@ import "core:fmt" import "../fontstash" import stbi "vendor:stb/image" -AlignVertical :: fontstash.AlignVertical +AlignVertical :: fontstash.AlignVertical AlignHorizontal :: fontstash.AlignHorizontal INIT_FONTIMAGE_SIZE :: 512 -MAX_FONTIMAGE_SIZE :: 2048 -MAX_FONTIMAGES :: 4 +MAX_FONTIMAGE_SIZE :: 2048 +MAX_FONTIMAGES :: 4 -MAX_STATES :: 32 +MAX_STATES :: 32 INIT_COMMANDS_SIZE :: 256 -INIT_POINTS_SIZE :: 128 -INIT_PATH_SIZE :: 16 -INIT_VERTS_SIZE :: 26 -KAPPA :: 0.5522847493 +INIT_POINTS_SIZE :: 128 +INIT_PATH_SIZE :: 16 +INIT_VERTS_SIZE :: 26 +KAPPA :: 0.5522847493 -Color :: [4]f32 +Color :: [4]f32 Matrix :: [6]f32 Vertex :: [4]f32 // x,y,u,v @@ -40,13 +40,13 @@ ImageFlag :: enum { ImageFlags :: bit_set[ImageFlag] Paint :: struct { - xform: Matrix, - extent: [2]f32, - radius: f32, - feather: f32, + xform: Matrix, + extent: [2]f32, + radius: f32, + feather: f32, innerColor: Color, outerColor: Color, - image: int, + image: int, } Winding :: enum { @@ -110,7 +110,7 @@ Texture :: enum { } ScissorT :: struct { - xform: Matrix, + xform: Matrix, extent: [2]f32, } @@ -131,83 +131,83 @@ PointFlag :: enum { PointFlags :: bit_set[PointFlag] Point :: struct { - x, y: f32, - dx, dy: f32, - len: f32, + x, y: f32, + dx, dy: f32, + len: f32, dmx, dmy: f32, - flags: PointFlags, + flags: PointFlags, } PathCache :: struct { points: [dynamic]Point, - paths: [dynamic]Path, - verts: [dynamic]Vertex, + paths: [dynamic]Path, + verts: [dynamic]Vertex, bounds: [4]f32, } Path :: struct { - first: int, - count: int, - closed: bool, - nbevel: int, - fill: []Vertex, - stroke: []Vertex, + first: int, + count: int, + closed: bool, + nbevel: int, + fill: []Vertex, + stroke: []Vertex, winding: Winding, - convex: bool, + convex: bool, } State :: struct { compositeOperation: CompositeOperationState, - shapeAntiAlias: bool, - fill: Paint, - stroke: Paint, - strokeWidth: f32, - miterLimit: f32, - lineJoin: LineCapType, - lineCap: LineCapType, - alpha: f32, - xform: Matrix, - scissor: ScissorT, + shapeAntiAlias: bool, + fill: Paint, + stroke: Paint, + strokeWidth: f32, + miterLimit: f32, + lineJoin: LineCapType, + lineCap: LineCapType, + alpha: f32, + xform: Matrix, + scissor: ScissorT, // font state - fontSize: f32, - letterSpacing: f32, - lineHeight: f32, - fontBlur: f32, + fontSize: f32, + letterSpacing: f32, + lineHeight: f32, + fontBlur: f32, alignHorizontal: AlignHorizontal, - alignVertical: AlignVertical, - fontId: int, + alignVertical: AlignVertical, + fontId: int, } Context :: struct { - params: Params, - commands: [dynamic]f32, - commandx, commandy: f32, - states: [MAX_STATES]State, - nstates: int, - cache: PathCache, - tessTol: f32, - distTol: f32, - fringeWidth: f32, + params: Params, + commands: [dynamic]f32, + commandx, commandy: f32, + states: [MAX_STATES]State, + nstates: int, + cache: PathCache, + tessTol: f32, + distTol: f32, + fringeWidth: f32, devicePxRatio: f32, // font - fs: fontstash.FontContext, - fontImages: [MAX_FONTIMAGES]int, + fs: fontstash.FontContext, + fontImages: [MAX_FONTIMAGES]int, fontImageIdx: int, // stats - drawCallCount: int, - fillTriCount: int, + drawCallCount: int, + fillTriCount: int, strokeTriCount: int, - textTriCount: int, + textTriCount: int, // flush texture textureDirty: bool, } Params :: struct { - userPtr: rawptr, + userPtr: rawptr, edgeAntiAlias: bool, // callbacks to fill out @@ -216,19 +216,19 @@ Params :: struct { // textures calls renderCreateTexture: proc( - uptr: rawptr, - type: Texture, - w, h: int, + uptr: rawptr, + type: Texture, + w, h: int, imageFlags: ImageFlags, - data: []byte, + data: []byte, ) -> int, renderDeleteTexture: proc(uptr: rawptr, image: int) -> bool, renderUpdateTexture: proc( - uptr: rawptr, + uptr: rawptr, image: int, - x, y: int, - w, h: int, - data: []byte, + x, y: int, + w, h: int, + data: []byte, ) -> bool, renderGetTextureSize: proc(uptr: rawptr, image: int, w, h: ^int) -> bool, @@ -237,37 +237,37 @@ Params :: struct { renderCancel: proc(uptr: rawptr), renderFlush: proc(uptr: rawptr), renderFill: proc( - uptr: rawptr, - paint: ^Paint, + uptr: rawptr, + paint: ^Paint, compositeOperation: CompositeOperationState, - scissor: ^ScissorT, - fringe: f32, - bounds: [4]f32, - paths: []Path, + scissor: ^ScissorT, + fringe: f32, + bounds: [4]f32, + paths: []Path, ), renderStroke: proc( - uptr: rawptr, - paint: ^Paint, + uptr: rawptr, + paint: ^Paint, compositeOperation: CompositeOperationState, - scissor: ^ScissorT, - fringe: f32, - strokeWidth: f32, - paths: []Path, + scissor: ^ScissorT, + fringe: f32, + strokeWidth: f32, + paths: []Path, ), renderTriangles: proc( - uptr: rawptr, - paint: ^Paint, + uptr: rawptr, + paint: ^Paint, compositeOperation: CompositeOperationState, - scissor: ^ScissorT, - verts: []Vertex, - fringe: f32, + scissor: ^ScissorT, + verts: []Vertex, + fringe: f32, ), } __allocPathCache :: proc(c: ^PathCache) { c.points = make([dynamic]Point, 0, INIT_POINTS_SIZE) - c.paths = make([dynamic]Path, 0, INIT_PATH_SIZE) - c.verts = make([dynamic]Vertex, 0, INIT_VERTS_SIZE) + c.paths = make([dynamic]Path, 0, INIT_PATH_SIZE) + c.verts = make([dynamic]Vertex, 0, INIT_VERTS_SIZE) } __deletePathCache :: proc(c: PathCache) { @@ -277,14 +277,14 @@ __deletePathCache :: proc(c: PathCache) { } __setDevicePxRatio :: proc(ctx: ^Context, ratio: f32) { - ctx.tessTol = 0.25 / ratio - ctx.distTol = 0.01 / ratio - ctx.fringeWidth = 1.0 / ratio + ctx.tessTol = 0.25 / ratio + ctx.distTol = 0.01 / ratio + ctx.fringeWidth = 1.0 / ratio ctx.devicePxRatio = ratio } __getState :: #force_inline proc(ctx: ^Context) -> ^State #no_bounds_check { - return &ctx.states[ctx.nstates - 1] + return &ctx.states[ctx.nstates-1] } CreateInternal :: proc(params: Params) -> (ctx: ^Context) { @@ -311,7 +311,7 @@ CreateInternal :: proc(params: Params) -> (ctx: ^Context) { // handle to the image needs to be set to the new generated texture ctx.fs.callbackResize = proc(data: rawptr, w, h: int) { - ctx := cast(^Context) data + ctx := (^Context)(data) ctx.fontImages[0] = ctx.params.renderCreateTexture(ctx.params.userPtr, .Alpha, w, h, {}, ctx.fs.textureData) } @@ -326,7 +326,7 @@ DeleteInternal :: proc(ctx: ^Context) { __deletePathCache(ctx.cache) fontstash.Destroy(&ctx.fs) - for &image in ctx.fontImages { + for image in ctx.fontImages { if image != 0 { DeleteImage(ctx, image) } @@ -350,9 +350,9 @@ DeleteInternal :: proc(ctx: ^Context) { devicePixelRatio to: frameBufferWidth / windowWidth. */ BeginFrame :: proc( - ctx: ^Context, - windowWidth: f32, - windowHeight: f32, + ctx: ^Context, + windowWidth: f32, + windowHeight: f32, devicePixelRatio: f32, ) { ctx.nstates = 0 @@ -371,9 +371,9 @@ BeginFrame :: proc( @(deferred_out=EndFrame) FrameScoped :: proc( - ctx: ^Context, - windowWidth: f32, - windowHeight: f32, + ctx: ^Context, + windowWidth: f32, + windowHeight: f32, devicePixelRatio: f32, ) -> ^Context { BeginFrame(ctx, windowWidth, windowHeight, devicePixelRatio) @@ -454,7 +454,7 @@ RGBA :: proc(r, g, b, a: u8) -> (res: Color) { LerpRGBA :: proc(c0, c1: Color, u: f32) -> (cint: Color) { clamped := clamp(u, 0.0, 1.0) oneminu := 1.0 - clamped - for i in 0..<4 { + for _, i in cint { cint[i] = c0[i] * oneminu + c1[i] * clamped } @@ -510,12 +510,9 @@ HSLA :: proc(hue, saturation, lightness: f32, a: u8) -> (col: Color) { // hex to 0xAARRGGBB color ColorHex :: proc(color: u32) -> (res: Color) { color := color - res.b = f32(0x000000FF & color) / 255 - color = color >> 8 - res.g = f32(0x000000FF & color) / 255 - color = color >> 8 - res.r = f32(0x000000FF & color) / 255 - color = color >> 8 + res.b = f32(0x000000FF & color) / 255; color >>= 8 + res.g = f32(0x000000FF & color) / 255; color >>= 8 + res.r = f32(0x000000FF & color) / 255; color >>= 8 res.a = f32(0x000000FF & color) / 255 return } @@ -634,7 +631,7 @@ TransformInverse :: proc(inv: ^Matrix, t: Matrix) -> bool { TransformPoint :: proc( dx: ^f32, dy: ^f32, - t: Matrix, + t: Matrix, sx: f32, sy: f32, ) { @@ -667,7 +664,7 @@ Save :: proc(ctx: ^Context) { // copy prior if ctx.nstates > 0 { - ctx.states[ctx.nstates] = ctx.states[ctx.nstates - 1] + ctx.states[ctx.nstates] = ctx.states[ctx.nstates-1] } ctx.nstates += 1 @@ -691,8 +688,8 @@ SaveScoped :: #force_inline proc(ctx: ^Context) { __setPaintColor :: proc(p: ^Paint, color: Color) { p^ = {} TransformIdentity(&p.xform) - p.radius = 0 - p.feather = 1 + p.radius = 0 + p.feather = 1 p.innerColor = color p.outerColor = color } @@ -706,25 +703,25 @@ Reset :: proc(ctx: ^Context) { __setPaintColor(&state.stroke, RGBA(0, 0, 0, 255)) state.compositeOperation = __compositeOperationState(.SOURCE_OVER) - state.shapeAntiAlias = true - state.strokeWidth = 1 - state.miterLimit = 10 - state.lineCap = .BUTT - state.lineJoin = .MITER - state.alpha = 1 + state.shapeAntiAlias = true + state.strokeWidth = 1 + state.miterLimit = 10 + state.lineCap = .BUTT + state.lineJoin = .MITER + state.alpha = 1 TransformIdentity(&state.xform) state.scissor.extent[0] = -1 state.scissor.extent[1] = -1 // font settings - state.fontSize = 16 - state.letterSpacing = 0 - state.lineHeight = 1 - state.fontBlur = 0 + state.fontSize = 16 + state.letterSpacing = 0 + state.lineHeight = 1 + state.fontBlur = 0 state.alignHorizontal = .LEFT - state.alignVertical = .BASELINE - state.fontId = 0 + state.alignVertical = .BASELINE + state.fontId = 0 } /////////////////////////////////////////////////////////// @@ -817,7 +814,7 @@ FillPaint :: proc(ctx: ^Context, paint: Paint) { Transform :: proc(ctx: ^Context, a, b, c, d, e, f: f32) { state := __getState(ctx) - TransformPremultiply(&state.xform, { a, b, c, d, e, f }) + TransformPremultiply(&state.xform, {a, b, c, d, e, f}) } // Resets current transform to a identity matrix. @@ -874,10 +871,10 @@ Scale :: proc(ctx: ^Context, x, y: f32) { There should be space for 6 floats in the return buffer for the values a-f. */ CurrentTransform :: proc(ctx: ^Context, xform: ^Matrix) { - state := __getState(ctx) if xform == nil { return } + state := __getState(ctx) xform^ = state.xform } @@ -901,7 +898,7 @@ CreateImagePath :: proc(ctx: ^Context, filename: cstring, imageFlags: ImageFlags return 0 } - data := mem.slice_ptr(img, int(w) * int(h) * int(n)) + data := img[:int(w) * int(h) * int(n)] image := CreateImageRGBA(ctx, int(w), int(h), imageFlags, data) stbi.image_free(img) return image @@ -919,13 +916,13 @@ CreateImageMem :: proc(ctx: ^Context, data: []byte, imageFlags: ImageFlags) -> i return 0 } - pixel_data := mem.slice_ptr(img, int(w) * int(h) * int(n)) - image := CreateImageRGBA(ctx, int(w), int(h), imageFlags, pixel_data) + data := img[:int(w) * int(h) * int(n)] + image := CreateImageRGBA(ctx, int(w), int(h), imageFlags, data) stbi.image_free(img) return image } -CreateImage :: proc { CreateImagePath, CreateImageMem } +CreateImage :: proc{CreateImagePath, CreateImageMem} // Creates image from specified image data. // Returns handle to the image. @@ -980,8 +977,8 @@ DeleteImage :: proc(ctx: ^Context, image: int) { LinearGradient :: proc( sx, sy: f32, ex, ey: f32, - icol: Color, - ocol: Color, + icol: Color, + ocol: Color, ) -> (p: Paint) { LARGE :: f32(1e5) @@ -1024,10 +1021,10 @@ LinearGradient :: proc( */ RadialGradient :: proc( cx, cy: f32, - inr: f32, - outr: f32, - icol: Color, - ocol: Color, + inr: f32, + outr: f32, + icol: Color, + ocol: Color, ) -> (p: Paint) { r := (inr+outr)*0.5 f := (outr-inr) @@ -1056,8 +1053,8 @@ RadialGradient :: proc( BoxGradient :: proc( x, y: f32, w, h: f32, - r: f32, - f: f32, + r: f32, + f: f32, icol: Color, ocol: Color, ) -> (p: Paint) { @@ -1084,10 +1081,10 @@ BoxGradient :: proc( */ ImagePattern :: proc( cx, cy: f32, - w, h: f32, - angle: f32, - image: int, - alpha: f32, + w, h: f32, + angle: f32, + image: int, + alpha: f32, ) -> (p: Paint) { TransformRotate(&p.xform, angle) p.xform[4] = cx @@ -1097,7 +1094,7 @@ ImagePattern :: proc( p.extent[1] = h p.image = image - p.innerColor = { 1,1,1,alpha } + p.innerColor = {1, 1, 1, alpha} p.outerColor = p.innerColor return @@ -1113,8 +1110,8 @@ ImagePattern :: proc( // Sets the current scissor rectangle. // The scissor rectangle is transformed by the current transform. Scissor :: proc( - ctx: ^Context, - x, y: f32, + ctx: ^Context, + x, y: f32, width, height: f32, ) { state := __getState(ctx) @@ -1139,11 +1136,11 @@ Scissor :: proc( transform space. The resulting shape is always rectangle. */ IntersectScissor :: proc( - ctx: ^Context, + ctx: ^Context, x, y, w, h: f32, ) { isect_rects :: proc( - dst: ^[4]f32, + dst: ^[4]f32, ax, ay, aw, ah: f32, bx, by, bw, bh: f32, ) { @@ -1158,8 +1155,6 @@ IntersectScissor :: proc( } state := __getState(ctx) - pxform: Matrix - invxorm: Matrix // If no previous scissor has been set, set the scissor as current scissor. if state.scissor.extent[0] < 0 { @@ -1167,9 +1162,11 @@ IntersectScissor :: proc( return } - pxform = state.scissor.xform + pxform := state.scissor.xform ex := state.scissor.extent[0] ey := state.scissor.extent[1] + + invxorm: Matrix TransformInverse(&invxorm, state.xform) TransformMultiply(&pxform, invxorm) tex := ex * abs(pxform[0]) + ey * abs(pxform[2]) @@ -1197,27 +1194,26 @@ ResetScissor :: proc(ctx: ^Context) { /////////////////////////////////////////////////////////// // state table instead of if else chains -OP_STATE_TABLE :: [CompositeOperation][2]BlendFactor { - .SOURCE_OVER = { .ONE, .ONE_MINUS_SRC_ALPHA }, - .SOURCE_IN = { .DST_ALPHA, .ZERO }, - .SOURCE_OUT = { .ONE_MINUS_DST_ALPHA, .ZERO }, - .ATOP = { .DST_ALPHA, .ONE_MINUS_SRC_ALPHA }, +OP_STATE_TABLE := [CompositeOperation][2]BlendFactor { + .SOURCE_OVER = {.ONE, .ONE_MINUS_SRC_ALPHA}, + .SOURCE_IN = {.DST_ALPHA, .ZERO}, + .SOURCE_OUT = {.ONE_MINUS_DST_ALPHA, .ZERO}, + .ATOP = {.DST_ALPHA, .ONE_MINUS_SRC_ALPHA}, - .DESTINATION_OVER = { .ONE_MINUS_DST_ALPHA, .ONE }, - .DESTINATION_IN = { .ZERO, .SRC_ALPHA }, - .DESTINATION_OUT = { .ZERO, .ONE_MINUS_SRC_ALPHA }, - .DESTINATION_ATOP = { .ONE_MINUS_DST_ALPHA, .SRC_ALPHA }, + .DESTINATION_OVER = {.ONE_MINUS_DST_ALPHA, .ONE}, + .DESTINATION_IN = {.ZERO, .SRC_ALPHA}, + .DESTINATION_OUT = {.ZERO, .ONE_MINUS_SRC_ALPHA}, + .DESTINATION_ATOP = {.ONE_MINUS_DST_ALPHA, .SRC_ALPHA}, - .LIGHTER = { .ONE, .ONE }, - .COPY = { .ONE, .ZERO }, - .XOR = { .ONE_MINUS_DST_ALPHA, .ONE_MINUS_SRC_ALPHA }, + .LIGHTER = {.ONE, .ONE}, + .COPY = {.ONE, .ZERO}, + .XOR = {.ONE_MINUS_DST_ALPHA, .ONE_MINUS_SRC_ALPHA}, } __compositeOperationState :: proc(op: CompositeOperation) -> (res: CompositeOperationState) { - table := OP_STATE_TABLE - factors := table[op] - res.srcRGB = factors.x - res.dstRGB = factors.y + factors := OP_STATE_TABLE[op] + res.srcRGB = factors.x + res.dstRGB = factors.y res.srcAlpha = factors.x res.dstAlpha = factors.y return @@ -1236,20 +1232,19 @@ GlobalCompositeBlendFunc :: proc(ctx: ^Context, sfactor, dfactor: BlendFactor) { // Sets the composite operation with custom pixel arithmetic for RGB and alpha components separately. The parameters should be one of NVGblendFactor. GlobalCompositeBlendFuncSeparate :: proc( - ctx: ^Context, - srcRGB: BlendFactor, - dstRGB: BlendFactor, + ctx: ^Context, + srcRGB: BlendFactor, + dstRGB: BlendFactor, srcAlpha: BlendFactor, dstAlpha: BlendFactor, ) { - op := CompositeOperationState { + state := __getState(ctx) + state.compositeOperation = CompositeOperationState{ srcRGB, dstRGB, srcAlpha, dstAlpha, } - state := __getState(ctx) - state.compositeOperation = op } /////////////////////////////////////////////////////////// @@ -1277,46 +1272,38 @@ __distPtSeg :: proc(x, y, px, py, qx, qy: f32) -> f32 { if d > 0 { t /= d } - - if t < 0 { - t = 0 - } else if t > 1 { - t = 1 - } + t = clamp(t, 0, 1) dx = px + t * pqx - x dy = py + t * pqy - y return dx * dx + dy * dy } -__appendCommands :: proc(ctx: ^Context, values: []f32) { +__appendCommands :: proc(ctx: ^Context, values: ..f32) { state := __getState(ctx) if Commands(values[0]) != .CLOSE && Commands(values[0]) != .WINDING { - ctx.commandx = values[len(values) - 2] - ctx.commandy = values[len(values) - 1] + ctx.commandx = values[len(values)-2] + ctx.commandy = values[len(values)-1] } - - i := 0 - for i < len(values) { + for i := 0; i < len(values); /**/ { cmd := Commands(values[i]) switch cmd { case .MOVE_TO, .LINE_TO: - TransformPoint(&values[i + 1], &values[i + 2], state.xform, values[i + 1], values[i + 2]) + TransformPoint(&values[i+1], &values[i+2], state.xform, values[i+1], values[i+2]) i += 3 - case .BEZIER_TO: - TransformPoint(&values[i + 1], &values[i + 2], state.xform, values[i + 1], values[i + 2]) - TransformPoint(&values[i + 3], &values[i + 4], state.xform, values[i + 3], values[i + 4]) - TransformPoint(&values[i + 5], &values[i + 6], state.xform, values[i + 5], values[i + 6]) + TransformPoint(&values[i+1], &values[i+2], state.xform, values[i+1], values[i+2]) + TransformPoint(&values[i+3], &values[i+4], state.xform, values[i+3], values[i+4]) + TransformPoint(&values[i+5], &values[i+6], state.xform, values[i+5], values[i+6]) i += 7 - - case .CLOSE: i += 1 - case .WINDING: i += 2 - - // default - case: i += 1 + case .CLOSE: + i += 1 + case .WINDING: + i += 2 + case: + i += 1 } } @@ -1331,22 +1318,22 @@ __clearPathCache :: proc(ctx: ^Context) { __lastPath :: proc(ctx: ^Context) -> ^Path { if len(ctx.cache.paths) > 0 { - return &ctx.cache.paths[len(ctx.cache.paths) - 1] + return &ctx.cache.paths[len(ctx.cache.paths)-1] } return nil } __addPath :: proc(ctx: ^Context) { - append(&ctx.cache.paths, Path { - first = len(ctx.cache.points), + append(&ctx.cache.paths, Path{ + first = len(ctx.cache.points), winding = .CCW, }) } __lastPoint :: proc(ctx: ^Context) -> ^Point { if len(ctx.cache.paths) > 0 { - return &ctx.cache.points[len(ctx.cache.points) - 1] + return &ctx.cache.points[len(ctx.cache.points)-1] } return nil @@ -1368,7 +1355,7 @@ __addPoint :: proc(ctx: ^Context, x, y: f32, flags: PointFlags) { } } - append(&ctx.cache.points, Point { + append(&ctx.cache.points, Point{ x = x, y = y, flags = flags, @@ -1415,7 +1402,7 @@ __polyArea :: proc(points: []Point) -> f32 { for i := 2; i < len(points); i += 1 { a := &points[0] - b := &points[i - 1] + b := &points[i-1] c := &points[i] area += __triarea2(a.x, a.y, b.x, b.y, c.x, c.y) } @@ -1448,24 +1435,24 @@ __normalize :: proc(x, y: ^f32) -> f32 { } __tesselateBezier :: proc( - ctx: ^Context, + ctx: ^Context, x1, y1: f32, x2, y2: f32, x3, y3: f32, x4, y4: f32, - level: int, - flags: PointFlags, + level: int, + flags: PointFlags, ) { if level > 10 { return } - x12 := (x1 + x2) * 0.5 - y12 := (y1 + y2) * 0.5 - x23 := (x2 + x3) * 0.5 - y23 := (y2 + y3) * 0.5 - x34 := (x3 + x4) * 0.5 - y34 := (y3 + y4) * 0.5 + x12 := (x1 + x2) * 0.5 + y12 := (y1 + y2) * 0.5 + x23 := (x2 + x3) * 0.5 + y23 := (y2 + y3) * 0.5 + x34 := (x3 + x4) * 0.5 + y34 := (y3 + y4) * 0.5 x123 := (x12 + x23) * 0.5 y123 := (y12 + y23) * 0.5 @@ -1479,8 +1466,8 @@ __tesselateBezier :: proc( return } - x234 := (x23 + x34) * 0.5 - y234 := (y23 + y34) * 0.5 + x234 := (x23 + x34) * 0.5 + y234 := (y23 + y34) * 0.5 x1234 := (x123 + x234) * 0.5 y1234 := (y123 + y234) * 0.5 @@ -1504,22 +1491,20 @@ __flattenPaths :: proc(ctx: ^Context) { case .MOVE_TO: __addPath(ctx) p := ctx.commands[i + 1:] - __addPoint(ctx, p[0], p[1], { .CORNER }) + __addPoint(ctx, p[0], p[1], {.CORNER}) i += 3 case .LINE_TO: p := ctx.commands[i + 1:] - __addPoint(ctx, p[0], p[1], { .CORNER }) + __addPoint(ctx, p[0], p[1], {.CORNER}) i += 3 case .BEZIER_TO: - last := __lastPoint(ctx) - - if last != nil { + if last := __lastPoint(ctx); last != nil { cp1 := ctx.commands[i + 1:] cp2 := ctx.commands[i + 3:] p := ctx.commands[i + 5:] - __tesselateBezier(ctx, last.x,last.y, cp1[0],cp1[1], cp2[0],cp2[1], p[0],p[1], 0, { .CORNER }) + __tesselateBezier(ctx, last.x,last.y, cp1[0],cp1[1], cp2[0],cp2[1], p[0],p[1], 0, {.CORNER}) } i += 7 @@ -1542,8 +1527,7 @@ __flattenPaths :: proc(ctx: ^Context) { cache.bounds[3] = -1e6 // Calculate the direction and length of line segments. - for j in 0.. 0.000001) { + if dmr2 > 0.000001 { scale := 1.0 / dmr2 - if (scale > 600.0) { + if scale > 600.0 { scale = 600.0 } p1.dmx *= scale @@ -1901,25 +1875,25 @@ __calculateJoins :: proc( } // Clear flags, but keep the corner. - p1.flags = (.CORNER in p1.flags) ? { .CORNER } : {} + p1.flags = {.CORNER} if .CORNER in p1.flags else nil // Keep track of left turns. __cross = p1.dx * p0.dy - p0.dx * p1.dy if __cross > 0.0 { nleft += 1 - incl(&p1.flags, PointFlag.LEFT) + p1.flags += {.LEFT} } // Calculate if we should use bevel or miter for inner join. limit = max(1.01, min(p0.len, p1.len) * iw) if (dmr2 * limit * limit) < 1.0 { - incl(&p1.flags, PointFlag.INNER_BEVEL) + p1.flags += {.INNER_BEVEL} } // Check to see if the corner needs to be beveled. if .CORNER in p1.flags { if (dmr2 * miterLimit*miterLimit) < 1.0 || lineJoin == .BEVEL || lineJoin == .ROUND { - incl(&p1.flags, PointFlag.BEVEL) + p1.flags += {.BEVEL} } } @@ -1942,11 +1916,11 @@ __allocTempVerts :: proc(ctx: ^Context, nverts: int) -> []Vertex { } __expandStroke :: proc( - ctx: ^Context, - w: f32, - fringe: f32, - lineCap: LineCapType, - lineJoin: LineCapType, + ctx: ^Context, + w: f32, + fringe: f32, + lineCap: LineCapType, + lineJoin: LineCapType, miterLimit: f32, ) -> bool { cache := &ctx.cache @@ -1968,7 +1942,7 @@ __expandStroke :: proc( // Calculate max vertex usage. cverts := 0 - for &path in cache.paths { + for path in cache.paths { loop := path.closed // TODO check if f32 calculation necessary? @@ -1991,8 +1965,7 @@ __expandStroke :: proc( verts := __allocTempVerts(ctx, cverts) dst_index: int - for i in 0.. bool { cache := &ctx.cache @@ -2097,7 +2070,7 @@ __expandFill :: proc( // Calculate max vertex usage. cverts := 0 - for &path in cache.paths { + for path in cache.paths { cverts += path.count + path.nbevel + 1 if fringe { @@ -2125,7 +2098,7 @@ __expandFill :: proc( p0 = &pts[path.count-1] p1 = &pts[0] - for j in 0.. f32 { font := fontstash.__getFont(fs, state.fontId) isize := i16(fstate.size * 10) iblur := i16(fstate.blur) - glyph := fontstash.__getGlyph(fs, font, codepoint, isize, iblur) + glyph, _ := fontstash.__getGlyph(fs, font, codepoint, isize, iblur) fscale := fontstash.__getPixelHeightScale(font, f32(isize) / 10) // transform x / y @@ -2910,12 +2872,12 @@ TextIcon :: proc(ctx: ^Context, xpos, ypos: f32, codepoint: rune) -> f32 { TransformPoint(&c[6], &c[7], state.xform, q.x0 * invscale, q.y1 * invscale) // Create triangles - verts[0] = { c[0], c[1], q.s0, q.t0 } - verts[1] = { c[4], c[5], q.s1, q.t1 } - verts[2] = { c[2], c[3], q.s1, q.t0 } - verts[3] = { c[0], c[1], q.s0, q.t0 } - verts[4] = { c[6], c[7], q.s0, q.t1 } - verts[5] = { c[4], c[5], q.s1, q.t1 } + verts[0] = {c[0], c[1], q.s0, q.t0} + verts[1] = {c[4], c[5], q.s1, q.t1} + verts[2] = {c[2], c[3], q.s1, q.t0} + verts[3] = {c[0], c[1], q.s0, q.t0} + verts[4] = {c[6], c[7], q.s0, q.t1} + verts[5] = {c[4], c[5], q.s1, q.t1} ctx.textureDirty = true __renderText(ctx, verts[:]) @@ -2986,12 +2948,12 @@ Text :: proc(ctx: ^Context, x, y: f32, text: string) -> f32 { // Create triangles if nverts + 6 <= cverts { - verts[nverts] = { c[0], c[1], q.s0, q.t0 } - verts[nverts + 1] = { c[4], c[5], q.s1, q.t1 } - verts[nverts + 2] = { c[2], c[3], q.s1, q.t0 } - verts[nverts + 3] = { c[0], c[1], q.s0, q.t0 } - verts[nverts + 4] = { c[6], c[7], q.s0, q.t1 } - verts[nverts + 5] = { c[4], c[5], q.s1, q.t1 } + verts[nverts+0] = {c[0], c[1], q.s0, q.t0} + verts[nverts+1] = {c[4], c[5], q.s1, q.t1} + verts[nverts+2] = {c[2], c[3], q.s1, q.t0} + verts[nverts+3] = {c[0], c[1], q.s0, q.t0} + verts[nverts+4] = {c[6], c[7], q.s0, q.t1} + verts[nverts+5] = {c[4], c[5], q.s1, q.t1} nverts += 6 } } @@ -3033,17 +2995,17 @@ TextMetrics :: proc(ctx: ^Context) -> (ascender, descender, lineHeight: f32) { // Returns the horizontal advance of the measured text (i.e. where the next character should drawn). // Measured values are returned in local coordinate space. TextBounds :: proc( - ctx: ^Context, - x, y: f32, - input: string, + ctx: ^Context, + x, y: f32, + input: string, bounds: ^[4]f32 = nil, ) -> (advance: f32) { - state := __getState(ctx) - scale := __getFontScale(state) * ctx.devicePxRatio + state := __getState(ctx) + scale := __getFontScale(state) * ctx.devicePxRatio invscale := f32(1.0) / scale if state.fontId == -1 { - return {} + return 0 } fs := &ctx.fs @@ -3055,11 +3017,11 @@ TextBounds :: proc( fontstash.SetFont(fs, state.fontId) width := fontstash.TextBounds(fs, input, x * scale, y * scale, bounds) - - // Use line bounds for height. - one, two := fontstash.LineBounds(fs, y * scale) if bounds != nil { + // Use line bounds for height. + one, two := fontstash.LineBounds(fs, y * scale) + bounds[1] = one bounds[3] = two bounds[0] *= invscale @@ -3073,10 +3035,10 @@ TextBounds :: proc( // text row with relative byte offsets into a string Text_Row :: struct { - start: int, - end: int, - next: int, - width: f32, + start: int, + end: int, + next: int, + width: f32, minx, maxx: f32, } @@ -3091,10 +3053,10 @@ Codepoint_Type :: enum { // White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered. // Words longer than the max width are slit at nearest character (i.e. no hyphenation). TextBox :: proc( - ctx: ^Context, - x, y: f32, + ctx: ^Context, + x, y: f32, break_row_width: f32, - input: string, + input: string, ) { state := __getState(ctx) rows: [2]Text_Row @@ -3112,8 +3074,7 @@ TextBox :: proc( y := y input := input for nrows, input_last in TextBreakLines(ctx, &input, break_row_width, &rows_mod) { - for i in 0.. (nrows: int, last: string, ok: bool) { state := __getState(ctx) scale := __getFontScale(state) * ctx.devicePxRatio @@ -3144,7 +3105,7 @@ TextBreakLines :: proc( word_start_x, word_min_x: f32 break_width, break_max_x: f32 - type := Codepoint_Type.Space + type := Codepoint_Type.Space ptype := Codepoint_Type.Space pcodepoint: rune @@ -3160,9 +3121,10 @@ TextBreakLines :: proc( fontstash.SetAlignVertical(fs, state.alignVertical) fontstash.SetFont(fs, state.fontId) - break_x := break_row_width * scale - iter := fontstash.TextIterInit(fs, 0, 0, text^) + break_x := break_row_width * scale + iter := fontstash.TextIterInit(fs, 0, 0, text^) prev_iter := iter + q: fontstash.Quad stopped_early: bool @@ -3179,36 +3141,37 @@ TextBreakLines :: proc( type = .Space case '\n': - type = pcodepoint == 13 ? .Space : .Newline + type = .Space if pcodepoint == 13 else .Newline case '\r': - type = pcodepoint == 10 ? .Space : .Newline + type = .Space if pcodepoint == 10 else .Newline case 0x0085: // NEL type = .Newline case: - if (iter.codepoint >= 0x4E00 && iter.codepoint <= 0x9FFF) || - (iter.codepoint >= 0x3000 && iter.codepoint <= 0x30FF) || - (iter.codepoint >= 0xFF00 && iter.codepoint <= 0xFFEF) || - (iter.codepoint >= 0x1100 && iter.codepoint <= 0x11FF) || - (iter.codepoint >= 0x3130 && iter.codepoint <= 0x318F) || - (iter.codepoint >= 0xAC00 && iter.codepoint <= 0xD7AF) { + switch iter.codepoint { + case 0x4E00..=0x9FFF, + 0x3000..=0x30FF, + 0xFF00..=0xFFEF, + 0x1100..=0x11FF, + 0x3130..=0x318F, + 0xAC00..=0xD7AF: type = .CJK - } else { + case: type = .Char } } if type == .Newline { // Always handle new lines. - rows[nrows].start = row_start != -1 ? row_start : iter.str - rows[nrows].end = row_end != -1 ? row_end : iter.str + rows[nrows].start = row_start if row_start != -1 else iter.str + rows[nrows].end = row_end if row_end != -1 else iter.str rows[nrows].width = row_width * invscale - rows[nrows].minx = row_min_x * invscale - rows[nrows].maxx = row_max_x * invscale - rows[nrows].next = iter.next + rows[nrows].minx = row_min_x * invscale + rows[nrows].maxx = row_max_x * invscale + rows[nrows].next = iter.next nrows += 1 if nrows >= max_rows { @@ -3270,7 +3233,7 @@ TextBreakLines :: proc( // Break to new line when a character is beyond break width. if (type == .Char || type == .CJK) && next_width > break_x { // The run length is too long, need to break to new line. - if (break_end == row_start) { + if break_end == row_start { // The current word is longer than the row length, just break it from here. rows[nrows].start = row_start rows[nrows].end = iter.str @@ -3341,7 +3304,7 @@ TextBreakLines :: proc( // NOTE a bit hacky, row.start / row.end need to work with last string range last = text^ // advance early - next := rows[nrows - 1].next + next := rows[nrows-1].next text^ = text[next:] // terminate the for loop on non ok ok = nrows != 0 @@ -3353,11 +3316,11 @@ TextBreakLines :: proc( // if the bounding box of the text should be returned. The bounds value are [xmin,ymin, xmax,ymax] // Measured values are returned in local coordinate space. TextBoxBounds :: proc( - ctx: ^Context, - x, y: f32, + ctx: ^Context, + x, y: f32, breakRowWidth: f32, - input: string, - bounds: ^[4]f32, + input: string, + bounds: ^[4]f32, ) { state := __getState(ctx) rows: [2]Text_Row @@ -3398,15 +3361,14 @@ TextBoxBounds :: proc( y := y for nrows, input_last in TextBreakLines(ctx, &input, breakRowWidth, &rows_mod) { - for i in 0.. int { state := __getState(ctx) From 20e954a864ecd2e9da8d5df23739f1b83c59eb9a Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 28 Jun 2023 12:16:49 +0100 Subject: [PATCH 81/97] Add `math.hypot` --- core/math/math.odin | 68 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/core/math/math.odin b/core/math/math.odin index 05177378f..70ffc1a35 100644 --- a/core/math/math.odin +++ b/core/math/math.odin @@ -2158,6 +2158,74 @@ signbit :: proc{ } +@(require_results) +hypot_f16 :: proc "contextless" (x, y: f16) -> (r: f16) { + switch { + case is_nan(x) || is_nan(y): + r = nan_f16() + case x == y: + r = x + case x == 0: + r = copy_sign_f16(1, y) + case (y > x) == (x > 0): + r = transmute(f16)(transmute(u16)x + 1) + case: + r = transmute(f16)(transmute(u16)x - 1) + } + return +} +@(require_results) +hypot_f32 :: proc "contextless" (x, y: f32) -> (r: f32) { + switch { + case is_nan(x) || is_nan(y): + r = nan_f32() + case x == y: + r = x + case x == 0: + r = copy_sign_f32(1, y) + case (y > x) == (x > 0): + r = transmute(f32)(transmute(u32)x + 1) + case: + r = transmute(f32)(transmute(u32)x - 1) + } + return +} +@(require_results) +hypot_f64 :: proc "contextless" (x, y: f64) -> (r: f64) { + switch { + case is_nan(x) || is_nan(y): + r = nan_f64() + case x == y: + r = x + case x == 0: + r = copy_sign_f64(1, y) + case (y > x) == (x > 0): + r = transmute(f64)(transmute(u64)x + 1) + case: + r = transmute(f64)(transmute(u64)x - 1) + } + return +} +@(require_results) hypot_f16le :: proc "contextless" (x, y: f16le) -> (r: f16le) { return f16le(hypot_f16(f16(x), f16(y))) } +@(require_results) hypot_f16be :: proc "contextless" (x, y: f16be) -> (r: f16be) { return f16be(hypot_f16(f16(x), f16(y))) } +@(require_results) hypot_f32le :: proc "contextless" (x, y: f32le) -> (r: f32le) { return f32le(hypot_f32(f32(x), f32(y))) } +@(require_results) hypot_f32be :: proc "contextless" (x, y: f32be) -> (r: f32be) { return f32be(hypot_f32(f32(x), f32(y))) } +@(require_results) hypot_f64le :: proc "contextless" (x, y: f64le) -> (r: f64le) { return f64le(hypot_f64(f64(x), f64(y))) } +@(require_results) hypot_f64be :: proc "contextless" (x, y: f64be) -> (r: f64be) { return f64be(hypot_f64(f64(x), f64(y))) } + +// hypot returns Sqrt(p*p + q*q), taking care to avoid unnecessary overflow and underflow. +// +// Special cases: +// hypot(±Inf, q) = +Inf +// hypot(p, ±Inf) = +Inf +// hypot(NaN, q) = NaN +// hypot(p, NaN) = NaN +hypot :: proc{ + hypot_f16, hypot_f16le, hypot_f16be, + hypot_f32, hypot_f32le, hypot_f32be, + hypot_f64, hypot_f64le, hypot_f64be, +} + F16_DIG :: 3 F16_EPSILON :: 0.00097656 F16_GUARD :: 0 From 866a9fdf192a5dcd54ae77ba797273e14899164a Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 28 Jun 2023 12:19:45 +0100 Subject: [PATCH 82/97] Actually add math.hypot --- core/math/math.odin | 72 ++++++++++++++++++++++++--------------------- 1 file changed, 39 insertions(+), 33 deletions(-) diff --git a/core/math/math.odin b/core/math/math.odin index 70ffc1a35..6f7a36bab 100644 --- a/core/math/math.odin +++ b/core/math/math.odin @@ -2160,51 +2160,57 @@ signbit :: proc{ @(require_results) hypot_f16 :: proc "contextless" (x, y: f16) -> (r: f16) { + p, q := abs(x), abs(y) switch { - case is_nan(x) || is_nan(y): - r = nan_f16() - case x == y: - r = x - case x == 0: - r = copy_sign_f16(1, y) - case (y > x) == (x > 0): - r = transmute(f16)(transmute(u16)x + 1) - case: - r = transmute(f16)(transmute(u16)x - 1) + case is_inf(p, 1) || is_inf(q, 1): + return inf_f16(1) + case is_nan(p) || is_nan(q): + return nan_f16() } - return + if p < q { + p, q = q, p + } + if p == 0 { + return 0 + } + q = q / p + return p * sqrt(1+q*q) } @(require_results) hypot_f32 :: proc "contextless" (x, y: f32) -> (r: f32) { + p, q := abs(x), abs(y) switch { - case is_nan(x) || is_nan(y): - r = nan_f32() - case x == y: - r = x - case x == 0: - r = copy_sign_f32(1, y) - case (y > x) == (x > 0): - r = transmute(f32)(transmute(u32)x + 1) - case: - r = transmute(f32)(transmute(u32)x - 1) + case is_inf(p, 1) || is_inf(q, 1): + return inf_f32(1) + case is_nan(p) || is_nan(q): + return nan_f32() } - return + if p < q { + p, q = q, p + } + if p == 0 { + return 0 + } + q = q / p + return p * sqrt(1+q*q) } @(require_results) hypot_f64 :: proc "contextless" (x, y: f64) -> (r: f64) { + p, q := abs(x), abs(y) switch { - case is_nan(x) || is_nan(y): - r = nan_f64() - case x == y: - r = x - case x == 0: - r = copy_sign_f64(1, y) - case (y > x) == (x > 0): - r = transmute(f64)(transmute(u64)x + 1) - case: - r = transmute(f64)(transmute(u64)x - 1) + case is_inf(p, 1) || is_inf(q, 1): + return inf_f64(1) + case is_nan(p) || is_nan(q): + return nan_f64() } - return + if p < q { + p, q = q, p + } + if p == 0 { + return 0 + } + q = q / p + return p * sqrt(1+q*q) } @(require_results) hypot_f16le :: proc "contextless" (x, y: f16le) -> (r: f16le) { return f16le(hypot_f16(f16(x), f16(y))) } @(require_results) hypot_f16be :: proc "contextless" (x, y: f16be) -> (r: f16be) { return f16be(hypot_f16(f16(x), f16(y))) } From abca3ceac8cdf4583e640b5a980b35e4bace07de Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 28 Jun 2023 12:19:57 +0100 Subject: [PATCH 83/97] Keep `-vet` happy --- examples/all/all_experimental.odin | 2 -- vendor/fontstash/fontstash.odin | 2 +- vendor/nanovg/nanovg.odin | 4 ++-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/examples/all/all_experimental.odin b/examples/all/all_experimental.odin index 9679e0ca4..cd60c269c 100644 --- a/examples/all/all_experimental.odin +++ b/examples/all/all_experimental.odin @@ -3,8 +3,6 @@ package all import c_tokenizer "core:c/frontend/tokenizer" import c_preprocessor "core:c/frontend/preprocessor" -import virtual "core:mem/virtual" _ :: c_tokenizer _ :: c_preprocessor -_ :: virtual diff --git a/vendor/fontstash/fontstash.odin b/vendor/fontstash/fontstash.odin index 93a48bc0d..edf9e12db 100644 --- a/vendor/fontstash/fontstash.odin +++ b/vendor/fontstash/fontstash.odin @@ -1067,7 +1067,7 @@ TextIterNext :: proc( iter.x = iter.nextx iter.y = iter.nexty iter.codepointCount += 1 - if glyph, ok := __getGlyph(ctx, iter.font, iter.codepoint, iter.isize, iter.iblur); ok { + if glyph, glyph_ok := __getGlyph(ctx, iter.font, iter.codepoint, iter.isize, iter.iblur); glyph_ok { __getQuad(ctx, iter.font, iter.previousGlyphIndex, glyph, iter.scale, iter.spacing, &iter.nextx, &iter.nexty, quad) iter.previousGlyphIndex = glyph.index } else { diff --git a/vendor/nanovg/nanovg.odin b/vendor/nanovg/nanovg.odin index 6b06e3534..8d30a407d 100644 --- a/vendor/nanovg/nanovg.odin +++ b/vendor/nanovg/nanovg.odin @@ -916,8 +916,8 @@ CreateImageMem :: proc(ctx: ^Context, data: []byte, imageFlags: ImageFlags) -> i return 0 } - data := img[:int(w) * int(h) * int(n)] - image := CreateImageRGBA(ctx, int(w), int(h), imageFlags, data) + pixel_data := img[:int(w) * int(h) * int(n)] + image := CreateImageRGBA(ctx, int(w), int(h), imageFlags, pixel_data) stbi.image_free(img) return image } From 0180a4fcd429ca6345d28ef4608d4445ecd00f55 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 28 Jun 2023 12:57:09 +0100 Subject: [PATCH 84/97] Add `math.sincos` --- core/math/math_sincos.odin | 308 +++++++++++++++++++++++++++++++++++++ 1 file changed, 308 insertions(+) create mode 100644 core/math/math_sincos.odin diff --git a/core/math/math_sincos.odin b/core/math/math_sincos.odin new file mode 100644 index 000000000..578876ac5 --- /dev/null +++ b/core/math/math_sincos.odin @@ -0,0 +1,308 @@ +package math + +import "core:math/bits" + +// The original C code, the long comment, and the constants +// below were from http://netlib.sandia.gov/cephes/cmath/sin.c, +// available from http://www.netlib.org/cephes/cmath.tgz. +// The go code is a simplified version of the original C. +// +// sin.c +// +// Circular sine +// +// SYNOPSIS: +// +// double x, y, sin(); +// y = sin( x ); +// +// DESCRIPTION: +// +// Range reduction is into intervals of pi/4. The reduction error is nearly +// eliminated by contriving an extended precision modular arithmetic. +// +// Two polynomial approximating functions are employed. +// Between 0 and pi/4 the sine is approximated by +// x + x**3 P(x**2). +// Between pi/4 and pi/2 the cosine is represented as +// 1 - x**2 Q(x**2). +// +// ACCURACY: +// +// Relative error: +// arithmetic domain # trials peak rms +// DEC 0, 10 150000 3.0e-17 7.8e-18 +// IEEE -1.07e9,+1.07e9 130000 2.1e-16 5.4e-17 +// +// Partial loss of accuracy begins to occur at x = 2**30 = 1.074e9. The loss +// is not gradual, but jumps suddenly to about 1 part in 10e7. Results may +// be meaningless for x > 2**49 = 5.6e14. +// +// cos.c +// +// Circular cosine +// +// SYNOPSIS: +// +// double x, y, cos(); +// y = cos( x ); +// +// DESCRIPTION: +// +// Range reduction is into intervals of pi/4. The reduction error is nearly +// eliminated by contriving an extended precision modular arithmetic. +// +// Two polynomial approximating functions are employed. +// Between 0 and pi/4 the cosine is approximated by +// 1 - x**2 Q(x**2). +// Between pi/4 and pi/2 the sine is represented as +// x + x**3 P(x**2). +// +// ACCURACY: +// +// Relative error: +// arithmetic domain # trials peak rms +// IEEE -1.07e9,+1.07e9 130000 2.1e-16 5.4e-17 +// DEC 0,+1.07e9 17000 3.0e-17 7.2e-18 +// +// Cephes Math Library Release 2.8: June, 2000 +// Copyright 1984, 1987, 1989, 1992, 2000 by Stephen L. Moshier +// +// The readme file at http://netlib.sandia.gov/cephes/ says: +// Some software in this archive may be from the book _Methods and +// Programs for Mathematical Functions_ (Prentice-Hall or Simon & Schuster +// International, 1989) or from the Cephes Mathematical Library, a +// commercial product. In either event, it is copyrighted by the author. +// What you see here may be used freely but it comes with no support or +// guarantee. +// +// The two known misprints in the book are repaired here in the +// source listings for the gamma function and the incomplete beta +// integral. +// +// Stephen L. Moshier +// moshier@na-net.ornl.gov + +sincos :: proc{ + sincos_f16, sincos_f16le, sincos_f16be, + sincos_f32, sincos_f32le, sincos_f32be, + sincos_f64, sincos_f64le, sincos_f64be, +} + +sincos_f16 :: proc "contextless" (x: f16) -> (sin, cos: f16) #no_bounds_check { + s, c := sincos_f64(f64(x)) + return f16(s), f16(c) +} +sincos_f16le :: proc "contextless" (x: f16le) -> (sin, cos: f16le) #no_bounds_check { + s, c := sincos_f64(f64(x)) + return f16le(s), f16le(c) +} +sincos_f16be :: proc "contextless" (x: f16be) -> (sin, cos: f16be) #no_bounds_check { + s, c := sincos_f64(f64(x)) + return f16be(s), f16be(c) +} + +sincos_f32 :: proc "contextless" (x: f32) -> (sin, cos: f32) #no_bounds_check { + s, c := sincos_f64(f64(x)) + return f32(s), f32(c) +} +sincos_f32le :: proc "contextless" (x: f32le) -> (sin, cos: f32le) #no_bounds_check { + s, c := sincos_f64(f64(x)) + return f32le(s), f32le(c) +} +sincos_f32be :: proc "contextless" (x: f32be) -> (sin, cos: f32be) #no_bounds_check { + s, c := sincos_f64(f64(x)) + return f32be(s), f32be(c) +} + +sincos_f64le :: proc "contextless" (x: f64le) -> (sin, cos: f64le) #no_bounds_check { + s, c := sincos_f64(f64(x)) + return f64le(s), f64le(c) +} +sincos_f64be :: proc "contextless" (x: f64be) -> (sin, cos: f64be) #no_bounds_check { + s, c := sincos_f64(f64(x)) + return f64be(s), f64be(c) +} + +sincos_f64 :: proc "contextless" (x: f64) -> (sin, cos: f64) #no_bounds_check { + x := x + + PI4A :: 0h3fe921fb40000000 // 7.85398125648498535156e-1 PI/4 split into three parts + PI4B :: 0h3e64442d00000000 // 3.77489470793079817668e-8 + PI4C :: 0h3ce8469898cc5170 // 2.69515142907905952645e-15 + + // special cases + switch { + case x == 0: + return x, 1 // return ±0.0, 1.0 + case is_nan(x) || is_inf(x, 0): + return nan_f64(), nan_f64() + } + + // make argument positive + sin_sign, cos_sign := false, false + if x < 0 { + x = -x + sin_sign = true + } + + j: u64 + y, z: f64 + if x >= REDUCE_THRESHOLD { + j, z = _trig_reduce_f64(x) + } else { + j = u64(x * (4 / PI)) // integer part of x/(PI/4), as integer for tests on the phase angle + y = f64(j) // integer part of x/(PI/4), as float + + if j&1 == 1 { // map zeros to origin + j += 1 + y += 1 + } + j &= 7 // octant modulo TAU radians (360 degrees) + z = ((x - y*PI4A) - y*PI4B) - y*PI4C // Extended precision modular arithmetic + } + if j > 3 { // reflect in x axis + j -= 4 + sin_sign, cos_sign = !sin_sign, !cos_sign + } + if j > 1 { + cos_sign = !cos_sign + } + + zz := z * z + + cos = 1.0 - 0.5*zz + zz*zz*((((((_cos[0]*zz)+_cos[1])*zz+_cos[2])*zz+_cos[3])*zz+_cos[4])*zz+_cos[5]) + sin = z + z*zz*((((((_sin[0]*zz)+_sin[1])*zz+_sin[2])*zz+_sin[3])*zz+_sin[4])*zz+_sin[5]) + + if j == 1 || j == 2 { + sin, cos = cos, sin + } + if cos_sign { + cos = -cos + } + if sin_sign { + sin = -sin + } + return +} + +// sin coefficients +@(private="file") +_sin := [?]f64{ + 0h3de5d8fd1fd19ccd, // 1.58962301576546568060e-10 + 0hbe5ae5e5a9291f5d, // -2.50507477628578072866e-8 + 0h3ec71de3567d48a1, // 2.75573136213857245213e-6 + 0hbf2a01a019bfdf03, // -1.98412698295895385996e-4 + 0h3f8111111110f7d0, // 8.33333333332211858878e-3 + 0hbfc5555555555548, // -1.66666666666666307295e-1 +} + +// cos coefficients +@(private="file") +_cos := [?]f64{ + 0hbda8fa49a0861a9b, // -1.13585365213876817300e-11, + 0h3e21ee9d7b4e3f05, // 2.08757008419747316778e-9, + 0hbe927e4f7eac4bc6, // -2.75573141792967388112e-7, + 0h3efa01a019c844f5, // 2.48015872888517045348e-5, + 0hbf56c16c16c14f91, // -1.38888888888730564116e-3, + 0h3fa555555555554b, // 4.16666666666665929218e-2, +} + +// REDUCE_THRESHOLD is the maximum value of x where the reduction using Pi/4 +// in 3 f64 parts still gives accurate results. This threshold +// is set by y*C being representable as a f64 without error +// where y is given by y = floor(x * (4 / Pi)) and C is the leading partial +// terms of 4/Pi. Since the leading terms (PI4A and PI4B in sin.go) have 30 +// and 32 trailing zero bits, y should have less than 30 significant bits. +// +// y < 1<<30 -> floor(x*4/Pi) < 1<<30 -> x < (1<<30 - 1) * Pi/4 +// +// So, conservatively we can take x < 1<<29. +// Above this threshold Payne-Hanek range reduction must be used. +@(private="file") +REDUCE_THRESHOLD :: 1 << 29 + +// _trig_reduce_f64 implements Payne-Hanek range reduction by Pi/4 +// for x > 0. It returns the integer part mod 8 (j) and +// the fractional part (z) of x / (Pi/4). +// The implementation is based on: +// "ARGUMENT REDUCTION FOR HUGE ARGUMENTS: Good to the Last Bit" +// K. C. Ng et al, March 24, 1992 +// The simulated multi-precision calculation of x*B uses 64-bit integer arithmetic. +_trig_reduce_f64 :: proc "contextless" (x: f64) -> (j: u64, z: f64) #no_bounds_check { + // bd_pi4 is the binary digits of 4/pi as a u64 array, + // that is, 4/pi = Sum bd_pi4[i]*2^(-64*i) + // 19 64-bit digits and the leading one bit give 1217 bits + // of precision to handle the largest possible f64 exponent. + @static bd_pi4 := [?]u64{ + 0x0000000000000001, + 0x45f306dc9c882a53, + 0xf84eafa3ea69bb81, + 0xb6c52b3278872083, + 0xfca2c757bd778ac3, + 0x6e48dc74849ba5c0, + 0x0c925dd413a32439, + 0xfc3bd63962534e7d, + 0xd1046bea5d768909, + 0xd338e04d68befc82, + 0x7323ac7306a673e9, + 0x3908bf177bf25076, + 0x3ff12fffbc0b301f, + 0xde5e2316b414da3e, + 0xda6cfd9e4f96136e, + 0x9e8c7ecd3cbfd45a, + 0xea4f758fd7cbe2f6, + 0x7a0e73ef14a525d4, + 0xd7f6bf623f1aba10, + 0xac06608df8f6d757, + } + + PI4 :: PI / 4 + if x < PI4 { + return 0, x + } + + MASK :: 0x7FF + SHIFT :: 64 - 11 - 1 + BIAS :: 1023 + + // Extract out the integer and exponent such that, + // x = ix * 2 ** exp. + ix := transmute(u64)x + exp := int(ix>>SHIFT&MASK) - BIAS - SHIFT + ix &~= MASK << SHIFT + ix |= 1 << SHIFT + // Use the exponent to extract the 3 appropriate u64 digits from bd_pi4, + // B ~ (z0, z1, z2), such that the product leading digit has the exponent -61. + // Note, exp >= -53 since x >= PI4 and exp < 971 for maximum f64. + digit, bitshift := uint(exp+61)/64, uint(exp+61)%64 + z0 := (bd_pi4[digit] << bitshift) | (bd_pi4[digit+1] >> (64 - bitshift)) + z1 := (bd_pi4[digit+1] << bitshift) | (bd_pi4[digit+2] >> (64 - bitshift)) + z2 := (bd_pi4[digit+2] << bitshift) | (bd_pi4[digit+3] >> (64 - bitshift)) + // Multiply mantissa by the digits and extract the upper two digits (hi, lo). + z2hi, _ := bits.mul(z2, ix) + z1hi, z1lo := bits.mul(z1, ix) + z0lo := z0 * ix + lo, c := bits.add(z1lo, z2hi, 0) + hi, _ := bits.add(z0lo, z1hi, c) + // The top 3 bits are j. + j = hi >> 61 + // Extract the fraction and find its magnitude. + hi = hi<<3 | lo>>61 + lz := uint(bits.leading_zeros(hi)) + e := u64(BIAS - (lz + 1)) + // Clear implicit mantissa bit and shift into place. + hi = (hi << (lz + 1)) | (lo >> (64 - (lz + 1))) + hi >>= 64 - SHIFT + // Include the exponent and convert to a float. + hi |= e << SHIFT + z = transmute(f64)hi + // Map zeros to origin. + if j&1 == 1 { + j += 1 + j &= 7 + z -= 1 + } + // Multiply the fractional part by pi/4. + return j, z * PI4 +} From a2b3c72647d4356175dbe5e6635fa0275933de4c Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 28 Jun 2023 13:18:36 +0100 Subject: [PATCH 85/97] Improve accuracy of `abs` or `complex*` types --- core/runtime/internal.odin | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/core/runtime/internal.odin b/core/runtime/internal.odin index 71ad9386a..ad8a40acf 100644 --- a/core/runtime/internal.odin +++ b/core/runtime/internal.odin @@ -566,16 +566,37 @@ max_f64 :: #force_inline proc "contextless" (a, b: f64) -> f64 { } abs_complex32 :: #force_inline proc "contextless" (x: complex32) -> f16 { - r, i := real(x), imag(x) - return f16(intrinsics.sqrt(f32(r*r + i*i))) + p, q := abs(real(x)), abs(imag(x)) + if p < q { + p, q = q, p + } + if p == 0 { + return 0 + } + q = q / p + return p * f16(intrinsics.sqrt(f32(1 + q*q))) } abs_complex64 :: #force_inline proc "contextless" (x: complex64) -> f32 { - r, i := real(x), imag(x) - return intrinsics.sqrt(r*r + i*i) + p, q := abs(real(x)), abs(imag(x)) + if p < q { + p, q = q, p + } + if p == 0 { + return 0 + } + q = q / p + return p * intrinsics.sqrt(1 + q*q) } abs_complex128 :: #force_inline proc "contextless" (x: complex128) -> f64 { - r, i := real(x), imag(x) - return intrinsics.sqrt(r*r + i*i) + p, q := abs(real(x)), abs(imag(x)) + if p < q { + p, q = q, p + } + if p == 0 { + return 0 + } + q = q / p + return p * intrinsics.sqrt(1 + q*q) } abs_quaternion64 :: #force_inline proc "contextless" (x: quaternion64) -> f16 { r, i, j, k := real(x), imag(x), jmag(x), kmag(x) From 1ecb4aa9aaa7344fe03a5e253fe19d0ca6abedd3 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 28 Jun 2023 13:20:23 +0100 Subject: [PATCH 86/97] Begin work on `core:math/cmplx` `complex*` types only at the moment, `quaternion*` types coming later --- core/math/cmplx/cmplx.odin | 513 +++++++++++++++++++++++++++++ core/math/cmplx/cmplx_invtrig.odin | 273 +++++++++++++++ core/math/cmplx/cmplx_trig.odin | 409 +++++++++++++++++++++++ 3 files changed, 1195 insertions(+) create mode 100644 core/math/cmplx/cmplx.odin create mode 100644 core/math/cmplx/cmplx_invtrig.odin create mode 100644 core/math/cmplx/cmplx_trig.odin diff --git a/core/math/cmplx/cmplx.odin b/core/math/cmplx/cmplx.odin new file mode 100644 index 000000000..c029be30c --- /dev/null +++ b/core/math/cmplx/cmplx.odin @@ -0,0 +1,513 @@ +package math_cmplx + +import "core:builtin" +import "core:math" + +// The original C code, the long comment, and the constants +// below are from http://netlib.sandia.gov/cephes/c9x-complex/clog.c. +// The go code is a simplified version of the original C. +// +// Cephes Math Library Release 2.8: June, 2000 +// Copyright 1984, 1987, 1989, 1992, 2000 by Stephen L. Moshier +// +// The readme file at http://netlib.sandia.gov/cephes/ says: +// Some software in this archive may be from the book _Methods and +// Programs for Mathematical Functions_ (Prentice-Hall or Simon & Schuster +// International, 1989) or from the Cephes Mathematical Library, a +// commercial product. In either event, it is copyrighted by the author. +// What you see here may be used freely but it comes with no support or +// guarantee. +// +// The two known misprints in the book are repaired here in the +// source listings for the gamma function and the incomplete beta +// integral. +// +// Stephen L. Moshier +// moshier@na-net.ornl.gov + +abs :: builtin.abs +conj :: builtin.conj +real :: builtin.real +imag :: builtin.imag +jmag :: builtin.jmag +kmag :: builtin.kmag + + +sin :: proc{ + sin_complex128, +} +cos :: proc{ + cos_complex128, +} +tan :: proc{ + tan_complex128, +} +cot :: proc{ + cot_complex128, +} + + +sinh :: proc{ + sinh_complex128, +} +cosh :: proc{ + cosh_complex128, +} +tanh :: proc{ + tanh_complex128, +} + + + +// sqrt returns the square root of x. +// The result r is chosen so that real(r) ≥ 0 and imag(r) has the same sign as imag(x). +sqrt :: proc{ + sqrt_complex32, + sqrt_complex64, + sqrt_complex128, +} +ln :: proc{ + ln_complex32, + ln_complex64, + ln_complex128, +} +log10 :: proc{ + log10_complex32, + log10_complex64, + log10_complex128, +} + +exp :: proc{ + exp_complex32, + exp_complex64, + exp_complex128, +} + +pow :: proc{ + pow_complex32, + pow_complex64, + pow_complex128, +} + +phase :: proc{ + phase_complex32, + phase_complex64, + phase_complex128, +} + +polar :: proc{ + polar_complex32, + polar_complex64, + polar_complex128, +} + +is_inf :: proc{ + is_inf_complex32, + is_inf_complex64, + is_inf_complex128, +} + +is_nan :: proc{ + is_nan_complex32, + is_nan_complex64, + is_nan_complex128, +} + + + +// sqrt_complex32 returns the square root of x. +// The result r is chosen so that real(r) ≥ 0 and imag(r) has the same sign as imag(x). +sqrt_complex32 :: proc "contextless" (x: complex32) -> complex32 { + return complex32(sqrt_complex128(complex128(x))) +} + +// sqrt_complex64 returns the square root of x. +// The result r is chosen so that real(r) ≥ 0 and imag(r) has the same sign as imag(x). +sqrt_complex64 :: proc "contextless" (x: complex64) -> complex64 { + return complex64(sqrt_complex128(complex128(x))) +} + + +// sqrt_complex128 returns the square root of x. +// The result r is chosen so that real(r) ≥ 0 and imag(r) has the same sign as imag(x). +sqrt_complex128 :: proc "contextless" (x: complex128) -> complex128 { + // The original C code, the long comment, and the constants + // below are from http://netlib.sandia.gov/cephes/c9x-complex/clog.c. + // The go code is a simplified version of the original C. + // + // Cephes Math Library Release 2.8: June, 2000 + // Copyright 1984, 1987, 1989, 1992, 2000 by Stephen L. Moshier + // + // The readme file at http://netlib.sandia.gov/cephes/ says: + // Some software in this archive may be from the book _Methods and + // Programs for Mathematical Functions_ (Prentice-Hall or Simon & Schuster + // International, 1989) or from the Cephes Mathematical Library, a + // commercial product. In either event, it is copyrighted by the author. + // What you see here may be used freely but it comes with no support or + // guarantee. + // + // The two known misprints in the book are repaired here in the + // source listings for the gamma function and the incomplete beta + // integral. + // + // Stephen L. Moshier + // moshier@na-net.ornl.gov + + // Complex square root + // + // DESCRIPTION: + // + // If z = x + iy, r = |z|, then + // + // 1/2 + // Re w = [ (r + x)/2 ] , + // + // 1/2 + // Im w = [ (r - x)/2 ] . + // + // Cancellation error in r-x or r+x is avoided by using the + // identity 2 Re w Im w = y. + // + // Note that -w is also a square root of z. The root chosen + // is always in the right half plane and Im w has the same sign as y. + // + // ACCURACY: + // + // Relative error: + // arithmetic domain # trials peak rms + // DEC -10,+10 25000 3.2e-17 9.6e-18 + // IEEE -10,+10 1,000,000 2.9e-16 6.1e-17 + + if imag(x) == 0 { + // Ensure that imag(r) has the same sign as imag(x) for imag(x) == signed zero. + if real(x) == 0 { + return complex(0, imag(x)) + } + if real(x) < 0 { + return complex(0, math.copy_sign(math.sqrt(-real(x)), imag(x))) + } + return complex(math.sqrt(real(x)), imag(x)) + } else if math.is_inf(imag(x), 0) { + return complex(math.inf_f64(1.0), imag(x)) + } + if real(x) == 0 { + if imag(x) < 0 { + r := math.sqrt(-0.5 * imag(x)) + return complex(r, -r) + } + r := math.sqrt(0.5 * imag(x)) + return complex(r, r) + } + a := real(x) + b := imag(x) + scale: f64 + // Rescale to avoid internal overflow or underflow. + if abs(a) > 4 || abs(b) > 4 { + a *= 0.25 + b *= 0.25 + scale = 2 + } else { + a *= 1.8014398509481984e16 // 2**54 + b *= 1.8014398509481984e16 + scale = 7.450580596923828125e-9 // 2**-27 + } + r := math.hypot(a, b) + t: f64 + if a > 0 { + t = math.sqrt(0.5*r + 0.5*a) + r = scale * abs((0.5*b)/t) + t *= scale + } else { + r = math.sqrt(0.5*r - 0.5*a) + t = scale * abs((0.5*b)/r) + r *= scale + } + if b < 0 { + return complex(t, -r) + } + return complex(t, r) +} + +ln_complex32 :: proc "contextless" (x: complex32) -> complex32 { + return complex(math.ln(abs(x)), phase(x)) +} +ln_complex64 :: proc "contextless" (x: complex64) -> complex64 { + return complex(math.ln(abs(x)), phase(x)) +} +ln_complex128 :: proc "contextless" (x: complex128) -> complex128 { + return complex(math.ln(abs(x)), phase(x)) +} + + +exp_complex32 :: proc "contextless" (x: complex32) -> complex32 { + switch re, im := real(x), imag(x); { + case math.is_inf(re, 0): + switch { + case re > 0 && im == 0: + return x + case math.is_inf(im, 0) || math.is_nan(im): + if re < 0 { + return complex(0, math.copy_sign(0, im)) + } else { + return complex(math.inf_f64(1.0), math.nan_f64()) + } + } + case math.is_nan(re): + if im == 0 { + return complex(math.nan_f16(), im) + } + } + r := math.exp(real(x)) + s, c := math.sincos(imag(x)) + return complex(r*c, r*s) +} +exp_complex64 :: proc "contextless" (x: complex64) -> complex64 { + switch re, im := real(x), imag(x); { + case math.is_inf(re, 0): + switch { + case re > 0 && im == 0: + return x + case math.is_inf(im, 0) || math.is_nan(im): + if re < 0 { + return complex(0, math.copy_sign(0, im)) + } else { + return complex(math.inf_f64(1.0), math.nan_f64()) + } + } + case math.is_nan(re): + if im == 0 { + return complex(math.nan_f32(), im) + } + } + r := math.exp(real(x)) + s, c := math.sincos(imag(x)) + return complex(r*c, r*s) +} +exp_complex128 :: proc "contextless" (x: complex128) -> complex128 { + switch re, im := real(x), imag(x); { + case math.is_inf(re, 0): + switch { + case re > 0 && im == 0: + return x + case math.is_inf(im, 0) || math.is_nan(im): + if re < 0 { + return complex(0, math.copy_sign(0, im)) + } else { + return complex(math.inf_f64(1.0), math.nan_f64()) + } + } + case math.is_nan(re): + if im == 0 { + return complex(math.nan_f64(), im) + } + } + r := math.exp(real(x)) + s, c := math.sincos(imag(x)) + return complex(r*c, r*s) +} + + +pow_complex32 :: proc "contextless" (x, y: complex32) -> complex32 { + if x == 0 { // Guaranteed also true for x == -0. + if is_nan(y) { + return nan_complex32() + } + r, i := real(y), imag(y) + switch { + case r == 0: + return 1 + case r < 0: + if i == 0 { + return complex(math.inf_f16(1), 0) + } + return inf_complex32() + case r > 0: + return 0 + } + unreachable() + } + modulus := abs(x) + if modulus == 0 { + return complex(0, 0) + } + r := math.pow(modulus, real(y)) + arg := phase(x) + theta := real(y) * arg + if imag(y) != 0 { + r *= math.exp(-imag(y) * arg) + theta += imag(y) * math.ln(modulus) + } + s, c := math.sincos(theta) + return complex(r*c, r*s) +} +pow_complex64 :: proc "contextless" (x, y: complex64) -> complex64 { + if x == 0 { // Guaranteed also true for x == -0. + if is_nan(y) { + return nan_complex64() + } + r, i := real(y), imag(y) + switch { + case r == 0: + return 1 + case r < 0: + if i == 0 { + return complex(math.inf_f32(1), 0) + } + return inf_complex64() + case r > 0: + return 0 + } + unreachable() + } + modulus := abs(x) + if modulus == 0 { + return complex(0, 0) + } + r := math.pow(modulus, real(y)) + arg := phase(x) + theta := real(y) * arg + if imag(y) != 0 { + r *= math.exp(-imag(y) * arg) + theta += imag(y) * math.ln(modulus) + } + s, c := math.sincos(theta) + return complex(r*c, r*s) +} +pow_complex128 :: proc "contextless" (x, y: complex128) -> complex128 { + if x == 0 { // Guaranteed also true for x == -0. + if is_nan(y) { + return nan_complex128() + } + r, i := real(y), imag(y) + switch { + case r == 0: + return 1 + case r < 0: + if i == 0 { + return complex(math.inf_f64(1), 0) + } + return inf_complex128() + case r > 0: + return 0 + } + unreachable() + } + modulus := abs(x) + if modulus == 0 { + return complex(0, 0) + } + r := math.pow(modulus, real(y)) + arg := phase(x) + theta := real(y) * arg + if imag(y) != 0 { + r *= math.exp(-imag(y) * arg) + theta += imag(y) * math.ln(modulus) + } + s, c := math.sincos(theta) + return complex(r*c, r*s) +} + + + +log10_complex32 :: proc "contextless" (x: complex32) -> complex32 { + return math.LN10*ln(x) +} +log10_complex64 :: proc "contextless" (x: complex64) -> complex64 { + return math.LN10*ln(x) +} +log10_complex128 :: proc "contextless" (x: complex128) -> complex128 { + return math.LN10*ln(x) +} + + +phase_complex32 :: proc "contextless" (x: complex32) -> f16 { + return math.atan2(imag(x), real(x)) +} +phase_complex64 :: proc "contextless" (x: complex64) -> f32 { + return math.atan2(imag(x), real(x)) +} +phase_complex128 :: proc "contextless" (x: complex128) -> f64 { + return math.atan2(imag(x), real(x)) +} + + +rect_complex32 :: proc "contextless" (r, θ: f16) -> complex32 { + s, c := math.sincos(θ) + return complex(r*c, r*s) +} +rect_complex64 :: proc "contextless" (r, θ: f32) -> complex64 { + s, c := math.sincos(θ) + return complex(r*c, r*s) +} +rect_complex128 :: proc "contextless" (r, θ: f64) -> complex128 { + s, c := math.sincos(θ) + return complex(r*c, r*s) +} + +polar_complex32 :: proc "contextless" (x: complex32) -> (r, θ: f16) { + return abs(x), phase(x) +} +polar_complex64 :: proc "contextless" (x: complex64) -> (r, θ: f32) { + return abs(x), phase(x) +} +polar_complex128 :: proc "contextless" (x: complex128) -> (r, θ: f64) { + return abs(x), phase(x) +} + + + + +nan_complex32 :: proc "contextless" () -> complex32 { + return complex(math.nan_f16(), math.nan_f16()) +} +nan_complex64 :: proc "contextless" () -> complex64 { + return complex(math.nan_f32(), math.nan_f32()) +} +nan_complex128 :: proc "contextless" () -> complex128 { + return complex(math.nan_f64(), math.nan_f64()) +} + + +inf_complex32 :: proc "contextless" () -> complex32 { + inf := math.inf_f16(1) + return complex(inf, inf) +} +inf_complex64 :: proc "contextless" () -> complex64 { + inf := math.inf_f32(1) + return complex(inf, inf) +} +inf_complex128 :: proc "contextless" () -> complex128 { + inf := math.inf_f64(1) + return complex(inf, inf) +} + + +is_inf_complex32 :: proc "contextless" (x: complex32) -> bool { + return math.is_inf(real(x), 0) || math.is_inf(imag(x), 0) +} +is_inf_complex64 :: proc "contextless" (x: complex64) -> bool { + return math.is_inf(real(x), 0) || math.is_inf(imag(x), 0) +} +is_inf_complex128 :: proc "contextless" (x: complex128) -> bool { + return math.is_inf(real(x), 0) || math.is_inf(imag(x), 0) +} + + +is_nan_complex32 :: proc "contextless" (x: complex32) -> bool { + if math.is_inf(real(x), 0) || math.is_inf(imag(x), 0) { + return false + } + return math.is_nan(real(x)) || math.is_nan(imag(x)) +} +is_nan_complex64 :: proc "contextless" (x: complex64) -> bool { + if math.is_inf(real(x), 0) || math.is_inf(imag(x), 0) { + return false + } + return math.is_nan(real(x)) || math.is_nan(imag(x)) +} +is_nan_complex128 :: proc "contextless" (x: complex128) -> bool { + if math.is_inf(real(x), 0) || math.is_inf(imag(x), 0) { + return false + } + return math.is_nan(real(x)) || math.is_nan(imag(x)) +} diff --git a/core/math/cmplx/cmplx_invtrig.odin b/core/math/cmplx/cmplx_invtrig.odin new file mode 100644 index 000000000..a746a370f --- /dev/null +++ b/core/math/cmplx/cmplx_invtrig.odin @@ -0,0 +1,273 @@ +package math_cmplx + +import "core:builtin" +import "core:math" + +// The original C code, the long comment, and the constants +// below are from http://netlib.sandia.gov/cephes/c9x-complex/clog.c. +// The go code is a simplified version of the original C. +// +// Cephes Math Library Release 2.8: June, 2000 +// Copyright 1984, 1987, 1989, 1992, 2000 by Stephen L. Moshier +// +// The readme file at http://netlib.sandia.gov/cephes/ says: +// Some software in this archive may be from the book _Methods and +// Programs for Mathematical Functions_ (Prentice-Hall or Simon & Schuster +// International, 1989) or from the Cephes Mathematical Library, a +// commercial product. In either event, it is copyrighted by the author. +// What you see here may be used freely but it comes with no support or +// guarantee. +// +// The two known misprints in the book are repaired here in the +// source listings for the gamma function and the incomplete beta +// integral. +// +// Stephen L. Moshier +// moshier@na-net.ornl.gov + +acos :: proc{ + acos_complex32, + acos_complex64, + acos_complex128, +} +acosh :: proc{ + acosh_complex32, + acosh_complex64, + acosh_complex128, +} + +asin :: proc{ + asin_complex32, + asin_complex64, + asin_complex128, +} +asinh :: proc{ + asinh_complex32, + asinh_complex64, + asinh_complex128, +} + +atan :: proc{ + atan_complex32, + atan_complex64, + atan_complex128, +} + +atanh :: proc{ + atanh_complex32, + atanh_complex64, + atanh_complex128, +} + + +acos_complex32 :: proc "contextless" (x: complex32) -> complex32 { + w := asin(x) + return complex(math.PI/2 - real(w), -imag(w)) +} +acos_complex64 :: proc "contextless" (x: complex64) -> complex64 { + w := asin(x) + return complex(math.PI/2 - real(w), -imag(w)) +} +acos_complex128 :: proc "contextless" (x: complex128) -> complex128 { + w := asin(x) + return complex(math.PI/2 - real(w), -imag(w)) +} + + +acosh_complex32 :: proc "contextless" (x: complex32) -> complex32 { + if x == 0 { + return complex(0, math.copy_sign(math.PI/2, imag(x))) + } + w := acos(x) + if imag(w) <= 0 { + return complex(-imag(w), real(w)) + } + return complex(imag(w), -real(w)) +} +acosh_complex64 :: proc "contextless" (x: complex64) -> complex64 { + if x == 0 { + return complex(0, math.copy_sign(math.PI/2, imag(x))) + } + w := acos(x) + if imag(w) <= 0 { + return complex(-imag(w), real(w)) + } + return complex(imag(w), -real(w)) +} +acosh_complex128 :: proc "contextless" (x: complex128) -> complex128 { + if x == 0 { + return complex(0, math.copy_sign(math.PI/2, imag(x))) + } + w := acos(x) + if imag(w) <= 0 { + return complex(-imag(w), real(w)) + } + return complex(imag(w), -real(w)) +} + +asin_complex32 :: proc "contextless" (x: complex32) -> complex32 { + return complex32(asin_complex128(complex128(x))) +} +asin_complex64 :: proc "contextless" (x: complex64) -> complex64 { + return complex64(asin_complex128(complex128(x))) +} +asin_complex128 :: proc "contextless" (x: complex128) -> complex128 { + switch re, im := real(x), imag(x); { + case im == 0 && abs(re) <= 1: + return complex(math.asin(re), im) + case re == 0 && abs(im) <= 1: + return complex(re, math.asinh(im)) + case math.is_nan(im): + switch { + case re == 0: + return complex(re, math.nan_f64()) + case math.is_inf(re, 0): + return complex(math.nan_f64(), re) + case: + return nan_complex128() + } + case math.is_inf(im, 0): + switch { + case math.is_nan(re): + return x + case math.is_inf(re, 0): + return complex(math.copy_sign(math.PI/4, re), im) + case: + return complex(math.copy_sign(0, re), im) + } + case math.is_inf(re, 0): + return complex(math.copy_sign(math.PI/2, re), math.copy_sign(re, im)) + } + ct := complex(-imag(x), real(x)) // i * x + xx := x * x + x1 := complex(1-real(xx), -imag(xx)) // 1 - x*x + x2 := sqrt(x1) // x2 = sqrt(1 - x*x) + w := ln(ct + x2) + return complex(imag(w), -real(w)) // -i * w +} + +asinh_complex32 :: proc "contextless" (x: complex32) -> complex32 { + return complex32(asinh_complex128(complex128(x))) +} +asinh_complex64 :: proc "contextless" (x: complex64) -> complex64 { + return complex64(asinh_complex128(complex128(x))) +} +asinh_complex128 :: proc "contextless" (x: complex128) -> complex128 { + switch re, im := real(x), imag(x); { + case im == 0 && abs(re) <= 1: + return complex(math.asinh(re), im) + case re == 0 && abs(im) <= 1: + return complex(re, math.asin(im)) + case math.is_inf(re, 0): + switch { + case math.is_inf(im, 0): + return complex(re, math.copy_sign(math.PI/4, im)) + case math.is_nan(im): + return x + case: + return complex(re, math.copy_sign(0.0, im)) + } + case math.is_nan(re): + switch { + case im == 0: + return x + case math.is_inf(im, 0): + return complex(im, re) + case: + return nan_complex128() + } + case math.is_inf(im, 0): + return complex(math.copy_sign(im, re), math.copy_sign(math.PI/2, im)) + } + xx := x * x + x1 := complex(1+real(xx), imag(xx)) // 1 + x*x + return ln(x + sqrt(x1)) // log(x + sqrt(1 + x*x)) +} + + +atan_complex32 :: proc "contextless" (x: complex32) -> complex32 { + return complex32(atan_complex128(complex128(x))) +} +atan_complex64 :: proc "contextless" (x: complex64) -> complex64 { + return complex64(atan_complex128(complex128(x))) +} +atan_complex128 :: proc "contextless" (x: complex128) -> complex128 { + // Complex circular arc tangent + // + // DESCRIPTION: + // + // If + // z = x + iy, + // + // then + // 1 ( 2x ) + // Re w = - arctan(-----------) + k PI + // 2 ( 2 2) + // (1 - x - y ) + // + // ( 2 2) + // 1 (x + (y+1) ) + // Im w = - log(------------) + // 4 ( 2 2) + // (x + (y-1) ) + // + // Where k is an arbitrary integer. + // + // catan(z) = -i catanh(iz). + // + // ACCURACY: + // + // Relative error: + // arithmetic domain # trials peak rms + // DEC -10,+10 5900 1.3e-16 7.8e-18 + // IEEE -10,+10 30000 2.3e-15 8.5e-17 + // The check catan( ctan(z) ) = z, with |x| and |y| < PI/2, + // had peak relative error 1.5e-16, rms relative error + // 2.9e-17. See also clog(). + + switch re, im := real(x), imag(x); { + case im == 0: + return complex(math.atan(re), im) + case re == 0 && abs(im) <= 1: + return complex(re, math.atanh(im)) + case math.is_inf(im, 0) || math.is_inf(re, 0): + if math.is_nan(re) { + return complex(math.nan_f64(), math.copy_sign(0, im)) + } + return complex(math.copy_sign(math.PI/2, re), math.copy_sign(0, im)) + case math.is_nan(re) || math.is_nan(im): + return nan_complex128() + } + x2 := real(x) * real(x) + a := 1 - x2 - imag(x)*imag(x) + if a == 0 { + return nan_complex128() + } + t := 0.5 * math.atan2(2*real(x), a) + w := _reduce_pi_f64(t) + + t = imag(x) - 1 + b := x2 + t*t + if b == 0 { + return nan_complex128() + } + t = imag(x) + 1 + c := (x2 + t*t) / b + return complex(w, 0.25*math.ln(c)) +} + +atanh_complex32 :: proc "contextless" (x: complex32) -> complex32 { + z := complex(-imag(x), real(x)) // z = i * x + z = atan(z) + return complex(imag(z), -real(z)) // z = -i * z +} +atanh_complex64 :: proc "contextless" (x: complex64) -> complex64 { + z := complex(-imag(x), real(x)) // z = i * x + z = atan(z) + return complex(imag(z), -real(z)) // z = -i * z +} +atanh_complex128 :: proc "contextless" (x: complex128) -> complex128 { + z := complex(-imag(x), real(x)) // z = i * x + z = atan(z) + return complex(imag(z), -real(z)) // z = -i * z +} \ No newline at end of file diff --git a/core/math/cmplx/cmplx_trig.odin b/core/math/cmplx/cmplx_trig.odin new file mode 100644 index 000000000..7ca404fab --- /dev/null +++ b/core/math/cmplx/cmplx_trig.odin @@ -0,0 +1,409 @@ +package math_cmplx + +import "core:math" +import "core:math/bits" + +// The original C code, the long comment, and the constants +// below are from http://netlib.sandia.gov/cephes/c9x-complex/clog.c. +// The go code is a simplified version of the original C. +// +// Cephes Math Library Release 2.8: June, 2000 +// Copyright 1984, 1987, 1989, 1992, 2000 by Stephen L. Moshier +// +// The readme file at http://netlib.sandia.gov/cephes/ says: +// Some software in this archive may be from the book _Methods and +// Programs for Mathematical Functions_ (Prentice-Hall or Simon & Schuster +// International, 1989) or from the Cephes Mathematical Library, a +// commercial product. In either event, it is copyrighted by the author. +// What you see here may be used freely but it comes with no support or +// guarantee. +// +// The two known misprints in the book are repaired here in the +// source listings for the gamma function and the incomplete beta +// integral. +// +// Stephen L. Moshier +// moshier@na-net.ornl.gov + +sin_complex128 :: proc "contextless" (x: complex128) -> complex128 { + // Complex circular sine + // + // DESCRIPTION: + // + // If + // z = x + iy, + // + // then + // + // w = sin x cosh y + i cos x sinh y. + // + // csin(z) = -i csinh(iz). + // + // ACCURACY: + // + // Relative error: + // arithmetic domain # trials peak rms + // DEC -10,+10 8400 5.3e-17 1.3e-17 + // IEEE -10,+10 30000 3.8e-16 1.0e-16 + // Also tested by csin(casin(z)) = z. + + switch re, im := real(x), imag(x); { + case im == 0 && (math.is_inf(re, 0) || math.is_nan(re)): + return complex(math.nan_f64(), im) + case math.is_inf(im, 0): + switch { + case re == 0: + return x + case math.is_inf(re, 0) || math.is_nan(re): + return complex(math.nan_f64(), im) + } + case re == 0 && math.is_nan(im): + return x + } + s, c := math.sincos(real(x)) + sh, ch := _sinhcosh_f64(imag(x)) + return complex(s*ch, c*sh) +} + +cos_complex128 :: proc "contextless" (x: complex128) -> complex128 { + // Complex circular cosine + // + // DESCRIPTION: + // + // If + // z = x + iy, + // + // then + // + // w = cos x cosh y - i sin x sinh y. + // + // ACCURACY: + // + // Relative error: + // arithmetic domain # trials peak rms + // DEC -10,+10 8400 4.5e-17 1.3e-17 + // IEEE -10,+10 30000 3.8e-16 1.0e-16 + + switch re, im := real(x), imag(x); { + case im == 0 && (math.is_inf(re, 0) || math.is_nan(re)): + return complex(math.nan_f64(), -im*math.copy_sign(0, re)) + case math.is_inf(im, 0): + switch { + case re == 0: + return complex(math.inf_f64(1), -re*math.copy_sign(0, im)) + case math.is_inf(re, 0) || math.is_nan(re): + return complex(math.inf_f64(1), math.nan_f64()) + } + case re == 0 && math.is_nan(im): + return complex(math.nan_f64(), 0) + } + s, c := math.sincos(real(x)) + sh, ch := _sinhcosh_f64(imag(x)) + return complex(c*ch, -s*sh) +} + +sinh_complex128 :: proc "contextless" (x: complex128) -> complex128 { + // Complex hyperbolic sine + // + // DESCRIPTION: + // + // csinh z = (cexp(z) - cexp(-z))/2 + // = sinh x * cos y + i cosh x * sin y . + // + // ACCURACY: + // + // Relative error: + // arithmetic domain # trials peak rms + // IEEE -10,+10 30000 3.1e-16 8.2e-17 + + switch re, im := real(x), imag(x); { + case re == 0 && (math.is_inf(im, 0) || math.is_nan(im)): + return complex(re, math.nan_f64()) + case math.is_inf(re, 0): + switch { + case im == 0: + return complex(re, im) + case math.is_inf(im, 0) || math.is_nan(im): + return complex(re, math.nan_f64()) + } + case im == 0 && math.is_nan(re): + return complex(math.nan_f64(), im) + } + s, c := math.sincos(imag(x)) + sh, ch := _sinhcosh_f64(real(x)) + return complex(c*sh, s*ch) +} + +cosh_complex128 :: proc "contextless" (x: complex128) -> complex128 { + // Complex hyperbolic cosine + // + // DESCRIPTION: + // + // ccosh(z) = cosh x cos y + i sinh x sin y . + // + // ACCURACY: + // + // Relative error: + // arithmetic domain # trials peak rms + // IEEE -10,+10 30000 2.9e-16 8.1e-17 + + switch re, im := real(x), imag(x); { + case re == 0 && (math.is_inf(im, 0) || math.is_nan(im)): + return complex(math.nan_f64(), re*math.copy_sign(0, im)) + case math.is_inf(re, 0): + switch { + case im == 0: + return complex(math.inf_f64(1), im*math.copy_sign(0, re)) + case math.is_inf(im, 0) || math.is_nan(im): + return complex(math.inf_f64(1), math.nan_f64()) + } + case im == 0 && math.is_nan(re): + return complex(math.nan_f64(), im) + } + s, c := math.sincos(imag(x)) + sh, ch := _sinhcosh_f64(real(x)) + return complex(c*ch, s*sh) +} + +tan_complex128 :: proc "contextless" (x: complex128) -> complex128 { + // Complex circular tangent + // + // DESCRIPTION: + // + // If + // z = x + iy, + // + // then + // + // sin 2x + i sinh 2y + // w = --------------------. + // cos 2x + cosh 2y + // + // On the real axis the denominator is zero at odd multiples + // of PI/2. The denominator is evaluated by its Taylor + // series near these points. + // + // ctan(z) = -i ctanh(iz). + // + // ACCURACY: + // + // Relative error: + // arithmetic domain # trials peak rms + // DEC -10,+10 5200 7.1e-17 1.6e-17 + // IEEE -10,+10 30000 7.2e-16 1.2e-16 + // Also tested by ctan * ccot = 1 and catan(ctan(z)) = z. + + switch re, im := real(x), imag(x); { + case math.is_inf(im, 0): + switch { + case math.is_inf(re, 0) || math.is_nan(re): + return complex(math.copy_sign(0, re), math.copy_sign(1, im)) + } + return complex(math.copy_sign(0, math.sin(2*re)), math.copy_sign(1, im)) + case re == 0 && math.is_nan(im): + return x + } + d := math.cos(2*real(x)) + math.cosh(2*imag(x)) + if abs(d) < 0.25 { + d = _tan_series_f64(x) + } + if d == 0 { + return inf_complex128() + } + return complex(math.sin(2*real(x))/d, math.sinh(2*imag(x))/d) +} + +tanh_complex128 :: proc "contextless" (x: complex128) -> complex128 { + switch re, im := real(x), imag(x); { + case math.is_inf(re, 0): + switch { + case math.is_inf(im, 0) || math.is_nan(im): + return complex(math.copy_sign(1, re), math.copy_sign(0, im)) + } + return complex(math.copy_sign(1, re), math.copy_sign(0, math.sin(2*im))) + case im == 0 && math.is_nan(re): + return x + } + d := math.cosh(2*real(x)) + math.cos(2*imag(x)) + if d == 0 { + return inf_complex128() + } + return complex(math.sinh(2*real(x))/d, math.sin(2*imag(x))/d) +} + +cot_complex128 :: proc "contextless" (x: complex128) -> complex128 { + d := math.cosh(2*imag(x)) - math.cos(2*real(x)) + if abs(d) < 0.25 { + d = _tan_series_f64(x) + } + if d == 0 { + return inf_complex128() + } + return complex(math.sin(2*real(x))/d, -math.sinh(2*imag(x))/d) +} + + +@(private="file") +_sinhcosh_f64 :: proc "contextless" (x: f64) -> (sh, ch: f64) { + if abs(x) <= 0.5 { + return math.sinh(x), math.cosh(x) + } + e := math.exp(x) + ei := 0.5 / e + e *= 0.5 + return e - ei, e + ei +} + + +// taylor series of cosh(2y) - cos(2x) +@(private) +_tan_series_f64 :: proc "contextless" (z: complex128) -> f64 { + MACH_EPSILON :: 1.0 / (1 << 53) + + x := abs(2 * real(z)) + y := abs(2 * imag(z)) + x = _reduce_pi_f64(x) + x, y = x * x, y * y + x2, y2 := 1.0, 1.0 + f, rn, d := 1.0, 0.0, 0.0 + + for { + rn += 1 + f *= rn + rn += 1 + f *= rn + x2 *= x + y2 *= y + t := y2 + x2 + t /= f + d += t + + rn += 1 + f *= rn + rn += 1 + f *= rn + x2 *= x + y2 *= y + t = y2 - x2 + t /= f + d += t + if !(abs(t/d) > MACH_EPSILON) { // don't use <=, because of floating point nonsense and NaN + break + } + } + return d +} + +// _reduce_pi_f64 reduces the input argument x to the range (-PI/2, PI/2]. +// x must be greater than or equal to 0. For small arguments it +// uses Cody-Waite reduction in 3 f64 parts based on: +// "Elementary Function Evaluation: Algorithms and Implementation" +// Jean-Michel Muller, 1997. +// For very large arguments it uses Payne-Hanek range reduction based on: +// "ARGUMENT REDUCTION FOR HUGE ARGUMENTS: Good to the Last Bit" +@(private) +_reduce_pi_f64 :: proc "contextless" (x: f64) -> f64 #no_bounds_check { + x := x + + // REDUCE_THRESHOLD is the maximum value of x where the reduction using + // Cody-Waite reduction still gives accurate results. This threshold + // is set by t*PIn being representable as a f64 without error + // where t is given by t = floor(x * (1 / PI)) and PIn are the leading partial + // terms of PI. Since the leading terms, PI1 and PI2 below, have 30 and 32 + // trailing zero bits respectively, t should have less than 30 significant bits. + // t < 1<<30 -> floor(x*(1/PI)+0.5) < 1<<30 -> x < (1<<30-1) * PI - 0.5 + // So, conservatively we can take x < 1<<30. + REDUCE_THRESHOLD :: f64(1 << 30) + + if abs(x) < REDUCE_THRESHOLD { + // Use Cody-Waite reduction in three parts. + // PI1, PI2 and PI3 comprise an extended precision value of PI + // such that PI ~= PI1 + PI2 + PI3. The parts are chosen so + // that PI1 and PI2 have an approximately equal number of trailing + // zero bits. This ensures that t*PI1 and t*PI2 are exact for + // large integer values of t. The full precision PI3 ensures the + // approximation of PI is accurate to 102 bits to handle cancellation + // during subtraction. + PI1 :: 0h400921fb40000000 // 3.141592502593994 + PI2 :: 0h3e84442d00000000 // 1.5099578831723193e-07 + PI3 :: 0h3d08469898cc5170 // 1.0780605716316238e-14 + + t := x / math.PI + t += 0.5 + t = f64(i64(t)) // i64(t) = the multiple + return ((x - t*PI1) - t*PI2) - t*PI3 + } + // Must apply Payne-Hanek range reduction + MASK :: 0x7FF + SHIFT :: 64 - 11 - 1 + BIAS :: 1023 + FRAC_MASK :: 1<>SHIFT&MASK) - BIAS - SHIFT + ix &= FRAC_MASK + ix |= 1 << SHIFT + + // bdpi is the binary digits of 1/PI as a u64 array, + // that is, 1/PI = SUM bdpi[i]*2^(-64*i). + // 19 64-bit digits give 1216 bits of precision + // to handle the largest possible f64 exponent. + @static bdpi := [?]u64{ + 0x0000000000000000, + 0x517cc1b727220a94, + 0xfe13abe8fa9a6ee0, + 0x6db14acc9e21c820, + 0xff28b1d5ef5de2b0, + 0xdb92371d2126e970, + 0x0324977504e8c90e, + 0x7f0ef58e5894d39f, + 0x74411afa975da242, + 0x74ce38135a2fbf20, + 0x9cc8eb1cc1a99cfa, + 0x4e422fc5defc941d, + 0x8ffc4bffef02cc07, + 0xf79788c5ad05368f, + 0xb69b3f6793e584db, + 0xa7a31fb34f2ff516, + 0xba93dd63f5f2f8bd, + 0x9e839cfbc5294975, + 0x35fdafd88fc6ae84, + 0x2b0198237e3db5d5, + } + + // Use the exponent to extract the 3 appropriate u64 digits from bdpi, + // B ~ (z0, z1, z2), such that the product leading digit has the exponent -64. + // Note, exp >= 50 since x >= REDUCE_THRESHOLD and exp < 971 for maximum f64. + digit, bitshift := uint(exp+64)/64, uint(exp+64)%64 + z0 := (bdpi[digit] << bitshift) | (bdpi[digit+1] >> (64 - bitshift)) + z1 := (bdpi[digit+1] << bitshift) | (bdpi[digit+2] >> (64 - bitshift)) + z2 := (bdpi[digit+2] << bitshift) | (bdpi[digit+3] >> (64 - bitshift)) + + // Multiply mantissa by the digits and extract the upper two digits (hi, lo). + z2hi, _ := bits.mul(z2, ix) + z1hi, z1lo := bits.mul(z1, ix) + z0lo := z0 * ix + lo, c := bits.add(z1lo, z2hi, 0) + hi, _ := bits.add(z0lo, z1hi, c) + + // Find the magnitude of the fraction. + lz := uint(bits.leading_zeros(hi)) + e := u64(BIAS - (lz + 1)) + + // Clear implicit mantissa bit and shift into place. + hi = (hi << (lz + 1)) | (lo >> (64 - (lz + 1))) + hi >>= 64 - SHIFT + + // Include the exponent and convert to a float. + hi |= e << SHIFT + x = transmute(f64)(hi) + + // map to (-PI/2, PI/2] + if x > 0.5 { + x -= 1 + } + return math.PI * x +} + From 5e99ff6769ba6463ca68f0e5c6f83ad35208b622 Mon Sep 17 00:00:00 2001 From: DanielGavin Date: Sun, 2 Jul 2023 18:24:37 +0200 Subject: [PATCH 87/97] Add support FlagBits2 --- .../vulkan/_gen/create_vulkan_odin_wrapper.py | 99 ++++++++ vendor/vulkan/enums.odin | 218 ++++++++++++++++++ vendor/vulkan/structs.odin | 6 - 3 files changed, 317 insertions(+), 6 deletions(-) diff --git a/vendor/vulkan/_gen/create_vulkan_odin_wrapper.py b/vendor/vulkan/_gen/create_vulkan_odin_wrapper.py index 26bfc0a82..942f212dc 100644 --- a/vendor/vulkan/_gen/create_vulkan_odin_wrapper.py +++ b/vendor/vulkan/_gen/create_vulkan_odin_wrapper.py @@ -429,6 +429,100 @@ def parse_enums(f): f.write("{} :: distinct bit_set[{}; Flags]\n".format(flag.ljust(max_len), flag_name)) f.write("{} :: enum u32 {{}}\n".format(flag_name.ljust(max_len))) +def parse_fake_enums(f): + data = re.findall(r"static const Vk(\w+FlagBits2) VK_(\w+?) = (\w+);", src, re.S) + + data.sort(key=lambda x: x[0]) + + + + fake_enums = {} + + for type_name, name, value in data: + if type_name in fake_enums: + fake_enums[type_name].append((name,value)) + else: + fake_enums[type_name] = [(name, value)] + + + + for name in fake_enums.keys(): + flags_name = name.replace("FlagBits", "Flags") + enum_name = name.replace("FlagBits", "Flag") + f.write("{} :: distinct bit_set[{}; Flags64]\n".format(flags_name, enum_name)) + f.write("{} :: enum Flags64 {{\n".format(name.replace("FlagBits", "Flag"))) + + prefix = to_snake_case(name).upper() + suffix = None + for ext in ext_suffixes: + prefix_new = remove_suffix(prefix, "_"+ext) + assert suffix is None + if prefix_new != prefix: + suffix = "_"+ext + prefix = prefix_new + break + + + prefix = prefix.replace("_FLAG_BITS2", "_2") + prefix += "_" + + ff = [] + + groups = [] + flags = {} + + names_and_values = fake_enums[name] + + for name, value in names_and_values: + value = value.replace("ULL", "") + n = fix_enum_name(name, prefix, suffix, True) + try: + v = fix_enum_value(value, prefix, suffix, True) + except FlagError as e: + v = int(str(e)) + groups.append((n, v)) + continue + except IgnoreFlagError as e: + groups.append((n, 0)) + continue + + if n == v: + continue + try: + flags[int(v)] = n + except ValueError as e: + pass + + if v == "NONE": + continue + + ff.append((n, v)) + + max_flag_value = max([int(v) for n, v in ff if is_int(v)] + [0]) + max_group_value = max([int(v) for n, v in groups if is_int(v)] + [0]) + if max_flag_value < max_group_value: + if (1< Date: Sun, 2 Jul 2023 18:28:37 +0200 Subject: [PATCH 88/97] Remove whitespaces --- vendor/vulkan/_gen/create_vulkan_odin_wrapper.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/vendor/vulkan/_gen/create_vulkan_odin_wrapper.py b/vendor/vulkan/_gen/create_vulkan_odin_wrapper.py index 942f212dc..c3497aa8f 100644 --- a/vendor/vulkan/_gen/create_vulkan_odin_wrapper.py +++ b/vendor/vulkan/_gen/create_vulkan_odin_wrapper.py @@ -434,8 +434,6 @@ def parse_fake_enums(f): data.sort(key=lambda x: x[0]) - - fake_enums = {} for type_name, name, value in data: @@ -444,8 +442,6 @@ def parse_fake_enums(f): else: fake_enums[type_name] = [(name, value)] - - for name in fake_enums.keys(): flags_name = name.replace("FlagBits", "Flags") enum_name = name.replace("FlagBits", "Flag") @@ -521,10 +517,6 @@ def parse_fake_enums(f): f.write("}\n\n") - - - - def parse_structs(f): data = re.findall(r"typedef (struct|union) Vk(\w+?) {(.+?)} \w+?;", src, re.S) data += re.findall(r"typedef (struct|union) Std(\w+?) {(.+?)} \w+?;", src, re.S) From 23bf7973fa571088cb5080d78d04a798a2cc1e7c Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Sun, 2 Jul 2023 19:56:30 +0200 Subject: [PATCH 89/97] detect unix color support --- src/main.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index 2198b4536..d66ec5846 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2547,6 +2547,10 @@ gb_internal int strip_semicolons(Parser *parser) { return cast(int)failed; } +#if defined(GB_SYSTEM_OSX) || defined(GB_SYSTEM_UNIX) +#include +#endif + gb_internal void init_terminal(void) { build_context.has_ansi_terminal_colours = false; #if defined(GB_SYSTEM_WINDOWS) @@ -2558,6 +2562,18 @@ gb_internal void init_terminal(void) { build_context.has_ansi_terminal_colours = true; } } +#elif defined(GB_SYSTEM_OSX) || defined(GB_SYSTEM_UNIX) + FILE* file = popen("tput colors", "r"); + if (file) { + char buffer[20]; + if (fgets(&buffer[0], 20, file)) { + u64 colors = gb_str_to_u64(buffer, nullptr, 10); + if (colors >= 8) { + build_context.has_ansi_terminal_colours = true; + } + } + pclose(file); + } #endif if (!build_context.has_ansi_terminal_colours) { From f4e87c9720fada536c756110f900c4317fc1c1ce Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Sun, 2 Jul 2023 20:20:10 +0200 Subject: [PATCH 90/97] ignore stderr in tput call --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index d66ec5846..80a6c819c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2563,7 +2563,7 @@ gb_internal void init_terminal(void) { } } #elif defined(GB_SYSTEM_OSX) || defined(GB_SYSTEM_UNIX) - FILE* file = popen("tput colors", "r"); + FILE* file = popen("tput colors 2>/dev/null", "r"); if (file) { char buffer[20]; if (fgets(&buffer[0], 20, file)) { From f68a3639b4ce2ba8fe90f6907e162ff0a5bbadc7 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Sun, 2 Jul 2023 21:11:19 +0200 Subject: [PATCH 91/97] use more common detection method and add FORCE_COLOR and NO_COLOR support --- src/main.cpp | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 80a6c819c..0bd45b1eb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2547,12 +2547,24 @@ gb_internal int strip_semicolons(Parser *parser) { return cast(int)failed; } -#if defined(GB_SYSTEM_OSX) || defined(GB_SYSTEM_UNIX) -#include -#endif - gb_internal void init_terminal(void) { build_context.has_ansi_terminal_colours = false; + + gbAllocator a = heap_allocator(); + + char const *no_color = gb_get_env("NO_COLOR", a); + defer (gb_free(a, cast(void *)no_color)); + if (no_color != nullptr) { + return; + } + + char const *force_color = gb_get_env("FORCE_COLOR", a); + defer (gb_free(a, cast(void *)force_color)); + if (force_color != nullptr) { + build_context.has_ansi_terminal_colours = true; + return; + } + #if defined(GB_SYSTEM_WINDOWS) HANDLE hnd = GetStdHandle(STD_ERROR_HANDLE); DWORD mode = 0; @@ -2563,21 +2575,15 @@ gb_internal void init_terminal(void) { } } #elif defined(GB_SYSTEM_OSX) || defined(GB_SYSTEM_UNIX) - FILE* file = popen("tput colors 2>/dev/null", "r"); - if (file) { - char buffer[20]; - if (fgets(&buffer[0], 20, file)) { - u64 colors = gb_str_to_u64(buffer, nullptr, 10); - if (colors >= 8) { - build_context.has_ansi_terminal_colours = true; - } - } - pclose(file); + char const *term_ = gb_get_env("TERM", a); + defer (gb_free(a, cast(void *)term_)); + String term = make_string_c(term_); + if (!str_eq(term, str_lit("dumb")) && isatty(STDERR_FILENO)) { + build_context.has_ansi_terminal_colours = true; } #endif if (!build_context.has_ansi_terminal_colours) { - gbAllocator a = heap_allocator(); char const *odin_terminal_ = gb_get_env("ODIN_TERMINAL", a); defer (gb_free(a, cast(void *)odin_terminal_)); String odin_terminal = make_string_c(odin_terminal_); From 49058620f090062e1e1a7b3158b22d17fe3e3dfa Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Sun, 2 Jul 2023 21:25:48 +0200 Subject: [PATCH 92/97] fix spacing --- src/main.cpp | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 0bd45b1eb..db2702b19 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2550,20 +2550,20 @@ gb_internal int strip_semicolons(Parser *parser) { gb_internal void init_terminal(void) { build_context.has_ansi_terminal_colours = false; - gbAllocator a = heap_allocator(); + gbAllocator a = heap_allocator(); - char const *no_color = gb_get_env("NO_COLOR", a); - defer (gb_free(a, cast(void *)no_color)); - if (no_color != nullptr) { - return; - } + char const *no_color = gb_get_env("NO_COLOR", a); + defer (gb_free(a, cast(void *)no_color)); + if (no_color != nullptr) { + return; + } - char const *force_color = gb_get_env("FORCE_COLOR", a); - defer (gb_free(a, cast(void *)force_color)); - if (force_color != nullptr) { - build_context.has_ansi_terminal_colours = true; - return; - } + char const *force_color = gb_get_env("FORCE_COLOR", a); + defer (gb_free(a, cast(void *)force_color)); + if (force_color != nullptr) { + build_context.has_ansi_terminal_colours = true; + return; + } #if defined(GB_SYSTEM_WINDOWS) HANDLE hnd = GetStdHandle(STD_ERROR_HANDLE); @@ -2575,12 +2575,12 @@ gb_internal void init_terminal(void) { } } #elif defined(GB_SYSTEM_OSX) || defined(GB_SYSTEM_UNIX) - char const *term_ = gb_get_env("TERM", a); - defer (gb_free(a, cast(void *)term_)); - String term = make_string_c(term_); - if (!str_eq(term, str_lit("dumb")) && isatty(STDERR_FILENO)) { - build_context.has_ansi_terminal_colours = true; - } + char const *term_ = gb_get_env("TERM", a); + defer (gb_free(a, cast(void *)term_)); + String term = make_string_c(term_); + if (!str_eq(term, str_lit("dumb")) && isatty(STDERR_FILENO)) { + build_context.has_ansi_terminal_colours = true; + } #endif if (!build_context.has_ansi_terminal_colours) { From fc81008ab5e8568eb7a76f6311919a44b9e4d8a4 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Sun, 2 Jul 2023 22:18:25 +0200 Subject: [PATCH 93/97] Fix #2615: can't iterate untyped string --- src/check_stmt.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index 964ff1842..a15977b7d 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -1528,7 +1528,7 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) Type *t = base_type(type_deref(operand.type)); switch (t->kind) { case Type_Basic: - if (t->Basic.kind == Basic_string) { + if (t->Basic.kind == Basic_string || t->Basic.kind == Basic_UntypedString) { is_possibly_addressable = false; array_add(&vals, t_rune); array_add(&vals, t_int); From a3e2d90f4cc591b7c2a3957aba4dc4a56451d372 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Sun, 2 Jul 2023 22:55:28 +0200 Subject: [PATCH 94/97] add test --- tests/issues/run.bat | 1 + tests/issues/run.sh | 1 + tests/issues/test_issue_2615.odin | 19 +++++++++++++++++++ 3 files changed, 21 insertions(+) create mode 100644 tests/issues/test_issue_2615.odin diff --git a/tests/issues/run.bat b/tests/issues/run.bat index bf49bc85b..105d474e3 100644 --- a/tests/issues/run.bat +++ b/tests/issues/run.bat @@ -13,6 +13,7 @@ set COMMON=-collection:tests=..\.. ..\..\..\odin test ..\test_issue_2087.odin %COMMON% -file || exit /b ..\..\..\odin build ..\test_issue_2113.odin %COMMON% -file -debug || exit /b ..\..\..\odin test ..\test_issue_2466.odin %COMMON% -file || exit /b +..\..\..\odin test ..\test_issue_2615.odin %COMMON% -file || exit /b @echo off diff --git a/tests/issues/run.sh b/tests/issues/run.sh index b0c43572f..c4c53e7e1 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -16,6 +16,7 @@ $ODIN test ../test_issue_2056.odin $COMMON -file $ODIN test ../test_issue_2087.odin $COMMON -file $ODIN build ../test_issue_2113.odin $COMMON -file -debug $ODIN test ../test_issue_2466.odin $COMMON -file +$ODIN test ../test_issue_2615.odin $COMMON -file if [[ $($ODIN build ../test_issue_2395.odin $COMMON -file 2>&1 >/dev/null | grep -c "$NO_NIL_ERR") -eq 2 ]] ; then echo "SUCCESSFUL 1/1" else diff --git a/tests/issues/test_issue_2615.odin b/tests/issues/test_issue_2615.odin new file mode 100644 index 000000000..229e5c35b --- /dev/null +++ b/tests/issues/test_issue_2615.odin @@ -0,0 +1,19 @@ +// Tests issue https://github.com/odin-lang/Odin/issues/2615 +// Cannot iterate over string literals +package test_issues + +import "core:testing" + +@(test) +test_cannot_iterate_over_string_literal :: proc(t: ^testing.T) { + for c, i in "fo世" { + switch i { + case 0: + testing.expect_value(t, c, 'f') + case 1: + testing.expect_value(t, c, 'o') + case 2: + testing.expect_value(t, c, '世') + } + } +} From 38d71e668d03040632e6e19f592cd63f7c5b4bd4 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Mon, 3 Jul 2023 19:56:56 +0200 Subject: [PATCH 95/97] add `ADDRESS_FAMILY` to darwin (currently only available in linux) --- core/os/os_darwin.odin | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/core/os/os_darwin.odin b/core/os/os_darwin.odin index ab05756ae..d8ba40fd0 100644 --- a/core/os/os_darwin.odin +++ b/core/os/os_darwin.odin @@ -314,15 +314,16 @@ Dirent :: struct { Dir :: distinct rawptr // DIR* +ADDRESS_FAMILY :: c.char SOCKADDR :: struct #packed { len: c.char, - family: c.char, + family: ADDRESS_FAMILY, sa_data: [14]c.char, } SOCKADDR_STORAGE_LH :: struct #packed { len: c.char, - family: c.char, + family: ADDRESS_FAMILY, __ss_pad1: [6]c.char, __ss_align: i64, __ss_pad2: [112]c.char, @@ -330,7 +331,7 @@ SOCKADDR_STORAGE_LH :: struct #packed { sockaddr_in :: struct #packed { sin_len: c.char, - sin_family: c.char, + sin_family: ADDRESS_FAMILY, sin_port: u16be, sin_addr: in_addr, sin_zero: [8]c.char, @@ -338,7 +339,7 @@ sockaddr_in :: struct #packed { sockaddr_in6 :: struct #packed { sin6_len: c.char, - sin6_family: c.char, + sin6_family: ADDRESS_FAMILY, sin6_port: u16be, sin6_flowinfo: c.uint, sin6_addr: in6_addr, From 1ff6212ffa32c42be86ee80292401f860435b443 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 7 Jul 2023 22:28:31 +0100 Subject: [PATCH 96/97] Always call `lb_run_remove_dead_instruction_pass` to fix `-debug` issues --- src/llvm_backend_general.cpp | 5 +++-- src/llvm_backend_opt.cpp | 10 ---------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 017eeca2e..676069120 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -3004,8 +3004,9 @@ gb_internal lbAddr lb_add_local(lbProcedure *p, Type *type, Entity *e, bool zero LLVMPositionBuilderAtEnd(p->builder, p->decl_block->block); char const *name = ""; - if (e != nullptr) { - // name = alloc_cstring(permanent_allocator(), e->token.string); + if (e != nullptr && e->token.string.len > 0 && e->token.string != "_") { + // NOTE(bill): for debugging purposes only + name = alloc_cstring(permanent_allocator(), e->token.string); } LLVMTypeRef llvm_type = lb_type(p->module, type); diff --git a/src/llvm_backend_opt.cpp b/src/llvm_backend_opt.cpp index 141ee88c7..54e667a0b 100644 --- a/src/llvm_backend_opt.cpp +++ b/src/llvm_backend_opt.cpp @@ -375,16 +375,6 @@ gb_internal void lb_run_function_pass_manager(LLVMPassManagerRef fpm, lbProcedur return; } LLVMRunFunctionPassManager(fpm, p->value); - switch (pass_manager_kind) { - case lbFunctionPassManager_none: - return; - case lbFunctionPassManager_default: - case lbFunctionPassManager_default_without_memcpy: - if (build_context.optimization_level < 0) { - return; - } - break; - } // NOTE(bill): LLVMAddDCEPass doesn't seem to be exported in the official DLL's for LLVM // which means we cannot rely upon it // This is also useful for read the .ll for debug purposes because a lot of instructions From 8f4c59b0801cc5ffa9ea10f72c29a94245f0bbb1 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 7 Jul 2023 22:29:53 +0100 Subject: [PATCH 97/97] Add `core:math/cmpl` to all_main.odin --- examples/all/all_main.odin | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/all/all_main.odin b/examples/all/all_main.odin index 60850c806..c6b727e42 100644 --- a/examples/all/all_main.odin +++ b/examples/all/all_main.odin @@ -87,6 +87,7 @@ import hlm "core:math/linalg/hlsl" import noise "core:math/noise" import rand "core:math/rand" import ease "core:math/ease" +import cmplx "core:math/cmplx" import mem "core:mem" import virtual "core:mem/virtual" @@ -206,6 +207,7 @@ _ :: hlm _ :: noise _ :: rand _ :: ease +_ :: cmplx _ :: mem _ :: virtual _ :: ast