Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Getting Started

I see you’ve decided to become even sillier. BIG mistake.

1) Download SillyScript

You can download SillyScript once I create SillyScript.

2) Create some SillyScript code

Save the following code into a file named list.silly:

1;
"This is a string";
false;

3) Convert your script to data

Finally, you can run the SillyScript executable to convert the script into a data format.

silly <script file path> <output file path>

So like this:

silly list.silly output.json

Syntax

The fundamentals of this language make it seem so useless and… silly. Just bear with it, it will make sense eventually.

Top-Level Syntax

All SillyScript code represents data. It can represent a list or dictionary.

While SillyScript can export to multiple data formats, JSON is the main one used in this documentation to represent how the structure of the language works.

List

The contents on the left side is an entirely valid SillyScript file.

SillyScript JSON

This is an entire valid SillyScript file:

1;
"This is a string";
false;

It is the equivalent of this JSON:

[1, "This is a string", false]

Dictionary

SillyScript JSON

This is ALSO an entire valid SillyScript file:

my_number: 1;
MyString: "This is a string";
myBool: false;

It is the equivalent of this JSON:

{
	"my_number": 1,
	"MyString": "This is a string",
	"myBool": false
}

Combination

SillyScript will automatically figure out if the file should be a List type of Dictionary type. These two types cannot be combined.

This is NOT a valid SillyScript file. The string must be labeled, or the labels must be removed from the number and boolean.

my_number: 1;
"This is a string";
myBool: false;

Lists and dictionaries can be mixed by adding them as sub-elements to each other using the proper syntax.

List

Use the blank colon syntax to create a new scope for a list.

:
	1;
	2;
	3;

Dictionary

Use the blank colon syntax to create a new scope for a dictionary.

:
	key1: 1;
	something: 2;
	bla: 3;

Combination

Here is an example of combining list and dictionaries and its equivalent in JSON:

SillyScript JSON

A complicated structure in SillyScript.

myList:
	1;
	"My String";
	:
		3;
		2;
		1;
	:
		thisIsDict: 321;
myString: "Another String";

What it represents in JSON.

{
	"myList": [
		1,
		"My String",
		[3, 2, 1],
		{ "thisIsDict": 321 }
	],
	"myString": "Another String"
}

Definitions

Definitions can be used to simplify common patterns.

Basic Syntax

A definition can be used to type specific data patterns.

def Wait(duration: int) -> dict:
	type: 0;
	duration: duration;

Calling this definition places the resulting data where the call occurs.

SillyScript JSON

The SillyScript definition call:

def Wait(duration: int) -> dict:
	type: 0;
	duration: duration;
 
Wait(123);

The equivalent JSON:

[
	{
		"type": 0,
		"duration": 123
	}
]

Arguments

Definition arguments are provided as a list of NAME: TYPE combinations. See Types for more information on types.

Named Arguments

Arguments can also be passed by name. This allows only certain arguments to be used.

For example:

SillyScript JSON

The SillyScript definition call:

def Jump(
	height: float = 5.0,
	duration: int = 60
) -> dict:
	type: 1;
	height: height;
	duration: duration;
 
Jump(duration: 123);

The equivalent JSON:

[
	{
		"type": 1,
		"height": 5.0,
		"duration": 123
	}
]

Enums

Enums are collections of identifiers that can be used as types.

Basic Syntax

An enum can be declared using the enum keyword:

enum BreakfastFood:
	pancake;
	hash_brown;
	burger;

By default, an enum identifier will generate as its index.

SillyScript JSON

The SillyScript definition call:

hash_brown;

The equivalent JSON:

[
	1
]

Underlying Type

An enum can generate as other types. Simple add an -> TYPE after the enum name to dictate what type it’s converted to.

Currently only string and int are supported.

SillyScript JSON

The SillyScript definition call:

enum BreakfastFood -> string:
	pancake;
	hash_brown;
	burger;
 
burger;

The equivalent JSON:

[
	"burger"
]

Argument

An enum’s name can be used as an argument.

SillyScript JSON

The SillyScript definition call:

enum BreakfastFood -> string:
	pancake;
	hash_brown;
	burger;
 
def Eat(food: BreakfastFood) -> dict:
	action: "eat";
	food: food;
 
Eat(pancake);
Eat(burger);

The equivalent JSON:

[
	{
		"action": "eat",
		"food": "pancake"
	},
	{
		"action": "eat",
		"food": "burger"
	}
]

Types

SillyScript is built on a handful of core-types.

Booleans

The bool type can only store two options: true or false.

SillyScript JSON

SillyScript code:

true;
false;

The equivalent JSON:

[
	true,
	false
]

Numbers

There are two number types: int and float. floats can have numbers with decimal places, ints must be integers.

SillyScript JSON

SillyScript code:

1;
0.5;

The equivalent JSON:

[
	1,
	0.5
]

Strings

The string type is a string of characters. At the current moment you can only use double-quotes for strings.

SillyScript JSON

SillyScript code:

"Hello world";

The equivalent JSON:

[
	"Hello world"
]

Null

