owoScript is a stack based, imperative, and Turing complete programming language. owoScript works with a simple and descriptive language that is then compiled to the best bytecode to ever exist on this planet.
Why is owoScript bytecode the best on the planet? Because it's entirely made out of OwO faces! Why have the speed and portability of the JVM when you can have the most adorable code out of almost any language you can write in? And never fear, your owoScript bytecode can easily be turned back into the higher level form at any time.
Additionally, owoScript leaves its syntax and parsing files easily accessible to be inspected and verified by anyone. owoScript's grammar is written in ANTLR4, a parsing tool so out of this world reliable and efficient that you'll wonder why your favorite programming language isn't using it yet.
See the /examples
directory for a hello world, truth machine, and a proof of turing completeness via
brainfuck equivalents (does your fancy OOP language prove that it is capable of universal computation?
Can you trust it if it doesn't?)
owoScript is written in a descriptive language consisting of stack operations, number literals,
while
loops, and if {} else {}
statements.
Whitespace is ignored.
owoScript is stack-based, with only integer types. For example,
the following literal will push 6
onto the stack:
literal 6;
and the following command will take that number off the stack and print it:
printnum;
operations take their values off the stack in the order they came onto it:
literal 8;
literal 2;
div;
corresponds to 8 / 2, not 2 / 8. However, in addition to the stack, an optional "hashmap",
mapping integer keys to integer values, is available through the store
and get
commands
Because owoScript is stack-based, arithmetic is performed in postfix notation.
This means that writing something like (2+3)*(5/7)
is written by taking the operator out of the middle of the expression and adding it on to the end.
So 2+3 is written as literal 2; literal 3; add;
and 5/7 is written as literal 5; literal 7; div;
The final expression would thus be written as:
literal 2;
literal 3;
add;
literal 5;
literal 7;
div;
mult;
Flow control is provided in while
loops, functions, and if {} else {}
statements
While loops simply repeat the operation inside them until the top of the stack is 0, or falsy. They do not pop values, and instead simply inspect the stack without changing it. While loops are skipped if the top of the stack is 0 when they are made.
Example:
// Truth machine
// When a truthy number is inputted, continue printing it forever
// Otherwise print once and stop
inputnum;
dupe;
printnum;
while {
dupe;
printnum;
}
Functions are defined at the top of the file and called like so, and can call other functions. Putting a function anywhere other than at the top of the file will result in very undefined behavior. Functions do not take argument, and simply operate off of the stack.
func square {
dupe;
mult;
}
literal 3;
square();
If statements have the syntax
if {
// statements or nop;
}
else {
// statements or nop;
}
and pop the top value of the stack. If it is truthy, the first block is executed. Otherwise, the second one is.
Commands are single word operations (except for number literals and bignumber literals, see below)
Keyword | Pops | Pushes |
---|---|---|
literal x | None | hex value of x, x must be single hex digit (0-9, a-f) |
number x | None | int value of x, x is signed number [*] |
add | a, b | a + b |
sub | a, b | a - b |
mult | a, b | a * b |
div | a, b | a // b (floor division) |
mod | a, b | a % b (modulus) |
exp | a, b | a ^ b (exponent) |
a | prints the character with code a | |
printnum | a | prints the number literally |
printstack | None | prints entire stack |
input | None | number of character read from stdin |
inputnum | None | decimal number read from stdin |
lt | a, b | 1 if a < b else 0 |
gt | a, b | 1 if a > b else 0 |
eq | a, b | 1 if a == b else 0 |
neq | a, b | 1 if a != b else 0 |
cmp | a, b | if a == b: 0, if a > b: 1, if a < b: -1 |
dupe | a | a, a (a repeated) |
dupedeep | a | extend stack with last a values on stack |
swap | a, b | b, a (top two values swapped) |
push | a, b | puts a b layers deep in the stack |
fetch | a | pulls the number a deep in the stack to the top |
stacklength | None | length of stack |
store | a, b | stores b in the a slot in the hashmap |
get | a | pushes the value stored in a slot in the hashmap |
stop | a | exits with return code a |
fetchdupe | a, b | same as fetch but doesn't remove number from inside stack |
pushdupe | a | same as push but doesn't remove number from top of stack |
nop | None | no operation, used for code clarity in if/else statements |
hexmult | a, b | a * 16 + b (hexadecimal digit appending) |
printhash | None | prints entire hashmap |
* these number literals will be less space efficient in OwO form, so if your number is representable by a single hex digit, that form is recommended.
The python script owo.py
is used for running owoScript bytecode or psuedocode, while the python
script owoc.py
is the compiler/decompiler used to transform code from pseudocode or bytecode.
The script bfowo.py
is used to convert brainfuck programs into owoscript. This conversion is moderately optimized,
but is still less efficient than really writing in owoscript would be.
A command line option can be used to specify whether you'd like the transpiler to "wrap" the brainfuck cells (like an 8-bit unsigned int) or whether you'd like python-style ints.
See sierpinski_bf.owop
and mandelbrot_bf.owop
for examples of these conversions.
owoScript is provably Turing complete, via a simple reduction to brainfuck
Brainfuck equivalents for OwOScript
Stack is used to hold current pointer while hashmap is used for cells
*/
// > Move pointer right
literal 1;
add;
// < Move pointer left
literal 1;
sub;
// + Increment cell
dupe;
dupe;
get;
literal 1;
add;
/*
Mod 256 wrapping if desired
literal 1;
literal 0;
hexmult;
literal 0;
hexmult;
mod;
*/
store;
// - Decrement cell
dupe;
dupe;
get;
literal 1;
sub;
/*
Mod 256 wrapping if desired
literal 1;
literal 0;
hexmult;
literal 0;
hexmult;
mod;
*/
store;
// . Output char
dupe;
get;
print;
// , Input char
dupe;
input;
store;
// [ ] Begin and end while loop
dupe;
get;
while {
discard;
//statements
dupe;
get;
}
discard;
"The only reason someone would do something like this if they could, which they can't, would be because they could, which they can't." - Rick Sanchez
I'm sure the question everyone is thinking is "why." Why make a programming language entirely in OwO faces? Why go out of my way to implement a formal grammar with a parser for a meme?
The simple answer, of course, is because I hate myself. The more accurate answer is because I wanted to try out writing a basic interpreter and grammar in ANTLR, in preparation for possibly writing an LLVM compiler for a few esoteric programming languages in the near future
I dedicate this project to all of my friends who answer the phone with "hewwo" so often I want to bash my head in.
This project is mostly a joke I embarked on as a fun way of learning parsing and the basics of imperative language design. Any ridiculous statements inside this project are purely satire.