[ANN] Cratera Compiler v1.0.0

classic Classic list List threaded Threaded
20 messages Options
Reply | Threaded
Open this post in threaded view
|

[ANN] Cratera Compiler v1.0.0

Soni "They/Them" L.
Git repo: https://soniex2.autistic.space/git-repos/cratera.git (yes it
says forbidden, that's just because you can't view it on a web browser.
works fine with git.)
Bittorrent:
magnet:?xt=urn:btih:4633253f7c1861af2ee30e6ca9b9d964557104b9&dn=cratera-compiler-1.0.0.tar
SHA256: 6b9ee22b8b143af445582e399d6610c571c034b18e0f7655bb33b1dbdfe347ae
cratera-compiler-1.0.0.tar
SHA512:
6156f34cc9c4af55552e2b56ce12e2fcfd498814ae11f984074b7ba5ed30fd90050c2d8a86877c5ce278a96437d1710081325bcd1b60ca32cbdd5acb288479ca
cratera-compiler-1.0.0.tar
(checksums are necessary due to bittorrent being vulnerable to SHAttered
attack)

About 2 months ago I tried to make a pure-Lua Cratera to Lua compiler.
Things didn't go very well at the time and the best I could do was
report a perceived bug with how Lua handles numeric literals (namely,
x=1print(x) not requiring a space between the 1 and the print). This
monday I had the idea to do it completely differently and rather than
reimplement the Lua parser in pure-Lua I decided to use a bit of an uh,
unorthodox method. The Lua parser parses things eagerly, using what I
believe is a hand-written recursive descent parser. However, it was much
easier to just tokenize the whole source and store the tokens in a
table, and then it's just a simple matter of lightly massaging the
source as needed. Yes, it's quite slow, but it works quite nicely!
Additionally, no debug mappings are necessary, as it does its best to
adjust line numbers!

It should support Lua 5.1, Lua 5.2, Lua 5.3 and, for the most part,
LuaJIT. That is, you should be able to pass any Lua 5.1, Lua 5.2, Lua
5.3 and LuaJIT-compatible source code through it. It might have trouble
with LuaJIT-specific extensions tho, such as "ULL" literals.

$ cat README.md
The Cratera Programming Language (and support components)
=========================================================

This repo contains the Cratera to Lua compiler, as well as support
components for the Cratera to Lua compiler, namely a pure-Lua Lua
tokenizer and a table-based parser thing.

Cratera is a language very similar to Lua, and as such most of the Lua
manual applies to it. Additionally, it supports the following syntax
sugar, called "traits":

     mytable:[mytrait].myfunction(myargument)

which is equivalent to:

     mytable[mytrait].myfunction(mytable, myargument)

