RedcrabX – ProgramBox Guide

Lua scripting on the worksheet  |  Version 1.0
Available in RedcrabX from version 10.3.0

Back to Quick Start Guide

Table of Contents

Lua manual: https://www.lua.org/manual/


1. Overview

The ProgramBox embeds a Lua 5.2 interpreter (MoonSharp) directly in the worksheet. It lets you write small programs with loops, conditions, and functions that can:

Note: ProgramBox scripts are not re-executed automatically on worksheet refresh. Press ▶ Run or Ctrl+Enter to execute the script manually.
PropertyValueDescription
LanguageLua 5.2Interpreted by MoonSharp – pure C#, no native DLL
Default size360 × 260 pxResizable by dragging the box border
Default box nameP1Editable in the status toolbar name field
Run shortcutCtrl+EnterExecutes the script in the editor
SandboxSoftSandboxFile I/O and OS calls are blocked for safety

2. Inserting a ProgramBox

  1. Click the Code button in the ribbon (Insert in Workspace group).
  2. The cursor changes to a crosshair.
  3. Click anywhere on the worksheet canvas to place the box.
  4. The box can be repositioned by dragging the top handle, and resized by dragging the bottom-right corner.
Tip: Place the ProgramBox below the MathBoxes whose variables it reads. That way the variables are already computed when you press Run.

3. Box Layout

The ProgramBox consists of three areas stacked vertically:

AreaDescription
Status toolbar Shows the box type label (Code), the editable name field (P1), the ▶ Run button, and a status indicator (Lua 5.2OK / Error).
Code editor (top 60 %) Monospaced editor (Consolas). Accepts Return and Tab. Horizontal and vertical scroll bars appear automatically.
Output view (bottom 40 %) Read-only. Shows everything written with print() and any runtime error messages. Automatically scrolls to the last line after each run.

4. Running a Script

There are two ways to run the script:

Each run starts a fresh interpreter instance: variables from previous runs do not carry over. The output view is cleared and replaced with the new output.

Sandbox: The MoonSharp SoftSandbox preset is active. The Lua standard modules io, os, and package are not available. The safe standard library (math, string, table, coroutine) is fully available.

5. Lua Language Basics

Lua is a lightweight scripting language with a clean, readable syntax. Here is a brief cheat-sheet for users new to Lua:

-- Comments start with two dashes

-- Variables (all variables are global by default; use 'local' for local scope)
local x = 42
local name = "RedcrabX"

-- Arithmetic
local area = math.pi * 5^2     -- ^ is the power operator

-- String formatting
print(string.format("Area = %.4f", area))

-- Conditions
if x > 40 then
    print("x is large")
elseif x == 40 then
    print("exactly 40")
else
    print("x is small")
end

-- Numeric for-loop  (start, stop, step)
for i = 1, 10 do
    io.write(i .. " ")          -- io.write is available in SoftSandbox
end

for i = 0, 360, 30 do          -- step = 30
    print(math.sin(math.rad(i)))
end

-- While loop
local n = 1
while n <= 5 do
    print(n)
    n = n + 1
end

-- Functions
local function square(v)
    return v * v
end
print(square(7))   --> 49

-- Tables (arrays use 1-based indexing)
local pts = {10, 20, 30, 40}
for i = 1, #pts do
    print(pts[i])
end
math library: All standard Lua math functions are available: math.sin, math.cos, math.sqrt, math.abs, math.floor, math.ceil, math.exp, math.log, math.pi, math.huge (infinity), etc. Angles in Lua’s math library are always in radians — use math.rad() to convert from degrees.

6. Reading Worksheet Variables

Scalar and array variables defined in MathBox cells on the same worksheet are available in the ProgramBox as two read-only global tables: var for scalars and arr for arrays. Both are snapshots taken at the moment the script starts.

MathBox (define variables)

r  := 50       // radius
cx := 120      // center x
cy := 100      // center y

ProgramBox (read those variables)

local r  = var.r    -- reads the MathBox variable 'r'
local cx = var.cx
local cy = var.cy

print("radius =", r)
print(string.format("center = (%.0f, %.0f)", cx, cy))
Note: Only numeric variables are exposed — var holds scalars, arr holds arrays. Matrix and string variables are not accessible at this time. If a variable does not exist, var.name returns nil and arr.name returns nil.

