sxript

Sxript source

The JavaScript runtime is the reference implementation. The BASIC runtime uses one shared library for QBJS and QB64, with a separate five-line CLI.

JavaScript

Runtime source — sxript.js

Save CLI to Device — cli.js · requires Node.js and src/sxript.js

BASIC — QBJS / QB64

Runtime source — sxript.bas

Save CLI to Device — cli.bas · requires sxript.bas

Documentation

Sxript is a programming language for calculations, text, collections of values, and programs built from expressions. The lessons below assume no previous programming experience. The language reference follows the lessons.

1. Run an expression

A program is text that tells a computer what to calculate or do. That text is called source code. Sxript reads the source, performs the calculation, and produces a result.

  1. Open the workspace in another tab. Keep this guide open beside it.
  2. Click Clear above the source editor, then enter the code below.
  3. Click Run. The newest result appears at the top of the output area. You can also press Ctrl+Enter, or Command+Enter on macOS.
2+3*4

Result+14.0

This is an expression: a piece of code that produces a value. Here * means multiplication. The result +14.0 means fourteen; the sign and decimal point are part of the number's displayed form.

Replace the editor contents with this expression and run it:

(2+3)*4

Result+20.0

Parentheses group a calculation. Sxript adds two and three first, then multiplies the result by four.

2. Numbers and text

A value is something a program can use: a number, some text, or a collection of other values. Try these calculations one at a time:

SourceMeaningResult
8+2Add+10.0
8-2Subtract+6.0
8*2Multiply+16.0
10/4Divide+2.5
2^3Raise to a power: 2 × 2 × 2+8.0
10%3Remainder after division+1.0
Group calculations explicitly. Sxript has its own operator rules. For example, 10-3-2 means 10-(3-2) and gives +9.0. Write (10-3)-2 to get +5.0. Use parentheses to make your intended calculation clear; the full ordering is in Syntax and operators.