This syntax sugar is similar to the "methods" syntax sugar ([Lua 5.3
§3.4.10](http://www.lua.org/manual/5.3/manual.html#3.4.10),
[Lua 5.2 §3.4.9](http://www.lua.org/manual/5.2/manual.html#3.4.9), [Lua
5.1 §2.5.8](http://www.lua.org/manual/5.1/manual.html#2.5.8)),
and, indeed, `mytable` is only evaluated once.

Why not use LPeg?
-----------------

The use of a custom parsing library boils down to two reasons:

1. LPeg can't stream or produce partial outputs. This just makes it
difficult to use for making a compiler.
2. LPeg can't process tables. It's still possible to use LPeg to parse
table-based structures, but one must serialize them beforehand, which
is... far from ideal, to say the least.

Reply | Threaded
Open this post in threaded view
|

[ANN] Cratera Compiler v1.0.1

Soni "They/Them" L.
Apologies for the debug print. v1.0.1 has been released. Nothing has
been changed other than removal of the debug prints. (Also, the magnet
link now uses opentrackr, which should speed up peer discovery.)

Bittorrent:
magnet:?xt=urn:btih:18c7918504736b9d33f7e6a994fe0bc4c94710c7&dn=cratera-compiler-1.0.1.tar&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce
SHA256: 7dbcfb41740a268e2914fa3d121ebd837d089ac335fa75d2da292d0a01041ed3
cratera-compiler-1.0.1.tar
SHA512:
0ddc5dafe8e84df9981f0656e50d10a56c683deaff829e40b14be4edf59a87bc26d2499bd0383cb70fbd00f15777305d7dab8de8723f366e1e7ba888296f1d74
cratera-compiler-1.0.1.tar

On 2019-07-31 1:06 a.m., Soni "They/Them" L. wrote:

> Git repo: https://soniex2.autistic.space/git-repos/cratera.git (yes it
> says forbidden, that's just because you can't view it on a web
> browser. works fine with git.)
> Bittorrent:
> magnet:?xt=urn:btih:4633253f7c1861af2ee30e6ca9b9d964557104b9&dn=cratera-compiler-1.0.0.tar
> SHA256:
> 6b9ee22b8b143af445582e399d6610c571c034b18e0f7655bb33b1dbdfe347ae
> cratera-compiler-1.0.0.tar
> SHA512:
> 6156f34cc9c4af55552e2b56ce12e2fcfd498814ae11f984074b7ba5ed30fd90050c2d8a86877c5ce278a96437d1710081325bcd1b60ca32cbdd5acb288479ca
> cratera-compiler-1.0.0.tar
> (checksums are necessary due to bittorrent being vulnerable to
> SHAttered attack)
>
> About 2 months ago I tried to make a pure-Lua Cratera to Lua compiler.
> Things didn't go very well at the time and the best I could do was
> report a perceived bug with how Lua handles numeric literals (namely,
> x=1print(x) not requiring a space between the 1 and the print). This
> monday I had the idea to do it completely differently and rather than
> reimplement the Lua parser in pure-Lua I decided to use a bit of an
> uh, unorthodox method. The Lua parser parses things eagerly, using
> what I believe is a hand-written recursive descent parser. However, it
> was much easier to just tokenize the whole source and store the tokens
> in a table, and then it's just a simple matter of lightly massaging
> the source as needed. Yes, it's quite slow, but it works quite nicely!
> Additionally, no debug mappings are necessary, as it does its best to
> adjust line numbers!
>
> It should support Lua 5.1, Lua 5.2, Lua 5.3 and, for the most part,
> LuaJIT. That is, you should be able to pass any Lua 5.1, Lua 5.2, Lua
> 5.3 and LuaJIT-compatible source code through it. It might have
> trouble with LuaJIT-specific extensions tho, such as "ULL" literals.
>
> $ cat README.md
> The Cratera Programming Language (and support components)
> =========================================================
>
> This repo contains the Cratera to Lua compiler, as well as support
> components for the Cratera to Lua compiler, namely a pure-Lua Lua
> tokenizer and a table-based parser thing.
>
> Cratera is a language very similar to Lua, and as such most of the Lua
> manual applies to it. Additionally, it supports the following syntax
> sugar, called "traits":
>
>     mytable:[mytrait].myfunction(myargument)
>
> which is equivalent to:
>
>     mytable[mytrait].myfunction(mytable, myargument)
>
> This syntax sugar is similar to the "methods" syntax sugar ([Lua 5.3
> §3.4.10](http://www.lua.org/manual/5.3/manual.html#3.4.10),
> [Lua 5.2 §3.4.9](http://www.lua.org/manual/5.2/manual.html#3.4.9),
> [Lua 5.1 §2.5.8](http://www.lua.org/manual/5.1/manual.html#2.5.8)),
> and, indeed, `mytable` is only evaluated once.
>
> Why not use LPeg?
> -----------------
>
> The use of a custom parsing library boils down to two reasons:
>
> 1. LPeg can't stream or produce partial outputs. This just makes it
> difficult to use for making a compiler.
> 2. LPeg can't process tables. It's still possible to use LPeg to parse
> table-based structures, but one must serialize them beforehand, which
> is... far from ideal, to say the least.


Reply | Threaded
Open this post in threaded view
|

Re: [ANN] Cratera Compiler v1.0.0

Egor Skriptunoff-2
In reply to this post by Soni "They/Them" L.
On Wed, Jul 31, 2019 at 7:07 AM Soni "They/Them" L. wrote:

Cratera is a language very similar to Lua, and as such most of the Lua
manual applies to it. Additionally, it supports the following syntax
sugar, called "traits":

     mytable:[mytrait].myfunction(myargument)

which is equivalent to:

     mytable[mytrait].myfunction(mytable, myargument)



1)
Is this feature really needed?
Please show an example.

2)
Am I able to invoke a method by its name from a variable in Cratera?
   local methodname = "close"
   file:[methodname]()

3)
Cratera's feature could be generalized further.
The following two simple rules look like a natural extension to Lua syntax:
- There could be multiple colons in a chain
- Each colon means "use current value as extra argument"
Examples:
   a:b[c].f(x)    ->  a.b[c].f(a,                        x)
   a.b:[c].f(x)   ->  a.b[c].f(   a.b,                   x)
   a.b[c]:f(x)    ->  a.b[c].f(        a.b[c],           x)
   a:b[c]:f(x)    ->  a.b[c].f(a,      a.b[c],           x)
   a.b[c].f:(x)   ->  a.b[c].f(                a.b[c].f, x)
   a:b:[c]:f:(x)  ->  a.b[c].f(a, a.b, a.b[c], a.b[c].f, x)
   c:()           ->  c(c)
It's not a monkey smile in the last line, variable "c" contains a "callable object"

 
Reply | Threaded
Open this post in threaded view
|

Re: [ANN] Cratera Compiler v1.0.0

Soni "They/Them" L.


On 2019-07-31 6:09 p.m., Egor Skriptunoff wrote:

> On Wed, Jul 31, 2019 at 7:07 AM Soni "They/Them" L. wrote:
>
>
>     Cratera is a language very similar to Lua, and as such most of the
>     Lua
>     manual applies to it. Additionally, it supports the following syntax
>     sugar, called "traits":
>
>          mytable:[mytrait].myfunction(myargument)
>
>     which is equivalent to:
>
>          mytable[mytrait].myfunction(mytable, myargument)
>
>
>
> 1)
> Is this feature really needed?
> Please show an example.

This is heavily inspired by Rust traits. In Rust, you can do something like:

fn main() {
     let v = <Vec<_> as From<&str>>::from("hello, world!");
     println!("{:?}", v);
}

(However, in this case you're using "<Type as Trait>::function()". I
don't know why Rust doesn't support "<binding as Trait>.method()" but I
had to adapt this whole thing.)

You can also call methods from traits using the object. I took these
ideas and adapted them for a dynamic programming language like Lua.

It's just a different way of doing OOP. Since Lua prides itself on
having many ways of doing OOP as long as you implement them yourself
(you can have classes in Lua, but you can also have prototype-based OOP,
but you can also have even other forms of OOP. some of those are more or
less efficient than others.), I decided to implement trait-based OOP as
efficiently as I knew how - with a language extension.

You can see a small example in tests.cratera (see below). However, keep
in mind it was my first cratera example and I hadn't fully worked out
how you should be doing things in cratera - I wouldn't do it exactly
like that nowadays. But it does capture the basic idea.

--------
-- example from tests.cratera

entity = {}

inventory = {get=false, set=false, size=false}
inventory.new=function(size)
   local t = {size=function() return size end}
   function t.set(e, i, o)
     if i <= 0 or i > e:[inventory].size() then error() end
     e[inventory][i] = o
   end
   function t.get(e, i)
     if i <= 0 or i > e:[inventory].size() then error() end
     return e[inventory][i]
   end
   return t
end
inventory.of=function(e) -- helper for passing standalone inventories around
   return {get=function(...)return e:[inventory].get(...)end,
set=function(...)return e:[inventory].set(...)end,
size=function(...)return e:[inventory].size(...)end}
end

entity[inventory] = inventory.new(5)

entity:[inventory].set(1, "Hello World!")

print(entity:[inventory].get(1))

for i=1, entity:[inventory].size() do
   print(i, entity:[inventory].get(i))
end

local myinv = inventory.of(entity)

for i=1, myinv.size() do
   print("wrapped", i, myinv.get(i))
end
--------

>
> 2)
> Am I able to invoke a method by its name from a variable in Cratera?
>    local methodname = "close"
>    file:[methodname]()
>

No. While that was implemented in the Cratera patchset, it's not
implemented in Cratera Compiler. Cratera is implemented with a function
by Cratera Compiler, and to support this syntax I'd need another
function. Given the usefulness of this feature for Cratera programs in
general (read: nonexistent), I decided against implementing it. It's
recommended that you wrap the Lua stdlib to use Cratera OOP if you're
writing a Cratera program. Then you'll be able to use:

file:[io].read()
file:[close].close()

> 3)
> Cratera's feature could be generalized further.
> The following two simple rules look like a natural extension to Lua
> syntax:
> - There could be multiple colons in a chain
> - Each colon means "use current value as extra argument"
> Examples:
>  a:b[c].f(x)    ->  a.b[c].f(a,  x)
>    a.b:[c].f(x)   ->  a.b[c].f(   a.b,   x)
>    a.b[c]:f(x)    ->  a.b[c].f(        a.b[c],   x)
>    a:b[c]:f(x)    ->  a.b[c].f(a,      a.b[c],   x)
>    a.b[c].f:(x)   ->  a.b[c].f(  a.b[c].f, x)
>    a:b:[c]:f:(x)  ->  a.b[c].f(a, a.b, a.b[c], a.b[c].f, x)
>    c:()           ->  c(c)
> It's not a monkey smile in the last line, variable "c" contains a
> "callable object"
>

