////////////////////////////////////////////////
////////////////////////////////////////////////
///////////   BASE IMPLEMENTATION   ////////////
////////////////////////////////////////////////
////////////////////////////////////////////////

////////////////////////////////
// Functions: Numerical/Math

// infinity

MR4TH_SYMBOL B32
is_inf_or_nan_F32(F32 x){
  U32 u = *(U32*)&x;
  B32 result = ((u&0x7f800000) == 0x7f800000);
  return(result);
}

MR4TH_SYMBOL B32
is_inf_or_nan_F64(F64 x){
  U64 u = *(U64*)&x;
  B64 result = ((u&0x7ff0000000000000) == 0x7ff0000000000000);
  return(result);
}

// float signs, rounding, and modulus

MR4TH_SYMBOL F32
abs_F32(F32 x){
  union{ F32 f; U32 u; } r;
  r.f = x;
  r.u &= 0x7fffffff;
  return(r.f);
}

MR4TH_SYMBOL F32
sign_F32(F32 x){
  union{ F32 f; U32 u; } r;
  r.f = x;
  F32 result = (r.u&0x80000000)?-1.f:1.f;
  return(result);
}

MR4TH_SYMBOL F32
trunc_F32(F32 x){
  return((x < 0x1p23)?(F32)(S32)x:x);
}

MR4TH_SYMBOL F32
floor_F32(F32 x){
  F32 r = trunc_F32(x);
  if (x < r){
    r -= 1.f;
  }
  return(r);
}

MR4TH_SYMBOL F32
ceil_F32(F32 x){
  F32 r = trunc_F32(x);
  if (x > r){
    r += 1.f;
  }
  return(r);
}

MR4TH_SYMBOL F32
nearest_F32(F32 x){
  F32 r = floor_F32(x + 0.5f);
  return(r);
}

MR4TH_SYMBOL F32
mod_F32(F32 x, F32 m){
  Assert(m > 0.f);
  //                               +   |         -
  F32 q = (x/m);       //        (x/m) |       (x/m)
  S32 q_s32 = (S32)q;  //   floor(x/m) |   ceil(x/m)
  F32 r = m*(F32)q_s32;// m*floor(x/m) | m*ceil(x/m)
  //                        <= x       |   >= x
  F32 d = x - r;
  if (d < 0){
    d += m;
  }
  return(d);
}

MR4TH_SYMBOL F32
frac_F32(F32 x){
  //                           +  |      -
  F32 q = (x);        //      (x) |     (x)
  S32 q_s32 = (S32)q; // floor(x) | ceil(x)
  F32 r = (F32)q_s32; // floor(x) | ceil(x)
  //                       <= x   |  >= x
  F32 d = x - r;
  return(d);
}

MR4TH_SYMBOL F64
abs_F64(F64 x){
  union{ F64 f; U64 u; } r;
  r.f = x;
  r.u &= 0x7fffffffffffffff;
  return(r.f);
}

MR4TH_SYMBOL F64
sign_F64(F64 x){
  union{ F64 f; U32 u; } r;
  r.f = x;
  F64 result = (r.u&0x8000000000000000)?-1.:1.;
  return(result);
}

MR4TH_SYMBOL F64
trunc_F64(F64 x){
  return((x < 0x1p52)?(F64)(S64)x:x);
}

MR4TH_SYMBOL F64
floor_F64(F64 x){
  F64 r = trunc_F64(x);
  if (x < r){
    r -= 1.f;
  }
  return(r);
}

MR4TH_SYMBOL F64
ceil_F64(F64 x){
  F64 r = trunc_F64(x);
  if (x > r){
    r += 1.f;
  }
  return(r);
}

MR4TH_SYMBOL F64
nearest_F64(F64 x){
  F64 r = floor_F64(x + 0.5f);
  return(r);
}

MR4TH_SYMBOL F64
mod_F64(F64 x, F64 m){
  Assert(m > 0.f);
  //                               +   |         -
  F64 q = (x/m);       //        (x/m) |       (x/m)
  S64 q_s64 = (S64)q;  //   floor(x/m) |   ceil(x/m)
  F64 r = m*(F64)q_s64;// m*floor(x/m) | m*ceil(x/m)
  //                        <= x       |   >= x
  F64 d = x - r;
  if (d < 0){
    d += m;
  }
  return(d);
}

MR4TH_SYMBOL F64
frac_F64(F64 x){
  //                           +  |      -
  F64 q = (x);        //      (x) |     (x)
  S64 q_s64 = (S64)q; // floor(x) | ceil(x)
  F64 r = (F64)q_s64; // floor(x) | ceil(x)
  //                       <= x   |  >= x
  F64 d = x - r;
  return(d);
}

// transcendental functions

#include <math.h>

MR4TH_SYMBOL F32
sin_F32(F32 x){
  return(sinf(x*tau_F32));
}

MR4TH_SYMBOL F32
cos_F32(F32 x){
  return(cosf(x*tau_F32));
}

MR4TH_SYMBOL F32
tan_F32(F32 x){
  return(tanf(x*tau_F32));
}

MR4TH_SYMBOL F32
atan_F32(F32 x){
  return(atanf(x)/tau_F32);
}

MR4TH_SYMBOL F32
atan2_F32(F32 x, F32 y){
  return(atan2f(y, x)/tau_F32);
}

MR4TH_SYMBOL F32
sqrt_F32(F32 x){
  return(sqrtf(x));
}

MR4TH_SYMBOL F32
inv_sqrt_F32(F32 x){
  return(1.f/sqrtf(x));
}

MR4TH_SYMBOL F32
ln_F32(F32 x){
  return(logf(x));
}

MR4TH_SYMBOL F32
pow_F32(F32 base, F32 x){
  return(powf(base, x));
}

MR4TH_SYMBOL F64
sin_F64(F64 x){
  return(sin(x*tau_F64));
}

MR4TH_SYMBOL F64
cos_F64(F64 x){
  return(cos(x*tau_F64));
}

MR4TH_SYMBOL F64
tan_F64(F64 x){
  return(tan(x*tau_F64));
}

MR4TH_SYMBOL F64
atan_F64(F64 x){
  return(atan(x)/tau_F64);
}

MR4TH_SYMBOL F64
atan2_F64(F64 x, F64 y){
  return(atan2(x, y)/tau_F64);
}

MR4TH_SYMBOL F64
sqrt_F64(F64 x){
  return(sqrt(x));
}

MR4TH_SYMBOL F64
inv_sqrt_F64(F64 x){
  return(1.f/sqrt(x));
}

MR4TH_SYMBOL F64
ln_F64(F64 x){
  return(log(x));
}

MR4TH_SYMBOL F64
pow_F64(F64 base, F64 x){
  return(powf(base, x));
}

// linear interpolation

MR4TH_SYMBOL F32
lerp(F32 a, F32 t, F32 b){
  F32 x = a + (b - a)*t;
  return(x);
}

MR4TH_SYMBOL F32
unlerp(F32 a, F32 x, F32 b){
  F32 t = 0.f;
  if (a != b){
    t = (x - a)/(b - a);
  }
  return(t);
}

// integer rounding

MR4TH_SYMBOL U32
next_pow2_U32(U32 x){
  U32 result = x;
  if (result == 0){
    result = 1;
  }
  else{
    // TODO(allen): speed up with bit scan intrinsic
    result -= 1;
    result |= (result >>  1);
    result |= (result >>  2);
    result |= (result >>  4);
    result |= (result >>  8);
    result |= (result >> 16);
    result += 1;
  }
  return(result);
}

MR4TH_SYMBOL S32
sign_extend_S32(S32 x, U32 bitidx){
  U32 shiftn = (31 - bitidx);
  x = x << shiftn;
  x = x >> shiftn;
  return(x);
}

MR4TH_SYMBOL U64
next_pow2_U64(U32 x){
  U64 result = x;
  if (result == 0){
    result = 1;
  }
  else{
    // TODO(allen): speed up with bit scan intrinsic
    result -= 1;
    result |= (result >>  1);
    result |= (result >>  2);
    result |= (result >>  4);
    result |= (result >>  8);
    result |= (result >> 16);
    result |= (result >> 32);
    result += 1;
  }
  return(result);
}

MR4TH_SYMBOL S64
sign_extend_S64(S64 x, U32 bitidx){
  U32 shiftn = (63 - bitidx);
  x = x << shiftn;
  x = x >> shiftn;
  return(x);
}

////////////////////////////////
// Functions: Compound Types

// 2d vectors

MR4TH_SYMBOL V2F32
v2_polar(F32 theta, F32 radius){
  V2F32 result = {radius*cos_F32(theta), radius*sin_F32(theta)};
  return(result);
}

MR4TH_SYMBOL F32
v2_angle_from_v(V2F32 v){
  F32 result = atan2_F32(v.x, v.y);
  return(result);
}

MR4TH_SYMBOL V2F32
v2_unit(V2F32 v){
  F32 recip = inv_sqrt_F32(v.x*v.x + v.y*v.y);
  V2F32 r = {0};
  r.x = v.x*recip;
  r.y = v.y*recip;
  return(r);
}

MR4TH_SYMBOL V2F32
v2_from_v2s32(V2S32 v){
  V2F32 r = {v.x, v.y};
  return(r);
}

// 3d vectors

MR4TH_SYMBOL V3F32
v3_spherical(F32 theta_xz, F32 theta_yz, F32 radius){
  F32 sxz = sin_F32(theta_xz);
  F32 syz = sin_F32(theta_yz);
  F32 cxz = cos_F32(theta_xz);
  F32 cyz = cos_F32(theta_yz);
  
  V3F32 result = {0};
  result.x = radius*sxz;
  result.y = radius*syz;
  result.z = radius*cxz*cyz;
  return(result);
}

MR4TH_SYMBOL V3F32
v3_cross(V3F32 a, V3F32 b){
  V3F32 result = {
    a.y*b.z - a.z*b.y,
    a.z*b.x - a.x*b.z,
    a.x*b.y - a.y*b.x,
  };
  return(result);
}

MR4TH_SYMBOL V3F32
v3_unit(V3F32 v){
  F32 recip = inv_sqrt_F32(v.x*v.x + v.y*v.y + v.z*v.z);
  V3F32 r = {0};
  r.x = v.x*recip;
  r.y = v.y*recip;
  r.z = v.z*recip;
  return(r);
}

// 4x4 matrix

MR4TH_SYMBOL B32
mat4x4_inv(F32 *in, F32 *out){
  /* credit: https://github.com/niswegmann/small-matrix-inverse/tree/master */
  
  out[0] =
    + in[ 5]*in[10]*in[15]
    - in[ 5]*in[11]*in[14]
    - in[ 9]*in[ 6]*in[15]
    + in[ 9]*in[ 7]*in[14]
    + in[13]*in[ 6]*in[11]
    - in[13]*in[ 7]*in[10];
  
  out[1] =
    - in[ 1]*in[10]*in[15]
    + in[ 1]*in[11]*in[14]
    + in[ 9]*in[ 2]*in[15]
    - in[ 9]*in[ 3]*in[14]
    - in[13]*in[ 2]*in[11]
    + in[13]*in[ 3]*in[10];
  
  out[2] =
    + in[ 1]*in[ 6]*in[15]
    - in[ 1]*in[ 7]*in[14]
    - in[ 5]*in[ 2]*in[15]
    + in[ 5]*in[ 3]*in[14]
    + in[13]*in[ 2]*in[ 7]
    - in[13]*in[ 3]*in[ 6];
  
  out[3] =
    - in[ 1]*in[ 6]*in[11]
    + in[ 1]*in[ 7]*in[10]
    + in[ 5]*in[ 2]*in[11]
    - in[ 5]*in[ 3]*in[10]
    - in[ 9]*in[ 2]*in[ 7]
    + in[ 9]*in[ 3]*in[ 6];
  
  out[4] =
    - in[ 4]*in[10]*in[15]
    + in[ 4]*in[11]*in[14]
    + in[ 8]*in[ 6]*in[15]
    - in[ 8]*in[ 7]*in[14]
    - in[12]*in[ 6]*in[11]
    + in[12]*in[ 7]*in[10];
  
  out[5] =
    + in[ 0]*in[10]*in[15]
    - in[ 0]*in[11]*in[14]
    - in[ 8]*in[ 2]*in[15]
    + in[ 8]*in[ 3]*in[14]
    + in[12]*in[ 2]*in[11]
    - in[12]*in[ 3]*in[10];
  
  out[6] =
    - in[ 0]*in[ 6]*in[15]
    + in[ 0]*in[ 7]*in[14]
    + in[ 4]*in[ 2]*in[15]
    - in[ 4]*in[ 3]*in[14]
    - in[12]*in[ 2]*in[ 7]
    + in[12]*in[ 3]*in[ 6];
  
  out[7] =
    + in[ 0]*in[ 6]*in[11]
    - in[ 0]*in[ 7]*in[10]
    - in[ 4]*in[ 2]*in[11]
    + in[ 4]*in[ 3]*in[10]
    + in[ 8]*in[ 2]*in[ 7]
    - in[ 8]*in[ 3]*in[ 6];
  
  out[8] =
    + in[ 4]*in[ 9]*in[15]
    - in[ 4]*in[11]*in[13]
    - in[ 8]*in[ 5]*in[15]
    + in[ 8]*in[ 7]*in[13]
    + in[12]*in[ 5]*in[11]
    - in[12]*in[ 7]*in[ 9];
  
  out[9] =
    - in[ 0]*in[ 9]*in[15]
    + in[ 0]*in[11]*in[13]
    + in[ 8]*in[ 1]*in[15]
    - in[ 8]*in[ 3]*in[13]
    - in[12]*in[ 1]*in[11]
    + in[12]*in[ 3]*in[ 9];
  
  out[10] =
    + in[ 0]*in[ 5]*in[15]
    - in[ 0]*in[ 7]*in[13]
    - in[ 4]*in[ 1]*in[15]
    + in[ 4]*in[ 3]*in[13]
    + in[12]*in[ 1]*in[ 7]
    - in[12]*in[ 3]*in[ 5];
  
  out[11] =
    - in[ 0]*in[ 5]*in[11]
    + in[ 0]*in[ 7]*in[ 9]
    + in[ 4]*in[ 1]*in[11]
    - in[ 4]*in[ 3]*in[ 9]
    - in[ 8]*in[ 1]*in[ 7]
    + in[ 8]*in[ 3]*in[ 5];
  
  out[12] =
    - in[ 4]*in[ 9]*in[14]
    + in[ 4]*in[10]*in[13]
    + in[ 8]*in[ 5]*in[14]
    - in[ 8]*in[ 6]*in[13]
    - in[12]*in[ 5]*in[10]
    + in[12]*in[ 6]*in[ 9];
  
  out[13] =
    + in[ 0]*in[ 9]*in[14]
    - in[ 0]*in[10]*in[13]
    - in[ 8]*in[ 1]*in[14]
    + in[ 8]*in[ 2]*in[13]
    + in[12]*in[ 1]*in[10]
    - in[12]*in[ 2]*in[ 9];
  
  out[14] =
    - in[ 0]*in[ 5]*in[14]
    + in[ 0]*in[ 6]*in[13]
    + in[ 4]*in[ 1]*in[14]
    - in[ 4]*in[ 2]*in[13]
    - in[12]*in[ 1]*in[ 6]
    + in[12]*in[ 2]*in[ 5];
  
  out[15] =
    + in[ 0]*in[ 5]*in[10]
    - in[ 0]*in[ 6]*in[ 9]
    - in[ 4]*in[ 1]*in[10]
    + in[ 4]*in[ 2]*in[ 9]
    + in[ 8]*in[ 1]*in[ 6]
    - in[ 8]*in[ 2]*in[ 5];
  
  F32 det = in[0]*out[0] + in[1]*out[4] + in[2]*out[8] + in[3]*out[12];
  
  B32 result = 0;
  if (det != 0.f){
    F32 invdet = 1.f/det;
    for (U32 i = 0; i < 16; i += 1){
      out[i] *= invdet;
    }
    result = 1;
  }
  
  return(result);
}

MR4TH_SYMBOL void
mat4x4_mul(F32 *a, F32 *b, F32 *out){
  out[ 0] = a[ 0]*b[ 0] + a[ 1]*b[ 4] + a[ 2]*b[ 8] + a[ 3]*b[12];
  out[ 1] = a[ 0]*b[ 1] + a[ 1]*b[ 5] + a[ 2]*b[ 9] + a[ 3]*b[13];
  out[ 2] = a[ 0]*b[ 2] + a[ 1]*b[ 6] + a[ 2]*b[10] + a[ 3]*b[14];
  out[ 3] = a[ 0]*b[ 3] + a[ 1]*b[ 7] + a[ 2]*b[11] + a[ 3]*b[15];
  out[ 4] = a[ 4]*b[ 0] + a[ 5]*b[ 4] + a[ 6]*b[ 8] + a[ 7]*b[12];
  out[ 5] = a[ 4]*b[ 1] + a[ 5]*b[ 5] + a[ 6]*b[ 9] + a[ 7]*b[13];
  out[ 6] = a[ 4]*b[ 2] + a[ 5]*b[ 6] + a[ 6]*b[10] + a[ 7]*b[14];
  out[ 7] = a[ 4]*b[ 3] + a[ 5]*b[ 7] + a[ 6]*b[11] + a[ 7]*b[15];
  out[ 8] = a[ 8]*b[ 0] + a[ 9]*b[ 4] + a[10]*b[ 8] + a[11]*b[12];
  out[ 9] = a[ 8]*b[ 1] + a[ 9]*b[ 5] + a[10]*b[ 9] + a[11]*b[13];
  out[10] = a[ 8]*b[ 2] + a[ 9]*b[ 6] + a[10]*b[10] + a[11]*b[14];
  out[11] = a[ 8]*b[ 3] + a[ 9]*b[ 7] + a[10]*b[11] + a[11]*b[15];
  out[12] = a[12]*b[ 0] + a[13]*b[ 4] + a[14]*b[ 8] + a[15]*b[12];
  out[13] = a[12]*b[ 1] + a[13]*b[ 5] + a[14]*b[ 9] + a[15]*b[13];
  out[14] = a[12]*b[ 2] + a[13]*b[ 6] + a[14]*b[10] + a[15]*b[14];
  out[15] = a[12]*b[ 3] + a[13]*b[ 7] + a[14]*b[11] + a[15]*b[15];
}

////////////////////////////////
// Functions: Memory Operations

MR4TH_SYMBOL void
memory_zero(void *ptr, U64 size){
  U64  z64 = size/8;
  U64  z8  = size%8;
  
  U64 *p64 = (U64*)ptr;
  for (;z64 > 0;){
    *p64 = 0;
    p64 += 1;
    z64 -= 1;
  }
  U8 *p8 = (U8*)p64;
  for (;z8 > 0;){
    *p8 = 0;
    p8 += 1;
    z8 -= 1;
  }
}

MR4TH_SYMBOL void
memory_fill(void *ptr, U64 size, U8 fillbyte){
  U64 fillqword = fillbyte;
  {
    fillqword |= fillqword << 8;
    fillqword |= fillqword << 16;
    fillqword |= fillqword << 32;
  }
  
  U64  z64 = size/8;
  U64  z8  = size%8;
  
  U64 *p64 = (U64*)ptr;
  for (;z64 > 0;){
    *p64 = fillqword;
    p64 += 1;
    z64 -= 1;
  }
  U8 *p8 = (U8*)p64;
  for (;z8 > 0;){
    *p8 = fillbyte;
    p8 += 1;
    z8 -= 1;
  }
}

MR4TH_SYMBOL B32
memory_match(void *a, void *b, U64 size){
  U64  z64 = size/8;
  U64  z8  = size%8;
  
  U64 *a64 = (U64*)a;
  U64 *b64 = (U64*)b;
  for (;z64 > 0;){
    if (*a64 != *b64){
      return(0);
    }
    a64 += 1;
    b64 += 1;
    z64 -= 1;
  }
  
  U8 *a8 = (U8*)a64;
  U8 *b8 = (U8*)b64;
  for (;z8 > 0;){
    if (*a8 != *b8){
      return(0);
    }
    a8 += 1;
    b8 += 1;
    z8 -= 1;
  }
  
  return(1);
}

MR4TH_SYMBOL void*
memory_move(void *dst, void *src, U64 size){
  U64  z64 = size/8;
  U64  z8  = size%8;
  
  // backwards
  if ((U8*)src < (U8*)dst){
    U8 *dst8 = ((U8*)dst) + size;
    U8 *src8 = ((U8*)src) + size;
    for (;z8 > 0;){
      dst8 -= 1;
      src8 -= 1;
      *dst8 = *src8;
      z8 -= 1;
    }
    U64 *dst64 = (U64*)dst8;
    U64 *src64 = (U64*)src8;
    for (;z64 > 0;){
      dst64 -= 1;
      src64 -= 1;
      *dst64 = *src64;
      z64 -= 1;
    }
  }
  
  // forwards
  else if ((U8*)src > (U8*)dst){
    U64 *dst64 = (U64*)dst;
    U64 *src64 = (U64*)src;
    for (;z64 > 0;){
      *dst64 = *src64;
      dst64 += 1;
      src64 += 1;
      z64 -= 1;
    }
    U8 *dst8 = (U8*)dst64;
    U8 *src8 = (U8*)src64;
    for (;z8 > 0;){
      *dst8 = *src8;
      dst8 += 1;
      src8 += 1;
      z8 -= 1;
    }
  }
  
  return(dst);
}

////////////////////////////////
// Functions: Symbolic Constants

