fracasm

fracasm is an esoteric programming language based on a counter machine.

A fracasm program is a sequence of statements that end in semicolons. Comments start with # and continue to the end of the line. The first statement should be preceded by @start:.

Variables and values

Variables don't need to be (and can't be) declared. Variable names can contain letters, numbers, underscores, apostrophes, and periods, and they are case-sensitive. Numbers are valid variable names, since there is never any ambiguity whether the number is being used as a variable name or as a number. All variables contain non-negative integers, and there is no upper limit to the value that a variable can have. Unless otherwise specified, all variables start out with the value 0.

The two most basic statements are adding a number to a variable (syntax: variable + number ;, e.g., a+1;), and subtracting a number from a variable (syntax: variable - number ;). (Note that although these look like non-side-effecting expressions, they do actually change the value of the variable; there aren't any expressions, so there's no ambiguity.) If you're adding or subtracting 1, there's a shorter form: just put the + or - before the variable name; e.g., +a; means the same thing as a+1;.

If you subtract a value from the variable, and subtracting the value would result in a negative value, then the whole statement doesn't execute. For instance, if a has the value 2, then after the statement a-5;, a will still have the value 2, not -3 (since variables cannot have negative values) nor 0. You can put multiple variable modifications in a single statement to take advantage of this; for instance, the statement a-5 b+2; will only add 2 to b if a is at least 5. It does not matter which order the additions and subtractions are in the statement; if any subtraction anywhere fails, the whole statement, even parts before that subtraction, does not execute. You can change this behavior by putting a question mark after the subtraction; a-5? b+2; will always add 2 to b, regardless of whether 5 could be subtracted from a. Putting two question marks after a subtraction will also set the variable to 0 if it's too small for the subtraction to succeed normally.

If you want to test the value of a variable without changing it, you can use >=; a statement part like a>=2 is equivalent to a-2 a+2. (Note that this means that testing a variable twice in the same statement, or testing a variable and subtracting from it in the same statement, probably won't do what you expect; a>=2 a>=3 is actually equivalent to a>=5. Don't depend on this, though; it might change in future versions.) There are no other comparison operators.

