Skip to content

Commit

Permalink
Merge branch 'release/v0.1.0' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
pmh committed Sep 9, 2012
2 parents dcc2beb + 8e8743e commit ab19702
Show file tree
Hide file tree
Showing 52 changed files with 808 additions and 1,617 deletions.
110 changes: 84 additions & 26 deletions README.md
@@ -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.
13 changes: 11 additions & 2 deletions bin/esc
Expand Up @@ -24,13 +24,22 @@ if (program.args[0]) {
}

if (program.rawArgs.length === 2) {
require("repl").start("> ", null, eval_espresso);
var repl = require("repl").start({
prompt : "> ",
eval : eval_espresso
});

eval(espresso.compile(""));
Object.prototype.inspect = function () {
return this["to-s"]();
};
};

function eval_espresso(cmd, context, filename, callback) {
callback(null, eval(espresso.compile(cmd.substr(1, cmd.length - 2), {skip_runtime: true})));
try {
callback(null, eval(espresso.compile(cmd.substr(1, cmd.length - 2), {skip_runtime: true})));
} catch (ex) {
console.log(ex);
callback(null);
}
}
16 changes: 8 additions & 8 deletions examples/Conditionals.es
@@ -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 }
6 changes: 6 additions & 0 deletions examples/Fibonacci.es
@@ -0,0 +1,6 @@
0 fib := 0
1 fib := 1

Number fib := (- 1) fib + (- 2) fib

15 fib println
7 changes: 4 additions & 3 deletions examples/HelloWorld.es
@@ -1,9 +1,9 @@
Person = Object clone

Person named: name := {}
Person named: name := clone

Person greet: name with: message :=
"#{self name} says: #{message}, #{name}!" println
Person greet: person with: message :=
"#{name} says: #{message}, #{person}!" println

Person greet: person @{understands?: 'name} with: message :=
greet: person name with: message
Expand All @@ -13,4 +13,5 @@ patrik = Person named: 'Patrik
dude = Person named: 'Dude

patrik greet: dude with: "Hello"
dude greet: patrik with: "Hello"
patrik greet: "World" with: "Hello"
2 changes: 1 addition & 1 deletion examples/PredicateDispatch.es
Expand Up @@ -11,7 +11,7 @@ Foo foo: Object
Foo foo: Foo
Foo foo: Bar

Foo + other := "Adding foo to foo gives: foofoo" println
Foo + other := "Adding foo to foo gives: 2(foo)" println
Foo + other @{ type == "Bar" } := "Adding bar to foo gives: foobar" println

Foo + Foo
Expand Down
7 changes: 2 additions & 5 deletions examples/Sharks.es
Expand Up @@ -4,11 +4,8 @@ require: "examples/shark-roles/Dying"
Shark = Object clone
Shark extend: Healthy

goblin = Shark clone
goblin name = "Goblin Shark"

lemon = Shark clone
lemon name = "Lemon Shark"
goblin = Shark clone: @{ name = "Goblin Shark" }
lemon = Shark clone: @{ name = "Lemon Shark" }

goblin attack: lemon
lemon attack: goblin
Expand Down
11 changes: 6 additions & 5 deletions examples/Spec.es
Expand Up @@ -3,15 +3,16 @@
Spec = Object clone

Spec suite = Object clone

Spec suite unknown-slot: slot args: *args := {
_it = clone
_it desc = [slot]
_it unknown-slot: slot args: *args := {
(slot type == "Lambda") if_true: {
" - #{(desc join: " ")} #{slot call-as: self}" println
} if_false: {
desc push: slot
}
_it desc push: slot
_it
}
_it unknown-slot: slot @{ understands?: 'call } args: *args := {
" - #{(desc join: " ")} #{slot call-as: self}" println
_it
}
_it
Expand Down
16 changes: 8 additions & 8 deletions examples/Strategy.es
Expand Up @@ -5,26 +5,26 @@ traits OpenedFile = Object clone
traits OpenedFile read: n := "reading #{n} bytes from #{filename}" println

traits OpenedFile close := {
self replace-delegate: traits OpenedFile with: traits ClosedFile
"closing file: #{filename}" println
self replace-delegate: traits OpenedFile with: traits ClosedFile
}



traits ClosedFile = Object clone

traits ClosedFile open: filename := {
self filename = filename
self replace-delegate: traits ClosedFile with: traits OpenedFile
"opening file: #{filename}" println
self replace-delegate: traits ClosedFile with: traits OpenedFile
}

File = Object clone: {
self filename = nil
}
File = Object clone

File extend: traits ClosedFile

file = File clone
file open: "foo.es"

file read: 1024
file close
file open: "foo.es"
file read: 1024
file close
12 changes: 6 additions & 6 deletions examples/Traits.es
Expand Up @@ -2,14 +2,14 @@
traits Point = Object clone
traits Point print := "#{type} => x: #{x}, y: #{y}" println

Point = traits Point clone: {
self x = 23
self y = 12
Point = traits Point clone: @{
x = 23
y = 12
}

ComputedPoint = traits Point clone: {
self x := 23 + 100
self y := x - 10
ComputedPoint = traits Point clone: @{
x := 23 + 100
y := x - 10
}

point = Point clone
Expand Down
3 changes: 2 additions & 1 deletion examples/shark-roles/Dying.es
@@ -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
}
2 changes: 1 addition & 1 deletion examples/shark-roles/Healthy.es
@@ -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
}
2 changes: 1 addition & 1 deletion lib/espresso.js
@@ -1,4 +1,4 @@
require("ometa");
require("./ometa/ometa");

var Espresso = Object.create({});

Expand Down

0 comments on commit ab19702

Please sign in to comment.