MR4TH_SYMBOL OperatingSystem
operating_system_from_context(void){
  OperatingSystem result = OperatingSystem_Null;
#if OS_WINDOWS
  result = OperatingSystem_Windows;
#elif OS_LINUX
  result = OperatingSystem_Linux;
#elif OS_MAC
  result = OperatingSystem_Mac;
#endif
  return(result);
}

MR4TH_SYMBOL Architecture
architecture_from_context(void){
  Architecture result = Architecture_Null;
#if ARCH_X64
  result = Architecture_X64;
#elif ARCH_X86
  result = Architecture_X86;
#elif ARCH_ARM
  result = Architecture_Arm;
#elif ARCH_ARM64
  result = Architecture_Arm64;
#endif
  return(result);
}

MR4TH_SYMBOL char*
string_from_operating_system(OperatingSystem os){
  char *result = "(null)";
  switch (os){
    case OperatingSystem_Windows:
    {
      result = "windows";
    }break;
    case OperatingSystem_Linux:
    {
      result = "linux";
    }break;
    case OperatingSystem_Mac:
    {
      result = "mac";
    }break;
  }
  return(result);
}

MR4TH_SYMBOL char*
string_from_architecture(Architecture arch){
  char *result = "(null)";
  switch (arch){
    case Architecture_X64:
    {
      result = "x64";
    }break;
    case Architecture_X86:
    {
      result = "x86";
    }break;
    case Architecture_Arm:
    {
      result = "arm";
    }break;
    case Architecture_Arm64:
    {
      result = "armm64";
    }break;
  }
  return(result);
}

MR4TH_SYMBOL char*
string_from_month(Month month){
  char *result = "(null)";
  switch (month){
    case Month_Jan:
    {
      result = "jan";
    }break;
    case Month_Feb:
    {
      result = "feb";
    }break;
    case Month_Mar:
    {
      result = "mar";
    }break;
    case Month_Apr:
    {
      result = "apr";
    }break;
    case Month_May:
    {
      result = "may";
    }break;
    case Month_Jun:
    {
      result = "jun";
    }break;
    case Month_Jul:
    {
      result = "jul";
    }break;
    case Month_Aug:
    {
      result = "aug";
    }break;
    case Month_Sep:
    {
      result = "sep";
    }break;
    case Month_Oct:
    {
      result = "oct";
    }break;
    case Month_Nov:
    {
      result = "nov";
    }break;
    case Month_Dec:
    {
      result = "dec";
    }break;
  }
  return(result);
}

MR4TH_SYMBOL char*
string_from_day_of_week(DayOfWeek day_of_week){
  char *result = "(null)";
  switch (day_of_week){
    case DayOfWeek_Sunday:
    {
      result = "sunday";
    }break;
    case DayOfWeek_Monday:
    {
      result = "monday";
    }break;
    case DayOfWeek_Tuesday:
    {
      result = "tuesday";
    }break;
    case DayOfWeek_Wednesday:
    {
      result = "wednesday";
    }break;
    case DayOfWeek_Thursday:
    {
      result = "thursday";
    }break;
    case DayOfWeek_Friday:
    {
      result = "friday";
    }break;
    case DayOfWeek_Saturday:
    {
      result = "saturday";
    }break;
  }
  return(result);
}

////////////////////////////////
// Functions: Time

MR4TH_SYMBOL DenseTime
dense_time_from_date_time(DateTime *in){
  U32 year_encoded = (U32)((S32)in->year + 0x8000);
  DenseTime result = 0;
  result += year_encoded;
  result *= 12;
  result += (in->mon - 1);
  result *= 31;
  result += (in->day - 1);
  result *= 24;
  result += in->hour;
  result *= 60;
  result += in->min;
  result *= 61;
  result += in->sec;
  result *= 1000;
  result += in->msec;
  return(result);
}

MR4TH_SYMBOL DateTime
date_time_from_dense_time(DenseTime in){
  DateTime result = {0};
  result.msec = in%1000;
  in /= 1000;
  result.sec = in%61;
  in /= 61;
  result.min = in%60;
  in /= 60;
  result.hour = in%24;
  in /= 24;
  result.day = (in%31) + 1;
  in /= 31;
  result.mon = (in%12) + 1;
  in /= 12;
  S32 year_encoded = (S32)in;
  result.year = (year_encoded - 0x8000);
  return(result);
}

////////////////////////////////
// Functions: Arena

// arena core implementation

#define MR4TH_MEM_INTERNAL_MIN_SIZE AlignUpPow2(sizeof(Arena), MR4TH_MEM_MAX_ALIGN)

#if !MR4TH_PLUGIN_MODE

// arena pre-requisites

StaticAssert(sizeof(Arena) <= MR4TH_MEM_INITIAL_COMMIT,
             mem_check_arena_size);

StaticAssert(IsPow2OrZero(MR4TH_MEM_COMMIT_BLOCK_SIZE) &&
             MR4TH_MEM_COMMIT_BLOCK_SIZE != 0,
             mem_check_commit_block_size);

StaticAssert(IsPow2OrZero(MR4TH_MEM_MAX_ALIGN) && MR4TH_MEM_MAX_ALIGN != 0,
             mem_check_max_align);

// arena functions

MR4TH_SYMBOL_MUST_SHARE Arena*
arena_new(U64 reserve_size, U64 alignment, B32 growing){
  ProfBeginFunc();
  
  Arena *result = 0;
  if (reserve_size >= MR4TH_MEM_INITIAL_COMMIT){
    void *memory = os_memory_reserve(reserve_size);
    if (os_memory_commit(memory, MR4TH_MEM_INITIAL_COMMIT)){
      AsanPoison(memory, reserve_size);
      AsanUnpoison(memory, MR4TH_MEM_INTERNAL_MIN_SIZE);
      result = (Arena*)memory;
      result->current = result;
      result->prev = 0;
      result->alignment = alignment;
      result->default_reserve_size = reserve_size;
      result->growing = growing;
      result->base_pos = 0;
      result->chunk_cap = reserve_size;
      result->chunk_pos = MR4TH_MEM_INTERNAL_MIN_SIZE;
      result->chunk_commit_pos = MR4TH_MEM_INITIAL_COMMIT;
    }
  }
  Assert(result != 0);
  
  ProfEndFunc();
  return(result);
}

MR4TH_SYMBOL_MUST_SHARE void
arena_release(Arena *arena){
  ProfBeginFunc();
  
  Arena *ptr = arena->current;
  for (;ptr != 0;){
    Arena *prev = ptr->prev;
    AsanPoison(ptr, ptr->chunk_cap);
    os_memory_release(ptr, ptr->chunk_cap);
    ptr = prev;
  }
  
  ProfEndFunc();
}

MR4TH_SYMBOL_MUST_SHARE void*
arena_push_no_zero(Arena *arena, U64 size){
  ProfBeginFunc();
  
  void *result = 0;
  Arena *current = arena->current;
  
  // allocate new chunk if necessary
  if (arena->growing){
    U64 next_chunk_pos = AlignUpPow2(current->chunk_pos, arena->alignment);
    next_chunk_pos += size;
    if (next_chunk_pos > current->chunk_cap){
      U64 new_reserve_size = arena->default_reserve_size;
      U64 enough_to_fit = size + MR4TH_MEM_INTERNAL_MIN_SIZE;
      if (new_reserve_size < enough_to_fit){
        new_reserve_size = AlignUpPow2(enough_to_fit, KB(4));
      }
      
      void *memory = os_memory_reserve(new_reserve_size);
      if (os_memory_commit(memory, MR4TH_MEM_INITIAL_COMMIT)){
        AsanPoison(memory, new_reserve_size);
        AsanUnpoison(memory, MR4TH_MEM_INTERNAL_MIN_SIZE);
        Arena *new_chunk = (Arena*)memory;
        new_chunk->prev = current;
        new_chunk->base_pos = current->base_pos + current->chunk_cap;
        new_chunk->chunk_cap = new_reserve_size;
        new_chunk->chunk_pos = MR4TH_MEM_INTERNAL_MIN_SIZE;
        new_chunk->chunk_commit_pos = MR4TH_MEM_INITIAL_COMMIT;
        current = arena->current = new_chunk;
      }
    }
  }
  
  {
    // if there is room in this chunk's reserve ...
    U64 result_pos = AlignUpPow2(current->chunk_pos, arena->alignment);
    U64 next_chunk_pos = result_pos + size;
    if (next_chunk_pos <= current->chunk_cap){
      
      // commit more memory if necessary
      if (next_chunk_pos > current->chunk_commit_pos){
        U64 next_commit_pos_aligned =
          AlignUpPow2(next_chunk_pos, MR4TH_MEM_COMMIT_BLOCK_SIZE);
        U64 next_commit_pos =
          ClampTop(next_commit_pos_aligned, current->chunk_cap);
        U64 commit_size = next_commit_pos - current->chunk_commit_pos;
        if (os_memory_commit((U8*)current + current->chunk_commit_pos, commit_size)){
          current->chunk_commit_pos = next_commit_pos;
        }
      }
      
      // if there is room in the commit range, return memory & advance pos
      if (next_chunk_pos <= current->chunk_commit_pos){
        AsanUnpoison((U8*)current + current->chunk_pos,
                     next_chunk_pos - current->chunk_pos);
        result = (U8*)current + result_pos;
        current->chunk_pos = next_chunk_pos;
      }
    }
  }
  
  ProfEndFunc();
  return(result);
}

MR4TH_SYMBOL_MUST_SHARE void
arena_pop_to(Arena *arena, U64 pos){
  ProfBeginFunc();
  
  U64 clamped_total_pos = ClampBot(pos, MR4TH_MEM_INTERNAL_MIN_SIZE);
  
  Arena *current = arena->current;
  U64 total_pos = current->base_pos + current->chunk_pos;
  if (clamped_total_pos < total_pos){
    // release all chunks that begin after this pos
    for (; clamped_total_pos < current->base_pos; ){
      Arena *prev = current->prev;
      AsanPoison(current, current->chunk_cap);
      os_memory_release(current, current->chunk_cap);
      current = prev;
    }
    
    // update arena's current
    {
      arena->current = current;
    }
    
    // update the chunk position of the chunk in
    //  the chain that contains this position.
    {
      U64 chunk_pos = clamped_total_pos - current->base_pos;
      U64 clamped_chunk_pos = ClampBot(chunk_pos, MR4TH_MEM_INTERNAL_MIN_SIZE);
      AsanPoison((U8*)current + clamped_chunk_pos,
                 current->chunk_pos - clamped_chunk_pos);
      current->chunk_pos = clamped_chunk_pos;
    }
  }
  
  ProfEndFunc();
}

MR4TH_SYMBOL_MUST_SHARE U64
arena_current_pos(Arena *arena){
  Arena *current = arena->current;
  U64 result = current->base_pos + current->chunk_pos;
  return(result);
}

MR4TH_SYMBOL_MUST_SHARE U64
arena_current_align(Arena *arena){
  return(arena->alignment);
}

MR4TH_SYMBOL_MUST_SHARE void
arena_set_align(Arena *arena, U64 alignment){
  arena->alignment = alignment;
}

MR4TH_SYMBOL_STATIC MR4TH_THREADVAR
Arena *arena__scratch_pool[MR4TH_MEM_SCRATCH_POOL_COUNT] = {0};

MR4TH_SYMBOL_MUST_SHARE ArenaTemp
arena_get_scratch(Arena **conflict_array, U32 count){
  ProfBeginFunc();
  
  // init on first time
  if (arena__scratch_pool[0] == 0){
    Arena **scratch_slot = arena__scratch_pool;
    for (U64 i = 0;
         i < MR4TH_MEM_SCRATCH_POOL_COUNT;
         i += 1, scratch_slot += 1){
      *scratch_slot = arena_alloc();
    }
  }
  
  // get non-conflicting arena
  ArenaTemp result = {0};
  Arena **scratch_slot = arena__scratch_pool;
  for (U64 i = 0;
       i < MR4TH_MEM_SCRATCH_POOL_COUNT;
       i += 1, scratch_slot += 1){
    B32 is_non_conflict = 1;
    Arena **conflict_ptr = conflict_array;
    for (U32 j = 0; j < count; j += 1, conflict_ptr += 1){
      if (*scratch_slot == *conflict_ptr){
        is_non_conflict = 0;
        break;
      }
    }
    if (is_non_conflict){
      result = arena_begin_temp(*scratch_slot);
      break;
    }
  }
  
  ProfEndFunc();
  return(result);
}

#endif /* arena core implementation */

// arena helpers

#if !defined(MR4TH_MEM_DEFAULT_RESERVE_SIZE)
# define MR4TH_MEM_DEFAULT_RESERVE_SIZE GB(1)
#endif
#if !defined(MR4TH_MEM_DEFAULT_ALIGNMENT)
# define MR4TH_MEM_DEFAULT_ALIGNMENT sizeof(void*)
#endif

MR4TH_SYMBOL Arena*
arena_alloc(void){
  Arena *result = arena_new(MR4TH_MEM_DEFAULT_RESERVE_SIZE, MR4TH_MEM_DEFAULT_ALIGNMENT, 1);
  return(result);
}

MR4TH_SYMBOL void*
arena_push(Arena *arena, U64 size){
  void *result = arena_push_no_zero(arena, size);
  MemoryZero(result, size);
  return(result);
}

MR4TH_SYMBOL void
arena_pop_amount(Arena *arena, U64 amount){
  U64 pos = arena_current_pos(arena);
  U64 new_pos = 0;
  if (pos > amount){
    new_pos = pos - amount;
  }
  arena_pop_to(arena, new_pos);
}

MR4TH_SYMBOL void
arena_align(Arena *arena, U64 pow2_align){
  Assert(IsPow2OrZero(pow2_align) && pow2_align != 0 &&
         pow2_align <= MR4TH_MEM_MAX_ALIGN);
  U64 pos = arena_current_pos(arena);
  U64 align_pos = AlignUpPow2(pos, pow2_align);
  U64 z = align_pos - pos;
  if (z > 0){
    arena_push(arena, z);
  }
}

MR4TH_SYMBOL ArenaTemp
arena_begin_temp(Arena *arena){
  U64 pos = arena_current_pos(arena);
  ArenaTemp temp = {arena, pos};
  return(temp);
}

MR4TH_SYMBOL void
arena_end_temp(ArenaTemp *temp){
  arena_pop_to(temp->arena, temp->pos);
}

////////////////////////////////
// Functions: Strings

// characters

MR4TH_SYMBOL B32
char_is_whitespace(U8 c){
  return(c == ' ' || c == '\n' || c == '\t' ||
         c == '\r' || c == '\f' || c == '\v');
}

MR4TH_SYMBOL B32
char_is_slash(U8 c){
  return(c == '/' || c == '\\');
}

MR4TH_SYMBOL B32
char_is_digit(U8 c){
  return('0' <= c && c <= '9');
}

MR4TH_SYMBOL U8
char_to_uppercase(U8 c){
  if ('a' <= c && c <= 'z'){
    c += 'A' - 'a';
  }
  return(c);
}

MR4TH_SYMBOL U8
char_to_lowercase(U8 c){
  if ('A' <= c && c <= 'Z'){
    c += 'a' - 'A';
  }
  return(c);
}

// in-place constructors

MR4TH_SYMBOL String8
str8_range(U8 *first, U8 *opl){
  String8 result = {first, (U64)(opl - first)};
  return(result);
}

MR4TH_SYMBOL String8
str8_cstring(U8 *cstr){
  U8 *ptr = cstr;
  for (;*ptr != 0; ptr += 1);
  String8 result = str8_range(cstr, ptr);
  return(result);
}

MR4TH_SYMBOL String8
str8_cstring_capped(U8 *cstr, U8 *opl){
  U8 *ptr = cstr;
  for (;ptr < opl && *ptr != 0; ptr += 1);
  String8 result = str8_range(cstr, ptr);
  return(result);
}

MR4TH_SYMBOL String16
str16_cstring(U16 *cstr){
  U16 *ptr = cstr;
  for (;*ptr != 0; ptr += 1);
  String16 result = {cstr, (U64)(ptr - cstr)};
  return(result);
}

// compound constructors

MR4TH_SYMBOL void
str8_list_push(Arena *arena, String8List *list, String8 string){
  String8Node *node = push_array(arena, String8Node, 1);
  node->string = string;
  SLLQueuePush(list->first, list->last, node);
  list->node_count += 1;
  list->total_size += string.size;
}

MR4TH_SYMBOL void
str8_list_push_front(Arena *arena, String8List *list, String8 string){
  String8Node *node = push_array(arena, String8Node, 1);
  node->string = string;
  SLLQueuePushFront(list->first, list->last, node);
  list->node_count += 1;
  list->total_size += string.size;
}

MR4TH_SYMBOL String8List
str8_list_copy(Arena *arena, String8List *list){
  String8List result = {0};
  for (String8Node *node = list->first;
       node != 0;
       node = node->next){
    String8 string = str8_push_copy(arena, node->string);
    str8_list_push(arena, &result, string);
  }
  return(result);
}

MR4TH_SYMBOL String8
str8_join(Arena *arena, String8List *list, StringJoin *join_optional){
  ProfBeginFunc();
  
  // setup join parameters
  MR4TH_SYMBOL_STATIC StringJoin dummy_join = {0};
  StringJoin *join = join_optional;
  if (join == 0){
    join = &dummy_join;
  }
  
  // compute total size
  U64 size = (join->pre.size +
              join->post.size +
              ((list->node_count>0)?
               (join->mid.size*(list->node_count - 1)):0) +
              list->total_size);
  
  // begin string build
  U8 *str = push_array(arena, U8, size + 1);
  U8 *ptr = str;
  
  // write pre
  MemoryCopy(ptr, join->pre.str, join->pre.size);
  ptr += join->pre.size;
  
  B32 is_mid = 0;
  for (String8Node *node = list->first;
       node != 0;
       node = node->next){
    // write mid
    if (is_mid){
      MemoryCopy(ptr, join->mid.str, join->mid.size);
      ptr += join->mid.size;
    }
    
    // write node string
    MemoryCopy(ptr, node->string.str, node->string.size);
    ptr += node->string.size;
    
    is_mid = 1;
  }
  
  // write post
  MemoryCopy(ptr, join->post.str, join->post.size);
  ptr += join->post.size;
  
  // write null
  *ptr = 0;
  
  String8 result = CLiteral(String8){str, size};
  ProfEndFunc();
  return(result);
}

MR4TH_SYMBOL String8List
str8_split(Arena *arena, String8 string, U8 *splits, U32 count){
  ProfBeginFunc();
  
  String8List result = {0};
  
  U8 *ptr = string.str;
  U8 *word_first = ptr;
  U8 *opl = string.str + string.size;
  for (;ptr < opl; ptr += 1){
    // is this a split
    U8 byte = *ptr;
    B32 is_split_byte = 0;
    for (U32 i = 0; i < count; i += 1){
      if (byte == splits[i]){
        is_split_byte = 1;
        break;
      }
    }
    
    if (is_split_byte){
      // try to emit word, advance word first pointer
      if (word_first < ptr){
        str8_list_push(arena, &result, str8_range(word_first, ptr));
      }
      word_first = ptr + 1;
    }
  }
  
  // try to emit final word
  if (word_first < ptr){
    str8_list_push(arena, &result, str8_range(word_first, ptr));
  }
  
  ProfEndFunc();
  return(result);
}

MR4TH_SYMBOL String8
str8_pushfv(Arena *arena, char *fmt, va_list args){
  ProfBeginFunc();
  
  // in case we need to try a second time
  va_list args2;
  va_copy(args2, args);
  
  // try to build the string in 1024 bytes
  U64 buffer_size = 1024;
  U8 *buffer = push_array(arena, U8, buffer_size);
  U64 actual_size = m4_vsnprintf((char*)buffer, buffer_size, fmt, args);
  
  String8 result = {0};
  if (actual_size < buffer_size){
    // if first try worked, put back what we didn't use and finish
    arena_pop_amount(arena, buffer_size - actual_size - 1);
    result = CLiteral(String8){buffer, actual_size};
  }
  else{
    // if first try failed, reset and try again with correct size
    arena_pop_amount(arena, buffer_size);
    U8 *fixed_buffer = push_array(arena, U8, actual_size + 1);
    U64 final_size = m4_vsnprintf((char*)fixed_buffer, actual_size + 1, fmt, args2);
    result = CLiteral(String8){fixed_buffer, final_size};
  }
  
  // end args2
  va_end(args2);
  
  ProfEndFunc();
  return(result);
}

MR4TH_SYMBOL String8
str8_pushf(Arena *arena, char *fmt, ...){
  va_list args;
  va_start(args, fmt);
  String8 result = str8_pushfv(arena, fmt, args);
  va_end(args);
  return(result);
}

MR4TH_SYMBOL void
str8_list_pushf(Arena *arena, String8List *list, char *fmt, ...){
  va_list args;
  va_start(args, fmt);
  String8 string = str8_pushfv(arena, fmt, args);
  va_end(args);
  str8_list_push(arena, list, string);
}

MR4TH_SYMBOL String8
str8_push_copy(Arena *arena, String8 string){
  String8 result = {0};
  result.str = push_array(arena, U8, string.size + 1);
  result.size = string.size;
  MemoryCopy(result.str, string.str, string.size);
  result.str[result.size] = 0;
  return(result);
}

// substrings

MR4TH_SYMBOL String8
str8_prefix(String8 str, U64 size){
  U64 size_clamped = ClampTop(size, str.size);
  String8 result = {str.str, size_clamped};
  return(result);
}

