Published by Jady on 5/23/24, 11:36 PM
I've narrowed down all the syntax definitions to just a few lines of really flexible code:
IToken fieldToken = new TokenString([new ParameterValueToken("fieldTypes"), new ParameterValueToken("fieldNames")]);
IToken parameterToken = new TokenString([new ParameterValueToken("parameterTypes"), new ParameterValueToken("parameterNames")]);
IToken varArgParameterToken = new TokenString([new ParameterValueToken("varArgParameterType"), new LiteralToken("..."), new ParameterValueToken("varArgParameterName")]);
AddFunction(ContextType.Program, Functions.Program, 100, new TokenSplit(new PassToken(), new LiteralToken(";"), new EOFToken(), new ParameterExpressionToken("statements")));
AddFunction(ContextType.Program, Functions.Struct, 90, new TokenString([new ParameterValueToken("name"), new TokenSplit(new LiteralToken("{"), new LiteralToken(";"), new LiteralToken("}"), new TokenOptions([fieldToken, new ParameterExpressionToken("functions")]))]));
AddFunction(ContextType.Program, Functions.Function, 80, new TokenString([new ParameterValueToken("returnType"), new ParameterValueToken("name"), new TokenSplit(new LiteralToken("("), new LiteralToken(","), new LiteralToken(")"), new TokenOptions([parameterToken, varArgParameterToken])), new TokenOptional(new TokenSplit(new LiteralToken("{"), new LiteralToken(";"), new LiteralToken("}"), new ParameterExpressionToken("statements")))]));
AddFunction(ContextType.Function, Functions.Declare, 100, new TokenString([new LiteralToken("Declare"), new ParameterValueToken("type"), new ParameterValueToken("name")]));
AddFunction(ContextType.Function, Functions.Define, 100, new TokenString([new ParameterValueToken("type"), new ParameterValueToken("name"), new LiteralToken("="), new ParameterExpressionToken("value")]));
AddFunction(ContextType.Function, Functions.Assign, 100, new TokenString([new ParameterExpressionToken("target"), new LiteralToken("="), new ParameterExpressionToken("value")]));
AddFunction(ContextType.Function, Functions.Return, 100, new TokenString([new LiteralToken("Return"), new TokenOptional(new ParameterExpressionToken("value"))]));
AddFunction(ContextType.Function, Functions.Add, 30, new TokenString([new ParameterExpressionToken("a"), new LiteralToken("+"), new ParameterExpressionToken("b")]));
AddFunction(ContextType.Function, Functions.LessThan, 40, new TokenString([new ParameterExpressionToken("a"), new LiteralToken("<"), new ParameterExpressionToken("b")]));
AddFunction(ContextType.Function, Functions.While, 100, new TokenString([new LiteralToken("While"), new LiteralToken("("), new ParameterExpressionToken("condition"), new LiteralToken(")"), new ParameterExpressionToken("body")]));
Published by Jady on 5/22/24, 10:57 PM
Here's a more in-depth diagram of how the compiler works, if you can understand stuff like regex:
// Source code
extern Void printf(String format, Int... values);
Foo
{
Int a;
Int b;
Foo New(Int a, Int b)
{
Declare Foo this;
this.a = a;
this.b = b;
Return this;
}
Int* A(Foo this)
{
Return this.a;
}
};
Void main()
{
Foo foo = Foo.New(2, 3);
printf("Starting at %i\n", foo.A());
While (foo.A() < 6)
{
foo.A() = foo.A() + 1;
printf("Loop %i\n", foo.A());
};
printf("Ending at %i\n", foo.A());
Return;
};
// Compiler first pass
// Mostly just grab all the type and function information
// Available functions
Parameter(String type, String name)
as "$type $name";
VarArgParameter(String type, String name)
as "$type... $name";
Body(Int index)
as "\{ $index=lexer.Index \}";
Function(String name, Parameter[] parameters, VarArgParameter? varArgParameter, String returnType, Body? body)
as "$returnType $name \(\) $body"
as "$returnType $name \( $varArgParameter (,)? \) $body"
as "$returnType $name \( $parameters (, $parameters)* (, $varArgParameter)? (,)? \) $body";
Field(String type, String name)
as "$type $name";
Struct(String name, Field[] fields, Function[] functions)
as "$name \{ ($fields | $functions)+ \}";
Program(Struct[] structs, Function[] functions)
as "($structs | $functions)+";
Program(
[
Struct("Foo", [Field("Int", "a"), Field("Int", "b")],
[
Function("New", [Parameter("Int", "a"), Parameter("Int", "b")], None, "Foo", Some(FOO.NEW BODY INDEX)),
Function("A", [Parameter("Foo", "this")], None, "Int*", Some(FOO.A BODY INDEX),
]),
],
[
Function("printf", [Parameter("String", "format")], Some(Parameter("Int", "values")), "Void", None),
Function("main", [], None, "Void", Some(MAIN BODY INDEX)),
]);
// Compiler second pass
// Generate any intermediary code needed to parse function bodies
// Available functions
Parameter(Type type, String name);
Body(Int index);
Function(String name, Parameter[] parameters, Parameter? varArgParameter, String returnType, Body? body);
Field(Type type, String name);
Struct(String name, Field[] fields, Function[] functions);
Program(Struct[] structs, Function[] functions);
Program(
[
Struct("Foo", [Field(Int, "a"), Field(Int, "b")],
[
Function("New", [Parameter(Int, "a"), Parameter(Int, "b")], None, Foo, Some(FOO.NEW BODY INDEX)),
Function("A", [Parameter(Foo, "this")], None, Int*, Some(FOO.A BODY INDEX),
Function("Get_a", [Parameter(Foo, "this")], None, Int*, None),
Function("Get_b", [Parameter(Foo, "this")], None, Int*, None),
]),
],
[
Function("printf", [Parameter(String, "format")], Some(Parameter(Int, "values")), Void, None),
Function("main", [], None, Void, Some(MAIN BODY INDEX)),
Function("Get_Foo.New", [Parameter(Type<Foo>, "type")], None, Foo.New, None),
Function("Get_Foo.A", [Parameter(Type<Foo>, "type")], None, Foo.A, None),
Function("Call_Foo.A", [Parameter(Foo, "this")], None, () => Foo.A(this), None),
]);
// Compiler third pass
// Now we get to the actual function definitions
// Available functions
/// Compiler functions
Define(Type type, String name, Value value)
as "$type $name = $value";
Declare(Type type, String name)
as "$type $name";
Assign(Value target, Value value)
as "$target = $value";
Return(Value? value)
as "Return $value?";
While(Expression condition, Expression body)
as "While ( $condition ) $body";
LessThan(Value left, Value right)
as "$left < $right";
Add(Value left, Value right)
as "$left + $right";
/// User functions
printf(String format, Value... values);
main();
/// Compiler-generated user functions
Foo.Get_a(Foo this)
as "$this.a";
Foo.Get_b(Foo this)
as "$this.b";
Get_Foo.New(Type type)
as "$type.New";
Get_Foo.A(Type type)
as "$type.A";
Call_Foo.A(Foo this)
as "$this.A";
Foo.New(Int a, Int b)
{
Declare(Foo, "this");
Assign(Foo.Get_a(this), a);
Assign(Foo.Get_b(this), b);
Return(Some(this));
}
Foo.A(Foo this)
{
Return(Foo.Get_a(this));
}
Main()
{
Define(Foo, "foo", Get_Foo.New(Foo)(2, 3));
printf("Starting at %i", Call_Foo.A(foo)());
While(LessThan(Call_Foo.A(foo)(), 6),
{
Assign(Call_Foo.A(foo)(), Add(Call_Foo.A(foo)(), 1));
printf("Loop %i", Call_Foo.A(foo)());
});
printf("Ending at %i", Call_Foo.A(foo));
Return(None);
}
Everything is functions. Even the functions.
Published by Jady on 5/21/24, 11:17 PM
Remember how I said everything's a function? I've been working on dot notation, which of course are also functions.
But they're procedurally generated functions at compile time to keep them safe, which is... particularly goofy.
Foo
{
Int a
Int GetA(Foo this)
{
Return this.a
}
}
Void Main()
{
Declare Foo foo
Foo.GetA(foo)
foo.GetA()
}
becomes
Foo
{
Int a
Int GetA(Foo this)
{
Return Get_a(this)
}
Int* Get_a(Foo this)
as "$0.a"
{
Return #access(this, a)
}
}
(Foo => Int) Get_GetA(Type<Foo> foo)
as "$0.GetA"
{
Return #access(foo, GetA)
}
(() => Int) Call_GetA(Foo foo)
as "$0.GetA"
{
Return () => #access(foo.Type, GetA)(foo)
}
Void Main()
{
Declare Foo foo
Get_GetA(Foo)(foo)
Call_GetA(foo)()
}
These extra generated functions only exist in the compiler though, and can't be accessed any other way.
Published by Jady on 5/18/24, 6:49 PM
The "make a game > make a game engine > make a programming language" pipeline is strong. Here's what I've done
in Cetus Script so far!
extern Void printf(String format, Int... values)
struct Foo
{
Int a
Int b
}
Void main()
{
Foo foo = New
foo.a = 2
printf("Starting at %i\n", foo.a)
While (foo.a < 6)
{
foo.a = foo.a + 1
printf("Loop %i\n", foo.a)
}
printf("Ending at %i\n", foo.a)
Return
}
The first really big feature of Cetus has already been implemented, at least compiler-side. Every single statement
in main
is a function! Foo foo = New
is secretly just Declare(Foo, foo)
.
foo.a = 2
is secretly Assign(Get(foo, a), 2)
. These functions just have patterns
associated with them that let them be used more fluently. Patterns will also eventually be able to be used for types
as well, so you can say Foo?
instead of Option
.
Another interesting feature is that pointers are handled automatically! foo.a
returns Int*
when used in Assign
, but it also returns Int
when used in printf
! You don't
get to handle reference and dereference pointers manually, but it can figure out what's usable on it's own.
Other quirks of the language include using PascalCase for things that public and camelCase for things that are
private, whitespace means nothing and semicolons are optional, etc.
The next thing I'm trying to work on is something like rust's traits, but also allows you to define fields in trait
objects, which you can't do with rust. Other planned features are things like properties being automatic type
definitions instead of just functions.