Jump to content

The Ergo Programming Language


SoulofDeity
 Share

Recommended Posts

I had originally began tinkering with this idea in a separate thread about a new interchange format, but finally decided it was deserving of it's own thread. The vision for Ergo is a statically typed compiled language for systems programming focused on abstraction, expressiveness, and simplicity. It's goals include high polymorphism, high code reusability, high readability, and a low learning curve.

 

 

Let

 

The 'let' keyword is used to declare new local variables.

let x = 5
x = 6
The Ergo programming language also supports tuple assigment like in Python.

let x, y = 5, 5
x, y = 6, 6
The types of the variables are deduced from the values assigned to them upon declaration.

 

 

And

 

The 'and' keyword is used to combine statements.

something () and something ()
Do

 

The 'do' keyword is used to execute instructions. When immediately followed by an instruction, it executes that instruction.

 

 

 

do something ()
this can also be used in conjunction with the 'and' keyword.

do something () and something ()
If a newline immediately follows a 'do' keyword, then it executes a block of instructions with the same indentation level.

do
  something ()
  something ()
For (+ 'from', 'to', 'by', 'in')

 

The 'for' keyword is the most powerful keyword in the Ergo language. It doesn't create loops, it declares the subject for a specific context. The most recognizable form is the iteration context.

 

 

 

for i from 0 to 5 by 1 do
  something ()

for j, k to 10, 10 do
  something ()
Here, the subjects are 'i', 'j', and 'k'. The above code is equivalent to the following C++ code:

for (int i = 0; i < 5; i++)
  something ();

for (int i = 0, j = 0; i < 10 && j < 10; i++, j++)
  something ();
Iterations have the format 'from <start> to <stop> by <step>'. The start and step are optional though. By default, the start will be 0 and the step will be 1. The next most recognizable form is the enumeration.

for e in c do
  something ()
Enumerations have the format 'in <collection>'. This is equivalent to the following C# code:

foreach (int e in c)
  something ();
The best way to visualize the 'for' statements in Ergo is to break them into 3 parts:

for i to 5 do something ()

for i            # part 1 - the subject is 'i'
to 5             # part 2 - the context is the range from 0 to 5 by 1
do something ()  # part 3 - the predicate
As you can see here, the loop is created by the context. In the future, if anything aside from an iteration or enumeration is used as a context, the predicate will only be executed once.

 

 

Next

 

The 'next' keyword is used to advance the iterator or enumerator in a context. It's equivalent to the 'continue' keyword in C.

for i to 5 do
  next
Exit

 

The 'exit' keyword is used to escape from a context. It's equivalent to the 'break' keyword in C.

 

 

 

for i to 5 do
  exit
On (+ or)

 

The 'on' keyword is used to create an event handler.

 

 

 

on print do
  something ()
Events may optionally have parameters.

on print format, text do
  something ()
In the above code, both 'format' and 'text' are parameters. Despite their appearance, they are statically typed. The way this works is simple:  they're generic. It's equivalent to saying the following in C++:

template <typename T, typename U>
void print (T format, U text) {
  something ();
}
When some code tries to trigger the event:

print ("%s", "Hello")
The compiler says, "oh, I see you're passing strings for the format and text, so the print event handler must accept those types". If you're an experienced programmer, just take a minute to let that one sink in. Unlike most other languages, Ergo actually assumes that you know what you're doing and generates code for whatever object types you trigger the event with. If you wanted to pass numbers instead, Ergo is perfectly okay with that and you don't have to change a single line of code.

 

This extreme level of polymorphism and simplicity makes Ergo easy to use for prototyping. However, it has to know ahead of time which types to generate code for. For this, the 'for' keyword is used to specify a set of type constraints.

on add x, y for int, int or float, float do
  something ()
These constraints ensure that only code for ints and floats will be generated. So, if you were to call 'add' with doubles, it'll implicitly cast to float instead of generating an add event handler for doubles.

 

 

If

 

The 'if' keyword is used to conditionally execute instructions.

if true do something ()
any statement checked with 'if' will be implicitly casted to a boolean. In other words, null or 0 is false and if it's not false it's true.

 

 

To

 

As shown in the prior example of an iteration context, the 'to' keyword creates a branch from one value to another. This is not limited to integers though. Ergo allows you to exploit this fact to chain multiple conditions together.

if x < 5 do
  something ()