MR4TH_SYMBOL String8
str8_chop(String8 str, U64 amount){
  U64 amount_clamped = ClampTop(amount, str.size);
  U64 remaining_size = str.size - amount_clamped;
  String8 result = {str.str, remaining_size};
  return(result);
}

MR4TH_SYMBOL String8
str8_postfix(String8 str, U64 size){
  U64 size_clamped = ClampTop(size, str.size);
  U64 skip_to = str.size - size_clamped;
  String8 result = {str.str + skip_to, size_clamped};
  return(result);
}

MR4TH_SYMBOL String8
str8_skip(String8 str, U64 amount){
  U64 amount_clamped = ClampTop(amount, str.size);
  U64 remaining_size = str.size - amount_clamped;
  String8 result = {str.str + amount_clamped, remaining_size};
  return(result);
}

MR4TH_SYMBOL String8
str8_skip_chop_whitespace(String8 str){
  String8 result = str;
  if (result.size > 0){
    U8 *sptr = str.str;
    U8 *eptr = str.str + str.size - 1;
    for (;sptr < eptr && char_is_whitespace(*sptr); sptr += 1);
    for (;sptr < eptr && char_is_whitespace(*eptr); eptr -= 1);
    if (char_is_whitespace(*sptr)){
      result.str = 0;
      result.size = 0;
    }
    else{
      result = str8_range(sptr, eptr + 1);
    }
  }
  return(result);
}

// hash

MR4TH_SYMBOL U64
str8_hash(String8 str){
  U64 hash = 5381;
  for (U8 *ptr = str.str, *opl = str.str + str.size;
       ptr < opl;
       ptr += 1){
    U8 c = *ptr;
    hash = (hash*33)^c;
  }
  return(hash);
}

// compare

MR4TH_SYMBOL B32
str8_match(String8 a, String8 b, StringMatchFlags flags){
  ProfBeginFunc();
  
  B32 result = 0;
  if ((flags & StringMatchFlag_PrefixMatch) != 0 || a.size == b.size){
    U64 size = a.size;
    if ((flags & StringMatchFlag_PrefixMatch) != 0){
      size = Min(a.size, b.size);
    }
    
    result = 1;
    B32 no_case = ((flags & StringMatchFlag_NoCase) != 0);
    for (U64 i = 0; i < size; i += 1){
      U8 ac = a.str[i];
      U8 bc = b.str[i];
      if (no_case){
        ac = char_to_uppercase(ac);
        bc = char_to_uppercase(bc);
      }
      if (ac != bc){
        result = 0;
        break;
      }
    }
  }
  
  ProfEndFunc();
  return(result);
}

// path helpers

MR4TH_SYMBOL String8
str8_chop_last_slash(String8 string){
  String8 result = string;
  if (string.size > 0){
    // pos one past last slash
    U64 pos = string.size;
    for (S64 i = string.size - 1; i >= 0; i -= 1){
      if (char_is_slash(string.str[i])){
        pos = i;
        break;
      }
    }
    
    // chop result string
    result.size = pos;
  }
  return(result);
}

MR4TH_SYMBOL String8
str8_file_name_from_path(String8 full_file_name){
  String8 result = {0};
  if (full_file_name.size > 0){
    U8 *opl = full_file_name.str + full_file_name.size;
    U8 *ptr = opl;
    for (;ptr > full_file_name.str; ptr -= 1){
      U8 c = ptr[-1];
      if (c == '/' || c == '\\'){
        break;
      }
    }
    result = str8_range(ptr, opl);
  }
  return(result);
}

MR4TH_SYMBOL String8
str8_base_name_from_file_name(String8 file_name){
  String8 result = {0};
  if (file_name.size > 0){
    U8 *opl = file_name.str + file_name.size;
    U8 *ptr = file_name.str;
    for (;ptr < opl; ptr += 1){
      U8 c = ptr[0];
      if (c == '.'){
        break;
      }
    }
    result = str8_range(file_name.str, ptr);
  }
  return(result);
}

// stylized constructors

MR4TH_SYMBOL String8
str8_join_flags(Arena *arena, String8List *list){
  StringJoin join = {0};
  join.mid = str8_lit(" | ");
  String8 result = str8_join(arena, list, &join);
  if (result.size == 0){
    result = str8_lit("0");
  }
  return(result);
}

// unicode

MR4TH_SYMBOL StringDecode
str_decode_utf8(U8 *str, U32 cap){
  ProfBeginFunc();
  
  MR4TH_SYMBOL_STATIC U8 length[] = {
    1, 1, 1, 1, // 000xx
    1, 1, 1, 1,
    1, 1, 1, 1,
    1, 1, 1, 1,
    0, 0, 0, 0, // 100xx
    0, 0, 0, 0,
    2, 2, 2, 2, // 110xx
    3, 3,       // 1110x
    4,          // 11110
    0,          // 11111
  };
  MR4TH_SYMBOL_STATIC U8 first_byte_mask[] = { 0, 0x7F, 0x1F, 0x0F, 0x07 };
  MR4TH_SYMBOL_STATIC U8 final_shift[] = { 0, 18, 12, 6, 0 };
  
  StringDecode result = {0};
  if (cap > 0){
    result.codepoint = '#';
    result.size = 1;
    
    U8 byte = str[0];
    U8 l = length[byte >> 3];
    if (0 < l && l <= cap){
      U32 cp = (byte & first_byte_mask[l]) << 18;
      switch (l){
        case 4: cp |= ((str[3] & 0x3F) << 0);
        case 3: cp |= ((str[2] & 0x3F) << 6);
        case 2: cp |= ((str[1] & 0x3F) << 12);
        default: break;
      }
      cp >>= final_shift[l];
      
      result.codepoint = cp;
      result.size = l;
    }
  }
  
  ProfEndFunc();
  return(result);
}

MR4TH_SYMBOL U32
str_encode_utf8(U8 *dst, U32 codepoint){
  ProfBeginFunc();
  
  U32 size = 0;
  if (codepoint < (1 << 7)){
    dst[0] = codepoint;
    size = 1;
  }
  else if (codepoint < (1 << 11)){
    dst[0] = 0xC0 | (codepoint >> 6);
    dst[1] = 0x80 | (codepoint & 0x3F);
    size = 2;
  }
  else if (codepoint < (1 << 16)){
    dst[0] = 0xE0 | (codepoint >> 12);
    dst[1] = 0x80 | ((codepoint >> 6) & 0x3F);
    dst[2] = 0x80 | (codepoint & 0x3F);
    size = 3;
  }
  else if (codepoint < (1 << 21)){
    dst[0] = 0xF0 | (codepoint >> 18);
    dst[1] = 0x80 | ((codepoint >> 12) & 0x3F);
    dst[2] = 0x80 | ((codepoint >> 6) & 0x3F);
    dst[3] = 0x80 | (codepoint & 0x3F);
    size = 4;
  }
  else{
    dst[0] = '#';
    size = 1;
  }
  
  ProfEndFunc();
  return(size);
}

MR4TH_SYMBOL StringDecode
str_decode_utf16(U16 *str, U32 cap){
  ProfBeginFunc();
  
  StringDecode result = {'#', 1};
  U16 x = str[0];
  if (x < 0xD800 || 0xDFFF < x){
    result.codepoint = x;
  }
  else if (cap >= 2){
    U16 y = str[1];
    if (0xD800 <= x && x < 0xDC00 &&
        0xDC00 <= y && y < 0xE000){
      U16 xj = x - 0xD800;
      U16 yj = y - 0xDc00;
      U32 xy = (xj << 10) | yj;
      result.codepoint = xy + 0x10000;
      result.size = 2;
    }
  }
  
  ProfEndFunc();
  return(result);
}

MR4TH_SYMBOL U32
str_encode_utf16(U16 *dst, U32 codepoint){
  ProfBeginFunc();
  
  U32 size = 0;
  if (codepoint < 0x10000){
    dst[0] = codepoint;
    size = 1;
  }
  else{
    U32 cpj = codepoint - 0x10000;
    dst[0] = (cpj >> 10) + 0xD800;
    dst[1] = (cpj & 0x3FF) + 0xDC00;
    size = 2;
  }
  
  ProfEndFunc();
  return(size);
}

MR4TH_SYMBOL String32
str32_from_str8(Arena *arena, String8 string){
  ProfBeginFunc();
  
  U32 *memory = push_array(arena, U32, string.size + 1);
  
  U32 *dptr = memory;
  U8 *ptr = string.str;
  U8 *opl = string.str + string.size;
  for (; ptr < opl;){
    StringDecode decode = str_decode_utf8(ptr, (U64)(opl - ptr));
    *dptr = decode.codepoint;
    ptr += decode.size;
    dptr += 1;
  }
  
  *dptr = 0;
  
  U64 alloc_count = string.size + 1;
  U64 string_count = (U64)(dptr - memory);
  U64 unused_count = alloc_count - string_count - 1;
  arena_pop_amount(arena, unused_count*sizeof(*memory));
  
  String32 result = {memory, string_count};
  ProfEndFunc();
  return(result);
}

MR4TH_SYMBOL String8
str8_from_str32(Arena *arena, String32 string){
  ProfBeginFunc();
  
  U8 *memory = push_array(arena, U8, string.size*4 + 1);
  
  U8 *dptr = memory;
  U32 *ptr = string.str;
  U32 *opl = string.str + string.size;
  for (; ptr < opl;){
    U32 size = str_encode_utf8(dptr, *ptr);
    ptr += 1;
    dptr += size;
  }
  
  *dptr = 0;
  
  U64 alloc_count = string.size*4 + 1;
  U64 string_count = (U64)(dptr - memory);
  U64 unused_count = alloc_count - string_count - 1;
  arena_pop_amount(arena, unused_count*sizeof(*memory));
  
  String8 result = {memory, string_count};
  ProfEndFunc();
  return(result);
}

MR4TH_SYMBOL String16
str16_from_str8(Arena *arena, String8 string){
  ProfBeginFunc();
  U16 *memory = push_array(arena, U16, string.size*2 + 1);
  
  U16 *dptr = memory;
  U8 *ptr = string.str;
  U8 *opl = string.str + string.size;
  for (; ptr < opl;){
    StringDecode decode = str_decode_utf8(ptr, (U64)(opl - ptr));
    U32 enc_size = str_encode_utf16(dptr, decode.codepoint);
    ptr += decode.size;
    dptr += enc_size;
  }
  
  *dptr = 0;
  
  U64 alloc_count = string.size*2 + 1;
  U64 string_count = (U64)(dptr - memory);
  U64 unused_count = alloc_count - string_count - 1;
  arena_pop_amount(arena, unused_count*sizeof(*memory));
  
  String16 result = {memory, string_count};
  ProfEndFunc();
  return(result);
}

MR4TH_SYMBOL String8
str8_from_str16(Arena *arena, String16 string){
  ProfBeginFunc();
  U8 *memory = push_array(arena, U8, string.size*3 + 1);
  
  U8 *dptr = memory;
  U16 *ptr = string.str;
  U16 *opl = string.str + string.size;
  for (; ptr < opl;){
    StringDecode decode = str_decode_utf16(ptr, (U64)(opl - ptr));
    U16 enc_size = str_encode_utf8(dptr, decode.codepoint);
    ptr += decode.size;
    dptr += enc_size;
  }
  
  *dptr = 0;
  
  U64 alloc_count = string.size*3 + 1;
  U64 string_count = (U64)(dptr - memory);
  U64 unused_count = alloc_count - string_count - 1;
  arena_pop_amount(arena, unused_count*sizeof(*memory));
  
  String8 result = {memory, string_count};
  ProfEndFunc();
  return(result);
}

// numeric conversion

MR4TH_SYMBOL B32
str8_is_u64(String8 string, U32 radix){
  Assert(2 <= radix && radix <= 16);
  
  B32 result = 1;
  {
    U8 *ptr = string.str;
    U8 *opl = string.str + string.size;
    for (; ptr < opl; ptr += 1){
      U8 num = 0xFF;
      
      // extract
      U8 s = *ptr - '0';
      if (s <= 9){
        num = s;
      }
      else{
        s = *ptr - 'A';
        if (s <= 5){
          num = 0xA + s;
        }
        else{
          s = *ptr - 'a';
          if (s <= 5){
            num = 0xA + s;
          }
        }
      }
      
      // bad parse check
      if (num >= radix){
        result = 0;
        break;
      }
    }
  }
  
  return(result);
}

MR4TH_SYMBOL U64
u64_from_str8(String8 string, U32 radix){
  Assert(2 <= radix && radix <= 16);
  
  U64 result = 0;
  {
    U8 *ptr = string.str;
    U8 *opl = string.str + string.size;
    for (; ptr < opl; ptr += 1){
      U8 num = 0xFF;
      
      // extract
      U8 s = *ptr - '0';
      if (s <= 9){
        num = s;
      }
      else{
        s = *ptr - 'A';
        if (s <= 5){
          num = 0xA + s;
        }
        else{
          s = *ptr - 'a';
          if (s <= 5){
            num = 0xA + s;
          }
        }
      }
      
      // bad parse check
      if (num >= radix){
        result = 0;
        break;
      }
      
      // increment result
      result *= radix;
      result += num;
    }
  }
  
  return(result);
}

MR4TH_SYMBOL U64
u64_from_str8_c_syntax(String8 string){
  U8 *ptr = string.str;
  U8 *opl = string.str + string.size;
  
  U32 radix = 10;
  if (ptr < opl && *ptr == '0'){
    radix = 010;
    ptr += 1;
    if (ptr < opl && *ptr == 'x'){
      radix = 0x10;
      ptr += 1;
    }
    else if (ptr < opl && *ptr == 'b'){
      radix = 2;
      ptr += 1;
    }
  }
  
  U64 result = u64_from_str8(str8_range(ptr, opl), radix);
  return(result);
}

MR4TH_SYMBOL S64
s64_from_str8_c_syntax(String8 string){
  U8 *ptr = string.str;
  U8 *opl = string.str + string.size;
  
  B32 negative = 0;
  if (ptr < opl){
    if (*ptr == '-'){
      negative = 1;
      ptr += 1;
    }
    else if (*ptr == '+'){
      ptr += 1;
    }
  }
  
  S64 result = u64_from_str8_c_syntax(str8_range(ptr, opl));
  if (negative){
    result = -result;
  }
  
  return(result);
}

MR4TH_SYMBOL F64
f64_from_str8(String8 string){
  F64 result = 0.f;
  
  U8 *ptr = string.str;
  U8 *opl = string.str + string.size;
  
  B32 negative = 0;
  if (ptr < opl){
    if (*ptr == '-'){
      negative = 1;
      ptr += 1;
    }
    else if (*ptr == '+'){
      ptr += 1;
    }
  }
  
  for (; ptr < opl; ptr += 1){
    if (*ptr == '.'){
      ptr += 1;
      break;
    }
    U32 x = 0;
    if ('0' <= *ptr && *ptr <= '9'){
      x = *ptr - '0';
    }
    result *= 10.f;
    result += x;
  }
  
  F32 mul = 0.1f;
  for (; ptr < opl; ptr += 1){
    U32 x = 0;
    if ('0' <= *ptr && *ptr <= '9'){
      x = *ptr - '0';
    }
    result += x*mul;
    mul /= 10.f;
  }
  
  if (negative){
    result = -result;
  }
  
  return(result);
}

////////////////////////////////
// Functions: Stream

// stream core implementation

#define STREAM_NODE_FROM_DATA(d) (STREAM_Node*)((U8*)(d) - sizeof(STREAM_Node))

#if !MR4TH_PLUGIN_MODE

MR4TH_SYMBOL_MUST_SHARE STREAM*
stream_new(void){
  Arena *arena = arena_alloc();
  STREAM *stream = push_array(arena, STREAM, 1);
  stream->arena = arena;
  stream->clear_pos = arena_current_pos(arena);
  return(stream);
}

MR4TH_SYMBOL_MUST_SHARE void
stream_release(STREAM *stream){
  stream_clear(stream);
}

MR4TH_SYMBOL_MUST_SHARE void
stream_clear(STREAM *stream){
  Arena *arena = stream->arena;
  U64 clear_pos = stream->clear_pos;
  arena_pop_to(arena, clear_pos);
  MemoryZeroStruct(stream);
  stream->arena = arena;
  stream->clear_pos = clear_pos;
}

MR4TH_SYMBOL_MUST_SHARE void
stream_write(STREAM *stream, String8 data){
  U64 data_pos = 0;
  
  for (;data_pos < data.size;){
    U64 remaining_size = data.size - data_pos;
    
    // try to get a node with buffer space from the stream
    //  (don't return a node with no space!)
    STREAM_Node *node = 0;
    {
      if (stream->buffer_cap > 0){
        node = stream->last_node;
      }
      if (node != 0 && stream->buffer_cap <= node->size){
        node = 0;
      }
    }
    
    // if we didn't get a buffer, try the unused node
    if (node == 0){
      if (stream->unused_node != 0){
        node = stream->unused_node;
        stream->unused_node = 0;
        stream->buffer_cap = node->size;
        node->size = 0;
        SLLQueuePush(stream->first_node, stream->last_node, node);
      }
    }
    
    // if we didn't get a buffer, make one now
    if (node == 0){
      U64 alloc_size = sizeof(STREAM_Node) + remaining_size;
      alloc_size = ClampBot(MR4TH_STREAM_ALLOC_SIZE, alloc_size);
      
      node = (STREAM_Node*)push_array_no_zero(stream->arena, U8, alloc_size);
      SLLQueuePush(stream->first_node, stream->last_node, node);
      node->size = 0;
      
      stream->buffer_cap = alloc_size - sizeof(STREAM_Node);
    }
    
    // get buffer from the node
    U8 *buffer = node->data + node->size;
    U64 buffer_size = stream->buffer_cap - node->size;
    
    // calculate amount to copy
    U64 copy_amount = ClampTop(remaining_size, buffer_size);
    
    // copy memory and increment counters
    MemoryCopy(buffer, data.str + data_pos, copy_amount);
    node->size += copy_amount;
    stream->total_size += copy_amount;
    data_pos += copy_amount;
  }
}

MR4TH_SYMBOL_MUST_SHARE U8*
stream_alloc(STREAM *stream, U64 size){
  // try to get a node with buffer space
  //  (don't return a node with no space)
  STREAM_Node *node = 0;
  {
    if (stream->buffer_cap > 0){
      node = stream->last_node;
    }
    if (node != 0 && stream->buffer_cap <= node->size){
      node = 0;
    }
  }
  
  // if there is a node but it's not big enough...
  if (node != 0 && stream->buffer_cap - node->size < size){
    U64 remaining_cap = stream->buffer_cap - node->size;
    U64 unused_ptr = IntFromPtr(node->data) + node->size;
    U64 unused_opl = IntFromPtr(node->data) + stream->buffer_cap;
    U64 unused_ptr_aligned = AlignUpPow2(unused_ptr, sizeof(void*));
    if (unused_opl > unused_ptr_aligned &&
        unused_opl - unused_ptr_aligned >= sizeof(STREAM_Node) + 32){
      STREAM_Node *unused_node = (STREAM_Node*)PtrFromInt(unused_ptr_aligned);
      U64 leftover_buffer_size =  unused_opl - unused_ptr_aligned - sizeof(STREAM_Node);
      unused_node->size = leftover_buffer_size;
      SLLStackPush(stream->unused_node, unused_node);
    }
    node = 0;
  }
  
  // if we don't have a node then setup a new node with sufficient space.
  if (node == 0){
    U64 alloc_size = sizeof(STREAM_Node) + size;
    alloc_size = ClampBot(MR4TH_STREAM_ALLOC_SIZE, alloc_size);
    
    node = (STREAM_Node*)push_array_no_zero(stream->arena, U8, alloc_size);
    SLLQueuePush(stream->first_node, stream->last_node, node);
    node->size = 0;
    
    stream->buffer_cap = alloc_size - sizeof(STREAM_Node);
  }
  
  // return the buffer
  U8 *result = node->data + node->size;
  node->size += size;
  stream->total_size += size;
  return(result);
}

MR4TH_SYMBOL_MUST_SHARE U64
stream_total_size(STREAM *stream){
  return(stream->total_size);
}

MR4TH_SYMBOL_MUST_SHARE STREAM_Handle*
stream_next_chunk(STREAM *stream, STREAM_Handle *handle, String8 *chunk_out){
  STREAM_Node *node = (STREAM_Node*)handle;
  if (node == 0){
    node = stream->first_node;
  }
  else{
    node = node->next;
  }
  if (node != 0){
    chunk_out->size = node->size;
    chunk_out->str  = node->data;
  }
  else{
    chunk_out->size = 0;
    chunk_out->str = 0;
  }
  return((STREAM_Handle*)node);
}

#endif /* stream core implementation */

// stream helpers

MR4TH_SYMBOL String8
stream_read(Arena *arena, STREAM *stream){
  String8 result = {0};
  result.str = push_array(arena, U8, stream->total_size);
  result.size = stream->total_size;
  
  U64 pos = 0;
  for (STREAM_Node *node = stream->first_node;
       node != 0;
       node = node->next){
    MemoryCopy(result.str + pos, node->data, node->size);
    pos += node->size;
  }
  return(result);
}

