^ go up - main page

the first programming language I ever made

my interest in programming pretty much began in late 2016, at age ~12, when someone from my school spent some time teaching me a bit about Scratch. well, technically that wasn't my first ever experience with programming: I had messed a little with Scratch before, but I struggled to not lose my interest before I figured out how to do absolutely anything with it. and even before that, I had played quite a bit with a website called LiveCodeLab; I remember I would make thousands of overlapping rotating low-poly spheres and pretend it was a big spinning planet with lots of waves on it.

but anyway, it was only after that one person taught me Scratch that I developed a long-term obsession with programming and computer languages. I kept making lots of things with Scratch, started learning HTML and bits of Python, JavaScript, and Lua (for ComputerCraft), discovered esoteric programming languages such as brainfuck... and at some point during all that, I decided I should try making my very own programming language.

the programming language

the language was named SCCL, "Scratch Console Command Language". obviously, this language was implemented in Scratch. it was first published on March 22 2017, but apparently I privated it some time later, after presumably succumbing to a fear of people mocking me for making such a hideous abomination that hardly works. it has stayed privated ever since... until now! you can see it for yourself here.

here is the Hello World program in SCCL:

set_1_Hello world!_print_1

the first thing you'll notice is that all of the "terms" (per my own nomenclature from the time) are separated by underscores, instead of spaces. this is because I wanted strings to be able to contain spaces, and this was the least effort way I could think to achieve this. of course, this means that now you can't put underscores in strings, but hopefully you'll never have to do that. in hindsight it feels like it wouldn't have been that hard to implement a thing where arguments containing spaces must be enclosed with quotation marks (à la vurl, a language of almost-comparable rudimentarity). but at the time, figuring out how to split a string into a list of terms separated by a given character was just about the most challenging problem I had ever independently solved while programming, and I suppose I didn't want to complicate that any further.

another thing you may notice is that the Hello World program isn't just print_Hello world!: you have to store the string in some numeric address and then print the value stored at that address. this is because SCCL was inspired by DDNC (DUO Decimal Numeric Code), the programming language created for Jack Eisenmann's DUO Decimal homebrew computer. back then I was very fixated on this computer and spent a lot of time messing with the emulator and writing programs in this charmingly rudimentary language. although, the one thing I never managed to figure out at the time was what "indirect addresses" (in other words, pointers) were supposed to do.

in DDNC, everything is numbers: every line of code is a numeric command followed by numeric arguments. there is just one command which lets you store a constant value into a variable, and then literally every other command exclusively takes in variable addresses as arguments. I suppose it's a little easier to parse (and makes the syntax of this number-only language less cumbersome) when you don't have to figure out whether each argument is supposed to be a constant value or a variable address, and when you don't have nested commands or expressions of any sort.

SCCL is similar, even down to the numeric variable addresses since Scratch only has lists and not dictionaries. although, it's not very hard to naively implement dictionaries by storing the keys in a separate list and linearly searching through that, but I guess I didn't figure that out either. or maybe I did, but decided I was fine with numbered variables and didn't bother. the main differences from DDNC are the human-readable command names (since we're not running on a 7-segment display anymore, are we), and the ability to store strings in variables, which comes for free since Scratch is a magical language where strings and numbers are virtually the same thing and you don't have to worry about allocating the memory to store those strings.

also, the entire program is on one line because Scratch's text input functionality only lets you type a single line. and don't try to write a program containing newlines and then paste it into the interpreter: Scratch will convert those into spaces, which will trip up the parser.

the perils of nesting

SCCL also allegedly has control flow mechanisms, namely if and while blocks terminated by an end or wend command respectively. for instance, this program asks you if 2 + 2 = 4, and tells you if you're right or wrong (and also doesn't handle the edge case of the user typing something that isn't "true" or "false"):

set_1_Does 2 + 2 = 4? (true/false)_prompt_2_1_if_2_set_1_Right!_print_1_end_not_2_2_if_2_set_1_Wrong!_print_1_end

...and if we may pretend for a moment that this language was more readable:

set 1 "Does 2 + 2 = 4? (true/false)"
prompt 2 1
if 2
    set 1 "Right!"
    print 1
end
not 2 2
if 2
    set 1 "Wrong!"
    print 1
end

I implemented these like so: if an if or while command's condition is false, it skips forwards through the code until it finds an end or wend term respectively, then jumps to immediately after it. when a wend command is encountered, it searches backwards through the code until it finds a while term, and jumps to it. there are at least a couple of flaws with this implementation. for one, it simply searches for any occurence at all of the name of the command it's looking for, regardless of whether it's actually a command. so your code may break if you just so happen to have the strings "end", "wend", or "while" as an argument.

the other flaw, which is the one I actually eventually realized at the time but didn't know how to solve, was that nested if or while statements do not work at all. because of course, those nested blocks will have their own end/wend commands, which the outer if/while block containing them will need to somehow know to ignore and not interpret as marking the end of its own block. funnily enough, about a month earlier I also wrote a brainfuck interpreter in Scratch which has the exact same flaw. I didn't realize that you didn't just have to skip to the next or previous bracket, you had to skip to the matching bracket.