to x > 5 do
  something ()
This construct is a 'to-do list'. If 'x' is less than 5, then the result will evaluate to true. This means that the next statement is implicitly 'true to x > 5'. Note that the range specifies an iteration context, so regardless of if 'x' is greater than 5 or not, the predicate will not be executed. In laymens terms: if 'x' is less than 5, then all the proceding 'to' statements are ignored. However, if 'x' is not less than 5, then the result will evaluate to false. This means that the next statement is implicitly 'false to x > 5'. In this case, if 'x' is equal to 5 then this becomes 'false to false', and thus the result remains false; passing on to the next 'to' statement. If 'x' is greater than 5, then this becomes 'false to true', causing the predicate to be executed once and the result to be changed to true; meaning all proceding 'to' statements will be ignored. In laymens terms, the 'to' keyword + boolean values = else if.

 

Note that this isn't limited to if statements. The following is a syntactically correct fake if-statement in Ergo

false to x < 5 do
  something ()
One way of looking at this is that there is no such thing as an if statement, only 'to' statements; and 'if' is just an alias for 'false to'. You can also use it to execute a piece of code a specific number of times.

0 to 5 do
  something ()
Else

 

The 'else' keyword is used to conditionally execute instructions if the prior statement wasn't executed. The most common use of this is with 'if' statements.

 

 

 

 

if true do
  something ()
else do
  something ()
For this usage, a 'do' statement must come immediately after the 'else' keyword.

if x < y do
  something ()
to x > y do
  something ()
else do
  something ()
--------------------

 

Up For Consideration

 

I'm considering the use of 'select' queries like in C#. The type system is also in need of work, C-types just look out of place in Ergo imo.

  • Like 3
Link to comment
Share on other sites

Some changes have been made. Firstly, the 'let' keyword is no longer used to create local variables. Instead, it's used to define locally-scoped macros.

 

For constants, the format of a let-statement is 'let var be something'. For example:

let x be 5

For functions, the format of a let-statement is 'let fn arg1, arg2... do something'. For example:

let print format, text do
  something ()

This new syntax makes it unneccessary for there to be an 'on' keyword.

 

 

Another change I've been working on is an alternative wordless syntax.

and        &&
as         :
be         :=
else       ..1b
from       =
if         0b..
let        \
or         ||
then       \
to         ..

An example usage of this, the following code:

let print format, text do
  let x be 5
  if format != null and text != null then do
    something ()
  else do
    something ()
  for i as Integer from 0 to 100 do
    something ()

could be alternatively be written as:

\ print format, text do
  \ x := 5
  0b.. format != null && text != null \ do
    something ()
  ..1b do
    something ()
  for i : Integer = 0 .. 100 do
    something ()

or

print format, text do
  x := 5
  0b.. format != null && text != null do
    something ()
  ..1b do
    something ()
  for i : Integer = 0 .. 100 do
    something ()

I'm hoping to make it possible for all keywords to have symbolic alternatives.

 

 

Another thing I've been working on is the type system. The suffixes 'b' and 'i' appended to numbers stand for 'boolean' and 'integer'. Instead of the traditional 'float' or 'double' types, I've been pondering the use of a type called 'scalar'. The meaning of the word is unambiguous between computing and mathematics as a representation of a real number which may be a field of a vector or matrix. If I did this, it'd also make sense for Ergo to natively support vector operations. A feature like that could allow Ergo to outperform C or C++ in many situations.

 

 

EDIT:

 

Thinking about the new syntax a little bit. With consideration for the symbolized form, a better format may be:

let print for format, text as String, String do
  something

The 'as String, String' part is an optional type constraint. One benefit of this syntax is continuity. Functions are pretty much the same thing as loops. 'for <input> do <something>'

  • Like 2
Link to comment
Share on other sites

Some more things to mention. As said in the previous post, the 'let' keyword is equivalent to '\', technically making it optional. Well, in Ergo, sets are delimited by commas and may optionally be encapsulated by parentheses. So

let print format, text do
  something

is equivalent to

print (format, text) do
  something ()

This is actually a lot like the B programming language.

print (format, text) {
  something ()
}

The only differences are that Ergo doesn't use brackets and that everything is generic by default, where everything in B is a 'word' (the optimal 'int' for the device being programmed for).

 

 

One idea I was thinking about was to have Ergo bring back ANSI-C style type constraints. To the less informed, this is how C used to look...