Types can be assigned null to store nothing. When passing null to a typed entry, the type must be “nullable”. A type can be made “nullable” by adding a question mark ? to the end of it.

Non-nullable types can be passed the nullable ones, but the opposite is not true.

SillyScript JSON

SillyScript code:

null;
 
def MaybeGiveInt(a: int?) -> list:
	a;
 
MaybeGiveInt(12);
MaybeGiveInt(null);

The equivalent JSON:

[
	null,
	[ 12 ],
	[ null ]
]

List

A list of data entries. A mixed list is denoted with just list.

A list that only contains a single type can be denoted using TYPE list (for example: int list).

SillyScript JSON

SillyScript code:

def OneTwoThree() -> int list:
	1; 2; 3;
 
def TakeList(list: int list) -> dict:
	data: list;
 
TakeList(OneTwoThree());

The equivalent JSON:

[
	{
		"data": [1, 2, 3]
	}
]

Dictionary

An unordered list of data entries with a unique identifier for each entry. A mixed dict is denoted with just dict.

A dictionary that only contains a single type can be denoted using TYPE dict. For example: string dict.

Roles

A “role” can be given to a type as a piece of metadata. This is done using an exclamation point !.

def Something(input: dict!funny) -> dict!sad:
	count: input.number

A type with a role can only be passed to another type with the same role identifier.

# Error: dict!sad cannot be passed to dict!funny.
Something(Something({ number: 32 }));

Custom Syntax

SillyScript is about being silly; what’s more silly than programming your programming language in your programming language? How certain statements should be generated for each project may be different, so it’s up to the user to describe which statements are supported and how they are generated.

The syntax keyword can be used to create custom syntax. The structure of a custom syntax declaration looks like this:

syntax NAME:
	pattern:
		PATTERN
Input Description
NAME A globally unique name for your syntax declaration.
PATTERN The syntax template this declaration adds.

Here is an example of a very basic custom syntax.

SillyScript JSON

The SillyScript custom syntax:

syntax WobblyLine:
	pattern:
		~~~
 
~~~;

The equivalent JSON:

[
	{ }
]

Expression Inputs

The above syntax doesn’t take any expression inputs, so it simply generates an empty object.

To accept expressions within the custom syntax, an argument surrounded by triangle brackets may be used:

SillyScript JSON

The SillyScript custom syntax:

syntax Shout:
	pattern:
		<words: string>!!!
 
"Hello"!!!;

The equivalent JSON:

[
	{ "words": "Hello" }
]

Multiple Patterns

A single custom-syntax declaration may contain multiple patterns. Each pattern may have different expression inputs, but any expression inputs with the same names MUST also have the same types.

SillyScript JSON

The SillyScript custom syntax:

syntax Action:
	pattern:
		<name: string> ate <count: int>;
	
	pattern:
		<name: string> ate just one.
 
Horse ate 3;
Horse ate just one;

The equivalent JSON:

[
	{ "name": "Horse", "count": 3 },
	{ "name": "Horse", "count": null }
]

If Example

Let’s try a more complicated example: an if statement.

syntax If:
	pattern -> dict!action:
		if <condition: dict!condition>:
			<contents: dict!action list>

This could be used in this SillyScript like so:

SillyScript JSON
def Has(name: string, value: int) -> dict!condition:
	name: name;
	value: value;
	
def DoThing(action_name: string) -> dict!action:
	action: action_name;
	
DoThing("Hop");
if Has("something", 123):
	DoThing("Skip");
	DoThing("Jump");
[
	{ "action": "Hop" },
	{
		"type": 2,
		"condition": {
			"name": "something",
			"value": 123
		},
		"contents": [
			{ "action": "Skip" },
			{ "action": "Jump" }
		]
	}
]

Pattern Type

To specify the type a pattern returns using -> TYPE. This can be helpful for assigning roles to dicts returned by patterns.

This can be used to simulate custom operators:

syntax NumEquality:
	pattern -> dict!condition:
		<left_number: dict!num> == <right_number: dict!num>

Borrowing from the if syntax code above, one can do:

SillyScript JSON
def Number(num: int) -> dict!num:
	type: 0;
	num: num;
 
def GetJumpCount() -> dict!num:
	type: 1;
 
if GetJumpCount() == Number(3):
	DoThing("TripleJump");
[
	{
		"type": 2,
		"condition": {
			"left_number": {
				"type": 1
			},
			"right_number": {
				"type": 0,
				"num": 3
			}
		},
		"contents": [
			{ "action": "TripleJump" }
		]
	}
]

Else Example

Let’s say we want to allow for an “else” case in our “if” syntax. Let’s create a new syntax declaration. We need to add internal: true so this syntax isn’t available for use with normal code.

syntax Else:
	pattern:
		else:
			<contents: dict!action list>

Now we can add an optional “else” case in our “if” syntax. The ? after the “else_contents” identifier means this syntax is optional. The “if” still works even if there isn’t an “else”.

syntax If:
	pattern:
		if <condition: dict!condition>:
			<contents: dict!action list>
		<else_contents?: syntax!Else>