Lua has a nesting limit of 200 or so. a:[b].c() is converted into
(CRATERA_FN)(a, b, "c"), which increases the nesting of a and b by one.
A normal Lua function chain can go on forever, but Cratera is limited to
less than 200 occurrences of traits per statement. If you could use
arbitrarily many colons in a chain, that limit would be even lower even
quicker.

Reply | Threaded
Open this post in threaded view
|

Re: [ANN] Cratera Compiler v1.0.0

Egor Skriptunoff-2
On Thu, Aug 1, 2019 at 4:53 PM Soni "They/Them" L. wrote:
-- example from tests.cratera



From your example it's not clear why this feature is better than usual OOP syntax.
Actually you DO NOT need reference to 'e' inside trait's functions (get, set, size).
You can easily rewrite your program in traditional way by replacing
e:[inventory].size()
with
e[inventory]:size()

Could you provide an example to show benefits of traits?
What kinds of tasks traits solve better than conventional 'objects-and-methods' approach?

Reply | Threaded
Open this post in threaded view
|

Re: [ANN] Cratera Compiler v1.0.0

Soni "They/Them" L.


On 2019-08-01 6:05 p.m., Egor Skriptunoff wrote:

> On Thu, Aug 1, 2019 at 4:53 PM Soni "They/Them" L. wrote:
>
>     -- example from tests.cratera
>
>
>
> From your example it's not clear why this feature is better than usual
> OOP syntax.
> Actually you DO NOT need reference to 'e' inside trait's functions
> (get, set, size).
> You can easily rewrite your program in traditional way by replacing
> e:[inventory].size()
> with
> e[inventory]:size()
>
> Could you provide an example to show benefits of traits?
> What kinds of tasks traits solve better than conventional
> 'objects-and-methods' approach?
>

Inventory may not be self-contained. Inventory could reference another
trait.

I recommend reading https://en.wikipedia.org/wiki/Entity_component_system

Reply | Threaded
Open this post in threaded view
|

Re: [ANN] Cratera Compiler v1.0.0

Soni "They/Them" L.


On 2019-08-01 6:13 p.m., Soni "They/Them" L. wrote:

>
>
> On 2019-08-01 6:05 p.m., Egor Skriptunoff wrote:
>> On Thu, Aug 1, 2019 at 4:53 PM Soni "They/Them" L. wrote:
>>
>>     -- example from tests.cratera
>>
>>
>>
>> From your example it's not clear why this feature is better than
>> usual OOP syntax.
>> Actually you DO NOT need reference to 'e' inside trait's functions
>> (get, set, size).
>> You can easily rewrite your program in traditional way by replacing
>> e:[inventory].size()
>> with
>> e[inventory]:size()
>>
>> Could you provide an example to show benefits of traits?
>> What kinds of tasks traits solve better than conventional
>> 'objects-and-methods' approach?
>>
>
> Inventory may not be self-contained. Inventory could reference another
> trait.
>
> I recommend reading https://en.wikipedia.org/wiki/Entity_component_system