Defensive access pattern

-- Use 'or' to supply a default when a variable may be undefined
local r = var.r or 40
print("r =", r)

Reading array variables with arr

Array variables (defined with a range or list in MathBox) are accessible via arr.name. Each value is a 1-based Lua table of numbers.

-- MathBox: data := [1, 4, 9, 16, 25]
local d = arr.data         -- 1-based Lua table
print("length =", #d)
print("first  =", d[1])
print("last   =", d[#d])

-- Iterate over all elements
for i = 1, #arr.data do
    print(i, arr.data[i])
end
Tip: Use arr.name to read an existing array variable directly. Use calcarr("expr") (see section 8) to evaluate a MathBox range expression such as [0..360:10] or a computed expression like "x^2".

7. Writing Variables to the Worksheet

The ProgramBox can write scalar and array values back to the shared variable store, making them instantly visible to all MathBoxes on the worksheet. After any write via setvar or setarr, all MathBoxes are recalculated automatically.

FunctionSignatureDescription
setvar setvar("name", value) Write a scalar number to the worksheet variable store.
setarr setarr("name", table) Write a Lua table of numbers as an array variable.

setvar — write a scalar

setvar("result", 42)               -- create or overwrite scalar 'result'
setvar("I", var.U / var.R)         -- derive from existing variables
setvar("area", math.pi * var.r^2)  -- combine Lua math with worksheet variables

setarr — write an array

setarr("samples", {1, 2, 3, 4, 5})   -- write a literal array

-- Build a table dynamically and write it back
local t = {}
for i = 1, 10 do
    t[i] = math.sin(math.rad(i * 36))
end
setarr("wave", t)

Example — Fibonacci sequence

-- MathBox: n := 10
local n = var.n or 10
local a, b = 0, 1
local fibs = {}
for i = 1, n do
    fibs[i] = a
    a, b = b, a + b
end
setarr("fib", fibs)          -- write array back to worksheet
setvar("fib_last", fibs[n])  -- write last value as scalar
print("fib_last =", fibs[n])
Variable lifetime: Variables written by setvar / setarr persist across worksheet refreshes and are re-applied automatically so MathBoxes that reference them stay consistent. They are overwritten the next time the script runs.

8. Evaluating MathBox Expressions

calc and calcarr pass an expression string to the same engine used by MathBox cells. All current worksheet variables (scalars and arrays) are in scope inside the expression string.

FunctionSignatureReturns
calc calc("expression") number — the numeric result of the expression
calcarr calcarr("expression") 1-based Lua table of numbers

calc — scalar expression

-- MathBox variables r, m, c are in scope inside the expression string
local area = calc("pi * r^2")
local E    = calc("m * c^2")
local ohm  = calc("U / I")

print(string.format("area = %.4f", area))

calcarr — array expression

-- Range generator [start..end:step]
local angles = calcarr("[0..360:10]")  -- 37 elements: 0, 10, 20, ..., 360
for i = 1, #angles do
    print(angles[i], math.sin(math.rad(angles[i])))
end

-- Read an existing array variable through the evaluator
local raw = calcarr("measurements")   -- same result as arr.measurements
print("count =", #raw, "  first =", raw[1])
Angle mode: calc and calcarr use the same DEG / RAD setting as the MathBox (degrees by default). calc("sin(90)") returns 1 in DEG mode.
Prefer arr.x for simply reading an existing array variable — it is cleaner and skips the expression parser. Use calcarr for range expressions such as [0..100:5] or computed expressions like "x^2".

The complete MathBox function library is available

Every function that works in a MathBox cell works identically inside a calc() or calcarr() string — including all special functions, aggregate functions, physics solvers, and electrical formulas. This makes calc / calcarr a particularly powerful bridge: Lua handles loops, conditions, and data structures while the MathBox engine provides the full domain-specific function set.

Key insight: anything you can type into a MathBox cell, you can pass as a string to calc() or calcarr().
CategoryExample functions
Standard math sin, cos, log, sqrt, atan2, round, abs
Special functions erf, erfc, gamma, lgamma, beta, sigmoid, relu, softplus, besselj, bessely, airy_ai, airy_bi
Array & aggregate sum, min, max, count, median, stddev, diff, aver, clip, fill, new, product
Physics pfa, centforce, kine, poten, freefall, orbvel, work, slopef
Electrical voltdrop, rccharge, parres, rlseries, lcrres, pow_factor
Geometry p2dist, p2linedist

Example — Special functions element-wise over a range

-- Gamma function: Γ(5) = 4! = 24
print(calc("gamma(5)"))            -- 24

-- Error function at a fixed point
print(calc("erf(1)"))              -- ≈ 0.84270

-- sigmoid applied element-wise via calcarr
local xs  = calcarr("[−3..3:0.5]")
local sig = calcarr("sigmoid([−3..3:0.5])")
for i = 1, #xs do
    print(string.format("x=%5.1f  sigmoid=%.5f", xs[i], sig[i]))
end

-- Bessel function J₀ profile, written back as an array variable
local J0 = calcarr("besselj(0, [0..12:0.25])")
setarr("J0_profile", J0)
print(string.format("%d Bessel values written", #J0))

Example — Aggregate functions on worksheet array variables

-- MathBox: measurements := [3.1, 2.9, 3.4, 3.0, 2.8]
local n      = calc("count(measurements)")
local mean   = calc("sum(measurements) / count(measurements)")
local spread = calc("stddev(measurements)")
print(string.format("n=%d  mean=%.3f  σ=%.3f", n, mean, spread))

-- First differences (delta between successive samples)
local deltas = calcarr("diff(measurements)")
for i = 1, #deltas do
    print(string.format("Δ[%d] = %.4f", i, deltas[i]))
end

Example — Physics and electrical solvers

-- Physics: same named-parameter syntax as in a MathBox cell
local tf = calc("freefall(h=80)")          -- free-fall time from 80 m  [s]
local Ek = calc("kine(m=2, v=10)")         -- kinetic energy ½mv²       [J]
print(string.format("tf = %.3f s,  Ek = %.1f J", tf, Ek))

-- Electrical: parallel resistance of two worksheet variables R1, R2
local Rp = calc("parres(R1, R2)")
print(string.format("R_par = %.4f Ω", Rp))

-- RC voltage drop over a time range, stored as array
local t_vec = calcarr("[0..0.1:0.005]")
local u_vec = calcarr("rccharge(U=230, R=470, C=0.001, t=[0..0.1:0.005])")
setarr("rc_voltage", u_vec)
print(string.format("u(t=0.1 s) ≈ %.2f V", u_vec[#u_vec]))
Function reference: See FUNCTIONS.md in the project root and the individual guide pages (Physics, Electrics, Geometry) for complete signatures of all supported MathBox functions.

9. Drawing on a DrawBox

All DrawBox draw functions are available as global Lua functions inside the ProgramBox. First select the target DrawBox with drawtarget, then call any draw function. The coordinate system is the same as in MathBox draw commands: origin at top-left (0, 0), X increases rightward, Y increases downward.

-- Select DrawBox D1 as the drawing target
drawtarget("D1")          -- note: string argument required in Lua

-- Draw a background grid
drawgrid(20)

-- Draw shapes using a loop
for i = 0, 11 do
    local angle = math.rad(i * 30)
    local x = 100 + 60 * math.cos(angle)
    local y = 100 + 60 * math.sin(angle)
    drawdot(x, y, 4)
end

drawtext(82, 10, "Clock positions")
Important: In MathBox draw commands the DrawBox name is written without quotes (e.g. drawtarget(D1)). In Lua you must pass it as a string: drawtarget("D1").
Tip: Call drawclear() at the start of the script to erase shapes left from a previous run before drawing new ones.

10. Draw Function Reference

All functions below operate on the current DrawBox target set with drawtarget("name"). Coordinates are in pixels; the origin is the top-left corner of the canvas.

FunctionParametersDescription
drawtarget(name)name: stringSelect the active DrawBox by name, e.g. "D1"
drawclear()Clear all shapes from the active DrawBox
drawgrid(spacing)spacing: numberDraw a light gray background grid; call before other draw commands
drawgrid(sx, sy)sx, sy: numbersGrid with different horizontal / vertical spacing
drawrect(x, y, h, w)x, y, h, w: numbersDraw a rectangle; (x,y) = top-left corner, h = height, w = width
drawcircle(x, y, r)x, y, r: numbersDraw a circle at center (x,y) with radius r
drawellipse(x, y, rx, ry)x, y, rx, ry: numbersDraw an ellipse at center (x,y) with half-axes rx and ry
drawarc(x, y, r, a1, a2)x, y, r, a1, a2: numbersDraw a circular arc; angles in degrees, clockwise from east
drawline(x1, y1, x2, y2)numbersDraw a line segment from (x1,y1) to (x2,y2)
drawarrow(x1, y1, x2, y2)numbersDraw a line with a filled arrowhead at (x2,y2)
drawdot(x, y, r)x, y, r: numbersDraw a filled black disk at (x,y) with radius r
drawtext(x, y, text)x, y: numbers; text: stringDraw a text label; (x,y) is the top-left corner
drawcross(x, y, size)x, y, size: numbersDraw a crosshair marker at (x,y); size = total arm length
drawdim(x1,y1,x2,y2,offset)numbersDraw a full dimension line with extension lines and arrowheads
drawpoly({x1,y1,x2,y2,...})Lua table of numbersDraw a closed polygon from coordinate pairs (at least 3 pairs)
drawarc angles: The arc angles are always in degrees regardless of the DEG/RAD mode setting in the MathBox toolbar. Use math.deg() to convert a radian value to degrees before passing it to drawarc.

11. Examples

Hello World

print("Hello from ProgramBox!")
print("2 + 2 =", 2 + 2)
print(string.format("pi = %.6f", math.pi))

Output:

Hello from ProgramBox!
2 + 2 =    4
pi = 3.141593

Sine table

print(string.format("%-8s %10s %10s", "deg", "sin", "cos"))
print(string.rep("-", 30))
for deg = 0, 360, 30 do
    local r = math.rad(deg)
    print(string.format("%-8.0f %10.4f %10.4f", deg, math.sin(r), math.cos(r)))
end

Read a MathBox variable and use it in a calculation

-- MathBox: voltage := 230   resistance := 47
local U = var.voltage or 230
local R = var.resistance or 100

local I = U / R
local P = U * I

print(string.format("U = %.1f V", U))
print(string.format("R = %.1f Ω", R))
print(string.format("I = %.4f A", I))
print(string.format("P = %.2f W", P))

Draw a clock face on a DrawBox

drawtarget("D1")
drawclear()
drawgrid(20)

local cx, cy, r = 120, 120, 80

-- Outer circle
drawcircle(cx, cy, r)
drawdot(cx, cy, 3)

-- Hour marks
for h = 0, 11 do
    local angle = math.rad(h * 30 - 90)
    local x1 = cx + (r - 8) * math.cos(angle)
    local y1 = cy + (r - 8) * math.sin(angle)
    local x2 = cx + r * math.cos(angle)
    local y2 = cy + r * math.sin(angle)
    drawline(x1, y1, x2, y2)
end

-- Hour labels
local labels = {"12","1","2","3","4","5","6","7","8","9","10","11"}
for h = 0, 11 do
    local angle = math.rad(h * 30 - 90)
    local x = cx + (r - 20) * math.cos(angle) - 4
    local y = cy + (r - 20) * math.sin(angle) - 7
    drawtext(x, y, labels[h + 1])
end

-- Hands  (read from MathBox variables or use defaults)
local hour_angle   = math.rad((var.hh or 10) * 30 + (var.mm or 10) * 0.5 - 90)
local minute_angle = math.rad((var.mm or 10) * 6 - 90)

drawarrow(cx, cy, cx + 0.55 * r * math.cos(hour_angle),
                  cy + 0.55 * r * math.sin(hour_angle))
drawarrow(cx, cy, cx + 0.80 * r * math.cos(minute_angle),
                  cy + 0.80 * r * math.sin(minute_angle))

Draw a regular polygon from a MathBox variable

-- MathBox:  sides := 6    radius := 60
local n  = var.sides  or 6
local r  = var.radius or 60
local cx = 120
local cy = 100

drawtarget("D1")
drawclear()

local pts = {}
for i = 0, n - 1 do
    local angle = math.rad(i * 360 / n - 90)
    pts[#pts + 1] = cx + r * math.cos(angle)
    pts[#pts + 1] = cy + r * math.sin(angle)
end
drawpoly(pts)

drawtext(cx - 10, cy - 8, n .. "-gon")

Annotated dimension sketch

-- MathBox:  w := 120    h := 80
local W = var.w or 120
local H = var.h or 80
local ox, oy = 40, 40   -- origin of the rectangle

drawtarget("D1")
drawclear()
drawgrid(10)

drawrect(ox, oy, H, W)
drawcross(ox, oy, 12)
drawcross(ox + W, oy + H, 12)

-- Width dimension below
drawdim(ox, oy + H, ox + W, oy + H, 20)
drawtext(ox + W/2 - 15, oy + H + 26, W .. " px")

-- Height dimension to the right
drawdim(ox + W, oy, ox + W, oy + H, 20)
drawtext(ox + W + 28, oy + H/2 - 7, H .. " px")

Write a computed result back to the worksheet

-- MathBox: base := 3    height := 5
local b = var.base   or 3
local h = var.height or 5
local area = 0.5 * b * h

setvar("triangle_area", area)   -- MathBoxes that use 'triangle_area' now update
print(string.format("Area = %.4f", area))

Process an array variable and write the result back

-- MathBox: data := [1, 4, 9, 16, 25]
local src = arr.data
if src == nil then print("no 'data' array found") ; return end

local cumsum = {}
local s = 0
for i = 1, #src do
    s = s + src[i]
    cumsum[i] = s
end
setarr("cumsum", cumsum)    -- write cumulative sums back to worksheet
print("total sum =", s)

Evaluate MathBox expressions and generate a range

-- MathBox: f := 1000    L := 0.01
local Xc = calc("1 / (2 * pi * f * L)")
local Xl = calc("2 * pi * f * L")
print(string.format("Xc = %.4f Ω", Xc))
print(string.format("Xl = %.4f Ω", Xl))

-- Build a time vector and a sine wave from it
local t_vec = calcarr("[0..1:0.05]")   -- 0.00, 0.05, 0.10, ..., 1.00
local wave = {}
for i = 1, #t_vec do
    wave[i] = math.sin(2 * math.pi * t_vec[i])
end
setarr("sine_wave", wave)
print(string.format("%d samples written to 'sine_wave'", #wave))

12. Error Messages

Message (output view)CauseFix
Syntax error: … The Lua code contains a syntax mistake. Check the line number in the message and correct the Lua syntax.
Runtime error: … An error occurred during script execution (e.g. calling nil, division by zero). Add nil-checks with or defaults; verify variable names.
drawpoly: argument must be a table drawpoly received something other than a Lua table. Pass a table of numbers: drawpoly({x1,y1,x2,y2,...}).
drawpoly: element [n] must be a number A non-numeric value appeared in the coordinate table. Ensure all entries in the table are numbers.
Status: Error (red) Any error that prevented the script from completing. Read the full error text in the output view.
setvar: expected setvar("name", number). First argument is not a string, or fewer than 2 arguments were passed. Use setvar("varname", value) with a quoted name and a numeric value.
setvar: argument 2 is a table; use setarr(…) A Lua table was passed as the value to setvar. Use setarr("name", {…}) for array values.
setvar: argument 2 must be a number. Second argument cannot be converted to a number (e.g. a string or boolean). Pass a numeric expression: setvar("x", 42) or setvar("x", var.a + 1).
setarr: expected setarr("name", table). First argument is not a string, or fewer than 2 arguments were passed. Use setarr("varname", table) with a quoted name.
setarr: argument 2 must be a table, e.g. {1, 2, 3}. Second argument to setarr is not a Lua table. Pass a table literal {1,2,3} or a local table variable.
setarr: all table values must be numbers. A non-numeric entry was found in the table passed to setarr. Ensure every element of the table is a number before calling setarr.
calc: expected calc("expression"). calc was called without arguments or with a non-string argument. Pass an expression string: calc("pi * r^2").
calc: <message> The MathBox expression evaluator rejected the expression. Check variable names and expression syntax; the message includes the evaluator’s own error text.
calcarr: expected calcarr("expression"). calcarr was called without arguments or with a non-string argument. Pass an expression string: calcarr("[0..360:10]").
calcarr: cannot evaluate "…". The expression could not be evaluated as an array by the MathBox engine. Use a range expression like [start..end:step] or an existing array variable name.
Status: OK (green) Script completed without errors.

13. Tips and Limitations


RedcrabX ProgramBox Guide  —  further topics: Quick Start Guide  |  DrawBox Guide  |  MathBox Guide  |  Chart Guide  |  Plot Guide