MR4TH_SYMBOL void
stream_printfv(STREAM *stream, char *fmt, va_list args){
  // in case we need to try a second time
  va_list args2;
  va_copy(args2, args);
  
  // try to get a buffer from the stream
  STREAM_Node *node = 0;
  U8 *buffer = 0;
  U64 buffer_size = 0;
  if (stream->buffer_cap > 0){
    node = stream->last_node;
    buffer = node->data + node->size;
    buffer_size = stream->buffer_cap - node->size;
  }
  
  // try to build the string in the current block
  U64 actual_size = 0;
  if (buffer_size > 0){
    actual_size = m4_vsnprintf((char*)buffer, buffer_size, fmt, args);
  }
  else{
    actual_size = m4_vsnprintf(0, 0, fmt, args);
  }
  
  // if first try worked, increment block size
  if (actual_size <= buffer_size){
    node->size += actual_size;
    stream->total_size += actual_size;
  }
  
  // if first try failed, reset and try again with correct size
  else{
    // TODO(allen): super annoying extra buffer copy, need to fix.
    ArenaTemp scratch = arena_get_scratch(0, 0);
    U8 *temp_buffer = push_array(scratch.arena, U8,actual_size + 1);
    U64 final_size = m4_vsnprintf((char*)temp_buffer, actual_size + 1, fmt, args2);
    Assert(final_size == actual_size);
    U8 *new_buffer = stream_alloc(stream, actual_size);
    MemoryCopy(new_buffer, temp_buffer, actual_size);
    arena_release_scratch(&scratch);
  }
  
  // end args2
  va_end(args2);
}

MR4TH_SYMBOL void
stream_printf(STREAM *stream, char *fmt, ...){
  va_list args;
  va_start(args, fmt);
  stream_printfv(stream, fmt, args);
  va_end(args);
}

////////////////////////////////
// Functions: Buffer

MR4TH_SYMBOL void
buffer_alloc(BUFFER *buffer, U64 size){
  MemoryZeroStruct(buffer);
  U64 real_size = AlignUpPow2(size, KB(4));
  buffer->base = os_memory_reserve(real_size);
  if (buffer->base != 0){
    buffer->size = real_size;
    buffer->commit_pos = 0;
  }
}

MR4TH_SYMBOL void
buffer_release(BUFFER *buffer){
  os_memory_release(buffer->base, buffer->size);
}

MR4TH_SYMBOL void
buffer_reset(BUFFER *buffer){
  if (buffer->commit_pos > 0){
    os_memory_decommit((U8*)buffer->base, buffer->commit_pos);
  }
  buffer->commit_pos = 0;
}

MR4TH_SYMBOL B32
buffer_commit_off__call(BUFFER *buffer, U64 off){
  B32 result = 0;
  U64 new_commit_pos = AlignUpPow2(off, MR4TH_MEM_COMMIT_BLOCK_SIZE);
  new_commit_pos = ClampTop(new_commit_pos, buffer->size);
  if (os_memory_commit((U8*)buffer->base + buffer->commit_pos, new_commit_pos - buffer->commit_pos)){
    buffer->commit_pos = new_commit_pos;
    result = 1;
  }
  return(result);
}

////////////////////////////////
// Types: Hash Table

MR4TH_SYMBOL void
hash_buckets_init(BUFFER *buckets, U64 count){
  Assert(count > 0 && IsPow2OrZero(count));
  buffer_commit_off(buckets, sizeof(void*)*count);
}

MR4TH_SYMBOL HASH_Node*
hash_buckets_first(BUFFER *buckets, U64 hash){
  U64 count = buckets->commit_pos/sizeof(void*);
  U64 bidx = hash&(count - 1);
  HASH_Node *result = ((HASH_Node**)buckets->base)[bidx];
  return(result);
}

MR4TH_SYMBOL void
hash_buckets_expand(BUFFER *buckets, U64 capacity){
  static const U32 ALPHA_NUMERATOR = 7;
  static const U32 ALPHA_DENOMINATOR = 8;
  
  U64 count = buckets->commit_pos/sizeof(void*);
  U64 max = buckets->size/sizeof(void*);
  U32 dbl_count = count*2;
  if (capacity*ALPHA_DENOMINATOR > count*ALPHA_NUMERATOR &&
      dbl_count <= max){
    U64 old_count = count;
    U64 new_count = count*4;
    if (new_count > max){
      new_count = dbl_count;
    }
    buffer_commit_off(buckets, sizeof(void*)*new_count);
    U64 mask = new_count - 1;
    U64 rehash_mask = mask&~(U64)(old_count - 1);
    HASH_Node **buckets_base = (HASH_Node**)buckets->base;
    for (U32 i = 0; i < old_count; i += 1){
      for (HASH_Node **ptr = buckets_base + i;
           *ptr != 0;){
        HASH_Node *node = *ptr;
        if ((node->hash&rehash_mask) != 0){
          U64 bidx = node->hash&mask;
          *ptr = (*ptr)->next;
          SLLStackPush(buckets_base[bidx], node);
        }
        else{
          ptr = &node->next;
        }
      }
    }
  }
}

MR4TH_SYMBOL void
hash_buckets_insert(Arena *arena, BUFFER *buckets, U64 hash, U64 val){
  U64 count = buckets->commit_pos/sizeof(void*);
  U64 bucket_idx = hash&(count - 1);
  HASH_Node *bucket = push_array(arena, HASH_Node, 1);
  bucket->hash = hash;
  bucket->val  = val;
  SLLStackPush(((HASH_Node**)buckets->base)[bucket_idx], bucket);
}

////////////////////////////////
// Functions: Log

MR4TH_SYMBOL_MUST_SHARE MR4TH_THREADVAR
LOG_ThreadVars *log_vars = 0;

MR4TH_SYMBOL void
log_accum_begin(LOG_LogToProc *proc, void *uptr){
  LOG_ThreadVars *vars = log_vars;
  if (vars == 0){
    Arena *arena = arena_alloc();
    vars = log_vars = push_array(arena, LOG_ThreadVars, 1);
    vars->arena = arena;
  }
  
  U64 pos = arena_current_pos(vars->arena);
  LOG_Node *node = push_array(vars->arena, LOG_Node, 1);
  node->pos = pos;
  node->logto = proc;
  node->uptr = uptr;
  SLLStackPush(vars->stack, node);
}

MR4TH_SYMBOL B32
log_gathering(void){
  LOG_ThreadVars *vars = log_vars;
  return(vars != 0 && vars->stack != 0);
}

MR4TH_SYMBOL void
log_emit(String8 message){
  LOG_ThreadVars *vars = log_vars;
  if (vars != 0 && vars->stack != 0){
    LOG_Node *node = vars->stack;
    String8 msg_copy = str8_push_copy(vars->arena, message);
    str8_list_push(vars->arena, &node->log, msg_copy);
    if (node->logto != 0){
      node->logto(node->uptr, msg_copy);
    }
  }
}

MR4TH_SYMBOL void
log_emitf(char *fmt, ...){
  LOG_ThreadVars *vars = log_vars;
  if (vars != 0 && vars->stack != 0){
    LOG_Node *node = vars->stack;
    va_list args;
    va_start(args, fmt);
    String8 string = str8_pushfv(vars->arena, fmt, args);
    va_end(args);
    str8_list_push(vars->arena, &node->log, string);
    if (node->logto != 0){
      node->logto(node->uptr, string);
    }
  }
}

MR4TH_SYMBOL String8
log_accum_end(Arena *arena){
  String8 result = {0};
  LOG_ThreadVars *vars = log_vars;
  if (vars != 0){
    LOG_Node *node = vars->stack;
    if (node != 0){
      result = str8_join(arena, &node->log, 0);
      SLLStackPop(vars->stack);
      arena_pop_to(vars->arena, node->pos);
    }
  }
  return(result);
}

////////////////////////////////
// Functions: Errors

// IMPORTANT: It is important that strings get pushed
// *on top* of the node that holds them within the arena.
// basically: Assert((U8*)node < (U8*)node->error.str);
// except that stops working if the arena is chained...

MR4TH_SYMBOL_MUST_SHARE MR4TH_THREADVAR
ER_ThreadVars *er_vars = 0;

MR4TH_SYMBOL void
er_accum_begin(void){
  ER_ThreadVars *vars = er_vars;
  if (vars == 0){
    Arena *arena = arena_new(KB(64), 1, 0);
    vars = er_vars = push_array(arena, ER_ThreadVars, 1);
    vars->arena = arena;
  }
  
  U64 pos = arena_current_pos(vars->arena);
  ER_Node *node = push_array(vars->arena, ER_Node, 1);
  if (node == 0){
    vars->over_stack += 1;
  }
  else{
    node->pos = pos;
    SLLStackPush(vars->stack, node);
  }
}

MR4TH_SYMBOL void
er_emit(String8 error){
  ER_ThreadVars *vars = er_vars;
  if (vars != 0 &&
      vars->over_stack == 0){
    ER_Node *node = vars->stack;
    if (node != 0 && node->error.size == 0){
      node->error = str8_push_copy(vars->arena, error);
    }
  }
}

MR4TH_SYMBOL void
er_emitf(char *fmt, ...){
  ER_ThreadVars *vars = er_vars;
  if (vars != 0 &&
      vars->over_stack == 0){
    ER_Node *node = vars->stack;
    if (node != 0 && node->error.size == 0){
      va_list args;
      va_start(args, fmt);
      String8 string = str8_pushfv(vars->arena, fmt, args);
      va_end(args);
      node->error = string;
    }
  }
}

MR4TH_SYMBOL String8
er_accum_end(Arena *arena){
  String8 result = {0};
  ER_ThreadVars *vars = er_vars;
  if (vars != 0){
    if (vars->over_stack == 0){
      ER_Node *node = vars->stack;
      if (node != 0){
        result = str8_push_copy(arena, node->error);
        SLLStackPop(vars->stack);
        arena_pop_to(vars->arena, node->pos);
      }
    }
    else{
      vars->over_stack -= 1;
    }
  }
  return(result);
}


////////////////////////////////////////////////
////////////////////////////////////////////////
////////   OS IMPLEMENTATION SHARED   //////////
////////////////////////////////////////////////
////////////////////////////////////////////////

////////////////////////////////
// Helper Implementation: File Handling

MR4TH_SYMBOL B32
os_file_write(String8 file_name, String8 data){
  String8Node node = {0};
  node.string = data;
  B32 result = os_file_write_list(file_name, &node);
  return(result);
}

MR4TH_SYMBOL String8
os__file_write_callback__list(void *udata){
  String8Node **ptr = (String8Node**)udata;
  String8 result = {0};
  if (*ptr != 0){
    result = (*ptr)->string;
    (*ptr) = (*ptr)->next;
  }
  return(result);
}

MR4TH_SYMBOL B32
os_file_write_list(String8 file_name, String8Node *first_node){
  B32 result = os_file_write_callback(file_name, &first_node, os__file_write_callback__list);
  return(result);
}

MR4TH_SYMBOL String8
os__file_write_callback__stream(void *udata){
  STREAM        *stream = *(STREAM**)       ((void**)udata + 0);
  STREAM_Handle**handle =  (STREAM_Handle**)((void**)udata + 1);
  String8 result = {0};
  *handle = stream_next_chunk(stream, *handle, &result);
  return(result);
}

MR4TH_SYMBOL B32
os_file_write_stream(String8 file_name, STREAM *stream){
  void *udata[2] = { stream, 0 };
  B32 result = os_file_write_callback(file_name, udata, os__file_write_callback__stream);
  return(result);
}

////////////////////////////////
// Helper Implementation: Libraries

MR4TH_SYMBOL OS_VRange
os_this_image(void){
  OS_VRange result = {0};
  OS_Library *this_lib = os_lib_from_addr((void*)os_this_image);
  if (this_lib != 0){
    result = os_lib_image_range(this_lib);
  }
  return(result);
}



/*******************************
**      Begin Base Win32      **
*******************************/

#if OS_WINDOWS

////////////////////////////////
// Win32 Implementation: Global Variables

MR4TH_SYMBOL_STATIC U64 w32_ticks_per_second = 1;

MR4TH_SYMBOL_STATIC Arena *w32_perm_arena = 0;
MR4TH_SYMBOL_STATIC String8 w32_binary_path = {0};
MR4TH_SYMBOL_STATIC String8 w32_user_path = {0};
MR4TH_SYMBOL_STATIC String8 w32_temp_path = {0};

MR4TH_SYMBOL_STATIC String8List w32_cmd_line = {0};

////////////////////////////////
// Win32 Implementation: Process Setup

MR4TH_SYMBOL void
os_main_init(int argc, char **argv){
  ProfBeginFunc();
  
  // setup precision time
  LARGE_INTEGER perf_freq = {0};
  if (QueryPerformanceFrequency(&perf_freq)){
    w32_ticks_per_second = ((U64)perf_freq.HighPart << 32) | perf_freq.LowPart;
  }
  timeBeginPeriod(1);
  
  // arena
  w32_perm_arena = arena_alloc();
  
  // command line arguments
  for (int i = 0; i < argc; i += 1){
    String8 arg = str8_cstring((U8*)argv[i]);
    str8_list_push(w32_perm_arena, &w32_cmd_line, arg);
  }
  
  // paths
  ArenaTemp scratch = arena_get_scratch(0, 0);
  
  // binary path
  {
    DWORD cap = 2048;
    U16 *buffer = 0;
    DWORD size = 0;
    for (U64 r = 0; r < 4; r += 1, cap *= 4){
      U16 *try_buffer = push_array(scratch.arena, U16, cap);
      DWORD try_size = GetModuleFileNameW(0, (WCHAR*)try_buffer, cap);
      if (try_size == cap && GetLastError() == ERROR_INSUFFICIENT_BUFFER){
        arena_end_temp(&scratch);
      }
      else{
        buffer = try_buffer;
        size = try_size;
        break;
      }
    }
    
    String8 full_path = str8_from_str16(scratch.arena, CLiteral(String16){buffer, size});
    String8 binary_path = str8_chop_last_slash(full_path);
    w32_binary_path = str8_push_copy(w32_perm_arena, binary_path);
  }
  
  // user data
  {
    HANDLE token = GetCurrentProcessToken();
    DWORD cap = 2048;
    U16 *buffer = push_array(scratch.arena, U16, cap);
    if (!GetUserProfileDirectoryW(token, (WCHAR*)buffer, &cap)){
      arena_end_temp(&scratch);
      buffer = push_array(scratch.arena, U16, cap + 1);
      if (GetUserProfileDirectoryW(token, (WCHAR*)buffer, &cap)){
        buffer = 0;
      }
    }
    
    if (buffer != 0){
      // the docs make it sound like we can only count on
      // cap getting the size on failure; so we're just going to cstring
      // this to be safe.
      w32_user_path = str8_from_str16(w32_perm_arena, str16_cstring(buffer));
    }
  }
  
  // temp data
  {
    DWORD cap = 2048;
    U16 *buffer = push_array(scratch.arena, U16, cap);
    DWORD size = GetTempPathW(cap, (WCHAR*)buffer);
    if (size >= cap){
      arena_end_temp(&scratch);
      buffer = push_array(scratch.arena, U16, size + 1);
      size = GetTempPathW(size + 1, (WCHAR*)buffer);
    }
    
    // size - 1, because this particular string function
    // in the Win32 API is different from the others and it includes
    // the trailing backslash. We want consistency, so the "- 1" removes it.
    w32_temp_path = str8_from_str16(w32_perm_arena, CLiteral(String16){buffer, size - 1});
  }
  
  arena_release_scratch(&scratch);
  
  ProfEndFunc();
}

MR4TH_SYMBOL String8List
os_command_line_arguments(void){
  String8List result = w32_cmd_line;
  return(result);
}

MR4TH_SYMBOL void
os_exit_process(U32 code){
  ProfClose();
  ExitProcess(code);
}

////////////////////////////////
// Win32 Implementation: Memory Functions

MR4TH_SYMBOL void*
os_memory_reserve(U64 size){
  void *result = VirtualAlloc(0, size, MEM_RESERVE, PAGE_READWRITE);
  return(result);
}

MR4TH_SYMBOL B32
os_memory_commit(void *ptr, U64 size){
  B32 result = (VirtualAlloc(ptr, size, MEM_COMMIT, PAGE_READWRITE) != 0);
  return(result);
}

MR4TH_SYMBOL void
os_memory_decommit(void *ptr, U64 size){
  VirtualFree(ptr, size, MEM_DECOMMIT);
}

MR4TH_SYMBOL void
os_memory_release(void *ptr, U64 size){
  VirtualFree(ptr, 0, MEM_RELEASE);
}

MR4TH_SYMBOL B32
os_memory_protect(void *ptr, U64 size, DataAccessFlags access_flags){
  DWORD protect = PAGE_NOACCESS;
  switch (access_flags&0x7){
    case 0:                                        protect = PAGE_NOACCESS; break;
    case DataAccessFlag_Read:                      protect = PAGE_READONLY; break;
    case DataAccessFlag_Write:
    case DataAccessFlag_Read|DataAccessFlag_Write: protect = PAGE_READWRITE; break;
    case DataAccessFlag_Execute:                                          protect = PAGE_EXECUTE; break;
    case DataAccessFlag_Read|DataAccessFlag_Execute:                      protect = PAGE_EXECUTE_READ; break;
    case DataAccessFlag_Write|DataAccessFlag_Execute:
    case DataAccessFlag_Read|DataAccessFlag_Write|DataAccessFlag_Execute: protect = PAGE_EXECUTE_READWRITE; break;
  }
  
  DWORD dummy = 0;
  B32 result = VirtualProtect(ptr, size, protect, &dummy);
  return(result);
}

////////////////////////////////
// Win32 Implementation: File Handling

MR4TH_SYMBOL String8
os_file_read(Arena *arena, String8 file_name){
  ProfBeginFunc();
  
  // get handle
  ArenaTemp scratch = arena_get_scratch(&arena, 1);
  String16 file_name16 = str16_from_str8(scratch.arena, file_name);
  HANDLE file = CreateFileW((WCHAR*)file_name16.str,
                            GENERIC_READ, 0, 0,
                            OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
                            0);
  
  String8 result = {0};
  if (file != INVALID_HANDLE_VALUE){
    // get size
    DWORD hi_size = 0;
    DWORD lo_size = GetFileSize(file, &hi_size);
    U64 total_size = (((U64)hi_size) << 32) | (U64)lo_size;
    
    // allocate buffer
    ArenaTemp restore_point = arena_begin_temp(arena);
    U8 *buffer = push_array_no_zero(arena, U8, total_size + 1);
    
    // read
    U8 *ptr = buffer;
    U8 *opl = buffer + total_size;
    B32 success = 1;
    for (;ptr < opl;){
      U64 total_to_read = (U64)(opl - ptr);
      DWORD to_read = (DWORD)total_to_read;
      if (total_to_read > max_U32){
        to_read = max_U32;
      }
      DWORD actual_read = 0;
      if (!ReadFile(file, ptr, to_read, &actual_read, 0)){
        success = 0;
        break;
      }
      ptr += actual_read;
    }
    
    // set result or reset memory
    if (success){
      buffer[total_size] = 0;
      result.str = buffer;
      result.size = total_size;
    }
    else{
      arena_end_temp(&restore_point);
    }
    
    CloseHandle(file);
  }
  
  arena_release_scratch(&scratch);
  
  ProfEndFunc();
  return(result);
}

MR4TH_SYMBOL B32
os_file_write_callback(String8 file_name, void *udata, OS_FileWriteCallback *callback){
  ProfBeginFunc();
  
  // get handle
  ArenaTemp scratch = arena_get_scratch(0, 0);
  String16 file_name16 = str16_from_str8(scratch.arena, file_name);
  HANDLE file = CreateFileW((WCHAR*)file_name16.str,
                            GENERIC_WRITE, 0, 0,
                            CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,
                            0);
  
  B32 result = 0;
  if (file != INVALID_HANDLE_VALUE){
    result = 1;
    
    for (;;){
      String8 chunk = callback(udata);
      if (chunk.size == 0){
        break;
      }
      
      U8 *ptr = chunk.str;
      U8 *opl = chunk.str + chunk.size;
      for (;ptr < opl;){
        U64 total_to_write = (U64)(opl - ptr);
        DWORD to_write = total_to_write;
        if (total_to_write > max_U32){
          to_write = max_U32;
        }
        DWORD actual_write = 0;
        if (!WriteFile(file, ptr, to_write, &actual_write, 0)){
          result = 0;
          goto dblbreak;
        }
        ptr += actual_write;
      }
    }
    dblbreak:;
    
    // close file
    CloseHandle(file);
  }
  
  arena_release_scratch(&scratch);
  
  ProfEndFunc();
  return(result);
}

MR4TH_SYMBOL FileProperties
os_file_properties(String8 file_name){
  ProfBeginFunc();
  
  // convert name
  ArenaTemp scratch = arena_get_scratch(0, 0);
  String16 file_name16 = str16_from_str8(scratch.arena, file_name);
  
  // get attribs and convert to properties
  FileProperties result = {0};
  WIN32_FILE_ATTRIBUTE_DATA attribs = {0};
  if (GetFileAttributesExW((WCHAR*)file_name16.str, GetFileExInfoStandard,
                           &attribs)){
    result.size = ((U64)attribs.nFileSizeHigh << 32) | (U64)attribs.nFileSizeLow;
    result.flags = w32_prop_flags_from_attribs(attribs.dwFileAttributes);
    result.create_time = w32_dense_time_from_file_time(&attribs.ftCreationTime);
    result.modify_time = w32_dense_time_from_file_time(&attribs.ftLastWriteTime);
    result.access = w32_access_from_attributes(attribs.dwFileAttributes);
  }
  
  arena_release_scratch(&scratch);
  
  ProfEndFunc();
  return(result);
}