nowadays I know the simple solution to solving this: when skipping forwards past a code block, keep a counter which stores how many levels of nested code blocks you're currently in (initialized to 1). increment it when a new block begins (when you see an if/while command), decrement it when a block ends (when you see an end/wend command). when the counter reaches 0, that's when you're finished skipping. the idea is similar when skipping backwards, except end/wend increments the counter and if/while decrements it. actually, I even know of a little trick to optimize while loops: have the while command push the current position in the code to a "loop stack" when entering its block, then have wend pop from the loop stack and jump to that location, which is faster than needlessly searching backwards for that while statement you've already seen before. but, I'm getting a bit ahead of myself.

miscellaneous funny aspects

the main mechanism for user input is the prompt command, which requests a string from the user via Scratch's "ask (X) and wait" block. however, apparently there are commands for waiting for a keypress and storing which key was pressed (key), and reading whether a given key is currenty being pressed... except, these commands only support the arrow keys and the spacebar. and reading the state of one of these keys is split across five separate commands (readup, readdown, readleft, readright, readspace). I should have just made a single readkey command which takes an argument, which would also be consistent with how the time command works. but anyway, Scratch makes it so you sort of have to copy and paste code for every single key individually to get something like this to work, and I didn't really feel like doing that, so I figured those 5 keys would be good enough. although, apparently Scratch does let you shove variables into the "is (key) down?" block, which would at least make a readkey command easier to implement, but I don't remember if this was a thing back in Scratch 2.0.

there are no equivalents for the DDNC commands involving "indirect addresses" (pointers) and lists, because as aforementioned, I couldn't figure out what "indirect addresses" were at the time. although, they technically aren't necessary, since you could theoretically use strings to store lists of values in some way, while accursedly accessing and mutating it with string operators like letter and join. but I never ended up writing any SCCL programs that needed to use lists anyways.

there is, however, the command sin. no, I didn't know what the sine function was at the time. I only included it because DDNC included it.

commands

X, Y, and Z represent numeric variable addresses. C represents a constant value.

variable addresses start at 1. in the original implementation, the maximum variable address is 87 (of all numbers).

following Scratch behavior, the strings "true" and "false" are used as boolean values.

command description
set_X_C store C into the variable X.
copy_X_Y copy the value of X into Y.
print_X print the value of X.
add_X_Y_Z set X to Y + Z.
sub_X_Y_Z set X to Y - Z.
mul_X_Y_Z set X to Y * Z.
div_X_Y_Z set X to Y / Z.
mod_X_Y_Z set X to Y % Z.
incr_X increment X by one.
decr_X decrement X by one.
round_X_Y set X to Y rounded to the nearest integer.
floor_X_Y set X to Y rounded down to the nearest integer.
ceiling_X_Y set X to Y rounded up to the nearest integer.
random_X_Y_Z set X a random number between Y and Z.
sin_X_Y set X to the sine of Y degrees.
equal_X_Y_Z set X to "true" if Y equals Z, "false" otherwise.
greater_X_Y_Z set X to "true" if Y is greater than Z, "false" otherwise.
following Scratch behavior, this command may also compare strings based on lexicographical order.
less_X_Y_Z set X to "true" if Y is less than Z, "false" otherwise.
following Scratch behavior, this command may also compare strings based on lexicographical order.
not_X_Y set X to "true" if Y does not equal "true", "false" otherwise.
or_X_Y_Z set X to "true" if either Y or Z equal "true", "false" otherwise.
and_X_Y_Z set X to "true" if both Y and Z equal "true", "false" otherwise.
join_X_Y_Z set X to the contatenation of Y and Z.
letter_X_Y_Z set X to the Yth character of Z (starting from 1).
length_X_Y set X to the number of characters in Y.
if_X begin a block of code that will execute if X is any value other than "false".
end end an if block.
while_X begin a block of code that will repeatedly execute until X is "false".
wend end a while block.
break immediately exit a while block.
time_X_Y set X to the component of the current time given by Y.
possible values for Y are: "second", "minute", "hour", "day" (of the week), "date" (of the month), "month", "year".
wait_X wait for X seconds.
readup_X set X to "true" if the up arrow key is pressed, "false" otherwise.
readdown_X set X to "true" if the down arrow key is pressed, "false" otherwise.
readleft_X set X to "true" if the left arrow key is pressed, "false" otherwise.
readright_X set X to "true" if the right arrow key is pressed, "false" otherwise.
readspace_X set X to "true" if the space key is pressed, "false" otherwise.
key_X wait for a key press, then store the key code into X.
key codes are "u" for up, "d" for down, "l" for left, "r" for right, "s" for space.
prompt_X_Y print Y to the console, then prompt the user for text input and store the result into X.
username_X store the user's Scratch username into X (or an empty string if the user is not logged in).
clear clear the console.

conclusion

this post has been sitting around unfinished for well over a month because I couldn't think of a way to shoehorn some sort of conclusion into this post. now I have realized that I don't really need to do that. so anyways that's it for this blog post, if you liked it make sure t