(Well, besides reading that, I recommend actually checking out Rust and
using it for a while. You'll see what I mean.)

Reply | Threaded
Open this post in threaded view
|

Re: [ANN] Cratera Compiler v1.0.0

Egor Skriptunoff-2
In reply to this post by Soni "They/Them" L.
On Fri, Aug 2, 2019 at 12:14 AM Soni "They/Them" L. wrote:
On 2019-08-01 6:05 p.m., Egor Skriptunoff wrote:
> Could you provide an example to show benefits of traits?
> What kinds of tasks traits solve better than conventional
> 'objects-and-methods' approach?
>

Inventory may not be self-contained. Inventory could reference another
trait.

I recommend reading https://en.wikipedia.org/wiki/Entity_component_system


I'm aware of entity-component-system approach.
And this is my understanding of how it should be implemented in Lua:
And I don't see how traits could help implementing it.
The best way to implement ECS is "don't use OOP at all".

Reply | Threaded
Open this post in threaded view
|

Re: [ANN] Cratera Compiler v1.0.0

Egor Skriptunoff-2
In reply to this post by Soni "They/Them" L.
On Fri, Aug 2, 2019 at 12:19 AM Soni "They/Them" L. wrote:
> On 2019-08-01 6:05 p.m., Egor Skriptunoff wrote:
>> Could you provide an example to show benefits of traits?
>> What kinds of tasks traits solve better than conventional
>> 'objects-and-methods' approach?
>>
>
> I recommend reading https://en.wikipedia.org/wiki/Entity_component_system

(Well, besides reading that, I recommend actually checking out Rust and
using it for a while. You'll see what I mean.)


Could you just show a simple example from real-life programming where using traits is better than not using them?
Such example should be understandable by any Lua programmer (without Rust knowledge).
If you can't provide such example then why Lua users might want to use Cratera?
Reply | Threaded
Open this post in threaded view
|

Re: [ANN] Cratera Compiler v1.0.0

Soni "They/Them" L.


On 2019-08-02 2:47 a.m., Egor Skriptunoff wrote:

> On Fri, Aug 2, 2019 at 12:19 AM Soni "They/Them" L. wrote:
>
>     > On 2019-08-01 6:05 p.m., Egor Skriptunoff wrote:
>     >> Could you provide an example to show benefits of traits?
>     >> What kinds of tasks traits solve better than conventional
>     >> 'objects-and-methods' approach?
>     >>
>     >
>     > I recommend reading
>     https://en.wikipedia.org/wiki/Entity_component_system
>
>     (Well, besides reading that, I recommend actually checking out
>     Rust and
>     using it for a while. You'll see what I mean.)
>
>
> Could you just show a simple example from real-life programming where
> using traits is better than not using them?
> Such example should be understandable by any Lua programmer (without
> Rust knowledge).
> If you can't provide such example then why Lua users might want to use
> Cratera?

Let's say you have a trait Foo, with perhaps some methods (or all
methods) depending on the object also being a Bar.

With inheritance, your Foo would be a Bar, probably, which makes it an
all-or-nothing situation.

Take a look at e.g.
https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.rev

Additionally, names don't matter. You can have both Foo and Bar have a
reverse method and it works fine. (this is also the case in Rust)

Reply | Threaded
Open this post in threaded view
|

Re: [ANN] Cratera Compiler v1.0.0

Coda Highland
In reply to this post by Egor Skriptunoff-2


On Fri, Aug 2, 2019 at 12:47 AM Egor Skriptunoff <[hidden email]> wrote:
On Fri, Aug 2, 2019 at 12:14 AM Soni "They/Them" L. wrote:
On 2019-08-01 6:05 p.m., Egor Skriptunoff wrote:
> Could you provide an example to show benefits of traits?
> What kinds of tasks traits solve better than conventional
> 'objects-and-methods' approach?
>

Inventory may not be self-contained. Inventory could reference another
trait.

I recommend reading https://en.wikipedia.org/wiki/Entity_component_system


I'm aware of entity-component-system approach.
And this is my understanding of how it should be implemented in Lua:
And I don't see how traits could help implementing it.
The best way to implement ECS is "don't use OOP at all".

I'm not quite sure how you come to that conclusion. ECS is an inherently object-oriented technique, and the ID-based technique exists specifically to enable an object-oriented technique to be implemented in languages that don't make it easy to write object-oriented code. In your SO post, you've basically reinvented pointers, and you've scattered the contents of things around instead of keeping related information together.

Traits are a perfect fit for ECS. Each entity is an object that has traits. Each component type is a trait, which carries with it the definition of what the trait is, what data it contains, and how to interface with it. An object can possess multiple traits, and their encapsulation means that they don't interfere with each other (which is an improvement over interfaces or multiple inheritance). Any object possessing a trait can be used interchangeably any context that expects that trait (that is, traits enable polymorphism).

Traits also make it easier to define prefabs, that is, an entity with preselected traits and properties that can be created on demand. Creating a new monster using the syntax in your post means picking out a new, unused ID, finding out what traits exist on the monster being created, and making new copies of those traits. With traits, you can just say "monster = Monster.new()" and be done with it.

As a Unity3D user, I have thousands of lines of real-world code demonstrating how to use object-oriented ECS. I won't necessarily say that my particular way of doing things is optimal, because I have a strong C++ background and I'm mixing components, interfaces, and normal OO inheritance all in one codebase, and Unity3D is C# so it doesn't have a native trait syntax so it's not as elegant as it COULD be, but it's a pretty clear illustration that it's valuable.

/s/ Adam
Reply | Threaded
Open this post in threaded view
|

Re: [ANN] Cratera Compiler v1.0.0

Soni "They/Them" L.


On 2019-08-02 11:17 a.m., Coda Highland wrote:

>
>
> On Fri, Aug 2, 2019 at 12:47 AM Egor Skriptunoff
> <[hidden email] <mailto:[hidden email]>> wrote:
>
>     On Fri, Aug 2, 2019 at 12:14 AM Soni "They/Them" L. wrote:
>
>         On 2019-08-01 6:05 p.m., Egor Skriptunoff wrote:
>         > Could you provide an example to show benefits of traits?
>         > What kinds of tasks traits solve better than conventional
>         > 'objects-and-methods' approach?
>         >
>
>         Inventory may not be self-contained. Inventory could reference
>         another
>         trait.
>
>         I recommend reading
>         https://en.wikipedia.org/wiki/Entity_component_system
>
>
>     I'm aware of entity-component-system approach.
>     And this is my understanding of how it should be implemented in Lua:
>     https://stackoverflow.com/a/42660932/1847592
>     And I don't see how traits could help implementing it.
>     The best way to implement ECS is "don't use OOP at all".
>
>
> I'm not quite sure how you come to that conclusion. ECS is an
> inherently object-oriented technique, and the ID-based technique
> exists specifically to enable an object-oriented technique to be
> implemented in languages that don't make it easy to write
> object-oriented code. In your SO post, you've basically reinvented
> pointers, and you've scattered the contents of things around instead
> of keeping related information together.
>
> Traits are a perfect fit for ECS. Each entity is an object that has
> traits. Each component type is a trait, which carries with it the
> definition of what the trait is, what data it contains, and how to
> interface with it. An object can possess multiple traits, and their
> encapsulation means that they don't interfere with each other (which
> is an improvement over interfaces or multiple inheritance). Any object
> possessing a trait can be used interchangeably any context that
> expects that trait (that is, traits enable polymorphism).
>
> Traits also make it easier to define prefabs, that is, an entity with
> preselected traits and properties that can be created on demand.
> Creating a new monster using the syntax in your post means picking out
> a new, unused ID, finding out what traits exist on the monster being
> created, and making new copies of those traits. With traits, you can
> just say "monster = Monster.new()" and be done with it.
>
> As a Unity3D user, I have thousands of lines of real-world code
> demonstrating how to use object-oriented ECS. I won't necessarily say
> that my particular way of doing things is optimal, because I have a
> strong C++ background and I'm mixing components, interfaces, and
> normal OO inheritance all in one codebase, and Unity3D is C# so it
> doesn't have a native trait syntax so it's not as elegant as it COULD
> be, but it's a pretty clear illustration that it's valuable.
>
> /s/ Adam

thanks for the help, I'm pretty bad at explaining things sometimes

Reply | Threaded
Open this post in threaded view
|

Re: [ANN] Cratera Compiler v1.0.0

Coda Highland
In reply to this post by Egor Skriptunoff-2


On Fri, Aug 2, 2019 at 2:33 AM Egor Skriptunoff <[hidden email]> wrote:
On Fri, Aug 2, 2019 at 12:19 AM Soni "They/Them" L. wrote:
> On 2019-08-01 6:05 p.m., Egor Skriptunoff wrote:
>> Could you provide an example to show benefits of traits?
>> What kinds of tasks traits solve better than conventional
>> 'objects-and-methods' approach?
>>
>
> I recommend reading https://en.wikipedia.org/wiki/Entity_component_system

(Well, besides reading that, I recommend actually checking out Rust and
using it for a while. You'll see what I mean.)


Could you just show a simple example from real-life programming where using traits is better than not using them?
Such example should be understandable by any Lua programmer (without Rust knowledge).
If you can't provide such example then why Lua users might want to use Cratera?

If your own example on SO satisfies you, then let me rewrite it using standard Lua syntax (not Cratera traits because I'm not familiar enough):

all_entities = {]

function HealthRegen(self, entity, dt)
  self.current_hp = math.min(
    self.max_hp,
    self.current_hp + self.max_hp * dt / (5*60)
  )
end

traits = {
  Health = function(hp) return { max_hp = hp, current_hp = hp, update = HealthRegen },
  Velocity = function(max) return { max_speed = max, speed_x = 0, speed_y = 0 }
}

function Instantiate(type)
  local obj = {}
  for trait, param in pairs(type) do
    obj[trait] = traits[trait](param)
  end
  table.insert(all_entities, obj)
  return obj
end

Player = { Health = 42, Velocity = 1.0 }
Monster = { Health = 100, Velocity = 2.5 }

player = Instantiate(Player)
monster = Instantiate(Monster)

function update(dt)
  for _, entity in pairs(all_entities) do
    if entity.update then entity:update(dt) end
    for _, trait in pairs(entity) do
      if trait.update then trait:update(entity, dt) end
    end
  end
end
Reply | Threaded
Open this post in threaded view
|

Re: [ANN] Cratera Compiler v1.0.0

Coda Highland


On Fri, Aug 2, 2019 at 9:41 AM Coda Highland <[hidden email]> wrote:


On Fri, Aug 2, 2019 at 2:33 AM Egor Skriptunoff <[hidden email]> wrote:
On Fri, Aug 2, 2019 at 12:19 AM Soni "They/Them" L. wrote:
> On 2019-08-01 6:05 p.m., Egor Skriptunoff wrote:
>> Could you provide an example to show benefits of traits?
>> What kinds of tasks traits solve better than conventional
>> 'objects-and-methods' approach?
>>
>
> I recommend reading https://en.wikipedia.org/wiki/Entity_component_system

(Well, besides reading that, I recommend actually checking out Rust and
using it for a while. You'll see what I mean.)


Could you just show a simple example from real-life programming where using traits is better than not using them?
Such example should be understandable by any Lua programmer (without Rust knowledge).
If you can't provide such example then why Lua users might want to use Cratera?

If your own example on SO satisfies you, then let me rewrite it using standard Lua syntax (not Cratera traits because I'm not familiar enough):

all_entities = {]

function HealthRegen(self, entity, dt)
  self.current_hp = math.min(
    self.max_hp,
    self.current_hp + self.max_hp * dt / (5*60)
  )
end

traits = {
  Health = function(hp) return { max_hp = hp, current_hp = hp, update = HealthRegen },
  Velocity = function(max) return { max_speed = max, speed_x = 0, speed_y = 0 }
}

function Instantiate(type)
  local obj = {}
  for trait, param in pairs(type) do
    obj[trait] = traits[trait](param)
  end
  table.insert(all_entities, obj)
  return obj
end

Player = { Health = 42, Velocity = 1.0 }
Monster = { Health = 100, Velocity = 2.5 }

player = Instantiate(Player)
monster = Instantiate(Monster)

function update(dt)
  for _, entity in pairs(all_entities) do
    if entity.update then entity:update(dt) end
    for _, trait in pairs(entity) do
      if trait.update then trait:update(entity, dt) end
    end
  end
end

Forgot to mention...

function HealthDamage(self, entity, amount)
  self.current_hp = self.current_hp - amount
  if self.current_hp <= 0 then
    self.current_hp = 0
    if entity.OnDeath then entity:OnDeath() end
  end
end

monster.Health:damage(monster, 10)

This isn't quite compatible with how Cratera's syntax does it, because at this point I would want to write monster:[Health]:damage(10) and I can't do that. But it should still get the point across.

/s/ Adam


Reply | Threaded
Open this post in threaded view
|

Re: [ANN] Cratera Compiler v1.0.0

Soni "They/Them" L.


On 2019-08-02 11:46 a.m., Coda Highland wrote:

>
>
> On Fri, Aug 2, 2019 at 9:41 AM Coda Highland <[hidden email]
> <mailto:[hidden email]>> wrote:
>
>
>
>     On Fri, Aug 2, 2019 at 2:33 AM Egor Skriptunoff
>     <[hidden email] <mailto:[hidden email]>>
>     wrote:
>
>         On Fri, Aug 2, 2019 at 12:19 AM Soni "They/Them" L. wrote:
>
>             > On 2019-08-01 6:05 p.m., Egor Skriptunoff wrote:
>             >> Could you provide an example to show benefits of traits?
>             >> What kinds of tasks traits solve better than conventional
>             >> 'objects-and-methods' approach?
>             >>
>             >
>             > I recommend reading
>             https://en.wikipedia.org/wiki/Entity_component_system
>
>             (Well, besides reading that, I recommend actually checking
>             out Rust and
>             using it for a while. You'll see what I mean.)
>
>
>         Could you just show a simple example from real-life
>         programming where using traits is better than not using them?
>         Such example should be understandable by any Lua programmer
>         (without Rust knowledge).
>         If you can't provide such example then why Lua users might
>         want to use Cratera?
>
>
>     If your own example on SO satisfies you, then let me rewrite it
>     using standard Lua syntax (not Cratera traits because I'm not
>     familiar enough):
>
>     all_entities = {]
>
>     function HealthRegen(self, entity, dt)
>       self.current_hp = math.min(
>         self.max_hp,
>         self.current_hp + self.max_hp * dt / (5*60)
>       )
>     end
>
>     traits = {
>       Health = function(hp) return { max_hp = hp, current_hp = hp,
>     update = HealthRegen },
>       Velocity = function(max) return { max_speed = max, speed_x = 0,
>     speed_y = 0 }
>     }
>
>     function Instantiate(type)
>       local obj = {}
>       for trait, param in pairs(type) do
>         obj[trait] = traits[trait](param)
>       end
>       table.insert(all_entities, obj)
>       return obj
>     end
>
>     Player = { Health = 42, Velocity = 1.0 }
>     Monster = { Health = 100, Velocity = 2.5 }
>
>     player = Instantiate(Player)
>     monster = Instantiate(Monster)
>
>     function update(dt)
>       for _, entity in pairs(all_entities) do
>         if entity.update then entity:update(dt) end
>         for _, trait in pairs(entity) do
>           if trait.update then trait:update(entity, dt) end
>         end
>       end
>     end
>
>
> Forgot to mention...
>
> function HealthDamage(self, entity, amount)
>   self.current_hp = self.current_hp - amount
>   if self.current_hp <= 0 then
>     self.current_hp = 0
>     if entity.OnDeath then entity:OnDeath() end
>   end
> end
>
> monster.Health:damage(monster, 10)
>
> This isn't quite compatible with how Cratera's syntax does it, because
> at this point I would want to write monster:[Health]:damage(10) and I
> can't do that. But it should still get the point across.
>
> /s/ Adam
>
>

you don't need to write monster:[Health]:damage(10) because you can just
write monster[Health] inside damage. e.g.:

function Health.damage(entity, amount)
     local healthComponent = entity[Health]
     -- ...
end

entity = {[Health] = Health.new()}

entity:[Health].damage(amount)

Reply | Threaded
Open this post in threaded view
|

Re: [ANN] Cratera Compiler v1.0.0

Coda Highland


On Fri, Aug 2, 2019 at 9:52 AM Soni "They/Them" L. <[hidden email]> wrote:


On 2019-08-02 11:46 a.m., Coda Highland wrote:
>
>
> On Fri, Aug 2, 2019 at 9:41 AM Coda Highland <[hidden email]
> <mailto:[hidden email]>> wrote:
>
>
>
>     On Fri, Aug 2, 2019 at 2:33 AM Egor Skriptunoff
>     <[hidden email] <mailto:[hidden email]>>
>     wrote:
>
>         On Fri, Aug 2, 2019 at 12:19 AM Soni "They/Them" L. wrote:
>
>             > On 2019-08-01 6:05 p.m., Egor Skriptunoff wrote:
>             >> Could you provide an example to show benefits of traits?
>             >> What kinds of tasks traits solve better than conventional
>             >> 'objects-and-methods' approach?
>             >>
>             >
>             > I recommend reading
>             https://en.wikipedia.org/wiki/Entity_component_system
>
>             (Well, besides reading that, I recommend actually checking
>             out Rust and
>             using it for a while. You'll see what I mean.)
>
>
>         Could you just show a simple example from real-life
>         programming where using traits is better than not using them?
>         Such example should be understandable by any Lua programmer
>         (without Rust knowledge).
>         If you can't provide such example then why Lua users might
>         want to use Cratera?
>
>
>     If your own example on SO satisfies you, then let me rewrite it
>     using standard Lua syntax (not Cratera traits because I'm not
>     familiar enough):
>
>     all_entities = {]
>
>     function HealthRegen(self, entity, dt)
>       self.current_hp = math.min(
>         self.max_hp,
>         self.current_hp + self.max_hp * dt / (5*60)
>       )
>     end
>
>     traits = {
>       Health = function(hp) return { max_hp = hp, current_hp = hp,
>     update = HealthRegen },
>       Velocity = function(max) return { max_speed = max, speed_x = 0,
>     speed_y = 0 }
>     }
>
>     function Instantiate(type)
>       local obj = {}
>       for trait, param in pairs(type) do
>         obj[trait] = traits[trait](param)
>       end
>       table.insert(all_entities, obj)
>       return obj
>     end
>
>     Player = { Health = 42, Velocity = 1.0 }
>     Monster = { Health = 100, Velocity = 2.5 }
>
>     player = Instantiate(Player)
>     monster = Instantiate(Monster)
>
>     function update(dt)
>       for _, entity in pairs(all_entities) do
>         if entity.update then entity:update(dt) end
>         for _, trait in pairs(entity) do
>           if trait.update then trait:update(entity, dt) end
>         end
>       end
>     end
>
>
> Forgot to mention...
>
> function HealthDamage(self, entity, amount)
>   self.current_hp = self.current_hp - amount
>   if self.current_hp <= 0 then
>     self.current_hp = 0
>     if entity.OnDeath then entity:OnDeath() end
>   end
> end
>
> monster.Health:damage(monster, 10)
>
> This isn't quite compatible with how Cratera's syntax does it, because
> at this point I would want to write monster:[Health]:damage(10) and I
> can't do that. But it should still get the point across.
>
> /s/ Adam
>
>

you don't need to write monster:[Health]:damage(10) because you can just
write monster[Health] inside damage. e.g.:

function Health.damage(entity, amount)
     local healthComponent = entity[Health]
     -- ...
end

entity = {[Health] = Health.new()}

entity:[Health].damage(amount)


I realized that while I was writing the message but the quick-and-dirty ECS scheme I had cobbled together there wasn't written with the Cratera style of doing things in mind and it was too late to amend it. Thanks for following up with a better description.

/s/ Adam
Reply | Threaded
Open this post in threaded view
|

Re: [ANN] Cratera Compiler v1.0.0

Egor Skriptunoff-2
In reply to this post by Coda Highland
On Fri, Aug 2, 2019 at 5:18 PM Coda Highland wrote:
ECS is an inherently object-oriented technique,

Apparently you don't understand what ECS is.

 
the ID-based technique exists specifically to enable an object-oriented technique to be implemented in languages that don't make it easy to write object-oriented code.

No.
ECS is not an "OOP for languages that don't have native OOP support"

 
you've scattered the contents of things around instead of keeping related information together.

Yes, from OOP's point of view ECS looks like "OMG! All the things are scattered!"
The key difference is: OOP is class-centric, ECS is "system"-centric (a "system" is a code which doesn't belong to any class).
Probably you have been programming OOP for too long, so that you have failed to unstick your brains from OOP to make a mental jump to ECS approach.
You didn't catch the main idea of ECS: all logic is being done by "systems" simultaneously for all objects,
that's why we "scatter" objects' properties instead of storing them inside objects.

This is the code you have written in another post, it's pure OOP objects-centric code:
>     function update(dt)
>       for _, entity in pairs(all_entities) do
>         if entity.update then entity:update(dt) end
>         for _, trait in pairs(entity) do
>           if trait.update then trait:update(entity, dt) end
>         end
>       end
>     end
The code organized in such way that it doesn't have a possibility to efficiently implement an interaction between objects (for example, between a bullet and a monster).
In ECS such interactions would be implemented with a separate "system".

And a "trait" in your code is just an a collection of initialization functions
(yes, the only thing OOP could do well enough is to create and destroy objects, all the other logic is beyond OOP)

Sorry, but what you call "object-oriented ECS" is not an ECS ;-)

Reply | Threaded
Open this post in threaded view
|

Re: [ANN] Cratera Compiler v1.0.0

Egor Skriptunoff-2
In reply to this post by Soni "They/Them" L.
On Fri, Aug 2, 2019 at 12:14 AM Soni "They/Them" L. wrote:
Inventory may not be self-contained. Inventory could reference another
trait.



AFAIU "trait" in Rust is the same as "interface" in other languages.
You must implement interface for a class to be able to use it with objects of this class.
In other words, a trait have to be "rooted" into a class.
That's why I don't understand what do you mean in "Inventory may not be self-contained".
Please give a non-trivial example about "a trait could reference another trait" (in Cratera, not in Rust).

My intention was to understand whether the Cratera-like syntax "a:b.c(x)" would bring additional expressiveness to Lua or not (I hope it would, and I guess you can show an example).
But there are 10 replies posted already in this thread, and I still can't imagine a real-life use case where traits could lead to a better code (more readable/compact/etc).
Is traits really just a Rust-nostalgic feature (as you've said: "It's just a different way of doing OOP"), which actually doesn't add new power to Lua?

Reply | Threaded
Open this post in threaded view
|

Re: [ANN] Cratera Compiler v1.0.0

Soni "They/Them" L.


On 2019-08-07 8:53 p.m., Egor Skriptunoff wrote:

> On Fri, Aug 2, 2019 at 12:14 AM Soni "They/Them" L. wrote:
>
>     Inventory may not be self-contained. Inventory could reference
>     another
>     trait.
>
>
>
> AFAIU "trait" in Rust is the same as "interface" in other languages.
> You must implement interface for a class to be able to use it with
> objects of this class.
> In other words, a trait have to be "rooted" into a class.
> That's why I don't understand what do you mean in "Inventory may not
> be self-contained".
> Please give a non-trivial example about "a trait could reference
> another trait" (in Cratera, not in Rust).
>
> My intention was to understand whether the Cratera-like syntax
> "a:b.c(x)" would bring additional expressiveness to Lua or not (I hope
> it would, and I guess you can show an example).
> But there are 10 replies posted already in this thread, and I still
> can't imagine a real-life use case where traits could lead to a better
> code (more readable/compact/etc).
> Is traits really just a Rust-nostalgic feature (as you've said: "It's
> just a different way of doing OOP"), which actually doesn't add new
> power to Lua?
>

Lua has many ways of doing OOP, Cratera adds a new one on top.

With traits, one can do a "type-system ECS", that gets compiled to
extremely efficient runtime code. However, it requires recompiling and
doesn't exist (at all) at runtime, which is a bit different from what an
ECS would do.

Cratera is my attempt to bring that "type-system ECS" to the runtime.
The basic idea is that many of the patterns you'd use in Rust you should
also be able to use in Cratera.

You can also, ofc, inject your own traits into existing objects. You
can't do that with "OOP". But you can with Cratera. Which means it's not
strictly OOP.

(Rust *also* has support for "inject your own traits", sort of. It's a
bit limited, as you can't inject not-your-own-crate's traits into
not-your-own-crate's types, but it does exist. Sometimes you have to
wrap an object in another due to those limitations, which is something
you don't in Cratera. This limitation mainly has to do with traits being
a type property in Rust, whereas in Cratera they're more of an object
property.)

ECS is all about composition, and so are traits. This may be the only
similarity between them, but I'm more inclined to believe there are others.

Reply | Threaded
Open this post in threaded view
|

Re: [ANN] Cratera Compiler v1.0.0

Coda Highland
In reply to this post by Egor Skriptunoff-2
On Wed, Aug 7, 2019 at 6:48 PM Egor Skriptunoff <[hidden email]> wrote:
On Fri, Aug 2, 2019 at 5:18 PM Coda Highland wrote:
ECS is an inherently object-oriented technique,

Apparently you don't understand what ECS is.

No, I do. ECS is a lot broader than the SPECIFIC implementation style you're using. It's a paradigm, a way of organizing code and data.
 
the ID-based technique exists specifically to enable an object-oriented technique to be implemented in languages that don't make it easy to write object-oriented code.

No.
ECS is not an "OOP for languages that don't have native OOP support"

I never said it was. I said that using IDs as a way of linking data structures together was. You can do OOP in non-OOP languages without using ECS.

you've scattered the contents of things around instead of keeping related information together.

Yes, from OOP's point of view ECS looks like "OMG! All the things are scattered!"
The key difference is: OOP is class-centric, ECS is "system"-centric (a "system" is a code which doesn't belong to any class).
Probably you have been programming OOP for too long, so that you have failed to unstick your brains from OOP to make a mental jump to ECS approach.
You didn't catch the main idea of ECS: all logic is being done by "systems" simultaneously for all objects,
that's why we "scatter" objects' properties instead of storing them inside objects.

You're attributing a line of reasoning to me that I don't actually believe, and I believe that stems from a misunderstanding -- but it's not me misunderstanding ECS, but rather you misunderstanding OOP. If you hear OOP and you can't think anything but "Java" (or "C++" or "C#", whatever) then you don't know what OOP is.

Object-oriented programming is not about classes. It's about objects. It's about having "things" that keep related data together and have some innate concept of how they're supposed to behave. Classes are a common way of giving a shared identity to a group of related objects, but they're not required. (For example, Javascript didn't get classes until 2015.)

The definition of a "system" is code that runs continuously and operates on all entities that bear specified components, and a component is a data structure containing a fixed set of predefined properties. That means that, by the above definition of an object, the combination of a component and the systems that operate on it comprise an object.
 
This is the code you have written in another post, it's pure OOP objects-centric code:
>     function update(dt)
>       for _, entity in pairs(all_entities) do
>         if entity.update then entity:update(dt) end
>         for _, trait in pairs(entity) do
>           if trait.update then trait:update(entity, dt) end
>         end
>       end
>     end
The code organized in such way that it doesn't have a possibility to efficiently implement an interaction between objects (for example, between a bullet and a monster).
In ECS such interactions would be implemented with a separate "system".

That's not true at all. I may have cobbled together the Lua example in a hurry, but I use an equivalent construction in my own code all the time. The bullet and the monster both have a trait (that is, a component) that describes their positions in space. A single piece of code (that is, a system) can handle every entity that's capable of being damaged by a bullet (which itself is determined by a trait/component).

I am taking advantage of an object-oriented style of programming in that loop, that's true. I never denied that. As an implementation detail for this toy example, I chose to implement systems as traits that possess an "update" method. This lets me run all of the systems with a single loop. It was convenient. In practice, I usually end up with a few different traits that systems can choose to use in order to have more fine-grained control over things like execution order.

I probably shouldn't have put an update method on the entity itself. That *is* an extension outside of the ECS formalism, and it muddies the discussion to have it there. That was a mistake on my part and I admit to it. (I don't even HAVE an update method in that position in my production code. I create a new component and attach it to the entity even if I'm only using it once.)

The part I'm actually NOT sure about is, in the canonical description of ECS, how you efficiently implement having different types of monsters react DIFFERENTLY to bullets. With traits, it's trivial: you just write another thing that provides the same trait and that has different code behind the interface. Are you supposed to switch on the object type in the is-affected-by-bullets system code? Do you have a separate system for each monster type, and you rely on each of those systems to detect that there's been a bullet interaction? Do you store an ID that's used to look up another system that provides the relevant behavior? I can grok each of these possibilities, and they'd all work (although the first one is DISGUSTING and I sincerely hope it's not the traditional answer) but I don't know what's the most common way to do it in the implementations you've worked with.
 
And a "trait" in your code is just an a collection of initialization functions
(yes, the only thing OOP could do well enough is to create and destroy objects, all the other logic is beyond OOP)

That's because it's just a toy example. :P I didn't want to implement some big flashy full-featured system with a varied set of behaviors just to prove a point.

That said, no. The initialization function creates an instance of a trait, but what makes it a trait is that every instance returned by the initialization function exposes a consistent interface, which I can access using the trait type. I built it this way just because it's Lua and that's the most straightforward way I know of to do it in Lua syntax.
 
Sorry, but what you call "object-oriented ECS" is not an ECS ;-)

It doesn't follow the canonical design of the original implementation, but that doesn't make it something that isn't ECS anymore. It still fits all of the core ideals of what ECS is supposed to do. And conversely, something following the canonical ECS design doesn't mean it's not OOP. It's just a particular way of looking at OOP. And, indeed, it's a very GOOD way to look at it.

/s/ Adam