MR4TH_SYMBOL B32
os_file_delete(String8 file_name){
  ProfBeginFunc();
  
  // convert name
  ArenaTemp scratch = arena_get_scratch(0, 0);
  String16 file_name16 = str16_from_str8(scratch.arena, file_name);
  // delete file
  B32 result = DeleteFileW((WCHAR*)file_name16.str);
  arena_release_scratch(&scratch);
  
  ProfEndFunc();
  return(result);
}

MR4TH_SYMBOL B32
os_file_rename(String8 og_name, String8 new_name){
  ProfBeginFunc();
  
  // convert name
  ArenaTemp scratch = arena_get_scratch(0, 0);
  String16 og_name16 = str16_from_str8(scratch.arena, og_name);
  String16 new_name16 = str16_from_str8(scratch.arena, new_name);
  // rename file
  B32 result = MoveFileW((WCHAR*)og_name16.str, (WCHAR*)new_name16.str);
  arena_release_scratch(&scratch);
  
  ProfEndFunc();
  return(result);
}

MR4TH_SYMBOL B32
os_file_make_directory(String8 path){
  ProfBeginFunc();
  
  // convert name
  ArenaTemp scratch = arena_get_scratch(0, 0);
  String16 path16 = str16_from_str8(scratch.arena, path);
  // make directory
  B32 result = CreateDirectoryW((WCHAR*)path16.str, 0);
  arena_release_scratch(&scratch);
  
  ProfEndFunc();
  return(result);
}

MR4TH_SYMBOL B32
os_file_delete_directory(String8 path){
  ProfBeginFunc();
  
  // convert name
  ArenaTemp scratch = arena_get_scratch(0, 0);
  String16 path16 = str16_from_str8(scratch.arena, path);
  // make directory
  B32 result = RemoveDirectoryW((WCHAR*)path16.str);
  arena_release_scratch(&scratch);
  
  ProfEndFunc();
  return(result);
}

MR4TH_SYMBOL OS_FileIter
os_file_iter_init(Arena *arena, String8 path){
  ProfBeginFunc();
  
  // convert name
  String8Node nodes[2];
  String8List list;
  
  nodes[0].string = path;
  nodes[0].next = nodes + 1;
  nodes[1].string = str8_lit("\\*");
  nodes[1].next = 0;
  
  list.first = nodes + 0;
  list.last = nodes + 1;
  list.node_count = 2;
  list.total_size = path.size + 2;
  
  ArenaTemp scratch = arena_get_scratch(&arena, 1);
  String8 path_star = str8_join(scratch.arena, &list, 0);
  // TODO(allen): Better unicode conversions here
  String16 path16 = str16_from_str8(scratch.arena, path_star);
  
  // store into iter
  OS_FileIter result = {0};
  W32_FileIter *w32_iter = (W32_FileIter*)&result;
  w32_iter->handle = FindFirstFileW((WCHAR*)path16.str, &w32_iter->find_data);
  arena_release_scratch(&scratch);
  
  ProfEndFunc();
  return(result);
}

MR4TH_SYMBOL B32
os_file_iter_next(Arena *arena, OS_FileIter *iter,
                  String8 *name, FileProperties *props){
  ProfBeginFunc();
  
  B32 result = 0;
  
  W32_FileIter *w32_iter = (W32_FileIter*)iter;
  if (w32_iter->handle != 0 &&
      w32_iter->handle != INVALID_HANDLE_VALUE){
    for (;!w32_iter->done;){
      // check for . and ..
      WCHAR *file_name = w32_iter->find_data.cFileName;
      B32 is_dot = (file_name[0] == '.' && file_name[1] == 0);
      B32 is_dotdot = (file_name[0] == '.' && file_name[1] == '.' &&
                       file_name[2] == 0);
      
      // setup to emit
      B32 emit = (!is_dot && !is_dotdot);
      WIN32_FIND_DATAW data = {0};
      if (emit){
        MemoryCopyStruct(&data, &w32_iter->find_data);
      }
      
      // increment the iterator
      if (!FindNextFileW(w32_iter->handle, &w32_iter->find_data)){
        w32_iter->done = 1;
      }
      
      // do the emit if we saved one earlier
      if (emit){
        *name = str8_from_str16(arena, str16_cstring((U16*)data.cFileName));
        props->size = ((U64)data.nFileSizeHigh << 32) | (U64)data.nFileSizeLow;
        props->flags = w32_prop_flags_from_attribs(data.dwFileAttributes);
        props->create_time = w32_dense_time_from_file_time(&data.ftCreationTime);
        props->modify_time = w32_dense_time_from_file_time(&data.ftLastWriteTime);
        props->access = w32_access_from_attributes(data.dwFileAttributes);
        result = 1;
        break;
      }
    }
  }
  
  ProfEndFunc();
  return(result);
}

MR4TH_SYMBOL void
os_file_iter_end(OS_FileIter *iter){
  ProfBeginFunc();
  
  W32_FileIter *w32_iter = (W32_FileIter*)iter;
  if (w32_iter->handle != 0 &&
      w32_iter->handle != INVALID_HANDLE_VALUE){
    FindClose(w32_iter->handle);
  }
  
  ProfEndFunc();
}

MR4TH_SYMBOL String8
os_file_path(Arena *arena, OS_SystemPath path){
  ProfBeginFunc();
  
  String8 result = {0};
  
  switch (path){
    case OS_SystemPath_CurrentDirectory:
    {
      ArenaTemp scratch = arena_get_scratch(&arena, 1);
      DWORD cap = 2048;
      U16 *buffer = push_array(scratch.arena, U16, cap);
      DWORD size = GetCurrentDirectoryW(cap, (WCHAR*)buffer);
      if (size >= cap){
        arena_end_temp(&scratch);
        buffer = push_array(scratch.arena, U16, size + 1);
        size = GetCurrentDirectoryW(size + 1, (WCHAR*)buffer);
      }
      result = str8_from_str16(arena, CLiteral(String16){buffer, size});
      arena_release_scratch(&scratch);
    }break;
    
    case OS_SystemPath_Binary:
    {
      result = str8_push_copy(arena, w32_binary_path);
    }break;
    
    case OS_SystemPath_UserData:
    {
      result = str8_push_copy(arena, w32_user_path);
    }break;
    
    case OS_SystemPath_TempData:
    {
      result = str8_push_copy(arena, w32_temp_path);
    }break;
  }
  
  ProfEndFunc();
  return(result);
}

MR4TH_SYMBOL void
os_set_current_directory(String8 path){
  ArenaTemp scratch = arena_get_scratch(0, 0);
  
  String16 path16 = str16_from_str8(scratch.arena, path);
  SetCurrentDirectoryW((WCHAR*)path16.str);
  
  arena_release_scratch(&scratch);
}


////////////////////////////////
// Win32 Implementation: Time

MR4TH_SYMBOL DateTime
os_now_universal_time(void){
  SYSTEMTIME system_time = {0};
  GetSystemTime(&system_time);
  DateTime result = w32_date_time_from_system_time(&system_time);
  return(result);
}

MR4TH_SYMBOL DateTime
os_local_time_from_universal(DateTime *univ_date_time){
  SYSTEMTIME univ_system_time = w32_system_time_from_date_time(univ_date_time);
  FILETIME univ_file_time = {0};
  SystemTimeToFileTime(&univ_system_time, &univ_file_time);
  FILETIME local_file_time = {0};
  FileTimeToLocalFileTime(&univ_file_time, &local_file_time);
  SYSTEMTIME local_system_time = {0};
  FileTimeToSystemTime(&local_file_time, &local_system_time);
  DateTime result = w32_date_time_from_system_time(&local_system_time);
  return(result);
}

MR4TH_SYMBOL DateTime
os_universal_time_from_local(DateTime *local_date_time){
  SYSTEMTIME local_system_time = w32_system_time_from_date_time(local_date_time);
  FILETIME local_file_time = {0};
  SystemTimeToFileTime(&local_system_time, &local_file_time);
  FILETIME univ_file_time = {0};
  LocalFileTimeToFileTime(&local_file_time, &univ_file_time);
  SYSTEMTIME univ_system_time = {0};
  FileTimeToSystemTime(&univ_file_time, &univ_system_time);
  DateTime result = w32_date_time_from_system_time(&univ_system_time);
  return(result);
}

MR4TH_SYMBOL U32
os_time_stamp_32_from_date_time(DateTime *date_time){
  SYSTEMTIME system_time = w32_system_time_from_date_time(date_time);
  FILETIME file_time = {0};
  SystemTimeToFileTime(&system_time, &file_time);
  U64 file_time64 = ( (U64) file_time.dwLowDateTime +
                     ((U64)(file_time.dwHighDateTime) << 32ll)
                     );
  U32 result = (U32) ((file_time64 - 116444736000000000ll)/10000000ll);
  return(result);
}


MR4TH_SYMBOL U64
os_now_ticks(void){
  U64 result = 0;
  LARGE_INTEGER perf_counter = {0};
  if (QueryPerformanceCounter(&perf_counter)){
    result = ((U64)perf_counter.HighPart << 32) | perf_counter.LowPart;
  }
  return(result);
}

MR4TH_SYMBOL void
os_get_microseconds_per_tick(U64 *usecs_out, U64 *ticks_out){
  *usecs_out = Million(1);
  *ticks_out = w32_ticks_per_second;
}

MR4TH_SYMBOL void
os_sleep_milliseconds(U32 t){
  Sleep(t);
}

////////////////////////////////
// Win32 Implementation: Libraries

MR4TH_SYMBOL OS_Library*
os_lib_load(String8 path){
  ArenaTemp scratch = arena_get_scratch(0, 0);
  String16 path16 = str16_from_str8(scratch.arena, path);
  OS_Library *result = (OS_Library*)(LoadLibraryW((WCHAR*)path16.str));
  arena_release_scratch(&scratch);
  return(result);
}

MR4TH_SYMBOL VoidFunc*
os_lib_get_proc(OS_Library *lib, char *name){
  HMODULE module = (HMODULE)(lib);
  VoidFunc *result = (VoidFunc*)(GetProcAddress(module, name));
  return(result);
}

MR4TH_SYMBOL void
os_lib_release(OS_Library *lib){
  HMODULE module = (HMODULE)(lib);
  FreeLibrary(module);
}

MR4TH_SYMBOL OS_Library*
os_lib_from_addr(void *addr){
  HMODULE module = 0;
  GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
                     (LPWSTR)addr, &module);
  return((OS_Library*)module);
}

MR4TH_SYMBOL OS_VRange
os_lib_image_range(OS_Library *lib){
  OS_VRange result = {0};
  HANDLE process = GetCurrentProcess();
  MODULEINFO module_info = {0};
  if (GetModuleInformation(process, (HMODULE)lib,
                           &module_info, sizeof(module_info))){
    result.addr = (U8*)module_info.lpBaseOfDll;
    result.size = module_info.SizeOfImage;
  }
  return(result);
}

////////////////////////////////
// Win32 Implementation: Entropy

MR4TH_SYMBOL void
os_get_entropy(void *data, U64 size){
  HCRYPTPROV prov = 0;
  CryptAcquireContextW(&prov, 0, 0, PROV_DSS, CRYPT_VERIFYCONTEXT);
  CryptGenRandom(prov, size, (BYTE*)data);
  CryptReleaseContext(prov, 0);
}

////////////////////////////////
// Win32 Functions: Specialized Init for WinMain

MR4TH_SYMBOL_STATIC HINSTANCE w32_instance = 0;

MR4TH_SYMBOL void
w32_WinMain_init(HINSTANCE hInstance,
                 HINSTANCE hPrevInstance,
                 LPSTR     lpCmdLine,
                 int       nShowCmd){
  int argc = __argc;
  char **argv = __argv;
  w32_instance = hInstance;
  os_main_init(argc, argv);
}

MR4TH_SYMBOL HINSTANCE
w32_get_instance(void){
  if (w32_instance == 0){
    w32_instance = GetModuleHandle(0);
  }
  return(w32_instance);
}

////////////////////////////////
// Win32 Functions: Time Helpers

MR4TH_SYMBOL DateTime
w32_date_time_from_system_time(SYSTEMTIME *in){
  DateTime result = {0};
  result.year = in->wYear;
  result.mon  = (U8)in->wMonth;
  result.day  = in->wDay;
  result.hour = in->wHour;
  result.min  = in->wMinute;
  result.sec  = in->wSecond;
  result.msec = in->wMilliseconds;
  return(result);
}

MR4TH_SYMBOL SYSTEMTIME
w32_system_time_from_date_time(DateTime *in){
  SYSTEMTIME result = {0};
  result.wYear = in->year;
  result.wMonth = in->mon;
  result.wDay = in->day;
  result.wHour = in->hour;
  result.wMinute = in->min;
  result.wSecond = in->sec;
  result.wMilliseconds = in->msec;
  return(result);
}

MR4TH_SYMBOL DenseTime
w32_dense_time_from_file_time(FILETIME *file_time){
  SYSTEMTIME system_time = {0};
  FileTimeToSystemTime(file_time, &system_time);
  DateTime date_time = w32_date_time_from_system_time(&system_time);
  DenseTime result = dense_time_from_date_time(&date_time);
  return(result);
}

////////////////////////////////
// Win32 Functions: File Helpers

MR4TH_SYMBOL FilePropertyFlags
w32_prop_flags_from_attribs(DWORD attribs){
  FilePropertyFlags result = 0;
  if (attribs & FILE_ATTRIBUTE_DIRECTORY){
    result |= FilePropertyFlag_IsFolder;
  }
  return(result);
}

MR4TH_SYMBOL DataAccessFlags
w32_access_from_attributes(DWORD attribs){
  DataAccessFlags result = DataAccessFlag_Read|DataAccessFlag_Execute;
  if (!(attribs & FILE_ATTRIBUTE_READONLY)){
    result |= DataAccessFlag_Write;
  }
  return(result);
}

#endif /* OS_WINDOWS */

/*******************************
**       End Base Win32       **
*******************************/



/*******************************
**      Begin Base Linux      **
*******************************/

#if OS_LINUX

////////////////////////////////
// Linux Implementation: Global Variables

MR4TH_SYMBOL_STATIC Arena *lnx_perm_arena = 0;
MR4TH_SYMBOL_STATIC String8 lnx_binary_path = {0};
MR4TH_SYMBOL_STATIC String8 lnx_user_path = {0};
MR4TH_SYMBOL_STATIC String8 lnx_temp_path = {0};

MR4TH_SYMBOL_STATIC String8List lnx_cmd_line = {0};

////////////////////////////////
// Linux Implementation: Process Setup

MR4TH_SYMBOL void
os_main_init(int argc, char **argv){
  ProfBeginFunc();
  
  // arena
  lnx_perm_arena = arena_alloc();
  
  // command line arguments
  for (int i = 0; i < argc; i += 1){
    String8 arg = str8_cstring((U8*)argv[i]);
    str8_list_push(lnx_perm_arena, &lnx_cmd_line, arg);
  }
  
  // paths
  ArenaTemp scratch = arena_get_scratch(0, 0);
  
  // binary path
  {
    B32 got_final_result = 0;
    U8 *buffer = 0;
    int size = 0;
    for (U64 cap = PATH_MAX, r = 0; r < 4; cap *= 2, r += 1){
      arena_end_temp(&scratch);
      buffer = push_array_no_zero(scratch.arena, U8, cap);
      size = readlink("/proc/self/exe", (char*)buffer, cap);
      if (size < cap){
        got_final_result = 1;
        break;
      }
    }
    
    if (got_final_result && size > 0){
      String8 full_name = str8(buffer, (U64)size);
      String8 name_chopped = str8_chop_last_slash(full_name);
      lnx_binary_path = str8_push_copy(lnx_perm_arena, name_chopped);
    }
  }
  
  // user data path
  {
    char *home = getenv("HOME");
    lnx_user_path = str8_cstring((U8*)home);
  }
  
  // temp data path
  {
    lnx_temp_path = str8_lit("/var/tmp");
  }
  
  arena_release_scratch(&scratch);
  
  ProfEndFunc();
}

MR4TH_SYMBOL String8List
os_command_line_arguments(void){
  String8List result = lnx_cmd_line;
  return(result);
}

MR4TH_SYMBOL void
os_exit_process(U32 code){
  ProfClose();
  exit((int)code);
}

////////////////////////////////
// Linux Implementation: Memory Functions

