Deturgenchry.markdown @master — view markup · raw · history · blame
The Deturgenchry Programming Language
WORK IN PROGRESS
Version 0.x, sometime in the twenty-tens
Chris Pressey, Cat's Eye Technologies
Introduction
Deturgenchry is a simple object-oriented language with several distinguishing features.
First, Deturgenchry is a single-assignment language. Neither method-local bindings, nor the members of an object, are mutable: once established, they may not be altered. Another, different local binding with a new name must be used, or another instance of the object (with a new value substituted for the given member) must be created.
Second, the implicit parameter self
passed to a method does not refer
directly to the object instance on which the method was invoked; it refers
to the method instance, currently executing. To get to the object
instance you have to say self.object
. And, since this is the currently
executing method instance, this is maybe where the local variables live:
self.x
, self.y
, etc. In other words, in Deturgenchry, self
is...
wait for it... the current continuation.
Third, unlike most OO languages where only a single implicit self
parameter is passed, in Deturgenchry two parameters are passed implicitly:
the self
and the other
. This dark symmetry is in honour of modern
psychoanalytic mumbo-jumbo or whatever. The other
refers to the method
instance that called the currently executing method. Or rather, the saved
continuation the represents the state of the method that called this method,
at the point in time at which it called this method.
Fourth, there is no return
statement. Instead, the other
is continued.
Examples
Deturgenchry is, like several other languages I've done (Xoomonk, Castile, etc.,) an experiment in test-driven language design. I have a rough idea for how the language should work, so I'm going to write a bunch of programs in the as-yet-imaginary language, as tests for an interpreter which doesn't exist yet. Then I'm going to write the interpreter to make the tests pass.
-> Functionality "Interpret Deturgenchry Program" is implemented by
-> shell command "./deturgenchry %(test-file)"
-> Tests for functionality "Interpret Deturgenchry Program"
A program consists of zero or more class definitions. When a program
is run, a class named Main
is sought, an instance of it is created,
and the nullary method main
on it is invoked.
| class Main {
| method main() {
| }
| }
= Null
| class Arbitrage {
| method main() {
| }
| }
? No Main class with main() method found
| class Main {
| method harangue() {
| }
| }
? No Main class with main() method found
| class Main {
| method main(n) {
| }
| }
? Too few parameters passed to method
The main method may return control to the operating system (or whatever
started running the program) by passing a value to other
.
| class Main {
| method main() {
| pass other 5
| }
| }
= IntVal 5
Local variables may be assigned values.
| class Main {
| method main() {
| k = 5
| }
| }
= Null
Local variables may be referenced in expressions.
| class Main {
| method main() {
| k = 5
| pass other k
| }
| }
= IntVal 5
Code in a method may instantiate objects of any class.
| class Junk {}
| class Main {
| method main() { pass other new Junk }
| }
= ObjVal "Junk" []
Classes don't have to have been defined yet to be referenced.
| class Main {
| method main() { pass other new Junk }
| }
| class Junk {}
= ObjVal "Junk" []
Classes don't even have to be defined at all, to be referenced. They're assumed to be "plain" classes in this case, with no relationship to any other classes.
| class Main {
| method main() { pass other new Junk }
| }
= ObjVal "Junk" []
A class may contain methods. A method may be invoked on an instance in the usual fashion.
| class Junk {
| method fire(n) {
| pass other n
| }
| }
| class Main {
| method main() {
| o = new Junk
| k = o.fire(2)
| pass other k
| }
| }
= IntVal 2
The number of actual parameters passed to a method must match the number of formal parameters the method declares.
| class Main {
| method fire(a,b,c) { pass other b }
| method main() {
| o = new Main
| k = o.fire(4,5,6)
| pass other k
| }
| }
= IntVal 5
| class Main {
| method fire(a,b,c) { pass other b }
| method main() {
| o = new Main
| k = o.fire(4,5)
| pass other k
| }
| }
? Too few parameters passed to method
| class Main {
| method fire(a,b,c) { pass other b }
| method main() {
| o = new Main
| k = o.fire(4,5,6,7)
| pass other k
| }
| }
? Too many parameters passed to method
A method has access to the currently executing method: self
.
This value is actually a continuation.
| class Junk {
| method fire() {
| pass other self
| }
| }
| class Main {
| method main() {
| o = new Junk
| pass other o.fire()
| }
| }
= ContVal (ObjVal "Junk" []) "fire" []
Methods provide access to the object they're attached to.
| class Junk {
| method fire() {
| pass other self.object
| }
| }
| class Main {
| method main() {
| o = new Junk
| pass other o.fire()
| }
| }
= ObjVal "Junk" []
Deturgenchry is single-assignment. It is not possible to assign to any
parameter to the current method, or any variable that has already
been bound, or to self
or other
.
| class Main {
| method main() {
| o = new Main
| o = new Main
| pass other o
| }
| }
? Attempted re-assignment of bound name o
| class Main {
| method main() {
| self = new Main
| pass other self
| }
| }
? Attempted re-assignment of bound name self
| class Main {
| method main() {
| other = new Main
| pass other new Main
| }
| }
? Attempted re-assignment of bound name other
The standard if-else construct is a standard enough conditional. There is no boolean type; there is only a special Null value, which represents falsehood. Everything else is truthy.
| class Bubkis {
| }
| class Main {
| method main() {
| if self {
| pass other new Main
| } else pass other new Bubkis
| }
| }
= ObjVal "Main" []
A method which doesn't pass anything back to other implicitly passes Null back to other.
| class Bubkis {
| method fantastic() {}
| }
| class Main {
| method main() {
| b = new Bubkis
| if b.fantastic() {
| pass other new Main
| } else pass other new Bubkis
| }
| }
= ObjVal "Bubkis" []
There is a built-in class called StdLib. Instances of this class expose methods to do common useful things, like arithmetic.
| class Main {
| method main() {
| stdlib = new StdLib
| pass other stdlib.gt(4, 5)
| }
| }
= Null
This is a complicated, contrived, syntactically correct Deturgenchry program.
| class binkie {
| method foo() {
| if self {} else {}
| }
| method bar(whee, y) {
| if y
| m = whee.x
| else {
| m = whee[x = y]
| }
| pass other m
| }
| }
| class schmoo {
| }
| class Main {
| method main() {
| pass other new Main
| }
| }
= ObjVal "Main" []
Grammar
Program ::= {ClassDefn}.
ClassDefn ::= "class" name "{" {MethodDefn} "}".
MethodDefn ::= "method" name "(" [name {"," name}] ")" Statement.
Statement ::= Block | Conditional | Transfer | Assignment.
Block ::= "{" {Statement} "}".
Conditional ::= "if" Expr Statement "else" Statement.
Transfer ::= "pass" Expr Expr.
Assignment ::= name<new,local> "=" Expr.
Expr ::= RefExpr
| "new" name<class>
| IntegerLiteral.
RefExpr ::= name<local> {"." name} [SetExpr | CallExpr].
SetExpr ::= "[" name "=" Expr {"," name "=" Expr} "]".
CallExpr ::= "(" Expr {"," Expr} ")"].
"Discussion"
I believe the germ of the idea that started Deturgenchry was this:
Recall that one way to make a recursive anonymous function x is to pass x as the first argument of x. The function x then calls this parameter to recurse.
This opens up a few possibilities. This first argument can be made implicit
and can be called self
. Further, self need not always be self exactly
(like how self
can be a subclass in OO code.) Further still, self
could
be a continuation which is (semi-)implicitly continued.
The existence of other
could possibly lead to a restriction: each method
may contain only one call site for any given method. That is, no method
may contain more than one call to any given method inside its definition.
This should allow the correct "return zone" to be known when continuing the
other
: it is just after the (unique, we now know) location of the call to
the current method.
Of course, there may be a problem with this if the other
is stored
somewhere and passed to a method that the other
did not directly call...