To write literal text, start with a backtick (`) and end with an apostrophe ('). These are two different characters:

`Hello, world!'

ResultHello, world!

Quoted text is often called a string. Its contents are data: Sxript does not treat the words as instructions. Use + to join two strings:

`Hello, '+`world!'

ResultHello, world!

3. Give values names

A variable is a name associated with a value. Run this expression:

let(width,5)

Result5

let stores the value 5 under the name width. The comma separates the name from the value. Now replace the editor contents with the following expression and run it in the same workspace:

[width]*3

Result+15.0

Square brackets retrieve the value associated with a name. Write width when giving the name to let, and [width] when using its stored value. Names are case-sensitive: width and Width are different names. Another let(width,8) replaces the stored value.

Workspace state. Definitions remain available between Run clicks. The editor's Clear button removes source text; the output's Clear button removes output history. Neither removes definitions. Reset clears definitions and output, then immediately runs the current editor contents. To start with an empty workspace, clear the editor before pressing Reset.

4. Write a program with several steps

The remaining lessons use complete programs. Replace the whole editor with each program before running it.

block({
  let(width,5):
  let(height,3):
  print_[width]*[height]
})

Result+15.0

This program calculates the area of a rectangle. Read it from top to bottom:

  1. block({ ... }) executes the instructions between the braces.
  2. The first two instructions give names to the rectangle's dimensions.
  3. The colon : separates one instruction from the next. Line breaks and indentation make the source readable; they do not separate instructions.
  4. print_ contributes a value to the program's result. Here it contributes the product of the stored dimensions.

The underscore is part of print_. A block that never uses print_ can produce an empty result, which the workspace displays as ().

Label a result

Use quote(value) to turn a computed value into text before adding a label. This also preserves the sign of a negative number:

block({
  let(temperature,-2):
  print_`temperature = '+quote([temperature])
})

Resulttemperature = -2.0

print_ continues to the next instruction; it does not end the program or automatically start a new line. Add \n inside text when you want a line break:

block({
  print_`first\n':
  print_`second'
})
first
second

5. Define a reusable calculation

A function is a calculation you can call with input values. Sxript already supplies functions such as sqrt(9), which gives the square root of nine. Define your own with func:

block({
  func(square,{[x]*[x]}):
  print_square(5)
})

Result+25.0

square is the function's name. The code inside { ... } is its body: the calculation performed when it is called. [x] is its first input. Calling square(5) supplies five, so the body calculates five times five.

Inputs supplied to a call are called arguments. A second argument is available as [y]:

block({
  func(rectarea,{[x]*[y]}):
  print_rectarea(5,3)
})

Result+15.0

The comma separates the two arguments. Supply each input that your function uses. Functions can use up to six supplied arguments: [x], [y], [z], [t], [u], and [v].

6. Work with a collection of values

A vector is an ordered collection. Angle brackets enclose its elements, separated by commas. Element positions start at one:

SourceMeaningResult
<2,4,6>A vector with three elements<2,4,6>
elem(<2,4,6>,2)Get the second element4
len(<2,4,6>)Count the elements3

Use apply to call a function on each element and collect the results:

block({
  func(square,{[x]*[x]}):
  let(numbers,<2,4,6>):
  print_apply(square,[numbers])
})

Result<+4.0,+16.0,+36.0>

This calls square(2), square(4), and square(6). The original vector remains available as [numbers]. To get its second element, write elem([numbers],2); square brackets retrieve a named value, not a numbered element.

7. Choose a result

A condition is a test used to choose what happens next. greater(a,b) returns 1 when a is greater than b, and 0 otherwise. iff selects between two alternatives:

block({
  let(temperature,25):
  print_iff(
    greater([temperature],20),
    {`above 20'},
    {`20 or below'}
  )
})

Resultabove 20

The first argument is the condition. The second is the code to execute when it is true; the third is the code to execute when it is false. The braces preserve each alternative until iff chooses one. Only the chosen alternative executes.

Change 25 to 10 and run again: the result becomes 20 or below. To test equality, use =, as in 5=5, which returns 1. Use greater for comparisons; < and > are vector brackets.

8. Repeat instructions

A loop repeats a set of instructions. For a fixed repetition count, put the count before do, and end the repeated instructions with loop:

block({
  3:
  do:
    print_`x':
  loop
})

Resultxxx

Here print_ contributes the letter three times. The result has no spaces or line breaks because the program did not supply any.

Use for when each repetition needs a changing counter. This program adds the whole numbers from one through five:

block({
  let(total,0):
  for(<i,1,5,1>,{
    let(total,[total]+[i])
  }):
  print_[total]
})

Result+15.0

<i,1,5,1> names the counter i, starts it at one, ends at five, and changes it by one each time. The body adds the current [i] to [total]. The successive totals are one, three, six, ten, and fifteen.

for includes the end value when the step reaches it. It chooses an increasing or decreasing direction from the start and end; the step's magnitude must be nonzero.

9. Read an error and correct the source

An error means the current calculation could not be completed. The workspace shows the message and relevant source. Start with the message, then check the name or punctuation it identifies.

[widht]

This reports Undefined symbol: widht. If you meant the earlier name width, correct the spelling and make sure its let instruction has run. A misspelled name and a name that has never been defined both fail a lookup.

2*(3+4

This reports Expected RPAREN; found EOF. RPAREN means a closing parenthesis; EOF means the source ended. Add the missing ) to get 2*(3+4).

If a program gives an empty result rather than an error, check its colons and print_ instructions. An unfinished expression such as 2+ can remain visible as source: Sxript allows partial expressions for constructing code.

A failed run can leave definitions created before the error. Clear the editor and press Reset when you need a clean start, then enter the complete corrected program.

10. Practice with complete programs

Modify the rectangle program to print both its area and its perimeter. For width five and height three, the area is fifteen and the perimeter is sixteen. Calculate the perimeter as 2*([width]+[height]).

Show a solution: area and perimeter
block({
  let(width,5):
  let(height,3):
  print_`area = '+quote([width]*[height])+`\n':
  print_`perimeter = '+quote(2*([width]+[height]))
})

Then define a function named cube that raises its input to the third power. Apply it to <1,2,3>. The result should be <+1.0,+8.0,+27.0>.

Show a solution: cube each element
block({
  func(cube,{[x]^3}):
  print_apply(cube,<1,2,3>)
})

For another complete example, select first night in the workspace's examples menu. Selecting an example loads and runs it. It demonstrates apply and reduce: apply produces a result for each element, while reduce combines elements using a two-input function. Next, inspect lists, physical units, and procedure.

Language reference

Syntax and operators

The following sections describe details used by longer programs. The bracket forms have distinct roles:

FormRoleExample
( ... )Group an expression or supply arguments to a function(2+3)*4, sqrt(9)
` ... 'Quoted text`hello'
< ... >Vector data<1,2,3>
{ ... }Preserved executable code, called occult code{[x]+1}
[ ... ]Look up a named value in the environment[total]

An environment is the set of names available at a point in a program. Occult code preserves instructions so another operation, such as block, func, or iff, can execute them later. Braces alone do not execute a sequence.

Operator order

Higher rows bind more tightly. Each row is a separate precedence level. Infix operators group from the right: a-b-c means a-(b-c). Parentheses override this ordering.

Highest to lowestOperation
! after a valueLogical NOT; 0! gives 1. This is not a factorial operator.
+, - before a valueUnary signs
^Power
*Multiplication
/Division
%Remainder
+Addition or concatenation
-Subtraction
=Equality
&Logical AND
|Logical OR
~function~Call a named two-input function between operands
?Apply the left operand as a function to the right operand

Consequently, 12/3*2 is 12/(3*2), and -2^2 is (-2)^2. Write (12/3)*2 and -(2^2) for the other groupings.

For conditions, numeric zero is false and nonzero numbers are true. Numeric & and | specifically test whether their operands are positive, and both operands execute. Use iff with braced alternatives when an operation must be skipped, such as avoiding division by zero.

Arithmetic operators apply element by element to vectors; a scalar is applied across the elements. Operations on two vectors stop at the shorter length. For complex multiplication, division, or powers, use the functions in complex-prg.txt.

Useful built-in functions

FunctionsPurpose
abs(x), sqrt(x)Absolute value and real square root
sin(x), cos(x), tan(x)Trigonometry; angles are in radians
eul(x), ln(x), gamma(x)Exponential, natural logarithm, and gamma function
quote(x), unquote(x)Convert a value to quoted text, or interpret text as source
len(x), elem(x,n)Count elements or select one by its one-based position
apply(f,values), reduce(f,values)Transform each element, or combine a collection
report(name)Inspect a user-defined function body

Real square roots require nonnegative arguments, and natural logarithms require positive arguments. Ordinary real powers do not automatically produce complex numbers. See the complex numbers example and gamma function example for their respective operations.

Anonymous functions use $, as in $({[x]*[x]}). Supplied arguments use the six parameter names listed in the lessons. User-defined functions do not declare or enforce a required argument count; a missing parameter can find a surrounding binding. Supply every argument your function body needs. Built-in functions check their argument counts.

Results from procedural code

print_ collects source fragments, joins them in order, and reduces the resulting expression. It is not an automatic line printer: block({print_1:print_2}) produces 12, while block({print_1+1:print_2+2}) produces +6.0. Use quoted text, quote, and explicit \n separators for labelled output.

for also joins its generated results. To collect them into a vector, put a comma after each generated element and use smooth to finish the collection:

smooth(<for(<i,1,3,1>,{[i],})>)

Result<+1.0,+2.0,+3.0>

The procedural layer also supports named jump targets, written anchor_name, unconditional jumps with goto_name, and conditional jumps with if_condition@name. The procedure example demonstrates these forms.

Scope: where names are available

Scope describes which names an expression can read or change. block shares the surrounding environment. sub can read surrounding values but keeps its own writes private:

block({
  let(answer,42):
  print_sub({
    let(answer,0):
    print_[answer]
  }):
  print_`,':
  print_[answer]
})

Result0,42

The sub expression produces zero, but the surrounding answer remains forty-two. Use sub for temporary calculations whose definitions should stay local.

Functions resolve ordinary names through the environment of the caller. A function call by itself does not isolate writes. For a function that needs private working variables, define its body using sub({ ... }).

Exact decimal arithmetic

Ordinary arithmetic uses binary floating point, a finite approximation to real numbers. Many decimal fractions cannot be represented exactly that way:

0.1+0.2

Result+0.30000000000000004

Use largeadd, largesub, and largemul for exact decimal addition, subtraction, and multiplication:

largeadd(0.1,0.2)

Result+0.3

Use these functions throughout an exact calculation. They cannot undo rounding already introduced by an ordinary operator: largeadd(0.1+0.2,0) still produces +0.30000000000000004.

largediv performs division with finite truncation precision; a repeating fraction such as one third is still truncated. largeabs gives an absolute value, while largesci and largeunsci convert to and from a coefficient-and-exponent representation.

The largenum-prg.txt module adds powers, factorials, Fibonacci numbers, comparisons, and precision control. Its largetrim(number,digits) counts significant digits, not decimal places. The examples build on these operations to calculate square roots, Pascal's triangle, the golden ratio, and digits of pi.

Modules and examples

A module is a source file containing reusable definitions. These files live in prg/. Complete demonstrations live in examples/.

include executes a module in the current environment and leaves its definitions available. This complete program loads vector helpers and uses one to add three elements:

block({
  include(`vector-prg.txt'):
  print_vsum(<1,2,3>)
})

Result+6.0

run executes module code behind a private write boundary, so its definitions stay local. Both include and run return the module program's printed result. Loading a definition-only module can therefore display () even when it succeeded.

Both operations execute the module each time they are called. The browser caches downloaded source; that does not make module execution happen only once. The workspace discovers files through the examples/ and prg/ directory listings. Selecting an example loads and runs it; selecting a program/module loads its source for inspection.

Generated code

Sxript can preserve and construct source code as values. Partial expressions such as 5+ and e? remain available until later source completes them. This is used by the functional and combinator examples.

Inside constructed occult code, {name} inserts the current value associated with name. This program constructs a function whose name comes from a variable, then calls it:

block({
  let(name,counter):
  unoccult({func({name},{0})}):
  print_counter(9)
})

Result0

The constructed counter function always returns zero; the supplied argument is unused. report, elem, replace, and unoccult let longer programs inspect, transform, and execute definitions. See mutable-counter.txt for a stateful example.

Run a local copy or use the command line

The browser lessons work in the existing workspace without installing a compiler. A terminal is a window for entering operating-system commands. To serve a downloaded project locally, open a terminal in the folder containing index.html. If Python is installed, run:

python -m http.server 8000

Open http://localhost:8000/index.html in your browser and keep the terminal running while you use it. Keep src/, prg/, and examples/ beside the HTML files so the workspace can load its runtime and source files.

A command-line interface (CLI) accepts expressions at a text prompt instead of in the browser editor. With Node.js installed, run these commands from the project folder:

node cli.js

Enter one expression per prompt. Definitions persist between entries. Use .help for commands, .load examples/first-night.txt to execute a file, and .exit to leave. To execute a file directly from your terminal:

node cli.js examples/first-night.txt

For a source checkout with package.json, npm test runs the JavaScript regression suite. This is a development check; it is not required for using the language.

BASIC runtime

The shared QBJS / QB64 interpreter is in src-basic/sxript.bas. The entry point is src-basic/cli.bas, which includes the library after the main program:

DIM expression AS STRING
DO: LINE INPUT "[sxript]: "; expression
    IF expression = ".exit" THEN EXIT DO ELSE PRINT SxriptEval$(expression)
LOOP
'$INCLUDE: 'sxript.bas'

Compile cli.bas with QB64, or load it with sxript.bas in QBJS. Definitions persist between expressions. Enter .exit to leave the prompt.

Keep the native executable in src-basic/ so the default ../prg/ module search resolves to the bundled modules. For QBJS, modules can be served over HTTP using SxriptModuleRoot, or registered with SxriptRegisterModule.

The library initializes on the first evaluation. Its public API includes SxriptEval$, SxriptReset, module registration, input and random providers, an evaluation step limit and tracing. Errors are returned as ERROR: message and are also available through SxriptLastError$ and SxriptLastErrorSource$. Native math, text encoding and date/time formatting follow the host runtime.