🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

C-Lesh - My Video Game Programming Language

Started by
7 comments, last by fastcall22 6 years, 4 months ago

So I wrote a programming language called C-Lesh to program games for my game maker Platformisis. It is a scripting language which tiles into the JavaScript game engine via a memory mapper using memory mapped I/O. Currently, I am porting the language as a standalone interpreter to be able to run on the PC and possibly other devices excluding the phone. The interpreter is being written in C++ so for those of you who are C++ fans you can see the different components implemented. Some background of the language and how to program in C-Lesh can be found here:

http://www.codeloader.net/readme.html

As I program this thing I will post code from different components and explain.

Codeloader - Free games, stories, and articles!
If you stare at a computer for 5 minutes you might be a nerdneck!
https://www.codeloader.dev

Advertisement

Really cool! Are you making this free to contribute to on github or anything or are you keeping this private? Either way it looks like it would be fun to play around with.

20 hours ago, samoan62 said:

Really cool! Are you making this free to contribute to on github or anything or are you keeping this private? Either way it looks like it would be fun to play around with.

It will be free and open source. I would love to share it even if no one uses it but me!

Codeloader - Free games, stories, and articles!
If you stare at a computer for 5 minutes you might be a nerdneck!
https://www.codeloader.dev

Here is my C-Lesh compiler/interpreter class:

namespace codeloader {
 
struct Foperand {
std::string string;
int number;
int address;
int index;
std::string field;
int type;
int code;
std::string num_placeholder;
std::string addr_placeholder;
std::string index_placeholder;
};
 
struct Fcondition {
int left;
int test;
int right;
int logic;
};
 
struct Fvalue {
std::string string;
int number;
int type;
};
 
struct Fblock {
int code;
std::vector< std::vector<Foperand> > expressions;
std::vector<Fcondition> conditional;
std::map<std::string, int> fields;
Fvalue value;
std::vector<std::string> strings;
};
 
struct Ftoken {
std::string token;
int line_no;
std::string line;
};
 
struct Fparse_obj {
int code;
std::string pattern;
};
 
struct Ffield {
std::string name;
int value;
std::string vplaceholder;
};
 
struct Flist {
int address;
int index;
std::string field;
std::string addr_placeholder;
std::string index_placeholder;
};
 
class Cutility {
 
public:
std::vector<std::string> Rsplit_file(std::string name);
std::vector<std::string> Rsplit_line(std::string line);
std::string Rreplace_token(std::string token, std::string replacement, std::string line);
std::string Rreplace_all(std::string token, std::string replacement, std::string line);
std::vector<std::string> Rsplit_string(std::string token, std::string string);
bool Ris_identifier(std::string token);
bool Ris_positive_number(std::string token);
std::string Rto_string(int number);
 
};
 
class Cclsh: public Cutility {
 
public:
enum clsh_defaults {
MAX_CYCLES = 1000000 // To prevent infinite loops.
};
 
std::map<std::string, Fvalue> symtab;
Fblock* memory;
int memory_size;
std::stack<int> stack;
int prgm_counter;
std::vector<Ftoken> tokens;
std::map<std::string, Fparse_obj> parse_table;
std::map<std::string, int> code_table;
std::vector<std::string> debug_symbols;
Ftoken last_token;
 
};

There are lot's of types but this is my representation of the interpreter/compiler. There is no parse tree stored nor is there byte code. C-Lesh's memory is block formatted.

Codeloader - Free games, stories, and articles!
If you stare at a computer for 5 minutes you might be a nerdneck!
https://www.codeloader.dev

And here's code for the parser:

Cclsh::Cclsh(int memory_size) :
code_table({
{ "eq", 1 },
{ "ne", 2 },
{ "lt", 3 },
{ "gt", 4 },
{ "le", 5 },
{ "ge", 6 },
{ "+", 1 },
{ "-", 2 },
{ "*", 3 },
{ "/", 4 },
{ "rem", 5 },
{ "cat", 6 },
{ "rand", 7 },
{ "e", 0 },
{ "n", 1 },
{ "s", 2 },
{ "a", 3 },
{ "f", 4 },
{ "l", 5 },
{ "and", 1 },
{ "or", 2 }
}),
parse_table({
{ "test", { 1, "<c>" } },
{ "move", { 2, "<e>" } },
{ "call", { 3, "<e>" } },
{ "return", { 4, "" } },
{ "stop", { 5, "" } },
{ "set", { 6, "<e> to <e>" } },
{ "output", { 7, "<e>" } },
{ "read", { 8, "<e>" } },
{ "load-map", { 9, "<e> from <e>" } },
{ "load-image", { 10, "<e> from <e> name <e>" } },
{ "load-sound", { 11, "<e> from <e> name <e>" } },
{ "keys", { 12, "<s>" } },
{ "version", { 13, "" } },
{ "log", { 14, "<s>" } },
{ "purge", { 15, "<s>" } },
{ "report", { 16, "" } }
}) {
// Initialize blocks.
this->memory = new Fblock[memory_size];
this->memory_size = memory_size; // Record size of memory.
for (int block_index = 0; block_index < memory_size; block_index++) {
Fblock* block = &this->memory[block_index];
block->code = 0;
block->value.number = 0;
block->value.type = this->code_table["n"];
}
// Initialize last token.
this->last_token.token = "";
}
 
Cclsh::~Cclsh() {
if (this->memory) {
delete[] this->memory;
}
}
 
void Cclsh::Rcompile(std::string name) {
// Do some cleanup.
this->symtab.clear();
this->tokens.clear();
this->prgm_counter = 0;
// Run preprocessor.
this->Rpreprocess();
try {
// Now compile the source.
this->Rparse_tokens(name);
// Do command parse.
while (this->tokens.size() > 0) {
// Now parse the command.
this->Rparse_command();
}
// Resolve symbol references.
this->Rreplace_symbols();
}
catch (std::string error) {
std::cout << error.c_str() << std::endl;
}
}

 

void Cclsh::Rparse_tokens(name) {
std::vector<std::string> lines = this->Rsplit_file(name);
int line_count = lines.size();
for (int line_index = 0; line_index < line_count; line_index++) {
std::string line = lines[line_index];
std::vector<std::string> tokens = this->Rsplit_line(line);
// Build out the tokens.
int tok_count = tokens.size();
for (int tok_index = 0; tok_index < tok_count; tok_index++) {
std::string token = tokens[tok_index];
Ftoken tok_obj;
tok_obj.token = token;
tok_obj.line_no = line_index + 1;
tok_obj.line = line;
this->tokens.push(tok_obj);
}
}
}
 
std::vector<Foperand> Cclsh::Rparse_expression() {
std::vector<Foperand> expression;
Foperand operand = this->Rparse_operand();
expression.push_back(operand);
while (this->Ris_operator()) {
Ftoken oper = this->Rparse_token();
int oper_code = this->code_table[oper.token];
Foperand op;
op.code = oper_code;
expression.push_back(op);
operand = this->Rparse_operand();
expression.push_back(operand);
}
return expression;
}
 
Foperand Cclsh::Rparse_operand() {
Foperand operand;
operand.string = "";
operand.number = 0;
operand.address = 0;
operand.index = 0;
operand.field = "";
operand.type = this->code_table["n"];
// Now proceed with the parsing.
if (this->Ris_number()) {
operand.type = this->code_table["n"];
operand.number = this->Rparse_number();
}
else if (this->Ris_address()) {
operand.type = this->code_table["a"];
operand.address = this->Rparse_address();
}
else if (this->Ris_field()) {
operand.type = this->code_table["f"];
Ffield field = this->Rparse_field();
operand.address = field.value;
operand.field = field.name;
}
else if (this->Ris_list()) {
operand.type = this->code_table["l"];
Flist list = this->Rparse_list();
operand.address = list.address;
operand.index = list.index;
operand.field = list.field;
}
else if (this->Ris_string()) {
operand.type = this->code_table["s"];
operand.string = this->Rparse_string();
}
else if (this->Ris_num_placeholder()) {
operand.type = this->code_table["n"];
operand.num_placeholder = this->Rparse_token();
}
else if (this->Ris_addr_placeholder()) {
operand.type = this->code_table["a"];
operand.addr_placeholder = this->Rparse_addr_placeholder();
}
else if (this->Ris_field_placeholder()) {
operand.type = this->code_table["f"];
std::vector<std::string> tokens = this->Rparse_field_placeholder();
operand.addr_placeholder = tokens[0];
operand.field = tokens[1];
}
else if (this->Ris_list_placeholder()) {
operand.type = this.code_table["l"];
std::vector<std::string> tokens = this->Rparse_list_placeholder();
operand.addr_placeholder = tokens[0];
operand.index_placeholder = tokens[1];
operand.field = tokens[2];
}
else {
this->Rgenerate_error("Operand is invalid. (" + this->Rpeek_token().token + ")");
}
return operand;
}
 
Fcondition Cclsh::Rparse_condition(Fblock& block) {
Fcondition condition;
condition.left = 0;
condition.right = 0;
condition.test = 0;
// The expression is stored in the expression list.
std::vector<Foperand> left_exp = this->Rparse_expression();
block.expressions.push_back(left_exp);
condition.left = block.expressions.size() - 1; // Just point to expression.
std::string test = this->Rparse_test();
condition.test = this->code_table[test];
std::vector<Foperand> right_exp = this->Rparse_expression();
block.expressions.push_back(right_exp);
condition.right = block.expressions.size() - 1;
return condition;
}
 
std::string Cclsh::Rparse_test() {
Ftoken token = this->Rparse_token();
std::string test = "";
if ((token.token == "eq") ||
(token.token == "ne") ||
(token.token == "lt") ||
(token.token == "gt") ||
(token.token == "le") ||
(token.token == "ge")) {
test = token.token;
}
else {
this->Rgenerate_error(token.token + " is not a valid test.");
}
return test;
}
 
std::vector<Fcondition> Cclsh::Rparse_conditional(Fblock& block) {
std::vector<Fcondition> conditional;
Fcondition condition = this->Rparse_condition(block);
conditional.push_back(condition);
while (this->Ris_logic()) {
Ftoken logic = this->Rparse_token();
Fcondition logic_code;
logic_code.logic = this->code_table[logic.token];
conditional.push_back(logic_code);
condition = this->Rparse_condition(block);
conditional.push_back(condition);
}
return conditional;
}
 
void Cclsh::Rparse_command() {
if (this->prgm_counter < this->memory_size) {
Ftoken code = this->Rparse_token();
if (code.token == "remark") { // Comment
// Parse all the way to end.
Ftoken end_tok = this->Rpeek_token();
while (end_tok.token != "end") {
this->Rparse_token(); // Remove comment token.
end_tok = this->Rpeek_token();
}
// Remove end command.
this->Rparse_token();
}
else if (code.token == "define") { // define <name> as <number>
Ftoken name = this->Rparse_token();
this->Rparse_keyword("as");
int value = this->Rparse_number();
this->symtab[name.token] = value;
}
else if (code.token == "label") { // label <name>
Ftoken name = this->Rparse_token();
this->symtab[name.token] = this->prgm_counter; // Update with current address.
}
else if (code.token == "var") { // var <name>
Ftoken name = this->Rparse_token();
this->symtab[name.token] = this->prgm_counter++; // Skip one block.
}
else if (code.token == "list") { // list <name> alloc <blocks>
Ftoken name = this->Rparse_token();
this->symtab[name.token] = this->prgm_counter;
this->Rparse_keyword("alloc");
int count = this->Rparse_number();
// Allocate free blocks for the list.
this->prgm_counter += count;
}
else { // Possible commands.
if (this->parse_table.find(code.token) != this->parse_table.end()) {
Fparse_obj command = this->parse_table[code.token];
Fblock block = this->memory[this->prgm_counter++];
// Clear out the block.
this->Rclear_block(block);
// Assign block code.
block.code = command.code;
if (command.pattern.length() > 0) {
std::vector<std::string> entries = this->Rsplit_line(command.pattern);
int entry_count = entries.size();
for (int entry_index = 0; entry_index < entry_count; entry_index++) {
std::string entry = entries[entry_index];
if (entry == "<c>") {
block.conditional = this->Rparse_conditional(block);
}
else if (entry == "<e>") {
block.expressions.push_back(this->Rparse_expression());
}
else if (entry == "<s>") {
block.strings.push_back(this->Rparse_string());
}
else { // This will be treated as keyword.
this->Rparse_keyword(entry);
}
}
}
// Record debug info.
this.debug_symbols.push_back(code.token);
}
else {
this->Rgenerate_error("Invalid command " + code.token + ".");
}
}
}
else {
// Should probably not be called.
this->Rgenerate_error("Program too big for memory.");
}
}

Codeloader - Free games, stories, and articles!
If you stare at a computer for 5 minutes you might be a nerdneck!
https://www.codeloader.dev

I'm currently reducing the number of commands in C-Lesh. With a basic set of 7 commands I will focus more on memory mapping for I/O which will allow more flexibility with the interpreter.
 

parse_table({
{ "test", { 1, "<c>" } },
{ "move", { 2, "<e>" } },
{ "call", { 3, "<e>" } },
{ "return", { 4, "" } },
{ "stop", { 5, "" } },
{ "set", { 6, "<e> to <e>" } },
{ "output", { 7, "<e>" } }

Codeloader - Free games, stories, and articles!
If you stare at a computer for 5 minutes you might be a nerdneck!
https://www.codeloader.dev

Currently creating the I/O module. Will post some code of this shortly.

Codeloader - Free games, stories, and articles!
If you stare at a computer for 5 minutes you might be a nerdneck!
https://www.codeloader.dev

Please don’t.  The forums are not an appropriate place for constant updates.  Why not start a blog instead?

https://www.gamedev.net/blogs/create/

This topic is closed to new replies.

Advertisement