// examples/visualize.ling — Ling source → SVG pattern visualizer
// Usage: ling run examples/visualize.ling <input.ling> [output.svg]
//
// Generates a dark-themed SVG where each function in the source file
// becomes a geometric pattern panel — style inspired by AI crypto-fill.
// ─── Geometry helpers ────────────────────────────────────────────────────────
fn hsl_color(hue, sat, lit) {
bind r = floor(lit + sat * sin(hue * 6.2832))
bind g = floor(lit + sat * sin(hue * 6.2832 + 2.0944))
bind b = floor(lit + sat * sin(hue * 6.2832 + 4.1888))
bind r = max(0.0, min(255.0, r))
bind g = max(0.0, min(255.0, g))
bind b = max(0.0, min(255.0, b))
return "rgb(" + to_str(r) + "," + to_str(g) + "," + to_str(b) + ")"
}
fn f(n) {
return to_str(floor(n * 10.0) / 10.0)
}
fn reg_mark(mx, my) {
bind sz = 10.0
bind s = "<circle cx='" + f(mx) + "' cy='" + f(my) + "' r='2.5' fill='#FFD700'/>"
bind s = s + "<line x1='" + f(mx - sz) + "' y1='" + f(my) + "' x2='" + f(mx + sz) + "' y2='" + f(my) + "' stroke='#FFD700' stroke-width='0.5'/>"
bind s = s + "<line x1='" + f(mx) + "' y1='" + f(my - sz) + "' x2='" + f(mx) + "' y2='" + f(my + sz) + "' stroke='#FFD700' stroke-width='0.5'/>"
return s
}
fn detect_vtex(body) {
if str_find(body, "vtex_rings") >= 0 { return "#00e5ff" }
if str_find(body, "vtex_flower") >= 0 { return "#ff79c6" }
if str_find(body, "vtex_spiral") >= 0 { return "#00ffb3" }
if str_find(body, "vtex_lotus") >= 0 { return "#ff5e8a" }
if str_find(body, "vtex_chakra") >= 0 { return "#bd93f9" }
if str_find(body, "vtex_yantra") >= 0 { return "#ffb86c" }
if str_find(body, "vtex_star") >= 0 { return "#ffd700" }
if str_find(body, "vtex_hyperbolic") >= 0 { return "#5575c8" }
if str_find(body, "vtex_tess") >= 0 { return "#50fa7b" }
if str_find(body, "vtex_grid") >= 0 { return "#8be9fd" }
if str_find(body, "vtex_halftone") >= 0 { return "#888899" }
if str_find(body, "vtex_letter_rain") >= 0 { return "#f1fa8c" }
if str_find(body, "audio_") >= 0 { return "#ff1493" }
if str_find(body, "camera") >= 0 { return "#ffe066" }
if str_find(body, "light") >= 0 { return "#ffe4b5" }
return "#6688aa"
}
// ─── Pattern generators (each returns an SVG fragment string) ────────────────
fn pat_dot_grid(ox, oy, w, h, color, phase) {
bind s = ""
bind rows = 5
bind cols = 6
bind r = 0
while r < rows {
bind c = 0
while c < cols {
bind t = (r * cols + c) / (rows * cols)
bind cx = ox + (c + 0.5) * w / cols
bind cy = oy + (r + 0.5) * h / rows
bind dr = 2.0 + 3.5 * abs(sin(t * 6.28 * 3.0 + phase * 9.42))
bind op = 0.35 + 0.65 * abs(sin(t * 6.28 + phase * 5.0))
bind s = s + "<circle cx='" + f(cx) + "' cy='" + f(cy) + "' r='" + f(dr) + "' fill='" + color + "' opacity='" + f(op) + "'/>"
bind c = c + 1
}
bind r = r + 1
}
return s
}
fn pat_waveform(ox, oy, w, h, color, phase) {
bind s = ""
bind steps = 36
bind pts1 = ""
bind pts2 = ""
bind pts3 = ""
bind i = 0
while i <= steps {
bind t = i / steps
bind px = ox + t * w
bind sep = if i == 0 { "" } else { " " }
bind py1 = oy + h * 0.35 + h * 0.22 * sin(t * 6.28 * 2.5 + phase * 12.56)
bind py2 = oy + h * 0.55 + h * 0.15 * sin(t * 6.28 * 4.0 + phase * 18.84)
bind py3 = oy + h * 0.72 + h * 0.10 * sin(t * 6.28 * 6.0 + phase * 25.0)
bind pts1 = pts1 + sep + f(px) + "," + f(py1)
bind pts2 = pts2 + sep + f(px) + "," + f(py2)
bind pts3 = pts3 + sep + f(px) + "," + f(py3)
bind i = i + 1
}
bind s = s + "<polyline points='" + pts1 + "' fill='none' stroke='" + color + "' stroke-width='2' opacity='0.85'/>"
bind s = s + "<polyline points='" + pts2 + "' fill='none' stroke='" + color + "' stroke-width='1.5' opacity='0.55'/>"
bind s = s + "<polyline points='" + pts3 + "' fill='none' stroke='" + color + "' stroke-width='1' opacity='0.30'/>"
return s
}
fn pat_spectral_grid(ox, oy, w, h, color, phase) {
bind s = ""
bind rows = 8
bind cols = 8
bind r = 0
while r < rows {
bind c = 0
while c < cols {
bind t = (r * cols + c) / (rows * cols)
bind active = sin(t * 6.28 * 7.0 + phase * 18.0 + r * 0.55 + c * 0.33) > 0.15
bind elem = if active {
"<rect x='" + f(ox + c * w / cols + 1.5) + "' y='" + f(oy + r * h / rows + 1.5) + "' width='" + f(w / cols - 3.0) + "' height='" + f(h / rows - 3.0) + "' fill='" + color + "' opacity='" + f(0.25 + 0.55 * abs(sin(t * 6.28 + phase * 6.28))) + "' rx='2'/>"
} else {
""
}
bind s = s + elem
bind c = c + 1
}
bind r = r + 1
}
return s
}
fn pat_radial_rings(ox, oy, w, h, color, phase) {
bind s = ""
bind cx = ox + w * 0.5
bind cy = oy + h * 0.5
bind max_r = min(w, h) * 0.44
bind rings = 4
bind r = 1
while r <= rings {
bind rad = max_r * r / rings
bind n_dots = 6 + r * 4
bind d = 0
while d < n_dots {
bind angle = d * 6.2832 / n_dots + phase * 6.2832 + r * 0.52
bind px = cx + rad * cos(angle)
bind py = cy + rad * sin(angle)
bind dr = 1.2 + 2.0 * (1.0 - r / (rings + 1.0))
bind s = s + "<circle cx='" + f(px) + "' cy='" + f(py) + "' r='" + f(dr) + "' fill='" + color + "' opacity='0.8'/>"
bind d = d + 1
}
bind r = r + 1
}
return s
}
fn pat_barcode(ox, oy, w, h, color, phase) {
bind s = ""
bind n = 40
bind bw = w / n
bind i = 0
while i < n {
bind t = i / n
bind bh = h * (0.25 + 0.75 * abs(sin(t * 6.28 * 6.0 + phase * 31.4)))
bind bx = ox + i * bw
bind by = oy + (h - bh) * 0.5
bind op = 0.3 + 0.65 * abs(sin(t * 3.14 + phase * 6.28))
bind s = s + "<rect x='" + f(bx) + "' y='" + f(by) + "' width='" + f(bw * 0.62) + "' height='" + f(bh) + "' fill='" + color + "' opacity='" + f(op) + "'/>"
bind i = i + 1
}
return s
}
fn pat_branch_lines(ox, oy, w, h, color, phase) {
bind s = ""
bind n = 18
bind cx = ox + w * 0.5
bind cy = oy + h * 0.88
bind i = 0
while i < n {
bind frac = i / n
bind angle = -1.5708 + (frac - 0.5) * 2.2 + phase * 0.4
bind len = h * (0.5 + 0.42 * abs(sin(i * 0.71 + phase * 3.14)))
bind x2 = cx + len * cos(angle)
bind y2 = cy + len * sin(angle)
bind op = 0.25 + 0.6 * abs(sin(i * 0.5 + phase * 6.28))
bind sw = 0.6 + 1.4 * abs(sin(i * 0.9 + phase * 2.0))
bind s = s + "<line x1='" + f(cx) + "' y1='" + f(cy) + "' x2='" + f(x2) + "' y2='" + f(y2) + "' stroke='" + color + "' stroke-width='" + f(sw) + "' opacity='" + f(op) + "'/>"
bind i = i + 1
}
return s
}
fn safe_id(name) {
bind s = str_replace(name, " ", "_")
bind s = str_replace(s, "(", "_")
bind s = str_replace(s, ")", "_")
bind s = str_replace(s, "{", "_")
bind s = str_replace(s, "}", "_")
bind s = str_replace(s, "-", "_")
bind s = str_replace(s, ".", "_")
return s
}
// ─── Render one function panel ───────────────────────────────────────────────
fn render_panel(fn_name, fn_body, px, py, pw, ph) {
bind hue = hash_str(fn_name)
bind hue2 = hash_str(fn_name + "b")
bind pt = floor(hash_int(fn_name + "pt", 6))
bind accent = detect_vtex(fn_body)
bind fg = hsl_color(hue, 90.0, 128.0)
bind bg = hsl_color(hue2, 20.0, 30.0)
bind id = safe_id(fn_name)
bind s = ""
// Panel background
bind s = s + "<rect x='" + f(px) + "' y='" + f(py) + "' width='" + f(pw) + "' height='" + f(ph) + "' fill='" + bg + "' fill-opacity='0.18' rx='5'/>"
bind s = s + "<rect x='" + f(px) + "' y='" + f(py) + "' width='" + f(pw) + "' height='" + f(ph) + "' fill='none' stroke='" + fg + "' stroke-width='0.8' rx='5' opacity='0.5'/>"
// Accent stripe (vtex color)
bind s = s + "<rect x='" + f(px) + "' y='" + f(py) + "' width='4' height='" + f(ph) + "' fill='" + accent + "' rx='3' opacity='0.9'/>"
// Clip rect for pattern area
bind clip_id = "clip_" + id
bind s = s + "<clipPath id='" + clip_id + "'><rect x='" + f(px + 5.0) + "' y='" + f(py + 2.0) + "' width='" + f(pw - 7.0) + "' height='" + f(ph - 18.0) + "'/></clipPath>"
bind s = s + "<g clip-path='url(#" + clip_id + ")'>"
// Pattern body
bind pattern = if pt == 0 {
pat_dot_grid(px + 5.0, py + 2.0, pw - 7.0, ph - 18.0, fg, hue)
} else {
if pt == 1 {
pat_waveform(px + 5.0, py + 2.0, pw - 7.0, ph - 18.0, fg, hue)
} else {
if pt == 2 {
pat_spectral_grid(px + 5.0, py + 2.0, pw - 7.0, ph - 18.0, fg, hue)
} else {
if pt == 3 {
pat_radial_rings(px + 5.0, py + 2.0, pw - 7.0, ph - 18.0, fg, hue)
} else {
if pt == 4 {
pat_barcode(px + 5.0, py + 2.0, pw - 7.0, ph - 18.0, fg, hue)
} else {
pat_branch_lines(px + 5.0, py + 2.0, pw - 7.0, ph - 18.0, fg, hue)
}
}
}
}
}
bind s = s + pattern
bind s = s + "</g>"
// Function label at bottom
bind label_y = py + ph - 5.0
bind s = s + "<text x='" + f(px + 8.0) + "' y='" + f(label_y) + "' font-family='monospace' font-size='8.5' fill='" + accent + "' opacity='0.95'>" + fn_name + "</text>"
return s
}
// ─── Extract basename from path ──────────────────────────────────────────────
fn extract_basename(path) {
bind result = path
bind searching = 1
while searching == 1 {
bind pos = str_find(result, "/")
bind result = if pos >= 0 { substr(result, pos + 1, 9999) } else { result }
bind searching = if pos >= 0 { 1 } else { 0 }
}
bind searching = 1
while searching == 1 {
bind pos = str_find(result, "\\")
bind result = if pos >= 0 { substr(result, pos + 1, 9999) } else { result }
bind searching = if pos >= 0 { 1 } else { 0 }
}
return result
}
// ─── Entry point ─────────────────────────────────────────────────────────────
bind start = do {
// ── Parse CLI args ──────────────────────────────────────────────────────
bind all_args = get_args()
bind n_args = len(all_args)
bind input = ""
bind output = ""
bind i = 1
while i < n_args {
bind a = list_get(all_args, i)
bind is_ling = ends_with(a, ".ling")
bind is_self = ends_with(a, "visualize.ling")
bind is_svg = ends_with(a, ".svg")
bind input = if is_ling {
if is_self { input }
else {
if input == "" { a } else { input }
}
} else { input }
bind output = if is_svg {
if output == "" { a } else { output }
} else { output }
bind i = i + 1
}
if input == "" {
print("usage: ling run examples/visualize.ling <input.ling> [output.svg]")
return ()
}
bind output = if output == "" { input + ".svg" } else { output }
// ── Read source ─────────────────────────────────────────────────────────
bind source = read_file(input)
bind lines = split(source, "\n")
bind n_lines = len(lines)
// ── Scan for function / entry-point definitions ─────────────────────────
bind fn_names = list_new()
bind fn_bodies = list_new()
bind current_fn = ""
bind current_body = ""
bind i = 0
while i < n_lines {
bind line = list_get(lines, i)
bind trimmed = trim(line)
// Detect function start: "fn name(" or Thai/Chinese variants
bind is_fn = starts_with(trimmed, "fn ")
bind is_th = starts_with(trimmed, "ฟังก์ชัน ")
bind is_zh = starts_with(trimmed, "函 ")
bind is_ko = starts_with(trimmed, "함수 ")
bind is_new_def = is_fn || is_th || is_zh || is_ko
// Save the previous function when starting a new definition
bind fn_names = if is_new_def {
if current_fn != "" { list_push(fn_names, current_fn) } else { fn_names }
} else { fn_names }
bind fn_bodies = if is_new_def {
if current_fn != "" { list_push(fn_bodies, current_body) } else { fn_bodies }
} else { fn_bodies }
// Compute name prefix offset (number of Unicode chars in keyword + space)
// ฟังก์ชัน = 8 chars + 1 space = 9; 함수 = 2 chars + 1 space = 3
bind kw_len = if is_fn { 3 }
else {
if is_th { 9 }
else {
if is_zh { 2 }
else { 3 }
}
}
// Extract new function name
bind pos_open = str_find(trimmed, "(")
bind raw_name = if pos_open >= 0 {
substr(trimmed, kw_len, pos_open - kw_len)
} else {
substr(trimmed, kw_len, 60)
}
bind new_fn_name = trim(raw_name)
// Update state
bind current_fn = if is_new_def { new_fn_name } else { current_fn }
bind current_body = if is_new_def { "" } else {
if current_fn != "" { current_body + " " + trimmed } else { current_body }
}
bind i = i + 1
}
// Save the last function
bind fn_names = if current_fn != "" { list_push(fn_names, current_fn) } else { fn_names }
bind fn_bodies = if current_fn != "" { list_push(fn_bodies, current_body) } else { fn_bodies }
bind n_fns = len(fn_names)
print("found " + to_str(n_fns) + " functions in " + input)
// ── SVG layout ──────────────────────────────────────────────────────────
bind cols = 4.0
bind cell_w = 200.0
bind cell_h = 200.0
bind pad = 10.0
bind sidebar = 170.0
bind rows = ceil(n_fns / cols)
bind total_w = sidebar + pad + cols * (cell_w + pad)
bind total_h = 60.0 + rows * (cell_h + pad) + 50.0
// ── Build SVG ───────────────────────────────────────────────────────────
bind svg = ""
bind svg = svg + "<svg xmlns='http://www.w3.org/2000/svg' width='" + f(total_w) + "' height='" + f(total_h) + "'>\n"
// Defs: glow filter + background grid
bind svg = svg + "<defs>"
bind svg = svg + "<filter id='glow' x='-20%' y='-20%' width='140%' height='140%'>"
bind svg = svg + "<feGaussianBlur stdDeviation='3' result='b'/>"
bind svg = svg + "<feMerge><feMergeNode in='b'/><feMergeNode in='SourceGraphic'/></feMerge>"
bind svg = svg + "</filter>"
bind svg = svg + "<pattern id='bg_grid' width='20' height='20' patternUnits='userSpaceOnUse'>"
bind svg = svg + "<path d='M 20 0 L 0 0 0 20' fill='none' stroke='#ffffff' stroke-width='0.2' opacity='0.06'/>"
bind svg = svg + "</pattern>"
bind svg = svg + "</defs>\n"
// Background
bind svg = svg + "<rect width='" + f(total_w) + "' height='" + f(total_h) + "' fill='#090912'/>\n"
bind svg = svg + "<rect width='" + f(total_w) + "' height='" + f(total_h) + "' fill='url(#bg_grid)'/>\n"
// Sidebar background
bind svg = svg + "<rect x='0' y='0' width='" + f(sidebar) + "' height='" + f(total_h) + "' fill='#0c0c1f'/>\n"
// Title
bind basename = extract_basename(input)
bind svg = svg + "<text x='12' y='34' font-family='monospace' font-size='16' fill='#e8e8f8' font-weight='bold'>" + basename + "</text>\n"
bind svg = svg + "<text x='12' y='50' font-family='monospace' font-size='10' fill='#6688aa'>" + to_str(n_fns) + " functions</text>\n"
// Sidebar legend header
bind svg = svg + "<text x='8' y='74' font-family='monospace' font-size='9' fill='#aaaacc' font-weight='bold'>PATTERNS</text>\n"
bind legend_items = list_new()
bind legend_items = list_push(legend_items, "dot grid")
bind legend_items = list_push(legend_items, "waveform")
bind legend_items = list_push(legend_items, "spectral")
bind legend_items = list_push(legend_items, "radial")
bind legend_items = list_push(legend_items, "barcode")
bind legend_items = list_push(legend_items, "branches")
bind li = 0
while li < 6 {
bind lhue = li / 6.0
bind lc = hsl_color(lhue, 100.0, 128.0)
bind ly = 88.0 + li * 16.0
bind svg = svg + "<rect x='8' y='" + f(ly - 6.0) + "' width='10' height='10' fill='" + lc + "' rx='2' opacity='0.8'/>"
bind svg = svg + "<text x='22' y='" + f(ly + 2.0) + "' font-family='monospace' font-size='8.5' fill='" + lc + "'>" + list_get(legend_items, li) + "</text>\n"
bind li = li + 1
}
// ── Render function panels ──────────────────────────────────────────────
bind i = 0
while i < n_fns {
bind fn_name = list_get(fn_names, i)
bind fn_body = list_get(fn_bodies, i)
bind col_idx = i % cols
bind row_idx = floor(i / cols)
bind px = sidebar + pad + col_idx * (cell_w + pad)
bind py = 60.0 + row_idx * (cell_h + pad)
bind panel = render_panel(fn_name, fn_body, px, py, cell_w, cell_h)
bind svg = svg + panel + "\n"
bind i = i + 1
}
// ── Registration marks (corners) ────────────────────────────────────────
bind svg = svg + reg_mark(12.0, 12.0)
bind svg = svg + reg_mark(total_w - 12.0, 12.0)
bind svg = svg + reg_mark(12.0, total_h - 12.0)
bind svg = svg + reg_mark(total_w - 12.0, total_h - 12.0)
// ── Footer ──────────────────────────────────────────────────────────────
bind svg = svg + "<text x='" + f(total_w * 0.5) + "' y='" + f(total_h - 8.0) + "' text-anchor='middle' font-family='monospace' font-size='8' fill='#333355'>ling visualize · " + basename + "</text>\n"
bind svg = svg + "</svg>"
// ── Write output ────────────────────────────────────────────────────────
write_file(output, svg)
print("written: " + output)
}