Syntax
A KupCAD script is a .kup file of statements. Every statement is an expression with a value,
and the value of the script’s last statement is its model.
Source files
Section titled “Source files”Source files are UTF-8. A file that isn’t is reported as a syntax error
(invalid-utf8) at its first bad byte.
Comments
Section titled “Comments”# starts a comment that runs to the end of the line. There are no block comments: =begin
and /* */ are not comments.
width = 40 # the plate's widthA comment whose text starts with @ is a doc comment. kupcad doc reads a script’s
# @title, # @description and # @author lines into its metadata (see
Parameters and Command line).
Comments that start with kupcad: hide lint findings (see
Lint rules).
# @title Mounting plate# @description A plate with four bolt holes.# @author KupCADcube(x: 40, y: 30, z: 4)Statements and lines
Section titled “Statements and lines”A statement ends at the end of its line. Several statements can share a line, separated by
;. Two statements side by side with no ; (puts 1 2) are a syntax error.
a = 1; b = 2puts a + b # 3A statement goes on to the next line when:
- the line ends inside parentheses, brackets or braces;
- the line ends with a binary operator (
total = 10 -then4is10 - 4) or a comma; - the next line starts with
., which continues a chain of calls.
total = 10 - 4puts total # 6
r = [1, 2, 3] .map { |v| v * 2 } .sumputs r # 12A block’s closing word ends the statement before it, so if ok then 1 else 2 end fits on one
line.
| Form | Example | Is |
|---|---|---|
Lower case or _ first |
width, _spare, empty? |
A local variable or a method |
| Upper case first | Gear, TEETH |
A constant: a class, a module or a value |
@ first |
@teeth |
An instance variable |
@@ first |
@@count |
A class variable |
$ first |
$total |
A variable of the file, which methods can assign too |
A name is letters, digits and _, after a first character that isn’t a digit. Letters outside
ASCII count (café). A name may contain ? and !; by convention they end a method that
answers a question (empty?) or one that changes its receiver. See
Variables and scope for what each kind of name can see.
The names of built-ins (cube, puts, text, params, Solid, Math and the rest) can’t
be assigned or redefined: cube = 5 is the compile error builtin-redefined.
Keywords
Section titled “Keywords”These words are reserved and can’t name a variable or a method:
and as begin break case class def doelse elsif end ensure export false for fromif import module next nil not or privatepublic rescue return self super then true unlessuntil when while with yieldin is reserved only after for’s variables, so in: still works as a keyword argument.
defined?, block_given? and raise are built in to the language too. Ruby’s loop,
retry, alias, lambda, proc and __method__ don’t exist (see
Differences from Ruby).
Numbers
Section titled “Numbers”Every number is a 64-bit float; whole numbers print without a decimal point. Lengths are in
millimetres; 2.cm, 1.inch and the other unit methods convert (see
Number).
| Literal | Value |
|---|---|
42, 3.5, .5 |
Decimal |
1_000_000 |
_ separates digits anywhere |
1.5e-3, 2E3 |
Exponent |
0xff, 0b1010, 0o17 |
Hexadecimal, binary, octal |
A number beyond the largest float (1e999) is the compile error invalid-number. A minus
written against a number is part of it: -2.abs is 2, while -2 ** 2 is still -4.
puts 0xff # 255puts 1_000_000 # 1000000puts 1.5e-3 # 0.0015puts 10 / 4 # 2.5Strings
Section titled “Strings”| Form | Interpolation | Escapes |
|---|---|---|
"..." |
#{...} |
All of the table below |
'...' |
None | Only \\ and \' |
%Q(...) |
#{...} |
As "..." |
%q(...) |
None | Only \\ and the closing delimiter |
#{...} evaluates the expression and inserts it as puts shows it. A %q or %Q literal takes
any punctuation as its delimiter; (, [, { and < pair with their closers and nest.
| Escape | Character |
|---|---|
\n, \t, \r, \f, \v, \b, \a, \e, \0 |
Line feed, tab, carriage return, form feed, vertical tab, backspace, bell, escape, NUL |
\u00e9 |
The code point in four hex digits |
\u{1F600} |
The code point in one to six hex digits |
\", \\, \#, \ |
A backslash before punctuation or a space stands for it |
Any other escape ("\q", "C:\data") is the compile error invalid-escape; write \\ for a
backslash. In a single-quoted string every other backslash stays as written, so 'C:\data'
is what it says.
x = 5puts "x is #{x + 1}" # x is 6puts 'x is #{x}' # x is #{x}puts 'C:\data' # C:\dataputs %q(say "hi") # say "hi"Two string literals side by side are not joined: use +.
Heredocs
Section titled “Heredocs”A heredoc writes a string over several lines. Its body is the lines after the opener’s line, up to a line holding only the name.
| Opener | Body |
|---|---|
<<~NAME |
The indentation of the least indented line is removed from every line |
<<-NAME |
As written; the closing name may be indented |
<<NAME |
As written |
A heredoc is not interpolated: #{...} stays as text, while escapes are read as in a
double-quoted string. The rest of the opener’s line is ordinary code, so f(<<~EOS) and
<<~EOS.strip work, and several heredocs opened on one line take their bodies in order.
note = <<~EOS first indented lastEOSputs note.inspect # "first\n indented\nlast\n"Symbols
Section titled “Symbols”A Symbol is an interned name: :width, :m3, :empty?, :Foo. A quoted symbol is read as
the string in the same quotes: :"part_#{i}" interpolates and :'a b' doesn’t. There are no
operator symbols such as :+. See Symbol.
i = 2puts :"part_#{i}".inspect # :part_2puts :"a b".inspect # :"a b"Word lists
Section titled “Word lists”%w[...] is an Array of Strings and %i[...] an Array of Symbols, split at whitespace.
%W and %I read the same; neither interpolates.
puts %w[m3 m4 m5].inspect # ["m3", "m4", "m5"]puts %i[m3 m4 m5].inspect # [:m3, :m4, :m5]Arrays
Section titled “Arrays”[1, "two", :three]. Elements can be any value, a trailing comma is allowed, and *list
spreads another Array’s elements in place. See Array.
size = [10, 20]puts [*size, 5].inspect # [10, 20, 5]A Map keeps its entries in the order they were added. See Map.
| Entry | Key |
|---|---|
width: 10 |
The Symbol :width |
"w": 10, "a b": 10 |
The String "w", "a b": a quoted key before : is a String, not a Symbol as in Ruby |
"w" => 10, k => 10, part.name => 10 |
The value before =>, a variable’s or a call’s included |
width: |
:width, its value the variable width |
**other |
Every entry of the Map other |
width = 10opts = { center: true }puts({ width:, "w" => 2, **opts }.inspect) # {width: 10, "w" => 2, center: true}puts({ "a b": 1 }.inspect) # {"a b" => 1}A { right after a call opens a block, not a Map: write puts({ a: 1 }), not puts { a: 1 }.
Ranges
Section titled “Ranges”a..b includes b and a...b leaves it out. Both ends are required; there are no endless or
beginless ranges. See Range.
puts (1..3).to_a.inspect # [1, 2, 3]puts (1...3).to_a.inspect # [1, 2]nil, true and false
Section titled “nil, true and false”nil is the absence of a value. Only nil and false are falsy: 0, "" and [] are
truthy. See Boolean and nil.
Lambdas
Section titled “Lambdas”->(x) { x * 2 } and ->(x) do ... end are lambdas, called with .call. See
Blocks and lambdas.
What follows a name
Section titled “What follows a name”Spacing decides how a name and what follows it are read, as in Ruby:
- After a method name,
-1,+1,*xs,**opts,&blkor[...]with a space before and none after is the first argument:puts -1,extrude [0, 0, 10]. - After a variable (a local bound earlier, a parameter, a constant, an
@,@@or$name), the same tokens are arithmetic or indexing:x -1isx - 1,pts [0]ispts[0]. - A line break after an operator counts as a space before the next line, so an operator that ends its line is arithmetic.
name(with no space holds the call’s arguments.name (with a space only groups the first argument:puts (1 + 2) * 3prints9, andf (1, 2)is the compile errorspaced-parentheses.- A call without parentheses takes everything after it as arguments:
[f -1, 2]is[f(-1, 2)], so write[f(-1), 2].
def show(*a) = a.inspectx = 5pts = [1, 2]puts show -1 # [-1]puts x -1 # 4puts pts [0] # 1puts (1 + 2) * 3 # 9puts [show(-1), 2].inspect # ["[-1]", 2]