MR4TH_SYMBOL void*
os_memory_reserve(U64 size){
  void *result = mmap(0, size, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
  if (result == MAP_FAILED){
    result = 0;
  }
  return(result);
}

MR4TH_SYMBOL B32
os_memory_commit(void *ptr, U64 size){
  B32 result = 0;
  if (mprotect(ptr, size, PROT_READ|PROT_WRITE) == 0){
    result = 1;
  }
  return(result);
}

MR4TH_SYMBOL void
os_memory_decommit(void *ptr, U64 size){
  madvise(ptr, size, MADV_DONTNEED);
  mprotect(ptr, size, PROT_NONE);
}

MR4TH_SYMBOL void
os_memory_release(void *ptr, U64 size){
  munmap(ptr, size);
}

MR4TH_SYMBOL B32
os_memory_protect(void *ptr, U64 size, DataAccessFlags access_flags){
  int prot = PROT_NONE;
  if ((access_flags&DataAccessFlag_Read) == 0){
    prot |= PROT_READ;
  }
  if ((access_flags&DataAccessFlag_Write) == 0){
    prot |= PROT_WRITE;
  }
  if ((access_flags&DataAccessFlag_Execute) == 0){
    prot |= PROT_EXEC;
  }
  
  B32 result = (mprotect(ptr, size, prot));
  return(result);
}

////////////////////////////////
// Linux Implementation: File Handling

MR4TH_SYMBOL String8
os_file_read(Arena *arena, String8 file_name){
  ProfBeginFunc();
  
  // get handle
  ArenaTemp scratch = arena_get_scratch(&arena, 1);
  //  (this ensures file_name8 has a null terminator)
  String8 file_name8 = str8_push_copy(scratch.arena, file_name);
  int file = open((char*)file_name8.str, O_RDONLY);
  
  String8 result = {0};
  if (file >= 0){
    // get size
    struct stat file_stat = {0};
    U64 size = 0;
    if (fstat(file, &file_stat) == 0){
      size = file_stat.st_size;
    }
    
    // allocate buffer
    if (size > 0){
      ArenaTemp restore_point = arena_begin_temp(arena);
      U8 *buffer = push_array_no_zero(arena, U8, size + 1);
      
      // read
      U8 *ptr = buffer;
      U8 *opl = buffer + size;
      B32 success = 1;
      for (;ptr < opl;){
        U64 total_to_read = (U64)(opl - ptr);
        U32 to_read = (U32)total_to_read;
        if (total_to_read > max_U32){
          to_read = max_U32;
        }
        int read_result = read(file, buffer, to_read);
        if (read_result < 0){
          success = 0;
          break;
        }
        else if (read_result == 0){
          break;
        }
        ptr += read_result;
      }
      
      // set result or reset memory
      if (success){
        U64 final_size = (U64)(ptr - buffer);
        final_size = ClampTop(final_size, size);
        buffer[final_size] = 0;
        result.str = buffer;
        result.size = final_size;
      }
      else{
        arena_end_temp(&restore_point);
      }
    }
    
    // close file
    close(file);
  }
  
  arena_release_scratch(&scratch);
  
  ProfEndFunc();
  return(result);
}

MR4TH_SYMBOL B32
os_file_write_callback(String8 file_name, void *udata, OS_FileWriteCallback *callback){
  ProfBeginFunc();
  
  // get handle
  ArenaTemp scratch = arena_get_scratch(0, 0);
  String8 file_name8 = str8_push_copy(scratch.arena, file_name);
  int file = open((char*)file_name8.str, O_CREAT|O_WRONLY|O_TRUNC, 0666);
  
  B32 result = 0;
  if (file >= 0){
    result = 1;
    
    for (;;){
      String8 chunk = callback(udata);
      if (chunk.size == 0){
        break;
      }
      
      U8 *ptr = chunk.str;
      U8 *opl = chunk.str + chunk.size;
      for (;ptr < opl;){
        U64 total_to_write = (U64)(opl - ptr);
        U32 to_write = total_to_write;
        if (total_to_write > (U32)SSIZE_MAX){
          to_write = (U32)SSIZE_MAX;
        }
        int write_result = write(file, ptr, to_write);
        
        if (write_result <= 0){
          result = 0;
          goto dblbreak;
        }
        ptr += write_result;
      }
      
    }
    dblbreak:;
    
    // close file
    close(file);
  }
  
  arena_release_scratch(&scratch);
  
  ProfEndFunc();
  return(result);
}

MR4TH_SYMBOL FileProperties
os_file_properties(String8 file_name){
  ProfBeginFunc();
  
  // convert name
  ArenaTemp scratch = arena_get_scratch(0, 0);
  //  (this ensures file_name8 has a null terminator)
  String8 file_name8 = str8_push_copy(scratch.arena, file_name);
  int file = open((char*)file_name8.str, O_RDONLY);
  
  // get file stat and convert to properties
  FileProperties result = {0};
  struct stat file_stat = {0};
  if (fstat(file, &file_stat) == 0){
    result = lnx_file_properties_from_stat(&file_stat);
  }
  
  arena_release_scratch(&scratch);
  
  ProfEndFunc();
  return(result);
}

MR4TH_SYMBOL B32
os_file_delete(String8 file_name){
  ProfBeginFunc();
  
  ArenaTemp scratch = arena_get_scratch(0, 0);
  String8 file_name8 = str8_push_copy(scratch.arena, file_name);
  
  B32 result = 0;
  if (remove((char*)file_name8.str) >= 0){
    result = 1;
  }
  
  arena_end_temp(&scratch);
  
  ProfEndFunc();
  return(result);
}

MR4TH_SYMBOL B32
os_file_rename(String8 og_name, String8 new_name){
  ProfBeginFunc();
  
  ArenaTemp scratch = arena_get_scratch(0, 0);
  String8 og_name8  = str8_push_copy(scratch.arena, og_name);
  String8 new_name8 = str8_push_copy(scratch.arena, new_name);
  
  int rename_result = rename((char*)og_name8.str, (char*)new_name8.str);
  B32 result = (rename_result >= 0);
  
  arena_release_scratch(&scratch);
  
  ProfEndFunc();
  return(result);
}

MR4TH_SYMBOL B32
os_file_make_directory(String8 path){
  ProfBeginFunc();
  
  ArenaTemp scratch = arena_get_scratch(0, 0);
  String8 path8 = str8_push_copy(scratch.arena, path);
  
  B32 result = 0;
  if (mkdir((char*)path8.str, 0755) >= 0){
    result = 1;
  }
  
  arena_release_scratch(&scratch);
  
  ProfEndFunc();
  return(result);
}

MR4TH_SYMBOL B32
os_file_delete_directory(String8 path){
  ProfBeginFunc();
  
  ArenaTemp scratch = arena_get_scratch(0, 0);
  String8 path8 = str8_push_copy(scratch.arena, path);
  
  B32 result = 0;
  if (rmdir((char*)path8.str) >= 0){
    result = 1;
  }
  
  arena_release_scratch(&scratch);
  
  ProfEndFunc();
  return(result);
}

MR4TH_SYMBOL OS_FileIter
os_file_iter_init(Arena *arena, String8 path){
  ProfBeginFunc();
  
  String8 path8 = str8_push_copy(arena, path);
  
  OS_FileIter result = {0};
  LNX_FileIter *lnx_iter = (LNX_FileIter*)&result;
  lnx_iter->dir = opendir((char*)path8.str);
  lnx_iter->path = path8;
  
  ProfEndFunc();
  return(result);
}

MR4TH_SYMBOL B32
os_file_iter_next(Arena *arena, OS_FileIter *iter,
                  String8 *name, FileProperties *props){
  B32 result = 0;
  LNX_FileIter *lnx_iter = (LNX_FileIter*)iter;
  
  String8 path = lnx_iter->path;
  
  for (;lnx_iter->dir != 0;){
    struct dirent *dp = readdir(lnx_iter->dir);
    result = (dp != 0);
    if (!result){
      break;
    }
    
    String8 file_name = str8_cstring((U8*)dp->d_name);
    
    struct stat st = {0};
    int stat_result = 0;
    {
      ArenaTemp scratch = arena_get_scratch(&arena, 1);
      
      String8 full_path = {0};
      full_path.size = path.size + 1 + file_name.size;
      full_path.str  = push_array_no_zero(scratch.arena, U8, full_path.size + 1);
      MemoryCopy(full_path.str, path.str, path.size);
      full_path.str[path.size] = '/';
      MemoryCopy(full_path.str + path.size + 1, file_name.str, file_name.size);
      full_path.str[full_path.size] = 0;
      
      stat_result = stat((char*)full_path.str, &st);
      
      arena_release_scratch(&scratch);
    }
    
    B32 filtered = 0;
    if (file_name.str != 0 &&
        ((file_name.str[0] == '.' && file_name.str[1] == 0) ||
         (file_name.str[0] == '.' && file_name.str[1] == '.' && file_name.str[2] == 0))){
      filtered = 1;
    }
    
    if (!filtered){
      *name = str8_push_copy(arena, file_name);
      if (stat_result >= 0){
        *props = lnx_file_properties_from_stat(&st);
      }
      result = 1;
      break;
    }
  }
  
  return(result);
}

MR4TH_SYMBOL void
os_file_iter_end(OS_FileIter *iter){
  ProfBeginFunc();
  
  LNX_FileIter *lnx_iter = (LNX_FileIter*)iter;
  if (lnx_iter->dir != 0){
    closedir(lnx_iter->dir);
    lnx_iter->dir = 0;
  }
  
  ProfEndFunc();
}

MR4TH_SYMBOL String8
os_file_path(Arena *arena, OS_SystemPath path){
  ProfBeginFunc();
  
  String8 result = {0};
  
  switch (path){
    case OS_SystemPath_CurrentDirectory:
    {
      char *cwd = getcwd(0, 0);
      result = str8_push_copy(arena, str8_cstring((U8*)cwd));
      free(cwd);
    }break;
    
    case OS_SystemPath_Binary:
    {
      result = str8_push_copy(arena, lnx_binary_path);
    }break;
    
    case OS_SystemPath_UserData:
    {
      result = str8_push_copy(arena, lnx_user_path);
    }break;
    
    case OS_SystemPath_TempData:
    {
      result = str8_push_copy(arena, lnx_temp_path);
    }break;
  }
  
  ProfEndFunc();
  return(result);
}

MR4TH_SYMBOL void
os_set_current_directory(String8 path){
  ArenaTemp scratch = arena_get_scratch(0, 0);
  String8 path8 = str8_push_copy(scratch.arena, path);
  chdir((char*)path8.str);
  arena_release_scratch(&scratch);
}


////////////////////////////////
// Linux Implementation: Time

MR4TH_SYMBOL DateTime
os_now_universal_time(void){
  time_t t = time(0);
  struct tm tm = {0};
  struct tm *tmptr = gmtime_r(&t, &tm);
  DateTime result = {0};
  if (tmptr != 0){
    result = lnx_date_time_from_tm(&tm);
  }
  return(result);
}

MR4TH_SYMBOL DateTime
os_local_time_from_universal(DateTime *date_time){
  struct tm univ_tm = lnx_tm_from_date_time(date_time);
  univ_tm.tm_isdst = -1;
  time_t univ_time_t = timegm(&univ_tm);
  struct tm local_tm = {0};
  localtime_r(&univ_time_t, &local_tm);
  DateTime result = lnx_date_time_from_tm(&local_tm);
  return(result);
}

MR4TH_SYMBOL DateTime
os_universal_time_from_local(DateTime *date_time){
  struct tm local_tm = lnx_tm_from_date_time(date_time);
  local_tm.tm_isdst = -1;
  time_t univ_time_t = mktime(&local_tm);
  struct tm univ_tm = {0};
  gmtime_r(&univ_time_t, &univ_tm);
  DateTime result = lnx_date_time_from_tm(&univ_tm);
  return(result);
}

MR4TH_SYMBOL U32
os_time_stamp_32_from_date_time(DateTime *date_time){
  struct tm tm = lnx_tm_from_date_time(date_time);
  time_t t = timegm(&tm);
  return((U32)t);
}

MR4TH_SYMBOL U64
os_now_ticks(void){
  struct timespec t = {0};
  clock_gettime(CLOCK_MONOTONIC, &t);
  U64 result = t.tv_sec*Million(1) + t.tv_nsec/Thousand(1);
  return(result);
}

MR4TH_SYMBOL void
os_get_microseconds_per_tick(U64 *usecs_out, U64 *ticks_out){
  *usecs_out = 1;
  *ticks_out = 1;
}

MR4TH_SYMBOL void
os_sleep_milliseconds(U32 t){
  usleep(t*Thousand(1));
}

////////////////////////////////
// Linux Implementation: Libraries

MR4TH_SYMBOL OS_Library*
os_lib_load(String8 path){
  ArenaTemp scratch = arena_get_scratch(0, 0);
  String8 path8 = str8_push_copy(scratch.arena, path);
  OS_Library *result = (OS_Library*)dlopen((char*)path8.str, RTLD_LAZY|RTLD_LOCAL);
  arena_release_scratch(&scratch);
  return(result);
}

MR4TH_SYMBOL VoidFunc*
os_lib_get_proc(OS_Library *lib, char *name){
  VoidFunc *proc = (VoidFunc*)dlsym((void*)lib, name);
  return(proc);
}

MR4TH_SYMBOL void
os_lib_release(OS_Library *lib){
  dlclose((void*)lib);
}

MR4TH_SYMBOL OS_Library*
os_lib_from_addr(void *addr){
  UAddr addrx = IntFromPtr(addr);
  ArenaTemp scratch = arena_get_scratch(0, 0);
  
  // NOTE(allen): 
  // Implementing this on Linux is a lot less solidly
  // supported than it is on Windows.
  //
  // As far as I can tell, reading "/proc/self/maps" is the
  // only portable way to get at the information. The fact
  // that this information is in only available in this text
  // based unaccelerated structured is unfortunate.
  //
  // But even worse is the fact that, even with this info,
  // to actually get one of the opaque handles to the
  // library, we have to call dlopen again -- no other API
  // appears to exist which can return the required information.
  //
  // But that also means that the library gets a reference
  // count from this call and so we have to then call dlclose
  // on the handle just to decrement that reference counter.
  //
  // I don't even know yet if this handles the base executable
  // of the program correctly.
  
  // read "/proc/self/maps"
  B32 got_result = 0;
  U8 *buffer = 0;
  int size = 0;
  for (U64 cap = KB(4), r = 0; r < 6; cap *= 64, r += 1){
    arena_end_temp(&scratch);
    buffer = push_array_no_zero(scratch.arena, U8, cap);
    size = readlink("/proc/self/maps", (char*)buffer, cap);
    if (size < cap){
      got_result = 1;
      break;
    }
  }
  
  // parse contents from "/proc/self/maps"
  String8 contents = str8(buffer, (U64)size);
  String8List lines = str8_split(scratch.arena, contents, (U8*)"\n", 1);
  for (String8Node *node = lines.first;
       node != 0;
       node = node->next){
    String8 line = node->string;
    U8 *opl = line.str + line.size;
    
    
    U8 *dash_ptr = line.str;
    for (;dash_ptr < opl && *dash_ptr != '-'; dash_ptr += 1);
    
    U8 *addr_max_opl = dash_ptr;
    for (;dash_ptr < opl && *dash_ptr != ' '; dash_ptr += 1);
    
    U8 *slash_ptr = addr_max_opl;
    for (;slash_ptr < opl && *slash_ptr != '/'; slash_ptr += 1);
    
    U64 addr_min = u64_from_str8(str8_range(line.str, dash_ptr), 0x10);
    U64 addr_max = u64_from_str8(str8_range(dash_ptr + 1, addr_max_opl), 0x10);
    String8 path = str8_range(slash_ptr, opl);
    
    
  }
  
  push_array_no_zero(scratch.arena, U8, size);
  
  arena_release_scratch(&scratch);
  
  OS_Library *library = 0;
  return(library);
}

MR4TH_SYMBOL OS_VRange
os_lib_image_range(OS_Library *lib){
  OS_VRange result = {0};
  // TODO(allen): 
  return(result);
}

////////////////////////////////
// Linux Implementation: Entropy

MR4TH_SYMBOL void
os_get_entropy(void *data, U64 size){
  getrandom(data, size, 0);
}

////////////////////////////////
// Linux Functions: Time Helpers

MR4TH_SYMBOL DateTime
lnx_date_time_from_tm(struct tm *tm){
  DateTime result = {0};
  result.year = tm->tm_year + 1900;
  result.mon  = tm->tm_mon + 1;
  result.day  = tm->tm_mday;
  result.hour = tm->tm_hour;
  result.min  = tm->tm_min;
  result.sec  = tm->tm_sec;
  result.msec = 0;
  return(result);
}

MR4TH_SYMBOL DenseTime
lnx_dense_time_from_time_t(time_t time){
  struct tm system_time = *gmtime(&time);
  DateTime date_time = lnx_date_time_from_tm(&system_time);
  DenseTime result = dense_time_from_date_time(&date_time);
  return(result);
}

MR4TH_SYMBOL struct tm
lnx_tm_from_date_time(DateTime *date_time){
  struct tm result = {0};
  result.tm_sec  = date_time->sec;
  result.tm_min  = date_time->min;
  result.tm_hour = date_time->hour;
  result.tm_mday = date_time->day;
  result.tm_mon  = date_time->mon - 1;
  result.tm_year = date_time->year;
  return(result);
}

////////////////////////////////
// Linux Functions: File Helpers

MR4TH_SYMBOL FilePropertyFlags
lnx_prop_flags_from_mode(mode_t mode){
  FilePropertyFlags result = 0;
  if ((mode&S_IFDIR) != 0){
    result |= FilePropertyFlag_IsFolder;
  }
  return(result);
}

MR4TH_SYMBOL DataAccessFlags
lnx_access_from_mode(mode_t mode){
  DataAccessFlags result = 0;
  if ((mode&(S_IRUSR|S_IRGRP)) != 0){
    result |= DataAccessFlag_Read;
  }
  if ((mode&(S_IWUSR|S_IWGRP)) != 0){
    result |= DataAccessFlag_Write;
  }
  if ((mode&(S_IXUSR|S_IXGRP)) != 0){
    result |= DataAccessFlag_Execute;
  }
  return(result);
}

MR4TH_SYMBOL FileProperties
lnx_file_properties_from_stat(struct stat *st){
  FileProperties result = {0};
  result.size = st->st_size;
  result.flags = lnx_prop_flags_from_mode(st->st_mode);
  result.create_time = lnx_dense_time_from_time_t(st->st_ctime);
  result.modify_time = lnx_dense_time_from_time_t(st->st_mtime);
  // TODO(allen): research:
  // how do these flags from fstat behave?
  //  S_IRUSR, S_IWUSR, S_IXUSR
  //  S_IRGRP, S_IWGRP, S_IXGRP
  //  S_IROTH, S_IWOTH, S_IXOTH
  result.access = lnx_access_from_mode(st->st_mode);
  return(result);
}

MR4TH_SYMBOL String8
lnx_proc_file_read(Arena *arena, char *path){
  ArenaTemp scratch = arena_get_scratch(&arena, 1);
  
  int file = open(path, O_RDONLY);
  
  String8List list = {0};
  if (file >= 0){
    S64 cap = KB(4);
    for (;;){
      U8 *buffer = push_array_no_zero(scratch.arena, U8, cap);
      int size = read(file, buffer, cap);
      if (size > 0){
        str8_list_push(scratch.arena, &list, str8(buffer, (U64)size));
      }
      if (size < cap){
        break;
      }
      cap *= 4;
    }
  }
  
  String8 result = str8_join(arena, &list, 0);
  
  arena_release_scratch(&scratch);
  
  return(result);
}

#endif /* OS_LINUX */

/*******************************
**       End Base Linux       **
*******************************/


////////////////////////////////////////////////
////////////////////////////////////////////////
/////////////////   M4 Printf   ////////////////
////////////////////////////////////////////////
////////////////////////////////////////////////

// stb_sprintf.h STB_SPRINTF_IMPLEMENTATION

#define stbsp__uint32 U32
#define stbsp__int32  S32
#define stbsp__uint64 U64
#define stbsp__int64  S64
#define stbsp__uint16 U16
#define stbsp__uintptr UAddr

// NOTE: modification - removed STB_SPRINTF_MSVC_MODE -- compatibility mode for behavior from MSVC2013 and earlier

#ifdef STB_SPRINTF_NOUNALIGNED // define this before inclusion to force stbsp_sprintf to always use aligned accesses
#define STBSP__UNALIGNED(code)
#else
#define STBSP__UNALIGNED(code) code
#endif

// internal float utility functions
static stbsp__int32 stbsp__real_to_str(char const **start, stbsp__uint32 *len, char *out, stbsp__int32 *decimal_pos, double value, stbsp__uint32 frac_digits);
static stbsp__int32 stbsp__real_to_parts(stbsp__int64 *bits, stbsp__int32 *expo, double value);
#define STBSP__SPECIAL 0x7000

static char stbsp__period = '.';
static char stbsp__comma = ',';
static struct{
  short temp; // force next field to be 2-byte aligned
  char pair[201];
} stbsp__digitpair =
{
  0,
  "00010203040506070809101112131415161718192021222324"
    "25262728293031323334353637383940414243444546474849"
    "50515253545556575859606162636465666768697071727374"
    "75767778798081828384858687888990919293949596979899"
};

STBSP__PUBLICDEF void m4_set_separators(char pcomma, char pperiod)
{
  stbsp__period = pperiod;
  stbsp__comma = pcomma;
}

#define STBSP__LEFTJUST 1
#define STBSP__LEADINGPLUS 2
#define STBSP__LEADINGSPACE 4
#define STBSP__LEADING_0X 8
#define STBSP__LEADINGZERO 16
#define STBSP__INTMAX 32
#define STBSP__TRIPLET_COMMA 64
#define STBSP__NEGATIVE 128
#define STBSP__MEMORY_SIZES 256
#define STBSP__HALFWIDTH 512

static void stbsp__lead_sign(stbsp__uint32 fl, char *sign)
{
  sign[0] = 0;
  if (fl & STBSP__NEGATIVE) {
    sign[0] = 1;
    sign[1] = '-';
  } else if (fl & STBSP__LEADINGSPACE) {
    sign[0] = 1;
    sign[1] = ' ';
  } else if (fl & STBSP__LEADINGPLUS) {
    sign[0] = 1;
    sign[1] = '+';
  }
}

static STBSP__ASAN stbsp__uint32 stbsp__strlen_limited(char const *s, stbsp__uint32 limit)
{
  char const * sn = s;
  
  // get up to 4-byte alignment
  for (;;) {
    if (((stbsp__uintptr)sn & 3) == 0)
      break;
    
    if (!limit || *sn == 0)
      return (stbsp__uint32)(sn - s);
    
    ++sn;
    --limit;
  }
  
  // scan over 4 bytes at a time to find terminating 0
  // this will intentionally scan up to 3 bytes past the end of buffers,
  // but becase it works 4B aligned, it will never cross page boundaries
  // (hence the STBSP__ASAN markup; the over-read here is intentional
  // and harmless)
  while (limit >= 4) {
    stbsp__uint32 v = *(stbsp__uint32 *)sn;
    // bit hack to find if there's a 0 byte in there
    if ((v - 0x01010101) & (~v) & 0x80808080UL)
      break;
    
    sn += 4;
    limit -= 4;
  }
  
  // handle the last few characters to find actual size
  while (limit && *sn) {
    ++sn;
    --limit;
  }
  
  return (stbsp__uint32)(sn - s);
}

STBSP__PUBLICDEF int m4_vsprintfcb(STBSP_SPRINTFCB *callback, void *user, char *buf, char const *fmt, va_list va)
{
  ArenaTemp scratch = arena_get_scratch(0, 0);
  
  static char hex[]  = "0123456789abcdefxp";
  static char hexu[] = "0123456789ABCDEFXP";
  static char spaces[] =
    "                                "
    "                                "
    "                                "
    "                                ";
  char *bf = buf;
  char const *f = fmt;
  int tlen = 0;
  
  for (;;){
    stbsp__int32 field_width;
    stbsp__int32 precision;
    stbsp__int32 tz;
    stbsp__uint32 flags;
    
    // macros for the callback buffer stuff
#define stbsp__chk_cb_bufL(bytes)                        \
{                                                     \
int len = (int)(bf - buf);                         \
if ((len + (bytes)) >= STB_SPRINTF_MIN){          \
tlen += len;                                    \
if (0 == (bf = buf = callback(buf, user, len))) goto done; \
}                                                  \
}
    
#define stbsp__chk_cb_buf(bytes)    \
{                                \
if (callback) {               \
stbsp__chk_cb_bufL(bytes); \
}                             \
}
    
#define stbsp__flush_cb()                      \
{                                           \
stbsp__chk_cb_bufL(STB_SPRINTF_MIN - 1); \
} // flush if there is even one byte in the buffer
    
#define stbsp__cb_buf_clamp(cl, v)                \
cl = v;                                        \
if (callback) {                                \
int lg = STB_SPRINTF_MIN - (int)(bf - buf); \
if (cl > lg)                                \
cl = lg;                                 \
}
    
    // fast copy everything up to the next % (or end of string)
    for (;;) {
      while (((stbsp__uintptr)f) & 3) {
        schk1:
        if (f[0] == '%') goto scandd;
        schk2:
        if (f[0] == 0) goto endfmt;
        stbsp__chk_cb_buf(1);
        *bf++ = f[0];
        ++f;
      }
      for (;;) {
        // Check if the next 4 bytes contain %(0x25) or end of string.
        // Using the 'hasless' trick:
        // https://graphics.stanford.edu/~seander/bithacks.html#HasLessInWord
        stbsp__uint32 v, c;
        v = *(stbsp__uint32 *)f;
        c = (~v) & 0x80808080;
        if (((v ^ 0x25252525) - 0x01010101) & c)
          goto schk1;
        if ((v - 0x01010101) & c)
          goto schk2;
        if (callback)
          if ((STB_SPRINTF_MIN - (int)(bf - buf)) < 4)
          goto schk1;
#ifdef STB_SPRINTF_NOUNALIGNED
        if(((stbsp__uintptr)bf) & 3) {
          bf[0] = f[0];
          bf[1] = f[1];
          bf[2] = f[2];
          bf[3] = f[3];
        } else
#endif
        {
          *(stbsp__uint32 *)bf = v;
        }
        bf += 4;
        f += 4;
      }
    }
    scandd:
    
    ++f;
    
    // ok, we have a percent, read the modifiers first
    field_width = 0;
    precision = -1;
    flags = 0;
    tz = 0;
    
    // flags
    for (;;) {
      switch (f[0]) {
        // if we have left justify
        case '-':
        flags |= STBSP__LEFTJUST;
        ++f;
        continue;
        // if we have leading plus
        case '+':
        flags |= STBSP__LEADINGPLUS;
        ++f;
        continue;
        // if we have leading space
        case ' ':
        flags |= STBSP__LEADINGSPACE;
        ++f;
        continue;
        // if we have leading 0x
        case '#':
        flags |= STBSP__LEADING_0X;
        ++f;
        continue;
        // if we have thousand commas
        case '\'':
        flags |= STBSP__TRIPLET_COMMA;
        ++f;
        continue;
        // if we have memory sizes
        case '$':
        flags |= STBSP__MEMORY_SIZES;
        ++f;
        continue;
        // if we have leading zero
        case '0':
        flags |= STBSP__LEADINGZERO;
        ++f;
        goto flags_done;
        default: goto flags_done;
      }
    }
    flags_done:
    
    // get the field width
    if (f[0] == '*'){
      field_width = va_arg(va, stbsp__uint32);
      ++f;
    }
    else{
      while ((f[0] >= '0') && (f[0] <= '9')) {
        field_width = field_width*10 + f[0] - '0';
        f++;
      }
    }
    
    // get the precision
    if (f[0] == '.'){
      ++f;
      if (f[0] == '*'){
        precision = va_arg(va, stbsp__uint32);
        ++f;
      }
      else{
        precision = 0;
        while ((f[0] >= '0') && (f[0] <= '9')){
          precision = precision*10 + f[0] - '0';
          f++;
        }
      }
    }
    
    // handle integer size overrides
    switch (f[0]){
      // are we halfwidth?
      case 'h':
      flags |= STBSP__HALFWIDTH;
      ++f;
      if (f[0] == 'h')
        ++f;  // QUARTERWIDTH
      break;
      // are we 64-bit (unix style)
      case 'l':
      flags |= ((sizeof(long) == 8) ? STBSP__INTMAX : 0);
      ++f;
      if (f[0] == 'l') {
        flags |= STBSP__INTMAX;
        ++f;
      }
      break;
      // are we 64-bit on intmax? (c99)
      case 'j':
      flags |= (sizeof(size_t) == 8) ? STBSP__INTMAX : 0;
      ++f;
      break;
      // are we 64-bit on size_t or ptrdiff_t? (c99)
      case 'z':
      flags |= (sizeof(ptrdiff_t) == 8) ? STBSP__INTMAX : 0;
      ++f;
      break;
      case 't':
      flags |= (sizeof(ptrdiff_t) == 8) ? STBSP__INTMAX : 0;
      ++f;
      break;
      // are we 64-bit (msft style)
      case 'I':
      if ((f[1] == '6') && (f[2] == '4')) {
        flags |= STBSP__INTMAX;
        f += 3;
      } else if ((f[1] == '3') && (f[2] == '2')) {
        f += 3;
      } else {
        flags |= ((sizeof(void *) == 8) ? STBSP__INTMAX : 0);
        ++f;
      }
      break;
      default: break;
    }
    
    // handle each replacement
    switch (f[0]){
#define STBSP__NUMSZ 512 // big enough for e308 (with commas) or e-307
      char num[STBSP__NUMSZ];
      char lead[8];
      char tail[8];
      char *s;
      char const *h;
      stbsp__uint32 l;
      stbsp__uint32 n;
      stbsp__uint32 cs;
      stbsp__uint64 n64;
      double fv;
      stbsp__int32 dp;
      char const *sn;
      
      // NOTE(allen): MODIFICATION
      // %S prints str (parameter: String8 str)
      case 'S':
      {
        String8 *str = va_arg(va, String8*);
        s = (char*)str->str;
        l = (precision >= 0 && precision < str->size) ? precision : (U32)str->size;
        lead[0] = 0;
        tail[0] = 0;
        precision = 0;
        dp = 0;
        cs = 0;
        goto scopy;
      }
      
      // NOTE(allen): MODIFICATION
      // %N prints n spaces (parameter: int n)
      case 'N':
      {
        // TODO(allen): there's probably a better way to set this
        // up so that it doesn't rely on the buffer of spaces -
        // perhaps a way to apply the space filling in the left justify path?
        l = va_arg(va, int);
        l = (sizeof(spaces) - 1 < l) ? (sizeof(spaces) - 1) : l;
        l = (precision >= 0 && precision < l) ? precision : l;
        s = spaces;
        lead[0] = 0;
        tail[0] = 0;
        precision = 0;
        dp = 0;
        cs = 0;
        goto scopy;
      }
      
      // NOTE(allen): MODIFICATION
      // %R prints n copies of c (parameter: int n, int c)
      case 'R':
      {char c;
        l = va_arg(va, int);
        c = (char)va_arg(va, int);
        l = (precision >= 0 && precision < l) ? precision : l;
        s = push_array_no_zero(scratch.arena, char, l);
        for (char *sptr = s; sptr < s + l; sptr += 1){
          *sptr = (char)c;
        }
        lead[0] = 0;
        tail[0] = 0;
        precision = 0;
        dp = 0;
        cs = 0;
        goto scopy;
      }
      
      case 's':
      {
        // get the string
        s = va_arg(va, char *);
        if (s == 0){
          s = (char *)"null";
        }
        // get the length, limited to desired precision
        // always limit to ~0u chars since our counts are 32b
        l = stbsp__strlen_limited(s, (precision >= 0) ? precision : ~0u);
        lead[0] = 0;
        tail[0] = 0;
        precision = 0;
        dp = 0;
        cs = 0;
        // copy the string in
        goto scopy;
      }
      
      case 'c': // char
      {
        // get the character
        s = num + STBSP__NUMSZ - 1;
        *s = (char)va_arg(va, int);
        l = 1;
        lead[0] = 0;
        tail[0] = 0;
        precision = 0;
        dp = 0;
        cs = 0;
        goto scopy;
      }
      
      case 'n': // weird write-bytes specifier
      {
        int *d = va_arg(va, int *);
        *d = tlen + (int)(bf - buf);
      } break;
      
      case 'A': // hex float
      case 'a': // hex float
      {
        h = (f[0] == 'A') ? hexu : hex;
        fv = va_arg(va, double);
        if (precision == -1){
          precision = 6; // default is 6
        }
        // read the double into a string
        if (stbsp__real_to_parts((stbsp__int64 *)&n64, &dp, fv)){
          flags |= STBSP__NEGATIVE;
        }
        
        s = num + 64;
        
        stbsp__lead_sign(flags, lead);
        
        if (dp == -1023){
          dp = (n64) ? -1022 : 0;
        }
        else{
          n64 |= (((stbsp__uint64)1) << 52);
        }
        n64 <<= (64 - 56);
        if (precision < 15){
          n64 += ((((stbsp__uint64)8) << 56) >> (precision*4));
        }
        // add leading chars
        
        lead[1 + lead[0]] = '0';
        lead[2 + lead[0]] = 'x';
        lead[0] += 2;
        
        *s++ = h[(n64 >> 60) & 15];
        n64 <<= 4;
        if (precision){
          *s++ = stbsp__period;
        }
        sn = s;
        
        // print the bits
        n = precision;
        if (n > 13){
          n = 13;
        }
        if (precision > (stbsp__int32)n){
          tz = precision - n;
        }
        precision = 0;
        while (n--) {
          *s++ = h[(n64 >> 60) & 15];
          n64 <<= 4;
        }
        
        // print the expo
        tail[1] = h[17];
        if (dp < 0){
          tail[2] = '-';
          dp = -dp;
        }
        else{
          tail[2] = '+';
        }
        n = (dp >= 1000) ? 6 : ((dp >= 100) ? 5 : ((dp >= 10) ? 4 : 3));
        tail[0] = (char)n;
        for (;;) {
          tail[n] = '0' + dp % 10;
          if (n <= 3) break;
          --n;
          dp /= 10;
        }
        
        dp = (int)(s - sn);
        l = (int)(s - (num + 64));
        s = num + 64;
        cs = 1 + (3 << 24);
        goto scopy;
      }
      
      case 'G': // float
      case 'g': // float
      {
        h = (f[0] == 'G') ? hexu : hex;
        fv = va_arg(va, double);
        if (precision == -1){
          precision = 6;
        }
        else if (precision == 0){
          precision = 1; // default is 6
        }
        // read the double into a string
        if (stbsp__real_to_str(&sn, &l, num, &dp, fv, (precision - 1) | 0x80000000)){
          flags |= STBSP__NEGATIVE;
        }
        
        // clamp the precision and delete extra zeros after clamp
        n = precision;
        if (l > (stbsp__uint32)precision){
          l = precision;
        }
        while ((l > 1) && (precision) && (sn[l - 1] == '0')){
          --precision;
          --l;
        }
        
        // should we use %e
        if ((dp <= -4) || (dp > (stbsp__int32)n)) {
          if (precision > (stbsp__int32)l){
            precision = l - 1;
          }
          else if (precision){
            --precision; // when using %e, there is one digit before the decimal
          }
          goto doexpfromg;
        }
        // this is the insane action to get the precision to match %g semantics for %f
        if (dp > 0){
          precision = (dp < (stbsp__int32)l) ? l - dp : 0;
        }
        else{
          precision = -dp + ((precision > (stbsp__int32)l) ? (stbsp__int32) l : precision);
        }
        goto dofloatfromg;
      }
      
      case 'E': // float
      case 'e': // float
      {
        h = (f[0] == 'E') ? hexu : hex;
        fv = va_arg(va, double);
        if (precision == -1){
          precision = 6; // default is 6
        }
        // read the double into a string
        if (stbsp__real_to_str(&sn, &l, num, &dp, fv, precision | 0x80000000)){
          flags |= STBSP__NEGATIVE;
        }
        goto doexpfromg;
      }
      
      doexpfromg:
      {
        tail[0] = 0;
        stbsp__lead_sign(flags, lead);
        if (dp == STBSP__SPECIAL) {
          s = (char *)sn;
          cs = 0;
          precision = 0;
          goto scopy;
        }
        s = num + 64;
        // handle leading chars
        *s++ = sn[0];
        
        if (precision){
          *s++ = stbsp__period;
        }
        
        // handle after decimal
        if ((l - 1) > (stbsp__uint32)precision){
          l = precision + 1;
        }
        for (n = 1; n < l; n++){
          *s++ = sn[n];
        }
        // trailing zeros
        tz = precision - (l - 1);
        precision = 0;
        // dump expo
        tail[1] = h[0xe];
        dp -= 1;
        if (dp < 0){
          tail[2] = '-';
          dp = -dp;
        }
        else{
          tail[2] = '+';
        }
        n = (dp >= 100) ? 5 : 4;
        tail[0] = (char)n;
        for (;;) {
          tail[n] = '0' + dp % 10;
          if (n <= 3)
            break;
          --n;
          dp /= 10;
        }
        cs = 1 + (3 << 24); // how many tens
        goto flt_lead;
      }
      
      case 'f': // float
      {
        fv = va_arg(va, double);
        goto doafloat;
      }
      
      doafloat:
      {
        // do kilos
        if (flags & STBSP__MEMORY_SIZES) {
          double divisor = 1024.f;
          while (flags < 0x4000000) {
            if ((fv < divisor) && (fv > -divisor))
              break;
            fv /= divisor;
            flags += 0x1000000;
          }
        }
        if (precision == -1){
          precision = 6; // default is 6
        }
        // read the double into a string
        if (stbsp__real_to_str(&sn, &l, num, &dp, fv, precision)){
          flags |= STBSP__NEGATIVE;
        }
      }
      
      dofloatfromg:
      {
        tail[0] = 0;
        stbsp__lead_sign(flags, lead);
        if (dp == STBSP__SPECIAL) {
          s = (char *)sn;
          cs = 0;
          precision = 0;
          goto scopy;
        }
        s = num + 64;
        
        // handle the three decimal varieties
        if (dp <= 0) {
          stbsp__int32 i;
          // handle 0.000*000xxxx
          *s++ = '0';
          if (precision){
            *s++ = stbsp__period;
          }
          n = -dp;
          if ((stbsp__int32)n > precision){
            n = precision;
          }
          i = n;
          while (i) {
            if ((((stbsp__uintptr)s) & 3) == 0)
              break;
            *s++ = '0';
            --i;
          }
          while (i >= 4) {
            *(stbsp__uint32 *)s = 0x30303030;
            s += 4;
            i -= 4;
          }
          while (i) {
            *s++ = '0';
            --i;
          }
          if ((stbsp__int32)(l + n) > precision){
            l = precision - n;
          }
          i = l;
          while (i) {
            *s++ = *sn++;
            --i;
          }
          tz = precision - (n + l);
          cs = 1 + (3 << 24); // how many tens did we write (for commas below)
        }
        else{
          cs = (flags & STBSP__TRIPLET_COMMA) ? ((600 - (stbsp__uint32)dp) % 3) : 0;
          if ((stbsp__uint32)dp >= l) {
            // handle xxxx000*000.0
            n = 0;
            for (;;) {
              if ((flags & STBSP__TRIPLET_COMMA) && (++cs == 4)) {
                cs = 0;
                *s++ = stbsp__comma;
              } else {
                *s++ = sn[n];
                ++n;
                if (n >= l)
                  break;
              }
            }
            if (n < (stbsp__uint32)dp) {
              n = dp - n;
              if ((flags & STBSP__TRIPLET_COMMA) == 0) {
                while (n) {
                  if ((((stbsp__uintptr)s) & 3) == 0)
                    break;
                  *s++ = '0';
                  --n;
                }
                while (n >= 4) {
                  *(stbsp__uint32 *)s = 0x30303030;
                  s += 4;
                  n -= 4;
                }
              }
              while (n) {
                if ((flags & STBSP__TRIPLET_COMMA) && (++cs == 4)) {
                  cs = 0;
                  *s++ = stbsp__comma;
                } else {
                  *s++ = '0';
                  --n;
                }
              }
            }
            cs = (int)(s - (num + 64)) + (3 << 24); // cs is how many tens
            if (precision) {
              *s++ = stbsp__period;
              tz = precision;
            }
          } else {
            // handle xxxxx.xxxx000*000
            n = 0;
            for (;;) {
              if ((flags & STBSP__TRIPLET_COMMA) && (++cs == 4)) {
                cs = 0;
                *s++ = stbsp__comma;
              } else {
                *s++ = sn[n];
                ++n;
                if (n >= (stbsp__uint32)dp)
                  break;
              }
            }
            cs = (int)(s - (num + 64)) + (3 << 24); // cs is how many tens
            if (precision){
              *s++ = stbsp__period;
            }
            if ((l - dp) > (stbsp__uint32)precision){
              l = precision + dp;
            }
            while (n < l) {
              *s++ = sn[n];
              ++n;
            }
            tz = precision - (l - dp);
          }
        }
        precision = 0;
        
        // handle k,m,g,t
        if (flags & STBSP__MEMORY_SIZES) {
          tail[0] = 0;
          {
            char idx = 1;
            tail[idx] = ' ';
            idx++;
            tail[idx] = " KMGT"[flags >> 24];
            idx++;
            tail[idx] = 'b';
            tail[0] = idx;
          }
        }
        goto flt_lead;
      }
      
      flt_lead:
      {
        // get the length that we copied
        l = (stbsp__uint32)(s - (num + 64));
        s = num + 64;
        goto scopy;
      }
      
      case 'B': // upper binary
      case 'b': // lower binary
      {
        h = (f[0] == 'B') ? hexu : hex;
        lead[0] = 0;
        if (flags & STBSP__LEADING_0X) {
          lead[0] = 2;
          lead[1] = '0';
          lead[2] = h[0xb];
        }
        l = (8 << 4) | (1 << 8);
        goto radixnum;
      }
      
      case 'o': // octal
      {
        h = hexu;
        lead[0] = 0;
        if (flags & STBSP__LEADING_0X) {
          lead[0] = 1;
          lead[1] = '0';
        }
        l = (3 << 4) | (3 << 8);
        goto radixnum;
      }
      
      case 'p': // pointer
      {
        flags |= (sizeof(void *) == 8) ? STBSP__INTMAX : 0;
        precision = sizeof(void *) * 2;
        flags &= ~STBSP__LEADINGZERO; // 'p' only prints the pointer with zeros
        // fall through - to X
      }
      
      case 'X': // upper hex
      case 'x': // lower hex
      {
        h = (f[0] == 'X') ? hexu : hex;
        l = (4 << 4) | (4 << 8);
        lead[0] = 0;
        if (flags & STBSP__LEADING_0X) {
          lead[0] = 2;
          lead[1] = '0';
          lead[2] = h[16];
        }
        goto radixnum;
      }
      
      radixnum:
      {
        // get the number
        if (flags & STBSP__INTMAX){
          n64 = va_arg(va, stbsp__uint64);
        }
        else{
          n64 = va_arg(va, stbsp__uint32);
        }
        
        s = num + STBSP__NUMSZ;
        dp = 0;
        // clear tail, and clear leading if value is zero
        tail[0] = 0;
        if (n64 == 0) {
          lead[0] = 0;
          if (precision == 0) {
            l = 0;
            cs = 0;
            goto scopy;
          }
        }
        // convert to string
        for (;;) {
          *--s = h[n64 & ((1 << (l >> 8)) - 1)];
          n64 >>= (l >> 8);
          if (!((n64) || ((stbsp__int32)((num + STBSP__NUMSZ) - s) < precision))){
            break;
          }
          if (flags & STBSP__TRIPLET_COMMA) {
            ++l;
            if ((l & 15) == ((l >> 4) & 15)) {
              l &= ~15;
              *--s = stbsp__comma;
            }
          }
        };
        // get the tens and the comma pos
        cs = (stbsp__uint32)((num + STBSP__NUMSZ) - s) + ((((l >> 4) & 15)) << 24);
        // get the length that we copied
        l = (stbsp__uint32)((num + STBSP__NUMSZ) - s);
        // copy it
        goto scopy;
      }
      
      case 'u': // unsigned
      case 'i':
      case 'd': // integer
      {
        // get the integer and abs it
        if (flags & STBSP__INTMAX) {
          stbsp__int64 i64 = va_arg(va, stbsp__int64);
          n64 = (stbsp__uint64)i64;
          if ((f[0] != 'u') && (i64 < 0)) {
            n64 = (stbsp__uint64)-i64;
            flags |= STBSP__NEGATIVE;
          }
        } else {
          stbsp__int32 i = va_arg(va, stbsp__int32);
          n64 = (stbsp__uint32)i;
          if ((f[0] != 'u') && (i < 0)) {
            n64 = (stbsp__uint32)-i;
            flags |= STBSP__NEGATIVE;
          }
        }
        
        if (flags & STBSP__MEMORY_SIZES) {
          if (n64 < 1024){
            precision = 0;
          }
          else if (precision == -1){
            precision = 1;
          }
          fv = (double)(stbsp__int64)n64;
          goto doafloat;
        }
        
        // convert to string
        s = num + STBSP__NUMSZ;
        l = 0;
        
        for (;;) {
          // do in 32-bit chunks (avoid lots of 64-bit divides even with constant denominators)
          char *o = s - 8;
          if (n64 >= 100000000) {
            n = (stbsp__uint32)(n64 % 100000000);
            n64 /= 100000000;
          } else {
            n = (stbsp__uint32)n64;
            n64 = 0;
          }
          if ((flags & STBSP__TRIPLET_COMMA) == 0) {
            do {
              s -= 2;
              *(stbsp__uint16 *)s = *(stbsp__uint16 *)&stbsp__digitpair.pair[(n % 100) * 2];
              n /= 100;
            } while (n);
          }
          while (n) {
            if ((flags & STBSP__TRIPLET_COMMA) && (l++ == 3)) {
              l = 0;
              *--s = stbsp__comma;
              --o;
            } else {
              *--s = (char)(n % 10) + '0';
              n /= 10;
            }
          }
          if (n64 == 0) {
            if ((s[0] == '0') && (s != (num + STBSP__NUMSZ))){
              ++s;
            }
            break;
          }
          while (s != o){
            if ((flags & STBSP__TRIPLET_COMMA) && (l++ == 3)){
              l = 0;
              *--s = stbsp__comma;
              --o;
            }
            else{
              *--s = '0';
            }
          }
        }
        
        tail[0] = 0;
        stbsp__lead_sign(flags, lead);
        
        // get the length that we copied
        l = (stbsp__uint32)((num + STBSP__NUMSZ) - s);
        if (l == 0) {
          *--s = '0';
          l = 1;
        }
        cs = l + (3 << 24);
        if (precision < 0){
          precision = 0;
        }
        goto scopy;
      }
      
      default: // unknown, just copy code
      {
        s = num + STBSP__NUMSZ - 1;
        *s = f[0];
        l = 1;
        field_width = flags = 0;
        lead[0] = 0;
        tail[0] = 0;
        precision = 0;
        dp = 0;
        cs = 0;
        goto scopy;
      }
      
      scopy:
      {
        // get field_width=leading/trailing space, precision=leading zeros
        if (precision < (stbsp__int32)l){
          precision = l;
        }
        n = precision + lead[0] + tail[0] + tz;
        if (field_width < (stbsp__int32)n){
          field_width = n;
        }
        field_width -= n;
        precision -= l;
        
        // handle right justify and leading zeros
        if ((flags & STBSP__LEFTJUST) == 0){
          // if leading zeros, everything is in precision
          if (flags & STBSP__LEADINGZERO){
            precision = (field_width > precision) ? field_width : precision;
            field_width = 0;
          }
          // if no leading zeros, then no commas
          else{
            flags &= ~STBSP__TRIPLET_COMMA;
          }
        }
        
        // copy the spaces and/or zeros
        if (field_width + precision) {
          stbsp__int32 i;
          stbsp__uint32 c;
          
          // copy leading spaces (or when doing %8.4d stuff)
          if ((flags & STBSP__LEFTJUST) == 0)
            while (field_width > 0) {
            stbsp__cb_buf_clamp(i, field_width);
            field_width -= i;
            while (i) {
              if ((((stbsp__uintptr)bf) & 3) == 0)
                break;
              *bf++ = ' ';
              --i;
            }
            while (i >= 4) {
              *(stbsp__uint32 *)bf = 0x20202020;
              bf += 4;
              i -= 4;
            }
            while (i) {
              *bf++ = ' ';
              --i;
            }
            stbsp__chk_cb_buf(1);
          }
          
          // copy leader
          sn = lead + 1;
          while (lead[0]) {
            stbsp__cb_buf_clamp(i, lead[0]);
            lead[0] -= (char)i;
            while (i) {
              *bf++ = *sn++;
              --i;
            }
            stbsp__chk_cb_buf(1);
          }
          
          // copy leading zeros
          c = cs >> 24;
          cs &= 0xffffff;
          cs = (flags & STBSP__TRIPLET_COMMA) ? ((stbsp__uint32)(c - ((precision + cs) % (c + 1)))) : 0;
          while (precision > 0) {
            stbsp__cb_buf_clamp(i, precision);
            precision -= i;
            if ((flags & STBSP__TRIPLET_COMMA) == 0) {
              while (i) {
                if ((((stbsp__uintptr)bf) & 3) == 0)
                  break;
                *bf++ = '0';
                --i;
              }
              while (i >= 4) {
                *(stbsp__uint32 *)bf = 0x30303030;
                bf += 4;
                i -= 4;
              }
            }
            while (i) {
              if ((flags & STBSP__TRIPLET_COMMA) && (cs++ == c)) {
                cs = 0;
                *bf++ = stbsp__comma;
              } else
                *bf++ = '0';
              --i;
            }
            stbsp__chk_cb_buf(1);
          }
        }
        
        // copy leader if there is still one
        sn = lead + 1;
        while (lead[0]) {
          stbsp__int32 i;
          stbsp__cb_buf_clamp(i, lead[0]);
          lead[0] -= (char)i;
          while (i) {
            *bf++ = *sn++;
            --i;
          }
          stbsp__chk_cb_buf(1);
        }
        
        // copy the string
        n = l;
        while (n) {
          stbsp__int32 i;
          stbsp__cb_buf_clamp(i, n);
          n -= i;
          STBSP__UNALIGNED(while (i >= 4) {
                             *(stbsp__uint32 volatile *)bf = *(stbsp__uint32 volatile *)s;
                             bf += 4;
                             s += 4;
                             i -= 4;
                           })
            while (i) {
            *bf++ = *s++;
            --i;
          }
          stbsp__chk_cb_buf(1);
        }
        
        // copy trailing zeros
        while (tz) {
          stbsp__int32 i;
          stbsp__cb_buf_clamp(i, tz);
          tz -= i;
          while (i) {
            if ((((stbsp__uintptr)bf) & 3) == 0)
              break;
            *bf++ = '0';
            --i;
          }
          while (i >= 4) {
            *(stbsp__uint32 *)bf = 0x30303030;
            bf += 4;
            i -= 4;
          }
          while (i) {
            *bf++ = '0';
            --i;
          }
          stbsp__chk_cb_buf(1);
        }
        
        // copy tail if there is one
        sn = tail + 1;
        while (tail[0]) {
          stbsp__int32 i;
          stbsp__cb_buf_clamp(i, tail[0]);
          tail[0] -= (char)i;
          while (i) {
            *bf++ = *sn++;
            --i;
          }
          stbsp__chk_cb_buf(1);
        }
        
        // handle the left justify
        if (flags & STBSP__LEFTJUST){
          if (field_width > 0){
            while (field_width){
              stbsp__int32 i;
              stbsp__cb_buf_clamp(i, field_width);
              field_width -= i;
              while (i){
                if ((((stbsp__uintptr)bf) & 3) == 0)
                  break;
                *bf++ = ' ';
                --i;
              }
              while (i >= 4){
                *(stbsp__uint32 *)bf = 0x20202020;
                bf += 4;
                i -= 4;
              }
              while (i--){
                *bf++ = ' ';
              }
              stbsp__chk_cb_buf(1);
            }
          }
        }
        
        arena_end_temp(&scratch);
      }break;
    }
    ++f;
  }
  endfmt:
  
  if (!callback)
    *bf = 0;
  else
    stbsp__flush_cb();
  
  done:
  arena_release_scratch(&scratch);
  return tlen + (int)(bf - buf);
}

// cleanup
#undef STBSP__LEFTJUST
#undef STBSP__LEADINGPLUS
#undef STBSP__LEADINGSPACE
#undef STBSP__LEADING_0X
#undef STBSP__LEADINGZERO
#undef STBSP__INTMAX
#undef STBSP__TRIPLET_COMMA
#undef STBSP__NEGATIVE
#undef STBSP__METRIC_SUFFIX
#undef STBSP__NUMSZ
#undef stbsp__chk_cb_bufL
#undef stbsp__chk_cb_buf
#undef stbsp__flush_cb
#undef stbsp__cb_buf_clamp

// ============================================================================
//   wrapper functions

STBSP__PUBLICDEF int m4_sprintf(char *buf, char const *fmt, ...)
{
  int result;
  va_list va;
  va_start(va, fmt);
  result = m4_vsprintfcb(0, 0, buf, fmt, va);
  va_end(va);
  return result;
}

typedef struct stbsp__context {
  char *buf;
  int count;
  int length;
  char tmp[STB_SPRINTF_MIN];
} stbsp__context;

static char *stbsp__clamp_callback(const char *buf, void *user, int len)
{
  stbsp__context *c = (stbsp__context *)user;
  c->length += len;
  
  if (len > c->count)
    len = c->count;
  
  if (len) {
    if (buf != c->buf) {
      const char *s, *se;
      char *d;
      d = c->buf;
      s = buf;
      se = buf + len;
      do {
        *d++ = *s++;
      } while (s < se);
    }
    c->buf += len;
    c->count -= len;
  }
  
  if (c->count <= 0)
    return c->tmp;
  return (c->count >= STB_SPRINTF_MIN) ? c->buf : c->tmp; // go direct into buffer if you can
}

static char * stbsp__count_clamp_callback( const char * buf, void * user, int len )
{
  stbsp__context * c = (stbsp__context*)user;
  (void) sizeof(buf);
  
  c->length += len;
  return c->tmp; // go direct into buffer if you can
}

STBSP__PUBLICDEF int m4_vsnprintf( char * buf, int count, char const * fmt, va_list va )
{
  stbsp__context c;
  
  if ( (count == 0) && !buf )
  {
    c.length = 0;
    
    m4_vsprintfcb( stbsp__count_clamp_callback, &c, c.tmp, fmt, va );
  }
  else
  {
    int l;
    
    c.buf = buf;
    c.count = count;
    c.length = 0;
    
    m4_vsprintfcb( stbsp__clamp_callback, &c, stbsp__clamp_callback(0,&c,0), fmt, va );
    
    // zero-terminate
    l = (int)( c.buf - buf );
    if ( l >= count ) // should never be greater, only equal (or less) than count
      l = count - 1;
    buf[l] = 0;
  }
  
  return c.length;
}

STBSP__PUBLICDEF int m4_snprintf(char *buf, int count, char const *fmt, ...)
{
  int result;
  va_list va;
  va_start(va, fmt);
  
  result = m4_vsnprintf(buf, count, fmt, va);
  va_end(va);
  
  return result;
}

STBSP__PUBLICDEF int m4_vsprintf(char *buf, char const *fmt, va_list va)
{
  return m4_vsprintfcb(0, 0, buf, fmt, va);
}

// =======================================================================
//   low level float utility functions

// copies d to bits w/ strict aliasing (this compiles to nothing on /Ox)
#define STBSP__COPYFP(dest, src)                   \
{                                               \
int cn;                                      \
for (cn = 0; cn < 8; cn++)                   \
((char *)&dest)[cn] = ((char *)&src)[cn]; \
}

// get float info
static stbsp__int32 stbsp__real_to_parts(stbsp__int64 *bits, stbsp__int32 *expo, double value)
{
  double d;
  stbsp__int64 b = 0;
  
  // load value and round at the frac_digits
  d = value;
  
  STBSP__COPYFP(b, d);
  
  *bits = b & ((((stbsp__uint64)1) << 52) - 1);
  *expo = (stbsp__int32)(((b >> 52) & 2047) - 1023);
  
  return (stbsp__int32)((stbsp__uint64) b >> 63);
}

static double const stbsp__bot[23] = {
  1e+000, 1e+001, 1e+002, 1e+003, 1e+004, 1e+005, 1e+006, 1e+007, 1e+008, 1e+009, 1e+010, 1e+011,
  1e+012, 1e+013, 1e+014, 1e+015, 1e+016, 1e+017, 1e+018, 1e+019, 1e+020, 1e+021, 1e+022
};
static double const stbsp__negbot[22] = {
  1e-001, 1e-002, 1e-003, 1e-004, 1e-005, 1e-006, 1e-007, 1e-008, 1e-009, 1e-010, 1e-011,
  1e-012, 1e-013, 1e-014, 1e-015, 1e-016, 1e-017, 1e-018, 1e-019, 1e-020, 1e-021, 1e-022
};
static double const stbsp__negboterr[22] = {
  -5.551115123125783e-018,  -2.0816681711721684e-019, -2.0816681711721686e-020, -4.7921736023859299e-021, -8.1803053914031305e-022, 4.5251888174113741e-023,
  4.5251888174113739e-024,  -2.0922560830128471e-025, -6.2281591457779853e-026, -3.6432197315497743e-027, 6.0503030718060191e-028,  2.0113352370744385e-029,
  -3.0373745563400371e-030, 1.1806906454401013e-032,  -7.7705399876661076e-032, 2.0902213275965398e-033,  -7.1542424054621921e-034, -7.1542424054621926e-035,
  2.4754073164739869e-036,  5.4846728545790429e-037,  9.2462547772103625e-038,  -4.8596774326570872e-039
};
static double const stbsp__top[13] = {
  1e+023, 1e+046, 1e+069, 1e+092, 1e+115, 1e+138, 1e+161, 1e+184, 1e+207, 1e+230, 1e+253, 1e+276, 1e+299
};
static double const stbsp__negtop[13] = {
  1e-023, 1e-046, 1e-069, 1e-092, 1e-115, 1e-138, 1e-161, 1e-184, 1e-207, 1e-230, 1e-253, 1e-276, 1e-299
};
static double const stbsp__toperr[13] = {
  8388608,
  6.8601809640529717e+028,
  -7.253143638152921e+052,
  -4.3377296974619174e+075,
  -1.5559416129466825e+098,
  -3.2841562489204913e+121,
  -3.7745893248228135e+144,
  -1.7356668416969134e+167,
  -3.8893577551088374e+190,
  -9.9566444326005119e+213,
  6.3641293062232429e+236,
  -5.2069140800249813e+259,
  -5.2504760255204387e+282
};
static double const stbsp__negtoperr[13] = {
  3.9565301985100693e-040,  -2.299904345391321e-063,  3.6506201437945798e-086,  1.1875228833981544e-109,
  -5.0644902316928607e-132, -6.7156837247865426e-155, -2.812077463003139e-178,  -5.7778912386589953e-201,
  7.4997100559334532e-224,  -4.6439668915134491e-247, -6.3691100762962136e-270, -9.436808465446358e-293,
  8.0970921678014997e-317
};

#if defined(_MSC_VER) && (_MSC_VER <= 1200)
static stbsp__uint64 const stbsp__powten[20] = {
  1,
  10,
  100,
  1000,
  10000,
  100000,
  1000000,
  10000000,
  100000000,
  1000000000,
  10000000000,
  100000000000,
  1000000000000,
  10000000000000,
  100000000000000,
  1000000000000000,
  10000000000000000,
  100000000000000000,
  1000000000000000000,
  10000000000000000000U
};
#define stbsp__tento19th ((stbsp__uint64)1000000000000000000)
#else
static stbsp__uint64 const stbsp__powten[20] = {
  1,
  10,
  100,
  1000,
  10000,
  100000,
  1000000,
  10000000,
  100000000,
  1000000000,
  10000000000ULL,
  100000000000ULL,
  1000000000000ULL,
  10000000000000ULL,
  100000000000000ULL,
  1000000000000000ULL,
  10000000000000000ULL,
  100000000000000000ULL,
  1000000000000000000ULL,
  10000000000000000000ULL
};
#define stbsp__tento19th (1000000000000000000ULL)
#endif

#define stbsp__ddmulthi(oh, ol, xh, yh)                            \
{                                                               \
double ahi = 0, alo, bhi = 0, blo;                           \
stbsp__int64 bt;                                             \
oh = xh * yh;                                                \
STBSP__COPYFP(bt, xh);                                       \
bt &= ((~(stbsp__uint64)0) << 27);                           \
STBSP__COPYFP(ahi, bt);                                      \
alo = xh - ahi;                                              \
STBSP__COPYFP(bt, yh);                                       \
bt &= ((~(stbsp__uint64)0) << 27);                           \
STBSP__COPYFP(bhi, bt);                                      \
blo = yh - bhi;                                              \
ol = ((ahi * bhi - oh) + ahi * blo + alo * bhi) + alo * blo; \
}

#define stbsp__ddtoS64(ob, xh, xl)          \
{                                        \
double ahi = 0, alo, vh, t;           \
ob = (stbsp__int64)xh;                \
vh = (double)ob;                      \
ahi = (xh - vh);                      \
t = (ahi - xh);                       \
alo = (xh - (ahi - t)) - (vh + t);    \
ob += (stbsp__int64)(ahi + alo + xl); \
}

#define stbsp__ddrenorm(oh, ol) \
{                            \
double s;                 \
s = oh + ol;              \
ol = ol - (s - oh);       \
oh = s;                   \
}

#define stbsp__ddmultlo(oh, ol, xh, xl, yh, yl) ol = ol + (xh * yl + xl * yh);

#define stbsp__ddmultlos(oh, ol, xh, yl) ol = ol + (xh * yl);

static void stbsp__raise_to_power10(double *ohi, double *olo, double d, stbsp__int32 power) // power can be -323 to +350
{
  double ph, pl;
  if ((power >= 0) && (power <= 22)) {
    stbsp__ddmulthi(ph, pl, d, stbsp__bot[power]);
  } else {
    stbsp__int32 e, et, eb;
    double p2h, p2l;
    
    e = power;
    if (power < 0)
      e = -e;
    et = (e * 0x2c9) >> 14; /* %23 */
    if (et > 13)
      et = 13;
    eb = e - (et * 23);
    
    ph = d;
    pl = 0.0;
    if (power < 0) {
      if (eb) {
        --eb;
        stbsp__ddmulthi(ph, pl, d, stbsp__negbot[eb]);
        stbsp__ddmultlos(ph, pl, d, stbsp__negboterr[eb]);
      }
      if (et) {
        stbsp__ddrenorm(ph, pl);
        --et;
        stbsp__ddmulthi(p2h, p2l, ph, stbsp__negtop[et]);
        stbsp__ddmultlo(p2h, p2l, ph, pl, stbsp__negtop[et], stbsp__negtoperr[et]);
        ph = p2h;
        pl = p2l;
      }
    } else {
      if (eb) {
        e = eb;
        if (eb > 22)
          eb = 22;
        e -= eb;
        stbsp__ddmulthi(ph, pl, d, stbsp__bot[eb]);
        if (e) {
          stbsp__ddrenorm(ph, pl);
          stbsp__ddmulthi(p2h, p2l, ph, stbsp__bot[e]);
          stbsp__ddmultlos(p2h, p2l, stbsp__bot[e], pl);
          ph = p2h;
          pl = p2l;
        }
      }
      if (et) {
        stbsp__ddrenorm(ph, pl);
        --et;
        stbsp__ddmulthi(p2h, p2l, ph, stbsp__top[et]);
        stbsp__ddmultlo(p2h, p2l, ph, pl, stbsp__top[et], stbsp__toperr[et]);
        ph = p2h;
        pl = p2l;
      }
    }
  }
  stbsp__ddrenorm(ph, pl);
  *ohi = ph;
  *olo = pl;
}

// given a float value, returns the significant bits in bits, and the position of the
//   decimal point in decimal_pos.  +/-INF and NAN are specified by special values
//   returned in the decimal_pos parameter.
// frac_digits is absolute normally, but if you want from first significant digits (got %g and %e), or in 0x80000000
static stbsp__int32 stbsp__real_to_str(char const **start, stbsp__uint32 *len, char *out, stbsp__int32 *decimal_pos, double value, stbsp__uint32 frac_digits)
{
  double d;
  stbsp__int64 bits = 0;
  stbsp__int32 expo, e, ng, tens;
  
  d = value;
  STBSP__COPYFP(bits, d);
  expo = (stbsp__int32)((bits >> 52) & 2047);
  ng = (stbsp__int32)((stbsp__uint64) bits >> 63);
  if (ng)
    d = -d;
  
  if (expo == 2047) // is nan or inf?
  {
    *start = (bits & ((((stbsp__uint64)1) << 52) - 1)) ? "NaN" : "Inf";
    *decimal_pos = STBSP__SPECIAL;
    *len = 3;
    return ng;
  }
  
  if (expo == 0) // is zero or denormal
  {
    if (((stbsp__uint64) bits << 1) == 0) // do zero
    {
      *decimal_pos = 1;
      *start = out;
      out[0] = '0';
      *len = 1;
      return ng;
    }
    // find the right expo for denormals
    {
      stbsp__int64 v = ((stbsp__uint64)1) << 51;
      while ((bits & v) == 0) {
        --expo;
        v >>= 1;
      }
    }
  }
  
  // find the decimal exponent as well as the decimal bits of the value
  {
    double ph, pl;
    
    // log10 estimate - very specifically tweaked to hit or undershoot by no more than 1 of log10 of all expos 1..2046
    tens = expo - 1023;
    tens = (tens < 0) ? ((tens * 617) / 2048) : (((tens * 1233) / 4096) + 1);
    
    // move the significant bits into position and stick them into an int
    stbsp__raise_to_power10(&ph, &pl, d, 18 - tens);
    
    // get full as much precision from double-double as possible
    stbsp__ddtoS64(bits, ph, pl);
    
    // check if we undershot
    if (((stbsp__uint64)bits) >= stbsp__tento19th)
      ++tens;
  }
  
  // now do the rounding in integer land
  frac_digits = (frac_digits & 0x80000000) ? ((frac_digits & 0x7ffffff) + 1) : (tens + frac_digits);
  if ((frac_digits < 24)) {
    stbsp__uint32 dg = 1;
    if ((stbsp__uint64)bits >= stbsp__powten[9])
      dg = 10;
    while ((stbsp__uint64)bits >= stbsp__powten[dg]) {
      ++dg;
      if (dg == 20)
        goto noround;
    }
    if (frac_digits < dg) {
      stbsp__uint64 r;
      // add 0.5 at the right position and round
      e = dg - frac_digits;
      if ((stbsp__uint32)e >= 24)
        goto noround;
      r = stbsp__powten[e];
      bits = bits + (r / 2);
      if ((stbsp__uint64)bits >= stbsp__powten[dg])
        ++tens;
      bits /= r;
    }
    noround:;
  }
  
  // kill long trailing runs of zeros
  if (bits) {
    stbsp__uint32 n;
    for (;;) {
      if (bits <= 0xffffffff)
        break;
      if (bits % 1000)
        goto donez;
      bits /= 1000;
    }
    n = (stbsp__uint32)bits;
    while ((n % 1000) == 0)
      n /= 1000;
    bits = n;
    donez:;
  }
  
  // convert to string
  out += 64;
  e = 0;
  for (;;) {
    stbsp__uint32 n;
    char *o = out - 8;
    // do the conversion in chunks of U32s (avoid most 64-bit divides, worth it, constant denomiators be damned)
    if (bits >= 100000000) {
      n = (stbsp__uint32)(bits % 100000000);
      bits /= 100000000;
    } else {
      n = (stbsp__uint32)bits;
      bits = 0;
    }
    while (n) {
      out -= 2;
      *(stbsp__uint16 *)out = *(stbsp__uint16 *)&stbsp__digitpair.pair[(n % 100) * 2];
      n /= 100;
      e += 2;
    }
    if (bits == 0) {
      if ((e) && (out[0] == '0')) {
        ++out;
        --e;
      }
      break;
    }
    while (out != o) {
      *--out = '0';
      ++e;
    }
  }
  
  *decimal_pos = tens;
  *start = out;
  *len = e;
  return ng;
}

#undef stbsp__ddmulthi
#undef stbsp__ddrenorm
#undef stbsp__ddmultlo
#undef stbsp__ddmultlos
#undef STBSP__SPECIAL
#undef STBSP__COPYFP

// clean up
#undef stbsp__uint16
#undef stbsp__uint32
#undef stbsp__int32
#undef stbsp__uint64
#undef stbsp__int64
#undef STBSP__UNALIGNED

