cwal

whcl: Builtin Commands
Login

whcl: Builtin Commands

(⬑Table of Contents)

Sections:

Pages dedicated to specific builtin commands:


Builtin Commands

WHCL has a relatively limited set of builtin commands (colloquially known as BICs). Though client code may freely define new commands, builtins have a few differences:

The following list of command names is reserved for potential future use as builtin commands:

The complete list of built-in commands follows in alphabetical order...

affirm

Usage: affirm EXPRESSION|{CODE}

Works exactly like assert except that it throws an exception, instead of a fatal error, if its expression evaluates to falsy.

alias

Usage: alias varName | object[property] [interceptor-function]

This command creates a new "property alias" value (a.k.a. property reference or propref), which exists to redirect fetching and setting of a given variable or object property to a different value.

This command is covered in detail in the variables docs.

array

See also: array data type

Usage: array [values...]

Creates a new array instance. Its arguments may be values and they may may optionally be contained in (...), which allows them to span lines without requiring escaping of the newlines. e.g. array a b c and array (a b c) are equivalent.

Value tokens in the form {...} are treated as object literals, not strings, and are recursively processed. See the object command for more details.

Value tokens in the form (...) are treated as array literals, not expressions, and are recursively processed.

Achtung: recursively treating (...) as arrays is of course incompatible with using (...) as a shorthand for [expr ...]. Evaluating expressions for use as array values in an array body requires the long-call form: array (a b [expr $x + $y] ...). Parens groups inside the expr call are treated as subexpressions, not array literals.

assert

See also: affirm

Usage: assert EXPRESSION|{CODE}

Kills the current script if its expression evaluates to falsy. Note that it takes an expression argument, not a command.

If given a single argument which is a {...} block, it runs that code in a new scope, using the result of the scope's last-run command as the test result.

break

Usage: break [value|builtin-command args...]

Stops execution of the inner-most loop construct, optionally giving the loop a result value. It is an error to call this from outside of a loop. If its first argument is a reference to a builtin command, that command is called and its result becomes the result.

See the flow control page for examples.

catch

Usage: catch [-noscope] [varname|$var[property]] {body}

If the body throws an exception then this stops propagation of the exception and evaluates to the exception value. If given a variable name or $var[property] then the exception is also stored there. The variable is created in the local scope and must not already exist in that scope. catch will translate non-exception syntax errors (but not other errors) to exceptions. If no exception is thrown, this evaluates to the undefined value and the variable is (for the sake of consistency and usability) still declared.

By default the body is run in its own scope. The -noscope flag causes it to run in the current scope instead. That's almost never really necessary but it was added to support whcl's own unit tests, which needed that to support a given test case. It does, however, provide a convenient way of catching exceptions thrown in scope-level var initialization:

assert ![info is-local x]
catch -noscope {
    decl x 1; # this is running in the outer scope, so...
}
assert [info is-local x]

concat

Usage: concat value1 ...

Works identically to passing all arguments to the concat method of an empty string, emitting each value in some string form and returning a string with their concatenated values. Provided primarily as a convenience to work around the lack of in-string var expansion. Note that it accepts as few as one arguments, the intent of that use case being to convert non-strings to strings.

const

Usages:

This is a convenience form of decl -const.

continue

Usage: continue

Skips to the next loop iteration of inner-most loop construct. It is an error to call this from outside of a loop.

__debug

(Achtung: this might get renamed to __testing or __testmode or some such.)

Usage: __debug {...}

These blocks function like eval -scope {...} except that they are only run if the interpreter's debug block feature flag is enabled and always evaluate to the undefined value. With whclsh they can be enabled with the --debug flag. In C that's done by calling whcl_feature_flag_set(theWhcl,WHCL_FEATURE_F_DEBUG_BLOCK,true).

Reminder to self: ideally these tokens would be stripped entirely during the compilation process, but by the time we can handle the {...} part the chain has already been allocated so we save no memory by stripping them. TODO: restructure the chain in the post-compilation step such that it behaves as if these blocks simply don't exist, as opposed to skipping over them at eval time (which is not slow but it's unnecessary).

decl

See also: variables and const

Usages:

Declares a symbolic name as being available for this scope and all subsequent sub-scopes which can see into this one.

With the first usage, if the final argument is the name of a builtin command then that command is run, passing on any remaining arguments. That permits declarations to use (e.g.) the object, array, and proc commands without having to wrap them in [...]. The {...} usage requires matching pairs of var names and values and does not support commands as values unless they're wrapped in [...].

