Compare commits

...

7 Commits

Author SHA1 Message Date
163f85b896 Fix a typo 2025-08-11 13:29:10 +10:00
09033cd432 More stuff 2025-08-11 10:07:05 +10:00
566d3aa0fb updates 2025-08-11 08:57:45 +10:00
f8397e85d4 Labels 2025-08-10 16:08:56 +10:00
3f2482d7ea Labels 2025-08-10 15:42:52 +10:00
2e388c6e68 Start work on lists 2025-08-10 13:31:28 +10:00
f43f79b869 Bugfix: no longer treats floats as ints 2025-08-09 19:52:49 +10:00
8 changed files with 666 additions and 21 deletions

1
.gitignore vendored
View File

@@ -1 +1,2 @@
ground
Bobfile

View File

@@ -1,5 +1,5 @@
compiler "g++";
binary "ground";
source "src/main.cpp";
flag "O3";
flag "Ofast";
compile;

View File

@@ -16,7 +16,7 @@ Ground is an interpreter which processes and interprets Ground instructions. It
Clone the repo and compile with your favourite C++ compiler:
```
g++ src/main.cpp -std=C++17 -O3 -o ground
g++ src/main.cpp -std=c++17 -O3 -o ground
```
(You can omit the -std flag on systems which default to the latest standard, and the -O3 flag if you're fine with a slightly slower interpreter.)

View File

@@ -1,6 +1,6 @@
## Ground Syntax Guide
### General syntax
## General syntax
Ground uses simple instructions and arguments to run code.
@@ -32,12 +32,32 @@ Reference a line (a line reference) with a percent symbol before a line number:
jump %10
```
### Keywords
Alternatively, set a label:
```
@myLabel # The '@' symbol denotes setting a label
```
and jump to that (setting labels will be discussed below):
```
jump %myLabel
```
Reference a list (a list reference) with an asterisk:
```
setlist *myList $value1 $value2 # and so on
```
## Keywords
Note: &var can be replaced with any direct reference. $value can be replaced with a literal value or a value reference. %1 can be replaced with a line reference.
Note: In most of these functions, if a direct reference is used, the value outputted by that function will be avaliable at that variable. Any existing value inside that variable will be overwritten.
### Control Flow
#### if
Make a decision based on a boolean. If the boolean is true, jumps to the line referenced.
@@ -54,7 +74,9 @@ Usage: `jump %1`
Ends the program. Requires an integer for a status code.
Usage: `end $value`
Usage: `end $intvalue`
### I/O
#### stdin
@@ -74,12 +96,60 @@ Allows output to the console, appending a new line at the end.
Usage: `stdlnout $value`
### Variables and Lists
#### set
Allows you to set a variable to a value.
Usage: `set &var $value`
#### setlist
Allows you to initialize a list.
Usage: `setlist *list $value1 $value2 $value3...`
#### setlistat
Sets a list item at an index. The item at the index must already exist. Lists are index 0.
Usage: `setlistat *list $intvalue $value`
#### getlistat
Gets a list item at an index, and puts it in the variable provided. The item at the index must already exist. Lists are index 0.
Usage: `getlistat *list $intvalue &var`
#### getlistsize
Gets the size of a list and puts it in the variable provided.
Usage: `getlistsize *list &var`
#### listappend
Appends an item to a list.
Usage: `listappend *list $var`
### String Operations
#### getstrsize
Gets the size of a string and puts it in the variable provided.
Usage: `getstrsize $stringvalue &var`
#### getstrcharat
Gets a character at a certain position in a string and saves it to a variable.
Usage: `getstrcharat $stringvalue $intvalue &var`
### Maths
#### add
Adds two numbers. Numbers mean an integer or a double. Outputs to a direct reference.
@@ -104,6 +174,8 @@ Divides two numbers. Numbers mean an integer or a double. Outputs to a direct re
Usage: `divide $value $value &var`
### Comparisons
#### equal
Checks if two values are equal. Outputs a boolean to a direct reference.
@@ -127,3 +199,73 @@ Usage: `greater $value $value &var`
Checks if the left value is lesser than the right value. Outputs a boolean to a direct reference.
Usage: `lesser $value $value &var`
### Type Conversions (ALL WORK IN PROGRESS)
#### stoi
Converts a string to an integer. Throws an error if the string cannot be turned into an integer.
Usage: `stoi $stringvalue &var`
#### stod
Converts a string to a double. Throws an error if the string cannot be turned into a double.
Usage: `stod $stringvalue &var`
#### tostring
Converts any type to a string.
Usage: `tostring $value &var`
### Functions and function specific features (ALL WORK IN PROGRESS)
Some symbols specific to this category:
* `!function`: A function reference
* `-type`: A type reference. Can be one of the following: "-string", "-char", "-int", "-double", "-bool"
#### fun
Defines a function. All code between `fun` and `endfun` will be included in the function.
Usage: `fun !functionname -type &var -type &var -type &var # and so on...`
#### endfun
Ends a function definition. When a function reaches the end the argument list will be cleared.
Usage: `endfun`
#### pusharg
Adds a value to the argument list which will be passed to the function when it is called.
Usage: `pusharg $value`
#### call
Calls a function, with all the arguments in the argument list. The return value will be put in the specified variable.
Usage: `call !function &var
### Interacting with Libraries (ALL WORK IN PROGRESS)
#### use
Attempts to import another Ground program. Gets inserted wherever the use statement is. Any code (including code outside function declarations) will be executed.
Note: Ground will check the directory where the program is stored when trying to find imported programs. If that fails, it will check the directory set in the $GROUND_PATH environment variable set by your system. The '.grnd' extension is appended automatically.
Usage: `use $stringvalue`
#### extern
Attempts to import a shared object library written for Ground. All functions in the external library will be usable with `call`.
Note: Ground will check the directory where the program is stored when trying to find external programs. If that fails, it will check the directory set in the $GROUND_PATH environment variable set by your system. The '.so', '.dll', etc extension is appended automatically.
Usage: `extern $stringvalue`

View File

@@ -20,8 +20,8 @@
With the licence out of the way, let's begin!
Ground is a programming language which takes some inspiration from
Assembly in it's design, but has higher level features (more types,
simpler IO, easier variables, etc) which make it easy to use.
Assembly in its design, but has higher level features (more types,
simpler IO, easier variables, etc) which makes it easy to use.
Ground works even better if you write a programming language that
compiles to Ground code. Ground is designed to have a similar
@@ -49,7 +49,15 @@ using namespace std;
function, interpreter function, Instruction struct
*/
enum class Instructions {
Jump, Stdout, Stdin, Stdlnout, Add, Subtract, Multiply, Divide, Equal, Inequal, Greater, Lesser, If, End, Set, Empty
Jump, If,
Stdout, Stdin, Stdlnout,
Add, Subtract, Multiply, Divide,
Equal, Inequal, Greater, Lesser,
End, Set, Empty,
Setlist, Getlistat, Setlistat, Getlistsize, Listappend, Listprepend,
Getstrcharat, Getstrsize,
Stoi, Stod, tostring,
Fun, Endfun, Pusharg, Call
};
/*
@@ -67,7 +75,7 @@ enum class Instructions {
See also parser function
*/
enum class Types {
Int, Double, String, Char, Bool, Value, Direct, Line
Int, Double, String, Char, Bool, Value, Direct, Line, ListRef, Label
};
/*
@@ -86,12 +94,67 @@ struct Literal {
variant<int, double, bool, string, char> val;
};
/*
List struct
Contains literal values inside a vector. For example, if the following
program was written:
setlist #myNums 3 5 9 13
The List struct which would be created and stored should look like this:
{
val = {
Literal {
val = 3
},
Literal {
val = 5
},
Literal {
val = 9
},
Literal {
val = 13
},
}
}
All elements in the list must be of the same type. See also Literal struct.
*/
struct List {
vector<Literal> val;
};
/*
ListRef struct
Contains the name of a list referenced by the program. For example, if the
following program was written:
setlist #myNums 3 5 9 13
The ListRef struct should look like this:
{
listName = "myNums";
}
*/
struct ListRef {
string listName;
};
/*
variables map
Contains all variables made while running the program. See also Literal struct.
*/
map<string, Literal> variables;
/*
lists map
Contains all lists made while running the program. See also List struct.
*/
map<string, List> lists;
/*
labels map
Contains all labels made in the program, for ease of jumping around the code.
*/
map<string, int> labels;
/*
ValueRef struct
If the program being executed makes a value reference, it is stored in a ValueRef
@@ -132,6 +195,8 @@ struct Direct {
*/
struct Line {
int lineNum;
bool isLabel = false;
string label;
};
/*
@@ -161,7 +226,7 @@ struct Line {
*/
struct Instruction {
Instructions inst = Instructions::Empty;
vector<variant<Literal, ValueRef, Direct, Line>> args;
vector<variant<Literal, ValueRef, ListRef, Direct, Line>> args;
};
/*
@@ -182,6 +247,7 @@ void error(string in, int exitc = 1) {
bool isInt(string in) {
try {
stoi(in);
if (stod(in) != stoi(in)) return false;
return true;
} catch (...) {
return false;
@@ -223,7 +289,17 @@ bool isDirect(string in) {
}
bool isLine(string in) {
if (in.size() >= 1 && in[0] == '%' && isInt(in.substr(1))) return true;
if (in.size() >= 1 && in[0] == '%') return true;
else return false;
}
bool isLabel(string in) {
if (in.size() >= 1 && in[0] == '@') return true;
else return false;
}
bool isListRef(string in) {
if (in.size() >= 1 && in[0] == '*') return true;
else return false;
}
@@ -241,6 +317,8 @@ Types getType(string in) {
if (isValue(in)) return Types::Value;
if (isDirect(in)) return Types::Direct;
if (isLine(in)) return Types::Line;
if (isLabel(in)) return Types::Label;
if (isListRef(in)) return Types::ListRef;
error("Could not determine type of \"" + in + "\"");
return Types::Int;
}
@@ -254,7 +332,7 @@ Types getType(string in) {
void exec(vector<Instruction> in) {
for (int i = 0; i < in.size(); i++) {
Instruction l = in[i];
// Pre process value references
// Pre process value references and labels
for (int j = 0; j < l.args.size(); j++) {
if (holds_alternative<ValueRef>(l.args[j])) {
if (variables.find(get<ValueRef>(l.args[j]).varName) != variables.end()) {
@@ -262,6 +340,17 @@ void exec(vector<Instruction> in) {
} else {
error("Could not find variable " + get<ValueRef>(l.args[j]).varName);
}
} else if (holds_alternative<Line>(l.args[j])) {
Line ln = get<Line>(l.args[j]);
if (ln.isLabel) {
if (labels.find(ln.label) != labels.end()) {
Line newLine;
newLine.lineNum = labels[ln.label];
l.args[j] = newLine;
} else {
error("Could not find label " + ln.label);
}
}
}
}
switch (l.inst) {
@@ -367,6 +456,278 @@ void exec(vector<Instruction> in) {
variables[varName] = varContents;
}
break;
/*
setlist instruction
This instruction takes a potentially infinite amount of arguments and
saves them to a list (vector) with name listName.
*/
case Instructions::Setlist:
if (l.args.size() < 2) {
error("Could not find all arguments required for Setlist inbuilt");
}
{
string listName;
List listContents;
if (holds_alternative<ListRef>(l.args[0])) {
listName = get<ListRef>(l.args[0]).listName;
} else {
error("First argument of setlist must be a list reference");
}
bool first = true;
for (variant<Literal, ValueRef, ListRef, Direct, Line> k : l.args) {
if (holds_alternative<Literal>(k)) {
listContents.val.push_back(get<Literal>(k));
} else {
if (!first) error("All arguments after first in setlist must be values");
first = false;
}
}
lists[listName] = listContents;
}
break;
/*
getlistat instruction
This instruction gets a list item from a list and an index and saves the
value to a variable.
*/
case Instructions::Getlistat:
if (l.args.size() < 3) {
error("Could not find all arguments required for Getlistat inbuilt");
}
{
ListRef listref;
int ref;
Direct var;
if (holds_alternative<ListRef>(l.args[0])) {
listref = get<ListRef>(l.args[0]);
} else {
error("First argument of getlistat must be a list reference");
}
if (holds_alternative<Literal>(l.args[1])) {
if (holds_alternative<int>(get<Literal>(l.args[1]).val)) {
ref = get<int>(get<Literal>(l.args[1]).val);
} else {
error("Second argument of getlistat must be an integer literal");
}
} else {
error("Second argument of getlistat must be an integer literal");
}
if (holds_alternative<Direct>(l.args[2])) {
var = get<Direct>(l.args[2]);
} else {
error("Third argument of getlistat must be a direct reference");
}
if (lists.find(listref.listName) != lists.end()) {
if (lists[listref.listName].val.size() > ref) {
variables[var.varName] = lists[listref.listName].val[ref];
} else {
error("Index " + to_string(ref) + " out of range of list " + listref.listName);
}
} else {
error("Unknown list: " + listref.listName);
}
}
break;
/*
getstrcharat instruction
This instruction gets a character from a string at an index and saves it
to a variable.
*/
case Instructions::Getstrcharat:
if (l.args.size() < 3) {
error("Could not find all arguments required for Getstrcharat inbuilt");
}
{
string instr;
int ref;
Direct var;
if (holds_alternative<Literal>(l.args[0])) {
if (holds_alternative<string>(get<Literal>(l.args[0]).val)) {
instr = get<string>(get<Literal>(l.args[0]).val);
} else {
error("First argument of getlistat must be a string literal");
}
} else {
error("First argument of getlistat must be a string literal");
}
if (holds_alternative<Literal>(l.args[1])) {
if (holds_alternative<int>(get<Literal>(l.args[1]).val)) {
ref = get<int>(get<Literal>(l.args[1]).val);
} else {
error("Second argument of getlistat must be an integer literal");
}
} else {
error("Second argument of getlistat must be an integer literal");
}
if (holds_alternative<Direct>(l.args[2])) {
var = get<Direct>(l.args[2]);
} else {
error("Third argument of getlistat must be a direct reference");
}
if (instr.size() > ref) {
Literal newLit;
newLit.val = instr[ref];
variables[var.varName] = newLit;
} else {
error("Index " + to_string(ref) + " out of range of string " + instr);
}
}
break;
/*
setlistat instruction
This instruction sets an item in a list to be a certain value.
*/
case Instructions::Setlistat:
if (l.args.size() < 3) {
error("Could not find all arguments required for Setlistat inbuilt");
}
{
ListRef listref;
int ref;
Literal value;
if (holds_alternative<ListRef>(l.args[0])) {
listref = get<ListRef>(l.args[0]);
} else {
error("First argument of setlistat must be a list reference");
}
if (holds_alternative<Literal>(l.args[1])) {
if (holds_alternative<int>(get<Literal>(l.args[1]).val)) {
ref = get<int>(get<Literal>(l.args[1]).val);
} else {
error("Second argument of setlistat must be an integer literal");
}
} else {
error("Second argument of setlistat must be an integer literal");
}
if (holds_alternative<Literal>(l.args[2])) {
value = get<Literal>(l.args[2]);
} else {
error("Third argument of setlistat must be a direct reference");
}
if (lists.find(listref.listName) != lists.end()) {
if (lists[listref.listName].val.size() > ref) {
lists[listref.listName].val[ref] = value;
} else {
error("Index " + to_string(ref) + " out of range of list " + listref.listName);
}
} else {
error("Unknown list: " + listref.listName);
}
}
break;
/*
listappend instruction
This instruction appends an item to a list.
*/
case Instructions::Listappend:
if (l.args.size() < 2) {
error("Could not find all arguments required for Listappend inbuilt");
}
{
ListRef listref;
Literal value;
if (holds_alternative<ListRef>(l.args[0])) {
listref = get<ListRef>(l.args[0]);
} else {
error("Second argument of listappend must be a list reference");
}
if (holds_alternative<Literal>(l.args[1])) {
value = get<Literal>(l.args[1]);
} else {
error("Second argument of listappend must be a direct reference");
}
if (lists.find(listref.listName) != lists.end()) {
lists[listref.listName].val.push_back(value);
} else {
error("Unknown list: " + listref.listName);
}
}
break;
/*
getlistsize instruction
This instruction saves the size of a list in a variable.
*/
case Instructions::Getlistsize:
if (l.args.size() < 2) {
error("Could not find all arguments required for Getlistsize inbuilt");
}
{
ListRef ref;
Direct var;
if (holds_alternative<ListRef>(l.args[0])) {
ref = get<ListRef>(l.args[0]);
} else {
error("First argument of getlistsize must be a list reference");
}
if (holds_alternative<Direct>(l.args[1])) {
var = get<Direct>(l.args[1]);
} else {
error("Second argument of getlistsize must be a direct reference");
}
Literal newLit;
if (lists.find(ref.listName) != lists.end()) {
newLit.val = int(lists[ref.listName].val.size());
variables[var.varName] = newLit;
} else {
error("Couldn't find the list " + ref.listName);
}
break;
}
/*
getstrsize instruction
This instruction saves the size of a string in a variable.
*/
case Instructions::Getstrsize:
if (l.args.size() < 2) {
error("Could not find all arguments required for Getstrsize inbuilt");
}
{
string ref;
Direct var;
if (holds_alternative<Literal>(l.args[0])) {
if (holds_alternative<string>(get<Literal>(l.args[0]).val)) {
ref = get<string>(get<Literal>(l.args[0]).val);
} else {
error("First argument of getlistsize must be a string value");
}
} else {
error("First argument of getlistsize must be a string value");
}
if (holds_alternative<Direct>(l.args[1])) {
var = get<Direct>(l.args[1]);
} else {
error("Second argument of getlistsize must be a direct reference");
}
Literal newLit;
newLit.val = int(ref.size());
variables[var.varName] = newLit;
break;
}
/*
stdin instruction
This instruction takes input from the standard character input via
@@ -374,7 +735,7 @@ void exec(vector<Instruction> in) {
*/
case Instructions::Stdin:
if (l.args.size() < 1) {
error("Could not find all arguments require for Stdin inbuilt");
error("Could not find all arguments required for Stdin inbuilt");
}
if (holds_alternative<Direct>(l.args[0])) {
string userIn;
@@ -430,6 +791,12 @@ void exec(vector<Instruction> in) {
final.val = get<double>(left.val) + double(get<int>(right.val));
} else if (holds_alternative<string>(left.val) && holds_alternative<string>(right.val)) {
final.val = get<string>(left.val) + get<string>(right.val);
} else if (holds_alternative<string>(left.val) && holds_alternative<char>(right.val)) {
final.val = get<string>(left.val).append(&get<char>(right.val));
} else if (holds_alternative<char>(left.val) && holds_alternative<string>(right.val)) {
final.val = string().append(&get<char>(left.val)) + get<string>(right.val);
} else if (holds_alternative<char>(left.val) && holds_alternative<char>(right.val)) {
final.val = string().append(&get<char>(left.val)).append(&get<char>(right.val));
} else {
error("Cannot add those two values");
}
@@ -955,16 +1322,25 @@ vector<vector<string>> lexer(string in) {
vector<Instruction> parser(vector<vector<string>> in) {
vector<Instruction> out;
int lineNum = 1;
for (vector<string> lineTokens : in) {
if (lineTokens.empty()) continue;
lineNum ++;
Instruction newInst;
if (lineTokens.empty()) {
out.push_back(newInst);
continue;
};
bool firstInst = true;
for (string i : lineTokens) {
if (firstInst) {
firstInst = false;
if (i == "stdin") newInst.inst = Instructions::Stdin;
if (isLabel(i)) {
labels[i.substr(1)] = lineNum;
out.push_back(newInst);
continue;
}
else if (i == "stdin") newInst.inst = Instructions::Stdin;
else if (i == "stdout") newInst.inst = Instructions::Stdout;
else if (i == "stdlnout") newInst.inst = Instructions::Stdlnout;
else if (i == "jump") newInst.inst = Instructions::Jump;
@@ -979,6 +1355,13 @@ vector<Instruction> parser(vector<vector<string>> in) {
else if (i == "inequal") newInst.inst = Instructions::Inequal;
else if (i == "end") newInst.inst = Instructions::End;
else if (i == "set") newInst.inst = Instructions::Set;
else if (i == "setlist") newInst.inst = Instructions::Setlist;
else if (i == "setlistat") newInst.inst = Instructions::Setlistat;
else if (i == "getlistat") newInst.inst = Instructions::Getlistat;
else if (i == "getlistsize") newInst.inst = Instructions::Getlistsize;
else if (i == "listappend") newInst.inst = Instructions::Listappend;
else if (i == "getstrsize") newInst.inst = Instructions::Getstrsize;
else if (i == "getstrcharat") newInst.inst = Instructions::Getstrcharat;
else error("Unexpected token: " + i);
} else {
Types type = getType(i);
@@ -997,13 +1380,28 @@ vector<Instruction> parser(vector<vector<string>> in) {
newInst.args.push_back(newDirect);
}
break;
case Types::Label:
error("Label should be defined as first token");
break;
case Types::Line:
{
Line newLine;
newLine.lineNum = stoi(i.substr(1));
if (isInt(i.substr(1))) newLine.lineNum = stoi(i.substr(1));
else {
newLine.isLabel = true;
newLine.label = i.substr(1);
newLine.lineNum = 0;
}
newInst.args.push_back(newLine);
}
break;
case Types::ListRef:
{
ListRef newLR;
newLR.listName = i.substr(1);
newInst.args.push_back(newLR);
}
break;
case Types::String:
{
Literal newLiteral;

89
tests/everything.grnd Normal file
View File

@@ -0,0 +1,89 @@
# I/O
stdlnout "Hello there!"
stdout "What is your name? "
stdin &name
add "Hello, " $name &out
stdlnout $out
# Types
stdlnout "dingus"
stdlnout 7
stdlnout 3.14159
stdlnout true
stdlnout 'e'
# Variables
set &testVar "This is a test"
stdlnout $testVar
# Lists
setlist *testList "Item 1" "Another Item" "Item the Third"
getlistat *testList 2 &tmp
stdlnout $tmp
setlistat *testList 1 "I changed this item"
getlistat *testList 1 &tmp
stdlnout $tmp
listappend *testList "I appended this item"
getlistat *testList 3 &tmp
stdlnout $tmp
getlistsize *testList &tmp
stdlnout $tmp
# String Operations
set &testStr "dingus"
getstrsize $testStr &tmp
stdlnout $tmp
getstrcharat $testStr 3 &tmp
stdlnout $tmp
# Maths
add 1 1 &tmp
stdlnout $tmp
subtract 10 5 &tmp
stdlnout $tmp
multiply 15 15 &tmp
stdlnout $tmp
divide 36 4 &tmp
stdlnout $tmp
# Comparisons
equal 5 5 &tmp
stdlnout $tmp
inequal 5 5 &tmp
stdlnout $tmp
greater 10 5 &tmp
stdlnout $tmp
lesser 10 5 &tmp
stdlnout $tmp
# Control flow
set &counter 0
@myLabel
add $counter 1 &counter
stdlnout $counter
inequal $counter 10 &case
if $case %myLabel
# That's it!
stdlnout "Everything ran! Check the output to see if it is what is expected."
end 0

View File

@@ -1,7 +1,9 @@
@begin
stdout "Do you like cheese? "
stdin &userin
equal $userin "yes" &condition
if $condition %7
if $condition %end
stdlnout "That is sad"
jump %1
jump %begin
@end
stdlnout "Awesome! I do too!"

13
tests/lists.grnd Normal file
View File

@@ -0,0 +1,13 @@
# A cool list
setlist *favWords "hello" "there" "general" "kenobi"
set &count 0
set &passedThrough true
@jmpbck
getlistat *favWords $count &tmp
stdlnout $tmp
add $count 1 &count
getlistsize *favWords &tmp2
inequal $count $tmp2 &tmp3
if $tmp3 %jmpbck