Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'release/v0.1.0' into develop
- Loading branch information
Showing
52 changed files
with
808 additions
and
1,617 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,37 +1,95 @@ | ||
Espresso is a minimalistic prototype-based Object-Oriented programming language that compiles to JavaScript. | ||
It's main goal is to be as expressive and flexible as possible while keeping it's core small, simple and consistent. | ||
Espresso makes a number of choices that aren't common in most other OO-languages, for example, classes are replaced with prototypes, variables with slots and statements with expressions. | ||
# Espresso | ||
|
||
More documentation will come as it get's closer to a first release, but in the meantime here's a basic example: | ||
Espresson is an experimental dynamic, prototype-based, object-oriented programming language that compiles down to JavaScript. | ||
|
||
``` | ||
// traits is just a predefined object for namespacing Trait objects | ||
While it is object-oriented it's still very different from most modern languages, for instance you won't find any classes or variables, instead there are only objects and slots, even lexical scope is implemented entirely in terms of language primitives. | ||
|
||
traits Point = Object clone | ||
traits Point print := "#{type} => x: #{x}, y: #{y}" println | ||
The way you communicate with objects in Espresso is by sending messages of which there are three kinds: unary, binary and keyword. | ||
|
||
Point = traits Point clone: { | ||
self x = 23 | ||
self y = 12 | ||
} | ||
Espresso also has, although currently very limited, support for predicate dispatch. | ||
|
||
ComputedPoint = traits Point clone: { | ||
self x := 23 + 100 | ||
self y := x - 10 | ||
} | ||
## Install | ||
|
||
point = Point clone | ||
point print | ||
`npm install -g espresso-language` | ||
|
||
computed = ComputedPoint clone | ||
computed print | ||
``` | ||
Note: This requires that you have nodejs v0.8.8 and npm installed already. | ||
|
||
More examples can be found in the examples/ folder. | ||
To try them out, clone this repository and cd into it and run (NOTE: this requires that you have nodejs v0.6.1 on your path): | ||
## Hello, world! | ||
|
||
`./bin/esc examples/<filename>.es` | ||
So, let's look att how Epresso works. We'll start with the ever present Hello, world: | ||
|
||
or just play with the repl: | ||
"Hello, world!" println //=> "Hello, world!" | ||
|
||
`./bin/esc` | ||
Here we are sending the unary message `println` to a string object containing `Hello, world!`. In this case, since println is a method, it results in a method call but it could just as well have been a regular slot access and it would have looked just the same. | ||
|
||
Now, let's expand this example and introduce a few more concepts: | ||
|
||
Greeter = Object clone | ||
Greeter greet: name with: message := { | ||
"#{message}, #{name}!" println | ||
} | ||
|
||
Greeter greet: "world" with: "Hello" //=> "Hello, world!" | ||
|
||
We begin by sending the `clone` message to the prototypical `Object` object, this returns a brand new object who's prototype points to `Object` and then assign it to `Greeter`. | ||
Next we define the `greet:with:` message on `Greeter`, the := operator signifies to the compiler that this should be treated as a method. | ||
Finally we send the `greet:with:` message to the `Greeter` object. | ||
One thing you may note is that the name of this message and it's arguments are mixed togheter, this is called a keyword message, but the name actually is `greet:with:` and you can validate this by asking `Greeter` for a reference to it rather than executing it, like so: | ||
|
||
greet = Greeter get: 'greet:with: | ||
greet call: "world", "Hello" //=> "Hello, world!" | ||
|
||
The `get:` message returns a method reference that we can `call:`. It takes a variadic number of arguments and will by default execute in the context of it's original receiver, in this case Greeter, though you can override that choice by sending the `call:as:` message instead. | ||
|
||
Now, you may be wondering about `Greeter` and `greet` since I earlier told you that there are no variables in Espresso, but they sure do look like variables don't they? Well, actually they are slots on an object, in this case the top-level object `Lobby`. | ||
|
||
Lobby println // => Lobby | ||
Lobby | ||
type | ||
unknown-slot:args: | ||
to-s | ||
Object | ||
Lambda | ||
Number | ||
String | ||
Array | ||
Boolean | ||
RegExp | ||
nil | ||
traits | ||
>>> Greeter | ||
>>> greet | ||
|
||
With that out of the way, let's return to our friend the Greeter and extend it further: | ||
|
||
Greeter = Object clone | ||
Greeter greet: someone with: message := { | ||
"#{name} says: #{message}, #{someone name}" println | ||
} | ||
|
||
g1 = Greeter clone: @{ name = "G1" } | ||
g2 = Greeter clone: @{ name = "G2" } | ||
|
||
g1 greet: g2 with: "Hello" //=> "G1 says: Hello, G2!" | ||
|
||
What we see here is a common architectural technique in prototype based languages, namely separating data from behavior. Our prototypical Greeter object depends on a name slot being defined but it never defines it rather it expects that any object which clones it does, in this case that would be g1 and g2. | ||
|
||
As you can see, g1 can now greet g2 with a nice message. But there's a slight problem here, and that is, our `greet:with:` method is very specific in that it requires it's first argument to be an object with a name slot. What if we wanted it to call it with a string instead? | ||
|
||
g1 greet: "world" with: "Hello" //=> "g1 says: Hello, nil!" | ||
|
||
Hmm, not exactly what we wanted. Now we could solve that using a conditional inside the `greet:with:` method but then it wouldn't be possible to extend it further without changing that method. This is where predicate dispatch comes in handy: | ||
|
||
Greeter greet: someone with: message := { | ||
"#{name} says: #{message}, #{someone}!" println | ||
} | ||
|
||
Greeter greet: someone @{understands?: 'name} with: message := { | ||
greet: someone name with: message | ||
} | ||
|
||
|
||
g1 greet: "world" with: "Hello" //=> "P1 says: Hello, world!" | ||
g1 greet: g2 with: "Hello" //=> "P1 says: Hello, P2!" | ||
|
||
Now we have two implementations of `greet:with:` a specific one which requires a named object and a general one that accepts any object. The great thing about this is that it can be extended further, even by third party code, without needing to change the original definition. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,16 @@ | ||
|
||
[true, false, nil, 'foo, 23, 0] each: { obj | | ||
obj if_true: { | ||
"#{obj} is true" println | ||
} if_false: { | ||
"#{obj} is false" println | ||
} | ||
obj if_true: { "#{obj} is true" println } | ||
obj if_false: { "#{obj} is false" println } | ||
} | ||
("Espresso" == "Espresso") if_true: { "Espresso equals Espresso!" println } | ||
(true && true) if_true: { "true && true are true" println } | ||
(true && false) if_false: { "true && false are false" println } | ||
(true && true) if_true: { "true && true == true" println } | ||
(true && false) if_false: { "true && false == false" println } | ||
nil nil? println | ||
"foo" nil? println | ||
"foo" nil? println | ||
(1 < 2) if_true: { "one is less than two" println } | ||
(2 > 1) if_true: { "two is greater than one" println } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
0 fib := 0 | ||
1 fib := 1 | ||
|
||
Number fib := (- 1) fib + (- 2) fib | ||
|
||
15 fib println |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,10 @@ | ||
Dying = Object clone | ||
|
||
Dying hide := { | ||
"#{name}: hidin' n healin'" println | ||
self replace-delegate: Dying with: Healthy | ||
} | ||
|
||
Dying attack: other := { | ||
"Hey, I'm dying over here, I'm not about to attack no #{other name} in this condition!" println | ||
"#{name}: Hey, I'm dying over here, I'm not about to attack no #{other name}!" println | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
Healthy = Object clone | ||
|
||
Healthy attack: other := { | ||
"Attacking #{other name}" println | ||
"#{name}: Attacking #{other name}" println | ||
other replace-delegate: Healthy with: Dying | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
require("ometa"); | ||
require("./ometa/ometa"); | ||
|
||
var Espresso = Object.create({}); | ||
|
||
|
Oops, something went wrong.