The first form evaluates to the new value, or undefined if no value was provided. The second form evaluates to undefined.

The -const flag marks the variable(s) as "constant," meaning it/they cannot be reassigned. It is illegal to use this flag without providing a value. If the value is a container object, constness does not protect its contents from being modified.

Sidebar: long story short, the requirement of explicitly declaring variables instead of using set to both declare and assign to variables is due to significant difference between how TCL and our scripting engine handle scopes and resolve symbols. We cannot sensibly support TCL-like usage within this framework.

decr

Usage: decr identifier|$var[property] [value=1]

See also: incr

Decrements the value of the referenced variable or object property by the given amount. Fails if given an unknown var name but will use a default value of 0 for unknown (or non-string) object properties. Evaluates to the new value.

do

Usage: do {script body} while {EXPR}

Implements a do-while loop.

See the flow control page for more details.

echo

Usage: echo [-n] [-s] [...args]

Outputs its arguments with a space between each one unless the -s flag is used. It outputs a newline at the end unless the -n flag is used.

eval

Usage: eval [-scope] [->] one-token

Evaluates the given code in the current scope unless the -scope flag is used, in which case it runs in its own scope. It evaluates to the value of the token. If the token is a block of script code, it evaluates to the value of the last command. eval treats a {...} as a script block, not a string.

The -> flag changes its semantics: if the eval results in a string or Script value, -> causes the contents of that string/Script to be eval'd. This is necessary for evaling code within strings (possibly dynamically-constructed). Without that capability, eval is of very limited use except for its ability to create a scope (which can also be done using if {true} {...}).

ACHTUNG: the -> flag must come after all other flags for boring technical reasons.

Note that eval retains certain scope-level state which allows, e.g., the eval'd code to pass on break and continue results if they are eval'd within the context of a loop.

eval has one truly useful feature which may not be immediately obvious: when evaluating a {...} block, the result of that evaluation is the result of the final expression or command call in that block. It's not always trivial to arrange the code so that "just the right" piece is the final line of the block, though. We can, however, use a second eval to get whatever result we want:

decl x eval -scope {
    decl o object
    set o[x] proc {} {...}
    set o[y] proc {} {...}
    # We want $o to be the result, but how do we do that?
    # Answer: use another eval:
    eval $o; # This becomes the outer eval's result.
             # This operation is very cheap, too.
    # Alternately:
    # expr $o; # Has the same effect
}

Potential TODOs:

Reminder to self: we can probably use this for evaling files via a -file flag, but an importer function would be better because it could have a configurable search path (like s2's import keyword).

exception

See also: exception data type

Usage: exception [integer code] message|builtin-command args...

Creates, but does not throw, an exception object. It passed 2+ arguments the first may be an integer or the string form of a CWAL_RC_xxx result code, with or without the CWAL_RC_ prefix. e.g. "RANGE" and "CWAL_RC_RANGE" are equivalent. If the value is 0, not provided, or is not a number then the the default of CWAL_RC_EXCEPTION is used.

The other argument may be any value type or may be a built-in with additional arguments, in which case the result of that builtin becomes the exception message.

The exception's code and message properties expose those values. The code value need not be from the CWAL_RC_... family of codes: it may be an arbitrary client-defined integer value. (If it is not an integer, it is coerced to one.)

To throw the exception use the throw command.

exit

Usage: exit [value|builtin-command args...]

Exits the script without an error, optionally with a value which gets propagated back to the interpreter's caller. Script code cannot stop propagation of exit but C code between the exit and the top-level application can do so.

expr

Usage: expr args...

Evaluates its arguments as a single expression, as opposed to a command. This performs conventional math operations on its arguments, which may include any of the standard math operators, parenthesized groups, etc. Expansion of $variables and [command...] are performed.

See the expr page for more details.

for

Usage: for {pre} {condition} {post} {body}

Performs a conventional "for" loop.

See the flow control page for more details.

foreach

Usage: foreach [indexIdentifier] valueIdentifier iterable {script body}

Iterates over list entries, object properties, or string characters.

See the the flow control page for full details.

if

Usage: if {condition} {script body} [else if {condition} {body}] [else {body}]

Implements a conventional if/else construct.

Documented in more detail in the flow control page.

incr

Usage: incr identifier|$var[property] [value=1]

See also: decr

Increments the value of the referenced variable or object property or list index entry by the given amount. Fails if given an unknown var name but will use a default value of 0 for unknown (or non-string) object properties and list entries. Evaluates to the new value.

info

Usage: info sub-command [arg]

Permits querying all sorts of information about variables and values. Described in the info command page.

new

Usage: new [-post] Constructable ...args [{post-construction code}]

new provides a mini-framework for constructing new "classes" (of a sort). Described in detail on the new page.

object

See also: object data type

Usages:

"Object" is the framework's general-purpose property storage and hashtable type, analog to a "dict" in TCL. This command offers several approaches to creating objects...

object Usage #1

Usages:

Creates a new object instance. Its optional arguments may be either pairs of keys and values or a single {...} containing pairs of keys and values, e.g. object {a b c d} and object a b c d are equivalent but the former may span lines without requiring that the newlines be escaped.

Keys may be any of:

Note that keys retain their type, are not coerced to strings, and property lookups are type-specific, so 1.0, 1, and "1" are three independent keys. It is never recommended to use floating-point numbers as keys because "1.0 is not always 1.0".

Value tokens in the form {...} are treated as objects, not strings, and are recursively processed.

Value tokens in the form (...) are treated as arrays, not strings, and are recursively processed, as per the array command. (See that command for an achtung regarding the different-from-the-norm semantics of (...).)

Keys may not currently be dynamic, e.g. var dereferences or the output of a command, but set $anObj[...] does accept such things.

object Usage #2 (-scope)

Usage: object -scope {code}

This variant behaves much differently than the one described above: it runs the given {...} as a new scope and takes over the scope's properties. The main implication of that is that all variables and functions declared within that scope become members of the object and retain special features like their "constness." For example:

decl o object -scope {
  decl a 1
  decl -const b 2
  proc foo {} {...}
}
assert [info is-function $o[foo]]
assert 2 == $o[b]
assert [[catch {set $o[b] 3}].code-string] == CWAL_RC_CONST_VIOLATION

Sidebar: this form was added primarily to simplify creating new classes for use with the new command. Interestingly, the C-level implementation for this approach requires only about a third as much code as the other one.

object Usage #3 (-this)

Usage: object -this {code}

This (haha!) variant behaves identically to -scope but also defines, for the duration of the object's body, this to be the under-construction object (the same object which stores the scope-level variables!). The primary use case for which to prefer -this over -scope is when defining new class prototypes and you want to change or remove the class prototype's own prototype:

decl Klass proc {} {...}
set Klass[__prototype] object -this {
    unset this.__prototype
    # 'this' _is_ the scope-level property storage, ergo...
    assert this == this.this
    decl x 1
    assert 1 == this.x
    ...
}

pragma

Usage: pragma [...]

Described in the pragmas page.

proc

Usage: proc [-global|-anon|-local] [name] {args} {body} [using [-scope] {...}]

Creates a function. Described in detail in the proc page.

return

Usage: return [value|builtin-command args...]

Exits the current function or, if running a top-level script, that script, optionally with a result value. If its first argument is a reference to a builtin command, that command is called and its result becomes the return result.

set

Usage: set [-const] varname|$var[property] value|builtin-command args...

Sets a variable to a new value. See variables for full details. The -const flag is only legal when used to set object properties, not scope-level variables or list indexes.

It accepts a builtin command as its final argument, as described for the decl command.

throw

Usage: throw message|builtin-command args...

Throws an exception with the given message string. If the argument is a built-in command then it is called and its result becomes the exception message, else the first argument becomes the exception message. If such a built-in returns an exception object, it is thrown as-is, rather than being embedded as the message of the returned exception. For example:

throw exception RANGE "Number is out of range."

unset

Usage: unset varname | var[property] | var.property ...

Unsets variables from the current scope or object properties. It accepts any number of variables or properties to unset. It throws if asked to unset an unknown (in the current scope) variable but silently ignores missing properties.

If the value of an unset var or property is a propref-type value, the propref, and not the property of the proxied value, is removed.

See variables for full details.

while

Usage: while {EXPR} {body}

Described in the flow control page.

with

Usage: with value {body}

This command enables a shorthand form of accessing properties of a given object within a block of code:

with $myObj {
    set .x 1
    set .y (.x + 1) ;# recall that (...) is (mostly) an alias for [expr ...]
}

In these code blocks, and only in these blocks, the property access syntax syntax .X, with no LHS, is legal. It may begin a chain of X.Y and X[Y] nested property accesses, e.g. .foo[bar].baz.

with runs within its code in its own scope. (Sidebar: it "could" run in the current scope, but only if we give up the ..X property access (see below).)

The result of with is its object operand.

Be aware that nesting with blocks is legal but their semantics are extremely simple: the innermost with to a .X property access determines the object from which the property will be resolved. For example:

# Beware the limits of nested with...
decl o object {x 1}
with $o {set .y (.x + 2)}
assert 1 == o.x
assert 3 == o.y

decl x object {z $o nope 1}
with $x {
    assert 1 == .nope ;# ==> x.nope
    with .z {; # .z ==> x.z
        # In this block, . refers _only_ to x.z aka $o
        assert 1 == .x ;# ==> o.x
        assert 3 == .y ;# ==> o.y
        assert undefined == .nope ;# ==> o.nope
        # However...
        assert 1 == ..nope ; # note the extra dot
    }
}

One . may preceed such property references for each level of with to look upwards, noting that it only works in the same lexical scope. A function call boundary will block inner withs from accessing outer ones.

Sidebar: in a language where object properties are not set in stone, with cannot do anything sensible by checking outer with blocks to try to resolve a .X reference, in particular in a setter context (which object do we target?).

Complete examples of using with can be found in its unit test script.


Builtin Values

In addition to the above commands, certain symbols refer to builtin values. Such symbols differ from variables primarily in that they (like built-in commands) always have O(1) lookup speed and cannot be modified by client code (neither script nor C code). The builtin values are...

All of those are constant values.

The following builtin values are "magic" in some way or other, following rules which apply to no other values:

The lists of builtin commands and values will grow as the language does, but the intent is to keep them brief.

For builtin values which support property access (namely using, this, and whcl), properties can be accessed via the usual propery access syntax.

The Dichotomy of Builtin vs Non-builtin Commands

whcl's syntax is as minimal as it gets: it accepts a "line" with a "command," calls the command, and passes the rest of the line to the command to do whatever it wants with it.

Easy peasy.

The devil, of course, is in the details. In order to be even remotely useful, whcl has to take the next step and provide features for manipulating the scripting environment, e.g. to change the current path of code execution (a.k.a. flow control). Though, in principle, such commands could be attached to whcl the same way that client-bound functions are, it would both severely limit certain capability and would force some large semantic changes on the language. One glaring example of this is that a client-installed command is always (always) run in a new scope. If that were the case for builtin commands, some of them could not function as-is or would have to explicitly know exactly how far down the scope stack they are running from the intended target scope (which, in reality, they cannot reliably know because the engine may inject scopes at any point for garbage-collection purposes). Similarly, builtin commands have direct access to the internal representation of the script language, allowing them to do things which external commands cannot do (and should not be able to, lest they hose it).

Astute whcl users will have noticed that certain built commands behave slightly differently in the face of builtin commands vs client-side commands, for example:

# This is legal:
return proc {} {...}

# But these have much different semantics:
return user-defined-command ...
return $user-defined-command ...

The reason boils down to the latter being ambiguous:

return user-defined-command  ;# a string or a call?
return $user-defined-command ;# reference the function's value or call it?

These cases require explicitly telling whcl their intent:

return [user-definied-command ...] ;# call and return result
return $user-defined-command       ;# a reference to the command's function
return user-defined-command        ;# the identifer/string "user-defined-command"

That ambiguity does not arise with builtin commands for one reason only: builtins have no script-level internal representation. They are "pure native code." It is not possible, in whcl, to take a reference to a builtin command and later dereference it to call that command. It is not possible to overwrite or remove builtins. Client-bound/external commands, on the other hand, are connected as script-accessible entities using the features of the underlying language-agnostic scripting engine, cwal. As such, they play by different rules.

Having said all of that: the capability of certain builtin commands to behave slightly differently when given another builtin command is provided soley as "syntactic sugar." Ideally, this distinction would not exist and non-builtins could be treated identically to builtins. The hard truth, however, is that we live on an Animal Farm: all commands are equal, but some are more equal than others. However, anyone bothered by this distinction is free to not make use of it:

return proc {} {...}   ;# can also be written like:
return [proc {} {...}] ;# same as non-builtin commands

Let's face it, though: the former is easier to both read and write. However, it is still not syntactically possible to pass around a reference to a builtin command via a variable for the simple reason that builtin commands have no state which a variable can reference. That difference "could" be smoothed over via the addition of further infrastructure, but such extra weight currently seems unnecessary. (Also, a use case for it has yet to crop up.)