add c and jai
This commit is contained in:
53
c/base/base_arena.c
Normal file
53
c/base/base_arena.c
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
#include "base/base_arena.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
Arena *arena_alloc(U64 cap) {
|
||||||
|
U8 *mem = (U8 *)malloc(sizeof(Arena) + cap);
|
||||||
|
if (!mem) return NULL;
|
||||||
|
Arena *arena = (Arena *)mem;
|
||||||
|
arena->base = mem + sizeof(Arena);
|
||||||
|
arena->pos = 0;
|
||||||
|
arena->cap = cap;
|
||||||
|
return arena;
|
||||||
|
}
|
||||||
|
|
||||||
|
void arena_release(Arena *arena) {
|
||||||
|
if (arena) free(arena);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *arena_push(Arena *arena, U64 size) {
|
||||||
|
U64 aligned = AlignPow2(size, 8);
|
||||||
|
if (arena->pos + aligned > arena->cap) {
|
||||||
|
Assert(!"Arena overflow");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
void *result = arena->base + arena->pos;
|
||||||
|
arena->pos += aligned;
|
||||||
|
MemoryZero(result, aligned);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *arena_push_no_zero(Arena *arena, U64 size) {
|
||||||
|
U64 aligned = AlignPow2(size, 8);
|
||||||
|
if (arena->pos + aligned > arena->cap) {
|
||||||
|
Assert(!"Arena overflow");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
void *result = arena->base + arena->pos;
|
||||||
|
arena->pos += aligned;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
U64 arena_pos(Arena *arena) {
|
||||||
|
return arena->pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
void arena_pop_to(Arena *arena, U64 pos) {
|
||||||
|
if (pos < arena->pos) {
|
||||||
|
arena->pos = pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void arena_clear(Arena *arena) {
|
||||||
|
arena->pos = 0;
|
||||||
|
}
|
||||||
44
c/base/base_arena.h
Normal file
44
c/base/base_arena.h
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
#pragma once
|
||||||
|
// base_arena.h - Linear arena allocator
|
||||||
|
// Simplified from raddebugger's virtual-memory-backed arena to a malloc-based one.
|
||||||
|
// Suitable for per-frame scratch allocations and persistent state.
|
||||||
|
|
||||||
|
#include "base/base_core.h"
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Arena type
|
||||||
|
|
||||||
|
typedef struct Arena {
|
||||||
|
U8 *base;
|
||||||
|
U64 pos;
|
||||||
|
U64 cap;
|
||||||
|
} Arena;
|
||||||
|
|
||||||
|
// Temporary scope (save/restore position)
|
||||||
|
typedef struct Temp {
|
||||||
|
Arena *arena;
|
||||||
|
U64 pos;
|
||||||
|
} Temp;
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Arena functions
|
||||||
|
|
||||||
|
Arena *arena_alloc(U64 cap);
|
||||||
|
void arena_release(Arena *arena);
|
||||||
|
void *arena_push(Arena *arena, U64 size);
|
||||||
|
void *arena_push_no_zero(Arena *arena, U64 size);
|
||||||
|
U64 arena_pos(Arena *arena);
|
||||||
|
void arena_pop_to(Arena *arena, U64 pos);
|
||||||
|
void arena_clear(Arena *arena);
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Temporary scope helpers
|
||||||
|
|
||||||
|
static inline Temp temp_begin(Arena *arena) { Temp t = {arena, arena->pos}; return t; }
|
||||||
|
static inline void temp_end(Temp temp) { arena_pop_to(temp.arena, temp.pos); }
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Push helper macros
|
||||||
|
|
||||||
|
#define push_array(arena, T, count) ((T *)arena_push((arena), sizeof(T) * (count)))
|
||||||
|
#define push_array_no_zero(arena, T, count) ((T *)arena_push_no_zero((arena), sizeof(T) * (count)))
|
||||||
181
c/base/base_core.h
Normal file
181
c/base/base_core.h
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
#pragma once
|
||||||
|
// base_core.h - Fundamental types, macros, and linked list helpers
|
||||||
|
// Inspired by raddebugger's base_core.h
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Codebase keywords
|
||||||
|
|
||||||
|
#ifndef __APPLE__
|
||||||
|
#define internal static
|
||||||
|
#define global static
|
||||||
|
#endif
|
||||||
|
#define local_persist static
|
||||||
|
|
||||||
|
#define trvke 1
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Base types
|
||||||
|
|
||||||
|
typedef uint8_t U8;
|
||||||
|
typedef uint16_t U16;
|
||||||
|
typedef uint32_t U32;
|
||||||
|
typedef uint64_t U64;
|
||||||
|
typedef int8_t S8;
|
||||||
|
typedef int16_t S16;
|
||||||
|
typedef int32_t S32;
|
||||||
|
typedef int64_t S64;
|
||||||
|
typedef S32 B32;
|
||||||
|
typedef float F32;
|
||||||
|
typedef double F64;
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Limits
|
||||||
|
|
||||||
|
#define max_U8 0xFF
|
||||||
|
#define max_U16 0xFFFF
|
||||||
|
#define max_U32 0xFFFFFFFF
|
||||||
|
#define max_U64 0xFFFFFFFFFFFFFFFFull
|
||||||
|
#define max_S8 0x7F
|
||||||
|
#define max_S16 0x7FFF
|
||||||
|
#define max_S32 0x7FFFFFFF
|
||||||
|
#define max_S64 0x7FFFFFFFFFFFFFFFll
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Units
|
||||||
|
|
||||||
|
#define KB(n) (((U64)(n)) << 10)
|
||||||
|
#define MB(n) (((U64)(n)) << 20)
|
||||||
|
#define GB(n) (((U64)(n)) << 30)
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Clamps, Mins, Maxes
|
||||||
|
|
||||||
|
#define Min(A, B) (((A) < (B)) ? (A) : (B))
|
||||||
|
#define Max(A, B) (((A) > (B)) ? (A) : (B))
|
||||||
|
#define ClampTop(A, X) Min(A, X)
|
||||||
|
#define ClampBot(X, B) Max(X, B)
|
||||||
|
#define Clamp(A, X, B) (((X) < (A)) ? (A) : ((X) > (B)) ? (B) : (X))
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Alignment / Sizing
|
||||||
|
|
||||||
|
#define AlignPow2(x, b) (((x) + (b) - 1) & (~((b) - 1)))
|
||||||
|
#define AlignDownPow2(x, b) ((x) & (~((b) - 1)))
|
||||||
|
#define ArrayCount(a) (sizeof(a) / sizeof((a)[0]))
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Memory macros
|
||||||
|
|
||||||
|
#define MemoryCopy(dst, src, size) memmove((dst), (src), (size))
|
||||||
|
#define MemorySet(dst, byte, size) memset((dst), (byte), (size))
|
||||||
|
#define MemoryCompare(a, b, size) memcmp((a), (b), (size))
|
||||||
|
#define MemoryZero(s, z) memset((s), 0, (z))
|
||||||
|
#define MemoryZeroStruct(s) MemoryZero((s), sizeof(*(s)))
|
||||||
|
#define MemoryZeroArray(a) MemoryZero((a), sizeof(a))
|
||||||
|
#define MemoryMatch(a, b, z) (MemoryCompare((a), (b), (z)) == 0)
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Pointer / integer casts
|
||||||
|
|
||||||
|
#define IntFromPtr(ptr) ((U64)(ptr))
|
||||||
|
#define PtrFromInt(i) (void *)(i)
|
||||||
|
#define OffsetOf(T, m) IntFromPtr(&(((T *)0)->m))
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Member access
|
||||||
|
|
||||||
|
#define CastFromMember(T, m, ptr) (T *)(((U8 *)(ptr)) - OffsetOf(T, m))
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// For-Loop construct macros
|
||||||
|
|
||||||
|
#define DeferLoop(begin, end) for (int _i_ = ((begin), 0); !_i_; _i_ += 1, (end))
|
||||||
|
#define DeferLoopChecked(begin, end) for (int _i_ = 2 * !(begin); (_i_ == 2 ? ((end), 0) : !_i_); _i_ += 1, (end))
|
||||||
|
|
||||||
|
#define EachIndex(it, count) (U64 it = 0; it < (count); it += 1)
|
||||||
|
#define EachElement(it, array) (U64 it = 0; it < ArrayCount(array); it += 1)
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Glue / Stringify
|
||||||
|
|
||||||
|
#define Stringify_(S) #S
|
||||||
|
#define Stringify(S) Stringify_(S)
|
||||||
|
#define Glue_(A, B) A##B
|
||||||
|
#define Glue(A, B) Glue_(A, B)
|
||||||
|
|
||||||
|
#define Swap(T, a, b) do { T t__ = a; a = b; b = t__; } while (0)
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Assert
|
||||||
|
|
||||||
|
#if defined(_MSC_VER)
|
||||||
|
# define Trap() __debugbreak()
|
||||||
|
#elif defined(__clang__) || defined(__GNUC__)
|
||||||
|
# define Trap() __builtin_trap()
|
||||||
|
#else
|
||||||
|
# define Trap() (*(volatile int *)0 = 0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define AssertAlways(x) do { if (!(x)) { Trap(); } } while (0)
|
||||||
|
|
||||||
|
#ifdef _DEBUG
|
||||||
|
# define Assert(x) AssertAlways(x)
|
||||||
|
#else
|
||||||
|
# define Assert(x) (void)(x)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define InvalidPath Assert(!"Invalid Path!")
|
||||||
|
#define NotImplemented Assert(!"Not Implemented!")
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Linked list macros
|
||||||
|
// Nil-aware doubly-linked-list operations
|
||||||
|
|
||||||
|
#define CheckNil(nil, p) ((p) == 0 || (p) == nil)
|
||||||
|
#define SetNil(nil, p) ((p) = nil)
|
||||||
|
|
||||||
|
// Doubly-linked-list (with nil support)
|
||||||
|
#define DLLInsert_NPZ(nil, f, l, p, n, next, prev) \
|
||||||
|
(CheckNil(nil, f) ? \
|
||||||
|
((f) = (l) = (n), SetNil(nil, (n)->next), SetNil(nil, (n)->prev)) : \
|
||||||
|
CheckNil(nil, p) ? \
|
||||||
|
((n)->next = (f), (f)->prev = (n), (f) = (n), SetNil(nil, (n)->prev)) : \
|
||||||
|
((p) == (l)) ? \
|
||||||
|
((l)->next = (n), (n)->prev = (l), (l) = (n), SetNil(nil, (n)->next)) : \
|
||||||
|
(((!CheckNil(nil, p) && CheckNil(nil, (p)->next)) ? (0) : ((p)->next->prev = (n))), \
|
||||||
|
((n)->next = (p)->next), ((p)->next = (n)), ((n)->prev = (p))))
|
||||||
|
|
||||||
|
#define DLLPushBack_NPZ(nil, f, l, n, next, prev) DLLInsert_NPZ(nil, f, l, l, n, next, prev)
|
||||||
|
#define DLLPushFront_NPZ(nil, f, l, n, next, prev) DLLInsert_NPZ(nil, l, f, f, n, prev, next)
|
||||||
|
|
||||||
|
#define DLLRemove_NPZ(nil, f, l, n, next, prev) \
|
||||||
|
(((n) == (f) ? (f) = (n)->next : (0)), \
|
||||||
|
((n) == (l) ? (l) = (l)->prev : (0)), \
|
||||||
|
(CheckNil(nil, (n)->prev) ? (0) : ((n)->prev->next = (n)->next)), \
|
||||||
|
(CheckNil(nil, (n)->next) ? (0) : ((n)->next->prev = (n)->prev)))
|
||||||
|
|
||||||
|
// Convenience wrappers using 0 as nil
|
||||||
|
#define DLLPushBack(f, l, n) DLLPushBack_NPZ(0, f, l, n, next, prev)
|
||||||
|
#define DLLPushFront(f, l, n) DLLPushFront_NPZ(0, f, l, n, next, prev)
|
||||||
|
#define DLLRemove(f, l, n) DLLRemove_NPZ(0, f, l, n, next, prev)
|
||||||
|
|
||||||
|
// Singly-linked queue (doubly-headed)
|
||||||
|
#define SLLQueuePush_NZ(nil, f, l, n, next) \
|
||||||
|
(CheckNil(nil, f) ? \
|
||||||
|
((f) = (l) = (n), SetNil(nil, (n)->next)) : \
|
||||||
|
((l)->next = (n), (l) = (n), SetNil(nil, (n)->next)))
|
||||||
|
|
||||||
|
#define SLLQueuePush(f, l, n) SLLQueuePush_NZ(0, f, l, n, next)
|
||||||
|
#define SLLQueuePushFront(f, l, n) (((n)->next = (f)), ((f) = (n)))
|
||||||
|
#define SLLQueuePop(f, l) ((f) == (l) ? ((f) = 0, (l) = 0) : ((f) = (f)->next))
|
||||||
|
|
||||||
|
// Singly-linked stack
|
||||||
|
#define SLLStackPush(f, n) ((n)->next = (f), (f) = (n))
|
||||||
|
#define SLLStackPop(f) ((f) = (f)->next)
|
||||||
3
c/base/base_inc.c
Normal file
3
c/base/base_inc.c
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// base_inc.c - Unity build for the base layer
|
||||||
|
#include "base/base_arena.c"
|
||||||
|
#include "base/base_strings.c"
|
||||||
8
c/base/base_inc.h
Normal file
8
c/base/base_inc.h
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
// base_inc.h - Umbrella include for the base layer
|
||||||
|
// Include this one header to get all base types.
|
||||||
|
|
||||||
|
#include "base/base_core.h"
|
||||||
|
#include "base/base_arena.h"
|
||||||
|
#include "base/base_math.h"
|
||||||
|
#include "base/base_strings.h"
|
||||||
110
c/base/base_math.h
Normal file
110
c/base/base_math.h
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
#pragma once
|
||||||
|
// base_math.h - Vector, range, and color types
|
||||||
|
// Inspired by raddebugger's base_math.h
|
||||||
|
|
||||||
|
#include "base/base_core.h"
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Axis enum
|
||||||
|
|
||||||
|
typedef enum Axis2 {
|
||||||
|
Axis2_X = 0,
|
||||||
|
Axis2_Y = 1,
|
||||||
|
Axis2_COUNT,
|
||||||
|
} Axis2;
|
||||||
|
|
||||||
|
typedef enum Side {
|
||||||
|
Side_Min = 0,
|
||||||
|
Side_Max = 1,
|
||||||
|
Side_COUNT,
|
||||||
|
} Side;
|
||||||
|
|
||||||
|
typedef enum Corner {
|
||||||
|
Corner_00 = 0, // top-left
|
||||||
|
Corner_01 = 1, // top-right
|
||||||
|
Corner_10 = 2, // bottom-left
|
||||||
|
Corner_11 = 3, // bottom-right
|
||||||
|
Corner_COUNT,
|
||||||
|
} Corner;
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Vector types
|
||||||
|
|
||||||
|
typedef struct Vec2F32 { F32 x, y; } Vec2F32;
|
||||||
|
typedef struct Vec2S32 { S32 x, y; } Vec2S32;
|
||||||
|
typedef struct Vec3F32 { F32 x, y, z; } Vec3F32;
|
||||||
|
typedef struct Vec4F32 { F32 x, y, z, w; } Vec4F32;
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Range types
|
||||||
|
|
||||||
|
typedef struct Rng1F32 { F32 min, max; } Rng1F32;
|
||||||
|
typedef struct Rng1S64 { S64 min, max; } Rng1S64;
|
||||||
|
typedef struct Rng2F32 { Vec2F32 p0, p1; } Rng2F32;
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Constructors
|
||||||
|
|
||||||
|
static inline Vec2F32 v2f32(F32 x, F32 y) { Vec2F32 r = {x, y}; return r; }
|
||||||
|
static inline Vec2S32 v2s32(S32 x, S32 y) { Vec2S32 r = {x, y}; return r; }
|
||||||
|
static inline Vec3F32 v3f32(F32 x, F32 y, F32 z) { Vec3F32 r = {x, y, z}; return r; }
|
||||||
|
static inline Vec4F32 v4f32(F32 x, F32 y, F32 z, F32 w) { Vec4F32 r = {x, y, z, w}; return r; }
|
||||||
|
static inline Rng1F32 rng1f32(F32 min, F32 max) { Rng1F32 r = {min, max}; return r; }
|
||||||
|
static inline Rng1S64 rng1s64(S64 min, S64 max) { Rng1S64 r = {min, max}; return r; }
|
||||||
|
static inline Rng2F32 rng2f32(Vec2F32 p0, Vec2F32 p1) { Rng2F32 r = {p0, p1}; return r; }
|
||||||
|
static inline Rng2F32 rng2f32p(F32 x0, F32 y0, F32 x1, F32 y1) { Rng2F32 r = {{x0, y0}, {x1, y1}}; return r; }
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Vec2F32 operations
|
||||||
|
|
||||||
|
static inline Vec2F32 add_2f32(Vec2F32 a, Vec2F32 b) { return v2f32(a.x + b.x, a.y + b.y); }
|
||||||
|
static inline Vec2F32 sub_2f32(Vec2F32 a, Vec2F32 b) { return v2f32(a.x - b.x, a.y - b.y); }
|
||||||
|
static inline Vec2F32 mul_2f32(Vec2F32 a, Vec2F32 b) { return v2f32(a.x * b.x, a.y * b.y); }
|
||||||
|
static inline Vec2F32 scale_2f32(Vec2F32 v, F32 s) { return v2f32(v.x * s, v.y * s); }
|
||||||
|
|
||||||
|
// Axis-indexed access
|
||||||
|
static inline F32 v2f32_axis(Vec2F32 v, Axis2 a) { return a == Axis2_X ? v.x : v.y; }
|
||||||
|
static inline void v2f32_set_axis(Vec2F32 *v, Axis2 a, F32 val) {
|
||||||
|
if (a == Axis2_X) v->x = val; else v->y = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Vec4F32 operations
|
||||||
|
|
||||||
|
static inline Vec4F32 add_4f32(Vec4F32 a, Vec4F32 b) { return v4f32(a.x+b.x, a.y+b.y, a.z+b.z, a.w+b.w); }
|
||||||
|
static inline Vec4F32 scale_4f32(Vec4F32 v, F32 s) { return v4f32(v.x*s, v.y*s, v.z*s, v.w*s); }
|
||||||
|
static inline Vec4F32 lerp_4f32(Vec4F32 a, Vec4F32 b, F32 t) {
|
||||||
|
return v4f32(a.x + (b.x - a.x)*t, a.y + (b.y - a.y)*t, a.z + (b.z - a.z)*t, a.w + (b.w - a.w)*t);
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Rng2F32 operations
|
||||||
|
|
||||||
|
static inline F32 rng2f32_width(Rng2F32 r) { return r.p1.x - r.p0.x; }
|
||||||
|
static inline F32 rng2f32_height(Rng2F32 r) { return r.p1.y - r.p0.y; }
|
||||||
|
static inline Vec2F32 rng2f32_dim(Rng2F32 r) { return v2f32(r.p1.x - r.p0.x, r.p1.y - r.p0.y); }
|
||||||
|
static inline Vec2F32 rng2f32_center(Rng2F32 r) { return v2f32((r.p0.x + r.p1.x)*0.5f, (r.p0.y + r.p1.y)*0.5f); }
|
||||||
|
static inline B32 rng2f32_contains(Rng2F32 r, Vec2F32 p) {
|
||||||
|
return p.x >= r.p0.x && p.x <= r.p1.x && p.y >= r.p0.y && p.y <= r.p1.y;
|
||||||
|
}
|
||||||
|
static inline Rng2F32 rng2f32_pad(Rng2F32 r, F32 p) {
|
||||||
|
return rng2f32p(r.p0.x - p, r.p0.y - p, r.p1.x + p, r.p1.y + p);
|
||||||
|
}
|
||||||
|
static inline Rng2F32 rng2f32_shift(Rng2F32 r, Vec2F32 v) {
|
||||||
|
return rng2f32p(r.p0.x + v.x, r.p0.y + v.y, r.p1.x + v.x, r.p1.y + v.y);
|
||||||
|
}
|
||||||
|
static inline Rng2F32 rng2f32_intersect(Rng2F32 a, Rng2F32 b) {
|
||||||
|
return rng2f32p(Max(a.p0.x, b.p0.x), Max(a.p0.y, b.p0.y),
|
||||||
|
Min(a.p1.x, b.p1.x), Min(a.p1.y, b.p1.y));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Axis-indexed range dimension
|
||||||
|
static inline F32 rng2f32_dim_axis(Rng2F32 r, Axis2 a) {
|
||||||
|
return a == Axis2_X ? (r.p1.x - r.p0.x) : (r.p1.y - r.p0.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// F32 helpers
|
||||||
|
|
||||||
|
static inline F32 lerp_1f32(F32 a, F32 b, F32 t) { return a + (b - a) * t; }
|
||||||
|
static inline F32 abs_f32(F32 x) { return x < 0 ? -x : x; }
|
||||||
34
c/base/base_strings.c
Normal file
34
c/base/base_strings.c
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#include "base/base_strings.h"
|
||||||
|
#include "base/base_arena.h"
|
||||||
|
|
||||||
|
Str8 str8_pushf(Arena *arena, const char *fmt, ...) {
|
||||||
|
va_list args, args2;
|
||||||
|
va_start(args, fmt);
|
||||||
|
va_copy(args2, args);
|
||||||
|
S32 len = vsnprintf(NULL, 0, fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
char *buf = push_array(arena, char, len + 1);
|
||||||
|
vsnprintf(buf, len + 1, fmt, args2);
|
||||||
|
va_end(args2);
|
||||||
|
|
||||||
|
Str8 r = {buf, (U64)len};
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
Str8 str8_push_copy(Arena *arena, Str8 s) {
|
||||||
|
if (s.size == 0 || !s.str) { Str8 r = {NULL, 0}; return r; }
|
||||||
|
char *buf = push_array_no_zero(arena, char, s.size + 1);
|
||||||
|
MemoryCopy(buf, s.str, s.size);
|
||||||
|
buf[s.size] = 0;
|
||||||
|
Str8 r = {buf, s.size};
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
void str8_list_push(Arena *arena, Str8List *list, Str8 s) {
|
||||||
|
Str8Node *node = push_array(arena, Str8Node, 1);
|
||||||
|
node->string = s;
|
||||||
|
SLLQueuePush(list->first, list->last, node);
|
||||||
|
list->count++;
|
||||||
|
list->total_size += s.size;
|
||||||
|
}
|
||||||
49
c/base/base_strings.h
Normal file
49
c/base/base_strings.h
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
#pragma once
|
||||||
|
// base_strings.h - Simple length-delimited string type
|
||||||
|
// Inspired by raddebugger's String8
|
||||||
|
|
||||||
|
#include "base/base_core.h"
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// String types
|
||||||
|
|
||||||
|
typedef struct Str8 {
|
||||||
|
const char *str;
|
||||||
|
U64 size;
|
||||||
|
} Str8;
|
||||||
|
|
||||||
|
typedef struct Str8Node {
|
||||||
|
struct Str8Node *next;
|
||||||
|
Str8 string;
|
||||||
|
} Str8Node;
|
||||||
|
|
||||||
|
typedef struct Str8List {
|
||||||
|
Str8Node *first;
|
||||||
|
Str8Node *last;
|
||||||
|
U64 count;
|
||||||
|
U64 total_size;
|
||||||
|
} Str8List;
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Forward declaration for Arena
|
||||||
|
typedef struct Arena Arena;
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Constructors
|
||||||
|
|
||||||
|
static inline Str8 str8(const char *s, U64 len) { Str8 r = {s, len}; return r; }
|
||||||
|
static inline Str8 str8_cstr(const char *s) { Str8 r = {s, s ? (U64)strlen(s) : 0}; return r; }
|
||||||
|
static inline Str8 str8_lit(const char *s) { Str8 r = {s, s ? (U64)strlen(s) : 0}; return r; }
|
||||||
|
static inline B32 str8_match(Str8 a, Str8 b) {
|
||||||
|
if (a.size != b.size) return 0;
|
||||||
|
return MemoryCompare(a.str, b.str, a.size) == 0;
|
||||||
|
}
|
||||||
|
static inline B32 str8_is_empty(Str8 s) { return s.size == 0 || s.str == NULL; }
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// String operations (require arena)
|
||||||
|
|
||||||
|
Str8 str8_pushf(Arena *arena, const char *fmt, ...);
|
||||||
|
Str8 str8_push_copy(Arena *arena, Str8 s);
|
||||||
|
|
||||||
|
void str8_list_push(Arena *arena, Str8List *list, Str8 s);
|
||||||
707
c/build.h
Normal file
707
c/build.h
Normal file
@@ -0,0 +1,707 @@
|
|||||||
|
// build.h — Minimal C build system (stb-style single header)
|
||||||
|
//
|
||||||
|
// Define BUILD_IMPLEMENTATION in exactly one file before including this header.
|
||||||
|
//
|
||||||
|
// Bootstrap (one-time):
|
||||||
|
// Windows: cl /nologo build.c
|
||||||
|
// macOS: cc build.c -o build
|
||||||
|
// After that, just run ./build (or build.exe) — it rebuilds itself.
|
||||||
|
|
||||||
|
#ifndef BUILD_H
|
||||||
|
#define BUILD_H
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
# define _CRT_SECURE_NO_WARNINGS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
# define WIN32_LEAN_AND_MEAN
|
||||||
|
# include <windows.h>
|
||||||
|
# include <direct.h>
|
||||||
|
# include <io.h>
|
||||||
|
#else
|
||||||
|
# include <sys/stat.h>
|
||||||
|
# include <sys/wait.h>
|
||||||
|
# include <unistd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Macros
|
||||||
|
|
||||||
|
#define ARRAY_LEN(a) (sizeof(a) / sizeof((a)[0]))
|
||||||
|
|
||||||
|
// Self-rebuild: call at the top of main(). Recompiles the build script
|
||||||
|
// if the source file is newer than the running binary, then re-executes.
|
||||||
|
#define GO_REBUILD_URSELF(argc, argv) \
|
||||||
|
go_rebuild_urself((argc), (argv), __FILE__)
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Logging
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
LOG_INFO,
|
||||||
|
LOG_WARNING,
|
||||||
|
LOG_ERROR,
|
||||||
|
} Log_Level;
|
||||||
|
|
||||||
|
void build_log(Log_Level level, const char *fmt, ...);
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Temp allocator — ring buffer for short-lived sprintf results
|
||||||
|
|
||||||
|
#ifndef TEMP_CAPACITY
|
||||||
|
#define TEMP_CAPACITY (8 * 1024 * 1024)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
char *temp_sprintf(const char *fmt, ...);
|
||||||
|
void temp_reset(void);
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// String builder — growable byte buffer
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char *items;
|
||||||
|
size_t count;
|
||||||
|
size_t capacity;
|
||||||
|
} String_Builder;
|
||||||
|
|
||||||
|
bool sb_read_file(String_Builder *sb, const char *path);
|
||||||
|
void sb_free(String_Builder *sb);
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// File I/O
|
||||||
|
|
||||||
|
bool write_entire_file(const char *path, const void *data, size_t size);
|
||||||
|
bool delete_file(const char *path);
|
||||||
|
bool rename_file(const char *old_path, const char *new_path);
|
||||||
|
bool mkdir_if_not_exists(const char *path);
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Rebuild checking — returns 1 if rebuild needed, 0 if up to date, -1 on error
|
||||||
|
|
||||||
|
int needs_rebuild(const char *output, const char **inputs, size_t count);
|
||||||
|
int needs_rebuild1(const char *output, const char *input);
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Command builder and runner
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char **items;
|
||||||
|
size_t count;
|
||||||
|
size_t capacity;
|
||||||
|
} Cmd;
|
||||||
|
|
||||||
|
// Appends arguments to a command. Use via the macro which auto-counts args.
|
||||||
|
void cmd__append(Cmd *cmd, size_t n, ...);
|
||||||
|
void cmd__append_arr(Cmd *cmd, const char **args, size_t n);
|
||||||
|
#define cmd_append(cmd, ...) do { \
|
||||||
|
const char *_cmd_args[] = {__VA_ARGS__}; \
|
||||||
|
cmd__append_arr((cmd), _cmd_args, sizeof(_cmd_args) / sizeof(_cmd_args[0])); \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
// Runs the command synchronously, resets cmd->count to 0, returns success.
|
||||||
|
bool cmd_run(Cmd *cmd);
|
||||||
|
|
||||||
|
// Frees the command's allocated memory.
|
||||||
|
void cmd_free(Cmd *cmd);
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Self-rebuild
|
||||||
|
|
||||||
|
void go_rebuild_urself(int argc, char **argv, const char *source);
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// File embedding — convert a binary file to a C header with an array + size.
|
||||||
|
|
||||||
|
bool embed_file(const char *input_path, const char *output_path, const char *var_name);
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// SPIR-V shader compilation — compile .glsl to .spv via glslc, then embed as C header.
|
||||||
|
|
||||||
|
bool compile_shader(const char *glslc_path, const char *src, const char *spv_path, const char *stage);
|
||||||
|
bool embed_spirv(const char *spv_path, const char *header_path, const char *array_name);
|
||||||
|
|
||||||
|
#endif // BUILD_H
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Implementation
|
||||||
|
////////////////////////////////
|
||||||
|
|
||||||
|
#ifdef BUILD_IMPLEMENTATION
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Temp allocator
|
||||||
|
|
||||||
|
static size_t g_temp_size = 0;
|
||||||
|
static char g_temp[TEMP_CAPACITY];
|
||||||
|
|
||||||
|
void temp_reset(void) {
|
||||||
|
g_temp_size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *temp_alloc(size_t size) {
|
||||||
|
if (g_temp_size + size > TEMP_CAPACITY) {
|
||||||
|
g_temp_size = 0; // wrap around
|
||||||
|
}
|
||||||
|
char *result = g_temp + g_temp_size;
|
||||||
|
g_temp_size += size;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *temp_sprintf(const char *fmt, ...) {
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
va_list args2;
|
||||||
|
va_copy(args2, args);
|
||||||
|
int n = vsnprintf(NULL, 0, fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
char *result = temp_alloc(n + 1);
|
||||||
|
vsnprintf(result, n + 1, fmt, args2);
|
||||||
|
va_end(args2);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Logging
|
||||||
|
|
||||||
|
void build_log(Log_Level level, const char *fmt, ...) {
|
||||||
|
switch (level) {
|
||||||
|
case LOG_INFO: fprintf(stderr, "[INFO] "); break;
|
||||||
|
case LOG_WARNING: fprintf(stderr, "[WARNING] "); break;
|
||||||
|
case LOG_ERROR: fprintf(stderr, "[ERROR] "); break;
|
||||||
|
}
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
vfprintf(stderr, fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
fprintf(stderr, "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// String builder
|
||||||
|
|
||||||
|
static void sb_ensure(String_Builder *sb, size_t needed) {
|
||||||
|
if (sb->count + needed <= sb->capacity) return;
|
||||||
|
size_t new_cap = sb->capacity ? sb->capacity * 2 : 256;
|
||||||
|
while (new_cap < sb->count + needed) new_cap *= 2;
|
||||||
|
sb->items = (char *)realloc(sb->items, new_cap);
|
||||||
|
sb->capacity = new_cap;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sb_read_file(String_Builder *sb, const char *path) {
|
||||||
|
FILE *f = fopen(path, "rb");
|
||||||
|
if (!f) {
|
||||||
|
build_log(LOG_ERROR, "Could not open %s: %s", path, strerror(errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fseek(f, 0, SEEK_END);
|
||||||
|
#ifdef _WIN32
|
||||||
|
long long m = _telli64(_fileno(f));
|
||||||
|
#else
|
||||||
|
long long m = ftell(f);
|
||||||
|
#endif
|
||||||
|
if (m < 0) {
|
||||||
|
build_log(LOG_ERROR, "Could not get size of %s: %s", path, strerror(errno));
|
||||||
|
fclose(f);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
fseek(f, 0, SEEK_SET);
|
||||||
|
|
||||||
|
sb_ensure(sb, (size_t)m);
|
||||||
|
fread(sb->items + sb->count, (size_t)m, 1, f);
|
||||||
|
if (ferror(f)) {
|
||||||
|
build_log(LOG_ERROR, "Could not read %s: %s", path, strerror(errno));
|
||||||
|
fclose(f);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
sb->count += (size_t)m;
|
||||||
|
|
||||||
|
fclose(f);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sb_free(String_Builder *sb) {
|
||||||
|
free(sb->items);
|
||||||
|
sb->items = NULL;
|
||||||
|
sb->count = 0;
|
||||||
|
sb->capacity = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// File I/O
|
||||||
|
|
||||||
|
bool write_entire_file(const char *path, const void *data, size_t size) {
|
||||||
|
FILE *f = fopen(path, "wb");
|
||||||
|
if (!f) {
|
||||||
|
build_log(LOG_ERROR, "Could not open %s for writing: %s", path, strerror(errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const char *buf = (const char *)data;
|
||||||
|
while (size > 0) {
|
||||||
|
size_t n = fwrite(buf, 1, size, f);
|
||||||
|
if (ferror(f)) {
|
||||||
|
build_log(LOG_ERROR, "Could not write to %s: %s", path, strerror(errno));
|
||||||
|
fclose(f);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
size -= n;
|
||||||
|
buf += n;
|
||||||
|
}
|
||||||
|
fclose(f);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool delete_file(const char *path) {
|
||||||
|
build_log(LOG_INFO, "Deleting %s", path);
|
||||||
|
#ifdef _WIN32
|
||||||
|
DWORD attr = GetFileAttributesA(path);
|
||||||
|
if (attr == INVALID_FILE_ATTRIBUTES) return true; // doesn't exist
|
||||||
|
if (attr & FILE_ATTRIBUTE_DIRECTORY) {
|
||||||
|
if (!RemoveDirectoryA(path)) {
|
||||||
|
build_log(LOG_ERROR, "Could not delete directory %s", path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!DeleteFileA(path)) {
|
||||||
|
build_log(LOG_ERROR, "Could not delete file %s", path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if (remove(path) < 0 && errno != ENOENT) {
|
||||||
|
build_log(LOG_ERROR, "Could not delete %s: %s", path, strerror(errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rename_file(const char *old_path, const char *new_path) {
|
||||||
|
build_log(LOG_INFO, "Renaming %s -> %s", old_path, new_path);
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (!MoveFileEx(old_path, new_path, MOVEFILE_REPLACE_EXISTING)) {
|
||||||
|
build_log(LOG_ERROR, "Could not rename %s to %s", old_path, new_path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if (rename(old_path, new_path) < 0) {
|
||||||
|
build_log(LOG_ERROR, "Could not rename %s to %s: %s", old_path, new_path, strerror(errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mkdir_if_not_exists(const char *path) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
int result = _mkdir(path);
|
||||||
|
#else
|
||||||
|
int result = mkdir(path, 0755);
|
||||||
|
#endif
|
||||||
|
if (result < 0) {
|
||||||
|
if (errno == EEXIST) return true;
|
||||||
|
build_log(LOG_ERROR, "Could not create directory %s: %s", path, strerror(errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
build_log(LOG_INFO, "Created directory %s", path);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Rebuild checking
|
||||||
|
|
||||||
|
int needs_rebuild(const char *output, const char **inputs, size_t count) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
HANDLE out_h = CreateFile(output, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
|
||||||
|
if (out_h == INVALID_HANDLE_VALUE) {
|
||||||
|
if (GetLastError() == ERROR_FILE_NOT_FOUND) return 1;
|
||||||
|
build_log(LOG_ERROR, "Could not open %s", output);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
FILETIME out_time;
|
||||||
|
if (!GetFileTime(out_h, NULL, NULL, &out_time)) {
|
||||||
|
CloseHandle(out_h);
|
||||||
|
build_log(LOG_ERROR, "Could not get time of %s", output);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
CloseHandle(out_h);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < count; i++) {
|
||||||
|
HANDLE in_h = CreateFile(inputs[i], GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
|
||||||
|
if (in_h == INVALID_HANDLE_VALUE) {
|
||||||
|
build_log(LOG_ERROR, "Could not open %s", inputs[i]);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
FILETIME in_time;
|
||||||
|
if (!GetFileTime(in_h, NULL, NULL, &in_time)) {
|
||||||
|
CloseHandle(in_h);
|
||||||
|
build_log(LOG_ERROR, "Could not get time of %s", inputs[i]);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
CloseHandle(in_h);
|
||||||
|
if (CompareFileTime(&in_time, &out_time) == 1) return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
#else
|
||||||
|
struct stat sb;
|
||||||
|
memset(&sb, 0, sizeof(sb));
|
||||||
|
if (stat(output, &sb) < 0) {
|
||||||
|
if (errno == ENOENT) return 1;
|
||||||
|
build_log(LOG_ERROR, "Could not stat %s: %s", output, strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
time_t out_time = sb.st_mtime;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < count; i++) {
|
||||||
|
if (stat(inputs[i], &sb) < 0) {
|
||||||
|
build_log(LOG_ERROR, "Could not stat %s: %s", inputs[i], strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (sb.st_mtime > out_time) return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
int needs_rebuild1(const char *output, const char *input) {
|
||||||
|
return needs_rebuild(output, &input, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Command builder and runner
|
||||||
|
|
||||||
|
static void cmd__grow(Cmd *cmd) {
|
||||||
|
size_t new_cap = cmd->capacity ? cmd->capacity * 2 : 32;
|
||||||
|
cmd->items = (const char **)realloc(cmd->items, new_cap * sizeof(const char *));
|
||||||
|
cmd->capacity = new_cap;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cmd__append(Cmd *cmd, size_t n, ...) {
|
||||||
|
va_list args;
|
||||||
|
va_start(args, n);
|
||||||
|
for (size_t i = 0; i < n; i++) {
|
||||||
|
const char *arg = va_arg(args, const char *);
|
||||||
|
if (cmd->count >= cmd->capacity) cmd__grow(cmd);
|
||||||
|
cmd->items[cmd->count++] = arg;
|
||||||
|
}
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
void cmd__append_arr(Cmd *cmd, const char **args, size_t n) {
|
||||||
|
for (size_t i = 0; i < n; i++) {
|
||||||
|
if (cmd->count >= cmd->capacity) cmd__grow(cmd);
|
||||||
|
cmd->items[cmd->count++] = args[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cmd_render(Cmd *cmd, char *buf, size_t buf_size) {
|
||||||
|
size_t pos = 0;
|
||||||
|
for (size_t i = 0; i < cmd->count && pos < buf_size - 1; i++) {
|
||||||
|
if (i > 0 && pos < buf_size - 1) buf[pos++] = ' ';
|
||||||
|
const char *arg = cmd->items[i];
|
||||||
|
size_t len = strlen(arg);
|
||||||
|
if (pos + len < buf_size - 1) {
|
||||||
|
memcpy(buf + pos, arg, len);
|
||||||
|
pos += len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf[pos] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
// Properly quote a command line for CreateProcess on Windows.
|
||||||
|
static void cmd_quote_win32(Cmd *cmd, String_Builder *quoted) {
|
||||||
|
for (size_t i = 0; i < cmd->count; i++) {
|
||||||
|
const char *arg = cmd->items[i];
|
||||||
|
size_t len = strlen(arg);
|
||||||
|
if (i > 0) {
|
||||||
|
sb_ensure(quoted, 1);
|
||||||
|
quoted->items[quoted->count++] = ' ';
|
||||||
|
}
|
||||||
|
if (len != 0 && strpbrk(arg, " \t\n\v\"") == NULL) {
|
||||||
|
sb_ensure(quoted, len);
|
||||||
|
memcpy(quoted->items + quoted->count, arg, len);
|
||||||
|
quoted->count += len;
|
||||||
|
} else {
|
||||||
|
sb_ensure(quoted, len * 2 + 3);
|
||||||
|
quoted->items[quoted->count++] = '"';
|
||||||
|
size_t backslashes = 0;
|
||||||
|
for (size_t j = 0; j < len; j++) {
|
||||||
|
char c = arg[j];
|
||||||
|
if (c == '\\') {
|
||||||
|
backslashes++;
|
||||||
|
} else {
|
||||||
|
if (c == '"') {
|
||||||
|
for (size_t k = 0; k < backslashes + 1; k++)
|
||||||
|
quoted->items[quoted->count++] = '\\';
|
||||||
|
}
|
||||||
|
backslashes = 0;
|
||||||
|
}
|
||||||
|
quoted->items[quoted->count++] = c;
|
||||||
|
}
|
||||||
|
for (size_t k = 0; k < backslashes; k++)
|
||||||
|
quoted->items[quoted->count++] = '\\';
|
||||||
|
quoted->items[quoted->count++] = '"';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb_ensure(quoted, 1);
|
||||||
|
quoted->items[quoted->count] = '\0';
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool cmd_run(Cmd *cmd) {
|
||||||
|
if (cmd->count == 0) {
|
||||||
|
build_log(LOG_ERROR, "Cannot run empty command");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log the command
|
||||||
|
{
|
||||||
|
char render_buf[4096];
|
||||||
|
cmd_render(cmd, render_buf, sizeof(render_buf));
|
||||||
|
build_log(LOG_INFO, "CMD: %s", render_buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
STARTUPINFO si;
|
||||||
|
memset(&si, 0, sizeof(si));
|
||||||
|
si.cb = sizeof(si);
|
||||||
|
si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
|
||||||
|
si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||||
|
si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
|
||||||
|
si.dwFlags |= STARTF_USESTDHANDLES;
|
||||||
|
|
||||||
|
PROCESS_INFORMATION pi;
|
||||||
|
memset(&pi, 0, sizeof(pi));
|
||||||
|
|
||||||
|
String_Builder quoted = {0};
|
||||||
|
cmd_quote_win32(cmd, "ed);
|
||||||
|
|
||||||
|
BOOL ok = CreateProcessA(NULL, quoted.items, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
|
||||||
|
sb_free("ed);
|
||||||
|
|
||||||
|
cmd->count = 0;
|
||||||
|
|
||||||
|
if (!ok) {
|
||||||
|
build_log(LOG_ERROR, "Could not create process for %s", cmd->items[0]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseHandle(pi.hThread);
|
||||||
|
WaitForSingleObject(pi.hProcess, INFINITE);
|
||||||
|
|
||||||
|
DWORD exit_code;
|
||||||
|
if (!GetExitCodeProcess(pi.hProcess, &exit_code)) {
|
||||||
|
build_log(LOG_ERROR, "Could not get exit code");
|
||||||
|
CloseHandle(pi.hProcess);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
CloseHandle(pi.hProcess);
|
||||||
|
|
||||||
|
if (exit_code != 0) {
|
||||||
|
build_log(LOG_ERROR, "Command exited with code %lu", exit_code);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
pid_t pid = fork();
|
||||||
|
if (pid < 0) {
|
||||||
|
build_log(LOG_ERROR, "Could not fork: %s", strerror(errno));
|
||||||
|
cmd->count = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pid == 0) {
|
||||||
|
// Child: build null-terminated argv
|
||||||
|
const char **argv_null = (const char **)malloc((cmd->count + 1) * sizeof(const char *));
|
||||||
|
memcpy(argv_null, cmd->items, cmd->count * sizeof(const char *));
|
||||||
|
argv_null[cmd->count] = NULL;
|
||||||
|
execvp(argv_null[0], (char *const *)argv_null);
|
||||||
|
build_log(LOG_ERROR, "Could not exec %s: %s", argv_null[0], strerror(errno));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd->count = 0;
|
||||||
|
|
||||||
|
int wstatus;
|
||||||
|
for (;;) {
|
||||||
|
if (waitpid(pid, &wstatus, 0) < 0) {
|
||||||
|
build_log(LOG_ERROR, "Could not wait on pid %d: %s", pid, strerror(errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (WIFEXITED(wstatus)) {
|
||||||
|
int code = WEXITSTATUS(wstatus);
|
||||||
|
if (code != 0) {
|
||||||
|
build_log(LOG_ERROR, "Command exited with code %d", code);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (WIFSIGNALED(wstatus)) {
|
||||||
|
build_log(LOG_ERROR, "Command killed by signal %d", WTERMSIG(wstatus));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void cmd_free(Cmd *cmd) {
|
||||||
|
free(cmd->items);
|
||||||
|
cmd->items = NULL;
|
||||||
|
cmd->count = 0;
|
||||||
|
cmd->capacity = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Self-rebuild
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
# define REBUILD_CMD(binary, source) "cl.exe", "/nologo", temp_sprintf("/Fe:%s", binary), source
|
||||||
|
#else
|
||||||
|
# define REBUILD_CMD(binary, source) "cc", "-o", binary, source
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void go_rebuild_urself(int argc, char **argv, const char *source) {
|
||||||
|
const char *binary = argv[0];
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
// Ensure .exe extension for Windows
|
||||||
|
size_t len = strlen(binary);
|
||||||
|
if (len < 4 || strcmp(binary + len - 4, ".exe") != 0) {
|
||||||
|
binary = temp_sprintf("%s.exe", binary);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int rebuild = needs_rebuild1(binary, source);
|
||||||
|
if (rebuild < 0) exit(1);
|
||||||
|
if (rebuild == 0) return;
|
||||||
|
|
||||||
|
const char *old_binary = temp_sprintf("%s.old", binary);
|
||||||
|
if (!rename_file(binary, old_binary)) exit(1);
|
||||||
|
|
||||||
|
Cmd cmd = {0};
|
||||||
|
cmd_append(&cmd, REBUILD_CMD(binary, source));
|
||||||
|
if (!cmd_run(&cmd)) {
|
||||||
|
rename_file(old_binary, binary);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-execute with the new binary
|
||||||
|
cmd_append(&cmd, binary);
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
for (i = 1; i < argc; i++) {
|
||||||
|
cmd__append(&cmd, 1, argv[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!cmd_run(&cmd)) exit(1);
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// File embedding
|
||||||
|
|
||||||
|
bool embed_file(const char *input_path, const char *output_path, const char *var_name) {
|
||||||
|
int rebuild = needs_rebuild1(output_path, input_path);
|
||||||
|
if (rebuild == 0) {
|
||||||
|
build_log(LOG_INFO, "Up to date: %s", output_path);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (rebuild < 0) return false;
|
||||||
|
|
||||||
|
build_log(LOG_INFO, "Embedding %s -> %s", input_path, output_path);
|
||||||
|
|
||||||
|
String_Builder src = {0};
|
||||||
|
if (!sb_read_file(&src, input_path)) return false;
|
||||||
|
|
||||||
|
String_Builder out = {0};
|
||||||
|
const char *header = temp_sprintf(
|
||||||
|
"// Auto-generated from %s — do not edit by hand.\n"
|
||||||
|
"#ifndef %s_GEN_H\n"
|
||||||
|
"#define %s_GEN_H\n\n"
|
||||||
|
"static const unsigned int %s_size = %u;\n\n"
|
||||||
|
"static const unsigned char %s_data[] = {\n",
|
||||||
|
input_path, var_name, var_name, var_name, (unsigned)src.count, var_name);
|
||||||
|
size_t header_len = strlen(header);
|
||||||
|
sb_ensure(&out, header_len);
|
||||||
|
memcpy(out.items + out.count, header, header_len);
|
||||||
|
out.count += header_len;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < src.count; i += 16) {
|
||||||
|
sb_ensure(&out, 4 + 16 * 6 + 2);
|
||||||
|
memcpy(out.items + out.count, " ", 4);
|
||||||
|
out.count += 4;
|
||||||
|
size_t end = i + 16;
|
||||||
|
if (end > src.count) end = src.count;
|
||||||
|
for (size_t j = i; j < end; j++) {
|
||||||
|
unsigned char b = (unsigned char)src.items[j];
|
||||||
|
char hex[7];
|
||||||
|
int n = snprintf(hex, sizeof(hex), "0x%02x, ", b);
|
||||||
|
memcpy(out.items + out.count, hex, (size_t)n);
|
||||||
|
out.count += (size_t)n;
|
||||||
|
}
|
||||||
|
out.items[out.count++] = '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *footer = "};\n\n#endif\n";
|
||||||
|
size_t footer_len = strlen(footer);
|
||||||
|
sb_ensure(&out, footer_len);
|
||||||
|
memcpy(out.items + out.count, footer, footer_len);
|
||||||
|
out.count += footer_len;
|
||||||
|
|
||||||
|
bool ok = write_entire_file(output_path, out.items, out.count);
|
||||||
|
sb_free(&src);
|
||||||
|
sb_free(&out);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// SPIR-V shader compilation
|
||||||
|
|
||||||
|
bool compile_shader(const char *glslc_path, const char *src, const char *spv_path, const char *stage) {
|
||||||
|
Cmd cmd = {0};
|
||||||
|
cmd_append(&cmd, glslc_path, temp_sprintf("-fshader-stage=%s", stage), "-o", spv_path, src);
|
||||||
|
return cmd_run(&cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool embed_spirv(const char *spv_path, const char *header_path, const char *array_name) {
|
||||||
|
String_Builder sb = {0};
|
||||||
|
if (!sb_read_file(&sb, spv_path)) return false;
|
||||||
|
|
||||||
|
FILE *out = fopen(header_path, "wb");
|
||||||
|
if (!out) {
|
||||||
|
build_log(LOG_ERROR, "Could not open %s for writing", header_path);
|
||||||
|
sb_free(&sb);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(out, "// Auto-generated from %s — do not edit\n", spv_path);
|
||||||
|
fprintf(out, "#pragma once\n\n");
|
||||||
|
size_t word_count = sb.count / 4;
|
||||||
|
fprintf(out, "#include <stdint.h>\n\n");
|
||||||
|
fprintf(out, "static const uint32_t %s[] = {\n", array_name);
|
||||||
|
const uint32_t *words = (const uint32_t *)sb.items;
|
||||||
|
for (size_t i = 0; i < word_count; i++) {
|
||||||
|
if (i % 8 == 0) fprintf(out, " ");
|
||||||
|
fprintf(out, "0x%08x,", words[i]);
|
||||||
|
if (i % 8 == 7 || i == word_count - 1) fprintf(out, "\n");
|
||||||
|
}
|
||||||
|
fprintf(out, "};\n");
|
||||||
|
|
||||||
|
fclose(out);
|
||||||
|
build_log(LOG_INFO, "Generated %s (%zu bytes)", header_path, sb.count);
|
||||||
|
sb_free(&sb);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // BUILD_IMPLEMENTATION
|
||||||
235
c/config/config.c
Normal file
235
c/config/config.c
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
// config.c — Global program configuration (config.ini next to binary)
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#ifdef __APPLE__
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <libgen.h>
|
||||||
|
#include <mach-o/dyld.h>
|
||||||
|
#else
|
||||||
|
#include <windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Config g_config = {0};
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Path resolution
|
||||||
|
|
||||||
|
static void config_get_path(char *out, S32 out_size) {
|
||||||
|
char exe_path[CONFIG_PATH_MAX];
|
||||||
|
#ifdef __APPLE__
|
||||||
|
U32 sz = sizeof(exe_path);
|
||||||
|
_NSGetExecutablePath(exe_path, &sz);
|
||||||
|
#else
|
||||||
|
GetModuleFileNameA(NULL, exe_path, CONFIG_PATH_MAX);
|
||||||
|
#endif
|
||||||
|
// Strip binary name to get directory
|
||||||
|
char *sep = strrchr(exe_path, '/');
|
||||||
|
#ifndef __APPLE__
|
||||||
|
char *bsep = strrchr(exe_path, '\\');
|
||||||
|
if (bsep && (!sep || bsep > sep)) sep = bsep;
|
||||||
|
#endif
|
||||||
|
if (sep) sep[1] = '\0';
|
||||||
|
snprintf(out, out_size, "%sconfig.ini", exe_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Helpers
|
||||||
|
|
||||||
|
static void config_trim(char *s) {
|
||||||
|
char *start = s;
|
||||||
|
while (*start == ' ' || *start == '\t') start++;
|
||||||
|
if (start != s) memmove(s, start, strlen(start) + 1);
|
||||||
|
S32 len = (S32)strlen(s);
|
||||||
|
while (len > 0 && (s[len - 1] == ' ' || s[len - 1] == '\t' ||
|
||||||
|
s[len - 1] == '\r' || s[len - 1] == '\n'))
|
||||||
|
s[--len] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Defaults
|
||||||
|
|
||||||
|
static void config_set_defaults(void) {
|
||||||
|
MemoryZeroStruct(&g_config);
|
||||||
|
snprintf(g_config.theme, sizeof(g_config.theme), "%s", "Gruvbox Dark");
|
||||||
|
g_config.show_line_numbers = 1;
|
||||||
|
g_config.syntax_enabled = 1;
|
||||||
|
g_config.block_cursor = 1;
|
||||||
|
g_config.cursor_smear = 1;
|
||||||
|
g_config.smooth_scroll = 1;
|
||||||
|
g_config.ui_scale = 1.0f;
|
||||||
|
g_config.editor_font_size = 15.0f;
|
||||||
|
snprintf(g_config.editor_font, sizeof(g_config.editor_font), "%s", "FiraCode");
|
||||||
|
snprintf(g_config.ui_font, sizeof(g_config.ui_font), "%s", "Inter");
|
||||||
|
g_config.window_width = 1280;
|
||||||
|
g_config.window_height = 800;
|
||||||
|
g_config.active_project[0] = '\0';
|
||||||
|
g_config.recent_dir_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Load
|
||||||
|
|
||||||
|
static void config_load(void) {
|
||||||
|
config_set_defaults();
|
||||||
|
|
||||||
|
char path[CONFIG_PATH_MAX];
|
||||||
|
config_get_path(path, sizeof(path));
|
||||||
|
|
||||||
|
FILE *f = fopen(path, "rb");
|
||||||
|
if (!f) {
|
||||||
|
// No config file — write defaults
|
||||||
|
config_save();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char line_buf[CONFIG_PATH_MAX + 64];
|
||||||
|
char current_section[64] = {0};
|
||||||
|
|
||||||
|
while (fgets(line_buf, sizeof(line_buf), f)) {
|
||||||
|
config_trim(line_buf);
|
||||||
|
if (line_buf[0] == '\0' || line_buf[0] == '#' || line_buf[0] == ';')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Section header
|
||||||
|
if (line_buf[0] == '[') {
|
||||||
|
char *close = strchr(line_buf, ']');
|
||||||
|
if (close) {
|
||||||
|
*close = '\0';
|
||||||
|
snprintf(current_section, sizeof(current_section), "%s", line_buf + 1);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key = value
|
||||||
|
char *eq = strchr(line_buf, '=');
|
||||||
|
if (!eq) continue;
|
||||||
|
*eq = '\0';
|
||||||
|
char *key = line_buf;
|
||||||
|
char *val = eq + 1;
|
||||||
|
config_trim(key);
|
||||||
|
config_trim(val);
|
||||||
|
|
||||||
|
if (strcmp(current_section, "general") == 0) {
|
||||||
|
if (strcmp(key, "theme") == 0)
|
||||||
|
snprintf(g_config.theme, sizeof(g_config.theme), "%s", val);
|
||||||
|
else if (strcmp(key, "show_line_numbers") == 0)
|
||||||
|
g_config.show_line_numbers = (strcmp(val, "true") == 0);
|
||||||
|
else if (strcmp(key, "syntax_enabled") == 0)
|
||||||
|
g_config.syntax_enabled = (strcmp(val, "true") == 0);
|
||||||
|
else if (strcmp(key, "block_cursor") == 0)
|
||||||
|
g_config.block_cursor = (strcmp(val, "true") == 0);
|
||||||
|
else if (strcmp(key, "cursor_smear") == 0)
|
||||||
|
g_config.cursor_smear = (strcmp(val, "true") == 0);
|
||||||
|
else if (strcmp(key, "smooth_scroll") == 0)
|
||||||
|
g_config.smooth_scroll = (strcmp(val, "true") == 0);
|
||||||
|
else if (strcmp(key, "ui_scale") == 0)
|
||||||
|
g_config.ui_scale = (F32)atof(val);
|
||||||
|
else if (strcmp(key, "editor_font_size") == 0)
|
||||||
|
g_config.editor_font_size = (F32)atof(val);
|
||||||
|
else if (strcmp(key, "editor_font") == 0)
|
||||||
|
snprintf(g_config.editor_font, sizeof(g_config.editor_font), "%s", val);
|
||||||
|
else if (strcmp(key, "ui_font") == 0)
|
||||||
|
snprintf(g_config.ui_font, sizeof(g_config.ui_font), "%s", val);
|
||||||
|
else if (strcmp(key, "active_project") == 0)
|
||||||
|
snprintf(g_config.active_project, sizeof(g_config.active_project), "%s", val);
|
||||||
|
else if (strcmp(key, "window_width") == 0)
|
||||||
|
g_config.window_width = atoi(val);
|
||||||
|
else if (strcmp(key, "window_height") == 0)
|
||||||
|
g_config.window_height = atoi(val);
|
||||||
|
} else if (strcmp(current_section, "recent_dirs") == 0) {
|
||||||
|
// Keys are "0", "1", "2", etc.
|
||||||
|
S32 idx = atoi(key);
|
||||||
|
if (idx >= 0 && idx < CONFIG_MAX_RECENT_DIRS && val[0] != '\0') {
|
||||||
|
snprintf(g_config.recent_dirs[idx], CONFIG_PATH_MAX, "%s", val);
|
||||||
|
if (idx + 1 > g_config.recent_dir_count)
|
||||||
|
g_config.recent_dir_count = idx + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Save
|
||||||
|
|
||||||
|
static void config_save(void) {
|
||||||
|
char path[CONFIG_PATH_MAX];
|
||||||
|
config_get_path(path, sizeof(path));
|
||||||
|
|
||||||
|
FILE *f = fopen(path, "wb");
|
||||||
|
if (!f) return;
|
||||||
|
|
||||||
|
fprintf(f, "# codeMAX configuration — managed by the program\r\n\r\n");
|
||||||
|
|
||||||
|
fprintf(f, "[general]\r\n");
|
||||||
|
fprintf(f, "theme = %s\r\n", g_config.theme);
|
||||||
|
fprintf(f, "show_line_numbers = %s\r\n", g_config.show_line_numbers ? "true" : "false");
|
||||||
|
fprintf(f, "syntax_enabled = %s\r\n", g_config.syntax_enabled ? "true" : "false");
|
||||||
|
fprintf(f, "block_cursor = %s\r\n", g_config.block_cursor ? "true" : "false");
|
||||||
|
fprintf(f, "cursor_smear = %s\r\n", g_config.cursor_smear ? "true" : "false");
|
||||||
|
fprintf(f, "smooth_scroll = %s\r\n", g_config.smooth_scroll ? "true" : "false");
|
||||||
|
fprintf(f, "ui_scale = %.2f\r\n", g_config.ui_scale);
|
||||||
|
fprintf(f, "editor_font_size = %.1f\r\n", g_config.editor_font_size);
|
||||||
|
fprintf(f, "editor_font = %s\r\n", g_config.editor_font);
|
||||||
|
fprintf(f, "ui_font = %s\r\n", g_config.ui_font);
|
||||||
|
fprintf(f, "window_width = %d\r\n", g_config.window_width);
|
||||||
|
fprintf(f, "window_height = %d\r\n", g_config.window_height);
|
||||||
|
if (g_config.active_project[0] != '\0')
|
||||||
|
fprintf(f, "active_project = %s\r\n", g_config.active_project);
|
||||||
|
|
||||||
|
fprintf(f, "\r\n[recent_dirs]\r\n");
|
||||||
|
for (S32 i = 0; i < g_config.recent_dir_count; i++) {
|
||||||
|
if (g_config.recent_dirs[i][0] != '\0')
|
||||||
|
fprintf(f, "%d = %s\r\n", i, g_config.recent_dirs[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Recent directories
|
||||||
|
|
||||||
|
static void config_push_recent_dir(const char *dir) {
|
||||||
|
if (!dir || dir[0] == '\0') return;
|
||||||
|
|
||||||
|
// Normalize path for comparison
|
||||||
|
char norm[CONFIG_PATH_MAX];
|
||||||
|
snprintf(norm, sizeof(norm), "%s", dir);
|
||||||
|
for (char *p = norm; *p; p++) {
|
||||||
|
if (*p == '/') *p = '\\';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if already in list — if so, move to front
|
||||||
|
S32 existing = -1;
|
||||||
|
for (S32 i = 0; i < g_config.recent_dir_count; i++) {
|
||||||
|
#ifdef __APPLE__
|
||||||
|
if (strcmp(g_config.recent_dirs[i], norm) == 0) { existing = i; break; }
|
||||||
|
#else
|
||||||
|
if (_stricmp(g_config.recent_dirs[i], norm) == 0) { existing = i; break; }
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existing == 0) return; // already at front
|
||||||
|
|
||||||
|
// Save the entry if it exists, or use the new dir
|
||||||
|
char entry[CONFIG_PATH_MAX];
|
||||||
|
snprintf(entry, sizeof(entry), "%s", norm);
|
||||||
|
|
||||||
|
// Shift entries down
|
||||||
|
S32 start = (existing >= 0) ? existing : g_config.recent_dir_count;
|
||||||
|
if (start >= CONFIG_MAX_RECENT_DIRS) start = CONFIG_MAX_RECENT_DIRS - 1;
|
||||||
|
for (S32 i = start; i > 0; i--)
|
||||||
|
memcpy(g_config.recent_dirs[i], g_config.recent_dirs[i - 1], CONFIG_PATH_MAX);
|
||||||
|
|
||||||
|
// Insert at front
|
||||||
|
snprintf(g_config.recent_dirs[0], CONFIG_PATH_MAX, "%s", entry);
|
||||||
|
|
||||||
|
if (existing < 0 && g_config.recent_dir_count < CONFIG_MAX_RECENT_DIRS)
|
||||||
|
g_config.recent_dir_count++;
|
||||||
|
|
||||||
|
config_save();
|
||||||
|
}
|
||||||
51
c/config/config.h
Normal file
51
c/config/config.h
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
#pragma once
|
||||||
|
// config.h — Global program configuration (config.ini next to binary)
|
||||||
|
//
|
||||||
|
// Stores internal program state: active theme, recent project directories,
|
||||||
|
// and other preferences. This file is managed by the program, not the user.
|
||||||
|
// Per-project settings belong in .editorconfig.
|
||||||
|
|
||||||
|
#include "base/base_inc.h"
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Constants
|
||||||
|
|
||||||
|
#define CONFIG_MAX_RECENT_DIRS 10
|
||||||
|
#define CONFIG_PATH_MAX 1024
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Config state
|
||||||
|
|
||||||
|
typedef struct Config {
|
||||||
|
char theme[64]; // active theme name
|
||||||
|
char recent_dirs[CONFIG_MAX_RECENT_DIRS][CONFIG_PATH_MAX]; // most recent first
|
||||||
|
S32 recent_dir_count;
|
||||||
|
B32 show_line_numbers;
|
||||||
|
B32 syntax_enabled;
|
||||||
|
B32 block_cursor;
|
||||||
|
B32 cursor_smear;
|
||||||
|
B32 smooth_scroll;
|
||||||
|
F32 ui_scale;
|
||||||
|
F32 editor_font_size;
|
||||||
|
char editor_font[64]; // "FiraCode", "JetBrains Mono", "Cascadia Mono"
|
||||||
|
char ui_font[64]; // "Inter", "FiraCode", etc.
|
||||||
|
S32 window_width;
|
||||||
|
S32 window_height;
|
||||||
|
char active_project[CONFIG_PATH_MAX];
|
||||||
|
} Config;
|
||||||
|
|
||||||
|
// Global config instance
|
||||||
|
extern Config g_config;
|
||||||
|
|
||||||
|
// Resolve the path to config.ini (next to the running binary).
|
||||||
|
static void config_get_path(char *out, S32 out_size);
|
||||||
|
|
||||||
|
// Load config from disk. Populates g_config. If the file doesn't exist,
|
||||||
|
// writes a default one and returns defaults.
|
||||||
|
static void config_load(void);
|
||||||
|
|
||||||
|
// Save current g_config state to disk.
|
||||||
|
static void config_save(void);
|
||||||
|
|
||||||
|
// Push a directory to the front of the recent list (deduplicates).
|
||||||
|
static void config_push_recent_dir(const char *dir);
|
||||||
604
c/installer/installer.c
Normal file
604
c/installer/installer.c
Normal file
@@ -0,0 +1,604 @@
|
|||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
// installer.c — Self-contained Windows installer for codeMAX
|
||||||
|
//
|
||||||
|
// Uses Win32 PropertySheet wizard for the UI, embeds payload EXEs as
|
||||||
|
// resources, and handles PATH, Start Menu, and Add/Remove Programs.
|
||||||
|
//
|
||||||
|
// When invoked with /uninstall, runs the uninstaller instead.
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
#include <commctrl.h>
|
||||||
|
#include <shlobj.h>
|
||||||
|
#include <objbase.h>
|
||||||
|
#include <shobjidl.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#pragma comment(lib, "comctl32.lib")
|
||||||
|
#pragma comment(lib, "ole32.lib")
|
||||||
|
#pragma comment(lib, "shell32.lib")
|
||||||
|
#pragma comment(lib, "advapi32.lib")
|
||||||
|
#pragma comment(lib, "user32.lib")
|
||||||
|
#pragma comment(lib, "gdi32.lib")
|
||||||
|
#pragma comment(lib, "uuid.lib")
|
||||||
|
#pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
|
||||||
|
|
||||||
|
#include "installer.h"
|
||||||
|
|
||||||
|
// Use codebase types
|
||||||
|
typedef unsigned char U8;
|
||||||
|
typedef unsigned short U16;
|
||||||
|
typedef unsigned int U32;
|
||||||
|
typedef unsigned long long U64;
|
||||||
|
typedef int S32;
|
||||||
|
typedef int B32;
|
||||||
|
|
||||||
|
#define ArrayCount(a) (sizeof(a) / sizeof((a)[0]))
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Globals
|
||||||
|
|
||||||
|
static wchar_t g_install_dir[MAX_PATH];
|
||||||
|
static B32 g_add_to_path = 1;
|
||||||
|
static HINSTANCE g_hinst;
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Helpers
|
||||||
|
|
||||||
|
static B32 extract_resource(S32 resource_id, const wchar_t *dest_path) {
|
||||||
|
HRSRC res = FindResourceW(NULL, MAKEINTRESOURCEW(resource_id), (LPCWSTR)RT_RCDATA);
|
||||||
|
if (!res) return 0;
|
||||||
|
HGLOBAL h = LoadResource(NULL, res);
|
||||||
|
if (!h) return 0;
|
||||||
|
void *data = LockResource(h);
|
||||||
|
DWORD size = SizeofResource(NULL, res);
|
||||||
|
if (!data || size == 0) return 0;
|
||||||
|
|
||||||
|
HANDLE f = CreateFileW(dest_path, GENERIC_WRITE, 0, NULL,
|
||||||
|
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||||
|
if (f == INVALID_HANDLE_VALUE) return 0;
|
||||||
|
DWORD written;
|
||||||
|
WriteFile(f, data, size, &written, NULL);
|
||||||
|
CloseHandle(f);
|
||||||
|
return written == size;
|
||||||
|
}
|
||||||
|
|
||||||
|
static B32 create_directory_recursive(const wchar_t *path) {
|
||||||
|
wchar_t tmp[MAX_PATH];
|
||||||
|
wcscpy_s(tmp, MAX_PATH, path);
|
||||||
|
for (wchar_t *p = tmp + 3; *p; p++) { // skip "C:\"
|
||||||
|
if (*p == L'\\' || *p == L'/') {
|
||||||
|
*p = 0;
|
||||||
|
CreateDirectoryW(tmp, NULL);
|
||||||
|
*p = L'\\';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return CreateDirectoryW(tmp, NULL) || GetLastError() == ERROR_ALREADY_EXISTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static B32 create_shortcut(const wchar_t *lnk_path, const wchar_t *target,
|
||||||
|
const wchar_t *work_dir, const wchar_t *description,
|
||||||
|
const wchar_t *icon_path) {
|
||||||
|
HRESULT hr;
|
||||||
|
IShellLinkW *sl = NULL;
|
||||||
|
hr = CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
|
||||||
|
&IID_IShellLinkW, (void **)&sl);
|
||||||
|
if (FAILED(hr)) return 0;
|
||||||
|
|
||||||
|
sl->lpVtbl->SetPath(sl, target);
|
||||||
|
sl->lpVtbl->SetWorkingDirectory(sl, work_dir);
|
||||||
|
if (description) sl->lpVtbl->SetDescription(sl, description);
|
||||||
|
if (icon_path) sl->lpVtbl->SetIconLocation(sl, icon_path, 0);
|
||||||
|
|
||||||
|
IPersistFile *pf = NULL;
|
||||||
|
hr = sl->lpVtbl->QueryInterface(sl, &IID_IPersistFile, (void **)&pf);
|
||||||
|
B32 ok = 0;
|
||||||
|
if (SUCCEEDED(hr)) {
|
||||||
|
ok = SUCCEEDED(pf->lpVtbl->Save(pf, lnk_path, TRUE));
|
||||||
|
pf->lpVtbl->Release(pf);
|
||||||
|
}
|
||||||
|
sl->lpVtbl->Release(sl);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
static B32 path_add(const wchar_t *dir) {
|
||||||
|
HKEY key;
|
||||||
|
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
|
||||||
|
L"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment",
|
||||||
|
0, KEY_READ | KEY_WRITE, &key) != ERROR_SUCCESS) return 0;
|
||||||
|
|
||||||
|
wchar_t buf[8192] = {0};
|
||||||
|
DWORD buf_size = sizeof(buf) - sizeof(wchar_t);
|
||||||
|
DWORD type = REG_EXPAND_SZ;
|
||||||
|
RegQueryValueExW(key, L"Path", NULL, &type, (BYTE *)buf, &buf_size);
|
||||||
|
|
||||||
|
// Check if already present
|
||||||
|
if (wcsstr(buf, dir)) {
|
||||||
|
RegCloseKey(key);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append
|
||||||
|
S32 len = (S32)wcslen(buf);
|
||||||
|
if (len > 0 && buf[len - 1] != L';')
|
||||||
|
wcscat_s(buf, ArrayCount(buf), L";");
|
||||||
|
wcscat_s(buf, ArrayCount(buf), dir);
|
||||||
|
|
||||||
|
RegSetValueExW(key, L"Path", 0, type,
|
||||||
|
(BYTE *)buf, (DWORD)((wcslen(buf) + 1) * sizeof(wchar_t)));
|
||||||
|
RegCloseKey(key);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void path_remove(const wchar_t *dir) {
|
||||||
|
HKEY key;
|
||||||
|
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
|
||||||
|
L"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment",
|
||||||
|
0, KEY_READ | KEY_WRITE, &key) != ERROR_SUCCESS) return;
|
||||||
|
|
||||||
|
wchar_t buf[8192] = {0};
|
||||||
|
DWORD buf_size = sizeof(buf) - sizeof(wchar_t);
|
||||||
|
DWORD type = REG_EXPAND_SZ;
|
||||||
|
RegQueryValueExW(key, L"Path", NULL, &type, (BYTE *)buf, &buf_size);
|
||||||
|
|
||||||
|
wchar_t result[8192] = {0};
|
||||||
|
wchar_t *ctx = NULL;
|
||||||
|
wchar_t copy[8192];
|
||||||
|
wcscpy_s(copy, ArrayCount(copy), buf);
|
||||||
|
|
||||||
|
wchar_t *tok = wcstok_s(copy, L";", &ctx);
|
||||||
|
B32 first = 1;
|
||||||
|
while (tok) {
|
||||||
|
if (_wcsicmp(tok, dir) != 0) {
|
||||||
|
if (!first) wcscat_s(result, ArrayCount(result), L";");
|
||||||
|
wcscat_s(result, ArrayCount(result), tok);
|
||||||
|
first = 0;
|
||||||
|
}
|
||||||
|
tok = wcstok_s(NULL, L";", &ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
RegSetValueExW(key, L"Path", 0, type,
|
||||||
|
(BYTE *)result, (DWORD)((wcslen(result) + 1) * sizeof(wchar_t)));
|
||||||
|
RegCloseKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void broadcast_env_change(void) {
|
||||||
|
SendMessageTimeoutW(HWND_BROADCAST, WM_SETTINGCHANGE, 0,
|
||||||
|
(LPARAM)L"Environment", SMTO_ABORTIFHUNG, 5000, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// In-memory dialog template builder
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
U8 data[4096];
|
||||||
|
S32 len;
|
||||||
|
} DlgBuf;
|
||||||
|
|
||||||
|
static void dlg_align(DlgBuf *b, S32 align) {
|
||||||
|
while (b->len % align) b->data[b->len++] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dlg_write(DlgBuf *b, const void *src, S32 n) {
|
||||||
|
memcpy(b->data + b->len, src, n);
|
||||||
|
b->len += n;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dlg_write16(DlgBuf *b, U16 v) { dlg_write(b, &v, 2); }
|
||||||
|
static void dlg_write32(DlgBuf *b, U32 v) { dlg_write(b, &v, 4); }
|
||||||
|
|
||||||
|
static void dlg_write_wstr(DlgBuf *b, const wchar_t *s) {
|
||||||
|
S32 n = (S32)((wcslen(s) + 1) * sizeof(wchar_t));
|
||||||
|
dlg_write(b, s, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
static DLGTEMPLATE *build_page_template(DlgBuf *b, S32 w, S32 h) {
|
||||||
|
b->len = 0;
|
||||||
|
// DLGTEMPLATE
|
||||||
|
DLGTEMPLATE *dt = (DLGTEMPLATE *)b->data;
|
||||||
|
dlg_write32(b, WS_CHILD | WS_VISIBLE | DS_SHELLFONT); // style
|
||||||
|
dlg_write32(b, 0); // dwExtendedStyle
|
||||||
|
dlg_write16(b, 0); // cdit (control count, filled later)
|
||||||
|
dlg_write16(b, 0); // x
|
||||||
|
dlg_write16(b, 0); // y
|
||||||
|
dlg_write16(b, (U16)w); // cx
|
||||||
|
dlg_write16(b, (U16)h); // cy
|
||||||
|
dlg_write16(b, 0); // menu (none)
|
||||||
|
dlg_write16(b, 0); // class (default)
|
||||||
|
dlg_write_wstr(b, L""); // title
|
||||||
|
// DS_SHELLFONT font
|
||||||
|
dlg_write16(b, 9); // point size
|
||||||
|
dlg_write_wstr(b, L"Segoe UI");
|
||||||
|
return dt;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void add_control(DlgBuf *b, DLGTEMPLATE *dt, U32 style, S32 x, S32 y,
|
||||||
|
S32 w, S32 h, S32 id, const wchar_t *cls, const wchar_t *text) {
|
||||||
|
dlg_align(b, 4);
|
||||||
|
// DLGITEMTEMPLATE
|
||||||
|
dlg_write32(b, style | WS_CHILD | WS_VISIBLE); // style
|
||||||
|
dlg_write32(b, 0); // dwExtendedStyle
|
||||||
|
dlg_write16(b, (U16)x);
|
||||||
|
dlg_write16(b, (U16)y);
|
||||||
|
dlg_write16(b, (U16)w);
|
||||||
|
dlg_write16(b, (U16)h);
|
||||||
|
dlg_write16(b, (U16)id);
|
||||||
|
dlg_write_wstr(b, cls);
|
||||||
|
dlg_write_wstr(b, text);
|
||||||
|
dlg_write16(b, 0); // creation data
|
||||||
|
dt->cdit++;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Page IDs for controls
|
||||||
|
|
||||||
|
#define IDC_DIR_EDIT 1001
|
||||||
|
#define IDC_DIR_BROWSE 1002
|
||||||
|
#define IDC_PATH_CHK 1003
|
||||||
|
#define IDC_PROGRESS 1004
|
||||||
|
#define IDC_STATUS 1005
|
||||||
|
#define IDC_LAUNCH_CHK 1006
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Wizard pages
|
||||||
|
|
||||||
|
static INT_PTR CALLBACK welcome_proc(HWND dlg, UINT msg, WPARAM wp, LPARAM lp) {
|
||||||
|
switch (msg) {
|
||||||
|
case WM_INITDIALOG:
|
||||||
|
return TRUE;
|
||||||
|
case WM_NOTIFY: {
|
||||||
|
NMHDR *nm = (NMHDR *)lp;
|
||||||
|
if (nm->code == PSN_SETACTIVE) {
|
||||||
|
PropSheet_SetWizButtons(GetParent(dlg), PSWIZB_NEXT);
|
||||||
|
SetWindowLongPtrW(dlg, DWLP_MSGRESULT, 0);
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static INT_PTR CALLBACK dir_proc(HWND dlg, UINT msg, WPARAM wp, LPARAM lp) {
|
||||||
|
switch (msg) {
|
||||||
|
case WM_INITDIALOG:
|
||||||
|
SetDlgItemTextW(dlg, IDC_DIR_EDIT, g_install_dir);
|
||||||
|
CheckDlgButton(dlg, IDC_PATH_CHK, g_add_to_path ? BST_CHECKED : BST_UNCHECKED);
|
||||||
|
return TRUE;
|
||||||
|
case WM_COMMAND:
|
||||||
|
if (LOWORD(wp) == IDC_DIR_BROWSE) {
|
||||||
|
BROWSEINFOW bi = {0};
|
||||||
|
bi.hwndOwner = dlg;
|
||||||
|
bi.lpszTitle = L"Select installation directory:";
|
||||||
|
bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE;
|
||||||
|
LPITEMIDLIST pidl = SHBrowseForFolderW(&bi);
|
||||||
|
if (pidl) {
|
||||||
|
wchar_t path[MAX_PATH];
|
||||||
|
SHGetPathFromIDListW(pidl, path);
|
||||||
|
wcscat_s(path, MAX_PATH, L"\\codeMAX");
|
||||||
|
SetDlgItemTextW(dlg, IDC_DIR_EDIT, path);
|
||||||
|
CoTaskMemFree(pidl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
case WM_NOTIFY: {
|
||||||
|
NMHDR *nm = (NMHDR *)lp;
|
||||||
|
if (nm->code == PSN_SETACTIVE)
|
||||||
|
PropSheet_SetWizButtons(GetParent(dlg), PSWIZB_BACK | PSWIZB_NEXT);
|
||||||
|
if (nm->code == PSN_WIZNEXT) {
|
||||||
|
GetDlgItemTextW(dlg, IDC_DIR_EDIT, g_install_dir, MAX_PATH);
|
||||||
|
g_add_to_path = (IsDlgButtonChecked(dlg, IDC_PATH_CHK) == BST_CHECKED);
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static B32 do_install(HWND dlg) {
|
||||||
|
HWND prog = GetDlgItem(dlg, IDC_PROGRESS);
|
||||||
|
HWND status = GetDlgItem(dlg, IDC_STATUS);
|
||||||
|
SendMessageW(prog, PBM_SETRANGE, 0, MAKELPARAM(0, 7));
|
||||||
|
SendMessageW(prog, PBM_SETSTEP, 1, 0);
|
||||||
|
|
||||||
|
// 1. Create install directory
|
||||||
|
SetWindowTextW(status, L"Creating install directory...");
|
||||||
|
create_directory_recursive(g_install_dir);
|
||||||
|
SendMessageW(prog, PBM_STEPIT, 0, 0);
|
||||||
|
|
||||||
|
// 2. Extract terminal exe
|
||||||
|
SetWindowTextW(status, L"Installing codemax.exe...");
|
||||||
|
wchar_t path[MAX_PATH];
|
||||||
|
_snwprintf(path, MAX_PATH, L"%s\\codemax.exe", g_install_dir);
|
||||||
|
if (!extract_resource(IDR_EXE_GUI, path)) {
|
||||||
|
MessageBoxW(dlg, L"Failed to extract codemax.exe", L"Error", MB_ICONERROR);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
SendMessageW(prog, PBM_STEPIT, 0, 0);
|
||||||
|
|
||||||
|
// 4. Copy self as uninstaller
|
||||||
|
SetWindowTextW(status, L"Creating uninstaller...");
|
||||||
|
wchar_t self_path[MAX_PATH];
|
||||||
|
GetModuleFileNameW(NULL, self_path, MAX_PATH);
|
||||||
|
_snwprintf(path, MAX_PATH, L"%s\\uninstall.exe", g_install_dir);
|
||||||
|
CopyFileW(self_path, path, FALSE);
|
||||||
|
SendMessageW(prog, PBM_STEPIT, 0, 0);
|
||||||
|
|
||||||
|
// 5. Add to PATH (optional)
|
||||||
|
if (g_add_to_path) {
|
||||||
|
SetWindowTextW(status, L"Updating PATH...");
|
||||||
|
path_add(g_install_dir);
|
||||||
|
}
|
||||||
|
SendMessageW(prog, PBM_STEPIT, 0, 0);
|
||||||
|
|
||||||
|
// 6. Create Start Menu shortcuts
|
||||||
|
SetWindowTextW(status, L"Creating shortcuts...");
|
||||||
|
CoInitialize(NULL);
|
||||||
|
{
|
||||||
|
wchar_t programs[MAX_PATH];
|
||||||
|
SHGetFolderPathW(NULL, CSIDL_COMMON_PROGRAMS, NULL, 0, programs);
|
||||||
|
wchar_t menu_dir[MAX_PATH];
|
||||||
|
_snwprintf(menu_dir, MAX_PATH, L"%s\\codeMAX", programs);
|
||||||
|
CreateDirectoryW(menu_dir, NULL);
|
||||||
|
|
||||||
|
wchar_t exe_path[MAX_PATH];
|
||||||
|
_snwprintf(exe_path, MAX_PATH, L"%s\\codemax.exe", g_install_dir);
|
||||||
|
|
||||||
|
wchar_t lnk[MAX_PATH];
|
||||||
|
_snwprintf(lnk, MAX_PATH, L"%s\\codeMAX.lnk", menu_dir);
|
||||||
|
create_shortcut(lnk, exe_path, g_install_dir,
|
||||||
|
L"codeMAX Text Editor", exe_path);
|
||||||
|
}
|
||||||
|
CoUninitialize();
|
||||||
|
SendMessageW(prog, PBM_STEPIT, 0, 0);
|
||||||
|
|
||||||
|
// 7. Register in Add/Remove Programs
|
||||||
|
SetWindowTextW(status, L"Registering application...");
|
||||||
|
{
|
||||||
|
HKEY key;
|
||||||
|
RegCreateKeyExA(HKEY_LOCAL_MACHINE, CODEMAX_UNINSTALL_KEY,
|
||||||
|
0, NULL, 0, KEY_WRITE, NULL, &key, NULL);
|
||||||
|
|
||||||
|
char dir_a[MAX_PATH], uninst[MAX_PATH * 2];
|
||||||
|
WideCharToMultiByte(CP_UTF8, 0, g_install_dir, -1, dir_a, MAX_PATH, NULL, NULL);
|
||||||
|
snprintf(uninst, sizeof(uninst), "\"%s\\uninstall.exe\" /uninstall", dir_a);
|
||||||
|
|
||||||
|
RegSetValueExA(key, "DisplayName", 0, REG_SZ,
|
||||||
|
(BYTE *)CODEMAX_DISPLAY_NAME, (DWORD)strlen(CODEMAX_DISPLAY_NAME) + 1);
|
||||||
|
RegSetValueExA(key, "DisplayVersion", 0, REG_SZ,
|
||||||
|
(BYTE *)CODEMAX_VERSION, (DWORD)strlen(CODEMAX_VERSION) + 1);
|
||||||
|
RegSetValueExA(key, "Publisher", 0, REG_SZ,
|
||||||
|
(BYTE *)CODEMAX_PUBLISHER, (DWORD)strlen(CODEMAX_PUBLISHER) + 1);
|
||||||
|
RegSetValueExA(key, "InstallLocation", 0, REG_SZ,
|
||||||
|
(BYTE *)dir_a, (DWORD)strlen(dir_a) + 1);
|
||||||
|
RegSetValueExA(key, "UninstallString", 0, REG_SZ,
|
||||||
|
(BYTE *)uninst, (DWORD)strlen(uninst) + 1);
|
||||||
|
|
||||||
|
char icon[MAX_PATH + 4];
|
||||||
|
snprintf(icon, sizeof(icon), "%s\\codemax.exe,0", dir_a);
|
||||||
|
RegSetValueExA(key, "DisplayIcon", 0, REG_SZ,
|
||||||
|
(BYTE *)icon, (DWORD)strlen(icon) + 1);
|
||||||
|
|
||||||
|
DWORD one = 1;
|
||||||
|
RegSetValueExA(key, "NoModify", 0, REG_DWORD, (BYTE *)&one, sizeof(one));
|
||||||
|
RegSetValueExA(key, "NoRepair", 0, REG_DWORD, (BYTE *)&one, sizeof(one));
|
||||||
|
|
||||||
|
RegCloseKey(key);
|
||||||
|
}
|
||||||
|
SendMessageW(prog, PBM_STEPIT, 0, 0);
|
||||||
|
|
||||||
|
broadcast_env_change();
|
||||||
|
SetWindowTextW(status, L"Installation complete.");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static INT_PTR CALLBACK progress_proc(HWND dlg, UINT msg, WPARAM wp, LPARAM lp) {
|
||||||
|
switch (msg) {
|
||||||
|
case WM_INITDIALOG:
|
||||||
|
return TRUE;
|
||||||
|
case WM_NOTIFY: {
|
||||||
|
NMHDR *nm = (NMHDR *)lp;
|
||||||
|
if (nm->code == PSN_SETACTIVE) {
|
||||||
|
// Disable all buttons during install
|
||||||
|
PropSheet_SetWizButtons(GetParent(dlg), 0);
|
||||||
|
// Run install
|
||||||
|
if (do_install(dlg)) {
|
||||||
|
PropSheet_SetWizButtons(GetParent(dlg), PSWIZB_NEXT);
|
||||||
|
} else {
|
||||||
|
PropSheet_SetWizButtons(GetParent(dlg), PSWIZB_BACK);
|
||||||
|
}
|
||||||
|
SetWindowLongPtrW(dlg, DWLP_MSGRESULT, 0);
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static INT_PTR CALLBACK finish_proc(HWND dlg, UINT msg, WPARAM wp, LPARAM lp) {
|
||||||
|
switch (msg) {
|
||||||
|
case WM_INITDIALOG:
|
||||||
|
CheckDlgButton(dlg, IDC_LAUNCH_CHK, BST_CHECKED);
|
||||||
|
return TRUE;
|
||||||
|
case WM_NOTIFY: {
|
||||||
|
NMHDR *nm = (NMHDR *)lp;
|
||||||
|
if (nm->code == PSN_SETACTIVE) {
|
||||||
|
PropSheet_SetWizButtons(GetParent(dlg), PSWIZB_FINISH);
|
||||||
|
}
|
||||||
|
if (nm->code == PSN_WIZFINISH) {
|
||||||
|
if (IsDlgButtonChecked(dlg, IDC_LAUNCH_CHK) == BST_CHECKED) {
|
||||||
|
wchar_t exe[MAX_PATH];
|
||||||
|
_snwprintf(exe, MAX_PATH, L"%s\\codemax.exe", g_install_dir);
|
||||||
|
ShellExecuteW(NULL, L"open", exe, NULL, g_install_dir, SW_SHOWNORMAL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Wizard launcher
|
||||||
|
|
||||||
|
static void run_installer(void) {
|
||||||
|
// Default install dir
|
||||||
|
wcscpy_s(g_install_dir, MAX_PATH, L"C:\\Program Files\\codeMAX");
|
||||||
|
|
||||||
|
InitCommonControls();
|
||||||
|
|
||||||
|
// Build page templates in memory
|
||||||
|
DlgBuf bufs[4] = {0};
|
||||||
|
S32 page_w = 317, page_h = 143;
|
||||||
|
|
||||||
|
// Page 0: Welcome
|
||||||
|
DLGTEMPLATE *dt0 = build_page_template(&bufs[0], page_w, page_h);
|
||||||
|
add_control(&bufs[0], dt0, SS_LEFT, 10, 10, 297, 40, -1,
|
||||||
|
L"Static", L"Welcome to the codeMAX installer.\n\n"
|
||||||
|
L"This will install codeMAX on your computer.");
|
||||||
|
add_control(&bufs[0], dt0, SS_LEFT, 10, 60, 297, 20, -1,
|
||||||
|
L"Static", L"Click Next to continue.");
|
||||||
|
|
||||||
|
// Page 1: Directory + options
|
||||||
|
DLGTEMPLATE *dt1 = build_page_template(&bufs[1], page_w, page_h);
|
||||||
|
add_control(&bufs[1], dt1, SS_LEFT, 10, 10, 297, 10, -1,
|
||||||
|
L"Static", L"Choose the installation directory:");
|
||||||
|
add_control(&bufs[1], dt1, ES_AUTOHSCROLL | WS_BORDER | WS_TABSTOP,
|
||||||
|
10, 28, 230, 14, IDC_DIR_EDIT, L"Edit", L"");
|
||||||
|
add_control(&bufs[1], dt1, BS_PUSHBUTTON | WS_TABSTOP,
|
||||||
|
248, 27, 60, 14, IDC_DIR_BROWSE, L"Button", L"Browse...");
|
||||||
|
add_control(&bufs[1], dt1, BS_AUTOCHECKBOX | WS_TABSTOP,
|
||||||
|
10, 52, 250, 14, IDC_PATH_CHK, L"Button", L"Add to system PATH");
|
||||||
|
|
||||||
|
// Page 2: Progress
|
||||||
|
DLGTEMPLATE *dt2 = build_page_template(&bufs[2], page_w, page_h);
|
||||||
|
add_control(&bufs[2], dt2, SS_LEFT, 10, 10, 297, 10, IDC_STATUS,
|
||||||
|
L"Static", L"Installing...");
|
||||||
|
add_control(&bufs[2], dt2, 0, 10, 30, 297, 14, IDC_PROGRESS,
|
||||||
|
PROGRESS_CLASSW, L"");
|
||||||
|
|
||||||
|
// Page 3: Finish
|
||||||
|
DLGTEMPLATE *dt3 = build_page_template(&bufs[3], page_w, page_h);
|
||||||
|
add_control(&bufs[3], dt3, SS_LEFT, 10, 10, 297, 20, -1,
|
||||||
|
L"Static", L"codeMAX has been installed successfully.");
|
||||||
|
add_control(&bufs[3], dt3, BS_AUTOCHECKBOX | WS_TABSTOP,
|
||||||
|
10, 45, 200, 14, IDC_LAUNCH_CHK, L"Button", L"Launch codeMAX GUI");
|
||||||
|
|
||||||
|
PROPSHEETPAGEW pages[4] = {0};
|
||||||
|
for (S32 i = 0; i < 4; i++) {
|
||||||
|
pages[i].dwSize = sizeof(PROPSHEETPAGEW);
|
||||||
|
pages[i].dwFlags = PSP_DLGINDIRECT;
|
||||||
|
pages[i].hInstance = g_hinst;
|
||||||
|
}
|
||||||
|
pages[0].pResource = dt0;
|
||||||
|
pages[0].pfnDlgProc = welcome_proc;
|
||||||
|
pages[1].pResource = dt1;
|
||||||
|
pages[1].pfnDlgProc = dir_proc;
|
||||||
|
pages[2].pResource = dt2;
|
||||||
|
pages[2].pfnDlgProc = progress_proc;
|
||||||
|
pages[3].pResource = dt3;
|
||||||
|
pages[3].pfnDlgProc = finish_proc;
|
||||||
|
|
||||||
|
PROPSHEETHEADERW psh = {0};
|
||||||
|
psh.dwSize = sizeof(PROPSHEETHEADERW);
|
||||||
|
psh.dwFlags = PSH_WIZARD | PSH_PROPSHEETPAGE | PSH_USEICONID;
|
||||||
|
psh.hwndParent = NULL;
|
||||||
|
psh.hInstance = g_hinst;
|
||||||
|
psh.pszIcon = MAKEINTRESOURCEW(IDI_CODEMAX);
|
||||||
|
psh.pszCaption = L"codeMAX Setup";
|
||||||
|
psh.nPages = 4;
|
||||||
|
psh.ppsp = pages;
|
||||||
|
|
||||||
|
PropertySheetW(&psh);
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Uninstaller
|
||||||
|
|
||||||
|
static void run_uninstaller(void) {
|
||||||
|
// Get install dir from our own exe path
|
||||||
|
wchar_t self[MAX_PATH];
|
||||||
|
GetModuleFileNameW(NULL, self, MAX_PATH);
|
||||||
|
wcscpy_s(g_install_dir, MAX_PATH, self);
|
||||||
|
wchar_t *last_sep = wcsrchr(g_install_dir, L'\\');
|
||||||
|
if (last_sep) *last_sep = 0;
|
||||||
|
|
||||||
|
S32 result = MessageBoxW(NULL,
|
||||||
|
L"Are you sure you want to uninstall codeMAX?",
|
||||||
|
L"codeMAX Uninstall", MB_YESNO | MB_ICONQUESTION);
|
||||||
|
if (result != IDYES) return;
|
||||||
|
|
||||||
|
// Delete installed files
|
||||||
|
wchar_t path[MAX_PATH];
|
||||||
|
_snwprintf(path, MAX_PATH, L"%s\\codemax.exe", g_install_dir);
|
||||||
|
DeleteFileW(path);
|
||||||
|
_snwprintf(path, MAX_PATH, L"%s\\codemax.exe", g_install_dir);
|
||||||
|
DeleteFileW(path);
|
||||||
|
|
||||||
|
// Remove from PATH
|
||||||
|
path_remove(g_install_dir);
|
||||||
|
broadcast_env_change();
|
||||||
|
|
||||||
|
// Remove Start Menu shortcuts
|
||||||
|
{
|
||||||
|
wchar_t programs[MAX_PATH];
|
||||||
|
SHGetFolderPathW(NULL, CSIDL_COMMON_PROGRAMS, NULL, 0, programs);
|
||||||
|
wchar_t menu_dir[MAX_PATH];
|
||||||
|
_snwprintf(menu_dir, MAX_PATH, L"%s\\codeMAX", programs);
|
||||||
|
|
||||||
|
_snwprintf(path, MAX_PATH, L"%s\\codeMAX.lnk", menu_dir);
|
||||||
|
DeleteFileW(path);
|
||||||
|
_snwprintf(path, MAX_PATH, L"%s\\codeMAX GUI.lnk", menu_dir);
|
||||||
|
DeleteFileW(path);
|
||||||
|
RemoveDirectoryW(menu_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove registry entry
|
||||||
|
RegDeleteKeyA(HKEY_LOCAL_MACHINE, CODEMAX_UNINSTALL_KEY);
|
||||||
|
|
||||||
|
// Schedule self-deletion and remove install dir
|
||||||
|
// Use cmd.exe to wait for us to exit, then delete
|
||||||
|
wchar_t cmd[MAX_PATH * 3];
|
||||||
|
_snwprintf(cmd, ArrayCount(cmd),
|
||||||
|
L"cmd.exe /c timeout /t 2 /nobreak >nul & del \"%s\\uninstall.exe\" & rmdir \"%s\"",
|
||||||
|
g_install_dir, g_install_dir);
|
||||||
|
|
||||||
|
STARTUPINFOW si = {0};
|
||||||
|
si.cb = sizeof(si);
|
||||||
|
si.dwFlags = STARTF_USESHOWWINDOW;
|
||||||
|
si.wShowWindow = SW_HIDE;
|
||||||
|
PROCESS_INFORMATION pi = {0};
|
||||||
|
CreateProcessW(NULL, cmd, NULL, NULL, FALSE,
|
||||||
|
CREATE_NO_WINDOW, NULL, NULL, &si, &pi);
|
||||||
|
CloseHandle(pi.hProcess);
|
||||||
|
CloseHandle(pi.hThread);
|
||||||
|
|
||||||
|
MessageBoxW(NULL, L"codeMAX has been uninstalled.", L"codeMAX Uninstall",
|
||||||
|
MB_OK | MB_ICONINFORMATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Entry point
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
(void)argc;
|
||||||
|
g_hinst = GetModuleHandleW(NULL);
|
||||||
|
|
||||||
|
// Check for uninstall flag
|
||||||
|
for (int i = 1; i < argc; i++) {
|
||||||
|
if (strcmp(argv[i], "/uninstall") == 0) {
|
||||||
|
run_uninstaller();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also check via GetCommandLineW for when launched without CRT argc/argv
|
||||||
|
wchar_t *cmdline = GetCommandLineW();
|
||||||
|
if (wcsstr(cmdline, L"/uninstall")) {
|
||||||
|
run_uninstaller();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
run_installer();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
16
c/installer/installer.h
Normal file
16
c/installer/installer.h
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#pragma once
|
||||||
|
// installer.h — Shared constants for installer and resource script
|
||||||
|
|
||||||
|
// Resource IDs
|
||||||
|
#define IDI_CODEMAX 101
|
||||||
|
#define IDR_EXE_TERMINAL 201
|
||||||
|
#define IDR_EXE_GUI 202
|
||||||
|
|
||||||
|
// Product info
|
||||||
|
#define CODEMAX_APP_NAME "codemax"
|
||||||
|
#define CODEMAX_DISPLAY_NAME "codeMAX"
|
||||||
|
#define CODEMAX_VERSION "1.0.0"
|
||||||
|
#define CODEMAX_PUBLISHER "codeMAX"
|
||||||
|
|
||||||
|
// Registry
|
||||||
|
#define CODEMAX_UNINSTALL_KEY "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\codeMAX"
|
||||||
17
c/installer/installer.manifest
Normal file
17
c/installer/installer.manifest
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||||
|
<assemblyIdentity type="win32" name="codemax.installer" version="1.0.0.0" processorArchitecture="amd64"/>
|
||||||
|
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<security>
|
||||||
|
<requestedPrivileges>
|
||||||
|
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
|
||||||
|
</requestedPrivileges>
|
||||||
|
</security>
|
||||||
|
</trustInfo>
|
||||||
|
<dependency>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls"
|
||||||
|
version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
|
||||||
|
</dependentAssembly>
|
||||||
|
</dependency>
|
||||||
|
</assembly>
|
||||||
14
c/installer/installer.rc
Normal file
14
c/installer/installer.rc
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// installer.rc — Resource script for codemax installer
|
||||||
|
// Embeds icon, UAC manifest, and payload executables.
|
||||||
|
|
||||||
|
#include "installer.h"
|
||||||
|
#include <winresrc.h>
|
||||||
|
|
||||||
|
// Application icon
|
||||||
|
IDI_CODEMAX ICON "assets\\icons\\codemax.ico"
|
||||||
|
|
||||||
|
// UAC elevation manifest
|
||||||
|
1 24 "src\\installer\\installer.manifest"
|
||||||
|
|
||||||
|
// Payload executable (embedded as RCDATA)
|
||||||
|
IDR_EXE_GUI RCDATA "build_release\\codemax.exe"
|
||||||
98
c/lexer/lexer.c
Normal file
98
c/lexer/lexer.c
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
// lexer.c -- Tokenizer infrastructure, color schemes, common helpers
|
||||||
|
|
||||||
|
#include "lexer/lexer.h"
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
const char *g_lang_names[LANG_COUNT] = {
|
||||||
|
"Plain Text", // LANG_PLAIN_TEXT
|
||||||
|
"C", // LANG_C
|
||||||
|
"Go", // LANG_GO
|
||||||
|
"JavaScript", // LANG_JS
|
||||||
|
"Lua", // LANG_LUA
|
||||||
|
"SQL", // LANG_SQL
|
||||||
|
};
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Tokenizer helpers
|
||||||
|
|
||||||
|
static void tokenizer_init(Tokenizer *tok, const char *data, S32 length) {
|
||||||
|
tok->buf = data;
|
||||||
|
tok->t = data;
|
||||||
|
tok->max_t = data + length;
|
||||||
|
tok->start_t = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tokenizer_eat_whitespace(Tokenizer *tok) {
|
||||||
|
while (tok->t < tok->max_t && (*tok->t == ' ' || *tok->t == '\t' ||
|
||||||
|
*tok->t == '\n' || *tok->t == '\r'))
|
||||||
|
tok->t++;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tokenizer_eat_until_newline(Tokenizer *tok) {
|
||||||
|
while (tok->t < tok->max_t && *tok->t != '\n')
|
||||||
|
tok->t++;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Paint helper -- fill out_tokens[start..start+len) with a token type
|
||||||
|
|
||||||
|
static void paint_token(U8 *out_tokens, S32 start, S32 len, Token_Type type) {
|
||||||
|
memset(out_tokens + start, (U8)type, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Language detection
|
||||||
|
|
||||||
|
static Lang lexer_detect_lang(const char *filename) {
|
||||||
|
if (!filename) return LANG_PLAIN_TEXT;
|
||||||
|
const char *dot = strrchr(filename, '.');
|
||||||
|
if (!dot) return LANG_PLAIN_TEXT;
|
||||||
|
dot++; // skip the '.'
|
||||||
|
|
||||||
|
if (strcmp(dot, "c") == 0 || strcmp(dot, "h") == 0 ||
|
||||||
|
strcmp(dot, "C") == 0 || strcmp(dot, "H") == 0)
|
||||||
|
return LANG_C;
|
||||||
|
|
||||||
|
if (strcmp(dot, "go") == 0)
|
||||||
|
return LANG_GO;
|
||||||
|
|
||||||
|
if (strcmp(dot, "js") == 0 || strcmp(dot, "mjs") == 0 ||
|
||||||
|
strcmp(dot, "cjs") == 0 || strcmp(dot, "jsx") == 0)
|
||||||
|
return LANG_JS;
|
||||||
|
|
||||||
|
if (strcmp(dot, "lua") == 0)
|
||||||
|
return LANG_LUA;
|
||||||
|
|
||||||
|
if (strcmp(dot, "sql") == 0 || strcmp(dot, "ddl") == 0 ||
|
||||||
|
strcmp(dot, "dml") == 0 || strcmp(dot, "pgsql") == 0 ||
|
||||||
|
strcmp(dot, "plsql") == 0 || strcmp(dot, "psql") == 0)
|
||||||
|
return LANG_SQL;
|
||||||
|
|
||||||
|
return LANG_PLAIN_TEXT;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Forward-declare language tokenizers
|
||||||
|
|
||||||
|
static void tokenize_c(const char *data, S32 length, U8 *out_tokens);
|
||||||
|
static void tokenize_go(const char *data, S32 length, U8 *out_tokens);
|
||||||
|
static void tokenize_js(const char *data, S32 length, U8 *out_tokens);
|
||||||
|
static void tokenize_lua(const char *data, S32 length, U8 *out_tokens);
|
||||||
|
static void tokenize_sql(const char *data, S32 length, U8 *out_tokens);
|
||||||
|
|
||||||
|
static void tokenize_plain(const char *data, S32 length, U8 *out_tokens) {
|
||||||
|
(void)data;
|
||||||
|
memset(out_tokens, TOK_DEFAULT, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
static LexerTokenizeFn lexer_get_tokenize_fn(Lang lang) {
|
||||||
|
switch (lang) {
|
||||||
|
case LANG_C: return tokenize_c;
|
||||||
|
case LANG_GO: return tokenize_go;
|
||||||
|
case LANG_JS: return tokenize_js;
|
||||||
|
case LANG_LUA: return tokenize_lua;
|
||||||
|
case LANG_SQL: return tokenize_sql;
|
||||||
|
default: return tokenize_plain;
|
||||||
|
}
|
||||||
|
}
|
||||||
98
c/lexer/lexer.h
Normal file
98
c/lexer/lexer.h
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
#pragma once
|
||||||
|
// lexer.h -- Token types, tokenizer interface, and color scheme
|
||||||
|
//
|
||||||
|
// Follows the Focus editor pattern: a universal Token_Type enum shared by all
|
||||||
|
// languages, a per-byte token-type array on each buffer, and language-specific
|
||||||
|
// tokenizer functions that fill that array.
|
||||||
|
|
||||||
|
#include "base/base_core.h"
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Token types (universal across all languages)
|
||||||
|
//
|
||||||
|
// The enum value doubles as an index into the color map, so CODE_DEFAULT
|
||||||
|
// must be 0. Keep this list ordered -- rendering indexes directly.
|
||||||
|
|
||||||
|
typedef enum Token_Type {
|
||||||
|
TOK_DEFAULT = 0,
|
||||||
|
|
||||||
|
TOK_COMMENT,
|
||||||
|
TOK_MULTILINE_COMMENT,
|
||||||
|
|
||||||
|
TOK_STRING_LITERAL,
|
||||||
|
TOK_CHAR_LITERAL,
|
||||||
|
|
||||||
|
TOK_NUMBER,
|
||||||
|
TOK_IDENTIFIER,
|
||||||
|
TOK_FUNCTION,
|
||||||
|
|
||||||
|
TOK_KEYWORD,
|
||||||
|
TOK_TYPE,
|
||||||
|
TOK_VALUE, // true, false, NULL, nullptr
|
||||||
|
TOK_MODIFIER, // const, static, volatile, extern ...
|
||||||
|
|
||||||
|
TOK_DIRECTIVE, // #include, #define, ...
|
||||||
|
TOK_PUNCTUATION,
|
||||||
|
TOK_OPERATION,
|
||||||
|
|
||||||
|
TOK_INVALID,
|
||||||
|
|
||||||
|
TOK_COUNT
|
||||||
|
} Token_Type;
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Language enum
|
||||||
|
|
||||||
|
typedef enum Lang {
|
||||||
|
LANG_PLAIN_TEXT = 0,
|
||||||
|
LANG_C,
|
||||||
|
LANG_GO,
|
||||||
|
LANG_JS,
|
||||||
|
LANG_LUA,
|
||||||
|
LANG_SQL,
|
||||||
|
LANG_COUNT
|
||||||
|
} Lang;
|
||||||
|
|
||||||
|
// Human-readable language names (indexed by Lang)
|
||||||
|
extern const char *g_lang_names[LANG_COUNT];
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Tokenizer state (shared by all language tokenizers)
|
||||||
|
|
||||||
|
typedef struct Tokenizer {
|
||||||
|
const char *buf; // start of buffer data
|
||||||
|
const char *t; // current scan cursor
|
||||||
|
const char *max_t; // one past end
|
||||||
|
const char *start_t; // cursor at start of current token
|
||||||
|
} Tokenizer;
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Token (returned by get_next_token functions)
|
||||||
|
|
||||||
|
typedef struct Token {
|
||||||
|
S32 start; // byte offset into buffer
|
||||||
|
S32 len; // byte length of token
|
||||||
|
Token_Type type;
|
||||||
|
} Token;
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Language tokenizer function signature
|
||||||
|
//
|
||||||
|
// A tokenizer function takes a buffer's data + length, and writes token types
|
||||||
|
// into the `out_tokens` array (one byte per source byte, same length as data).
|
||||||
|
// This is the Focus "paint the token array" approach.
|
||||||
|
|
||||||
|
typedef void (*LexerTokenizeFn)(const char *data, S32 length, U8 *out_tokens);
|
||||||
|
|
||||||
|
// Get the tokenizer function for a language.
|
||||||
|
static LexerTokenizeFn lexer_get_tokenize_fn(Lang lang);
|
||||||
|
|
||||||
|
// Detect language from a file name / extension.
|
||||||
|
static Lang lexer_detect_lang(const char *filename);
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Common tokenizer helpers (usable by all language tokenizers)
|
||||||
|
|
||||||
|
static void tokenizer_init(Tokenizer *tok, const char *data, S32 length);
|
||||||
|
static void tokenizer_eat_whitespace(Tokenizer *tok);
|
||||||
|
static void tokenizer_eat_until_newline(Tokenizer *tok);
|
||||||
423
c/lexer/lexer_c.c
Normal file
423
c/lexer/lexer_c.c
Normal file
@@ -0,0 +1,423 @@
|
|||||||
|
// lexer_c.c -- C language tokenizer
|
||||||
|
//
|
||||||
|
// Follows the Focus editor pattern: a get_next_token() loop that advances
|
||||||
|
// a cursor through the source, producing Token structs. After each token,
|
||||||
|
// we paint the per-byte token-type array with the token's type.
|
||||||
|
// A second pass retroactively marks identifiers followed by '(' as functions.
|
||||||
|
|
||||||
|
#include "lexer/lexer.h"
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// C keyword tables
|
||||||
|
|
||||||
|
typedef struct KeywordEntry {
|
||||||
|
const char *word;
|
||||||
|
Token_Type type;
|
||||||
|
} KeywordEntry;
|
||||||
|
|
||||||
|
static const KeywordEntry c_keywords[] = {
|
||||||
|
// Control-flow & language keywords
|
||||||
|
{"auto", TOK_KEYWORD},
|
||||||
|
{"break", TOK_KEYWORD},
|
||||||
|
{"case", TOK_KEYWORD},
|
||||||
|
{"continue", TOK_KEYWORD},
|
||||||
|
{"default", TOK_KEYWORD},
|
||||||
|
{"do", TOK_KEYWORD},
|
||||||
|
{"else", TOK_KEYWORD},
|
||||||
|
{"enum", TOK_KEYWORD},
|
||||||
|
{"for", TOK_KEYWORD},
|
||||||
|
{"goto", TOK_KEYWORD},
|
||||||
|
{"if", TOK_KEYWORD},
|
||||||
|
{"inline", TOK_KEYWORD},
|
||||||
|
{"restrict", TOK_KEYWORD},
|
||||||
|
{"return", TOK_KEYWORD},
|
||||||
|
{"sizeof", TOK_KEYWORD},
|
||||||
|
{"struct", TOK_KEYWORD},
|
||||||
|
{"switch", TOK_KEYWORD},
|
||||||
|
{"typedef", TOK_KEYWORD},
|
||||||
|
{"union", TOK_KEYWORD},
|
||||||
|
{"while", TOK_KEYWORD},
|
||||||
|
{"_Alignas", TOK_KEYWORD},
|
||||||
|
{"alignas", TOK_KEYWORD},
|
||||||
|
{"_Alignof", TOK_KEYWORD},
|
||||||
|
{"alignof", TOK_KEYWORD},
|
||||||
|
{"_Atomic", TOK_KEYWORD},
|
||||||
|
{"_Generic", TOK_KEYWORD},
|
||||||
|
{"_Noreturn", TOK_KEYWORD},
|
||||||
|
{"static_assert", TOK_KEYWORD},
|
||||||
|
{"_Static_assert", TOK_KEYWORD},
|
||||||
|
|
||||||
|
// Types
|
||||||
|
{"char", TOK_TYPE},
|
||||||
|
{"double", TOK_TYPE},
|
||||||
|
{"float", TOK_TYPE},
|
||||||
|
{"int", TOK_TYPE},
|
||||||
|
{"long", TOK_TYPE},
|
||||||
|
{"short", TOK_TYPE},
|
||||||
|
{"void", TOK_TYPE},
|
||||||
|
{"bool", TOK_TYPE},
|
||||||
|
{"_Bool", TOK_TYPE},
|
||||||
|
{"_Complex", TOK_TYPE},
|
||||||
|
{"_Imaginary", TOK_TYPE},
|
||||||
|
// stdint
|
||||||
|
{"int8_t", TOK_TYPE},
|
||||||
|
{"int16_t", TOK_TYPE},
|
||||||
|
{"int32_t", TOK_TYPE},
|
||||||
|
{"int64_t", TOK_TYPE},
|
||||||
|
{"uint8_t", TOK_TYPE},
|
||||||
|
{"uint16_t", TOK_TYPE},
|
||||||
|
{"uint32_t", TOK_TYPE},
|
||||||
|
{"uint64_t", TOK_TYPE},
|
||||||
|
{"size_t", TOK_TYPE},
|
||||||
|
{"ssize_t", TOK_TYPE},
|
||||||
|
{"ptrdiff_t", TOK_TYPE},
|
||||||
|
{"intptr_t", TOK_TYPE},
|
||||||
|
{"uintptr_t", TOK_TYPE},
|
||||||
|
{"nullptr_t", TOK_TYPE},
|
||||||
|
{"FILE", TOK_TYPE},
|
||||||
|
// Project types
|
||||||
|
{"U8", TOK_TYPE},
|
||||||
|
{"U16", TOK_TYPE},
|
||||||
|
{"U32", TOK_TYPE},
|
||||||
|
{"U64", TOK_TYPE},
|
||||||
|
{"S8", TOK_TYPE},
|
||||||
|
{"S16", TOK_TYPE},
|
||||||
|
{"S32", TOK_TYPE},
|
||||||
|
{"S64", TOK_TYPE},
|
||||||
|
{"B32", TOK_TYPE},
|
||||||
|
{"F32", TOK_TYPE},
|
||||||
|
{"F64", TOK_TYPE},
|
||||||
|
|
||||||
|
// Values
|
||||||
|
{"false", TOK_VALUE},
|
||||||
|
{"true", TOK_VALUE},
|
||||||
|
{"NULL", TOK_VALUE},
|
||||||
|
{"nullptr", TOK_VALUE},
|
||||||
|
|
||||||
|
// Modifiers
|
||||||
|
{"const", TOK_MODIFIER},
|
||||||
|
{"constexpr", TOK_MODIFIER},
|
||||||
|
{"extern", TOK_MODIFIER},
|
||||||
|
{"register", TOK_MODIFIER},
|
||||||
|
{"signed", TOK_MODIFIER},
|
||||||
|
{"static", TOK_MODIFIER},
|
||||||
|
{"unsigned", TOK_MODIFIER},
|
||||||
|
{"volatile", TOK_MODIFIER},
|
||||||
|
{"thread_local", TOK_MODIFIER},
|
||||||
|
{"_Thread_local", TOK_MODIFIER},
|
||||||
|
};
|
||||||
|
|
||||||
|
#define C_KEYWORD_COUNT (S32)(sizeof(c_keywords) / sizeof(c_keywords[0]))
|
||||||
|
|
||||||
|
static Token_Type c_lookup_keyword(const char *word, S32 len) {
|
||||||
|
for (S32 i = 0; i < C_KEYWORD_COUNT; i++) {
|
||||||
|
const char *kw = c_keywords[i].word;
|
||||||
|
S32 kwlen = (S32)strlen(kw);
|
||||||
|
if (kwlen == len && memcmp(kw, word, len) == 0)
|
||||||
|
return c_keywords[i].type;
|
||||||
|
}
|
||||||
|
return TOK_IDENTIFIER;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Character helpers
|
||||||
|
|
||||||
|
static B32 c_is_ident_start(char c) {
|
||||||
|
return isalpha((unsigned char)c) || c == '_';
|
||||||
|
}
|
||||||
|
|
||||||
|
static B32 c_is_ident_char(char c) {
|
||||||
|
return isalnum((unsigned char)c) || c == '_';
|
||||||
|
}
|
||||||
|
|
||||||
|
static B32 c_is_hex(char c) {
|
||||||
|
return isdigit((unsigned char)c) ||
|
||||||
|
(c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Individual token parsers
|
||||||
|
|
||||||
|
static Token c_parse_identifier(Tokenizer *tok) {
|
||||||
|
Token token;
|
||||||
|
token.start = (S32)(tok->start_t - tok->buf);
|
||||||
|
const char *begin = tok->t;
|
||||||
|
while (tok->t < tok->max_t && c_is_ident_char(*tok->t))
|
||||||
|
tok->t++;
|
||||||
|
S32 len = (S32)(tok->t - begin);
|
||||||
|
token.type = c_lookup_keyword(begin, len);
|
||||||
|
token.len = (S32)(tok->t - tok->start_t);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Token c_parse_number(Tokenizer *tok) {
|
||||||
|
Token token;
|
||||||
|
token.start = (S32)(tok->start_t - tok->buf);
|
||||||
|
token.type = TOK_NUMBER;
|
||||||
|
|
||||||
|
char start_char = *tok->t;
|
||||||
|
tok->t++;
|
||||||
|
if (tok->t >= tok->max_t) goto done;
|
||||||
|
|
||||||
|
if (start_char == '0' && tok->t < tok->max_t) {
|
||||||
|
if (*tok->t == 'x' || *tok->t == 'X') {
|
||||||
|
tok->t++;
|
||||||
|
while (tok->t < tok->max_t && c_is_hex(*tok->t)) tok->t++;
|
||||||
|
goto suffixes;
|
||||||
|
}
|
||||||
|
if (*tok->t == 'b' || *tok->t == 'B') {
|
||||||
|
tok->t++;
|
||||||
|
while (tok->t < tok->max_t && (*tok->t == '0' || *tok->t == '1')) tok->t++;
|
||||||
|
goto suffixes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decimal / float
|
||||||
|
{
|
||||||
|
B32 seen_dot = 0;
|
||||||
|
while (tok->t < tok->max_t && (isdigit((unsigned char)*tok->t) || *tok->t == '.')) {
|
||||||
|
if (*tok->t == '.') {
|
||||||
|
if (seen_dot) break;
|
||||||
|
seen_dot = 1;
|
||||||
|
}
|
||||||
|
tok->t++;
|
||||||
|
}
|
||||||
|
// Exponent
|
||||||
|
if (tok->t < tok->max_t && (*tok->t == 'e' || *tok->t == 'E')) {
|
||||||
|
tok->t++;
|
||||||
|
if (tok->t < tok->max_t && (*tok->t == '+' || *tok->t == '-')) tok->t++;
|
||||||
|
while (tok->t < tok->max_t && isdigit((unsigned char)*tok->t)) tok->t++;
|
||||||
|
}
|
||||||
|
// Float suffix
|
||||||
|
if (tok->t < tok->max_t && seen_dot) {
|
||||||
|
if (*tok->t == 'f' || *tok->t == 'F' || *tok->t == 'l' || *tok->t == 'L')
|
||||||
|
tok->t++;
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suffixes:
|
||||||
|
// Integer suffixes: u, l, ll, ul, ull, lu, llu
|
||||||
|
if (tok->t < tok->max_t && (*tok->t == 'u' || *tok->t == 'U')) {
|
||||||
|
tok->t++;
|
||||||
|
if (tok->t < tok->max_t && (*tok->t == 'l' || *tok->t == 'L')) {
|
||||||
|
tok->t++;
|
||||||
|
if (tok->t < tok->max_t && (*tok->t == 'l' || *tok->t == 'L')) tok->t++;
|
||||||
|
}
|
||||||
|
} else if (tok->t < tok->max_t && (*tok->t == 'l' || *tok->t == 'L')) {
|
||||||
|
tok->t++;
|
||||||
|
if (tok->t < tok->max_t && (*tok->t == 'l' || *tok->t == 'L')) tok->t++;
|
||||||
|
if (tok->t < tok->max_t && (*tok->t == 'u' || *tok->t == 'U')) tok->t++;
|
||||||
|
}
|
||||||
|
|
||||||
|
done:
|
||||||
|
token.len = (S32)(tok->t - tok->start_t);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Token c_parse_string(Tokenizer *tok) {
|
||||||
|
Token token;
|
||||||
|
token.start = (S32)(tok->start_t - tok->buf);
|
||||||
|
token.type = TOK_STRING_LITERAL;
|
||||||
|
|
||||||
|
B32 escape = 0;
|
||||||
|
tok->t++; // skip opening "
|
||||||
|
while (tok->t < tok->max_t && *tok->t != '\n') {
|
||||||
|
if (*tok->t == '"' && !escape) { tok->t++; break; }
|
||||||
|
escape = !escape && (*tok->t == '\\');
|
||||||
|
tok->t++;
|
||||||
|
}
|
||||||
|
token.len = (S32)(tok->t - tok->start_t);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Token c_parse_char_literal(Tokenizer *tok) {
|
||||||
|
Token token;
|
||||||
|
token.start = (S32)(tok->start_t - tok->buf);
|
||||||
|
token.type = TOK_CHAR_LITERAL;
|
||||||
|
|
||||||
|
tok->t++; // skip opening '
|
||||||
|
if (tok->t < tok->max_t && *tok->t == '\\') {
|
||||||
|
tok->t++; // escape char
|
||||||
|
if (tok->t < tok->max_t && *tok->t != '\n') tok->t++; // the escaped char
|
||||||
|
} else if (tok->t < tok->max_t && *tok->t != '\n' && *tok->t != '\'') {
|
||||||
|
tok->t++; // the char
|
||||||
|
}
|
||||||
|
if (tok->t < tok->max_t && *tok->t == '\'') tok->t++; // closing '
|
||||||
|
|
||||||
|
token.len = (S32)(tok->t - tok->start_t);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Token c_parse_slash_or_comment(Tokenizer *tok) {
|
||||||
|
Token token;
|
||||||
|
token.start = (S32)(tok->start_t - tok->buf);
|
||||||
|
|
||||||
|
tok->t++; // skip '/'
|
||||||
|
if (tok->t >= tok->max_t) {
|
||||||
|
token.type = TOK_OPERATION;
|
||||||
|
token.len = 1;
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*tok->t == '/') {
|
||||||
|
// Line comment
|
||||||
|
token.type = TOK_COMMENT;
|
||||||
|
tok->t++;
|
||||||
|
while (tok->t < tok->max_t && *tok->t != '\n') tok->t++;
|
||||||
|
} else if (*tok->t == '*') {
|
||||||
|
// Block comment
|
||||||
|
token.type = TOK_MULTILINE_COMMENT;
|
||||||
|
tok->t++;
|
||||||
|
while (tok->t < tok->max_t) {
|
||||||
|
if (*tok->t == '*' && (tok->t + 1) < tok->max_t && *(tok->t + 1) == '/') {
|
||||||
|
tok->t += 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tok->t++;
|
||||||
|
}
|
||||||
|
} else if (*tok->t == '=') {
|
||||||
|
token.type = TOK_OPERATION;
|
||||||
|
tok->t++;
|
||||||
|
} else {
|
||||||
|
token.type = TOK_OPERATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
token.len = (S32)(tok->t - tok->start_t);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Token c_parse_directive(Tokenizer *tok) {
|
||||||
|
Token token;
|
||||||
|
token.start = (S32)(tok->start_t - tok->buf);
|
||||||
|
token.type = TOK_DIRECTIVE;
|
||||||
|
|
||||||
|
tok->t++; // skip '#'
|
||||||
|
// Skip whitespace between # and directive name
|
||||||
|
while (tok->t < tok->max_t && (*tok->t == ' ' || *tok->t == '\t')) tok->t++;
|
||||||
|
// Read directive name
|
||||||
|
while (tok->t < tok->max_t && isalpha((unsigned char)*tok->t)) tok->t++;
|
||||||
|
|
||||||
|
token.len = (S32)(tok->t - tok->start_t);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Two-char operator check
|
||||||
|
static Token c_parse_operator(Tokenizer *tok) {
|
||||||
|
Token token;
|
||||||
|
token.start = (S32)(tok->start_t - tok->buf);
|
||||||
|
token.type = TOK_OPERATION;
|
||||||
|
|
||||||
|
char c = *tok->t;
|
||||||
|
tok->t++;
|
||||||
|
|
||||||
|
if (tok->t < tok->max_t) {
|
||||||
|
char n = *tok->t;
|
||||||
|
// Two-character operators
|
||||||
|
if ((c == '=' && n == '=') || (c == '!' && n == '=') ||
|
||||||
|
(c == '<' && n == '=') || (c == '>' && n == '=') ||
|
||||||
|
(c == '+' && n == '=') || (c == '-' && n == '=') ||
|
||||||
|
(c == '*' && n == '=') || (c == '%' && n == '=') ||
|
||||||
|
(c == '&' && n == '=') || (c == '|' && n == '=') ||
|
||||||
|
(c == '^' && n == '=') || (c == '+' && n == '+') ||
|
||||||
|
(c == '-' && n == '-') || (c == '&' && n == '&') ||
|
||||||
|
(c == '|' && n == '|') || (c == '<' && n == '<') ||
|
||||||
|
(c == '>' && n == '>') || (c == '-' && n == '>')) {
|
||||||
|
tok->t++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle negative number literal: operator '-' followed by digit
|
||||||
|
// (not done here -- handled by caller context if needed)
|
||||||
|
|
||||||
|
token.len = (S32)(tok->t - tok->start_t);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Main get_next_token
|
||||||
|
|
||||||
|
static Token c_get_next_token(Tokenizer *tok) {
|
||||||
|
tokenizer_eat_whitespace(tok);
|
||||||
|
|
||||||
|
Token token = {0};
|
||||||
|
token.start = (S32)(tok->t - tok->buf);
|
||||||
|
token.type = TOK_DEFAULT;
|
||||||
|
|
||||||
|
if (tok->t >= tok->max_t) {
|
||||||
|
token.len = 0;
|
||||||
|
return token; // EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
tok->start_t = tok->t;
|
||||||
|
char c = *tok->t;
|
||||||
|
|
||||||
|
if (c_is_ident_start(c)) {
|
||||||
|
return c_parse_identifier(tok);
|
||||||
|
}
|
||||||
|
if (isdigit((unsigned char)c)) {
|
||||||
|
return c_parse_number(tok);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (c) {
|
||||||
|
case '"': return c_parse_string(tok);
|
||||||
|
case '\'': return c_parse_char_literal(tok);
|
||||||
|
case '/': return c_parse_slash_or_comment(tok);
|
||||||
|
case '#': return c_parse_directive(tok);
|
||||||
|
|
||||||
|
// Punctuation
|
||||||
|
case ';': case ',': case '.':
|
||||||
|
case '{': case '}': case '(': case ')': case '[': case ']':
|
||||||
|
case '\\':
|
||||||
|
token.type = TOK_PUNCTUATION;
|
||||||
|
tok->t++;
|
||||||
|
token.len = 1;
|
||||||
|
return token;
|
||||||
|
|
||||||
|
// Operators
|
||||||
|
case '=': case '!': case '<': case '>':
|
||||||
|
case '+': case '-': case '*': case '%':
|
||||||
|
case '&': case '|': case '^': case '~':
|
||||||
|
case '?': case ':':
|
||||||
|
return c_parse_operator(tok);
|
||||||
|
|
||||||
|
default:
|
||||||
|
token.type = TOK_INVALID;
|
||||||
|
tok->t++;
|
||||||
|
token.len = 1;
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// tokenize_c -- main entry point
|
||||||
|
//
|
||||||
|
// Tokenizes the full buffer and paints out_tokens[] with token types.
|
||||||
|
// Second pass: retroactively marks identifiers before '(' as functions.
|
||||||
|
|
||||||
|
static void tokenize_c(const char *data, S32 length, U8 *out_tokens) {
|
||||||
|
memset(out_tokens, TOK_DEFAULT, length);
|
||||||
|
|
||||||
|
Tokenizer tok;
|
||||||
|
tokenizer_init(&tok, data, length);
|
||||||
|
|
||||||
|
Token prev = {0};
|
||||||
|
prev.type = TOK_DEFAULT;
|
||||||
|
|
||||||
|
while (tok.t < tok.max_t) {
|
||||||
|
Token token = c_get_next_token(&tok);
|
||||||
|
if (token.len == 0) break;
|
||||||
|
|
||||||
|
paint_token(out_tokens, token.start, token.len, token.type);
|
||||||
|
|
||||||
|
// Retroactively mark identifier before '(' as a function
|
||||||
|
if (token.type == TOK_PUNCTUATION && token.len == 1 &&
|
||||||
|
data[token.start] == '(' && prev.type == TOK_IDENTIFIER) {
|
||||||
|
paint_token(out_tokens, prev.start, prev.len, TOK_FUNCTION);
|
||||||
|
}
|
||||||
|
|
||||||
|
prev = token;
|
||||||
|
}
|
||||||
|
}
|
||||||
489
c/lexer/lexer_go.c
Normal file
489
c/lexer/lexer_go.c
Normal file
@@ -0,0 +1,489 @@
|
|||||||
|
// lexer_go.c -- Go language tokenizer
|
||||||
|
//
|
||||||
|
// Same pattern as lexer_c.c: a get_next_token() loop that advances a cursor
|
||||||
|
// through the source, producing Token structs. After each token we paint
|
||||||
|
// the per-byte token-type array. A second pass marks identifiers before '('
|
||||||
|
// as functions.
|
||||||
|
|
||||||
|
#include "lexer/lexer.h"
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Go keyword tables
|
||||||
|
|
||||||
|
typedef struct GoKeywordEntry {
|
||||||
|
const char *word;
|
||||||
|
Token_Type type;
|
||||||
|
} GoKeywordEntry;
|
||||||
|
|
||||||
|
static const GoKeywordEntry go_keywords[] = {
|
||||||
|
// Control-flow & language keywords
|
||||||
|
{"break", TOK_KEYWORD},
|
||||||
|
{"case", TOK_KEYWORD},
|
||||||
|
{"chan", TOK_KEYWORD},
|
||||||
|
{"const", TOK_KEYWORD},
|
||||||
|
{"continue", TOK_KEYWORD},
|
||||||
|
{"default", TOK_KEYWORD},
|
||||||
|
{"defer", TOK_KEYWORD},
|
||||||
|
{"else", TOK_KEYWORD},
|
||||||
|
{"fallthrough", TOK_KEYWORD},
|
||||||
|
{"for", TOK_KEYWORD},
|
||||||
|
{"func", TOK_KEYWORD},
|
||||||
|
{"go", TOK_KEYWORD},
|
||||||
|
{"goto", TOK_KEYWORD},
|
||||||
|
{"if", TOK_KEYWORD},
|
||||||
|
{"import", TOK_KEYWORD},
|
||||||
|
{"interface", TOK_KEYWORD},
|
||||||
|
{"map", TOK_KEYWORD},
|
||||||
|
{"package", TOK_KEYWORD},
|
||||||
|
{"range", TOK_KEYWORD},
|
||||||
|
{"return", TOK_KEYWORD},
|
||||||
|
{"select", TOK_KEYWORD},
|
||||||
|
{"struct", TOK_KEYWORD},
|
||||||
|
{"switch", TOK_KEYWORD},
|
||||||
|
{"type", TOK_KEYWORD},
|
||||||
|
{"var", TOK_KEYWORD},
|
||||||
|
|
||||||
|
// Built-in types
|
||||||
|
{"bool", TOK_TYPE},
|
||||||
|
{"byte", TOK_TYPE},
|
||||||
|
{"complex64", TOK_TYPE},
|
||||||
|
{"complex128", TOK_TYPE},
|
||||||
|
{"error", TOK_TYPE},
|
||||||
|
{"float32", TOK_TYPE},
|
||||||
|
{"float64", TOK_TYPE},
|
||||||
|
{"int", TOK_TYPE},
|
||||||
|
{"int8", TOK_TYPE},
|
||||||
|
{"int16", TOK_TYPE},
|
||||||
|
{"int32", TOK_TYPE},
|
||||||
|
{"int64", TOK_TYPE},
|
||||||
|
{"rune", TOK_TYPE},
|
||||||
|
{"string", TOK_TYPE},
|
||||||
|
{"uint", TOK_TYPE},
|
||||||
|
{"uint8", TOK_TYPE},
|
||||||
|
{"uint16", TOK_TYPE},
|
||||||
|
{"uint32", TOK_TYPE},
|
||||||
|
{"uint64", TOK_TYPE},
|
||||||
|
{"uintptr", TOK_TYPE},
|
||||||
|
{"any", TOK_TYPE},
|
||||||
|
{"comparable", TOK_TYPE},
|
||||||
|
|
||||||
|
// Built-in values
|
||||||
|
{"true", TOK_VALUE},
|
||||||
|
{"false", TOK_VALUE},
|
||||||
|
{"nil", TOK_VALUE},
|
||||||
|
{"iota", TOK_VALUE},
|
||||||
|
|
||||||
|
// Built-in functions (treated as modifiers for distinct coloring)
|
||||||
|
{"append", TOK_MODIFIER},
|
||||||
|
{"cap", TOK_MODIFIER},
|
||||||
|
{"clear", TOK_MODIFIER},
|
||||||
|
{"close", TOK_MODIFIER},
|
||||||
|
{"complex", TOK_MODIFIER},
|
||||||
|
{"copy", TOK_MODIFIER},
|
||||||
|
{"delete", TOK_MODIFIER},
|
||||||
|
{"imag", TOK_MODIFIER},
|
||||||
|
{"len", TOK_MODIFIER},
|
||||||
|
{"make", TOK_MODIFIER},
|
||||||
|
{"max", TOK_MODIFIER},
|
||||||
|
{"min", TOK_MODIFIER},
|
||||||
|
{"new", TOK_MODIFIER},
|
||||||
|
{"panic", TOK_MODIFIER},
|
||||||
|
{"print", TOK_MODIFIER},
|
||||||
|
{"println", TOK_MODIFIER},
|
||||||
|
{"real", TOK_MODIFIER},
|
||||||
|
{"recover", TOK_MODIFIER},
|
||||||
|
};
|
||||||
|
|
||||||
|
#define GO_KEYWORD_COUNT (S32)(sizeof(go_keywords) / sizeof(go_keywords[0]))
|
||||||
|
|
||||||
|
static Token_Type go_lookup_keyword(const char *word, S32 len) {
|
||||||
|
for (S32 i = 0; i < GO_KEYWORD_COUNT; i++) {
|
||||||
|
const char *kw = go_keywords[i].word;
|
||||||
|
S32 kwlen = (S32)strlen(kw);
|
||||||
|
if (kwlen == len && memcmp(kw, word, len) == 0)
|
||||||
|
return go_keywords[i].type;
|
||||||
|
}
|
||||||
|
return TOK_IDENTIFIER;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Character helpers
|
||||||
|
|
||||||
|
static B32 go_is_ident_start(char c) {
|
||||||
|
return isalpha((unsigned char)c) || c == '_';
|
||||||
|
}
|
||||||
|
|
||||||
|
static B32 go_is_ident_char(char c) {
|
||||||
|
return isalnum((unsigned char)c) || c == '_';
|
||||||
|
}
|
||||||
|
|
||||||
|
static B32 go_is_hex(char c) {
|
||||||
|
return isdigit((unsigned char)c) ||
|
||||||
|
(c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
|
||||||
|
}
|
||||||
|
|
||||||
|
static B32 go_is_octal(char c) {
|
||||||
|
return c >= '0' && c <= '7';
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Individual token parsers
|
||||||
|
|
||||||
|
static Token go_parse_identifier(Tokenizer *tok) {
|
||||||
|
Token token;
|
||||||
|
token.start = (S32)(tok->start_t - tok->buf);
|
||||||
|
const char *begin = tok->t;
|
||||||
|
while (tok->t < tok->max_t && go_is_ident_char(*tok->t))
|
||||||
|
tok->t++;
|
||||||
|
S32 len = (S32)(tok->t - begin);
|
||||||
|
token.type = go_lookup_keyword(begin, len);
|
||||||
|
token.len = (S32)(tok->t - tok->start_t);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Token go_parse_number(Tokenizer *tok) {
|
||||||
|
Token token;
|
||||||
|
token.start = (S32)(tok->start_t - tok->buf);
|
||||||
|
token.type = TOK_NUMBER;
|
||||||
|
|
||||||
|
char start_char = *tok->t;
|
||||||
|
tok->t++;
|
||||||
|
if (tok->t >= tok->max_t) goto done;
|
||||||
|
|
||||||
|
if (start_char == '0' && tok->t < tok->max_t) {
|
||||||
|
// Hex: 0x or 0X
|
||||||
|
if (*tok->t == 'x' || *tok->t == 'X') {
|
||||||
|
tok->t++;
|
||||||
|
while (tok->t < tok->max_t && (go_is_hex(*tok->t) || *tok->t == '_')) tok->t++;
|
||||||
|
// Hex float: optional .hex_digits, optional p exponent
|
||||||
|
if (tok->t < tok->max_t && *tok->t == '.') {
|
||||||
|
tok->t++;
|
||||||
|
while (tok->t < tok->max_t && (go_is_hex(*tok->t) || *tok->t == '_')) tok->t++;
|
||||||
|
}
|
||||||
|
if (tok->t < tok->max_t && (*tok->t == 'p' || *tok->t == 'P')) {
|
||||||
|
tok->t++;
|
||||||
|
if (tok->t < tok->max_t && (*tok->t == '+' || *tok->t == '-')) tok->t++;
|
||||||
|
while (tok->t < tok->max_t && (isdigit((unsigned char)*tok->t) || *tok->t == '_')) tok->t++;
|
||||||
|
}
|
||||||
|
goto imaginary;
|
||||||
|
}
|
||||||
|
// Octal: 0o or 0O
|
||||||
|
if (*tok->t == 'o' || *tok->t == 'O') {
|
||||||
|
tok->t++;
|
||||||
|
while (tok->t < tok->max_t && (go_is_octal(*tok->t) || *tok->t == '_')) tok->t++;
|
||||||
|
goto imaginary;
|
||||||
|
}
|
||||||
|
// Binary: 0b or 0B
|
||||||
|
if (*tok->t == 'b' || *tok->t == 'B') {
|
||||||
|
tok->t++;
|
||||||
|
while (tok->t < tok->max_t && (*tok->t == '0' || *tok->t == '1' || *tok->t == '_')) tok->t++;
|
||||||
|
goto imaginary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decimal or float (also handles legacy octal like 0755)
|
||||||
|
while (tok->t < tok->max_t && (isdigit((unsigned char)*tok->t) || *tok->t == '_'))
|
||||||
|
tok->t++;
|
||||||
|
|
||||||
|
// Decimal float: .digits
|
||||||
|
if (tok->t < tok->max_t && *tok->t == '.') {
|
||||||
|
// Check next char is digit to avoid consuming '..' or method calls
|
||||||
|
if ((tok->t + 1) < tok->max_t && isdigit((unsigned char)*(tok->t + 1))) {
|
||||||
|
tok->t++;
|
||||||
|
while (tok->t < tok->max_t && (isdigit((unsigned char)*tok->t) || *tok->t == '_')) tok->t++;
|
||||||
|
} else if ((tok->t + 1) >= tok->max_t || !go_is_ident_start(*(tok->t + 1))) {
|
||||||
|
// Trailing dot like "1." -- still a float in Go
|
||||||
|
tok->t++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exponent
|
||||||
|
if (tok->t < tok->max_t && (*tok->t == 'e' || *tok->t == 'E')) {
|
||||||
|
tok->t++;
|
||||||
|
if (tok->t < tok->max_t && (*tok->t == '+' || *tok->t == '-')) tok->t++;
|
||||||
|
while (tok->t < tok->max_t && (isdigit((unsigned char)*tok->t) || *tok->t == '_')) tok->t++;
|
||||||
|
}
|
||||||
|
|
||||||
|
imaginary:
|
||||||
|
// Imaginary suffix
|
||||||
|
if (tok->t < tok->max_t && *tok->t == 'i')
|
||||||
|
tok->t++;
|
||||||
|
|
||||||
|
done:
|
||||||
|
token.len = (S32)(tok->t - tok->start_t);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dot-starting float: .5, .123e4
|
||||||
|
static Token go_parse_dot_number(Tokenizer *tok) {
|
||||||
|
Token token;
|
||||||
|
token.start = (S32)(tok->start_t - tok->buf);
|
||||||
|
token.type = TOK_NUMBER;
|
||||||
|
|
||||||
|
tok->t++; // skip '.'
|
||||||
|
while (tok->t < tok->max_t && (isdigit((unsigned char)*tok->t) || *tok->t == '_'))
|
||||||
|
tok->t++;
|
||||||
|
// Exponent
|
||||||
|
if (tok->t < tok->max_t && (*tok->t == 'e' || *tok->t == 'E')) {
|
||||||
|
tok->t++;
|
||||||
|
if (tok->t < tok->max_t && (*tok->t == '+' || *tok->t == '-')) tok->t++;
|
||||||
|
while (tok->t < tok->max_t && (isdigit((unsigned char)*tok->t) || *tok->t == '_')) tok->t++;
|
||||||
|
}
|
||||||
|
// Imaginary
|
||||||
|
if (tok->t < tok->max_t && *tok->t == 'i')
|
||||||
|
tok->t++;
|
||||||
|
|
||||||
|
token.len = (S32)(tok->t - tok->start_t);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Token go_parse_string(Tokenizer *tok) {
|
||||||
|
Token token;
|
||||||
|
token.start = (S32)(tok->start_t - tok->buf);
|
||||||
|
token.type = TOK_STRING_LITERAL;
|
||||||
|
|
||||||
|
B32 escape = 0;
|
||||||
|
tok->t++; // skip opening "
|
||||||
|
while (tok->t < tok->max_t && *tok->t != '\n') {
|
||||||
|
if (*tok->t == '"' && !escape) { tok->t++; break; }
|
||||||
|
escape = !escape && (*tok->t == '\\');
|
||||||
|
tok->t++;
|
||||||
|
}
|
||||||
|
token.len = (S32)(tok->t - tok->start_t);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Raw string literal: `...`
|
||||||
|
static Token go_parse_raw_string(Tokenizer *tok) {
|
||||||
|
Token token;
|
||||||
|
token.start = (S32)(tok->start_t - tok->buf);
|
||||||
|
token.type = TOK_STRING_LITERAL;
|
||||||
|
|
||||||
|
tok->t++; // skip opening `
|
||||||
|
while (tok->t < tok->max_t) {
|
||||||
|
if (*tok->t == '`') { tok->t++; break; }
|
||||||
|
tok->t++;
|
||||||
|
}
|
||||||
|
token.len = (S32)(tok->t - tok->start_t);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rune literal: 'x', '\n', '\u0041', etc.
|
||||||
|
static Token go_parse_rune_literal(Tokenizer *tok) {
|
||||||
|
Token token;
|
||||||
|
token.start = (S32)(tok->start_t - tok->buf);
|
||||||
|
token.type = TOK_CHAR_LITERAL;
|
||||||
|
|
||||||
|
tok->t++; // skip opening '
|
||||||
|
if (tok->t < tok->max_t && *tok->t == '\\') {
|
||||||
|
tok->t++; // skip backslash
|
||||||
|
// Consume escape sequence characters
|
||||||
|
if (tok->t < tok->max_t) {
|
||||||
|
char esc = *tok->t;
|
||||||
|
tok->t++;
|
||||||
|
if (esc == 'x') {
|
||||||
|
// \xNN
|
||||||
|
for (int i = 0; i < 2 && tok->t < tok->max_t && go_is_hex(*tok->t); i++) tok->t++;
|
||||||
|
} else if (esc == 'u') {
|
||||||
|
// \uNNNN
|
||||||
|
for (int i = 0; i < 4 && tok->t < tok->max_t && go_is_hex(*tok->t); i++) tok->t++;
|
||||||
|
} else if (esc == 'U') {
|
||||||
|
// \UNNNNNNNN
|
||||||
|
for (int i = 0; i < 8 && tok->t < tok->max_t && go_is_hex(*tok->t); i++) tok->t++;
|
||||||
|
} else if (go_is_octal(esc)) {
|
||||||
|
// \NNN
|
||||||
|
for (int i = 0; i < 2 && tok->t < tok->max_t && go_is_octal(*tok->t); i++) tok->t++;
|
||||||
|
}
|
||||||
|
// else: simple escape like \n, \t, \\, \' -- already consumed
|
||||||
|
}
|
||||||
|
} else if (tok->t < tok->max_t && *tok->t != '\n' && *tok->t != '\'') {
|
||||||
|
tok->t++; // the rune character
|
||||||
|
}
|
||||||
|
if (tok->t < tok->max_t && *tok->t == '\'') tok->t++; // closing '
|
||||||
|
|
||||||
|
token.len = (S32)(tok->t - tok->start_t);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Token go_parse_slash_or_comment(Tokenizer *tok) {
|
||||||
|
Token token;
|
||||||
|
token.start = (S32)(tok->start_t - tok->buf);
|
||||||
|
|
||||||
|
tok->t++; // skip '/'
|
||||||
|
if (tok->t >= tok->max_t) {
|
||||||
|
token.type = TOK_OPERATION;
|
||||||
|
token.len = 1;
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*tok->t == '/') {
|
||||||
|
// Line comment
|
||||||
|
token.type = TOK_COMMENT;
|
||||||
|
tok->t++;
|
||||||
|
while (tok->t < tok->max_t && *tok->t != '\n') tok->t++;
|
||||||
|
} else if (*tok->t == '*') {
|
||||||
|
// Block comment
|
||||||
|
token.type = TOK_MULTILINE_COMMENT;
|
||||||
|
tok->t++;
|
||||||
|
while (tok->t < tok->max_t) {
|
||||||
|
if (*tok->t == '*' && (tok->t + 1) < tok->max_t && *(tok->t + 1) == '/') {
|
||||||
|
tok->t += 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tok->t++;
|
||||||
|
}
|
||||||
|
} else if (*tok->t == '=') {
|
||||||
|
// /=
|
||||||
|
token.type = TOK_OPERATION;
|
||||||
|
tok->t++;
|
||||||
|
} else {
|
||||||
|
token.type = TOK_OPERATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
token.len = (S32)(tok->t - tok->start_t);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Token go_parse_operator(Tokenizer *tok) {
|
||||||
|
Token token;
|
||||||
|
token.start = (S32)(tok->start_t - tok->buf);
|
||||||
|
token.type = TOK_OPERATION;
|
||||||
|
|
||||||
|
char c = *tok->t;
|
||||||
|
tok->t++;
|
||||||
|
|
||||||
|
if (tok->t < tok->max_t) {
|
||||||
|
char n = *tok->t;
|
||||||
|
// Three-character operators
|
||||||
|
if (c == '<' && n == '<' && (tok->t + 1) < tok->max_t && *(tok->t + 1) == '=') {
|
||||||
|
tok->t += 2; goto done;
|
||||||
|
}
|
||||||
|
if (c == '>' && n == '>' && (tok->t + 1) < tok->max_t && *(tok->t + 1) == '=') {
|
||||||
|
tok->t += 2; goto done;
|
||||||
|
}
|
||||||
|
if (c == '&' && n == '^' && (tok->t + 1) < tok->max_t && *(tok->t + 1) == '=') {
|
||||||
|
tok->t += 2; goto done;
|
||||||
|
}
|
||||||
|
// := (short variable declaration)
|
||||||
|
if (c == ':' && n == '=') { tok->t++; goto done; }
|
||||||
|
// Two-character operators
|
||||||
|
if ((c == '=' && n == '=') || (c == '!' && n == '=') ||
|
||||||
|
(c == '<' && n == '=') || (c == '>' && n == '=') ||
|
||||||
|
(c == '+' && n == '=') || (c == '-' && n == '=') ||
|
||||||
|
(c == '*' && n == '=') || (c == '%' && n == '=') ||
|
||||||
|
(c == '&' && n == '=') || (c == '|' && n == '=') ||
|
||||||
|
(c == '^' && n == '=') || (c == '+' && n == '+') ||
|
||||||
|
(c == '-' && n == '-') || (c == '&' && n == '&') ||
|
||||||
|
(c == '|' && n == '|') || (c == '<' && n == '<') ||
|
||||||
|
(c == '>' && n == '>') || (c == '-' && n == '>') ||
|
||||||
|
(c == '<' && n == '-') || (c == '&' && n == '^')) {
|
||||||
|
tok->t++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
done:
|
||||||
|
token.len = (S32)(tok->t - tok->start_t);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Main get_next_token
|
||||||
|
|
||||||
|
static Token go_get_next_token(Tokenizer *tok) {
|
||||||
|
tokenizer_eat_whitespace(tok);
|
||||||
|
|
||||||
|
Token token = {0};
|
||||||
|
token.start = (S32)(tok->t - tok->buf);
|
||||||
|
token.type = TOK_DEFAULT;
|
||||||
|
|
||||||
|
if (tok->t >= tok->max_t) {
|
||||||
|
token.len = 0;
|
||||||
|
return token; // EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
tok->start_t = tok->t;
|
||||||
|
char c = *tok->t;
|
||||||
|
|
||||||
|
if (go_is_ident_start(c)) {
|
||||||
|
return go_parse_identifier(tok);
|
||||||
|
}
|
||||||
|
if (isdigit((unsigned char)c)) {
|
||||||
|
return go_parse_number(tok);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (c) {
|
||||||
|
case '"': return go_parse_string(tok);
|
||||||
|
case '`': return go_parse_raw_string(tok);
|
||||||
|
case '\'': return go_parse_rune_literal(tok);
|
||||||
|
case '/': return go_parse_slash_or_comment(tok);
|
||||||
|
|
||||||
|
// Dot: could start a float literal like .5
|
||||||
|
case '.':
|
||||||
|
if ((tok->t + 1) < tok->max_t && isdigit((unsigned char)*(tok->t + 1))) {
|
||||||
|
return go_parse_dot_number(tok);
|
||||||
|
}
|
||||||
|
// Ellipsis ... or just punctuation
|
||||||
|
token.type = TOK_PUNCTUATION;
|
||||||
|
tok->t++;
|
||||||
|
if (tok->t < tok->max_t && *tok->t == '.' &&
|
||||||
|
(tok->t + 1) < tok->max_t && *(tok->t + 1) == '.') {
|
||||||
|
tok->t += 2; // consume ...
|
||||||
|
}
|
||||||
|
token.len = (S32)(tok->t - tok->start_t);
|
||||||
|
return token;
|
||||||
|
|
||||||
|
// Punctuation
|
||||||
|
case ';': case ',':
|
||||||
|
case '{': case '}': case '(': case ')': case '[': case ']':
|
||||||
|
token.type = TOK_PUNCTUATION;
|
||||||
|
tok->t++;
|
||||||
|
token.len = 1;
|
||||||
|
return token;
|
||||||
|
|
||||||
|
// Operators
|
||||||
|
case '=': case '!': case '<': case '>':
|
||||||
|
case '+': case '-': case '*': case '%':
|
||||||
|
case '&': case '|': case '^': case '~':
|
||||||
|
case ':':
|
||||||
|
return go_parse_operator(tok);
|
||||||
|
|
||||||
|
default:
|
||||||
|
token.type = TOK_INVALID;
|
||||||
|
tok->t++;
|
||||||
|
token.len = 1;
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// tokenize_go -- main entry point
|
||||||
|
//
|
||||||
|
// Tokenizes the full buffer and paints out_tokens[] with token types.
|
||||||
|
// Second pass: retroactively marks identifiers before '(' as functions.
|
||||||
|
|
||||||
|
static void tokenize_go(const char *data, S32 length, U8 *out_tokens) {
|
||||||
|
memset(out_tokens, TOK_DEFAULT, length);
|
||||||
|
|
||||||
|
Tokenizer tok;
|
||||||
|
tokenizer_init(&tok, data, length);
|
||||||
|
|
||||||
|
Token prev = {0};
|
||||||
|
prev.type = TOK_DEFAULT;
|
||||||
|
|
||||||
|
while (tok.t < tok.max_t) {
|
||||||
|
Token token = go_get_next_token(&tok);
|
||||||
|
if (token.len == 0) break;
|
||||||
|
|
||||||
|
paint_token(out_tokens, token.start, token.len, token.type);
|
||||||
|
|
||||||
|
// Retroactively mark identifier before '(' as a function
|
||||||
|
if (token.type == TOK_PUNCTUATION && token.len == 1 &&
|
||||||
|
data[token.start] == '(' && prev.type == TOK_IDENTIFIER) {
|
||||||
|
paint_token(out_tokens, prev.start, prev.len, TOK_FUNCTION);
|
||||||
|
}
|
||||||
|
|
||||||
|
prev = token;
|
||||||
|
}
|
||||||
|
}
|
||||||
651
c/lexer/lexer_js.c
Normal file
651
c/lexer/lexer_js.c
Normal file
@@ -0,0 +1,651 @@
|
|||||||
|
// lexer_js.c -- JavaScript language tokenizer
|
||||||
|
//
|
||||||
|
// Supports nested tagged template literals with ${...} interpolation.
|
||||||
|
// Uses a depth stack to track template literal nesting so that expressions
|
||||||
|
// inside ${...} can themselves contain template literals at arbitrary depth.
|
||||||
|
//
|
||||||
|
// Example: html`outer ${css`inner ${x}`} rest`
|
||||||
|
// ^^^^ ^^^^^^ ^^^ ^^^^^^ ^ ^ ^^^^^^
|
||||||
|
// func string func str id str string
|
||||||
|
|
||||||
|
#include "lexer/lexer.h"
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// JavaScript keyword tables
|
||||||
|
|
||||||
|
typedef struct JSKeywordEntry {
|
||||||
|
const char *word;
|
||||||
|
Token_Type type;
|
||||||
|
} JSKeywordEntry;
|
||||||
|
|
||||||
|
static const JSKeywordEntry js_keywords[] = {
|
||||||
|
// Control-flow & language keywords
|
||||||
|
{"break", TOK_KEYWORD},
|
||||||
|
{"case", TOK_KEYWORD},
|
||||||
|
{"catch", TOK_KEYWORD},
|
||||||
|
{"class", TOK_KEYWORD},
|
||||||
|
{"const", TOK_KEYWORD},
|
||||||
|
{"continue", TOK_KEYWORD},
|
||||||
|
{"debugger", TOK_KEYWORD},
|
||||||
|
{"default", TOK_KEYWORD},
|
||||||
|
{"delete", TOK_KEYWORD},
|
||||||
|
{"do", TOK_KEYWORD},
|
||||||
|
{"else", TOK_KEYWORD},
|
||||||
|
{"extends", TOK_KEYWORD},
|
||||||
|
{"finally", TOK_KEYWORD},
|
||||||
|
{"for", TOK_KEYWORD},
|
||||||
|
{"function", TOK_KEYWORD},
|
||||||
|
{"if", TOK_KEYWORD},
|
||||||
|
{"in", TOK_KEYWORD},
|
||||||
|
{"instanceof", TOK_KEYWORD},
|
||||||
|
{"let", TOK_KEYWORD},
|
||||||
|
{"new", TOK_KEYWORD},
|
||||||
|
{"of", TOK_KEYWORD},
|
||||||
|
{"return", TOK_KEYWORD},
|
||||||
|
{"switch", TOK_KEYWORD},
|
||||||
|
{"throw", TOK_KEYWORD},
|
||||||
|
{"try", TOK_KEYWORD},
|
||||||
|
{"typeof", TOK_KEYWORD},
|
||||||
|
{"var", TOK_KEYWORD},
|
||||||
|
{"void", TOK_KEYWORD},
|
||||||
|
{"while", TOK_KEYWORD},
|
||||||
|
{"with", TOK_KEYWORD},
|
||||||
|
{"yield", TOK_KEYWORD},
|
||||||
|
{"async", TOK_KEYWORD},
|
||||||
|
{"await", TOK_KEYWORD},
|
||||||
|
|
||||||
|
// Module keywords (directive-style coloring)
|
||||||
|
{"import", TOK_DIRECTIVE},
|
||||||
|
{"export", TOK_DIRECTIVE},
|
||||||
|
{"from", TOK_DIRECTIVE},
|
||||||
|
{"as", TOK_DIRECTIVE},
|
||||||
|
|
||||||
|
// Values
|
||||||
|
{"true", TOK_VALUE},
|
||||||
|
{"false", TOK_VALUE},
|
||||||
|
{"null", TOK_VALUE},
|
||||||
|
{"undefined", TOK_VALUE},
|
||||||
|
{"NaN", TOK_VALUE},
|
||||||
|
{"Infinity", TOK_VALUE},
|
||||||
|
{"this", TOK_VALUE},
|
||||||
|
{"super", TOK_VALUE},
|
||||||
|
|
||||||
|
// Modifiers / contextual keywords
|
||||||
|
{"static", TOK_MODIFIER},
|
||||||
|
{"get", TOK_MODIFIER},
|
||||||
|
{"set", TOK_MODIFIER},
|
||||||
|
};
|
||||||
|
|
||||||
|
#define JS_KEYWORD_COUNT (S32)(sizeof(js_keywords) / sizeof(js_keywords[0]))
|
||||||
|
|
||||||
|
static Token_Type js_lookup_keyword(const char *word, S32 len) {
|
||||||
|
for (S32 i = 0; i < JS_KEYWORD_COUNT; i++) {
|
||||||
|
const char *kw = js_keywords[i].word;
|
||||||
|
S32 kwlen = (S32)strlen(kw);
|
||||||
|
if (kwlen == len && memcmp(kw, word, len) == 0)
|
||||||
|
return js_keywords[i].type;
|
||||||
|
}
|
||||||
|
return TOK_IDENTIFIER;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Character helpers
|
||||||
|
|
||||||
|
static B32 js_is_ident_start(char c) {
|
||||||
|
return isalpha((unsigned char)c) || c == '_' || c == '$';
|
||||||
|
}
|
||||||
|
|
||||||
|
static B32 js_is_ident_char(char c) {
|
||||||
|
return isalnum((unsigned char)c) || c == '_' || c == '$';
|
||||||
|
}
|
||||||
|
|
||||||
|
static B32 js_is_hex(char c) {
|
||||||
|
return isdigit((unsigned char)c) ||
|
||||||
|
(c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Individual token parsers
|
||||||
|
|
||||||
|
static Token js_parse_identifier(Tokenizer *tok) {
|
||||||
|
Token token;
|
||||||
|
token.start = (S32)(tok->start_t - tok->buf);
|
||||||
|
const char *begin = tok->t;
|
||||||
|
while (tok->t < tok->max_t && js_is_ident_char(*tok->t))
|
||||||
|
tok->t++;
|
||||||
|
S32 len = (S32)(tok->t - begin);
|
||||||
|
token.type = js_lookup_keyword(begin, len);
|
||||||
|
token.len = (S32)(tok->t - tok->start_t);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Token js_parse_number(Tokenizer *tok) {
|
||||||
|
Token token;
|
||||||
|
token.start = (S32)(tok->start_t - tok->buf);
|
||||||
|
token.type = TOK_NUMBER;
|
||||||
|
|
||||||
|
char start_char = *tok->t;
|
||||||
|
tok->t++;
|
||||||
|
if (tok->t >= tok->max_t) goto done;
|
||||||
|
|
||||||
|
if (start_char == '0' && tok->t < tok->max_t) {
|
||||||
|
// Hex: 0x / 0X
|
||||||
|
if (*tok->t == 'x' || *tok->t == 'X') {
|
||||||
|
tok->t++;
|
||||||
|
while (tok->t < tok->max_t && (js_is_hex(*tok->t) || *tok->t == '_')) tok->t++;
|
||||||
|
if (tok->t < tok->max_t && *tok->t == 'n') tok->t++; // BigInt
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
// Octal: 0o / 0O
|
||||||
|
if (*tok->t == 'o' || *tok->t == 'O') {
|
||||||
|
tok->t++;
|
||||||
|
while (tok->t < tok->max_t && ((*tok->t >= '0' && *tok->t <= '7') || *tok->t == '_')) tok->t++;
|
||||||
|
if (tok->t < tok->max_t && *tok->t == 'n') tok->t++;
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
// Binary: 0b / 0B
|
||||||
|
if (*tok->t == 'b' || *tok->t == 'B') {
|
||||||
|
tok->t++;
|
||||||
|
while (tok->t < tok->max_t && (*tok->t == '0' || *tok->t == '1' || *tok->t == '_')) tok->t++;
|
||||||
|
if (tok->t < tok->max_t && *tok->t == 'n') tok->t++;
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decimal digits
|
||||||
|
while (tok->t < tok->max_t && (isdigit((unsigned char)*tok->t) || *tok->t == '_'))
|
||||||
|
tok->t++;
|
||||||
|
|
||||||
|
// Float: .digits
|
||||||
|
if (tok->t < tok->max_t && *tok->t == '.') {
|
||||||
|
tok->t++;
|
||||||
|
while (tok->t < tok->max_t && (isdigit((unsigned char)*tok->t) || *tok->t == '_'))
|
||||||
|
tok->t++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exponent
|
||||||
|
if (tok->t < tok->max_t && (*tok->t == 'e' || *tok->t == 'E')) {
|
||||||
|
tok->t++;
|
||||||
|
if (tok->t < tok->max_t && (*tok->t == '+' || *tok->t == '-')) tok->t++;
|
||||||
|
while (tok->t < tok->max_t && (isdigit((unsigned char)*tok->t) || *tok->t == '_'))
|
||||||
|
tok->t++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// BigInt suffix
|
||||||
|
if (tok->t < tok->max_t && *tok->t == 'n') tok->t++;
|
||||||
|
|
||||||
|
done:
|
||||||
|
token.len = (S32)(tok->t - tok->start_t);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float starting with dot: .5, .123e4
|
||||||
|
static Token js_parse_dot_number(Tokenizer *tok) {
|
||||||
|
Token token;
|
||||||
|
token.start = (S32)(tok->start_t - tok->buf);
|
||||||
|
token.type = TOK_NUMBER;
|
||||||
|
|
||||||
|
tok->t++; // skip '.'
|
||||||
|
while (tok->t < tok->max_t && (isdigit((unsigned char)*tok->t) || *tok->t == '_'))
|
||||||
|
tok->t++;
|
||||||
|
if (tok->t < tok->max_t && (*tok->t == 'e' || *tok->t == 'E')) {
|
||||||
|
tok->t++;
|
||||||
|
if (tok->t < tok->max_t && (*tok->t == '+' || *tok->t == '-')) tok->t++;
|
||||||
|
while (tok->t < tok->max_t && (isdigit((unsigned char)*tok->t) || *tok->t == '_'))
|
||||||
|
tok->t++;
|
||||||
|
}
|
||||||
|
|
||||||
|
token.len = (S32)(tok->t - tok->start_t);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
// String literal with given quote character (" or ')
|
||||||
|
static Token js_parse_string(Tokenizer *tok, char quote) {
|
||||||
|
Token token;
|
||||||
|
token.start = (S32)(tok->start_t - tok->buf);
|
||||||
|
token.type = TOK_STRING_LITERAL;
|
||||||
|
|
||||||
|
B32 escape = 0;
|
||||||
|
tok->t++; // skip opening quote
|
||||||
|
while (tok->t < tok->max_t && *tok->t != '\n') {
|
||||||
|
if (*tok->t == quote && !escape) { tok->t++; break; }
|
||||||
|
escape = !escape && (*tok->t == '\\');
|
||||||
|
tok->t++;
|
||||||
|
}
|
||||||
|
token.len = (S32)(tok->t - tok->start_t);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
// / followed by / or * (comments), or /= (divide-assign), or bare / (divide)
|
||||||
|
static Token js_parse_slash_or_comment(Tokenizer *tok) {
|
||||||
|
Token token;
|
||||||
|
token.start = (S32)(tok->start_t - tok->buf);
|
||||||
|
|
||||||
|
tok->t++; // skip '/'
|
||||||
|
if (tok->t >= tok->max_t) {
|
||||||
|
token.type = TOK_OPERATION;
|
||||||
|
token.len = 1;
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*tok->t == '/') {
|
||||||
|
// Line comment
|
||||||
|
token.type = TOK_COMMENT;
|
||||||
|
tok->t++;
|
||||||
|
while (tok->t < tok->max_t && *tok->t != '\n') tok->t++;
|
||||||
|
} else if (*tok->t == '*') {
|
||||||
|
// Block comment
|
||||||
|
token.type = TOK_MULTILINE_COMMENT;
|
||||||
|
tok->t++;
|
||||||
|
while (tok->t < tok->max_t) {
|
||||||
|
if (*tok->t == '*' && (tok->t + 1) < tok->max_t && *(tok->t + 1) == '/') {
|
||||||
|
tok->t += 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tok->t++;
|
||||||
|
}
|
||||||
|
} else if (*tok->t == '=') {
|
||||||
|
// /=
|
||||||
|
token.type = TOK_OPERATION;
|
||||||
|
tok->t++;
|
||||||
|
} else {
|
||||||
|
// bare / (division)
|
||||||
|
token.type = TOK_OPERATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
token.len = (S32)(tok->t - tok->start_t);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regex literal: /pattern/flags
|
||||||
|
static Token js_parse_regex(Tokenizer *tok) {
|
||||||
|
Token token;
|
||||||
|
token.start = (S32)(tok->start_t - tok->buf);
|
||||||
|
token.type = TOK_STRING_LITERAL; // color regex like strings
|
||||||
|
|
||||||
|
B32 escape = 0;
|
||||||
|
B32 in_class = 0; // inside character class [...]
|
||||||
|
tok->t++; // skip opening /
|
||||||
|
|
||||||
|
while (tok->t < tok->max_t && *tok->t != '\n') {
|
||||||
|
if (escape) {
|
||||||
|
escape = 0;
|
||||||
|
tok->t++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (*tok->t == '\\') {
|
||||||
|
escape = 1;
|
||||||
|
tok->t++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (*tok->t == '[') { in_class = 1; tok->t++; continue; }
|
||||||
|
if (*tok->t == ']') { in_class = 0; tok->t++; continue; }
|
||||||
|
if (*tok->t == '/' && !in_class) {
|
||||||
|
tok->t++; // closing /
|
||||||
|
// Consume flags: d, g, i, m, s, u, v, y
|
||||||
|
while (tok->t < tok->max_t && isalpha((unsigned char)*tok->t)) tok->t++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tok->t++;
|
||||||
|
}
|
||||||
|
|
||||||
|
token.len = (S32)(tok->t - tok->start_t);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Operators (handles multi-character sequences)
|
||||||
|
static Token js_parse_operator(Tokenizer *tok) {
|
||||||
|
Token token;
|
||||||
|
token.start = (S32)(tok->start_t - tok->buf);
|
||||||
|
token.type = TOK_OPERATION;
|
||||||
|
|
||||||
|
char c = *tok->t;
|
||||||
|
tok->t++;
|
||||||
|
|
||||||
|
if (tok->t < tok->max_t) {
|
||||||
|
char n = *tok->t;
|
||||||
|
|
||||||
|
// Three-character (and four-character) operators
|
||||||
|
if ((tok->t + 1) < tok->max_t) {
|
||||||
|
char nn = *(tok->t + 1);
|
||||||
|
// >>> and >>>=
|
||||||
|
if (c == '>' && n == '>' && nn == '>') {
|
||||||
|
tok->t += 2;
|
||||||
|
if (tok->t < tok->max_t && *tok->t == '=') tok->t++;
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
// ===
|
||||||
|
if (c == '=' && n == '=' && nn == '=') { tok->t += 2; goto done; }
|
||||||
|
// !==
|
||||||
|
if (c == '!' && n == '=' && nn == '=') { tok->t += 2; goto done; }
|
||||||
|
// **=
|
||||||
|
if (c == '*' && n == '*' && nn == '=') { tok->t += 2; goto done; }
|
||||||
|
// <<=
|
||||||
|
if (c == '<' && n == '<' && nn == '=') { tok->t += 2; goto done; }
|
||||||
|
// >>=
|
||||||
|
if (c == '>' && n == '>' && nn == '=') { tok->t += 2; goto done; }
|
||||||
|
// &&=
|
||||||
|
if (c == '&' && n == '&' && nn == '=') { tok->t += 2; goto done; }
|
||||||
|
// ||=
|
||||||
|
if (c == '|' && n == '|' && nn == '=') { tok->t += 2; goto done; }
|
||||||
|
// ??=
|
||||||
|
if (c == '?' && n == '?' && nn == '=') { tok->t += 2; goto done; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Two-character operators
|
||||||
|
if ((c == '=' && n == '=') || (c == '!' && n == '=') ||
|
||||||
|
(c == '<' && n == '=') || (c == '>' && n == '=') ||
|
||||||
|
(c == '+' && n == '=') || (c == '-' && n == '=') ||
|
||||||
|
(c == '*' && n == '=') || (c == '%' && n == '=') ||
|
||||||
|
(c == '&' && n == '=') || (c == '|' && n == '=') ||
|
||||||
|
(c == '^' && n == '=') || (c == '+' && n == '+') ||
|
||||||
|
(c == '-' && n == '-') || (c == '&' && n == '&') ||
|
||||||
|
(c == '|' && n == '|') || (c == '<' && n == '<') ||
|
||||||
|
(c == '>' && n == '>') || (c == '=' && n == '>') ||
|
||||||
|
(c == '*' && n == '*') || (c == '?' && n == '?') ||
|
||||||
|
(c == '?' && n == '.')) {
|
||||||
|
tok->t++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
done:
|
||||||
|
token.len = (S32)(tok->t - tok->start_t);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Regex vs division heuristic
|
||||||
|
//
|
||||||
|
// After identifiers, numbers, values, strings, ) and ] -- slash is division.
|
||||||
|
// After keywords, operators, most punctuation, start of file -- slash is regex.
|
||||||
|
|
||||||
|
static B32 js_slash_is_regex(Token prev, const char *data) {
|
||||||
|
switch (prev.type) {
|
||||||
|
case TOK_DEFAULT: return 1; // start of file / no previous token
|
||||||
|
case TOK_KEYWORD: return 1; // e.g. return /regex/
|
||||||
|
case TOK_DIRECTIVE: return 1; // after import/export
|
||||||
|
case TOK_OPERATION:
|
||||||
|
// After ++ or --, it's division (x++ / y)
|
||||||
|
if (prev.len == 2) {
|
||||||
|
char c0 = data[prev.start];
|
||||||
|
char c1 = data[prev.start + 1];
|
||||||
|
if ((c0 == '+' && c1 == '+') || (c0 == '-' && c1 == '-'))
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
case TOK_PUNCTUATION:
|
||||||
|
// After ) or ], it's division
|
||||||
|
if (prev.len == 1) {
|
||||||
|
char c = data[prev.start];
|
||||||
|
if (c == ')' || c == ']') return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
case TOK_IDENTIFIER:
|
||||||
|
case TOK_FUNCTION:
|
||||||
|
case TOK_NUMBER:
|
||||||
|
case TOK_STRING_LITERAL:
|
||||||
|
case TOK_CHAR_LITERAL:
|
||||||
|
case TOK_VALUE:
|
||||||
|
return 0; // division
|
||||||
|
default:
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Template literal content scanner
|
||||||
|
//
|
||||||
|
// Scans the string content inside a template literal (the text between
|
||||||
|
// backticks, or between a closing } and the next ${ or closing backtick).
|
||||||
|
// Paints each byte as TOK_STRING_LITERAL.
|
||||||
|
//
|
||||||
|
// Returns 1 if we hit ${ (caller should enter expression mode),
|
||||||
|
// 0 if we hit closing ` or EOF (template is done).
|
||||||
|
|
||||||
|
static int js_scan_template_content(Tokenizer *tok, U8 *out_tokens) {
|
||||||
|
while (tok->t < tok->max_t) {
|
||||||
|
if (*tok->t == '`') {
|
||||||
|
// Closing backtick -- template done
|
||||||
|
S32 pos = (S32)(tok->t - tok->buf);
|
||||||
|
out_tokens[pos] = TOK_STRING_LITERAL;
|
||||||
|
tok->t++;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (*tok->t == '$' && (tok->t + 1) < tok->max_t && *(tok->t + 1) == '{') {
|
||||||
|
// Interpolation start -- paint ${ as punctuation
|
||||||
|
S32 pos = (S32)(tok->t - tok->buf);
|
||||||
|
out_tokens[pos] = TOK_PUNCTUATION;
|
||||||
|
out_tokens[pos + 1] = TOK_PUNCTUATION;
|
||||||
|
tok->t += 2;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (*tok->t == '\\' && (tok->t + 1) < tok->max_t) {
|
||||||
|
// Escape sequence -- paint both chars as string
|
||||||
|
S32 pos = (S32)(tok->t - tok->buf);
|
||||||
|
out_tokens[pos] = TOK_STRING_LITERAL;
|
||||||
|
out_tokens[pos + 1] = TOK_STRING_LITERAL;
|
||||||
|
tok->t += 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Regular string character
|
||||||
|
S32 pos = (S32)(tok->t - tok->buf);
|
||||||
|
out_tokens[pos] = TOK_STRING_LITERAL;
|
||||||
|
tok->t++;
|
||||||
|
}
|
||||||
|
return 0; // EOF -- unclosed template
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Get next non-template token
|
||||||
|
//
|
||||||
|
// Called by the main loop for normal expression parsing. Template backticks
|
||||||
|
// and interpolation-closing braces are handled directly by the main loop
|
||||||
|
// before this function is reached.
|
||||||
|
|
||||||
|
static Token js_get_next_token(Tokenizer *tok, Token prev, const char *data) {
|
||||||
|
tokenizer_eat_whitespace(tok);
|
||||||
|
|
||||||
|
Token token = {0};
|
||||||
|
token.start = (S32)(tok->t - tok->buf);
|
||||||
|
token.type = TOK_DEFAULT;
|
||||||
|
|
||||||
|
if (tok->t >= tok->max_t) {
|
||||||
|
token.len = 0;
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
tok->start_t = tok->t;
|
||||||
|
char c = *tok->t;
|
||||||
|
|
||||||
|
if (js_is_ident_start(c)) {
|
||||||
|
return js_parse_identifier(tok);
|
||||||
|
}
|
||||||
|
if (isdigit((unsigned char)c)) {
|
||||||
|
return js_parse_number(tok);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (c) {
|
||||||
|
case '"': return js_parse_string(tok, '"');
|
||||||
|
case '\'': return js_parse_string(tok, '\'');
|
||||||
|
|
||||||
|
case '/':
|
||||||
|
// Check for comment first (// or /*)
|
||||||
|
if ((tok->t + 1) < tok->max_t) {
|
||||||
|
char n = *(tok->t + 1);
|
||||||
|
if (n == '/' || n == '*')
|
||||||
|
return js_parse_slash_or_comment(tok);
|
||||||
|
}
|
||||||
|
// Regex or division based on context
|
||||||
|
if (js_slash_is_regex(prev, data))
|
||||||
|
return js_parse_regex(tok);
|
||||||
|
return js_parse_slash_or_comment(tok);
|
||||||
|
|
||||||
|
// Dot: float literal (.5), spread (...), or member access
|
||||||
|
case '.':
|
||||||
|
if ((tok->t + 1) < tok->max_t && isdigit((unsigned char)*(tok->t + 1))) {
|
||||||
|
return js_parse_dot_number(tok);
|
||||||
|
}
|
||||||
|
if ((tok->t + 2) < tok->max_t && *(tok->t + 1) == '.' && *(tok->t + 2) == '.') {
|
||||||
|
token.type = TOK_OPERATION;
|
||||||
|
tok->t += 3;
|
||||||
|
token.len = 3;
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
token.type = TOK_PUNCTUATION;
|
||||||
|
tok->t++;
|
||||||
|
token.len = 1;
|
||||||
|
return token;
|
||||||
|
|
||||||
|
// Punctuation
|
||||||
|
case ';': case ',':
|
||||||
|
case '(': case ')': case '[': case ']':
|
||||||
|
case '{': case '}':
|
||||||
|
case ':':
|
||||||
|
token.type = TOK_PUNCTUATION;
|
||||||
|
tok->t++;
|
||||||
|
token.len = 1;
|
||||||
|
return token;
|
||||||
|
|
||||||
|
// Operators
|
||||||
|
case '=': case '!': case '<': case '>':
|
||||||
|
case '+': case '-': case '*': case '%':
|
||||||
|
case '&': case '|': case '^': case '~':
|
||||||
|
case '?':
|
||||||
|
return js_parse_operator(tok);
|
||||||
|
|
||||||
|
// Private class fields: #name
|
||||||
|
case '#':
|
||||||
|
token.type = TOK_IDENTIFIER;
|
||||||
|
tok->t++;
|
||||||
|
while (tok->t < tok->max_t && js_is_ident_char(*tok->t)) tok->t++;
|
||||||
|
token.len = (S32)(tok->t - tok->start_t);
|
||||||
|
return token;
|
||||||
|
|
||||||
|
// Decorators: @name
|
||||||
|
case '@':
|
||||||
|
token.type = TOK_DIRECTIVE;
|
||||||
|
tok->t++;
|
||||||
|
while (tok->t < tok->max_t && js_is_ident_char(*tok->t)) tok->t++;
|
||||||
|
token.len = (S32)(tok->t - tok->start_t);
|
||||||
|
return token;
|
||||||
|
|
||||||
|
default:
|
||||||
|
token.type = TOK_INVALID;
|
||||||
|
tok->t++;
|
||||||
|
token.len = 1;
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// tokenize_js -- main entry point
|
||||||
|
//
|
||||||
|
// Tokenizes the full buffer and paints out_tokens[] with token types.
|
||||||
|
//
|
||||||
|
// Template literal nesting is tracked with a depth stack. Each level
|
||||||
|
// records how many unmatched { braces exist in the current interpolation
|
||||||
|
// expression. When a } is encountered and the brace count is zero, it
|
||||||
|
// closes the interpolation and we resume scanning template string content.
|
||||||
|
//
|
||||||
|
// html`text ${obj.x} more ${css`inner ${y}`} end`
|
||||||
|
// |str ||expr | str || |str || || str|
|
||||||
|
// ^punc ^punc ^punc ^ ^^
|
||||||
|
// ${ } ${ } }` (nesting!)
|
||||||
|
|
||||||
|
#define JS_MAX_TEMPLATE_DEPTH 32
|
||||||
|
|
||||||
|
static void tokenize_js(const char *data, S32 length, U8 *out_tokens) {
|
||||||
|
memset(out_tokens, TOK_DEFAULT, length);
|
||||||
|
|
||||||
|
Tokenizer tok;
|
||||||
|
tokenizer_init(&tok, data, length);
|
||||||
|
|
||||||
|
S32 tmpl_depth = 0;
|
||||||
|
S32 brace_count[JS_MAX_TEMPLATE_DEPTH];
|
||||||
|
memset(brace_count, 0, sizeof(brace_count));
|
||||||
|
|
||||||
|
Token prev = {0};
|
||||||
|
prev.type = TOK_DEFAULT;
|
||||||
|
|
||||||
|
while (tok.t < tok.max_t) {
|
||||||
|
tokenizer_eat_whitespace(&tok);
|
||||||
|
if (tok.t >= tok.max_t) break;
|
||||||
|
|
||||||
|
char c = *tok.t;
|
||||||
|
|
||||||
|
// ---- Template literal: opening backtick ----
|
||||||
|
// This handles both top-level template literals and nested ones
|
||||||
|
// (e.g. a tagged template inside a ${...} interpolation).
|
||||||
|
if (c == '`') {
|
||||||
|
S32 pos = (S32)(tok.t - tok.buf);
|
||||||
|
out_tokens[pos] = TOK_STRING_LITERAL;
|
||||||
|
tok.t++;
|
||||||
|
|
||||||
|
int result = js_scan_template_content(&tok, out_tokens);
|
||||||
|
if (result == 1) {
|
||||||
|
// Hit ${ -- push template depth
|
||||||
|
if (tmpl_depth < JS_MAX_TEMPLATE_DEPTH) {
|
||||||
|
brace_count[tmpl_depth] = 0;
|
||||||
|
tmpl_depth++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// result == 0: self-contained template (no interpolation, or
|
||||||
|
// all interpolations already resolved recursively)
|
||||||
|
|
||||||
|
prev.type = TOK_STRING_LITERAL;
|
||||||
|
prev.start = pos;
|
||||||
|
prev.len = (S32)(tok.t - tok.buf) - pos;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Closing brace that ends a template interpolation ----
|
||||||
|
// When we're inside a template expression and the brace count is
|
||||||
|
// zero, this } closes the ${...} and we resume string scanning.
|
||||||
|
if (c == '}' && tmpl_depth > 0 && brace_count[tmpl_depth - 1] == 0) {
|
||||||
|
S32 pos = (S32)(tok.t - tok.buf);
|
||||||
|
out_tokens[pos] = TOK_PUNCTUATION;
|
||||||
|
tok.t++;
|
||||||
|
tmpl_depth--;
|
||||||
|
|
||||||
|
// Resume template string content
|
||||||
|
int result = js_scan_template_content(&tok, out_tokens);
|
||||||
|
if (result == 1) {
|
||||||
|
// Hit another ${ -- re-enter expression mode
|
||||||
|
if (tmpl_depth < JS_MAX_TEMPLATE_DEPTH) {
|
||||||
|
brace_count[tmpl_depth] = 0;
|
||||||
|
tmpl_depth++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// result == 0: template closed with backtick
|
||||||
|
|
||||||
|
prev.type = TOK_STRING_LITERAL;
|
||||||
|
prev.start = pos;
|
||||||
|
prev.len = (S32)(tok.t - tok.buf) - pos;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Normal token ----
|
||||||
|
tok.start_t = tok.t;
|
||||||
|
Token token = js_get_next_token(&tok, prev, data);
|
||||||
|
if (token.len == 0) break;
|
||||||
|
|
||||||
|
paint_token(out_tokens, token.start, token.len, token.type);
|
||||||
|
|
||||||
|
// Track brace depth for template interpolation
|
||||||
|
if (tmpl_depth > 0 && token.type == TOK_PUNCTUATION && token.len == 1) {
|
||||||
|
char tc = data[token.start];
|
||||||
|
if (tc == '{') brace_count[tmpl_depth - 1]++;
|
||||||
|
else if (tc == '}') brace_count[tmpl_depth - 1]--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retroactively mark identifier before '(' as function
|
||||||
|
if (token.type == TOK_PUNCTUATION && token.len == 1 &&
|
||||||
|
data[token.start] == '(' && prev.type == TOK_IDENTIFIER) {
|
||||||
|
paint_token(out_tokens, prev.start, prev.len, TOK_FUNCTION);
|
||||||
|
}
|
||||||
|
|
||||||
|
prev = token;
|
||||||
|
}
|
||||||
|
}
|
||||||
422
c/lexer/lexer_lua.c
Normal file
422
c/lexer/lexer_lua.c
Normal file
@@ -0,0 +1,422 @@
|
|||||||
|
// lexer_lua.c -- Lua language tokenizer
|
||||||
|
//
|
||||||
|
// Same pattern as lexer_go.c: a get_next_token() loop that advances a cursor
|
||||||
|
// through the source, producing Token structs. After each token we paint
|
||||||
|
// the per-byte token-type array. A second pass marks identifiers before '('
|
||||||
|
// as functions.
|
||||||
|
|
||||||
|
#include "lexer/lexer.h"
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Lua keyword tables
|
||||||
|
|
||||||
|
typedef struct LuaKeywordEntry {
|
||||||
|
const char *word;
|
||||||
|
Token_Type type;
|
||||||
|
} LuaKeywordEntry;
|
||||||
|
|
||||||
|
static const LuaKeywordEntry lua_keywords[] = {
|
||||||
|
// Keywords
|
||||||
|
{"and", TOK_KEYWORD},
|
||||||
|
{"break", TOK_KEYWORD},
|
||||||
|
{"do", TOK_KEYWORD},
|
||||||
|
{"else", TOK_KEYWORD},
|
||||||
|
{"elseif", TOK_KEYWORD},
|
||||||
|
{"end", TOK_KEYWORD},
|
||||||
|
{"for", TOK_KEYWORD},
|
||||||
|
{"function", TOK_KEYWORD},
|
||||||
|
{"goto", TOK_KEYWORD},
|
||||||
|
{"if", TOK_KEYWORD},
|
||||||
|
{"in", TOK_KEYWORD},
|
||||||
|
{"local", TOK_KEYWORD},
|
||||||
|
{"not", TOK_KEYWORD},
|
||||||
|
{"or", TOK_KEYWORD},
|
||||||
|
{"repeat", TOK_KEYWORD},
|
||||||
|
{"return", TOK_KEYWORD},
|
||||||
|
{"then", TOK_KEYWORD},
|
||||||
|
{"until", TOK_KEYWORD},
|
||||||
|
{"while", TOK_KEYWORD},
|
||||||
|
|
||||||
|
// Values
|
||||||
|
{"true", TOK_VALUE},
|
||||||
|
{"false", TOK_VALUE},
|
||||||
|
{"nil", TOK_VALUE},
|
||||||
|
|
||||||
|
// Built-in globals / types (use TOK_TYPE for distinct coloring)
|
||||||
|
{"self", TOK_TYPE},
|
||||||
|
|
||||||
|
// Built-in functions (TOK_MODIFIER for distinct coloring)
|
||||||
|
{"assert", TOK_MODIFIER},
|
||||||
|
{"collectgarbage", TOK_MODIFIER},
|
||||||
|
{"dofile", TOK_MODIFIER},
|
||||||
|
{"error", TOK_MODIFIER},
|
||||||
|
{"getmetatable", TOK_MODIFIER},
|
||||||
|
{"ipairs", TOK_MODIFIER},
|
||||||
|
{"load", TOK_MODIFIER},
|
||||||
|
{"loadfile", TOK_MODIFIER},
|
||||||
|
{"next", TOK_MODIFIER},
|
||||||
|
{"pairs", TOK_MODIFIER},
|
||||||
|
{"pcall", TOK_MODIFIER},
|
||||||
|
{"print", TOK_MODIFIER},
|
||||||
|
{"rawequal", TOK_MODIFIER},
|
||||||
|
{"rawget", TOK_MODIFIER},
|
||||||
|
{"rawlen", TOK_MODIFIER},
|
||||||
|
{"rawset", TOK_MODIFIER},
|
||||||
|
{"require", TOK_MODIFIER},
|
||||||
|
{"select", TOK_MODIFIER},
|
||||||
|
{"setmetatable", TOK_MODIFIER},
|
||||||
|
{"tonumber", TOK_MODIFIER},
|
||||||
|
{"tostring", TOK_MODIFIER},
|
||||||
|
{"type", TOK_MODIFIER},
|
||||||
|
{"unpack", TOK_MODIFIER},
|
||||||
|
{"xpcall", TOK_MODIFIER},
|
||||||
|
};
|
||||||
|
|
||||||
|
#define LUA_KEYWORD_COUNT (S32)(sizeof(lua_keywords) / sizeof(lua_keywords[0]))
|
||||||
|
|
||||||
|
static Token_Type lua_lookup_keyword(const char *word, S32 len) {
|
||||||
|
for (S32 i = 0; i < LUA_KEYWORD_COUNT; i++) {
|
||||||
|
const char *kw = lua_keywords[i].word;
|
||||||
|
S32 kwlen = (S32)strlen(kw);
|
||||||
|
if (kwlen == len && memcmp(kw, word, len) == 0)
|
||||||
|
return lua_keywords[i].type;
|
||||||
|
}
|
||||||
|
return TOK_IDENTIFIER;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Character helpers
|
||||||
|
|
||||||
|
static B32 lua_is_ident_start(char c) {
|
||||||
|
return isalpha((unsigned char)c) || c == '_';
|
||||||
|
}
|
||||||
|
|
||||||
|
static B32 lua_is_ident_char(char c) {
|
||||||
|
return isalnum((unsigned char)c) || c == '_';
|
||||||
|
}
|
||||||
|
|
||||||
|
static B32 lua_is_hex(char c) {
|
||||||
|
return isdigit((unsigned char)c) ||
|
||||||
|
(c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Long bracket level detection
|
||||||
|
// Returns the level (number of '=' signs) if we're at a long bracket opening
|
||||||
|
// like [[ or [==[ etc. Returns -1 if not a long bracket.
|
||||||
|
|
||||||
|
static S32 lua_long_bracket_level(const char *p, const char *max_p) {
|
||||||
|
if (p >= max_p || *p != '[') return -1;
|
||||||
|
p++;
|
||||||
|
S32 level = 0;
|
||||||
|
while (p < max_p && *p == '=') { level++; p++; }
|
||||||
|
if (p < max_p && *p == '[') return level;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan past the closing long bracket of the given level.
|
||||||
|
// Cursor should be right after the opening [=*[.
|
||||||
|
static void lua_scan_long_bracket_close(Tokenizer *tok, S32 level) {
|
||||||
|
while (tok->t < tok->max_t) {
|
||||||
|
if (*tok->t == ']') {
|
||||||
|
const char *p = tok->t + 1;
|
||||||
|
S32 count = 0;
|
||||||
|
while (p < tok->max_t && *p == '=' && count < level) { count++; p++; }
|
||||||
|
if (count == level && p < tok->max_t && *p == ']') {
|
||||||
|
tok->t = p + 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tok->t++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Individual token parsers
|
||||||
|
|
||||||
|
static Token lua_parse_identifier(Tokenizer *tok) {
|
||||||
|
Token token;
|
||||||
|
token.start = (S32)(tok->start_t - tok->buf);
|
||||||
|
const char *begin = tok->t;
|
||||||
|
while (tok->t < tok->max_t && lua_is_ident_char(*tok->t))
|
||||||
|
tok->t++;
|
||||||
|
S32 len = (S32)(tok->t - begin);
|
||||||
|
token.type = lua_lookup_keyword(begin, len);
|
||||||
|
token.len = (S32)(tok->t - tok->start_t);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Token lua_parse_number(Tokenizer *tok) {
|
||||||
|
Token token;
|
||||||
|
token.start = (S32)(tok->start_t - tok->buf);
|
||||||
|
token.type = TOK_NUMBER;
|
||||||
|
|
||||||
|
char c = *tok->t;
|
||||||
|
tok->t++;
|
||||||
|
|
||||||
|
if (c == '0' && tok->t < tok->max_t && (*tok->t == 'x' || *tok->t == 'X')) {
|
||||||
|
// Hex literal
|
||||||
|
tok->t++;
|
||||||
|
while (tok->t < tok->max_t && lua_is_hex(*tok->t)) tok->t++;
|
||||||
|
// Hex float
|
||||||
|
if (tok->t < tok->max_t && *tok->t == '.') {
|
||||||
|
tok->t++;
|
||||||
|
while (tok->t < tok->max_t && lua_is_hex(*tok->t)) tok->t++;
|
||||||
|
}
|
||||||
|
// Hex exponent (p/P)
|
||||||
|
if (tok->t < tok->max_t && (*tok->t == 'p' || *tok->t == 'P')) {
|
||||||
|
tok->t++;
|
||||||
|
if (tok->t < tok->max_t && (*tok->t == '+' || *tok->t == '-')) tok->t++;
|
||||||
|
while (tok->t < tok->max_t && isdigit((unsigned char)*tok->t)) tok->t++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Decimal
|
||||||
|
while (tok->t < tok->max_t && isdigit((unsigned char)*tok->t)) tok->t++;
|
||||||
|
// Float
|
||||||
|
if (tok->t < tok->max_t && *tok->t == '.') {
|
||||||
|
if ((tok->t + 1) < tok->max_t && isdigit((unsigned char)*(tok->t + 1))) {
|
||||||
|
tok->t++;
|
||||||
|
while (tok->t < tok->max_t && isdigit((unsigned char)*tok->t)) tok->t++;
|
||||||
|
} else if ((tok->t + 1) >= tok->max_t || !lua_is_ident_start(*(tok->t + 1))) {
|
||||||
|
tok->t++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Exponent
|
||||||
|
if (tok->t < tok->max_t && (*tok->t == 'e' || *tok->t == 'E')) {
|
||||||
|
tok->t++;
|
||||||
|
if (tok->t < tok->max_t && (*tok->t == '+' || *tok->t == '-')) tok->t++;
|
||||||
|
while (tok->t < tok->max_t && isdigit((unsigned char)*tok->t)) tok->t++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
token.len = (S32)(tok->t - tok->start_t);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Token lua_parse_dot_number(Tokenizer *tok) {
|
||||||
|
Token token;
|
||||||
|
token.start = (S32)(tok->start_t - tok->buf);
|
||||||
|
token.type = TOK_NUMBER;
|
||||||
|
|
||||||
|
tok->t++; // skip '.'
|
||||||
|
while (tok->t < tok->max_t && isdigit((unsigned char)*tok->t)) tok->t++;
|
||||||
|
if (tok->t < tok->max_t && (*tok->t == 'e' || *tok->t == 'E')) {
|
||||||
|
tok->t++;
|
||||||
|
if (tok->t < tok->max_t && (*tok->t == '+' || *tok->t == '-')) tok->t++;
|
||||||
|
while (tok->t < tok->max_t && isdigit((unsigned char)*tok->t)) tok->t++;
|
||||||
|
}
|
||||||
|
|
||||||
|
token.len = (S32)(tok->t - tok->start_t);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Token lua_parse_string(Tokenizer *tok, char quote) {
|
||||||
|
Token token;
|
||||||
|
token.start = (S32)(tok->start_t - tok->buf);
|
||||||
|
token.type = TOK_STRING_LITERAL;
|
||||||
|
|
||||||
|
B32 escape = 0;
|
||||||
|
tok->t++; // skip opening quote
|
||||||
|
while (tok->t < tok->max_t && *tok->t != '\n') {
|
||||||
|
if (*tok->t == quote && !escape) { tok->t++; break; }
|
||||||
|
escape = !escape && (*tok->t == '\\');
|
||||||
|
tok->t++;
|
||||||
|
}
|
||||||
|
token.len = (S32)(tok->t - tok->start_t);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Token lua_parse_long_string(Tokenizer *tok, S32 level) {
|
||||||
|
Token token;
|
||||||
|
token.start = (S32)(tok->start_t - tok->buf);
|
||||||
|
token.type = TOK_STRING_LITERAL;
|
||||||
|
|
||||||
|
// Skip past opening [=*[
|
||||||
|
tok->t++; // first [
|
||||||
|
tok->t += level; // = signs
|
||||||
|
tok->t++; // second [
|
||||||
|
|
||||||
|
lua_scan_long_bracket_close(tok, level);
|
||||||
|
|
||||||
|
token.len = (S32)(tok->t - tok->start_t);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Token lua_parse_comment(Tokenizer *tok) {
|
||||||
|
Token token;
|
||||||
|
token.start = (S32)(tok->start_t - tok->buf);
|
||||||
|
|
||||||
|
tok->t += 2; // skip '--'
|
||||||
|
|
||||||
|
// Check for long comment --[=*[
|
||||||
|
S32 level = lua_long_bracket_level(tok->t, tok->max_t);
|
||||||
|
if (level >= 0) {
|
||||||
|
token.type = TOK_MULTILINE_COMMENT;
|
||||||
|
tok->t++; // first [
|
||||||
|
tok->t += level; // = signs
|
||||||
|
tok->t++; // second [
|
||||||
|
lua_scan_long_bracket_close(tok, level);
|
||||||
|
} else {
|
||||||
|
// Single-line comment
|
||||||
|
token.type = TOK_COMMENT;
|
||||||
|
while (tok->t < tok->max_t && *tok->t != '\n') tok->t++;
|
||||||
|
}
|
||||||
|
|
||||||
|
token.len = (S32)(tok->t - tok->start_t);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Token lua_parse_operator(Tokenizer *tok) {
|
||||||
|
Token token;
|
||||||
|
token.start = (S32)(tok->start_t - tok->buf);
|
||||||
|
token.type = TOK_OPERATION;
|
||||||
|
|
||||||
|
char c = *tok->t;
|
||||||
|
tok->t++;
|
||||||
|
|
||||||
|
if (tok->t < tok->max_t) {
|
||||||
|
char n = *tok->t;
|
||||||
|
// Two-character operators
|
||||||
|
if ((c == '=' && n == '=') || (c == '~' && n == '=') ||
|
||||||
|
(c == '<' && n == '=') || (c == '>' && n == '=') ||
|
||||||
|
(c == '.' && n == '.') || (c == '<' && n == '<') ||
|
||||||
|
(c == '>' && n == '>') || (c == '/' && n == '/')) {
|
||||||
|
tok->t++;
|
||||||
|
// Three-character: .. can be followed by . to make ...
|
||||||
|
if (c == '.' && n == '.' && tok->t < tok->max_t && *tok->t == '.') {
|
||||||
|
tok->t++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
token.len = (S32)(tok->t - tok->start_t);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Main get_next_token
|
||||||
|
|
||||||
|
static Token lua_get_next_token(Tokenizer *tok) {
|
||||||
|
tokenizer_eat_whitespace(tok);
|
||||||
|
|
||||||
|
Token token = {0};
|
||||||
|
token.start = (S32)(tok->t - tok->buf);
|
||||||
|
token.type = TOK_DEFAULT;
|
||||||
|
|
||||||
|
if (tok->t >= tok->max_t) {
|
||||||
|
token.len = 0;
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
tok->start_t = tok->t;
|
||||||
|
char c = *tok->t;
|
||||||
|
|
||||||
|
if (lua_is_ident_start(c)) {
|
||||||
|
return lua_parse_identifier(tok);
|
||||||
|
}
|
||||||
|
if (isdigit((unsigned char)c)) {
|
||||||
|
return lua_parse_number(tok);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (c) {
|
||||||
|
case '\'':
|
||||||
|
case '"':
|
||||||
|
return lua_parse_string(tok, c);
|
||||||
|
|
||||||
|
case '[': {
|
||||||
|
// Check for long string [=*[
|
||||||
|
S32 level = lua_long_bracket_level(tok->t, tok->max_t);
|
||||||
|
if (level >= 0) {
|
||||||
|
return lua_parse_long_string(tok, level);
|
||||||
|
}
|
||||||
|
// Plain bracket punctuation
|
||||||
|
token.type = TOK_PUNCTUATION;
|
||||||
|
tok->t++;
|
||||||
|
token.len = 1;
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
case '-':
|
||||||
|
// Check for comment --
|
||||||
|
if ((tok->t + 1) < tok->max_t && *(tok->t + 1) == '-') {
|
||||||
|
return lua_parse_comment(tok);
|
||||||
|
}
|
||||||
|
// Minus operator
|
||||||
|
token.type = TOK_OPERATION;
|
||||||
|
tok->t++;
|
||||||
|
token.len = 1;
|
||||||
|
return token;
|
||||||
|
|
||||||
|
case '.':
|
||||||
|
// .digits = float literal
|
||||||
|
if ((tok->t + 1) < tok->max_t && isdigit((unsigned char)*(tok->t + 1))) {
|
||||||
|
return lua_parse_dot_number(tok);
|
||||||
|
}
|
||||||
|
// .. or ... or just .
|
||||||
|
return lua_parse_operator(tok);
|
||||||
|
|
||||||
|
// Punctuation
|
||||||
|
case ';': case ',': case ':':
|
||||||
|
case '{': case '}': case '(': case ')': case ']':
|
||||||
|
token.type = TOK_PUNCTUATION;
|
||||||
|
tok->t++;
|
||||||
|
token.len = 1;
|
||||||
|
return token;
|
||||||
|
|
||||||
|
// Directive: shebang or labels (::label::)
|
||||||
|
case '#':
|
||||||
|
// Shebang line or length operator
|
||||||
|
if (tok->t == tok->buf && (tok->t + 1) < tok->max_t && *(tok->t + 1) == '!') {
|
||||||
|
token.type = TOK_DIRECTIVE;
|
||||||
|
while (tok->t < tok->max_t && *tok->t != '\n') tok->t++;
|
||||||
|
token.len = (S32)(tok->t - tok->start_t);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
token.type = TOK_OPERATION;
|
||||||
|
tok->t++;
|
||||||
|
token.len = 1;
|
||||||
|
return token;
|
||||||
|
|
||||||
|
// Operators
|
||||||
|
case '=': case '~': case '<': case '>':
|
||||||
|
case '+': case '*': case '/': case '%':
|
||||||
|
case '^': case '&': case '|':
|
||||||
|
return lua_parse_operator(tok);
|
||||||
|
|
||||||
|
default:
|
||||||
|
token.type = TOK_INVALID;
|
||||||
|
tok->t++;
|
||||||
|
token.len = 1;
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// tokenize_lua -- main entry point
|
||||||
|
|
||||||
|
static void tokenize_lua(const char *data, S32 length, U8 *out_tokens) {
|
||||||
|
memset(out_tokens, TOK_DEFAULT, length);
|
||||||
|
|
||||||
|
Tokenizer tok;
|
||||||
|
tokenizer_init(&tok, data, length);
|
||||||
|
|
||||||
|
Token prev = {0};
|
||||||
|
prev.type = TOK_DEFAULT;
|
||||||
|
|
||||||
|
while (tok.t < tok.max_t) {
|
||||||
|
Token token = lua_get_next_token(&tok);
|
||||||
|
if (token.len == 0) break;
|
||||||
|
|
||||||
|
paint_token(out_tokens, token.start, token.len, token.type);
|
||||||
|
|
||||||
|
// Retroactively mark identifier before '(' as a function
|
||||||
|
if (token.type == TOK_PUNCTUATION && token.len == 1 &&
|
||||||
|
data[token.start] == '(' && prev.type == TOK_IDENTIFIER) {
|
||||||
|
paint_token(out_tokens, prev.start, prev.len, TOK_FUNCTION);
|
||||||
|
}
|
||||||
|
|
||||||
|
prev = token;
|
||||||
|
}
|
||||||
|
}
|
||||||
645
c/lexer/lexer_sql.c
Normal file
645
c/lexer/lexer_sql.c
Normal file
@@ -0,0 +1,645 @@
|
|||||||
|
// lexer_sql.c -- SQL language tokenizer
|
||||||
|
//
|
||||||
|
// Same pattern as lexer_go.c: a get_next_token() loop that advances a cursor
|
||||||
|
// through the source, producing Token structs. After each token we paint
|
||||||
|
// the per-byte token-type array. A second pass marks identifiers before '('
|
||||||
|
// as functions.
|
||||||
|
//
|
||||||
|
// SQL keywords are case-insensitive, so comparisons use a case-insensitive
|
||||||
|
// match. Covers standard SQL plus common extensions (MySQL, PostgreSQL, etc.).
|
||||||
|
|
||||||
|
#include "lexer/lexer.h"
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// SQL keyword tables
|
||||||
|
|
||||||
|
typedef struct SQLKeywordEntry {
|
||||||
|
const char *word;
|
||||||
|
Token_Type type;
|
||||||
|
} SQLKeywordEntry;
|
||||||
|
|
||||||
|
static const SQLKeywordEntry sql_keywords[] = {
|
||||||
|
// DML / DQL keywords
|
||||||
|
{"SELECT", TOK_KEYWORD},
|
||||||
|
{"FROM", TOK_KEYWORD},
|
||||||
|
{"WHERE", TOK_KEYWORD},
|
||||||
|
{"INSERT", TOK_KEYWORD},
|
||||||
|
{"INTO", TOK_KEYWORD},
|
||||||
|
{"UPDATE", TOK_KEYWORD},
|
||||||
|
{"DELETE", TOK_KEYWORD},
|
||||||
|
{"SET", TOK_KEYWORD},
|
||||||
|
{"VALUES", TOK_KEYWORD},
|
||||||
|
{"AS", TOK_KEYWORD},
|
||||||
|
{"ON", TOK_KEYWORD},
|
||||||
|
{"JOIN", TOK_KEYWORD},
|
||||||
|
{"INNER", TOK_KEYWORD},
|
||||||
|
{"LEFT", TOK_KEYWORD},
|
||||||
|
{"RIGHT", TOK_KEYWORD},
|
||||||
|
{"OUTER", TOK_KEYWORD},
|
||||||
|
{"FULL", TOK_KEYWORD},
|
||||||
|
{"CROSS", TOK_KEYWORD},
|
||||||
|
{"NATURAL", TOK_KEYWORD},
|
||||||
|
{"USING", TOK_KEYWORD},
|
||||||
|
{"ORDER", TOK_KEYWORD},
|
||||||
|
{"BY", TOK_KEYWORD},
|
||||||
|
{"GROUP", TOK_KEYWORD},
|
||||||
|
{"HAVING", TOK_KEYWORD},
|
||||||
|
{"LIMIT", TOK_KEYWORD},
|
||||||
|
{"OFFSET", TOK_KEYWORD},
|
||||||
|
{"UNION", TOK_KEYWORD},
|
||||||
|
{"ALL", TOK_KEYWORD},
|
||||||
|
{"DISTINCT", TOK_KEYWORD},
|
||||||
|
{"TOP", TOK_KEYWORD},
|
||||||
|
{"FETCH", TOK_KEYWORD},
|
||||||
|
{"NEXT", TOK_KEYWORD},
|
||||||
|
{"ROWS", TOK_KEYWORD},
|
||||||
|
{"ONLY", TOK_KEYWORD},
|
||||||
|
{"FIRST", TOK_KEYWORD},
|
||||||
|
{"LAST", TOK_KEYWORD},
|
||||||
|
|
||||||
|
// DDL keywords
|
||||||
|
{"CREATE", TOK_KEYWORD},
|
||||||
|
{"ALTER", TOK_KEYWORD},
|
||||||
|
{"DROP", TOK_KEYWORD},
|
||||||
|
{"TABLE", TOK_KEYWORD},
|
||||||
|
{"VIEW", TOK_KEYWORD},
|
||||||
|
{"INDEX", TOK_KEYWORD},
|
||||||
|
{"DATABASE", TOK_KEYWORD},
|
||||||
|
{"SCHEMA", TOK_KEYWORD},
|
||||||
|
{"COLUMN", TOK_KEYWORD},
|
||||||
|
{"ADD", TOK_KEYWORD},
|
||||||
|
{"RENAME", TOK_KEYWORD},
|
||||||
|
{"TRUNCATE", TOK_KEYWORD},
|
||||||
|
{"REPLACE", TOK_KEYWORD},
|
||||||
|
{"TEMPORARY", TOK_KEYWORD},
|
||||||
|
{"TEMP", TOK_KEYWORD},
|
||||||
|
{"IF", TOK_KEYWORD},
|
||||||
|
{"EXISTS", TOK_KEYWORD},
|
||||||
|
{"CASCADE", TOK_KEYWORD},
|
||||||
|
{"RESTRICT", TOK_KEYWORD},
|
||||||
|
|
||||||
|
// Constraints & keys
|
||||||
|
{"PRIMARY", TOK_KEYWORD},
|
||||||
|
{"KEY", TOK_KEYWORD},
|
||||||
|
{"FOREIGN", TOK_KEYWORD},
|
||||||
|
{"REFERENCES", TOK_KEYWORD},
|
||||||
|
{"UNIQUE", TOK_KEYWORD},
|
||||||
|
{"CHECK", TOK_KEYWORD},
|
||||||
|
{"CONSTRAINT", TOK_KEYWORD},
|
||||||
|
{"DEFAULT", TOK_KEYWORD},
|
||||||
|
{"AUTOINCREMENT", TOK_KEYWORD},
|
||||||
|
{"AUTO_INCREMENT", TOK_KEYWORD},
|
||||||
|
{"IDENTITY", TOK_KEYWORD},
|
||||||
|
|
||||||
|
// Control flow / procedural
|
||||||
|
{"BEGIN", TOK_KEYWORD},
|
||||||
|
{"END", TOK_KEYWORD},
|
||||||
|
{"COMMIT", TOK_KEYWORD},
|
||||||
|
{"ROLLBACK", TOK_KEYWORD},
|
||||||
|
{"SAVEPOINT", TOK_KEYWORD},
|
||||||
|
{"TRANSACTION", TOK_KEYWORD},
|
||||||
|
{"RETURN", TOK_KEYWORD},
|
||||||
|
{"RETURNS", TOK_KEYWORD},
|
||||||
|
{"DECLARE", TOK_KEYWORD},
|
||||||
|
{"CURSOR", TOK_KEYWORD},
|
||||||
|
{"OPEN", TOK_KEYWORD},
|
||||||
|
{"CLOSE", TOK_KEYWORD},
|
||||||
|
{"DEALLOCATE", TOK_KEYWORD},
|
||||||
|
{"EXEC", TOK_KEYWORD},
|
||||||
|
{"EXECUTE", TOK_KEYWORD},
|
||||||
|
{"CALL", TOK_KEYWORD},
|
||||||
|
{"PROCEDURE", TOK_KEYWORD},
|
||||||
|
{"FUNCTION", TOK_KEYWORD},
|
||||||
|
{"TRIGGER", TOK_KEYWORD},
|
||||||
|
{"CASE", TOK_KEYWORD},
|
||||||
|
{"WHEN", TOK_KEYWORD},
|
||||||
|
{"THEN", TOK_KEYWORD},
|
||||||
|
{"ELSE", TOK_KEYWORD},
|
||||||
|
{"WHILE", TOK_KEYWORD},
|
||||||
|
{"LOOP", TOK_KEYWORD},
|
||||||
|
{"FOR", TOK_KEYWORD},
|
||||||
|
{"EACH", TOK_KEYWORD},
|
||||||
|
{"ROW", TOK_KEYWORD},
|
||||||
|
{"AFTER", TOK_KEYWORD},
|
||||||
|
{"BEFORE", TOK_KEYWORD},
|
||||||
|
{"INSTEAD", TOK_KEYWORD},
|
||||||
|
{"OF", TOK_KEYWORD},
|
||||||
|
|
||||||
|
// Logical operators (keywords)
|
||||||
|
{"AND", TOK_KEYWORD},
|
||||||
|
{"OR", TOK_KEYWORD},
|
||||||
|
{"NOT", TOK_KEYWORD},
|
||||||
|
{"IN", TOK_KEYWORD},
|
||||||
|
{"BETWEEN", TOK_KEYWORD},
|
||||||
|
{"LIKE", TOK_KEYWORD},
|
||||||
|
{"ILIKE", TOK_KEYWORD},
|
||||||
|
{"IS", TOK_KEYWORD},
|
||||||
|
{"ANY", TOK_KEYWORD},
|
||||||
|
{"SOME", TOK_KEYWORD},
|
||||||
|
{"EXCEPT", TOK_KEYWORD},
|
||||||
|
{"INTERSECT", TOK_KEYWORD},
|
||||||
|
{"WITH", TOK_KEYWORD},
|
||||||
|
{"RECURSIVE", TOK_KEYWORD},
|
||||||
|
{"OVER", TOK_KEYWORD},
|
||||||
|
{"PARTITION", TOK_KEYWORD},
|
||||||
|
{"WINDOW", TOK_KEYWORD},
|
||||||
|
{"LATERAL", TOK_KEYWORD},
|
||||||
|
|
||||||
|
// Misc
|
||||||
|
{"GRANT", TOK_KEYWORD},
|
||||||
|
{"REVOKE", TOK_KEYWORD},
|
||||||
|
{"EXPLAIN", TOK_KEYWORD},
|
||||||
|
{"ANALYZE", TOK_KEYWORD},
|
||||||
|
{"VACUUM", TOK_KEYWORD},
|
||||||
|
{"PRAGMA", TOK_KEYWORD},
|
||||||
|
{"DESCRIBE", TOK_KEYWORD},
|
||||||
|
{"SHOW", TOK_KEYWORD},
|
||||||
|
{"USE", TOK_KEYWORD},
|
||||||
|
{"COPY", TOK_KEYWORD},
|
||||||
|
{"PERFORM", TOK_KEYWORD},
|
||||||
|
{"RAISE", TOK_KEYWORD},
|
||||||
|
|
||||||
|
// Modifiers
|
||||||
|
{"ASC", TOK_MODIFIER},
|
||||||
|
{"DESC", TOK_MODIFIER},
|
||||||
|
{"NULLS", TOK_MODIFIER},
|
||||||
|
{"NOT", TOK_MODIFIER},
|
||||||
|
|
||||||
|
// Data types
|
||||||
|
{"INT", TOK_TYPE},
|
||||||
|
{"INTEGER", TOK_TYPE},
|
||||||
|
{"SMALLINT", TOK_TYPE},
|
||||||
|
{"BIGINT", TOK_TYPE},
|
||||||
|
{"TINYINT", TOK_TYPE},
|
||||||
|
{"MEDIUMINT", TOK_TYPE},
|
||||||
|
{"SERIAL", TOK_TYPE},
|
||||||
|
{"BIGSERIAL", TOK_TYPE},
|
||||||
|
{"FLOAT", TOK_TYPE},
|
||||||
|
{"REAL", TOK_TYPE},
|
||||||
|
{"DOUBLE", TOK_TYPE},
|
||||||
|
{"DECIMAL", TOK_TYPE},
|
||||||
|
{"NUMERIC", TOK_TYPE},
|
||||||
|
{"PRECISION", TOK_TYPE},
|
||||||
|
{"CHAR", TOK_TYPE},
|
||||||
|
{"VARCHAR", TOK_TYPE},
|
||||||
|
{"TEXT", TOK_TYPE},
|
||||||
|
{"NCHAR", TOK_TYPE},
|
||||||
|
{"NVARCHAR", TOK_TYPE},
|
||||||
|
{"NTEXT", TOK_TYPE},
|
||||||
|
{"BLOB", TOK_TYPE},
|
||||||
|
{"CLOB", TOK_TYPE},
|
||||||
|
{"BYTEA", TOK_TYPE},
|
||||||
|
{"BOOLEAN", TOK_TYPE},
|
||||||
|
{"BOOL", TOK_TYPE},
|
||||||
|
{"DATE", TOK_TYPE},
|
||||||
|
{"TIME", TOK_TYPE},
|
||||||
|
{"DATETIME", TOK_TYPE},
|
||||||
|
{"TIMESTAMP", TOK_TYPE},
|
||||||
|
{"TIMESTAMPTZ", TOK_TYPE},
|
||||||
|
{"INTERVAL", TOK_TYPE},
|
||||||
|
{"UUID", TOK_TYPE},
|
||||||
|
{"JSON", TOK_TYPE},
|
||||||
|
{"JSONB", TOK_TYPE},
|
||||||
|
{"XML", TOK_TYPE},
|
||||||
|
{"ARRAY", TOK_TYPE},
|
||||||
|
{"ENUM", TOK_TYPE},
|
||||||
|
{"MONEY", TOK_TYPE},
|
||||||
|
{"BIT", TOK_TYPE},
|
||||||
|
{"VARBIT", TOK_TYPE},
|
||||||
|
{"INET", TOK_TYPE},
|
||||||
|
{"CIDR", TOK_TYPE},
|
||||||
|
{"MACADDR", TOK_TYPE},
|
||||||
|
{"POINT", TOK_TYPE},
|
||||||
|
{"LINE", TOK_TYPE},
|
||||||
|
{"POLYGON", TOK_TYPE},
|
||||||
|
{"GEOMETRY", TOK_TYPE},
|
||||||
|
{"GEOGRAPHY", TOK_TYPE},
|
||||||
|
|
||||||
|
// Values
|
||||||
|
{"NULL", TOK_VALUE},
|
||||||
|
{"TRUE", TOK_VALUE},
|
||||||
|
{"FALSE", TOK_VALUE},
|
||||||
|
{"CURRENT_DATE", TOK_VALUE},
|
||||||
|
{"CURRENT_TIME", TOK_VALUE},
|
||||||
|
{"CURRENT_TIMESTAMP", TOK_VALUE},
|
||||||
|
{"CURRENT_USER", TOK_VALUE},
|
||||||
|
|
||||||
|
// Built-in aggregate / window / common functions (TOK_MODIFIER)
|
||||||
|
{"COUNT", TOK_MODIFIER},
|
||||||
|
{"SUM", TOK_MODIFIER},
|
||||||
|
{"AVG", TOK_MODIFIER},
|
||||||
|
{"MIN", TOK_MODIFIER},
|
||||||
|
{"MAX", TOK_MODIFIER},
|
||||||
|
{"COALESCE", TOK_MODIFIER},
|
||||||
|
{"NULLIF", TOK_MODIFIER},
|
||||||
|
{"CAST", TOK_MODIFIER},
|
||||||
|
{"CONVERT", TOK_MODIFIER},
|
||||||
|
{"IFNULL", TOK_MODIFIER},
|
||||||
|
{"ISNULL", TOK_MODIFIER},
|
||||||
|
{"NVL", TOK_MODIFIER},
|
||||||
|
{"ROW_NUMBER", TOK_MODIFIER},
|
||||||
|
{"RANK", TOK_MODIFIER},
|
||||||
|
{"DENSE_RANK", TOK_MODIFIER},
|
||||||
|
{"NTILE", TOK_MODIFIER},
|
||||||
|
{"LAG", TOK_MODIFIER},
|
||||||
|
{"LEAD", TOK_MODIFIER},
|
||||||
|
{"FIRST_VALUE", TOK_MODIFIER},
|
||||||
|
{"LAST_VALUE", TOK_MODIFIER},
|
||||||
|
{"SUBSTR", TOK_MODIFIER},
|
||||||
|
{"SUBSTRING", TOK_MODIFIER},
|
||||||
|
{"TRIM", TOK_MODIFIER},
|
||||||
|
{"UPPER", TOK_MODIFIER},
|
||||||
|
{"LOWER", TOK_MODIFIER},
|
||||||
|
{"LENGTH", TOK_MODIFIER},
|
||||||
|
{"CONCAT", TOK_MODIFIER},
|
||||||
|
{"REPLACE", TOK_MODIFIER},
|
||||||
|
{"ABS", TOK_MODIFIER},
|
||||||
|
{"ROUND", TOK_MODIFIER},
|
||||||
|
{"CEIL", TOK_MODIFIER},
|
||||||
|
{"FLOOR", TOK_MODIFIER},
|
||||||
|
{"NOW", TOK_MODIFIER},
|
||||||
|
{"EXTRACT", TOK_MODIFIER},
|
||||||
|
{"STRING_AGG", TOK_MODIFIER},
|
||||||
|
{"GROUP_CONCAT", TOK_MODIFIER},
|
||||||
|
{"ARRAY_AGG", TOK_MODIFIER},
|
||||||
|
{"GREATEST", TOK_MODIFIER},
|
||||||
|
{"LEAST", TOK_MODIFIER},
|
||||||
|
};
|
||||||
|
|
||||||
|
#define SQL_KEYWORD_COUNT (S32)(sizeof(sql_keywords) / sizeof(sql_keywords[0]))
|
||||||
|
|
||||||
|
// Case-insensitive keyword lookup
|
||||||
|
static Token_Type sql_lookup_keyword(const char *word, S32 len) {
|
||||||
|
for (S32 i = 0; i < SQL_KEYWORD_COUNT; i++) {
|
||||||
|
const char *kw = sql_keywords[i].word;
|
||||||
|
S32 kwlen = (S32)strlen(kw);
|
||||||
|
if (kwlen != len) continue;
|
||||||
|
B32 match = 1;
|
||||||
|
for (S32 j = 0; j < len; j++) {
|
||||||
|
if (toupper((unsigned char)word[j]) != (unsigned char)kw[j]) {
|
||||||
|
match = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (match) return sql_keywords[i].type;
|
||||||
|
}
|
||||||
|
return TOK_IDENTIFIER;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Character helpers
|
||||||
|
|
||||||
|
static B32 sql_is_ident_start(char c) {
|
||||||
|
return isalpha((unsigned char)c) || c == '_';
|
||||||
|
}
|
||||||
|
|
||||||
|
static B32 sql_is_ident_char(char c) {
|
||||||
|
return isalnum((unsigned char)c) || c == '_';
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Individual token parsers
|
||||||
|
|
||||||
|
static Token sql_parse_identifier(Tokenizer *tok) {
|
||||||
|
Token token;
|
||||||
|
token.start = (S32)(tok->start_t - tok->buf);
|
||||||
|
const char *begin = tok->t;
|
||||||
|
while (tok->t < tok->max_t && sql_is_ident_char(*tok->t))
|
||||||
|
tok->t++;
|
||||||
|
S32 len = (S32)(tok->t - begin);
|
||||||
|
token.type = sql_lookup_keyword(begin, len);
|
||||||
|
token.len = (S32)(tok->t - tok->start_t);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Token sql_parse_number(Tokenizer *tok) {
|
||||||
|
Token token;
|
||||||
|
token.start = (S32)(tok->start_t - tok->buf);
|
||||||
|
token.type = TOK_NUMBER;
|
||||||
|
|
||||||
|
while (tok->t < tok->max_t && isdigit((unsigned char)*tok->t))
|
||||||
|
tok->t++;
|
||||||
|
|
||||||
|
// Decimal part
|
||||||
|
if (tok->t < tok->max_t && *tok->t == '.') {
|
||||||
|
tok->t++;
|
||||||
|
while (tok->t < tok->max_t && isdigit((unsigned char)*tok->t))
|
||||||
|
tok->t++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exponent
|
||||||
|
if (tok->t < tok->max_t && (*tok->t == 'e' || *tok->t == 'E')) {
|
||||||
|
tok->t++;
|
||||||
|
if (tok->t < tok->max_t && (*tok->t == '+' || *tok->t == '-')) tok->t++;
|
||||||
|
while (tok->t < tok->max_t && isdigit((unsigned char)*tok->t)) tok->t++;
|
||||||
|
}
|
||||||
|
|
||||||
|
token.len = (S32)(tok->t - tok->start_t);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Token sql_parse_dot_number(Tokenizer *tok) {
|
||||||
|
Token token;
|
||||||
|
token.start = (S32)(tok->start_t - tok->buf);
|
||||||
|
token.type = TOK_NUMBER;
|
||||||
|
|
||||||
|
tok->t++; // skip '.'
|
||||||
|
while (tok->t < tok->max_t && isdigit((unsigned char)*tok->t)) tok->t++;
|
||||||
|
if (tok->t < tok->max_t && (*tok->t == 'e' || *tok->t == 'E')) {
|
||||||
|
tok->t++;
|
||||||
|
if (tok->t < tok->max_t && (*tok->t == '+' || *tok->t == '-')) tok->t++;
|
||||||
|
while (tok->t < tok->max_t && isdigit((unsigned char)*tok->t)) tok->t++;
|
||||||
|
}
|
||||||
|
|
||||||
|
token.len = (S32)(tok->t - tok->start_t);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Single-quoted string: 'text', with '' as escape for embedded quote
|
||||||
|
static Token sql_parse_string(Tokenizer *tok) {
|
||||||
|
Token token;
|
||||||
|
token.start = (S32)(tok->start_t - tok->buf);
|
||||||
|
token.type = TOK_STRING_LITERAL;
|
||||||
|
|
||||||
|
tok->t++; // skip opening '
|
||||||
|
while (tok->t < tok->max_t) {
|
||||||
|
if (*tok->t == '\'') {
|
||||||
|
tok->t++;
|
||||||
|
// '' is an escaped quote inside a string
|
||||||
|
if (tok->t < tok->max_t && *tok->t == '\'') {
|
||||||
|
tok->t++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tok->t++;
|
||||||
|
}
|
||||||
|
token.len = (S32)(tok->t - tok->start_t);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Double-quoted identifier: "column_name"
|
||||||
|
static Token sql_parse_quoted_identifier(Tokenizer *tok) {
|
||||||
|
Token token;
|
||||||
|
token.start = (S32)(tok->start_t - tok->buf);
|
||||||
|
token.type = TOK_IDENTIFIER;
|
||||||
|
|
||||||
|
tok->t++; // skip opening "
|
||||||
|
while (tok->t < tok->max_t) {
|
||||||
|
if (*tok->t == '"') {
|
||||||
|
tok->t++;
|
||||||
|
// "" is an escaped quote inside a quoted identifier
|
||||||
|
if (tok->t < tok->max_t && *tok->t == '"') {
|
||||||
|
tok->t++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tok->t++;
|
||||||
|
}
|
||||||
|
token.len = (S32)(tok->t - tok->start_t);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backtick-quoted identifier (MySQL): `column_name`
|
||||||
|
static Token sql_parse_backtick_identifier(Tokenizer *tok) {
|
||||||
|
Token token;
|
||||||
|
token.start = (S32)(tok->start_t - tok->buf);
|
||||||
|
token.type = TOK_IDENTIFIER;
|
||||||
|
|
||||||
|
tok->t++; // skip opening `
|
||||||
|
while (tok->t < tok->max_t) {
|
||||||
|
if (*tok->t == '`') { tok->t++; break; }
|
||||||
|
tok->t++;
|
||||||
|
}
|
||||||
|
token.len = (S32)(tok->t - tok->start_t);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Line comment: -- ...
|
||||||
|
static Token sql_parse_line_comment(Tokenizer *tok) {
|
||||||
|
Token token;
|
||||||
|
token.start = (S32)(tok->start_t - tok->buf);
|
||||||
|
token.type = TOK_COMMENT;
|
||||||
|
|
||||||
|
tok->t += 2; // skip '--'
|
||||||
|
while (tok->t < tok->max_t && *tok->t != '\n') tok->t++;
|
||||||
|
|
||||||
|
token.len = (S32)(tok->t - tok->start_t);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block comment: /* ... */
|
||||||
|
static Token sql_parse_block_comment(Tokenizer *tok) {
|
||||||
|
Token token;
|
||||||
|
token.start = (S32)(tok->start_t - tok->buf);
|
||||||
|
token.type = TOK_MULTILINE_COMMENT;
|
||||||
|
|
||||||
|
tok->t += 2; // skip '/*'
|
||||||
|
while (tok->t < tok->max_t) {
|
||||||
|
if (*tok->t == '*' && (tok->t + 1) < tok->max_t && *(tok->t + 1) == '/') {
|
||||||
|
tok->t += 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tok->t++;
|
||||||
|
}
|
||||||
|
|
||||||
|
token.len = (S32)(tok->t - tok->start_t);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
// MySQL # comment
|
||||||
|
static Token sql_parse_hash_comment(Tokenizer *tok) {
|
||||||
|
Token token;
|
||||||
|
token.start = (S32)(tok->start_t - tok->buf);
|
||||||
|
token.type = TOK_COMMENT;
|
||||||
|
|
||||||
|
tok->t++; // skip '#'
|
||||||
|
while (tok->t < tok->max_t && *tok->t != '\n') tok->t++;
|
||||||
|
|
||||||
|
token.len = (S32)(tok->t - tok->start_t);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variable / parameter: @var, @@var, :param, $1
|
||||||
|
static Token sql_parse_variable(Tokenizer *tok) {
|
||||||
|
Token token;
|
||||||
|
token.start = (S32)(tok->start_t - tok->buf);
|
||||||
|
token.type = TOK_DIRECTIVE;
|
||||||
|
|
||||||
|
char c = *tok->t;
|
||||||
|
tok->t++;
|
||||||
|
|
||||||
|
if (c == '@') {
|
||||||
|
// @@ for system variables
|
||||||
|
if (tok->t < tok->max_t && *tok->t == '@') tok->t++;
|
||||||
|
while (tok->t < tok->max_t && sql_is_ident_char(*tok->t)) tok->t++;
|
||||||
|
} else if (c == ':') {
|
||||||
|
while (tok->t < tok->max_t && sql_is_ident_char(*tok->t)) tok->t++;
|
||||||
|
} else if (c == '$') {
|
||||||
|
// $1, $2, ... (PostgreSQL positional params)
|
||||||
|
while (tok->t < tok->max_t && isdigit((unsigned char)*tok->t)) tok->t++;
|
||||||
|
// Also handle $tag$ dollar-quoted strings if no digits follow
|
||||||
|
if (tok->t == tok->start_t + 1) {
|
||||||
|
// Just a lone $, treat as punctuation
|
||||||
|
token.type = TOK_PUNCTUATION;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
token.len = (S32)(tok->t - tok->start_t);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Token sql_parse_operator(Tokenizer *tok) {
|
||||||
|
Token token;
|
||||||
|
token.start = (S32)(tok->start_t - tok->buf);
|
||||||
|
token.type = TOK_OPERATION;
|
||||||
|
|
||||||
|
char c = *tok->t;
|
||||||
|
tok->t++;
|
||||||
|
|
||||||
|
if (tok->t < tok->max_t) {
|
||||||
|
char n = *tok->t;
|
||||||
|
// Two-character operators
|
||||||
|
if ((c == '<' && n == '=') || (c == '>' && n == '=') ||
|
||||||
|
(c == '<' && n == '>') || (c == '!' && n == '=') ||
|
||||||
|
(c == '|' && n == '|') || (c == ':' && n == ':')) {
|
||||||
|
tok->t++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
token.len = (S32)(tok->t - tok->start_t);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Main get_next_token
|
||||||
|
|
||||||
|
static Token sql_get_next_token(Tokenizer *tok) {
|
||||||
|
tokenizer_eat_whitespace(tok);
|
||||||
|
|
||||||
|
Token token = {0};
|
||||||
|
token.start = (S32)(tok->t - tok->buf);
|
||||||
|
token.type = TOK_DEFAULT;
|
||||||
|
|
||||||
|
if (tok->t >= tok->max_t) {
|
||||||
|
token.len = 0;
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
tok->start_t = tok->t;
|
||||||
|
char c = *tok->t;
|
||||||
|
|
||||||
|
if (sql_is_ident_start(c)) {
|
||||||
|
return sql_parse_identifier(tok);
|
||||||
|
}
|
||||||
|
if (isdigit((unsigned char)c)) {
|
||||||
|
return sql_parse_number(tok);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (c) {
|
||||||
|
case '\'':
|
||||||
|
return sql_parse_string(tok);
|
||||||
|
|
||||||
|
case '"':
|
||||||
|
return sql_parse_quoted_identifier(tok);
|
||||||
|
|
||||||
|
case '`':
|
||||||
|
return sql_parse_backtick_identifier(tok);
|
||||||
|
|
||||||
|
case '-':
|
||||||
|
if ((tok->t + 1) < tok->max_t && *(tok->t + 1) == '-') {
|
||||||
|
return sql_parse_line_comment(tok);
|
||||||
|
}
|
||||||
|
token.type = TOK_OPERATION;
|
||||||
|
tok->t++;
|
||||||
|
token.len = 1;
|
||||||
|
return token;
|
||||||
|
|
||||||
|
case '/':
|
||||||
|
if ((tok->t + 1) < tok->max_t && *(tok->t + 1) == '*') {
|
||||||
|
return sql_parse_block_comment(tok);
|
||||||
|
}
|
||||||
|
token.type = TOK_OPERATION;
|
||||||
|
tok->t++;
|
||||||
|
token.len = 1;
|
||||||
|
return token;
|
||||||
|
|
||||||
|
case '#':
|
||||||
|
return sql_parse_hash_comment(tok);
|
||||||
|
|
||||||
|
case '.':
|
||||||
|
if ((tok->t + 1) < tok->max_t && isdigit((unsigned char)*(tok->t + 1))) {
|
||||||
|
return sql_parse_dot_number(tok);
|
||||||
|
}
|
||||||
|
token.type = TOK_PUNCTUATION;
|
||||||
|
tok->t++;
|
||||||
|
token.len = 1;
|
||||||
|
return token;
|
||||||
|
|
||||||
|
case '@': case '$':
|
||||||
|
return sql_parse_variable(tok);
|
||||||
|
|
||||||
|
case ':':
|
||||||
|
// :param or :: cast operator
|
||||||
|
if ((tok->t + 1) < tok->max_t) {
|
||||||
|
char n = *(tok->t + 1);
|
||||||
|
if (n == ':') return sql_parse_operator(tok);
|
||||||
|
if (sql_is_ident_start(n)) return sql_parse_variable(tok);
|
||||||
|
}
|
||||||
|
token.type = TOK_PUNCTUATION;
|
||||||
|
tok->t++;
|
||||||
|
token.len = 1;
|
||||||
|
return token;
|
||||||
|
|
||||||
|
// Punctuation
|
||||||
|
case ';': case ',':
|
||||||
|
case '{': case '}': case '(': case ')': case '[': case ']':
|
||||||
|
token.type = TOK_PUNCTUATION;
|
||||||
|
tok->t++;
|
||||||
|
token.len = 1;
|
||||||
|
return token;
|
||||||
|
|
||||||
|
// Operators
|
||||||
|
case '=': case '<': case '>': case '!':
|
||||||
|
case '+': case '*': case '%': case '&':
|
||||||
|
case '|': case '^': case '~':
|
||||||
|
return sql_parse_operator(tok);
|
||||||
|
|
||||||
|
default:
|
||||||
|
token.type = TOK_INVALID;
|
||||||
|
tok->t++;
|
||||||
|
token.len = 1;
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// tokenize_sql -- main entry point
|
||||||
|
|
||||||
|
static void tokenize_sql(const char *data, S32 length, U8 *out_tokens) {
|
||||||
|
memset(out_tokens, TOK_DEFAULT, length);
|
||||||
|
|
||||||
|
Tokenizer tok;
|
||||||
|
tokenizer_init(&tok, data, length);
|
||||||
|
|
||||||
|
Token prev = {0};
|
||||||
|
prev.type = TOK_DEFAULT;
|
||||||
|
|
||||||
|
while (tok.t < tok.max_t) {
|
||||||
|
Token token = sql_get_next_token(&tok);
|
||||||
|
if (token.len == 0) break;
|
||||||
|
|
||||||
|
paint_token(out_tokens, token.start, token.len, token.type);
|
||||||
|
|
||||||
|
// Retroactively mark identifier before '(' as a function
|
||||||
|
if (token.type == TOK_PUNCTUATION && token.len == 1 &&
|
||||||
|
data[token.start] == '(' && prev.type == TOK_IDENTIFIER) {
|
||||||
|
paint_token(out_tokens, prev.start, prev.len, TOK_FUNCTION);
|
||||||
|
}
|
||||||
|
|
||||||
|
prev = token;
|
||||||
|
}
|
||||||
|
}
|
||||||
502
c/lexer/lexer_theme.c
Normal file
502
c/lexer/lexer_theme.c
Normal file
@@ -0,0 +1,502 @@
|
|||||||
|
// lexer_theme.c -- Built-in color themes
|
||||||
|
//
|
||||||
|
// Colors ported from the Focus editor theme files.
|
||||||
|
// Hex values are RGB (alpha channel dropped, always opaque in terminal).
|
||||||
|
|
||||||
|
#include "lexer/lexer_theme.h"
|
||||||
|
|
||||||
|
S32 g_active_theme_idx = 0;
|
||||||
|
|
||||||
|
Theme g_themes[THEME_COUNT] = {
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Default
|
||||||
|
// Uses the terminal's own 256-color palette. No background override.
|
||||||
|
{
|
||||||
|
.name = "Default",
|
||||||
|
.use_truecolor = 0,
|
||||||
|
.colors = {
|
||||||
|
/* DEFAULT */ RGB_HEX(0xBDBDBD),
|
||||||
|
/* COMMENT */ RGB_HEX(0x6A6A6A),
|
||||||
|
/* MULTILINE_COMMENT */ RGB_HEX(0x6A6A6A),
|
||||||
|
/* STRING_LITERAL */ RGB_HEX(0x6AAF50),
|
||||||
|
/* CHAR_LITERAL */ RGB_HEX(0x6AAF50),
|
||||||
|
/* NUMBER */ RGB_HEX(0xC678DD),
|
||||||
|
/* IDENTIFIER */ RGB_HEX(0xBDBDBD),
|
||||||
|
/* FUNCTION */ RGB_HEX(0x56B6C2),
|
||||||
|
/* KEYWORD */ RGB_HEX(0xD19A66),
|
||||||
|
/* TYPE */ RGB_HEX(0x61AFEF),
|
||||||
|
/* VALUE */ RGB_HEX(0xC678DD),
|
||||||
|
/* MODIFIER */ RGB_HEX(0xE06C75),
|
||||||
|
/* DIRECTIVE */ RGB_HEX(0xE06C75),
|
||||||
|
/* PUNCTUATION */ RGB_HEX(0xBDBDBD),
|
||||||
|
/* OPERATION */ RGB_HEX(0xBDBDBD),
|
||||||
|
/* INVALID */ RGB_HEX(0xFF5555),
|
||||||
|
},
|
||||||
|
.ansi_colors = {
|
||||||
|
/* DEFAULT */ 7,
|
||||||
|
/* COMMENT */ 242,
|
||||||
|
/* MULTILINE_COMMENT */ 242,
|
||||||
|
/* STRING_LITERAL */ 2,
|
||||||
|
/* CHAR_LITERAL */ 2,
|
||||||
|
/* NUMBER */ 5,
|
||||||
|
/* IDENTIFIER */ 7,
|
||||||
|
/* FUNCTION */ 6,
|
||||||
|
/* KEYWORD */ 3,
|
||||||
|
/* TYPE */ 4,
|
||||||
|
/* VALUE */ 5,
|
||||||
|
/* MODIFIER */ 1,
|
||||||
|
/* DIRECTIVE */ 1,
|
||||||
|
/* PUNCTUATION */ 7,
|
||||||
|
/* OPERATION */ 7,
|
||||||
|
/* INVALID */ 9,
|
||||||
|
},
|
||||||
|
.background = RGB_HEX(0x1E1E1E),
|
||||||
|
.set_background = 0,
|
||||||
|
.status_bg = RGB_HEX(0x252525),
|
||||||
|
.status_fg = RGB_HEX(0xDDDDDD),
|
||||||
|
.status_fg_dim = RGB_HEX(0x888888),
|
||||||
|
.mb_bg = RGB_HEX(0x3D5A80),
|
||||||
|
.mb_fg = RGB_HEX(0xFFFFFF),
|
||||||
|
.mb_detail = RGB_HEX(0x888888),
|
||||||
|
},
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Default Light
|
||||||
|
// Uses the terminal's own 256-color palette with a light background.
|
||||||
|
{
|
||||||
|
.name = "Default Light",
|
||||||
|
.use_truecolor = 0,
|
||||||
|
.colors = {
|
||||||
|
/* DEFAULT */ RGB_HEX(0x383A42),
|
||||||
|
/* COMMENT */ RGB_HEX(0xA0A1A7),
|
||||||
|
/* MULTILINE_COMMENT */ RGB_HEX(0xA0A1A7),
|
||||||
|
/* STRING_LITERAL */ RGB_HEX(0xA62C21),
|
||||||
|
/* CHAR_LITERAL */ RGB_HEX(0xA62C21),
|
||||||
|
/* NUMBER */ RGB_HEX(0x986801),
|
||||||
|
/* IDENTIFIER */ RGB_HEX(0x383A42),
|
||||||
|
/* FUNCTION */ RGB_HEX(0x0184BC),
|
||||||
|
/* KEYWORD */ RGB_HEX(0x4078F2),
|
||||||
|
/* TYPE */ RGB_HEX(0x50A14F),
|
||||||
|
/* VALUE */ RGB_HEX(0x986801),
|
||||||
|
/* MODIFIER */ RGB_HEX(0x4078F2),
|
||||||
|
/* DIRECTIVE */ RGB_HEX(0xA62C21),
|
||||||
|
/* PUNCTUATION */ RGB_HEX(0x383A42),
|
||||||
|
/* OPERATION */ RGB_HEX(0x383A42),
|
||||||
|
/* INVALID */ RGB_HEX(0xFF0000),
|
||||||
|
},
|
||||||
|
.ansi_colors = {
|
||||||
|
/* DEFAULT */ 0, // black
|
||||||
|
/* COMMENT */ 243, // dark gray
|
||||||
|
/* MULTILINE_COMMENT */ 243,
|
||||||
|
/* STRING_LITERAL */ 1, // red
|
||||||
|
/* CHAR_LITERAL */ 1,
|
||||||
|
/* NUMBER */ 5, // magenta
|
||||||
|
/* IDENTIFIER */ 0, // black
|
||||||
|
/* FUNCTION */ 6, // cyan
|
||||||
|
/* KEYWORD */ 4, // blue
|
||||||
|
/* TYPE */ 2, // green
|
||||||
|
/* VALUE */ 5, // magenta
|
||||||
|
/* MODIFIER */ 4, // blue
|
||||||
|
/* DIRECTIVE */ 1, // red
|
||||||
|
/* PUNCTUATION */ 0, // black
|
||||||
|
/* OPERATION */ 0, // black
|
||||||
|
/* INVALID */ 9, // bright red
|
||||||
|
},
|
||||||
|
.background = RGB_HEX(0xFAFAFA),
|
||||||
|
.set_background = 0,
|
||||||
|
.status_bg = RGB_HEX(0xD0D0D0),
|
||||||
|
.status_fg = RGB_HEX(0x000000),
|
||||||
|
.status_fg_dim = RGB_HEX(0x606060),
|
||||||
|
.mb_bg = RGB_HEX(0x0060C0),
|
||||||
|
.mb_fg = RGB_HEX(0xFFFFFF),
|
||||||
|
.mb_detail = RGB_HEX(0x808080),
|
||||||
|
},
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Focus
|
||||||
|
// The default Focus editor theme.
|
||||||
|
{
|
||||||
|
.name = "Focus",
|
||||||
|
.use_truecolor = 1,
|
||||||
|
.colors = {
|
||||||
|
/* DEFAULT */ RGB_HEX(0xBFC9DB),
|
||||||
|
/* COMMENT */ RGB_HEX(0x87919D),
|
||||||
|
/* MULTILINE_COMMENT */ RGB_HEX(0x87919D),
|
||||||
|
/* STRING_LITERAL */ RGB_HEX(0xD4BC7D),
|
||||||
|
/* CHAR_LITERAL */ RGB_HEX(0xD4BC7D),
|
||||||
|
/* NUMBER */ RGB_HEX(0xD699B5),
|
||||||
|
/* IDENTIFIER */ RGB_HEX(0xBFC9DB),
|
||||||
|
/* FUNCTION */ RGB_HEX(0xD0C5A9),
|
||||||
|
/* KEYWORD */ RGB_HEX(0xE67D74),
|
||||||
|
/* TYPE */ RGB_HEX(0x82AAA3),
|
||||||
|
/* VALUE */ RGB_HEX(0xD699B5),
|
||||||
|
/* MODIFIER */ RGB_HEX(0xE67D74),
|
||||||
|
/* DIRECTIVE */ RGB_HEX(0xE67D74),
|
||||||
|
/* PUNCTUATION */ RGB_HEX(0xBFC9DB),
|
||||||
|
/* OPERATION */ RGB_HEX(0xE0AD82),
|
||||||
|
/* INVALID */ RGB_HEX(0xFF0000),
|
||||||
|
},
|
||||||
|
.ansi_colors = {},
|
||||||
|
.background = RGB_HEX(0x15212A),
|
||||||
|
.set_background = 1,
|
||||||
|
.status_bg = RGB_HEX(0x0C1620),
|
||||||
|
.status_fg = RGB_HEX(0xBFC9DB),
|
||||||
|
.status_fg_dim = RGB_HEX(0x607080),
|
||||||
|
.mb_bg = RGB_HEX(0x1C3040),
|
||||||
|
.mb_fg = RGB_HEX(0xBFC9DB),
|
||||||
|
.mb_detail = RGB_HEX(0x607080),
|
||||||
|
},
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Handmade Hero
|
||||||
|
// Based on Casey Muratori's emacs theme from the Handmade Hero series.
|
||||||
|
{
|
||||||
|
.name = "Handmade Hero",
|
||||||
|
.use_truecolor = 1,
|
||||||
|
.colors = {
|
||||||
|
/* DEFAULT */ RGB_HEX(0xCDAA7D),
|
||||||
|
/* COMMENT */ RGB_HEX(0x7F7F7F),
|
||||||
|
/* MULTILINE_COMMENT */ RGB_HEX(0x87919D),
|
||||||
|
/* STRING_LITERAL */ RGB_HEX(0x6B8E23),
|
||||||
|
/* CHAR_LITERAL */ RGB_HEX(0x6B8E23),
|
||||||
|
/* NUMBER */ RGB_HEX(0xD699B5),
|
||||||
|
/* IDENTIFIER */ RGB_HEX(0xBFC9DB),
|
||||||
|
/* FUNCTION */ RGB_HEX(0xCDAA7D),
|
||||||
|
/* KEYWORD */ RGB_HEX(0xB8860B),
|
||||||
|
/* TYPE */ RGB_HEX(0xB8860B),
|
||||||
|
/* VALUE */ RGB_HEX(0x6B8E23),
|
||||||
|
/* MODIFIER */ RGB_HEX(0xE67D74),
|
||||||
|
/* DIRECTIVE */ RGB_HEX(0xE67D74),
|
||||||
|
/* PUNCTUATION */ RGB_HEX(0xCDAA7D),
|
||||||
|
/* OPERATION */ RGB_HEX(0xCDAA7D),
|
||||||
|
/* INVALID */ RGB_HEX(0xFF0000),
|
||||||
|
},
|
||||||
|
.ansi_colors = {},
|
||||||
|
.background = RGB_HEX(0x161616),
|
||||||
|
.set_background = 1,
|
||||||
|
.status_bg = RGB_HEX(0x0C0C0C),
|
||||||
|
.status_fg = RGB_HEX(0xCDAA7D),
|
||||||
|
.status_fg_dim = RGB_HEX(0x6B5A3F),
|
||||||
|
.mb_bg = RGB_HEX(0x403020),
|
||||||
|
.mb_fg = RGB_HEX(0xCDAA7D),
|
||||||
|
.mb_detail = RGB_HEX(0x6B5A3F),
|
||||||
|
},
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Witness Classic
|
||||||
|
// Jonathan Blow's classic color scheme.
|
||||||
|
{
|
||||||
|
.name = "Witness Classic",
|
||||||
|
.use_truecolor = 1,
|
||||||
|
.colors = {
|
||||||
|
/* DEFAULT */ RGB_HEX(0xD3B58D),
|
||||||
|
/* COMMENT */ RGB_HEX(0xFFFF00),
|
||||||
|
/* MULTILINE_COMMENT */ RGB_HEX(0x87919D),
|
||||||
|
/* STRING_LITERAL */ RGB_HEX(0xBEBEBE),
|
||||||
|
/* CHAR_LITERAL */ RGB_HEX(0xBEBEBE),
|
||||||
|
/* NUMBER */ RGB_HEX(0xD699B5),
|
||||||
|
/* IDENTIFIER */ RGB_HEX(0xBFC9DB),
|
||||||
|
/* FUNCTION */ RGB_HEX(0xD3B58D),
|
||||||
|
/* KEYWORD */ RGB_HEX(0xFFFFFF),
|
||||||
|
/* TYPE */ RGB_HEX(0x98FB98),
|
||||||
|
/* VALUE */ RGB_HEX(0x7FFFD4),
|
||||||
|
/* MODIFIER */ RGB_HEX(0xE67D74),
|
||||||
|
/* DIRECTIVE */ RGB_HEX(0xE67D74),
|
||||||
|
/* PUNCTUATION */ RGB_HEX(0xD3B58D),
|
||||||
|
/* OPERATION */ RGB_HEX(0xD3B58D),
|
||||||
|
/* INVALID */ RGB_HEX(0xFF0000),
|
||||||
|
},
|
||||||
|
.ansi_colors = {},
|
||||||
|
.background = RGB_HEX(0x292929),
|
||||||
|
.set_background = 1,
|
||||||
|
.status_bg = RGB_HEX(0x222222),
|
||||||
|
.status_fg = RGB_HEX(0xDDDDDD),
|
||||||
|
.status_fg_dim = RGB_HEX(0x888888),
|
||||||
|
.mb_bg = RGB_HEX(0x3A3A50),
|
||||||
|
.mb_fg = RGB_HEX(0xFFFFFF),
|
||||||
|
.mb_detail = RGB_HEX(0x888888),
|
||||||
|
},
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Witness
|
||||||
|
// Jonathan Blow's alternative dark-teal theme.
|
||||||
|
{
|
||||||
|
.name = "Witness",
|
||||||
|
.use_truecolor = 1,
|
||||||
|
.colors = {
|
||||||
|
/* DEFAULT */ RGB_HEX(0xD3B58D),
|
||||||
|
/* COMMENT */ RGB_HEX(0x3DDF23),
|
||||||
|
/* MULTILINE_COMMENT */ RGB_HEX(0x87919D),
|
||||||
|
/* STRING_LITERAL */ RGB_HEX(0x0FDFAF),
|
||||||
|
/* CHAR_LITERAL */ RGB_HEX(0x0FDFAF),
|
||||||
|
/* NUMBER */ RGB_HEX(0xD699B5),
|
||||||
|
/* IDENTIFIER */ RGB_HEX(0xBFC9DB),
|
||||||
|
/* FUNCTION */ RGB_HEX(0xD3B58D),
|
||||||
|
/* KEYWORD */ RGB_HEX(0xFFFFFF),
|
||||||
|
/* TYPE */ RGB_HEX(0x98FB98),
|
||||||
|
/* VALUE */ RGB_HEX(0x7FFFD4),
|
||||||
|
/* MODIFIER */ RGB_HEX(0xE67D74),
|
||||||
|
/* DIRECTIVE */ RGB_HEX(0xE67D74),
|
||||||
|
/* PUNCTUATION */ RGB_HEX(0xD3B58D),
|
||||||
|
/* OPERATION */ RGB_HEX(0xE0AD82),
|
||||||
|
/* INVALID */ RGB_HEX(0xFF0000),
|
||||||
|
},
|
||||||
|
.ansi_colors = {},
|
||||||
|
.background = RGB_HEX(0x072626),
|
||||||
|
.set_background = 1,
|
||||||
|
.status_bg = RGB_HEX(0x041818),
|
||||||
|
.status_fg = RGB_HEX(0xD3B58D),
|
||||||
|
.status_fg_dim = RGB_HEX(0x6B7A6B),
|
||||||
|
.mb_bg = RGB_HEX(0x0E3A3A),
|
||||||
|
.mb_fg = RGB_HEX(0xD3B58D),
|
||||||
|
.mb_detail = RGB_HEX(0x6B7A6B),
|
||||||
|
},
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Visual Studio Classic
|
||||||
|
// Light theme matching Visual Studio 6.0 (1998).
|
||||||
|
{
|
||||||
|
.name = "VS Classic",
|
||||||
|
.use_truecolor = 1,
|
||||||
|
.colors = {
|
||||||
|
/* DEFAULT */ RGB_HEX(0x000000),
|
||||||
|
/* COMMENT */ RGB_HEX(0x008000),
|
||||||
|
/* MULTILINE_COMMENT */ RGB_HEX(0x008000),
|
||||||
|
/* STRING_LITERAL */ RGB_HEX(0x800000),
|
||||||
|
/* CHAR_LITERAL */ RGB_HEX(0x800000),
|
||||||
|
/* NUMBER */ RGB_HEX(0x000000),
|
||||||
|
/* IDENTIFIER */ RGB_HEX(0x000000),
|
||||||
|
/* FUNCTION */ RGB_HEX(0x000000),
|
||||||
|
/* KEYWORD */ RGB_HEX(0x0000FF),
|
||||||
|
/* TYPE */ RGB_HEX(0x0000FF),
|
||||||
|
/* VALUE */ RGB_HEX(0x0000FF),
|
||||||
|
/* MODIFIER */ RGB_HEX(0x0000FF),
|
||||||
|
/* DIRECTIVE */ RGB_HEX(0x0000FF),
|
||||||
|
/* PUNCTUATION */ RGB_HEX(0x000000),
|
||||||
|
/* OPERATION */ RGB_HEX(0x000000),
|
||||||
|
/* INVALID */ RGB_HEX(0xFF0000),
|
||||||
|
},
|
||||||
|
.ansi_colors = {},
|
||||||
|
.background = RGB_HEX(0xFFFFFF),
|
||||||
|
.set_background = 1,
|
||||||
|
.status_bg = RGB_HEX(0xD4D0C8),
|
||||||
|
.status_fg = RGB_HEX(0x000000),
|
||||||
|
.status_fg_dim = RGB_HEX(0x606060),
|
||||||
|
.mb_bg = RGB_HEX(0x0A246A),
|
||||||
|
.mb_fg = RGB_HEX(0xFFFFFF),
|
||||||
|
.mb_detail = RGB_HEX(0xA0A0A0),
|
||||||
|
.fb_bg = RGB_HEX(0xFFFFFF),
|
||||||
|
.set_fb_bg = 1,
|
||||||
|
},
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// RAD Debugger
|
||||||
|
// Default color scheme from the RAD Debugger project.
|
||||||
|
{
|
||||||
|
.name = "RAD Debugger",
|
||||||
|
.use_truecolor = 1,
|
||||||
|
.colors = {
|
||||||
|
/* DEFAULT */ RGB_HEX(0xDADADA),
|
||||||
|
/* COMMENT */ RGB_HEX(0x5D7856),
|
||||||
|
/* MULTILINE_COMMENT */ RGB_HEX(0x5D7856),
|
||||||
|
/* STRING_LITERAL */ RGB_HEX(0xFFA070),
|
||||||
|
/* CHAR_LITERAL */ RGB_HEX(0xFFA070),
|
||||||
|
/* NUMBER */ RGB_HEX(0xD0D0A0),
|
||||||
|
/* IDENTIFIER */ RGB_HEX(0xDADADA),
|
||||||
|
/* FUNCTION */ RGB_HEX(0xDADADA),
|
||||||
|
/* KEYWORD */ RGB_HEX(0xF0C674),
|
||||||
|
/* TYPE */ RGB_HEX(0x70C0B0),
|
||||||
|
/* VALUE */ RGB_HEX(0xD0D0A0),
|
||||||
|
/* MODIFIER */ RGB_HEX(0xF0C674),
|
||||||
|
/* DIRECTIVE */ RGB_HEX(0xCC7070),
|
||||||
|
/* PUNCTUATION */ RGB_HEX(0xDADADA),
|
||||||
|
/* OPERATION */ RGB_HEX(0xDADADA),
|
||||||
|
/* INVALID */ RGB_HEX(0xFF0000),
|
||||||
|
},
|
||||||
|
.ansi_colors = {},
|
||||||
|
.background = RGB_HEX(0x1C1C1C),
|
||||||
|
.set_background = 1,
|
||||||
|
.status_bg = RGB_HEX(0x121212),
|
||||||
|
.status_fg = RGB_HEX(0xDADADA),
|
||||||
|
.status_fg_dim = RGB_HEX(0x707070),
|
||||||
|
.mb_bg = RGB_HEX(0x3A2A1A),
|
||||||
|
.mb_fg = RGB_HEX(0xDADADA),
|
||||||
|
.mb_detail = RGB_HEX(0x707070),
|
||||||
|
},
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// 4coder
|
||||||
|
// Default theme from Allen Webster's 4coder editor.
|
||||||
|
{
|
||||||
|
.name = "4coder",
|
||||||
|
.use_truecolor = 1,
|
||||||
|
.colors = {
|
||||||
|
/* DEFAULT */ RGB_HEX(0x90B080),
|
||||||
|
/* COMMENT */ RGB_HEX(0x2090F0),
|
||||||
|
/* MULTILINE_COMMENT */ RGB_HEX(0x2090F0),
|
||||||
|
/* STRING_LITERAL */ RGB_HEX(0x50FF30),
|
||||||
|
/* CHAR_LITERAL */ RGB_HEX(0x50FF30),
|
||||||
|
/* NUMBER */ RGB_HEX(0x50FF30),
|
||||||
|
/* IDENTIFIER */ RGB_HEX(0x90B080),
|
||||||
|
/* FUNCTION */ RGB_HEX(0x90B080),
|
||||||
|
/* KEYWORD */ RGB_HEX(0xD08F20),
|
||||||
|
/* TYPE */ RGB_HEX(0xD08F20),
|
||||||
|
/* VALUE */ RGB_HEX(0x50FF30),
|
||||||
|
/* MODIFIER */ RGB_HEX(0xD08F20),
|
||||||
|
/* DIRECTIVE */ RGB_HEX(0xFF5F5F),
|
||||||
|
/* PUNCTUATION */ RGB_HEX(0x90B080),
|
||||||
|
/* OPERATION */ RGB_HEX(0x90B080),
|
||||||
|
/* INVALID */ RGB_HEX(0xFF0000),
|
||||||
|
},
|
||||||
|
.ansi_colors = {},
|
||||||
|
.background = RGB_HEX(0x0C0C0C),
|
||||||
|
.set_background = 1,
|
||||||
|
.status_bg = RGB_HEX(0x1A1A1A),
|
||||||
|
.status_fg = RGB_HEX(0x90B080),
|
||||||
|
.status_fg_dim = RGB_HEX(0x506050),
|
||||||
|
.mb_bg = RGB_HEX(0x2A3A20),
|
||||||
|
.mb_fg = RGB_HEX(0x90B080),
|
||||||
|
.mb_detail = RGB_HEX(0x606060),
|
||||||
|
},
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Ryan Fleury
|
||||||
|
// Ryan Fleury's personal theme, ported from the fleury-theme.el emacs package.
|
||||||
|
{
|
||||||
|
.name = "Ryan Fleury",
|
||||||
|
.use_truecolor = 1,
|
||||||
|
.colors = {
|
||||||
|
/* DEFAULT */ RGB_HEX(0xB99468),
|
||||||
|
/* COMMENT */ RGB_HEX(0x666666),
|
||||||
|
/* MULTILINE_COMMENT */ RGB_HEX(0x666666),
|
||||||
|
/* STRING_LITERAL */ RGB_HEX(0xFFAA00),
|
||||||
|
/* CHAR_LITERAL */ RGB_HEX(0xFFAA00),
|
||||||
|
/* NUMBER */ RGB_HEX(0xFFAA00),
|
||||||
|
/* IDENTIFIER */ RGB_HEX(0xB99468),
|
||||||
|
/* FUNCTION */ RGB_HEX(0xDE451F),
|
||||||
|
/* KEYWORD */ RGB_HEX(0xF0C674),
|
||||||
|
/* TYPE */ RGB_HEX(0xEDB211),
|
||||||
|
/* VALUE */ RGB_HEX(0xFFAA00),
|
||||||
|
/* MODIFIER */ RGB_HEX(0xF0C674),
|
||||||
|
/* DIRECTIVE */ RGB_HEX(0xDC7575),
|
||||||
|
/* PUNCTUATION */ RGB_HEX(0xB99468),
|
||||||
|
/* OPERATION */ RGB_HEX(0xB99468),
|
||||||
|
/* INVALID */ RGB_HEX(0xFF0000),
|
||||||
|
},
|
||||||
|
.ansi_colors = {},
|
||||||
|
.background = RGB_HEX(0x020202),
|
||||||
|
.set_background = 1,
|
||||||
|
.status_bg = RGB_HEX(0x1A120B),
|
||||||
|
.status_fg = RGB_HEX(0xE7AA4D),
|
||||||
|
.status_fg_dim = RGB_HEX(0x63523D),
|
||||||
|
.mb_bg = RGB_HEX(0x3A2510),
|
||||||
|
.mb_fg = RGB_HEX(0xE7AA4D),
|
||||||
|
.mb_detail = RGB_HEX(0x63523D),
|
||||||
|
},
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Gruber Darker
|
||||||
|
// Gruber Darker theme by Alexey Kutepov (rexim).
|
||||||
|
{
|
||||||
|
.name = "Gruber Darker",
|
||||||
|
.use_truecolor = 1,
|
||||||
|
.colors = {
|
||||||
|
/* DEFAULT */ RGB_HEX(0xE4E4EF),
|
||||||
|
/* COMMENT */ RGB_HEX(0x565F73),
|
||||||
|
/* MULTILINE_COMMENT */ RGB_HEX(0x565F73),
|
||||||
|
/* STRING_LITERAL */ RGB_HEX(0x73C936),
|
||||||
|
/* CHAR_LITERAL */ RGB_HEX(0x73C936),
|
||||||
|
/* NUMBER */ RGB_HEX(0xFFDD33),
|
||||||
|
/* IDENTIFIER */ RGB_HEX(0xE4E4EF),
|
||||||
|
/* FUNCTION */ RGB_HEX(0x96A6C8),
|
||||||
|
/* KEYWORD */ RGB_HEX(0xFFDD33),
|
||||||
|
/* TYPE */ RGB_HEX(0x96A6C8),
|
||||||
|
/* VALUE */ RGB_HEX(0xFFDD33),
|
||||||
|
/* MODIFIER */ RGB_HEX(0x9E95C7),
|
||||||
|
/* DIRECTIVE */ RGB_HEX(0x9E95C7),
|
||||||
|
/* PUNCTUATION */ RGB_HEX(0xE4E4EF),
|
||||||
|
/* OPERATION */ RGB_HEX(0xE4E4EF),
|
||||||
|
/* INVALID */ RGB_HEX(0xF43841),
|
||||||
|
},
|
||||||
|
.ansi_colors = {},
|
||||||
|
.background = RGB_HEX(0x181818),
|
||||||
|
.set_background = 1,
|
||||||
|
.status_bg = RGB_HEX(0x101010),
|
||||||
|
.status_fg = RGB_HEX(0xE4E4EF),
|
||||||
|
.status_fg_dim = RGB_HEX(0x565F73),
|
||||||
|
.mb_bg = RGB_HEX(0x282828),
|
||||||
|
.mb_fg = RGB_HEX(0xE4E4EF),
|
||||||
|
.mb_detail = RGB_HEX(0x95A99F),
|
||||||
|
},
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// VS Dark
|
||||||
|
// Visual Studio Dark theme.
|
||||||
|
{
|
||||||
|
.name = "VS Dark",
|
||||||
|
.use_truecolor = 1,
|
||||||
|
.colors = {
|
||||||
|
/* DEFAULT */ RGB_HEX(0xDCDCAA),
|
||||||
|
/* COMMENT */ RGB_HEX(0x6A9955),
|
||||||
|
/* MULTILINE_COMMENT */ RGB_HEX(0x6A9955),
|
||||||
|
/* STRING_LITERAL */ RGB_HEX(0xCE9178),
|
||||||
|
/* CHAR_LITERAL */ RGB_HEX(0xCE9178),
|
||||||
|
/* NUMBER */ RGB_HEX(0xB5CEA8),
|
||||||
|
/* IDENTIFIER */ RGB_HEX(0x9CDCFE),
|
||||||
|
/* FUNCTION */ RGB_HEX(0xDCDCAA),
|
||||||
|
/* KEYWORD */ RGB_HEX(0x569CD6),
|
||||||
|
/* TYPE */ RGB_HEX(0x4EC9B0),
|
||||||
|
/* VALUE */ RGB_HEX(0x569CD6),
|
||||||
|
/* MODIFIER */ RGB_HEX(0x569CD6),
|
||||||
|
/* DIRECTIVE */ RGB_HEX(0xC586C0),
|
||||||
|
/* PUNCTUATION */ RGB_HEX(0xD4D4D4),
|
||||||
|
/* OPERATION */ RGB_HEX(0xD4D4D4),
|
||||||
|
/* INVALID */ RGB_HEX(0xFF0000),
|
||||||
|
},
|
||||||
|
.ansi_colors = {},
|
||||||
|
.background = RGB_HEX(0x1E1E1E),
|
||||||
|
.set_background = 1,
|
||||||
|
.status_bg = RGB_HEX(0x252526),
|
||||||
|
.status_fg = RGB_HEX(0xCCCCCC),
|
||||||
|
.status_fg_dim = RGB_HEX(0x808080),
|
||||||
|
.mb_bg = RGB_HEX(0x094771),
|
||||||
|
.mb_fg = RGB_HEX(0xFFFFFF),
|
||||||
|
.mb_detail = RGB_HEX(0x808080),
|
||||||
|
},
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Freshcut Contrast
|
||||||
|
// High-contrast dark theme by Dayle Rees.
|
||||||
|
{
|
||||||
|
.name = "Freshcut Contrast",
|
||||||
|
.use_truecolor = 1,
|
||||||
|
.colors = {
|
||||||
|
/* DEFAULT */ RGB_HEX(0xF8F8F2),
|
||||||
|
/* COMMENT */ RGB_HEX(0x737B84),
|
||||||
|
/* MULTILINE_COMMENT */ RGB_HEX(0x737B84),
|
||||||
|
/* STRING_LITERAL */ RGB_HEX(0xE9EE00),
|
||||||
|
/* CHAR_LITERAL */ RGB_HEX(0xE9EE00),
|
||||||
|
/* NUMBER */ RGB_HEX(0x8FBE00),
|
||||||
|
/* IDENTIFIER */ RGB_HEX(0xF8F8F2),
|
||||||
|
/* FUNCTION */ RGB_HEX(0xAEE239),
|
||||||
|
/* KEYWORD */ RGB_HEX(0xC8D7E8),
|
||||||
|
/* TYPE */ RGB_HEX(0x4ECDC4),
|
||||||
|
/* VALUE */ RGB_HEX(0x8FBE00),
|
||||||
|
/* MODIFIER */ RGB_HEX(0x00A8C6),
|
||||||
|
/* DIRECTIVE */ RGB_HEX(0x00A8C6),
|
||||||
|
/* PUNCTUATION */ RGB_HEX(0xF8F8F2),
|
||||||
|
/* OPERATION */ RGB_HEX(0xF8F8F2),
|
||||||
|
/* INVALID */ RGB_HEX(0xFF0000),
|
||||||
|
},
|
||||||
|
.ansi_colors = {},
|
||||||
|
.background = RGB_HEX(0x000000),
|
||||||
|
.set_background = 1,
|
||||||
|
.status_bg = RGB_HEX(0x3C3C3C),
|
||||||
|
.status_fg = RGB_HEX(0xE0E0E0),
|
||||||
|
.status_fg_dim = RGB_HEX(0x808080),
|
||||||
|
.mb_bg = RGB_HEX(0x505050),
|
||||||
|
.mb_fg = RGB_HEX(0xFFFFFF),
|
||||||
|
.mb_detail = RGB_HEX(0x909090),
|
||||||
|
},
|
||||||
|
};
|
||||||
50
c/lexer/lexer_theme.h
Normal file
50
c/lexer/lexer_theme.h
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
#pragma once
|
||||||
|
// lexer_theme.h -- Color themes for syntax highlighting
|
||||||
|
//
|
||||||
|
// Each theme maps Token_Type values to 24-bit RGB colors, plus a background.
|
||||||
|
// Themes use true-color ANSI escapes (supported by Windows Terminal, most
|
||||||
|
// modern terminals).
|
||||||
|
|
||||||
|
#include "base/base_core.h"
|
||||||
|
#include "lexer/lexer.h"
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// RGB color
|
||||||
|
|
||||||
|
typedef struct RGB { U8 r, g, b; } RGB;
|
||||||
|
|
||||||
|
// Use for static initializers (no compound literal — MSVC C11 compat)
|
||||||
|
#define RGB_HEX(hex) { ((hex) >> 16) & 0xFF, ((hex) >> 8) & 0xFF, (hex) & 0xFF }
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Theme
|
||||||
|
|
||||||
|
typedef struct Theme {
|
||||||
|
const char *name;
|
||||||
|
B32 use_truecolor; // if false, use ansi_colors[] with term_fg() instead
|
||||||
|
RGB colors[TOK_COUNT]; // foreground color per token type (true color)
|
||||||
|
U8 ansi_colors[TOK_COUNT]; // foreground per token type (256-color fallback)
|
||||||
|
RGB background; // terminal background color
|
||||||
|
B32 set_background; // whether to override terminal background
|
||||||
|
|
||||||
|
// UI chrome colors
|
||||||
|
RGB status_bg; // status bar background
|
||||||
|
RGB status_fg; // status bar foreground (focused)
|
||||||
|
RGB status_fg_dim; // status bar foreground (unfocused)
|
||||||
|
RGB mb_bg; // minibuffer item background (highlighted)
|
||||||
|
RGB mb_fg; // minibuffer item foreground (highlighted)
|
||||||
|
RGB mb_detail; // minibuffer detail text color
|
||||||
|
RGB fb_bg; // file browser background (0,0,0 = use WindowBg)
|
||||||
|
B32 set_fb_bg; // whether to override file browser background
|
||||||
|
} Theme;
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Built-in themes
|
||||||
|
|
||||||
|
#define THEME_COUNT 13
|
||||||
|
|
||||||
|
extern Theme g_themes[THEME_COUNT];
|
||||||
|
extern S32 g_active_theme_idx;
|
||||||
|
|
||||||
|
// Convenience: pointer to the active theme
|
||||||
|
static inline Theme *theme_active(void) { return &g_themes[g_active_theme_idx]; }
|
||||||
130
c/platform/platform.h
Normal file
130
c/platform/platform.h
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
#pragma once
|
||||||
|
// platform.h — GUI windowing, input, and system services
|
||||||
|
//
|
||||||
|
// Provides window creation, keyboard/mouse input, clipboard access,
|
||||||
|
// and process spawning. Implementation: platform_win32.c.
|
||||||
|
|
||||||
|
#include "base/base_core.h"
|
||||||
|
#include "base/base_math.h"
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Key codes (matching Windows VK codes)
|
||||||
|
|
||||||
|
enum {
|
||||||
|
PKEY_BACKSPACE = 0x08,
|
||||||
|
PKEY_TAB = 0x09,
|
||||||
|
PKEY_RETURN = 0x0D,
|
||||||
|
PKEY_ESCAPE = 0x1B,
|
||||||
|
PKEY_PAGEUP = 0x21,
|
||||||
|
PKEY_PAGEDOWN = 0x22,
|
||||||
|
PKEY_END = 0x23,
|
||||||
|
PKEY_HOME = 0x24,
|
||||||
|
PKEY_LEFT = 0x25,
|
||||||
|
PKEY_UP = 0x26,
|
||||||
|
PKEY_RIGHT = 0x27,
|
||||||
|
PKEY_DOWN = 0x28,
|
||||||
|
PKEY_DELETE = 0x2E,
|
||||||
|
PKEY_0 = 0x30,
|
||||||
|
PKEY_1 = 0x31,
|
||||||
|
PKEY_2 = 0x32,
|
||||||
|
PKEY_3 = 0x33,
|
||||||
|
PKEY_A = 0x41,
|
||||||
|
PKEY_B = 0x42,
|
||||||
|
PKEY_C = 0x43,
|
||||||
|
PKEY_E = 0x45,
|
||||||
|
PKEY_F = 0x46,
|
||||||
|
PKEY_G = 0x47,
|
||||||
|
PKEY_H = 0x48,
|
||||||
|
PKEY_J = 0x4A,
|
||||||
|
PKEY_K = 0x4B,
|
||||||
|
PKEY_L = 0x4C,
|
||||||
|
PKEY_N = 0x4E,
|
||||||
|
PKEY_O = 0x4F,
|
||||||
|
PKEY_P = 0x50,
|
||||||
|
PKEY_Q = 0x51,
|
||||||
|
PKEY_S = 0x53,
|
||||||
|
PKEY_V = 0x56,
|
||||||
|
PKEY_W = 0x57,
|
||||||
|
PKEY_X = 0x58,
|
||||||
|
PKEY_Y = 0x59,
|
||||||
|
PKEY_Z = 0x5A,
|
||||||
|
PKEY_F4 = 0x73,
|
||||||
|
PKEY_F8 = 0x77,
|
||||||
|
PKEY_EQUAL = 0xBB,
|
||||||
|
PKEY_MINUS = 0xBD,
|
||||||
|
PKEY_BACKSLASH = 0xDC,
|
||||||
|
};
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Input accumulated per frame
|
||||||
|
|
||||||
|
#define PLATFORM_MAX_CHARS_PER_FRAME 64
|
||||||
|
#define PLATFORM_MAX_KEYS_PER_FRAME 32
|
||||||
|
|
||||||
|
// Editor-facing input (ASCII chars + key codes + modifiers)
|
||||||
|
typedef struct PlatformInput {
|
||||||
|
char chars[PLATFORM_MAX_CHARS_PER_FRAME];
|
||||||
|
S32 char_count;
|
||||||
|
|
||||||
|
U8 keys[PLATFORM_MAX_KEYS_PER_FRAME];
|
||||||
|
S32 key_count;
|
||||||
|
|
||||||
|
B32 ctrl_held;
|
||||||
|
B32 shift_held;
|
||||||
|
B32 alt_held;
|
||||||
|
} PlatformInput;
|
||||||
|
|
||||||
|
// Raw windowing input (UTF-16 chars + VK codes + mouse)
|
||||||
|
typedef struct PlatformRawInput {
|
||||||
|
U16 chars[PLATFORM_MAX_CHARS_PER_FRAME];
|
||||||
|
S32 char_count;
|
||||||
|
U8 keys[PLATFORM_MAX_KEYS_PER_FRAME];
|
||||||
|
S32 key_count;
|
||||||
|
B32 ctrl_held;
|
||||||
|
B32 shift_held;
|
||||||
|
Vec2F32 mouse_pos;
|
||||||
|
Vec2F32 scroll_delta;
|
||||||
|
B32 mouse_down;
|
||||||
|
B32 was_mouse_down;
|
||||||
|
} PlatformRawInput;
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Window
|
||||||
|
|
||||||
|
typedef struct PlatformWindow PlatformWindow;
|
||||||
|
|
||||||
|
typedef struct PlatformWindowDesc {
|
||||||
|
const char *title;
|
||||||
|
S32 width;
|
||||||
|
S32 height;
|
||||||
|
} PlatformWindowDesc;
|
||||||
|
|
||||||
|
typedef void (*PlatformFrameCallback)(void *user_data);
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Window API
|
||||||
|
|
||||||
|
PlatformWindow *platform_create_window(PlatformWindowDesc *desc);
|
||||||
|
void platform_destroy_window(PlatformWindow *window);
|
||||||
|
B32 platform_poll_events(PlatformWindow *window);
|
||||||
|
void platform_get_size(PlatformWindow *window, S32 *w, S32 *h);
|
||||||
|
void *platform_get_native_handle(PlatformWindow *window);
|
||||||
|
PlatformRawInput platform_get_input(PlatformWindow *window);
|
||||||
|
void platform_set_frame_callback(PlatformWindow *window, PlatformFrameCallback cb, void *user_data);
|
||||||
|
F32 platform_get_dpi_scale(PlatformWindow *window);
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Input adapter — convert raw windowing input to editor input
|
||||||
|
|
||||||
|
PlatformInput platform_adapt_input(PlatformRawInput *raw);
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Clipboard
|
||||||
|
|
||||||
|
void platform_clipboard_set(const char *text);
|
||||||
|
const char *platform_clipboard_get(void);
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Process spawning
|
||||||
|
|
||||||
|
void platform_spawn_terminal(const char *command, const char *working_dir);
|
||||||
297
c/platform/platform_win32.c
Normal file
297
c/platform/platform_win32.c
Normal file
@@ -0,0 +1,297 @@
|
|||||||
|
// platform_win32.c — Win32 windowing, input, clipboard, and process spawning
|
||||||
|
|
||||||
|
#include "platform/platform.h"
|
||||||
|
|
||||||
|
#ifndef WIN32_LEAN_AND_MEAN
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#endif
|
||||||
|
#include <windows.h>
|
||||||
|
#include <malloc.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Window internals
|
||||||
|
|
||||||
|
struct PlatformWindow {
|
||||||
|
HWND hwnd;
|
||||||
|
B32 should_close;
|
||||||
|
S32 width;
|
||||||
|
S32 height;
|
||||||
|
PlatformFrameCallback frame_callback;
|
||||||
|
void *frame_callback_user_data;
|
||||||
|
PlatformRawInput input;
|
||||||
|
B32 prev_mouse_down;
|
||||||
|
};
|
||||||
|
|
||||||
|
static PlatformWindow *g_main_window = NULL;
|
||||||
|
static B32 g_wndclass_registered = false;
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Window procedure
|
||||||
|
|
||||||
|
// Forward declare ImGui Win32 handler (defined in imgui_impl_win32.cpp)
|
||||||
|
extern LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
|
||||||
|
|
||||||
|
static LRESULT CALLBACK platform_wndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
|
||||||
|
// Let ImGui process input first
|
||||||
|
if (ImGui_ImplWin32_WndProcHandler(hwnd, msg, wparam, lparam))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
PlatformWindow *pw = (PlatformWindow *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
|
||||||
|
|
||||||
|
switch (msg) {
|
||||||
|
case WM_SIZE:
|
||||||
|
if (pw && wparam != SIZE_MINIMIZED) {
|
||||||
|
pw->width = (S32)LOWORD(lparam);
|
||||||
|
pw->height = (S32)HIWORD(lparam);
|
||||||
|
if (pw->frame_callback)
|
||||||
|
pw->frame_callback(pw->frame_callback_user_data);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
case WM_CHAR:
|
||||||
|
if (pw && wparam >= 32 && wparam < 0xFFFF) {
|
||||||
|
PlatformRawInput *ev = &pw->input;
|
||||||
|
if (ev->char_count < PLATFORM_MAX_CHARS_PER_FRAME)
|
||||||
|
ev->chars[ev->char_count++] = (U16)wparam;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
case WM_KEYDOWN:
|
||||||
|
case WM_SYSKEYDOWN:
|
||||||
|
if (pw) {
|
||||||
|
PlatformRawInput *ev = &pw->input;
|
||||||
|
if (ev->key_count < PLATFORM_MAX_KEYS_PER_FRAME)
|
||||||
|
ev->keys[ev->key_count++] = (U8)wparam;
|
||||||
|
ev->ctrl_held = (GetKeyState(VK_CONTROL) & 0x8000) != 0;
|
||||||
|
ev->shift_held = (GetKeyState(VK_SHIFT) & 0x8000) != 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case WM_MOUSEWHEEL:
|
||||||
|
if (pw) {
|
||||||
|
S16 wheel_delta = (S16)HIWORD(wparam);
|
||||||
|
pw->input.scroll_delta.y += (F32)wheel_delta / (F32)WHEEL_DELTA * 6.0f;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
case WM_SETCURSOR:
|
||||||
|
if (LOWORD(lparam) == HTCLIENT) {
|
||||||
|
SetCursor(LoadCursor(NULL, IDC_IBEAM));
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case WM_DPICHANGED:
|
||||||
|
if (pw) {
|
||||||
|
RECT *suggested = (RECT *)lparam;
|
||||||
|
SetWindowPos(hwnd, NULL, suggested->left, suggested->top,
|
||||||
|
suggested->right - suggested->left, suggested->bottom - suggested->top,
|
||||||
|
SWP_NOZORDER | SWP_NOACTIVATE);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
case WM_CLOSE:
|
||||||
|
if (pw) pw->should_close = true;
|
||||||
|
return 0;
|
||||||
|
case WM_DESTROY:
|
||||||
|
if (pw == g_main_window)
|
||||||
|
PostQuitMessage(0);
|
||||||
|
return 0;
|
||||||
|
case WM_SYSCOMMAND:
|
||||||
|
if ((wparam & 0xfff0) == SC_KEYMENU)
|
||||||
|
return 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return DefWindowProcW(hwnd, msg, wparam, lparam);
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Window lifecycle
|
||||||
|
|
||||||
|
PlatformWindow *platform_create_window(PlatformWindowDesc *desc) {
|
||||||
|
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
|
||||||
|
|
||||||
|
if (!g_wndclass_registered) {
|
||||||
|
WNDCLASSEXW wc = {0};
|
||||||
|
wc.cbSize = sizeof(wc);
|
||||||
|
wc.style = CS_CLASSDC;
|
||||||
|
wc.lpfnWndProc = platform_wndproc;
|
||||||
|
wc.hInstance = GetModuleHandleW(NULL);
|
||||||
|
wc.hIcon = LoadIconW(wc.hInstance, MAKEINTRESOURCEW(101));
|
||||||
|
wc.hIconSm = LoadIconW(wc.hInstance, MAKEINTRESOURCEW(101));
|
||||||
|
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
|
||||||
|
wc.lpszClassName = L"codemax_wc";
|
||||||
|
RegisterClassExW(&wc);
|
||||||
|
g_wndclass_registered = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
UINT dpi = GetDpiForSystem();
|
||||||
|
int screen_w = GetSystemMetrics(SM_CXSCREEN);
|
||||||
|
int screen_h = GetSystemMetrics(SM_CYSCREEN);
|
||||||
|
int x = (screen_w - desc->width) / 2;
|
||||||
|
int y = (screen_h - desc->height) / 2;
|
||||||
|
|
||||||
|
DWORD style = WS_OVERLAPPEDWINDOW;
|
||||||
|
RECT rect = { 0, 0, (LONG)desc->width, (LONG)desc->height };
|
||||||
|
AdjustWindowRectExForDpi(&rect, style, FALSE, 0, dpi);
|
||||||
|
|
||||||
|
int wchar_count = MultiByteToWideChar(CP_UTF8, 0, desc->title, -1, NULL, 0);
|
||||||
|
wchar_t *wtitle = (wchar_t *)_malloca(wchar_count * sizeof(wchar_t));
|
||||||
|
MultiByteToWideChar(CP_UTF8, 0, desc->title, -1, wtitle, wchar_count);
|
||||||
|
|
||||||
|
HWND hwnd = CreateWindowExW(
|
||||||
|
0, L"codemax_wc", wtitle,
|
||||||
|
style,
|
||||||
|
x, y,
|
||||||
|
rect.right - rect.left,
|
||||||
|
rect.bottom - rect.top,
|
||||||
|
NULL, NULL, GetModuleHandleW(NULL), NULL
|
||||||
|
);
|
||||||
|
_freea(wtitle);
|
||||||
|
|
||||||
|
if (!hwnd) return NULL;
|
||||||
|
|
||||||
|
PlatformWindow *window = (PlatformWindow *)calloc(1, sizeof(PlatformWindow));
|
||||||
|
window->hwnd = hwnd;
|
||||||
|
window->should_close = false;
|
||||||
|
window->width = desc->width;
|
||||||
|
window->height = desc->height;
|
||||||
|
|
||||||
|
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)window);
|
||||||
|
g_main_window = window;
|
||||||
|
|
||||||
|
ShowWindow(hwnd, SW_SHOWDEFAULT);
|
||||||
|
UpdateWindow(hwnd);
|
||||||
|
return window;
|
||||||
|
}
|
||||||
|
|
||||||
|
void platform_destroy_window(PlatformWindow *window) {
|
||||||
|
if (!window) return;
|
||||||
|
if (window->hwnd) DestroyWindow(window->hwnd);
|
||||||
|
if (g_main_window == window) g_main_window = NULL;
|
||||||
|
free(window);
|
||||||
|
}
|
||||||
|
|
||||||
|
B32 platform_poll_events(PlatformWindow *window) {
|
||||||
|
MSG msg;
|
||||||
|
while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) {
|
||||||
|
TranslateMessage(&msg);
|
||||||
|
DispatchMessageW(&msg);
|
||||||
|
if (msg.message == WM_QUIT)
|
||||||
|
window->should_close = true;
|
||||||
|
}
|
||||||
|
return !window->should_close;
|
||||||
|
}
|
||||||
|
|
||||||
|
void platform_get_size(PlatformWindow *window, S32 *w, S32 *h) {
|
||||||
|
if (w) *w = window->width;
|
||||||
|
if (h) *h = window->height;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *platform_get_native_handle(PlatformWindow *window) {
|
||||||
|
return (void *)window->hwnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlatformRawInput platform_get_input(PlatformWindow *window) {
|
||||||
|
PlatformRawInput result = window->input;
|
||||||
|
|
||||||
|
POINT cursor;
|
||||||
|
GetCursorPos(&cursor);
|
||||||
|
ScreenToClient(window->hwnd, &cursor);
|
||||||
|
result.mouse_pos = v2f32((F32)cursor.x, (F32)cursor.y);
|
||||||
|
|
||||||
|
result.was_mouse_down = window->prev_mouse_down;
|
||||||
|
result.mouse_down = (GetAsyncKeyState(VK_LBUTTON) & 0x8000) != 0;
|
||||||
|
window->prev_mouse_down = result.mouse_down;
|
||||||
|
|
||||||
|
memset(&window->input, 0, sizeof(window->input));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void platform_set_frame_callback(PlatformWindow *window, PlatformFrameCallback cb, void *user_data) {
|
||||||
|
window->frame_callback = cb;
|
||||||
|
window->frame_callback_user_data = user_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
F32 platform_get_dpi_scale(PlatformWindow *window) {
|
||||||
|
if (!window || !window->hwnd) return 1.0f;
|
||||||
|
return (F32)GetDpiForWindow(window->hwnd) / 96.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Input adapter
|
||||||
|
|
||||||
|
PlatformInput platform_adapt_input(PlatformRawInput *raw) {
|
||||||
|
PlatformInput pi = {0};
|
||||||
|
pi.ctrl_held = raw->ctrl_held;
|
||||||
|
pi.shift_held = raw->shift_held;
|
||||||
|
pi.alt_held = (GetKeyState(VK_MENU) & 0x8000) != 0;
|
||||||
|
|
||||||
|
// Convert UTF-16 chars to ASCII (printable range only)
|
||||||
|
for (S32 i = 0; i < raw->char_count && pi.char_count < PLATFORM_MAX_CHARS_PER_FRAME; i++) {
|
||||||
|
U16 ch = raw->chars[i];
|
||||||
|
if (ch >= 32 && ch < 127)
|
||||||
|
pi.chars[pi.char_count++] = (char)ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key codes are identical (both use Windows VK codes)
|
||||||
|
for (S32 i = 0; i < raw->key_count && pi.key_count < PLATFORM_MAX_KEYS_PER_FRAME; i++)
|
||||||
|
pi.keys[pi.key_count++] = raw->keys[i];
|
||||||
|
|
||||||
|
return pi;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Clipboard
|
||||||
|
|
||||||
|
void platform_clipboard_set(const char *text) {
|
||||||
|
if (!text) return;
|
||||||
|
int len = (int)strlen(text);
|
||||||
|
if (len == 0) return;
|
||||||
|
int wlen = MultiByteToWideChar(CP_UTF8, 0, text, len, NULL, 0);
|
||||||
|
if (wlen == 0) return;
|
||||||
|
HGLOBAL hmem = GlobalAlloc(GMEM_MOVEABLE, (wlen + 1) * sizeof(wchar_t));
|
||||||
|
if (!hmem) return;
|
||||||
|
wchar_t *wbuf = (wchar_t *)GlobalLock(hmem);
|
||||||
|
MultiByteToWideChar(CP_UTF8, 0, text, len, wbuf, wlen);
|
||||||
|
wbuf[wlen] = L'\0';
|
||||||
|
GlobalUnlock(hmem);
|
||||||
|
HWND hwnd = g_main_window ? g_main_window->hwnd : NULL;
|
||||||
|
if (OpenClipboard(hwnd)) {
|
||||||
|
EmptyClipboard();
|
||||||
|
SetClipboardData(CF_UNICODETEXT, hmem);
|
||||||
|
CloseClipboard();
|
||||||
|
} else {
|
||||||
|
GlobalFree(hmem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *platform_clipboard_get(void) {
|
||||||
|
static char buf[64 * 1024];
|
||||||
|
buf[0] = '\0';
|
||||||
|
HWND hwnd = g_main_window ? g_main_window->hwnd : NULL;
|
||||||
|
if (!OpenClipboard(hwnd)) return NULL;
|
||||||
|
HGLOBAL hmem = GetClipboardData(CF_UNICODETEXT);
|
||||||
|
if (hmem) {
|
||||||
|
wchar_t *wbuf = (wchar_t *)GlobalLock(hmem);
|
||||||
|
if (wbuf) {
|
||||||
|
int len = WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, buf, sizeof(buf) - 1, NULL, NULL);
|
||||||
|
buf[len > 0 ? len - 1 : 0] = '\0';
|
||||||
|
GlobalUnlock(hmem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CloseClipboard();
|
||||||
|
return buf[0] ? buf : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Process spawning
|
||||||
|
|
||||||
|
void platform_spawn_terminal(const char *command, const char *working_dir) {
|
||||||
|
char cmd_line[2048];
|
||||||
|
snprintf(cmd_line, sizeof(cmd_line), "cmd.exe /k %s", command);
|
||||||
|
|
||||||
|
STARTUPINFOA si = {0};
|
||||||
|
si.cb = sizeof(si);
|
||||||
|
PROCESS_INFORMATION pi = {0};
|
||||||
|
CreateProcessA(NULL, cmd_line, NULL, NULL, FALSE,
|
||||||
|
CREATE_NEW_CONSOLE, NULL, working_dir, &si, &pi);
|
||||||
|
if (pi.hProcess) CloseHandle(pi.hProcess);
|
||||||
|
if (pi.hThread) CloseHandle(pi.hThread);
|
||||||
|
}
|
||||||
10
jai/modules/Console_Render/console.jai
Normal file
10
jai/modules/Console_Render/console.jai
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
swap_buffer :: (screen: *Screen) {
|
||||||
|
using screen;
|
||||||
|
write_string(cast(string)buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
clear_screen :: (screen: *Screen) {
|
||||||
|
for 0..screen.height * screen.width {
|
||||||
|
print_color(" ", color = .BLACK);
|
||||||
|
}
|
||||||
|
}
|
||||||
107
jai/modules/Console_Render/draw.jai
Normal file
107
jai/modules/Console_Render/draw.jai
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
// "Squashes" two dimensional coordinates onto the one dimensional screen buffer
|
||||||
|
draw :: (using screen: *Screen, p: Vec2s64, char: u8 = DEFAULT_PIXEL_CHAR) {
|
||||||
|
if p.x >= 0 && p.x < width && p.y >= 0 && p.y < height {
|
||||||
|
buffer[p.y * width + p.x] = char;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implementation of the Bresenham line algorithm
|
||||||
|
// https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm
|
||||||
|
draw_line :: (screen: *Screen, p1: Vec2s64, p2: Vec2s64, char: u8 = DEFAULT_PIXEL_CHAR) {
|
||||||
|
p1_c := p1;
|
||||||
|
p2_c := p2;
|
||||||
|
|
||||||
|
dx := abs(p2_c.x - p1_c.x);
|
||||||
|
dy := -abs(p2_c.y - p1_c.y);
|
||||||
|
sx := ifx p1_c.x < p2_c.x then 1 else -1;
|
||||||
|
sy := ifx p1_c.y < p2_c.y then 1 else -1;
|
||||||
|
|
||||||
|
err := dx + dy;
|
||||||
|
e2 : s64;
|
||||||
|
|
||||||
|
while true {
|
||||||
|
draw(screen, Vec2s64.{p1_c.x, p1_c.y}, char);
|
||||||
|
|
||||||
|
if p1_c.x == p2_c.x && p1_c.y == p2_c.y then break;
|
||||||
|
|
||||||
|
e2 = 2 * err;
|
||||||
|
|
||||||
|
if e2 >= dy {
|
||||||
|
err += dy;
|
||||||
|
p1_c.x += sx;
|
||||||
|
}
|
||||||
|
|
||||||
|
if e2 <= dx {
|
||||||
|
err += dx;
|
||||||
|
p1_c.y += sy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
draw_triangle :: (screen: *Screen, p1: Vec2s64, p2: Vec2s64, p3: Vec2s64, char: u8 = DEFAULT_PIXEL_CHAR) {
|
||||||
|
draw_line(screen, p1, p2, char);
|
||||||
|
draw_line(screen, p2, p3, char);
|
||||||
|
draw_line(screen, p3, p1, char);
|
||||||
|
}
|
||||||
|
|
||||||
|
draw_triangle :: (screen: *Screen, t: Triangle, char: u8 = DEFAULT_PIXEL_CHAR) {
|
||||||
|
draw_triangle(screen, t.p1, t.p2, t.p3, char);
|
||||||
|
}
|
||||||
|
|
||||||
|
draw_quad :: (screen: *Screen, p1: Vec2s64, p2: Vec2s64, char: u8 = DEFAULT_PIXEL_CHAR) {
|
||||||
|
// (x1, y1) => top left
|
||||||
|
// (x2, y2) => bottom right
|
||||||
|
|
||||||
|
draw_line(screen, .{p1.x, p1.y}, .{p1.x, p2.y}, char);
|
||||||
|
draw_line(screen, .{p1.x, p2.y}, .{p2.x, p2.y}, char);
|
||||||
|
draw_line(screen, .{p2.x, p2.y}, .{p2.x, p1.y}, char);
|
||||||
|
draw_line(screen, .{p2.x, p1.y}, .{p1.x, p1.y}, char);
|
||||||
|
}
|
||||||
|
|
||||||
|
draw_quad :: (screen: *Screen, p1: Vec2s64, p2: Vec2s64, p3: Vec2s64, p4: Vec2s64, char: u8 = DEFAULT_PIXEL_CHAR) {
|
||||||
|
// (x1, y1) => top left
|
||||||
|
// (x2, y2) => bottom left
|
||||||
|
// (x3, y3) => bottom right
|
||||||
|
// (x4, y4) => top right
|
||||||
|
|
||||||
|
draw_line(screen, .{p1.x, p1.y}, .{p2.x, p2.y}, char);
|
||||||
|
draw_line(screen, .{p2.x, p2.y}, .{p3.x, p3.y}, char);
|
||||||
|
draw_line(screen, .{p3.x, p3.y}, .{p4.x, p4.y}, char);
|
||||||
|
draw_line(screen, .{p4.x, p4.y}, .{p1.x, p1.y}, char);
|
||||||
|
}
|
||||||
|
|
||||||
|
draw_quad :: (screen: *Screen, q: Quad, char: u8 = DEFAULT_PIXEL_CHAR) {
|
||||||
|
draw_quad(screen, q.p1, q.p2, q.p3, q.p4, char);
|
||||||
|
}
|
||||||
|
|
||||||
|
draw_text :: (using screen: *Screen, p: Vec2s64, s: string) {
|
||||||
|
if p.x >= 0 && p.x < width && p.y >= 0 && p.y < height {
|
||||||
|
for cast([]u8)s {
|
||||||
|
buffer[(p.y * width + p.x) + it_index] = it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// When attempting to draw outside of the visible screen, "clip" the pixels
|
||||||
|
// by setting their position to the maximum width / height available
|
||||||
|
clip :: (using screen: *Screen, p: *Vec2s64) {
|
||||||
|
if p.x < 0 then p.x = 0;
|
||||||
|
if p.x >= width then p.x = width;
|
||||||
|
if p.y < 0 then p.y = 0;
|
||||||
|
if p.y >= height then p.y = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
fill :: (screen: *Screen, p1: Vec2s64, p2: Vec2s64, char: u8 = DEFAULT_PIXEL_CHAR) {
|
||||||
|
clip(screen, *p1);
|
||||||
|
clip(screen, *p2);
|
||||||
|
|
||||||
|
for x: p1.x..p2.x {
|
||||||
|
for y: p1.y..p2.y {
|
||||||
|
draw(screen, .{x, y}, char);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fill_entire_screen :: (screen: *Screen, char: u8 = DEFAULT_PIXEL_CHAR) {
|
||||||
|
fill(screen, .{0,0}, .{screen.width, screen.height}, char);
|
||||||
|
}
|
||||||
8
jai/modules/Console_Render/file_operations.jai
Normal file
8
jai/modules/Console_Render/file_operations.jai
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
// Render current screen state to a file
|
||||||
|
render_to_file :: (screen: *Screen, preserve_color: bool, filepath: string) {
|
||||||
|
assert(false, "Not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
#scope_file
|
||||||
|
|
||||||
|
#import "File";
|
||||||
42
jai/modules/Console_Render/macos/console.jai
Normal file
42
jai/modules/Console_Render/macos/console.jai
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
get_console_size :: () -> s64, s64 {
|
||||||
|
//@Hack - This is terrible, but this is typically called only once, so maybe it doesn't matter...
|
||||||
|
width := string_to_int(get_stdout_from_cmd("tput cols"));
|
||||||
|
height := string_to_int(get_stdout_from_cmd("tput lines"));
|
||||||
|
|
||||||
|
return cast(s64)width, cast(s64)height;
|
||||||
|
}
|
||||||
|
|
||||||
|
#scope_file
|
||||||
|
|
||||||
|
get_stdout_from_cmd :: (cmd: string) -> string {
|
||||||
|
buffer: [256]u8;
|
||||||
|
stream : *FILE;
|
||||||
|
data : string;
|
||||||
|
|
||||||
|
// m_cmd := tprint("% %", cmd, " 2>&1");
|
||||||
|
|
||||||
|
stream = popen(to_c_string(cmd), to_c_string("r"));
|
||||||
|
|
||||||
|
if stream {
|
||||||
|
while !feof(stream) {
|
||||||
|
if fgets(buffer.data, buffer.count, stream) {
|
||||||
|
data = tprint("%1%2", data, cast(string)(buffer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pclose(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
to_c_string :: (s: string) -> *u8 {
|
||||||
|
result := cast(*u8) alloc(s.count + 1);
|
||||||
|
memcpy(result, s.data, s.count);
|
||||||
|
result[s.count] = 0;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#import "POSIX";
|
||||||
19
jai/modules/Console_Render/math_extras.jai
Normal file
19
jai/modules/Console_Render/math_extras.jai
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
get_triangle_centroid :: (t: Triangle) -> Vec2s64 {
|
||||||
|
return get_triangle_centroid(t.p1, t.p2, t.p3);
|
||||||
|
}
|
||||||
|
|
||||||
|
get_triangle_centroid :: (p1: Vec2s64, p2: Vec2s64, p3: Vec2s64) -> Vec2s64 {
|
||||||
|
x := (p1.x + p2.x + p3.x) / 3;
|
||||||
|
y := (p1.y + p2.y + p3.y) / 3;
|
||||||
|
|
||||||
|
return .{x, y};
|
||||||
|
}
|
||||||
|
|
||||||
|
make_quad_from_rect :: (p1: Vec2s64, p2: Vec2s64) -> Quad {
|
||||||
|
return .{
|
||||||
|
.{ p1.x, p1.y },
|
||||||
|
.{ p2.x, p1.y },
|
||||||
|
.{ p2.x, p2.y },
|
||||||
|
.{ p1.x, p2.y },
|
||||||
|
};
|
||||||
|
}
|
||||||
122
jai/modules/Console_Render/module.jai
Normal file
122
jai/modules/Console_Render/module.jai
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
/******************************************************
|
||||||
|
* Copyright 2024 Max Amundsen - All Rights Reserved
|
||||||
|
******************************************************
|
||||||
|
|
||||||
|
Console Screen Rendering:
|
||||||
|
|
||||||
|
Terminal emulators, or consoles, are programs that display text from a sequence of characters.
|
||||||
|
These characters are usually fixed-width, meaning each character takes up the same amount of space on screen.
|
||||||
|
The dimensions of your console window determines how the console will display the text in rows and columns.
|
||||||
|
|
||||||
|
This module is designed to draw simple 2d, or 3d shapes, inside a standard terminal emulator program.
|
||||||
|
|
||||||
|
--------------------------------------------------------------
|
||||||
|
Suppose the following sequence of characters is sent to the console:
|
||||||
|
|
||||||
|
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U']
|
||||||
|
|
||||||
|
For a console displaying 7 characters horizontally, and 3 characters vertically, the result would be the following:
|
||||||
|
|
||||||
|
0123456
|
||||||
|
_________
|
||||||
|
0 |ABCDEFG|
|
||||||
|
1 |HIJKLMN|
|
||||||
|
2 |OPQRSTU|
|
||||||
|
---------
|
||||||
|
|
||||||
|
As displayed in the above figure, the 0th element, 'A', in the sequence represents (0,0) in 2D space, while the 9th element, 'J' represents (2,1).
|
||||||
|
|
||||||
|
=> Calculating the two dimensional area of the console output yields the number of elements in the initial 1D sequence:
|
||||||
|
|
||||||
|
(7 * 3) = 21
|
||||||
|
|
||||||
|
This may appear obvious, however this is an important connection to make, when considering how the console represents its output.
|
||||||
|
|
||||||
|
The terminal automatically transforms our 1D sequence, to a 2D sequence displayed on your monitor, based on the size of the window.
|
||||||
|
|
||||||
|
Since the console is responsible for transforming our 1D array into a 2D output, we must provide the console with a 1D array.
|
||||||
|
In order to draw 2D and 3D elements on the screen, we must construct procedures in the code to represent the transformations from
|
||||||
|
higher dimensions, down to the first dimension.
|
||||||
|
|
||||||
|
This simple 2d -> 1d transformation is implemented as the `draw` procedure found in the `draw.jai` file in this module.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
BLANK_PIXEL : u8 : 0x20;
|
||||||
|
DEFAULT_PIXEL_CHAR : u8 : #char "#";
|
||||||
|
|
||||||
|
Vec2s64 :: struct {
|
||||||
|
x: s64;
|
||||||
|
y: s64;
|
||||||
|
}
|
||||||
|
|
||||||
|
Triangle :: struct {
|
||||||
|
p1: Vec2s64;
|
||||||
|
p3: Vec2s64;
|
||||||
|
p2: Vec2s64;
|
||||||
|
}
|
||||||
|
|
||||||
|
Quad :: struct {
|
||||||
|
p1: Vec2s64;
|
||||||
|
p2: Vec2s64;
|
||||||
|
p3: Vec2s64;
|
||||||
|
p4: Vec2s64;
|
||||||
|
}
|
||||||
|
|
||||||
|
Screen :: struct {
|
||||||
|
width : s64;
|
||||||
|
height: s64;
|
||||||
|
buffer: [..] u8;
|
||||||
|
}
|
||||||
|
|
||||||
|
init_screen :: (screen: *Screen, width: s64, height: s64) {
|
||||||
|
buffer : [..] u8;
|
||||||
|
|
||||||
|
array_resize(*buffer, width * height);
|
||||||
|
|
||||||
|
screen.width = width;
|
||||||
|
screen.height = height;
|
||||||
|
screen.buffer = buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
resize_screen :: (screen: *Screen, width: s64, height: s64) {
|
||||||
|
screen.width = width;
|
||||||
|
screen.height = height;
|
||||||
|
|
||||||
|
array_resize(*screen.buffer, width * height);
|
||||||
|
}
|
||||||
|
|
||||||
|
maybe_resize_screen :: (screen: *Screen) {
|
||||||
|
w, h := get_console_size();
|
||||||
|
|
||||||
|
should_update := false;
|
||||||
|
|
||||||
|
if w != screen.width {
|
||||||
|
should_update = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if h != screen.height {
|
||||||
|
should_update = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if should_update {
|
||||||
|
resize_screen(screen, w, h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#load "draw.jai";
|
||||||
|
#load "transform.jai";
|
||||||
|
#load "file_operations.jai";
|
||||||
|
#load "console.jai";
|
||||||
|
#load "math_extras.jai";
|
||||||
|
|
||||||
|
#if OS == .MACOS {
|
||||||
|
#load "macos/console.jai";
|
||||||
|
} else {
|
||||||
|
#assert false "Platform unsupported. Sorry.";
|
||||||
|
}
|
||||||
|
|
||||||
|
#scope_module
|
||||||
|
|
||||||
|
#import "Basic";
|
||||||
|
#import "Math";
|
||||||
67
jai/modules/Console_Render/transform.jai
Normal file
67
jai/modules/Console_Render/transform.jai
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
// for n points
|
||||||
|
translate :: (amount: Vec2s64, pn: ..*Vec2s64) {
|
||||||
|
for pn {
|
||||||
|
it.x += amount.x;
|
||||||
|
it.y += amount.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// for a triangle
|
||||||
|
translate :: (amount: Vec2s64, t: *Triangle) {
|
||||||
|
translate(amount, *t.p1, *t.p2, *t.p3);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// for a quad
|
||||||
|
translate :: (amount: Vec2s64, q: *Quad) {
|
||||||
|
translate(amount, *q.p1, *q.p2, *q.p3, *q.p4);
|
||||||
|
}
|
||||||
|
|
||||||
|
// single point rotation
|
||||||
|
rotate :: (angle: s64, center: Vec2s64, p: *Vec2s64) {
|
||||||
|
rad := cast(float64)angle * (PI / 180.0);
|
||||||
|
|
||||||
|
// translate to center
|
||||||
|
tx := p.x - center.x;
|
||||||
|
ty := p.y - center.y;
|
||||||
|
|
||||||
|
// apply rotation
|
||||||
|
rx := cast(s64)(tx * cos(rad) - ty * sin(rad));
|
||||||
|
ry := cast(s64)(tx * sin(rad) + ty * cos(rad));
|
||||||
|
|
||||||
|
// translate back
|
||||||
|
p.x = rx + center.x;
|
||||||
|
p.y = ry + center.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
// rotate n points
|
||||||
|
rotate :: (angle: s64, center: Vec2s64, pn: ..*Vec2s64) {
|
||||||
|
for pn {
|
||||||
|
rotate(angle, center, it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// rotate 3 points via triangle struct
|
||||||
|
rotate :: (angle: s64, center: Vec2s64, t: *Triangle) {
|
||||||
|
rotate(angle, center, *t.p1, *t.p2, *t.p3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// rotate 4 points via quad struct
|
||||||
|
rotate :: (angle: s64, center: Vec2s64, q: *Quad) {
|
||||||
|
rotate(angle, center, *q.p1, *q.p2, *q.p3, *q.p4);
|
||||||
|
}
|
||||||
|
|
||||||
|
scale :: (factor: Vector2, pn: ..*Vec2s64) {
|
||||||
|
for pn {
|
||||||
|
it.x = cast(s64)(it.x * factor.x);
|
||||||
|
it.y = cast(s64)(it.y * factor.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scale :: (factor: Vector2, t: *Triangle) {
|
||||||
|
scale(factor, *t.p1, *t.p2, *t.p3);
|
||||||
|
}
|
||||||
|
|
||||||
|
scale :: (factor: Vector2, q: *Quad) {
|
||||||
|
scale(factor, *q.p1, *q.p2, *q.p3, *q.p4);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user