print (format, text)
  char *format;
  char *text; {
}

What I like about this the most is the separation between the definition of the function and the 'contract'. While it may seem wordy, it's also extensible. Instead of types, you could have 'concepts'; which are a set of assertions about which attributes, operators, and functions that an object has. In this case, we are saying that 'format' must match the concept of a 'char *'. This was actually a highly anticipated template metaprogramming feature which had been planned to be introduced into the C++ standard a while back but was pulled out at the last moment because the standardization committee didn't feel it was mature enough. Before I implement it in Ergo though, I want to tinker with the syntax a bit to make it more understandable.

  • Like 1
Link to comment
Share on other sites

Working on the shorthand syntax a bit.

and     &&
be      :=
do      =>
let     \
minus   -
on      \
or      ||
plus    +
times   *
to      ..
        ::             # used for delimiting namespaces

Here's a short preview of what function declaration looks like:

System::Console::Print := argc, argv =>
  x := 5

Print := System::Console::Print

This is equivalent to

on System::Console::Print := argc, argv do
  let x be 5

let Print be System::Console::Print

For the sake of clarity, I decided not to kill off the 'on' keyword in favor of 'let'. Since I'm using the ':=' operator to assign by reference, I thought that the C++ style of 'System::Console::Print' looked more suitable than 'System.Console.Print'. For the 'do' keyword, I chose '=>' because it's used for lambda in C#. I had thought about incorporating ternary if statements like:

if      \
then    ?
else    :

So you could do:

\(x < 5)? 
  => something
:(x > 5)?
  => something
:
  => something

In place of

if x < 5 then
  do something
else if x > 5 then
  do something
else
  do something

Note that the '\' is optional; I only used it because I'm an obsessive compulsive douchbag turned on by symmetry. Anyhow, if I go this route, then I can't use the ':' symbol for 'as'. Although, if I excluded the 'as', then ':' could also be used for the 'in' keyword like:

for e : c =>
  something

This may actually be for the best considering that the traditional syntax for 'let' as 'let x be y in z' would simply be 'x := y : z'.

  • Like 1
Link to comment
Share on other sites

As seen above, I've been advocating duck-typing and a sort of parametric/ad hoc hybrid polymorphism. I think I'm going to do away with the ad hoc part and add a 'use' keyword paired with a set of types.

 

The idea being that you can do something like:

use add integer, integer

add := x, y =>
  x + y

Before I had this idea, unless you added a set of type-constraints, code would only be generated if you used it on those types. Here, I'm doing away with type-constraints and allowing the programmer to explicitly declare the use of types to generate code for. Note that you'd only need to do this to generate code for unused types, eg. If you call 'add(5, 5)' somewhere in your code, it'll see that you're using a type signature of 'integer, integer' and behave as though you've added 'use add integer, integer' to your code. For this reason, the 'use' keyword is only necessary when creating libraries; and in any other situation, simple parametric polymorphism is fine.

 

Old Notes...

 

Another thing I should mention; I had stated this before, but the ':=' operator is for assignment by reference. Assignment by value uses the '=' operator. So

x = 5i
y := x

Is equivalent to the following C code:

int x = 5;
int restrict *y = &x;

One of the benefits of this is the added safety. You can't accidentally assign a value to a pointer or reference a reference. It also means that

x := y => y + 5

is completely different from

x = y => y + 5

In that the first stub of code assigns a reference of the lambda to 'x' so you can call 'x' like a function, and the second stub assigns the value of the evaluated lambda to 'x' (x = y + 5).

 

 

Since I'm no longer using ':=' to mean 'local variable', I'll need to think of a way to handle that. That said, I don't want to just get rid of pointers altogether because they're a powerful feature. But at the same time, I don't want to make everything as complex as C and C++.

 

 

 

EDIT:

An idea I just had, 'x()' could be interpreted as, "value of x". In this sense, the '(' and ')' could be used for both arrays and functions. Then, perhaps '{ }' could be used for references. eg.

x = (1, 2, 3)
x(0)           // value is '1'   (*x)
x(1)           // value is '2'   (*(x+1))
x{0}           // pointer to 'x' (&x)
x{1}           // pointer to 'x + 1' ((&x)+1)

print(x(1))

It's like inside-out C pointers. 

Link to comment
Share on other sites

 Share

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.