From 18ac230c21e3b2469632939c618a30ce4545682e Mon Sep 17 00:00:00 2001 From: hanez Date: Fri, 20 Mar 2026 20:57:43 +0100 Subject: [PATCH] More notcurses fun. Not stable! (0.39.4) --- CMakeLists.txt | 2 +- docs/external/notcurses.md | 9 ++ docs/opcodes.md | 8 ++ examples/extra/notcurses_menu.fun | 33 +++++++ examples/extra/notcurses_progress.fun | 38 ++++++++ lib/ui/notcurses.fun | 130 ++++++++++++++++++++++++++ src/bytecode.h | 10 +- src/funstx.c | 4 +- src/parser.c | 130 ++++++++++++++++++++++++-- src/vm.c | 8 ++ src/vm.h | 6 +- src/vm/notcurses/box.c | 66 +++++++++++++ src/vm/notcurses/draw_char.c | 40 ++++++++ src/vm/notcurses/fill.c | 45 +++++++++ src/vm/notcurses/get_size.c | 29 ++++++ src/vm/notcurses/hline.c | 39 ++++++++ src/vm/notcurses/render.c | 23 +++++ src/vm/notcurses/set_style.c | 71 ++++++++++++++ src/vm/notcurses/vline.c | 39 ++++++++ 19 files changed, 715 insertions(+), 15 deletions(-) create mode 100644 examples/extra/notcurses_menu.fun create mode 100644 examples/extra/notcurses_progress.fun create mode 100644 src/vm/notcurses/box.c create mode 100644 src/vm/notcurses/draw_char.c create mode 100644 src/vm/notcurses/fill.c create mode 100644 src/vm/notcurses/get_size.c create mode 100644 src/vm/notcurses/hline.c create mode 100644 src/vm/notcurses/render.c create mode 100644 src/vm/notcurses/set_style.c create mode 100644 src/vm/notcurses/vline.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 4de1877..f2621ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.10) -project(fun VERSION 0.39.3 LANGUAGES C) +project(fun VERSION 0.39.4 LANGUAGES C) set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) diff --git a/docs/external/notcurses.md b/docs/external/notcurses.md index 9985479..f845843 100644 --- a/docs/external/notcurses.md +++ b/docs/external/notcurses.md @@ -11,8 +11,17 @@ - OP_NC_CLEAR: clear screen/plane; returns 0 - OP_NC_DRAW_TEXT: pops text, x, y; draws; returns 0 - OP_NC_GETCH: pops timeout_ms; returns codepoint or -1 on timeout/error +- OP_NC_GET_SIZE: push [rows, cols] of the stdplane, or -1 if unavailable +- OP_NC_SET_STYLE: pops style, bg_rgb, fg_rgb; sets colors/styles for stdplane; returns 0/-1 +- OP_NC_DRAW_CHAR: pops ch, x, y; draws a single Unicode codepoint; returns 0/-1 +- OP_NC_DRAW_HLINE: pops len, x, y, ch; draws a horizontal line; returns 0/-1 +- OP_NC_DRAW_VLINE: pops len, x, y, ch; draws a vertical line; returns 0/-1 +- OP_NC_BOX: pops x, y, w, h, style; draws a rectangle using ASCII/Unicode box glyphs; returns 0/-1 +- OP_NC_FILL: pops x, y, w, h, ch; fills an area with a codepoint; returns 0/-1 +- OP_NC_RENDER: forces a render; returns 0/-1 ## Notes: - Requires Notcurses development headers/libs. - Behavior may vary across terminals; see the implementation for details. +- When Fun is built without Notcurses (FUN_WITH_NOTCURSES=OFF), these opcodes still exist but perform no-ops and return -1/0 as documented, allowing scripts to degrade gracefully. diff --git a/docs/opcodes.md b/docs/opcodes.md index 47e2b01..e4b3372 100644 --- a/docs/opcodes.md +++ b/docs/opcodes.md @@ -232,6 +232,14 @@ This document provides an overview of the available VM opcodes implemented under - OP_NC_CLEAR: Clear screen; pushes 1/0. - OP_NC_DRAW_TEXT: Draw text at (x,y); pops text, x, y; pushes 1/0. - OP_NC_GETCH: Get key with timeout; pops timeout_ms:int; pushes int key or -1. +- OP_NC_GET_SIZE: Get stdplane size; pushes [rows:int, cols:int] or -1 if unavailable. +- OP_NC_SET_STYLE: Set stdplane style/colors; pops style:int, bg_rgb:int, fg_rgb:int; pushes 0/-1. +- OP_NC_DRAW_CHAR: Draw a single codepoint at y,x; pops ch:int, x:int, y:int; pushes 0/-1. +- OP_NC_DRAW_HLINE: Draw horizontal line; pops len:int, x:int, y:int, ch:int; pushes 0/-1. +- OP_NC_DRAW_VLINE: Draw vertical line; pops len:int, x:int, y:int, ch:int; pushes 0/-1. +- OP_NC_BOX: Draw a rectangular box; pops x:int, y:int, w:int, h:int, style:int; pushes 0/-1. +- OP_NC_FILL: Fill rectangle with codepoint; pops x:int, y:int, w:int, h:int, ch:int; pushes 0/-1. +- OP_NC_RENDER: Force a render; pushes 0/-1. ## SQLite-compatible lib (libSQL) diff --git a/examples/extra/notcurses_menu.fun b/examples/extra/notcurses_menu.fun new file mode 100644 index 0000000..8c17449 --- /dev/null +++ b/examples/extra/notcurses_menu.fun @@ -0,0 +1,33 @@ +#!/usr/bin/env fun + +include + +n = Notcurses() +if n.init() == 0 + print("Notcurses not available. Build with -DFUN_WITH_NOTCURSES=ON.") + exit(1) + +items = ["First", "Second", "Third", "Fourth"] +sel = 0 +while true + n.clear() + // Title + n.set_style(0xFFFF00, 0x000000, 1) // bold yellow + n.draw_text(1, 2, "Demo Menu (j/k to move, Enter to select, ESC to quit)") + + // Render menu list + n.menu(3, 4, items, sel, 30, 0, 32) + + key = n.getch(0) + if key == 27 // ESC + break + if key == 106 // 'j' + sel = (sel + 1) % len(items) + if key == 107 // 'k' + sel = (sel - 1 + len(items)) % len(items) + if key == 10 // Enter + n.status_bar("Selected: " + items[sel], 32) + // brief wait + sleep_ms(300) + +n.shutdown() diff --git a/examples/extra/notcurses_progress.fun b/examples/extra/notcurses_progress.fun new file mode 100644 index 0000000..8176685 --- /dev/null +++ b/examples/extra/notcurses_progress.fun @@ -0,0 +1,38 @@ +#!/usr/bin/env fun + +include + +n = Notcurses() +if n.init() == 0 + print("Notcurses not available. Build with -DFUN_WITH_NOTCURSES=ON.") + exit(1) + +n.clear() +sz = n.size() +rows = 24 +cols = 80 +if typeof(sz) == "array" + if len(sz) >= 2 + rows = sz[0] + cols = sz[1] + +y = to_number(rows) / 2 +w = cols - 10 +if w < 20 + w = 20 + +i = 0 +while i <= 100 + // styles handled inside progress(); keep plane sane + rc = n.set_style(0x00FF00, 0x000000, 1) + frac = to_number(i) / 100 + rc = n.progress(y, 5, w, frac, 0) + k = n.getch(10) + i = i + 1 + +// final message +rc = n.set_style(0xFFFFFF, 0x000000, 1) +rc = n.draw_text(y + 2, 5, "Done!") +rc = n.render() + +rc = n.shutdown() diff --git a/lib/ui/notcurses.fun b/lib/ui/notcurses.fun index ab8253f..e4b352c 100644 --- a/lib/ui/notcurses.fun +++ b/lib/ui/notcurses.fun @@ -40,3 +40,133 @@ class Notcurses() return nc_shutdown() // (banner helper removed temporarily due to parser limitations) + + // --- Extended primitives --- + fun size(this) + return nc_get_size() + + fun render(this) + return nc_render() + + // fg/bg are 0xRRGGBB, style bitmask: bold(1), dim(2), italic(4), underline(8), strike(16), reverse(32) + fun set_style(this, fg, bg, style) + // parser order for builtin is (style,bg,fg) + return nc_set_style(to_number(style), to_number(bg), to_number(fg)) + + fun draw_char(this, y, x, ch) + return nc_draw_char(to_number(y), to_number(x), to_number(ch)) + + fun hline(this, y, x, len, ch) + return nc_draw_hline(to_number(y), to_number(x), to_number(len), to_number(ch)) + + fun vline(this, y, x, len, ch) + return nc_draw_vline(to_number(y), to_number(x), to_number(len), to_number(ch)) + + fun box(this, y, x, h, w, style) + // builtin expects (x,y,w,h,style) + return nc_box(to_number(x), to_number(y), to_number(w), to_number(h), to_number(style)) + + fun fill(this, y, x, h, w, ch) + // builtin expects (x,y,w,h,ch) + return nc_fill(to_number(x), to_number(y), to_number(w), to_number(h), to_number(ch)) + + // --- Widgets as methods --- + // Status bar on the last row + fun status_bar(this, text, style) + sz = this.size() + if style == nil + style = 32 + rc = this.set_style(0x000000, 0xFFFFFF, style) + rc = this.fill(sz[0] - 1, 0, 1, sz[1], 32) + rc = this.draw_text(sz[0] - 1, 1, text) + rc = this.render() + return 0 + + // Draw styled label + fun label(this, y, x, text, style) + if style != nil + rc = this.set_style(0xFFFFFF, 0x000000, style) + rc = this.draw_text(y, x, text) + rc = this.render() + return 0 + + // Panel with optional title + fun box_panel(this, y, x, h, w, title, style) + if style == nil + style = 0 + rc = this.box(y, x, h, w, style) + if title != nil + rc = this.draw_text(y, x + 2, to_string(title)) + rc = this.render() + return 0 + + // Progress bar (frac in 0..1) + fun progress(this, y, x, w, frac, style) + if style == nil + style = 0 + if frac < 0 + frac = 0 + if frac > 1 + frac = 1 + // frame and title + rc = nc_box(to_number(x - 2), to_number(y - 1), to_number(w + 4), 3, to_number(style)) + // background in dim gray + rc = this.set_style(0x606060, 0x000000, 2) + rc = nc_fill(to_number(y), to_number(x), to_number(w), 1, 46) // '.' + // filled portion in bold green + rc = this.set_style(0x00FF00, 0x000000, 1) + fw = to_number(frac) * w + if fw < 0 + fw = 0 + if fw > w + fw = w + if fw > 0 + rc = nc_fill(to_number(y), to_number(x), to_number(fw), 1, 35) // '#' + rc = nc_draw_text(to_number(y - 1), to_number(x), "Progress") + rc = nc_render() + return 0 + + // Simple vertical menu, highlight selected index + fun menu(this, y, x, items, selected_idx, w, style, sel_style) + if style == nil + style = 0 + if sel_style == nil + sel_style = 32 + i = 0 + while i < len(items) + if i == selected_idx + rc = this.set_style(0x000000, 0xFFFFFF, sel_style) + else + rc = this.set_style(0xFFFFFF, 0x000000, style) + if w != nil + if len(to_string(items[i])) > w + rc = this.draw_text(y + i, x, substr(to_string(items[i]), 0, w)) + else + rc = this.draw_text(y + i, x, to_string(items[i])) + else + rc = this.draw_text(y + i, x, to_string(items[i])) + i = i + 1 + rc = this.render() + return 0 + + // Input line echo with cursor + fun input_line(this, y, x, prompt, buffer, cursor_idx, w, style) + if style == nil + style = 0 + if w == nil + w = len(buffer) + len(prompt) + 2 + rc = this.set_style(0xFFFFFF, 0x000000, style) + rc = this.draw_text(y, x, to_string(prompt)) + rc = this.fill(y, x + len(prompt), 1, w, 32) + rc = this.draw_text(y, x + len(prompt), to_string(buffer)) + this.set_style(0x000000, 0xFFFFFF, 32) + ci = cursor_idx + if ci < 0 + ci = 0 + if ci > len(buffer) + ci = len(buffer) + rc = this.draw_char(y, x + len(prompt) + ci, 32) + rc = this.render() + return 0 + +// --- End of class Notcurses --- diff --git a/src/bytecode.h b/src/bytecode.h index 7710e12..555ebd8 100644 --- a/src/bytecode.h +++ b/src/bytecode.h @@ -290,7 +290,15 @@ typedef enum { OP_NC_SHUTDOWN, // shuts down Notcurses; returns 0 OP_NC_CLEAR, // clears screen/plane; returns 0 OP_NC_DRAW_TEXT, // pops text, x, y; draws; returns 0 - OP_NC_GETCH // pops timeout_ms; returns codepoint or -1 on timeout/error + OP_NC_GETCH, // pops timeout_ms; returns codepoint or -1 on timeout/error + OP_NC_GET_SIZE, // pushes [rows:int, cols:int] or -1 when unavailable + OP_NC_SET_STYLE, // pops style:int, bg_rgb:int, fg_rgb:int; returns 0/-1 + OP_NC_DRAW_CHAR, // pops ch:int, x:int, y:int; returns 0/-1 + OP_NC_DRAW_HLINE,// pops len:int, x:int, y:int, ch:int; returns 0/-1 + OP_NC_DRAW_VLINE,// pops len:int, x:int, y:int, ch:int; returns 0/-1 + OP_NC_BOX, // pops x:int, y:int, w:int, h:int, style:int; returns 0/-1 + OP_NC_FILL, // pops x:int, y:int, w:int, h:int, ch:int; returns 0/-1 + OP_NC_RENDER // forces a render; returns 0/-1 } OpCode; typedef struct { diff --git a/src/funstx.c b/src/funstx.c index 8dc8c13..bdfb122 100644 --- a/src/funstx.c +++ b/src/funstx.c @@ -1,8 +1,8 @@ -/** +/* * This file is part of the Fun programming language. * https://fun-lang.xyz/ * - * Copyright 2026 + * Copyright 2026 Johannes Findeisen * Licensed under the terms of the Apache-2.0 license. * https://opensource.org/license/apache-2-0 */ diff --git a/src/parser.c b/src/parser.c index cfa7963..abb6711 100644 --- a/src/parser.c +++ b/src/parser.c @@ -2437,6 +2437,118 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos) free(name); return 1; } + if (strcmp(name, "nc_get_size") == 0) { + (*pos)++; /* '(' */ + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "nc_get_size expects ()"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_NC_GET_SIZE, 0); + free(name); + return 1; + } + if (strcmp(name, "nc_set_style") == 0) { + (*pos)++; /* '(' */ + /* (style, bg_rgb, fg_rgb) */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_set_style expects (style, bg_rgb, fg_rgb)"); free(name); return 0; } + if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "nc_set_style expects (style, bg_rgb, fg_rgb)"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_set_style expects (style, bg_rgb, fg_rgb)"); free(name); return 0; } + if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "nc_set_style expects (style, bg_rgb, fg_rgb)"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_set_style expects (style, bg_rgb, fg_rgb)"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after nc_set_style args"); free(name); return 0; } + bytecode_add_instruction(bc, OP_NC_SET_STYLE, 0); + free(name); + return 1; + } + if (strcmp(name, "nc_draw_char") == 0) { + (*pos)++; /* '(' */ + /* (y, x, ch) */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_draw_char expects (y, x, ch)"); free(name); return 0; } + if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "nc_draw_char expects (y, x, ch)"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_draw_char expects (y, x, ch)"); free(name); return 0; } + if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "nc_draw_char expects (y, x, ch)"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_draw_char expects (y, x, ch)"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after nc_draw_char args"); free(name); return 0; } + bytecode_add_instruction(bc, OP_NC_DRAW_CHAR, 0); + free(name); + return 1; + } + if (strcmp(name, "nc_draw_hline") == 0) { + (*pos)++; /* '(' */ + /* (y, x, len, ch) */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_draw_hline expects (y, x, len, ch)"); free(name); return 0; } + if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "nc_draw_hline expects (y, x, len, ch)"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_draw_hline expects (y, x, len, ch)"); free(name); return 0; } + if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "nc_draw_hline expects (y, x, len, ch)"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_draw_hline expects (y, x, len, ch)"); free(name); return 0; } + if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "nc_draw_hline expects (y, x, len, ch)"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_draw_hline expects (y, x, len, ch)"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after nc_draw_hline args"); free(name); return 0; } + bytecode_add_instruction(bc, OP_NC_DRAW_HLINE, 0); + free(name); + return 1; + } + if (strcmp(name, "nc_draw_vline") == 0) { + (*pos)++; /* '(' */ + /* (y, x, len, ch) */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_draw_vline expects (y, x, len, ch)"); free(name); return 0; } + if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "nc_draw_vline expects (y, x, len, ch)"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_draw_vline expects (y, x, len, ch)"); free(name); return 0; } + if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "nc_draw_vline expects (y, x, len, ch)"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_draw_vline expects (y, x, len, ch)"); free(name); return 0; } + if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "nc_draw_vline expects (y, x, len, ch)"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_draw_vline expects (y, x, len, ch)"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after nc_draw_vline args"); free(name); return 0; } + bytecode_add_instruction(bc, OP_NC_DRAW_VLINE, 0); + free(name); + return 1; + } + if (strcmp(name, "nc_box") == 0) { + (*pos)++; /* '(' */ + /* (x, y, w, h, style) -- wrapper may reorder */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_box expects (x, y, w, h, style)"); free(name); return 0; } + if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "nc_box expects (x, y, w, h, style)"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_box expects (x, y, w, h, style)"); free(name); return 0; } + if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "nc_box expects (x, y, w, h, style)"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_box expects (x, y, w, h, style)"); free(name); return 0; } + if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "nc_box expects (x, y, w, h, style)"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_box expects (x, y, w, h, style)"); free(name); return 0; } + if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "nc_box expects (x, y, w, h, style)"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_box expects (x, y, w, h, style)"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after nc_box args"); free(name); return 0; } + bytecode_add_instruction(bc, OP_NC_BOX, 0); + free(name); + return 1; + } + if (strcmp(name, "nc_fill") == 0) { + (*pos)++; /* '(' */ + /* (x, y, w, h, ch) */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_fill expects (x, y, w, h, ch)"); free(name); return 0; } + if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "nc_fill expects (x, y, w, h, ch)"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_fill expects (x, y, w, h, ch)"); free(name); return 0; } + if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "nc_fill expects (x, y, w, h, ch)"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_fill expects (x, y, w, h, ch)"); free(name); return 0; } + if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "nc_fill expects (x, y, w, h, ch)"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_fill expects (x, y, w, h, ch)"); free(name); return 0; } + if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "nc_fill expects (x, y, w, h, ch)"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_fill expects (x, y, w, h, ch)"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after nc_fill args"); free(name); return 0; } + bytecode_add_instruction(bc, OP_NC_FILL, 0); + free(name); + return 1; + } + if (strcmp(name, "nc_render") == 0) { + (*pos)++; /* '(' */ + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "nc_render expects ()"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_NC_RENDER, 0); + free(name); + return 1; + } if (strcmp(name, "pcsc_release") == 0) { (*pos)++; /* '(' */ if (!emit_expression(bc, src, len, pos)) { @@ -5686,18 +5798,18 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si *pos = local_pos; skip_to_eol(src, len, pos); return; - } else if (local_pos < len && src[local_pos] == '(') { - /* call as a statement: compile full call expression and drop result */ - /* rewind to statement start and emit as expression */ + } else { + /* Treat as a generic expression-statement (e.g., method calls like obj.method(...), + property access chains, or builtin calls behind wrappers). We rewind to the + start of the statement, emit the full expression, and drop its result. */ local_pos = *pos; if (emit_expression(bc, src, len, &local_pos)) { - bytecode_add_instruction(bc, OP_POP, 0); /* dApache-2.0ard return value */ + bytecode_add_instruction(bc, OP_POP, 0); /* discard return value of standalone expr */ + *pos = local_pos; + skip_to_eol(src, len, pos); + return; } - *pos = local_pos; - skip_to_eol(src, len, pos); - return; - } else { - /* invalid statement: identifier not followed by assignment or call */ + /* if it wasn't a valid expression either, report a meaningful error */ parser_fail(local_pos, "Expected assignment '=' or call '(...)' after identifier"); return; } diff --git a/src/vm.c b/src/vm.c index 4406d3b..140eead 100644 --- a/src/vm.c +++ b/src/vm.c @@ -939,6 +939,14 @@ void vm_run(VM *vm, Bytecode *entry) { #include "vm/notcurses/getch.c" #include "vm/notcurses/init.c" #include "vm/notcurses/shutdown.c" +#include "vm/notcurses/get_size.c" +#include "vm/notcurses/set_style.c" +#include "vm/notcurses/draw_char.c" +#include "vm/notcurses/hline.c" +#include "vm/notcurses/vline.c" +#include "vm/notcurses/box.c" +#include "vm/notcurses/fill.c" +#include "vm/notcurses/render.c" #endif /* SQLite ops */ diff --git a/src/vm.h b/src/vm.h index 37ac3b7..ca07050 100644 --- a/src/vm.h +++ b/src/vm.h @@ -61,7 +61,9 @@ static const char *opcode_names[] = { /* C++ demo */ "CPP_ADD", /* Notcurses TUI (optional) */ - "NC_INIT", "NC_SHUTDOWN", "NC_CLEAR", "NC_DRAW_TEXT", "NC_GETCH"}; + "NC_INIT", "NC_SHUTDOWN", "NC_CLEAR", "NC_DRAW_TEXT", "NC_GETCH", + "NC_GET_SIZE", "NC_SET_STYLE", "NC_DRAW_CHAR", "NC_DRAW_HLINE", "NC_DRAW_VLINE", + "NC_BOX", "NC_FILL", "NC_RENDER"}; typedef struct { Bytecode *fn; @@ -145,7 +147,7 @@ void vm_debug_request_finish(VM *vm); void vm_debug_request_continue(VM *vm); static inline int opcode_is_valid(int op) { - return op >= OP_NOP && op <= OP_NC_GETCH; // all current opcodes (including optional NC_*) + return op >= OP_NOP && op <= OP_NC_RENDER; // all current opcodes (including optional NC_*) } /* --- Minimal C ABI helpers for FFI (Rust opcode experiments) --- */ diff --git a/src/vm/notcurses/box.c b/src/vm/notcurses/box.c new file mode 100644 index 0000000..f7dabc4 --- /dev/null +++ b/src/vm/notcurses/box.c @@ -0,0 +1,66 @@ +/** + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + */ + +/* NC_BOX */ +case OP_NC_BOX: { + Value stylev = pop_value(vm); + Value hv = pop_value(vm); + Value wv = pop_value(vm); + Value yv = pop_value(vm); + Value xv = pop_value(vm); + int style = (stylev.type == VAL_INT ? (int)stylev.i : (stylev.type == VAL_FLOAT ? (int)stylev.d : 0)); + int h = (hv.type == VAL_INT ? (int)hv.i : (hv.type == VAL_FLOAT ? (int)hv.d : 0)); + int w = (wv.type == VAL_INT ? (int)wv.i : (wv.type == VAL_FLOAT ? (int)wv.d : 0)); + int y = (yv.type == VAL_INT ? (int)yv.i : (yv.type == VAL_FLOAT ? (int)yv.d : 0)); + int x = (xv.type == VAL_INT ? (int)xv.i : (xv.type == VAL_FLOAT ? (int)xv.d : 0)); + free_value(stylev); free_value(hv); free_value(wv); free_value(yv); free_value(xv); +#ifdef FUN_WITH_NOTCURSES + if (_fun_nc && _fun_nc_std && h >= 2 && w >= 2) { + /* Pick characters: style 0 light (ASCII), 1 heavy (Unicode), fallback to ASCII */ + uint32_t hch = (style == 1 ? 0x2501 : '-'); // heavy box drawings horizontal + uint32_t vch = (style == 1 ? 0x2503 : '|'); // heavy vertical + uint32_t tl = (style == 1 ? 0x250F : '+'); + uint32_t tr = (style == 1 ? 0x2513 : '+'); + uint32_t bl = (style == 1 ? 0x2517 : '+'); + uint32_t br = (style == 1 ? 0x251B : '+'); + + /* Draw edges */ + // top and bottom + { + char topc[5]; int tlen=0; uint32_t cp=hch; + if (cp<=0x7F){topc[0]=(char)cp;tlen=1;} else if (cp<=0x7FF){topc[0]=(char)(0xC0|(cp>>6)); topc[1]=(char)(0x80|(cp&0x3F)); tlen=2;} else if (cp<=0xFFFF){topc[0]=(char)(0xE0|(cp>>12)); topc[1]=(char)(0x80|((cp>>6)&0x3F)); topc[2]=(char)(0x80|(cp&0x3F)); tlen=3;} else {topc[0]=(char)(0xF0|(cp>>18)); topc[1]=(char)(0x80|((cp>>12)&0x3F)); topc[2]=(char)(0x80|((cp>>6)&0x3F)); topc[3]=(char)(0x80|(cp&0x3F)); tlen=4;} + topc[tlen]='\0'; + for (int i=1;i>6)); vertc[1]=(char)(0x80|(cp&0x3F)); vlen=2;} else if (cp<=0xFFFF){vertc[0]=(char)(0xE0|(cp>>12)); vertc[1]=(char)(0x80|((cp>>6)&0x3F)); vertc[2]=(char)(0x80|(cp&0x3F)); vlen=3;} else {vertc[0]=(char)(0xF0|(cp>>18)); vertc[1]=(char)(0x80|((cp>>12)&0x3F)); vertc[2]=(char)(0x80|((cp>>6)&0x3F)); vertc[3]=(char)(0x80|(cp&0x3F)); vlen=4;} + vertc[vlen]='\0'; + for (int i=1;i>6)); cbuf[1]=(char)(0x80|(cp&0x3F)); clen=2;} else if (cp<=0xFFFF){cbuf[0]=(char)(0xE0|(cp>>12)); cbuf[1]=(char)(0x80|((cp>>6)&0x3F)); cbuf[2]=(char)(0x80|(cp&0x3F)); clen=3;} else {cbuf[0]=(char)(0xF0|(cp>>18)); cbuf[1]=(char)(0x80|((cp>>12)&0x3F)); cbuf[2]=(char)(0x80|((cp>>6)&0x3F)); cbuf[3]=(char)(0x80|(cp&0x3F)); clen=4;} cbuf[clen]='\0'; ncplane_putstr_yx(_fun_nc_std, y, x, cbuf); + cp=tr; if (cp<=0x7F){cbuf[0]=(char)cp;clen=1;} else if (cp<=0x7FF){cbuf[0]=(char)(0xC0|(cp>>6)); cbuf[1]=(char)(0x80|(cp&0x3F)); clen=2;} else if (cp<=0xFFFF){cbuf[0]=(char)(0xE0|(cp>>12)); cbuf[1]=(char)(0x80|((cp>>6)&0x3F)); cbuf[2]=(char)(0x80|(cp&0x3F)); clen=3;} else {cbuf[0]=(char)(0xF0|(cp>>18)); cbuf[1]=(char)(0x80|((cp>>12)&0x3F)); cbuf[2]=(char)(0x80|((cp>>6)&0x3F)); cbuf[3]=(char)(0x80|(cp&0x3F)); clen=4;} cbuf[clen]='\0'; ncplane_putstr_yx(_fun_nc_std, y, x+w-1, cbuf); + cp=bl; if (cp<=0x7F){cbuf[0]=(char)cp;clen=1;} else if (cp<=0x7FF){cbuf[0]=(char)(0xC0|(cp>>6)); cbuf[1]=(char)(0x80|(cp&0x3F)); clen=2;} else if (cp<=0xFFFF){cbuf[0]=(char)(0xE0|(cp>>12)); cbuf[1]=(char)(0x80|((cp>>6)&0x3F)); cbuf[2]=(char)(0x80|(cp&0x3F)); clen=3;} else {cbuf[0]=(char)(0xF0|(cp>>18)); cbuf[1]=(char)(0x80|((cp>>12)&0x3F)); cbuf[2]=(char)(0x80|((cp>>6)&0x3F)); cbuf[3]=(char)(0x80|(cp&0x3F)); clen=4;} cbuf[clen]='\0'; ncplane_putstr_yx(_fun_nc_std, y+h-1, x, cbuf); + cp=br; if (cp<=0x7F){cbuf[0]=(char)cp;clen=1;} else if (cp<=0x7FF){cbuf[0]=(char)(0xC0|(cp>>6)); cbuf[1]=(char)(0x80|(cp&0x3F)); clen=2;} else if (cp<=0xFFFF){cbuf[0]=(char)(0xE0|(cp>>12)); cbuf[1]=(char)(0x80|((cp>>6)&0x3F)); cbuf[2]=(char)(0x80|(cp&0x3F)); clen=3;} else {cbuf[0]=(char)(0xF0|(cp>>18)); cbuf[1]=(char)(0x80|((cp>>12)&0x3F)); cbuf[2]=(char)(0x80|((cp>>6)&0x3F)); cbuf[3]=(char)(0x80|(cp&0x3F)); clen=4;} cbuf[clen]='\0'; ncplane_putstr_yx(_fun_nc_std, y+h-1, x+w-1, cbuf); + } + + push_value(vm, make_int(0)); + } else { + push_value(vm, make_int(-1)); + } +#else + (void)style; (void)h; (void)w; (void)y; (void)x; + push_value(vm, make_int(-1)); +#endif + break; +} diff --git a/src/vm/notcurses/draw_char.c b/src/vm/notcurses/draw_char.c new file mode 100644 index 0000000..afd7451 --- /dev/null +++ b/src/vm/notcurses/draw_char.c @@ -0,0 +1,40 @@ +/** + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + */ + +/* NC_DRAW_CHAR */ +case OP_NC_DRAW_CHAR: { + Value yv = pop_value(vm); + Value xv = pop_value(vm); + Value chv = pop_value(vm); + int y = (yv.type == VAL_INT ? (int)yv.i : (yv.type == VAL_FLOAT ? (int)yv.d : 0)); + int x = (xv.type == VAL_INT ? (int)xv.i : (xv.type == VAL_FLOAT ? (int)xv.d : 0)); + uint32_t cp = (chv.type == VAL_INT ? (uint32_t)chv.i : (chv.type == VAL_FLOAT ? (uint32_t)chv.d : (uint32_t)'?')); + free_value(yv); + free_value(xv); + free_value(chv); +#ifdef FUN_WITH_NOTCURSES + if (_fun_nc && _fun_nc_std) { + char utf8[5]; + int len = 0; + if (cp <= 0x7F) { utf8[0] = (char)cp; len = 1; } + else if (cp <= 0x7FF) { utf8[0] = (char)(0xC0 | (cp >> 6)); utf8[1] = (char)(0x80 | (cp & 0x3F)); len = 2; } + else if (cp <= 0xFFFF) { utf8[0] = (char)(0xE0 | (cp >> 12)); utf8[1] = (char)(0x80 | ((cp >> 6) & 0x3F)); utf8[2] = (char)(0x80 | (cp & 0x3F)); len = 3; } + else { utf8[0] = (char)(0xF0 | (cp >> 18)); utf8[1] = (char)(0x80 | ((cp >> 12) & 0x3F)); utf8[2] = (char)(0x80 | ((cp >> 6) & 0x3F)); utf8[3] = (char)(0x80 | (cp & 0x3F)); len = 4; } + utf8[len] = '\0'; + ncplane_putstr_yx(_fun_nc_std, y, x, utf8); + push_value(vm, make_int(0)); + } else { + push_value(vm, make_int(-1)); + } +#else + (void)cp; (void)x; (void)y; + push_value(vm, make_int(-1)); +#endif + break; +} diff --git a/src/vm/notcurses/fill.c b/src/vm/notcurses/fill.c new file mode 100644 index 0000000..64d4add --- /dev/null +++ b/src/vm/notcurses/fill.c @@ -0,0 +1,45 @@ +/** + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + */ + +/* NC_FILL */ +case OP_NC_FILL: { + Value chv = pop_value(vm); + Value hv = pop_value(vm); + Value wv = pop_value(vm); + Value yv = pop_value(vm); + Value xv = pop_value(vm); + int h = (hv.type == VAL_INT ? (int)hv.i : (hv.type == VAL_FLOAT ? (int)hv.d : 0)); + int w = (wv.type == VAL_INT ? (int)wv.i : (wv.type == VAL_FLOAT ? (int)wv.d : 0)); + int y = (yv.type == VAL_INT ? (int)yv.i : (yv.type == VAL_FLOAT ? (int)yv.d : 0)); + int x = (xv.type == VAL_INT ? (int)xv.i : (xv.type == VAL_FLOAT ? (int)xv.d : 0)); + uint32_t cp = (chv.type == VAL_INT ? (uint32_t)chv.i : (chv.type == VAL_FLOAT ? (uint32_t)chv.d : (uint32_t)' ')); + free_value(chv); free_value(hv); free_value(wv); free_value(yv); free_value(xv); +#ifdef FUN_WITH_NOTCURSES + if (_fun_nc && _fun_nc_std && h > 0 && w > 0) { + char utf8[5]; int ulen = 0; + if (cp <= 0x7F) { utf8[0] = (char)cp; ulen = 1; } + else if (cp <= 0x7FF) { utf8[0]=(char)(0xC0|(cp>>6)); utf8[1]=(char)(0x80|(cp&0x3F)); ulen=2; } + else if (cp <= 0xFFFF) { utf8[0]=(char)(0xE0|(cp>>12)); utf8[1]=(char)(0x80|((cp>>6)&0x3F)); utf8[2]=(char)(0x80|(cp&0x3F)); ulen=3; } + else { utf8[0]=(char)(0xF0|(cp>>18)); utf8[1]=(char)(0x80|((cp>>12)&0x3F)); utf8[2]=(char)(0x80|((cp>>6)&0x3F)); utf8[3]=(char)(0x80|(cp&0x3F)); ulen=4; } + utf8[ulen] = '\0'; + for (int yy = 0; yy < h; yy++) { + for (int xx = 0; xx < w; xx++) { + ncplane_putstr_yx(_fun_nc_std, y + yy, x + xx, utf8); + } + } + push_value(vm, make_int(0)); + } else { + push_value(vm, make_int(-1)); + } +#else + (void)cp; (void)x; (void)y; (void)w; (void)h; + push_value(vm, make_int(-1)); +#endif + break; +} diff --git a/src/vm/notcurses/get_size.c b/src/vm/notcurses/get_size.c new file mode 100644 index 0000000..cc4a63c --- /dev/null +++ b/src/vm/notcurses/get_size.c @@ -0,0 +1,29 @@ +/** + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + */ + +/* NC_GET_SIZE */ +case OP_NC_GET_SIZE: { +#ifdef FUN_WITH_NOTCURSES + if (_fun_nc_std) { + int rows = 0, cols = 0; + ncplane_dim_yx(_fun_nc_std, &rows, &cols); + Value tmp[2]; + tmp[0] = make_int(rows); + tmp[1] = make_int(cols); + Value arr = make_array_from_values(tmp, 2); + push_value(vm, arr); + } else { + push_value(vm, make_int(-1)); + } +#else + (void)_fun_nc_std; + push_value(vm, make_int(-1)); +#endif + break; +} diff --git a/src/vm/notcurses/hline.c b/src/vm/notcurses/hline.c new file mode 100644 index 0000000..e371e2a --- /dev/null +++ b/src/vm/notcurses/hline.c @@ -0,0 +1,39 @@ +/** + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + */ + +/* NC_DRAW_HLINE */ +case OP_NC_DRAW_HLINE: { + Value chv = pop_value(vm); + Value lenv = pop_value(vm); + Value xv = pop_value(vm); + Value yv = pop_value(vm); + int len = (lenv.type == VAL_INT ? (int)lenv.i : (lenv.type == VAL_FLOAT ? (int)lenv.d : 0)); + int x = (xv.type == VAL_INT ? (int)xv.i : (xv.type == VAL_FLOAT ? (int)xv.d : 0)); + int y = (yv.type == VAL_INT ? (int)yv.i : (yv.type == VAL_FLOAT ? (int)yv.d : 0)); + uint32_t cp = (chv.type == VAL_INT ? (uint32_t)chv.i : (chv.type == VAL_FLOAT ? (uint32_t)chv.d : (uint32_t)'-')); + free_value(lenv); free_value(xv); free_value(yv); free_value(chv); +#ifdef FUN_WITH_NOTCURSES + if (_fun_nc && _fun_nc_std && len > 0) { + char utf8[5]; int ulen = 0; + if (cp <= 0x7F) { utf8[0] = (char)cp; ulen = 1; } + else if (cp <= 0x7FF) { utf8[0]=(char)(0xC0|(cp>>6)); utf8[1]=(char)(0x80|(cp&0x3F)); ulen=2; } + else if (cp <= 0xFFFF) { utf8[0]=(char)(0xE0|(cp>>12)); utf8[1]=(char)(0x80|((cp>>6)&0x3F)); utf8[2]=(char)(0x80|(cp&0x3F)); ulen=3; } + else { utf8[0]=(char)(0xF0|(cp>>18)); utf8[1]=(char)(0x80|((cp>>12)&0x3F)); utf8[2]=(char)(0x80|((cp>>6)&0x3F)); utf8[3]=(char)(0x80|(cp&0x3F)); ulen=4; } + utf8[ulen] = '\0'; + for (int i = 0; i < len; i++) ncplane_putstr_yx(_fun_nc_std, y, x + i, utf8); + push_value(vm, make_int(0)); + } else { + push_value(vm, make_int(-1)); + } +#else + (void)cp; (void)x; (void)y; (void)len; + push_value(vm, make_int(-1)); +#endif + break; +} diff --git a/src/vm/notcurses/render.c b/src/vm/notcurses/render.c new file mode 100644 index 0000000..c464353 --- /dev/null +++ b/src/vm/notcurses/render.c @@ -0,0 +1,23 @@ +/** + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + */ + +/* NC_RENDER */ +case OP_NC_RENDER: { +#ifdef FUN_WITH_NOTCURSES + if (_fun_nc) { + int rc = notcurses_render(_fun_nc); + push_value(vm, make_int(rc == 0 ? 0 : -1)); + } else { + push_value(vm, make_int(-1)); + } +#else + push_value(vm, make_int(-1)); +#endif + break; +} diff --git a/src/vm/notcurses/set_style.c b/src/vm/notcurses/set_style.c new file mode 100644 index 0000000..aae5147 --- /dev/null +++ b/src/vm/notcurses/set_style.c @@ -0,0 +1,71 @@ +/** + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + */ + +/* NC_SET_STYLE */ +case OP_NC_SET_STYLE: { + Value fgv = pop_value(vm); + Value bgv = pop_value(vm); + Value stylev = pop_value(vm); + int style = (stylev.type == VAL_INT ? (int)stylev.i : (stylev.type == VAL_FLOAT ? (int)stylev.d : 0)); + int bg = (bgv.type == VAL_INT ? (int)bgv.i : (bgv.type == VAL_FLOAT ? (int)bgv.d : 0)); + int fg = (fgv.type == VAL_INT ? (int)fgv.i : (fgv.type == VAL_FLOAT ? (int)fgv.d : 0)); + free_value(stylev); + free_value(bgv); + free_value(fgv); +#ifdef FUN_WITH_NOTCURSES + if (_fun_nc && _fun_nc_std) { + /* Extract RGB components */ + unsigned fg_r = (unsigned)((fg >> 16) & 0xFF); + unsigned fg_g = (unsigned)((fg >> 8) & 0xFF); + unsigned fg_b = (unsigned)(fg & 0xFF); + unsigned bg_r = (unsigned)((bg >> 16) & 0xFF); + unsigned bg_g = (unsigned)((bg >> 8) & 0xFF); + unsigned bg_b = (unsigned)(bg & 0xFF); + + /* Set colors */ + ncplane_set_fg_rgb8(_fun_nc_std, fg_r, fg_g, fg_b); + ncplane_set_bg_rgb8(_fun_nc_std, bg_r, bg_g, bg_b); + + /* Map simple bitmask to notcurses styles */ + /* some notcurses versions spell these differently; provide fallbacks */ + #ifndef NCSTYLE_DIM + # ifdef NCSTYLE_FAINT + # define NCSTYLE_DIM NCSTYLE_FAINT + # else + # define NCSTYLE_DIM 0 + # endif + #endif + #ifndef NCSTYLE_REVERSE + # ifdef NCSTYLE_REVERSED + # define NCSTYLE_REVERSE NCSTYLE_REVERSED + # else + # define NCSTYLE_REVERSE 0 + # endif + #endif + unsigned styles = 0; + if (style & 1) styles |= NCSTYLE_BOLD; + if (style & 2) styles |= NCSTYLE_DIM; + if (style & 4) styles |= NCSTYLE_ITALIC; + if (style & 8) styles |= NCSTYLE_UNDERLINE; + if (style & 16) styles |= NCSTYLE_STRUCK; + if (style & 32) styles |= NCSTYLE_REVERSE; + ncplane_set_styles(_fun_nc_std, styles); + + push_value(vm, make_int(0)); + } else { + push_value(vm, make_int(-1)); + } +#else + (void)style; + (void)bg; + (void)fg; + push_value(vm, make_int(-1)); +#endif + break; +} diff --git a/src/vm/notcurses/vline.c b/src/vm/notcurses/vline.c new file mode 100644 index 0000000..090990b --- /dev/null +++ b/src/vm/notcurses/vline.c @@ -0,0 +1,39 @@ +/** + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + */ + +/* NC_DRAW_VLINE */ +case OP_NC_DRAW_VLINE: { + Value chv = pop_value(vm); + Value lenv = pop_value(vm); + Value xv = pop_value(vm); + Value yv = pop_value(vm); + int len = (lenv.type == VAL_INT ? (int)lenv.i : (lenv.type == VAL_FLOAT ? (int)lenv.d : 0)); + int x = (xv.type == VAL_INT ? (int)xv.i : (xv.type == VAL_FLOAT ? (int)xv.d : 0)); + int y = (yv.type == VAL_INT ? (int)yv.i : (yv.type == VAL_FLOAT ? (int)yv.d : 0)); + uint32_t cp = (chv.type == VAL_INT ? (uint32_t)chv.i : (chv.type == VAL_FLOAT ? (uint32_t)chv.d : (uint32_t)'|')); + free_value(lenv); free_value(xv); free_value(yv); free_value(chv); +#ifdef FUN_WITH_NOTCURSES + if (_fun_nc && _fun_nc_std && len > 0) { + char utf8[5]; int ulen = 0; + if (cp <= 0x7F) { utf8[0] = (char)cp; ulen = 1; } + else if (cp <= 0x7FF) { utf8[0]=(char)(0xC0|(cp>>6)); utf8[1]=(char)(0x80|(cp&0x3F)); ulen=2; } + else if (cp <= 0xFFFF) { utf8[0]=(char)(0xE0|(cp>>12)); utf8[1]=(char)(0x80|((cp>>6)&0x3F)); utf8[2]=(char)(0x80|(cp&0x3F)); ulen=3; } + else { utf8[0]=(char)(0xF0|(cp>>18)); utf8[1]=(char)(0x80|((cp>>12)&0x3F)); utf8[2]=(char)(0x80|((cp>>6)&0x3F)); utf8[3]=(char)(0x80|(cp&0x3F)); ulen=4; } + utf8[ulen] = '\0'; + for (int i = 0; i < len; i++) ncplane_putstr_yx(_fun_nc_std, y + i, x, utf8); + push_value(vm, make_int(0)); + } else { + push_value(vm, make_int(-1)); + } +#else + (void)cp; (void)x; (void)y; (void)len; + push_value(vm, make_int(-1)); +#endif + break; +}