ExampleMeaning
a+2;Add 2 to the variable a (the result is stored in a.
a-2;If a is at least 2, subtract 2 from the variable a; otherwise, do nothing.
a-2 b+2;If a is at least 2, subtract 2 from a and add 2 to b; otherwise, do nothing.
b+2 a-2;Same as above.
a-2? b+2;If a is at least 2, subtract 2 from a and add 2 to b; otherwise, just add 2 to b.
a-2?? b+2;If a is at least 2, subtract 2 from a and add 2 to b; otherwise, set a to 0 and add 2 to b
a>=2 b+2;If a is at least 2, add 2 to b; otherwise, do nothing
b+2 a>=2;Same as the previous statement.
a-2 b-2;If a and b are both at least 2, subtract 2 from both a and b; otherwise, do nothing.

Input and output

The main way to do input and output is using @in and @out directives. These have the syntax @in or @out space-separated list of variable names ; and can appear anywhere in the program (except in the middle of a statement). All variables in an @in directive will be read in when the program starts, and will have the value that was read instead of 0 at the beginning of the program. All variables in an @out directive will be displayed when the program finishes.

It's also possible to display output while the program is running, though this isn't guaranteed to work in all implementations of the language. At the end of a statement (before the semicolon) put !print tokens to print a constant message or !printvars space-separated variable names to print the value(s) of variable(s). (!printvars by itself will display the value of all variables.) tokens can include any token recognized, and can include double-quoted strings. In quoted strings, backslashes can be used to escape quotation marks and other backslashes.

In the current interpreter, these do not work if !print or !printvar is the first thing in the statement.

ExampleMeaning
@in a b c;At the beginning of the program, ask for three values. Put the values in the variables a, b, and c before the program starts.
@out a b;At the end of the program, display the values of variables a and b.
a+2 !print Hello world;Adds 2 to a and displays the message "Hello world".
a+2 !print "Hello world";Same
a+2 !print "Hello, \"world\"!";Same, except displays the message 'Hello, "world"!' (more punctuation).
a-2 !print "Hello world";If a is at least 2, subtract 2 from a and display the message "Hello world". Otherwise, do nothing and display nothing.
a>=1 !printvars;If a is at least 1, display all variables.
a>=1 !print "The value of a is" !printvars a;If a is at least 1, display the string "The value of a is" followed by the value of a.
a+0 !print "Hello world";Unconditionally print "Hello world" without doing anything else.

Program flow

By default, statements are executed in order. However, like most programming languages, there are ways to change this.

Alternatives

A statement can have multiple alternatives, separated by vertical bars (|). If one alternative fails (due to a subtraction that would make the variable negative, or due to a comparison that fails), the next alternative is tried. When an alternative succeeds, the rest of the alternatives in the statement are ignored and the next statement starts.

It's possible to put multiple alternatives inside parentheses; (a-1 | b-1) c+1, for instance, is equivalent to a-1 c+1 | b-1 c+1. If there are multiple parenthesized alternatives in a statement, they're checked in order. Note that the translation to the non-parenthesized version is actually done at compile-time, so use these sparingly. Question-marks are also translated to alternatives at compile-time.

ExampleTranslates toMeaning
a-1 b+1 | c+1If a is at least 1, subtract 1 from a and add 1 to b. Otherwise add 1 to c.
a>=1 b+1 | c+1If a is at least 1, add 1 to b. Otherwise add 1 to c.
(a-1 | b-1) c+1a-1 c+1 | b-1 c+1If a is at least 1, subtract 1 from a and add 1 to c. Otherwise, if b is at least 1, subtract 1 from b and add 1 to c. Otherwise do nothing.
(a-1 | b-1) (c-1 | d-1)a-1 c-1 | a-1 d-1 | b-1 c-1 | b-1 d-1If a is at least 1, subtract 1 from a; otherwise if b is at least 1, subtract 1 from b. If either a or b was at least 1, then if c is at least 1, subtract 1 from c; otherwise, if d is at least 1, subtract 1 from d.

Gotos

A statement can have a label. Anything that can be used as a variable name can also be used as a label name, though a label cannot have the same name as a variable (well, it can, but it won't do what you expect). Label a statement by putting the label followed by a colon before the statement.

To go to a labelled statement, use the syntax > label as part of a statement (this can be used anywhere addition and subtraction can be used). You can also use @repeat to repeat the current statement; if the statement is labelled, @repeat is the same as > the-statement's-label.

ExampleMeaning
loop: a+1;A statement labelled loop
>loop;Go to the label loop
a>=1 >loop;If a is at least 1, go to the label loop
a-1 @repeat;Subtracts 1 from a while a is at least 1 (this sets a to 0).
loop: a-1 >loop;Same (except now other code can go to the label).
a-1 b+1 @repeat;Subtracts 1 from a and adds 1 to b while b is at least 1. This has the effect of adding a to b (result stored in b) while setting a to 0.
a-2 @repeat;Sets a to a mod 2 (1 if a is odd, 0 if a is even).
a-2 b+1 @repeat;Adds a/2 to b (stores the result in b) and sets a to a mod 2.

With everything up to this point, the language is Turing-complete.

Copy loops

Sometimes you want to add a variable to another variable, or copy a variable into another variable, without setting the original variable to 0. It's possible to do this with two loops:

a-1 b+1 tmp+1 @repeat; # move a to both b and a temporary variable
tmp-1 a+1 @repeat; # restore a from the temporary variable

However, there's a shortcut for this particular set of loops: source-variable >> statement-parts. This will execute statement-parts source-variable times. If any of statement-parts fails, the result is undefined.

Copy loops can appear as part of a larger statement: other variable modifications can appear before the loop, and the loop can be inside an alternative. However, copy loops cannot appear inside parentheses, nor can multiple copy loops appear in the same alternative.

The source variable can be followed by / and a number to divide it by that number (rounding down).

ExampleMeaning
a >> b+1;Set b to a+b without changing a
a >= 5 a >> b+1 | a+1;If a is at least 5, set b to a+b without changing a; otherwise, add 1 to a.
a/2 >> b+1;Set b to a/2+b, rounding down.
a >> (b-1 | c+1)Do a times: if b is at least 1, then subtract 1 from b, otherwise add 1 to c.

Threads

fracasm has multi-threading, sort of. However:

A simple use of threads is to simulate subroutines: put the subroutine at the end of the program and start a thread at the beginning of the subroutine to call it.

To start a thread, use + label-to-start-at. (You can also start multiple threads at the same line using label-to-start-at + number-of-threads.) To end the current thread, use @end as a part of a statement.

If a statement ends in | @wait;, then if none of the alternatives succeed, then other threads execute and the thread waits until one of them does. If all threads are waiting or have ended, the program ends.

StatementMeaning
+a;Start a thread at the line labelled a.
a>=1 +b;If a is at least 1, start a thread at the line labelled b.
a>=1 @end;End the current thread if a is at least 1.
a-1 | @wait;If a is at least 1, subtract 1 from a. Otherwise, executes other threads until a is at least 1 and no higher-priority thread is executing; then it resumes this thread at subtracts 1 from a.

What threads actually are

You may have noticed that the syntax for starting threads is the same as the syntax for adding to a variable. That's because labels are variables; a label is a variable whose value is the number of threads currently at the label. (Unlabelled statements are given labels with names that can't be directly referenced by the program.) At each step of the program, the statement with the highest priority whose label has a positive value and that's not waiting executes, the label's value is decreased by 1, and unless the selected alternative has @end, the label of the next statement's value is increased by 1.

Also, >label is actually a shortcut for +label @end.

Other statement types

@always

If @always precedes a statement, then the statement will always execute if any of its alternatives succeed and there's no thread at a higher priority currently executing.

!unreachable

An alternative can include !unreachable or !error; if this alternative is executed, the behavior is undefined. !unreachable can be followed by an error message that the interpreter may optionally display.

@start

@start variable = value ; assigns a value to a variable (or starts some number of threads) before the program starts. If a variable appears both in a @start directive and an @in directive, the behavior is undefined.

@const

@const name = value ; defines a constant, which can be used anywhere a number can. Constant names can be anything that can be a variable name except for numbers. A constant can have the same name as a variable or label without causing a conflict.

!anything

Statements that start with ! are compiler-dependent. Unrecognized statement types starting with ! are ignored.

!trace

Put !trace; at the bottom of the program (or top if you're using @priority -;) to show all variables for each step of the program.

Translation to FRACTRAN

Perhaps you're wondering why I made some of the design decisions I did, or why I chose the name I did? Because it's intended to be an assembly language for FRACTRAN.

To translate to FRACTRAN, first replace anything that I showed an equivalence for (including making the labels be explicitly variables and turning copy loops into actual loops). Then for each alternative that both adds and subtracts from the same variable, change the additions to jumps to new high-priority statements that add to that variable (because FRACTRAN doesn't allow adding to and subtracting from the same variable in the same statement).

Each alternative becomes a fraction. Each variable is associated with a prime number. Each fraction's numerator is the product of the variables added in the statement, to the power of the number to add to the variable; and the fraction's denominator is the product of the variables subtracted from in the statement, to the power of the number to subtract from the variable. Within a statement, the fractions go in decreasing order; the fractions for higher-priority statements come first.

@start and @in directives specify the initial number, and @out directives indicate how to interpret the output.

The !prime directive can be used to specify which prime to associate with which variable.

Grammar of fracasm

In the following grammar, * indicates 0 or more of the previous thing, and ? indicates 0 or 1 of the previous thing.

programstatement*
statement@in identifier* ;
| @out identifier* ;
| !prime (identifier = constant)* ;
| @const (identifier = constant)*
| @priority (+ | -) ;
| @start (identifier = constant | identifier + constant) (; | :)
| !trace ;
| pragma tokens-other-than-; ;
| normal-statement
normal-statement(@always | label :)? alternative (| alternative)* ;
labelidentifier (& identifier)*
alternativepart* (identifier (/ constant)? >> part*)? alt-pragma*
| @wait alt-pragma*
partidentifier (+ | - | >=) constant
| (+ | - | >) identifier
| @end | @repeat
| ( part* (| part*)* )
alt-pragma!printvars identifier*
| (!print | !unreachable | !error | pragma) tokens-except-;-|-and-pragma
identifieranything matching [\w\d_'.]+
pragmaanything matching ![\w\d_'.]+
constantany identifier that's either all digits or has been assigned a value using @const
tokenidentifier | pragma
| anything matching "((?:[^"\\]|\\.)+)"
| anything matching @[\w\d_'.]+|>[>=]|[&:|+\->/;=()?]

Download

Interpreter written in Python (51KB, 228KiB when unzipped). Also includes FRACTRAN interpreter (each fraction should be on its own line) and a copy of pyprimes (0.2.2a) (because it's easier for me to just copy it than to actually install it correctly).