Contents | Open Console |
|
|
Abstract |
Sxript is an interpreted high-level functional/imperative programming language designed for numerical evaluation and problem solving with high-order functions, variable storage, scoped subprograms, and arbitrary-precision numbers, along with file I/O, graphical output, and more. The Sxript source code is made from scratch; no libraries, dll's, or borrowed constructs are used.
Sxript may be used as a stand-alone tool on most devices, or a programmer may include sxript as a component of another BASIC, JavaScript, or C++ application. Prototyped in a QB45-compatible dialect, the main codebase may be used directly with modern QB64 and FreeBasic compilers. Identical JavaScript and C++ implementations are auto-generated one-to-one translations of the main codebase. |
Use In-Browser (or Download) |
|
|
|
|
Legal |
Sxript is conveyed as open source free software, as detailed in license.txt. |
Logo |
Signature of a Medieval Illiterate - Frederick W. Umminger Jr., Yale Record, 1959. Thanks to Futility Closet. (Apparently a company that makes jackets has the same logo. I hope they know what it means! Thanks Fellippe for pointing this out.) |
Pronunciation |
Some subconsciously swap a c in place of the x, pronouncing "script". Others omit the second letter entirely to say "sript". A student from India uttered "schr-r-ript", with the r rolled. While there is no official pronunciation of Sxript, I endorse any of the three above pronunciations in conversation. |
Credit |
Thanks to Luke for your valuable feedback. Honorable mention for qbguy. |
Contact |
www.barnesreport.net |
About |
This project is the embodiment of Greenspun's tenth rule of programming, stating that "any sufficiently complicated C or Fortran program contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp." Whether this is true or untrue is for you to decide.
Any device with a JavaScript engine, C++ compiler, or BASIC compiler can run Sxript. The source code is built only from universal coding constructs that behave identically across all sensible languages, namely "for" loops, if-then structures, simple arrays, etc. This discipline gives rise to a "Rosetta Stone" effect, enabling one-to-one translation to other languages with no hand-editing. When the next big language comes along, whatever that may be, Sxript will likely have a home there. ![]() |
|
|
"I pray daily that more of my fellow programmers may find the means of freeing themselves from the curse of compatibility." - Edsger Dijkstra |
|
|
The entire Sxript project (including this page) is contained in:
There is no formal "install" process. See VERSION.txt for specific release information. |
|
Sxript is powered by the QB45-compatible source file sxript.bm, along with its header, sxript.bi. All implementations, including non-BASIC versions, stem automatically from these two source files. If edits are made to the source files sxript.bi/bm, the JavaScript and C++ versions need not be updated by hand: executing the program BI+BM, combines each source file to create sxriptflat.bas, overwriting the previous version. Following this, executing BAS-TO-JS/CPP will translate sxriptflat.bas into sxript.js for JavaScript, or sxript.h for C++. |
|
BASIC Three identical Sxript executables lay in the base folder, sxript (Linux), sxriptfb (FreeBASIC-L), sxript.exe (Wine). While any of these executables may already work on your system, it's recommended for Windows, gnu/Linux, and Mac OS users to compile natively. To generate executables on your own, use a QB45-compatible compiler to process sxript.bas, or the FreeBASIC compiler for sxriptfb.bas. JavaScript The Sxript folder includes two browser-based JavaScript implementations: calculator.html (mobile), and console.html (desktop). These are backed by the source file sxript.js. C++ Sxript ships with a C++ executable named sxriptcpp, backed by sxript.cpp and sxript.h. The majority of testing has occurred on a Linux system using the gcc compiler. |
|
|
"Data dominates. If you've chosen the right data structures and organized things well, the algorithms will almost always be self-evident. Data structures, not algorithms, are central to programming." - Rob Pike |
|
|
The Sxript interpreter treats general input recursively as a kind of mathematical expression. Closed sets of parentheses ( ) are eagerly evaluated starting with the most-embedded set, from left to right. If the text immediately to the left of ( ) matches a primitive or function name, then the function body is invoked. Functions of more than one variable have arugments separated by a comma ( , ). Note that such a list is not evaluated in linear order. (When sequence is important, use a subprogram.)
Evaluation occurs over a number of "passes" through a core evaluation function, with the result of one step being the input of the next step. When the input expression finally emerges unchanged, evaluation is complete and recursion closes. |
|
Data comes in several flavors: Numbers, Strings, Vectors, Occulted, Symbols
Numbers are numeric literals. These are type DOUBLE in BASIC & C++ implementations, and type FLOAT in JavaScript. Acceptable numerical characters are the numerals 0 through 9, along with decimal points (.), sign information ( + - ), and special characters denoting scientific notation. To complement native numerics, Sxript has an `in-house' large-number format designed to accomodate arbitrary-precision calculations, utilized through the large primitive. Scientific notation varies per implementation of Sxript. For instance, JavaScript uses the format #.##e~x, where #.## is the base number, e denotes the operation *10^(), and ~x is a signed real number. Meanwhile, most BASIC implementations prefer the format #.##D~x. For more exotic formats, edits to sxript.bm could be necessary. Strings, also known as quotes, hold alphanumeric text whose meaning is completely ignored by the interpreter. String literals are always enclosed on the left by the "backtick" key ( ` ), which is on the "tilde" key without pressing SHIFT. Strings are closed on the right by the single quote ( ' ). The only means of preserving hard-typed whitespace is to enclose it in a string. All other whitespace-making characters are crushed by the interpreter. Vectors, resembling arrays or lists, are structures that hold one or more elements in linear sequence. These structures are enclosed by angle brackets ( < > ), with neighboring elements separated by a comma ( , ). Vectors need not have uniform type: elements may mix between numbers, strings, embedded vectors, and so on. The starting index for vectors is one ( 1 ), and the final index can be recovered by the len( ) primitive. Occulted code is anything contained in curled braces ( { } ). These structures are lazily evaluated (not evaluated) until a language primitive shaves off the outer braces. Note that a function body is always defined as occulted code. Symbols are special occurrences of, in strict order, [x], [y], [z], [t], [u], [v] that occur inside a function body. Arguments passed to a function are represented inside the function body by such symbols. For instance, a function of one argument will only have [x] somewhere in the function body. A function of two arguments must have [x] and [y]. When more than six arguments must be sent to a function, they must be passed as a vector or other structure. The square bracket wrapping ( [ ] ) has another meaning when enclosing a known variable name. This is spelled out explicitly in the functions section. |
|
On operator is a one-character symbol that sits between a "left" argument and a "right" argument. When sandwiched between numbers, operators have a straightforward meaning. However, strings and vectors can also be passed to operators, and in some cases, operators may handle unlike types on the left and right. (This is called operator overloading.) Operators are selected for evaluation in descending order according to their precedence order. Following is a table of all operators in descending precedence order:
In the spirit of "teach by example", the code boxes below show a few straightforward uses for operators, along with several non-obvious instances of operator overloading. |
|
Note: The plus and minus symbols (+ and -), used for identifying whether a number is positive or negative, have tighter binding than operators have to numbers. This can cause some confusion, for instance -10^(-2)is interpreted as (-10)^(-2), which evaluates to +0.01. |
|
|
Write functions that do one thing and do it well. Write functions to work together. Write functions to handle text streams, because that is a universal interface. - Doug McIlroy (paraphrased), inventor of Unix pipes |
|
|
A variable is a named storage chunk that can hold any structure at all from numbers and strings to occulted code to whole subprograms. All variable assignments are achieved through the let primitive, taking two arguments: (i) the variable name (wrapping and whitespace characters are automatically stripped), and (ii) the variable content.
Variable content is restored by enclosing the name in a replacement operator, usually a set of parentheses. (Note that variable names not specially wrapped are not evaluated until that variable is wrapped.) The following code boxes demonstrate basic variable storage and manipulation. |
|
Variables typically have no means of penetrating through layers of braces ( { } ), implying (among other things) that variables cannot up-penetrate through any scope layer. This is an incredibly safe default for a programming language, but a way around this should be available. In Sxript, a variable name wrapped in square brackets ( [ ] ) can propagate into the first and second layers of braces ( { } ). |
|
A function is a mechanism that returns something uniquely depending on the arguments sent to it. All function assignments are cast through the func primitive, taking two arguments: (i) the function name (wrapping and whitespace characters are automatically stripped), and (ii) the function body as occulted code, enclosed in curled braces ( { } ). A function is invoked by typing its name followed by a parenthesized list of arguments, as in funcname(arg1,arg2,..). The function body refers to its arguments in strict order: [x], [y], [z], [t], [u], [v]. If more than six values must be passed to a function, they must occur as the elements of a vector or other structure. The func primitive itself returns the function name, meaning functions are immediately invokable. The following code boxe demonstrates function storage and manipulation. |
|
Note: It is "strictly recommended" that you enclose mathematical function definitions in parentheses ( ), as seen in the definition sum in the box above. Otherwise, ave would quietly report 12.5 instead of 7.5 for the example chosen. |
|
A Lambda expression, or anonymous function, is a function whose content is not named by the programmer, but instead treated as data, evaluated specifically when called. |
Doug Crockford: "It took a generation to agree that high level languages were a good idea. It took a generation to agree that goto was a bad idea. It took a generation to agree that objects were a good idea. It took two generations to agree that lambdas were a good idea." |
The syntax for a lambda expression begins with (you guessed it) a call to the lambda primitive. Lambda's only argument is a function body, which must occur as occulted code, i.e., wrapped in curled braces ( { } ). The argument list is simply parked to the right of the lambda({ }) structure as shown. |
|
|
If the question "which is more powerful, functions or operators?" ever arises in your mind, allow me to spoil the answer for you: functions. The reason is that code is one-dimensional, and operators can never take take more than two arguments: you are limited to the "left" side and the "right" side. With functions however, you can throw any number of arguments. Therefore, we can syntactically treat two-argument functions as operators, and set them in-line with "ordinary" operators. This is executed by wrapping the function name in tilde ( ~ ) characters, as in ~funcname~, and then using this construct as you would an operator. |
|
Note: Confusion may arise when using tilde functions as operators: ordinary operators are sought out in a left-to-right fashion, heeding the precedence hierarchy detailed above. Tilde `operators', on the other hand, are parsed from right-to-left with no regard to operator precedence. Use parentheses to arrange order in calculations as shown below. |
|
|
|
"Perfection [in design] is achieved, not when there is nothing more to add, but when there is nothing left to take away." - Antoine de Saint-Exupry |
|
|
A subprogram is a sequence of statements evaluated in linear order, each separated by the colon ( : ) symbol, with the whole sequence enclosed in curled braces ( { } ) as occulted code, invoked by the sub primitive. Functions and variables created inside a subprogram are scoped as private to that subprogram and its child entities. A subprogram delivers output through a minimum of one print statement in its body, analogous to C-family functions requiring a "return" statement. |
|
The argument symbols [x], [y], [z], [t], [u], [v] only propagate to a certain "depth" into the function body, where in this context, "depth" is measured in layers of curled braces ( { } ). The rule-of-thumb is: [x]-symbols penetrate into the zeroth and the first layers of braces ( { } ) in a function body, and no deeper. This "engineers out" any naming conflicts that occur when functions are defined within functions. |
|
A code block, invoked by the block primitive, resembles the subprogram in every fashion, save one difference: blocks do not have private scope. A code block shares scope with the entity that calls it. The following pair of boxes illustrates the subtle difference between a subprogram and a code block. |
|
|
Subprograms saved on a local drive as plain text can be loaded into the Sxript interpreter by the exe function, taking a single argument specifying the path to file. Any functions or variables defined inside the subprogram are privately scoped. |
|
|
Code blocks saved on a local drive as plain text can be loaded into the Sxript interpreter by the include function, taking a single argument specifying the path to file. This is analogous to an executed subprogram, except any functions or variables defined inside the code block are not privately scoped. |
|
|
|
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." - Brian W. Kernighan |
|
|
A limited set of imperative commands not part of our functional paradigm has been reserved for allowing greater flexibility within subprograms. These statements, namely if, goto, do, loop, print, etc., must occur in within a subprogram or code block, and always have same-sub / same-block scope. For instance, you cannot use goto to cross into a different subprogram, or to enter the middle of a different function. |
|
The anchor statement is equivalent to the traditional "label" or line number in programming languages. Anchors are used in conjunction with the goto statement, which, as the name implies, is precisely the classic "GOTO" that we love to hate. This programmer's conclusion is that GOTO can be useful, but only if one isn't making spaghetti code. |
|
|
The if_... @ anchor structure is a conditional GOTO statement. If the contents between _ and @ evaluates to +1 or `true', then control is passed to anchor. In the box below, the if_... @ anchor causes the interpreter to skip the assignemnt let(a,555). |
|
|
The do...loop structure is the simplest mechanism for repetitive behavior. The number of iterations N is set by the number that directly precedes the do statement. Everything until the next loop statement is evaluated N times. Early loop termination can be achieved with a (conditional) goto statement. Loops can also be nested. |
|
|
The print statement is the means through which subprograms and code blocks hand tangible output to the parent function. For private-scope subprograms, print is the only way for a function to throw output. This is somewhat similar to the "return" statement in C-family languages, except print does not terminate the function call. There can be multiple print statements per subprogram, but there must never be zero. |
|
|
|
"The key to performance is elegance, not battalions of special cases." - Jon Bentley and Douglas McIlroy |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
"Simple things should be simple, complex things should be possible." - Alan Kay |
|
|
It turns out that the FOIL operation from algebra, the two-dimensional dot and cross products from vector algebra, along with complex number operations, can boil out from a more general formula for multiplication, developed here. If we don't strictly insist on multiplication as the central operation, we produce complex number addition as well. |
|
|
|
|
Analytic functions are "well-behaved" mathematical expressions that depend on one (or more) continuous variable(s). These are the natural fodder for calculus functions, and also ASCII plotting (see Hardware I/O). With baseline support for trigonometry (see numerical primitives), we expand utility by exploiting trig identities. Among our definitions are the hyperbolic counterparts to the baseline trig functions in terms of the exponential. |
|
|
|
|
Arithmetic Functions are primarily a re-skinning of elementary operators. Here we also define a few specialized tools for averaging lists, evaluatinbg factorials recursively, and computing compund interest. |
|
|
|
|
The word "bit" is an abbreviation of binary digit, particularly a one ( 1 ) or a zero ( 0 ). A bitwise operator is an operator specifically designed to operate on a pair of bits, whose return value is (typically) also a bit. Here we use the "and" and "or" elementary operators ( & | ), along with a homemade "not" function to construct a canon of bitwise operators. It's interesting to note that all bitwise operators could possibly be constructed using only nand. |
|
|
|
|
This is the proverbial "Calculus 101" package. Starting with any "well-behaved" analytic function, we can compute the derivative, the integral, and also solve f(x)=0 problems numerically by Newton's method. |
|
|
|
|
With proper preparation, Sxript handles complex number operations, which begins by including the generalized multiplication formulas developed in AbstractMult.txt. Having complex addition and complex multiplication on hand, we recast the cadd and cmult functions such that they can be used as tilde operators. We do the same for complex subtraction while defining csub on-the-fly. (It doesnt't exist in AbstractMult.txt.) The imaginary number i is naturally defined as the Sxript vector <0,1>. |
|
|
|
|
The General Science Functions are a miscellany of everyday unit conversions. |
|
|
|
|
Your classic hash table. Use is self-explanatory. |
|
|
|
|
By `large numbers', we really mean `high-precision' numbers. |
|
|
|
|
A matrix in Sxript is a vector of vectors. It must be "rectangular" in shape, but many are square. |
|
|
|
|
Traditionally in functional and imperative lamguages, the treatment of data v.s. functions couldn't be more different. This can be a bit limiting to a programmer, especially when circumstances beckon that data and functions be nontrivially related. A paradigm called Object-Based Programming is a response to this conundrum.
An object is a collection of related data and functions. While true Object Oriented Programming has no strict definition, there are several conventionally agreed-upon tenets (abstraction, encapsulation, inheritance, and polymorphism to name a few) that make a given language a "true OOP langauge". Taking lessons from JavaScript and Python, Sxript has thus far made no attempt to satisfy all requirements. Working in a functional paradigm, we have something more powerful anyway! |
|
An object is an N*2 matrix, where N is the total number of members in the object. A member is a generic term for "data" or "function". Each member is uniquely identified by a key, stored as a string. |
|
Being just fancy vectors, the programmer has full freedom as to how objects are created. They can be the output of functions, subprograms, user input, and so on. In the above code box we define an object named billy, having four members. The data represented by `name' and `country' are stored as-is, but the data called `birthyear' is evaluated to 1984 before the object is stored. The final member marked `talent' is a function body. (William has a talent for multiplication.) |
|
|
An object is referenced (like an ordinary variable) by enclosing its name in a set of parentheses ( ). To access a particular member, we use the mem function. For the example on hand, mem((billy),`name') evaluates to `William Barnes'. Likewise, mem((billy),`talent') simply returns the occulted code {([x]*[y])}. To make use of the function, we could place "lambda(" on the left, and ")(arg1,arg2)" on the right. This means we'd have a full-blown lambda expression with arguments, which the interpreter knows how to handle. |
|
|
One notion that is considered fundamental to object-oriented programming is inheritance. Inheritance is a scheme for objects to be arranged in a tree-like hierarchy such that any given object "inherits" (has access to) the members of its "parent" object, also known as a prototype object. Any object can have its contents rewritten without affecting the prototype, and editing a prototype does not literally change its child objects. It is important to note that the parent-child relationship is specified by the child, not the parent. That is, the prototype does not "know" which objects refer up to it. The child object specifies its prototype obejct by the `prototype' tag. |
|
|
|
Most OOP languages include some kind of "this" or "self" keyword, which is a way to "grab the name" of whatever object is in context. This is especially useful when one member of an object depends explicitly on a different member. The object-based programming paradigm in Sxript does not have any kind of "this" keyword, but with a few tricks from functional programming, self reference is a breeze. |
|
|
|
|
Here we begin to cover the engineering student's view of vector algebra: dot product, cross product, magnitude calculation, etc. From here, the discussion moves past trigonometry, and onto linear algebra. |
|
|
|
|
"There are two ways of constructing a software design. One way is to make it so simple that there are obviously no deficiencies. And the other way is to make it so complicated that there are no obvious deficiencies." - C.A.R. Hoare |
|
|
A combinator is a device from functional programming & lambda calculus that has no practical value beyond proving that a language can support a combinator. They come in several flavors, most notably the "Y"-combinator. In this particular example we demonstrate the point by using a recursive factorial function without ever defining it. |
|
|
|
|
Currying is a gesture form functional programming where one takes a function of several arguments and breaks it down into one or more functions of fewer arguments. |
|
|
...And a slightly different take on the subject... |
|
|
|
|
Baker, Cooper, Fletcher, Miller, and Smith live on different floors of an apartment house that contains only five floors.
|
|
|
|
|
This is a famous `fractal fern' demo translated from QB45 code. The `magic numbers' in the code below have not been changed from the original. The plotting software of choice is gnuplot. |
|
|
|
|
The Fibonacci sequence is a special sequence of the integers, starting with 0 and 1, defined such that the next Fibonacci number is the sum of the previous two Fibonaci numbers. In this example we deonstrate several different Fibinacci functions. We use one of them to compute out to the 5000th, 10000th, and 15000th Fibonacci numbers. Wolfram Alpha was able to verify the first two of these. |
|
|
|
|
|
|
Turtle graphics refers to a particular method of plotting connected line segments (typically) in two dimensions. Imagine a `turtle' that can take only two kinds of instructions: (i) walk forward, or (ii) swivel left/right by some angle. As the turtle goes, he traces a line. In the following example we send the turle on a series of `circular' walks, each with the same starting place, but with slightly increasing starting orientation. The overall result resembles a flower. |
|
|
|
|
While concepts of `functional programming' are written all over this page, a particularly unique example has been prepared for this section. Suppose we have a high-order function whose output is not a variable or a string, but the declaration of a yet new function. That is, every time this `parent' function is called, a new `child' function is defined. Of course, functions in Sxript have private scope, which leads to a puzzle: how can variables defined in `parent' still survive in `child' after `parent' closes? In other words, we ask, how can a variable be passed backwards through a given scope layer? Sxript's way of dealing with this problem is illustrated in the code box below. |
|
|
|
|
Certain constructs from geometry - spheres, boxes, and the like - are much more cleanly contained as objects as opposed to a splay of functions. |
|
|
|
|
Evaluate the following sum: 1/2 + 1/4 + 1/8 + 1/16 + 1/32 + ... = ? |
|
|
|
|
The golden ratio can be computed in many ways. Here we choose one of the worst: deriving a formula basied on the definition, taking a crude guess, and then letting a high-precision Newton's method take over to refine the guess. The end result is the golden ratio to roughly 750 digits. |
|
|
|
|
Haskell imitation... |
|
|
|
|
Consider a cylindrical tank (or pipe) laying on its side, partially filled with liquid. What is the relationship between the liquid height and the volume occupation? (Solve this problem twice.) |
|
|
|
|
|
|
Janes Noodling invoice helper program. |
|
|
|
|
Koch line fractal. |
|
|
|
|
|
Here we use an Euler's forward method to compute the three-dimensional trajectory of a particle that is subject to a few made-up forces. |
|
|
|
|
|
Nodes. |
|
|
|
|
This sample code uses a generalized Newton's method to solve systems of nonlinear equations. The first set of code boxes utilizes the inverse Jacobian technique, which (for now) limits the analysis to two-dimensional systems. The second technique employs a sequence in steps linear alrebra to solve the system without needing to invert a matrix. |
|
|
|
|
|
|
This sample code converts numbers to letters. |
|
|
|
|
The Pascal triangle. |
|
|
|
|
The number pi=3.14159... can be expressed in terms of an infinite nesting of factors of sqrt(2). Proof left to the reader.
![]() |
|
|
|
|
Calculates the resistance (in Ohms) represented by a three-color sequence. The fourth color denotes the uncertainty as a percentage. |
|
|
|
|
The Sierpinski triangle is a fun fractal... |
|
|
|
|
Your classic recursive string permutation routine... |
|
|
|
|
A symplectic integrator is a numerical integration scheme used for solving physics problems. An improvement of any of Euler's methods, the symplectic integrator is a Hamiltonian-preserving scheme. In the following example we calculate the trajectory of a harmonic oscillator. |
|
|
|
|
Printing vectors with a fixed number of spaces between each element... |
|
|
|
|
A lazy trip (mostly) around the unit circle... |
|
|
|
|
Obligatory. |
|
|
|