Compare commits
13 Commits
bfbe73b58a
...
cf4c8a1517
| Author | SHA1 | Date |
|---|---|---|
|
|
cf4c8a1517 | |
|
|
6a0f940bd0 | |
|
|
d609d8be68 | |
|
|
040d759cab | |
|
|
01d9c5a90e | |
|
|
7d89e76f6c | |
|
|
0c06106280 | |
|
|
813985a6dc | |
|
|
040ae9f17d | |
|
|
0221865a5c | |
|
|
784ed4949e | |
|
|
0db7298edf | |
|
|
8f0eca7916 |
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Compiled binary
|
||||||
|
/nipbox
|
||||||
|
*.exe
|
||||||
|
|
||||||
|
# Nim build artifacts
|
||||||
|
nimcache/
|
||||||
|
build/
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# IDE / Editor
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*~
|
||||||
|
|
||||||
|
# Agent / internal
|
||||||
|
.agent/
|
||||||
|
.claude/
|
||||||
|
.kiro/
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
// recipes/nipbox/nipbox-shell.kdl
|
||||||
|
// Multi-call binary with echo/cat/ls/cp, linked to libnexus.a.
|
||||||
|
|
||||||
|
package "nipbox-shell" {
|
||||||
|
version "0.1.0"
|
||||||
|
description "Sovereign Userland Shell"
|
||||||
|
|
||||||
|
binary "nipbox" {
|
||||||
|
source "src/nipbox.nim"
|
||||||
|
type "multicall"
|
||||||
|
|
||||||
|
commands {
|
||||||
|
cmd "echo"
|
||||||
|
cmd "cat"
|
||||||
|
cmd "ls"
|
||||||
|
cmd "cp"
|
||||||
|
cmd "mv"
|
||||||
|
cmd "rm"
|
||||||
|
}
|
||||||
|
|
||||||
|
link {
|
||||||
|
library "libnexus.a"
|
||||||
|
static true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
// recipes/nipbox/nipbox-variants.kdl
|
||||||
|
// USE flags for minimal/desktop modes.
|
||||||
|
|
||||||
|
variants "nipbox-profiles" {
|
||||||
|
|
||||||
|
profile "minimal" {
|
||||||
|
description "Minimal command-line environment"
|
||||||
|
use {
|
||||||
|
gui false
|
||||||
|
network true
|
||||||
|
ipv6 false
|
||||||
|
debug false
|
||||||
|
}
|
||||||
|
packages {
|
||||||
|
include "nipbox-shell"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
profile "desktop" {
|
||||||
|
description "Full desktop environment support"
|
||||||
|
use {
|
||||||
|
gui true
|
||||||
|
wayland true
|
||||||
|
opengl true
|
||||||
|
audio true
|
||||||
|
}
|
||||||
|
packages {
|
||||||
|
include "nipbox-shell"
|
||||||
|
include "nexbox/nexbox-desktop"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,277 @@
|
||||||
|
# SPDX-License-Identifier: LUL-1.0
|
||||||
|
# Copyright (c) 2026 Markus Maiwald
|
||||||
|
# Stewardship: Self Sovereign Society Foundation
|
||||||
|
#
|
||||||
|
# This file is part of the Nexus SDK.
|
||||||
|
# See legal/LICENSE_UNBOUND.md for license terms.
|
||||||
|
|
||||||
|
# MARKUS MAIWALD (ARCHITECT) | VOXIS FORGE (AI)
|
||||||
|
# Scribe v3: The Sovereign TUI Editor
|
||||||
|
# Phase 24: Full TUI with Navigation & Multi-Sector IO
|
||||||
|
|
||||||
|
import strutils, sequtils
|
||||||
|
import ../../libs/membrane/libc as lb
|
||||||
|
|
||||||
|
# --- CONSTANTS ---
|
||||||
|
const
|
||||||
|
KEY_CTRL_Q = char(17)
|
||||||
|
KEY_CTRL_S = char(19)
|
||||||
|
KEY_CTRL_X = char(24) # Alternative exit
|
||||||
|
KEY_ESC = char(27)
|
||||||
|
KEY_BACKSPACE = char(127)
|
||||||
|
KEY_ENTER = char(13) # CR
|
||||||
|
KEY_LF = char(10)
|
||||||
|
|
||||||
|
# --- STATE ---
|
||||||
|
var lines: seq[string]
|
||||||
|
var cursor_x: int = 0
|
||||||
|
var cursor_y: int = 0 # Line index in buffer
|
||||||
|
var scroll_y: int = 0 # Index of top visible line
|
||||||
|
var screen_rows: int = 20 # Fixed for now, or detect?
|
||||||
|
var screen_cols: int = 80
|
||||||
|
var filename: string = ""
|
||||||
|
var status_msg: string = "CTRL-S: Save | CTRL-Q: Quit"
|
||||||
|
var is_running: bool = true
|
||||||
|
|
||||||
|
# --- TERMINAL HELPERS ---
|
||||||
|
|
||||||
|
proc write_raw(s: string) =
|
||||||
|
if s.len > 0:
|
||||||
|
discard lb.write(cint(1), cast[pointer](unsafeAddr s[0]), csize_t(s.len))
|
||||||
|
|
||||||
|
proc term_clear() =
|
||||||
|
write_raw("\x1b[2J") # Clear entire screen
|
||||||
|
|
||||||
|
proc term_move(row, col: int) =
|
||||||
|
# ANSI is 1-indexed
|
||||||
|
write_raw("\x1b[" & $(row + 1) & ";" & $(col + 1) & "H")
|
||||||
|
|
||||||
|
proc term_hide_cursor() = write_raw("\x1b[?25l")
|
||||||
|
proc term_show_cursor() = write_raw("\x1b[?25h")
|
||||||
|
|
||||||
|
# --- FILE IO ---
|
||||||
|
|
||||||
|
proc load_file(fname: string) =
|
||||||
|
lines = @[]
|
||||||
|
let fd = lb.open(fname.cstring, 0)
|
||||||
|
if fd >= 0:
|
||||||
|
var content = ""
|
||||||
|
var buf: array[512, char]
|
||||||
|
while true:
|
||||||
|
let n = lb.read(fd, addr buf[0], 512)
|
||||||
|
if n <= 0: break
|
||||||
|
for i in 0..<n: content.add(buf[i])
|
||||||
|
discard lb.close(fd)
|
||||||
|
|
||||||
|
if content.len > 0:
|
||||||
|
lines = content.splitLines()
|
||||||
|
else:
|
||||||
|
lines.add("")
|
||||||
|
else:
|
||||||
|
# New File
|
||||||
|
lines.add("")
|
||||||
|
|
||||||
|
if lines.len == 0: lines.add("")
|
||||||
|
|
||||||
|
proc save_file() =
|
||||||
|
var content = lines.join("\n")
|
||||||
|
# Ensure trailing newline often expected
|
||||||
|
if content.len > 0 and content[^1] != '\n': content.add('\n')
|
||||||
|
|
||||||
|
# FLAGS: O_WRONLY(1) | O_CREAT(64) | O_TRUNC(512) = 577
|
||||||
|
let fd = lb.open(filename.cstring, 577)
|
||||||
|
if fd < 0:
|
||||||
|
status_msg = "Error: Save Failed (VFS Open)."
|
||||||
|
return
|
||||||
|
|
||||||
|
let n = lb.write(fd, cast[pointer](unsafeAddr content[0]), csize_t(content.len))
|
||||||
|
discard lb.close(fd)
|
||||||
|
status_msg = "Saved " & $n & " bytes."
|
||||||
|
|
||||||
|
# --- LOGIC ---
|
||||||
|
|
||||||
|
proc scroll_to_cursor() =
|
||||||
|
if cursor_y < scroll_y:
|
||||||
|
scroll_y = cursor_y
|
||||||
|
if cursor_y >= scroll_y + screen_rows:
|
||||||
|
scroll_y = cursor_y - screen_rows + 1
|
||||||
|
|
||||||
|
proc render() =
|
||||||
|
term_hide_cursor()
|
||||||
|
term_move(0, 0)
|
||||||
|
|
||||||
|
# Draw Content
|
||||||
|
for i in 0..<screen_rows:
|
||||||
|
let line_idx = scroll_y + i
|
||||||
|
term_move(i, 0)
|
||||||
|
write_raw("\x1b[K") # Clear Line
|
||||||
|
|
||||||
|
if line_idx < lines.len:
|
||||||
|
var line = lines[line_idx]
|
||||||
|
# Truncate for display if needed? For now wrap or let terminal handle
|
||||||
|
if line.len > screen_cols: line = line[0..<screen_cols]
|
||||||
|
write_raw(line)
|
||||||
|
else:
|
||||||
|
write_raw("~") # Vim style empty lines
|
||||||
|
|
||||||
|
# Draw Status Bar
|
||||||
|
term_move(screen_rows, 0)
|
||||||
|
write_raw("\x1b[7m") # Invert
|
||||||
|
var bar = " " & filename & " - " & $cursor_x & ":" & $cursor_y & " | " & status_msg
|
||||||
|
while bar.len < screen_cols: bar.add(" ")
|
||||||
|
if bar.len > screen_cols: bar = bar[0..<screen_cols]
|
||||||
|
write_raw(bar)
|
||||||
|
write_raw("\x1b[0m") # Reset
|
||||||
|
|
||||||
|
# Position Cursor
|
||||||
|
term_move(cursor_y - scroll_y, cursor_x)
|
||||||
|
term_show_cursor()
|
||||||
|
|
||||||
|
proc insert_char(c: char) =
|
||||||
|
if cursor_y >= lines.len: lines.add("")
|
||||||
|
var line = lines[cursor_y]
|
||||||
|
if cursor_x > line.len: cursor_x = line.len
|
||||||
|
|
||||||
|
if cursor_x == line.len:
|
||||||
|
line.add(c)
|
||||||
|
else:
|
||||||
|
line.insert($c, cursor_x)
|
||||||
|
|
||||||
|
lines[cursor_y] = line
|
||||||
|
cursor_x += 1
|
||||||
|
|
||||||
|
proc insert_newline() =
|
||||||
|
if cursor_y >= lines.len: lines.add("") # Should catch
|
||||||
|
|
||||||
|
let current_line = lines[cursor_y]
|
||||||
|
if cursor_x >= current_line.len:
|
||||||
|
# Append new empty line
|
||||||
|
lines.insert("", cursor_y + 1)
|
||||||
|
else:
|
||||||
|
# Split line
|
||||||
|
let left = current_line[0..<cursor_x]
|
||||||
|
let right = current_line[cursor_x..^1]
|
||||||
|
lines[cursor_y] = left
|
||||||
|
lines.insert(right, cursor_y + 1)
|
||||||
|
|
||||||
|
cursor_y += 1
|
||||||
|
cursor_x = 0
|
||||||
|
|
||||||
|
proc backspace() =
|
||||||
|
if cursor_y >= lines.len: return
|
||||||
|
|
||||||
|
if cursor_x > 0:
|
||||||
|
var line = lines[cursor_y]
|
||||||
|
# Delete char at x-1
|
||||||
|
if cursor_x - 1 < line.len:
|
||||||
|
line.delete(cursor_x - 1, cursor_x - 1)
|
||||||
|
lines[cursor_y] = line
|
||||||
|
cursor_x -= 1
|
||||||
|
elif cursor_y > 0:
|
||||||
|
# Merge with previous line
|
||||||
|
let current = lines[cursor_y]
|
||||||
|
let prev_len = lines[cursor_y - 1].len
|
||||||
|
lines[cursor_y - 1].add(current)
|
||||||
|
lines.delete(cursor_y)
|
||||||
|
cursor_y -= 1
|
||||||
|
cursor_x = prev_len
|
||||||
|
|
||||||
|
proc handle_arrow(code: char) =
|
||||||
|
case code:
|
||||||
|
of 'A': # UP
|
||||||
|
if cursor_y > 0: cursor_y -= 1
|
||||||
|
of 'B': # DOWN
|
||||||
|
if cursor_y < lines.len - 1: cursor_y += 1
|
||||||
|
of 'C': # RIGHT
|
||||||
|
if cursor_y < lines.len:
|
||||||
|
if cursor_x < lines[cursor_y].len: cursor_x += 1
|
||||||
|
elif cursor_y < lines.len - 1: # Wrap to next line
|
||||||
|
cursor_y += 1
|
||||||
|
cursor_x = 0
|
||||||
|
of 'D': # LEFT
|
||||||
|
if cursor_x > 0: cursor_x -= 1
|
||||||
|
elif cursor_y > 0: # Wrap to prev line end
|
||||||
|
cursor_y -= 1
|
||||||
|
cursor_x = lines[cursor_y].len
|
||||||
|
else: discard
|
||||||
|
|
||||||
|
# Snap cursor to line length
|
||||||
|
if cursor_y < lines.len:
|
||||||
|
if cursor_x > lines[cursor_y].len: cursor_x = lines[cursor_y].len
|
||||||
|
|
||||||
|
# --- MAIN LOOP ---
|
||||||
|
|
||||||
|
proc read_input() =
|
||||||
|
# We need a custom input loop that handles escapes
|
||||||
|
# This uses libc.read on fd 0 (stdin)
|
||||||
|
|
||||||
|
var c: char
|
||||||
|
let n = lb.read(0, addr c, 1)
|
||||||
|
if n <= 0: return
|
||||||
|
|
||||||
|
if c == KEY_CTRL_Q or c == KEY_CTRL_X:
|
||||||
|
is_running = false
|
||||||
|
return
|
||||||
|
|
||||||
|
if c == KEY_CTRL_S:
|
||||||
|
save_file()
|
||||||
|
return
|
||||||
|
|
||||||
|
if c == KEY_ESC:
|
||||||
|
# Potential Sequence
|
||||||
|
# Busy wait briefly for next char to confirm sequence vs lone ESC
|
||||||
|
# In a real OS we'd have poll/timeout. Here we hack.
|
||||||
|
# Actually, let's just try to read immediately.
|
||||||
|
var c2: char
|
||||||
|
let n2 = lb.read(0, addr c2, 1) # This might block if not buffered?
|
||||||
|
# Our lb.read is non-blocking if ring is empty, returns 0.
|
||||||
|
# But for a sequence, the chars should be in the packet together or close.
|
||||||
|
# If 0, it was just ESC.
|
||||||
|
|
||||||
|
if n2 > 0 and c2 == '[':
|
||||||
|
var c3: char
|
||||||
|
let n3 = lb.read(0, addr c3, 1)
|
||||||
|
if n3 > 0:
|
||||||
|
handle_arrow(c3)
|
||||||
|
return
|
||||||
|
|
||||||
|
if c == KEY_BACKSPACE or c == '\b':
|
||||||
|
backspace()
|
||||||
|
return
|
||||||
|
|
||||||
|
if c == KEY_ENTER or c == KEY_LF:
|
||||||
|
insert_newline()
|
||||||
|
return
|
||||||
|
|
||||||
|
# Normal char
|
||||||
|
if c >= ' ' and c <= '~':
|
||||||
|
insert_char(c)
|
||||||
|
|
||||||
|
proc start_editor*(fname: string) =
|
||||||
|
filename = fname
|
||||||
|
is_running = true
|
||||||
|
cursor_x = 0
|
||||||
|
cursor_y = 0
|
||||||
|
scroll_y = 0
|
||||||
|
status_msg = "CTRL-S: Save | CTRL-Q: Quit"
|
||||||
|
|
||||||
|
write_raw("[Scribe] Loading " & fname & "...\n")
|
||||||
|
load_file(fname)
|
||||||
|
|
||||||
|
term_clear()
|
||||||
|
|
||||||
|
while is_running:
|
||||||
|
# lb.pump_membrane_stack() - Handled by Kernel
|
||||||
|
scroll_to_cursor()
|
||||||
|
render()
|
||||||
|
|
||||||
|
# Input Loop (Non-blocking check mostly)
|
||||||
|
# We loop quickly to feel responsive
|
||||||
|
read_input()
|
||||||
|
|
||||||
|
# Yield slightly
|
||||||
|
for i in 0..5000: discard
|
||||||
|
|
||||||
|
term_clear()
|
||||||
|
term_move(0, 0)
|
||||||
|
write_raw("Scribe Closed.\n")
|
||||||
|
|
@ -0,0 +1,256 @@
|
||||||
|
# SPDX-License-Identifier: LUL-1.0
|
||||||
|
# Copyright (c) 2026 Markus Maiwald
|
||||||
|
# Stewardship: Self Sovereign Society Foundation
|
||||||
|
#
|
||||||
|
# This file is part of the Nexus SDK.
|
||||||
|
# See legal/LICENSE_UNBOUND.md for license terms.
|
||||||
|
|
||||||
|
# MARKUS MAIWALD (ARCHITECT) | VOXIS FORGE (AI)
|
||||||
|
# NipBox KDL Core (The Semantic Spine)
|
||||||
|
# Defines the typed object system for the Sovereign Shell.
|
||||||
|
|
||||||
|
import strutils
|
||||||
|
import std/assertions
|
||||||
|
|
||||||
|
type
|
||||||
|
ValueKind* = enum
|
||||||
|
VString, VInt, VBool, VNull
|
||||||
|
|
||||||
|
Value* = object
|
||||||
|
case kind*: ValueKind
|
||||||
|
of VString: s*: string
|
||||||
|
of VInt: i*: int
|
||||||
|
of VBool: b*: bool
|
||||||
|
of VNull: discard
|
||||||
|
|
||||||
|
# A KDL Node: name arg1 arg2 key=val { children }
|
||||||
|
Node* = ref object
|
||||||
|
name*: string
|
||||||
|
args*: seq[Value]
|
||||||
|
props*: seq[tuple[key: string, val: Value]]
|
||||||
|
children*: seq[Node]
|
||||||
|
|
||||||
|
# --- Constructors ---
|
||||||
|
|
||||||
|
proc newVal*(s: string): Value = Value(kind: VString, s: s)
|
||||||
|
proc newVal*(i: int): Value = Value(kind: VInt, i: i)
|
||||||
|
proc newVal*(b: bool): Value = Value(kind: VBool, b: b)
|
||||||
|
proc newNull*(): Value = Value(kind: VNull)
|
||||||
|
|
||||||
|
proc newNode*(name: string): Node =
|
||||||
|
new(result)
|
||||||
|
result.name = name
|
||||||
|
result.args = @[]
|
||||||
|
result.props = @[]
|
||||||
|
result.children = @[]
|
||||||
|
|
||||||
|
proc addArg*(n: Node, v: Value) =
|
||||||
|
n.args.add(v)
|
||||||
|
|
||||||
|
proc addProp*(n: Node, key: string, v: Value) =
|
||||||
|
n.props.add((key, v))
|
||||||
|
|
||||||
|
proc addChild*(n: Node, child: Node) =
|
||||||
|
n.children.add(child)
|
||||||
|
|
||||||
|
# --- Serialization (The Renderer) ---
|
||||||
|
|
||||||
|
proc `$`*(v: Value): string =
|
||||||
|
case v.kind
|
||||||
|
of VString: "\"" & v.s & "\"" # TODO: Escape quotes properly
|
||||||
|
of VInt: $v.i
|
||||||
|
of VBool: $v.b
|
||||||
|
of VNull: "null"
|
||||||
|
|
||||||
|
proc render*(n: Node, indent: int = 0): string =
|
||||||
|
let prefix = repeat(' ', indent)
|
||||||
|
var line = prefix & n.name
|
||||||
|
|
||||||
|
# Args
|
||||||
|
for arg in n.args:
|
||||||
|
line.add(" " & $arg)
|
||||||
|
|
||||||
|
# Props
|
||||||
|
for prop in n.props:
|
||||||
|
line.add(" " & prop.key & "=" & $prop.val)
|
||||||
|
|
||||||
|
# Children
|
||||||
|
if n.children.len > 0:
|
||||||
|
line.add(" {\n")
|
||||||
|
for child in n.children:
|
||||||
|
line.add(render(child, indent + 2))
|
||||||
|
line.add(prefix & "}\n")
|
||||||
|
else:
|
||||||
|
line.add("\n")
|
||||||
|
|
||||||
|
return line
|
||||||
|
|
||||||
|
# Table View (For Flat Lists)
|
||||||
|
proc renderTable*(nodes: seq[Node]): string =
|
||||||
|
var s = ""
|
||||||
|
for n in nodes:
|
||||||
|
s.add(render(n))
|
||||||
|
return s
|
||||||
|
|
||||||
|
# --- Parser ---
|
||||||
|
|
||||||
|
type Parser = ref object
|
||||||
|
input: string
|
||||||
|
pos: int
|
||||||
|
|
||||||
|
proc peek(p: Parser): char =
|
||||||
|
if p.pos >= p.input.len: return '\0'
|
||||||
|
return p.input[p.pos]
|
||||||
|
|
||||||
|
proc next(p: Parser): char =
|
||||||
|
if p.pos >= p.input.len: return '\0'
|
||||||
|
result = p.input[p.pos]
|
||||||
|
p.pos.inc
|
||||||
|
|
||||||
|
proc skipSpace(p: Parser) =
|
||||||
|
while true:
|
||||||
|
let c = p.peek()
|
||||||
|
if c == ' ' or c == '\t' or c == '\r': discard p.next()
|
||||||
|
else: break
|
||||||
|
|
||||||
|
proc parseIdentifier(p: Parser): string =
|
||||||
|
# Simple identifier: strictly alphanumeric + _ - for now
|
||||||
|
# TODO: Quoted identifiers
|
||||||
|
if p.peek() == '"':
|
||||||
|
discard p.next()
|
||||||
|
while true:
|
||||||
|
let c = p.next()
|
||||||
|
if c == '\0': break
|
||||||
|
if c == '"': break
|
||||||
|
result.add(c)
|
||||||
|
else:
|
||||||
|
while true:
|
||||||
|
let c = p.peek()
|
||||||
|
if c in {'a'..'z', 'A'..'Z', '0'..'9', '_', '-', '.', '/'}:
|
||||||
|
result.add(p.next())
|
||||||
|
else: break
|
||||||
|
|
||||||
|
proc parseValue(p: Parser): Value =
|
||||||
|
skipSpace(p)
|
||||||
|
let c = p.peek()
|
||||||
|
if c == '"':
|
||||||
|
# String
|
||||||
|
discard p.next()
|
||||||
|
var s = ""
|
||||||
|
while true:
|
||||||
|
let ch = p.next()
|
||||||
|
if ch == '\0': break
|
||||||
|
if ch == '"': break
|
||||||
|
s.add(ch)
|
||||||
|
return newVal(s)
|
||||||
|
elif c in {'0'..'9', '-'}:
|
||||||
|
# Number (Int only for now)
|
||||||
|
var s = ""
|
||||||
|
s.add(p.next())
|
||||||
|
while p.peek() in {'0'..'9'}:
|
||||||
|
s.add(p.next())
|
||||||
|
try:
|
||||||
|
return newVal(parseInt(s))
|
||||||
|
except:
|
||||||
|
return newVal(0)
|
||||||
|
elif c == 't': # true
|
||||||
|
if p.input.substr(p.pos, p.pos+3) == "true":
|
||||||
|
p.pos += 4
|
||||||
|
return newVal(true)
|
||||||
|
elif c == 'f': # false
|
||||||
|
if p.input.substr(p.pos, p.pos+4) == "false":
|
||||||
|
p.pos += 5
|
||||||
|
return newVal(false)
|
||||||
|
elif c == 'n': # null
|
||||||
|
if p.input.substr(p.pos, p.pos+3) == "null":
|
||||||
|
p.pos += 4
|
||||||
|
return newNull()
|
||||||
|
|
||||||
|
# Fallback: Bare string identifier
|
||||||
|
return newVal(parseIdentifier(p))
|
||||||
|
|
||||||
|
proc parseNode(p: Parser): Node =
|
||||||
|
skipSpace(p)
|
||||||
|
let name = parseIdentifier(p)
|
||||||
|
if name.len == 0: return nil
|
||||||
|
|
||||||
|
var node = newNode(name)
|
||||||
|
|
||||||
|
while true:
|
||||||
|
skipSpace(p)
|
||||||
|
let c = p.peek()
|
||||||
|
if c == '\n' or c == ';' or c == '}' or c == '\0': break
|
||||||
|
if c == '{': break # Children start
|
||||||
|
|
||||||
|
# Arg or Prop?
|
||||||
|
# Peek ahead to see if next is identifier=value
|
||||||
|
# Simple heuristic: parse identifier, if next char is '=', it's a prop.
|
||||||
|
let startPos = p.pos
|
||||||
|
let id = parseIdentifier(p)
|
||||||
|
if id.len > 0 and p.peek() == '=':
|
||||||
|
# Property
|
||||||
|
discard p.next() # skip =
|
||||||
|
let val = parseValue(p)
|
||||||
|
node.addProp(id, val)
|
||||||
|
else:
|
||||||
|
# Argument
|
||||||
|
# Backtrack? Or realize we parsed a value?
|
||||||
|
# If `id` was a bare string value, it works.
|
||||||
|
# If `id` was quoted string, `parseIdentifier` handled it.
|
||||||
|
# But `parseValue` handles numbers/bools too. `parseIdentifier` does NOT.
|
||||||
|
|
||||||
|
# Better approach:
|
||||||
|
# Reset pos
|
||||||
|
p.pos = startPos
|
||||||
|
# Check if identifier followed by =
|
||||||
|
# We need a proper lookahead for keys.
|
||||||
|
# For now, simplistic:
|
||||||
|
|
||||||
|
let val = parseValue(p)
|
||||||
|
# Check if we accidentally parsed a key?
|
||||||
|
# If val is string, and next char is '=', convert to key?
|
||||||
|
if val.kind == VString and p.peek() == '=':
|
||||||
|
discard p.next()
|
||||||
|
let realVal = parseValue(p)
|
||||||
|
node.addProp(val.s, realVal)
|
||||||
|
else:
|
||||||
|
node.addArg(val)
|
||||||
|
|
||||||
|
# Children
|
||||||
|
skipSpace(p)
|
||||||
|
if p.peek() == '{':
|
||||||
|
discard p.next() # skip {
|
||||||
|
while true:
|
||||||
|
skipSpace(p)
|
||||||
|
if p.peek() == '}':
|
||||||
|
discard p.next()
|
||||||
|
break
|
||||||
|
skipSpace(p)
|
||||||
|
# Skip newlines
|
||||||
|
while p.peek() == '\n': discard p.next()
|
||||||
|
if p.peek() == '}':
|
||||||
|
discard p.next()
|
||||||
|
break
|
||||||
|
let child = parseNode(p)
|
||||||
|
if child != nil:
|
||||||
|
node.addChild(child)
|
||||||
|
else:
|
||||||
|
# Check if just newline?
|
||||||
|
if p.peek() == '\n': discard p.next()
|
||||||
|
else: break # Error or empty
|
||||||
|
|
||||||
|
return node
|
||||||
|
|
||||||
|
proc parseKdl*(input: string): seq[Node] =
|
||||||
|
var p = Parser(input: input, pos: 0)
|
||||||
|
result = @[]
|
||||||
|
while true:
|
||||||
|
skipSpace(p)
|
||||||
|
while p.peek() == '\n' or p.peek() == ';': discard p.next()
|
||||||
|
if p.peek() == '\0': break
|
||||||
|
|
||||||
|
let node = parseNode(p)
|
||||||
|
if node != nil:
|
||||||
|
result.add(node)
|
||||||
|
else:
|
||||||
|
break
|
||||||
1162
src/nipbox.nim
1162
src/nipbox.nim
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,89 @@
|
||||||
|
# SPDX-License-Identifier: LUL-1.0
|
||||||
|
# Copyright (c) 2026 Markus Maiwald
|
||||||
|
# Stewardship: Self Sovereign Society Foundation
|
||||||
|
#
|
||||||
|
# This file is part of the Nexus SDK.
|
||||||
|
# See legal/LICENSE_UNBOUND.md for license terms.
|
||||||
|
|
||||||
|
# src/npl/nipbox/std.nim
|
||||||
|
# Adapter for Legacy Code -> New LibC
|
||||||
|
|
||||||
|
type cptr* = pointer
|
||||||
|
|
||||||
|
# LibC Imports
|
||||||
|
proc write*(fd: cint, buf: cptr, count: csize_t): int {.importc, cdecl.}
|
||||||
|
proc read*(fd: cint, buf: cptr, count: csize_t): int {.importc, cdecl.}
|
||||||
|
proc open*(pathname: cstring, flags: cint): cint {.importc, cdecl.}
|
||||||
|
proc close*(fd: cint): cint {.importc, cdecl.}
|
||||||
|
proc exit*(status: cint) {.importc, cdecl.}
|
||||||
|
proc nexus_list*(buf: pointer, len: int): int {.importc, cdecl.}
|
||||||
|
proc syscall(nr: int, a0: int = 0, a1: int = 0, a2: int = 0): int {.importc, cdecl.}
|
||||||
|
|
||||||
|
# Legacy Aliases
|
||||||
|
proc list_files*(buf: pointer, len: uint64): int64 =
|
||||||
|
return int64(nexus_list(buf, int(len)))
|
||||||
|
|
||||||
|
proc nexus_syscall*(cmd: cint, arg: uint64): cint =
|
||||||
|
return cint(syscall(int(cmd), int(arg), 0, 0))
|
||||||
|
|
||||||
|
proc nexus_yield*() =
|
||||||
|
discard syscall(0, 0) # Exit/Yield
|
||||||
|
|
||||||
|
type FileArgs* = object
|
||||||
|
name*: uint64
|
||||||
|
data*: uint64
|
||||||
|
len*: uint64
|
||||||
|
|
||||||
|
proc nexus_file_write*(name: string, data: string) =
|
||||||
|
# Disabled
|
||||||
|
return
|
||||||
|
|
||||||
|
proc nexus_file_read*(name: string, buffer: pointer, max_len: uint64): int =
|
||||||
|
# Disabled
|
||||||
|
return -1
|
||||||
|
|
||||||
|
# Print Helpers
|
||||||
|
proc print*(s: string) =
|
||||||
|
if s.len > 0:
|
||||||
|
discard write(cint(1), unsafeAddr s[0], csize_t(s.len))
|
||||||
|
var nl = "\n"
|
||||||
|
discard write(cint(1), unsafeAddr nl[0], 1)
|
||||||
|
|
||||||
|
proc print_raw*(s: string) =
|
||||||
|
if s.len > 0:
|
||||||
|
discard write(cint(1), unsafeAddr s[0], csize_t(s.len))
|
||||||
|
|
||||||
|
proc print_int*(n: int) =
|
||||||
|
var s = ""
|
||||||
|
var v = n
|
||||||
|
if v == 0: s = "0"
|
||||||
|
else:
|
||||||
|
while v > 0:
|
||||||
|
s.add(char((v mod 10) + 48))
|
||||||
|
v = v div 10
|
||||||
|
# Reverse
|
||||||
|
var r = ""
|
||||||
|
var i = s.len - 1
|
||||||
|
while i >= 0:
|
||||||
|
r.add(s[i])
|
||||||
|
i -= 1
|
||||||
|
print_raw(r)
|
||||||
|
|
||||||
|
proc my_readline*(out_str: var string): bool =
|
||||||
|
out_str = ""
|
||||||
|
while true:
|
||||||
|
var c: char
|
||||||
|
let n = read(cint(0), addr c, 1)
|
||||||
|
if n <= 0: return false
|
||||||
|
|
||||||
|
if c == '\n' or c == '\r':
|
||||||
|
print_raw("\n")
|
||||||
|
return true
|
||||||
|
elif c == '\b' or c == char(127):
|
||||||
|
if out_str.len > 0:
|
||||||
|
var bs = "\b \b"
|
||||||
|
discard write(cint(1), addr bs[0], 3)
|
||||||
|
out_str.setLen(out_str.len - 1)
|
||||||
|
else:
|
||||||
|
out_str.add(c)
|
||||||
|
discard write(cint(1), addr c, 1)
|
||||||
Loading…
Reference in New Issue