So, in a sense it's a C# or C++-like language that compiles into Z80? (If so, then you are a god!) :PIt seems that it is a very C++ like language(I read through part of the readme)
Shouldn't this go in "Other calc related ideas and projects"?
FYI, this discussion has moved almost entirely to Cemetech.net , So if anyone wants to participate further, I recommend either going there or emailing me at [email protected]
BOOLEANS - Booleans are a whole byte because it takes extra instructions to extract/insert just one bit, which adds more than just another byte to the program just for using it; so it actually saves space that way.Hmm... So, for Boolean values, would it be faster on-calc if we just used single bytes and checked if they were (non-)zero ourselves?
Sorry DJ ... I began posting about it in both places, and got a decent initial response in both. However, it only REALLY took off at Cemetech, and thus decided to keep most of the posts there (they are time consuming, because they are thought out). Rather than just abandon [these people] altogether, I left notifications ... but I see how that could be a problem. I don't want to "steal" followers from one site to another, and I suppose I could have just copied posts and reposted them here. So much information was interchanged at Cemetech that I found it would be an overwhelming task to keep it all in both places; however, to give SOMETHING of substance, perhaps I will go back and take the key points and put them in a post here. That way there is a glimpse of what's going on, and some of the key discussion.Aaah ok, I guess I kinda misinterpreted it then. I thought your Google site had a discussion board and it seemed that on Cemetech you constantly encouraged people to go there, so I thought it was to move the entire discussion there instead of an established board. It's always good to have some major updates on big sites too (even if not all of them) so it rejoins as many community members as possible. Of course since on Omnimaga the language is in direct competition with two on-calc languages the audience might be a bit harder to convince, though, although we never know.
However, my project is hosted at google code ( http://code.google.com/p/opia/ ), and even when I discuss it anywhere, I leave links there regularly, because that is where one must go to see the language documentation and source etc. (I am not going to host it in multiple places). I will, however, go back and put a lot of the key discussion here as well.
Sorry for the misunderstanding! ... I promise a lot of information HERE soon.
switch(value) { case 1: ... 2: ... 3: ... }Since a jump-table is clearly the worst choice, the only real choice now is whether I want uber-efficiency (using direct addresses) or flexibility (using look up tables). I either I provide enums with even values (and hidden) so that they are efficient to use with switches, or I provide "selectors" which are each to be used with exactly ONE switch each, and contain actual case-addresses as values. The big difference is that an enum could be USED in any switch, but a selector IS the control for a particular switch. ... Perhaps I could provide both? Enums are flexible and better as values (being one byte each), while selectors are uber-efficient when used as case-selectors. A selector would be declared somewhat like an inline enum:
---Jump Table--- --Look-Up-Table-- --Direct_Address--
ld a,(value) ;3 ld hl,cases ;3 ld hl,(value) ;3
ld ($+4),a ;3 ld bc,(value) ;4 jp (hl) ;1
jr N ;2 add hl,bc ;1
jp case_1 jp (hl) ;1
jp case_2 cases:
jp case_3 .dw case_1
.dw case_2
.dw case_3
---------------- ----------------- ------------------
= 8 (+3 per case) = 9 (+2 per case) = 4 bytes used (+0)
// enum usage
enum Foo {X,Y,Z}
Foo f1 = Foo.X;
f2 := Foo.X;
// switch "on Foo"
switch(Foo) g1 = g1.X; // "g1.X" because g1 uses specific case-addresses
g2 := switch(Foo).X; // g2 uses case-addresses that DIFFER from g1...
g1 = g2; // ...which means that THIS IS NOT ALLOWED
// switch on anonymous enum
switch{X,Y,Z} h1 = h1.X;
h2 := switch{X,Y,Z}.X;
// Later on, g1 is used in a switch statement:
switch(g1) {
case X: ...
case Y: ...
case Z: ...
// NO default code (all cases are explicit)
}
enum Foo {X,Y,Z}
switch(Foo) g1, g2;
Foo f;
...
switch(f) { ... } // f is used as an INDEX for a look-up-table for this switch
...
switch(f) { ... } // ...and again in this switch.
...
switch(g1) { // No index. This is simply 'goto (g1)'
case X: // g1.X refers to the address of this location in code
case Y: // g1.Y refers to this address, etc.
...
}
...
switch(g2) { ... } // Likewise, g2 will have it's own switch, and it's own X,Y,Z
...
switch(g2) { ... } // ILLEGAL! (g1 and g2 are variables for ONE specific switch)
...
g1 = g2.X // ILLEGAL, because that would cause the switch on g1 to goto g2's "case X"
cofunc generate(:word) { for(i := 2; true; i++) { yield i; } }
cofunc filter { func(:word) in; word prime; } (:word) {
while(true) { i := in(); if(i % prime != 0) { yield i; } }
}
cofunc sieve(:word) {
in := new generate{};
while(true) {
prime := in(); yield prime;
in = new filter{in, prime};
}
}
func main() {
nextPrime := sieve{}; // Print first 100 primes:
for(i := 0; i < 100; i++) { Print(nextPrime()); }
}
cofunc generate(...) { ...i := 2;... } // word i = 2;
cofunc filter(func(:word) in ...) { ...i := in();... } // word i = in();
cofunc sieve(...) {
...in := new generate();... // *generate in = new generate();
...prime := in();... // word prime = in();
}
func main() { ...nextPrime := sieve();... } // sieve nextPrime = sieve();
The only bearing on "new" is that, without it, that function would overwrite the same static variable each time; so I used "new" to say "allocate new memory for this" (which actually makes the variable a pointer, since that returns an address ... another nice thing about automatic stuff like that) :)
// ===== Previous setup for cofuncs =====
cofunc ABC(a:b:c) { ... } // a,b,c represent init args, call args, returns
cofunc B(b) { ... } // short for B( :b: )
cofunc C(:c) { ... } // short for C( : :c)
cofunc BC(b:c) { ... } // short for BC( :b:c)
cofunc AB(a:b: ) { ... } // no short form for this
cofunc AC(a: :c) { ... } // no short form for this
func(b:c) f = BC(); // funcs declared with same call-args & returns
// ===== Current setup for cofuncs =====
cofunc ABC{a}(b:c) { ... } // a,b,c represent init args, call args, returns
cofunc B(b) { ... } // previously B( :b: )
cofunc C(:c) { ... } // previously C( : :c)
cofunc BC(b:c) { ... } // previously BC( :b:c)
cofunc AB{a}(b) { ... } // previously AB(a:b: )
cofunc AC{a}(:c) { ... } // previously AC(a: :c)
func(b:c) f = ABC{a}; // func declaration matches that of cofunc ABC
// ===== Example of using cofuncs as struct-funcs =====
cofunc blah { byte x, y; } (byte z : char) { ... }
b := blah{1,2}; // Just like a struct!
b.x = 5;
char c = b(3); // Just like a func!
func(byte:char) f = c;
func(byte:char) f2 = blah{3,4};
(as you can see, barely anybody replies to your posts here, but this should change eventually).I think it's more due to the post lenght. A lot of people tend to be busy, have troubles following long text or in Omnimaga case, have language barrier to overcome. There are also the ones who are lazier or are not as tech-savy and can't help as much. As a result, they go TL;DR on the posts. Those that do will usually skim through them and do short replies.
I think the best thing to at least build the parse tree part of the compiler (without releasing it) and add the features you have designed so far. If you're programming it correctly in a massively inheritable object oriented way, this should be extremely easy to add and remove features at around the same speed you can come up with them on paper.
Quigibo: I've had a few premature run-throughs with compilers before. Last time, I went through and changed some key rules which really affected parsing and verification rules for classes & interfaces. I do have a couple aspects already in place; However, the new design is free of MUCH of that red tape (since it lacks a type-hierarchy), so I might be able to put more up sooner (school etc. permitting)
input:
expr TOKEN_SEMICOLON
expr:
expr TOKEN_PLUS expr
| expr TOKEN_MULTIPLY expr
| var TOKEN_ASSIGN expr
| var
var:
char
| char var
char:
TOKEN_A
| TOKEN_B
etc.
struct S {
byte x;
cofunc f(...) { ... } // f and g may contain yields
cofunc g(...) { ... } // f and g have access to each other and x
}
s := S{1};
s.f(...);
s.g(...);
struct T { cofunc(...) { ... } } // the cofunc is an anonymous field of T
cofunc T(...) { ... } // This is a shorthand for the same thing above
t := T{};
t();
cofunc T(byte x : byte) { // THIS SHORT-HAND FORM...
for(byte last = 0; true; last += x) {
yield last;
}
}
struct T { // ...REALLY MEANS THIS
cofunc(byte x : byte) {
for(byte last = 0; true; last += x) {
yield last;
}
}
}
t := T{}; // make a T variable
t(1); // 0+1 = 1
t(2); // 1+2 = 3
struct Foo {
byte x;
cofunc f(byte y) { ... }
cofunc g(char c) { ... }
}
f := Foo{1}; // x
f.f(2); // y
f.g('H'); // c
struct S {
func fp(byte); // function-pointer
func mp(this, byte); // method-pointer (caller is passed to "this" automatically)
cofunc cf(byte x) { ... } // member cofunc ("this" is implied)
// NOTE: cf is NOT directly modifiable (it changes after each 'yield')
}
func f(*S, byte x) { ... } // non-member function
func S.m(byte x) { ... } // non-member method
s := S{f,m}; // cf is not in the list (not a pointer)
// To show that they are all of type func(*S,byte):
s.fp = s.mp = f; // all point to f
s.fp = s.mp = S.m; // all point to S.m
s.fp = s.mp = s.cf; // ... (Yes, even the cofunc!)
// To show how call syntax differs:
s.fp(s,5); // pass an *S directly (does not have to be s!)
s.mp(5); // s is automatically as "this"
s.cf(5); // s is automatically passed as "this"
cofunc CF(byte x) { ... } // cannot access y in there
struct S { byte y; CF cf; }
s := S{5,CF{}};
s.cf(5);
ptr := s.cf; // ptr is a func(*CF, byte), rather than a func(*S,byte)
*[]byte x; // pointer to array of bytes
[]*byte y; // array of pointers to byte
byte b;
x[i] = b; // short for x*[i] = b;
y[i] = b; // short for y[i] = &b;
y[i]* = b; // no shorthand
b = x[i]; // short for b = x*[i];
b = y[i]; // short for b = y[i]*;
The rule is to FIRST insert derferences "*" so that array-indexing "[]" (and dot ".") makes sense (thus {x[j]} is ALWAYS {x*[j]}), and SECONDLY to make the right-side suitable for the left side. The only issue with this it creates asymmetry (note the differences between {y[j] = b} and {b = y[j]}). This is not fixable because {&b = ...} is illegal anyway, and I'm set about having assignment-to-pointer always give an address (plus it allows values to be passed "by reference" more transparently).
for { ... }
for(test) { ... }
for(init; test; next) { ... }
for(var : a, b) { ... }
for(var : a, b, i) { ... }
for(var : arr) { ... }
for(num) { ... }
while(c) { ... } // loop while c is true
until(c) { ... } // loop until c is true
do { ... } while(c); // do ... and then repeat while c is true
do { ... } until(c); // do ... and then repeat until c is true
do { ... } // do "forever" (useful)
for(init; test; update) { ... } // C++/Java/C# "for"
for(var, start, end, inc) { ... } // BASIC "for" (optional "inc")
for(var : array) { ... } // "foreach" (probably added in later); possibly also
// work with funcs/cofuncs where the last of the return-values is a bool
// "A" (a large chunk of code) is duplicated to do this:
A;
while(C) {
B;
A;
}
// My solution (just as it reads, i.e. begin at label "start"):
do(start) {
B;
start:
A;
} while(C); // ...but repeat the WHOLE loop "while C" is true
// A common solution (but requiring "loop manipulation"):
do { A; if(!C){break;} B; } // !C is evil
foo: while(...) {
bar: while(...) {
while(...) {
break; // break from the innermost loop (or switch)
continue; // continue the innermost loop (or switch)
break foo; // break from the "foo" loop (or switch)
continue bar; // continue (skip to next iteration of) the "bar" loop (or switch)
}
}
}
func Foo(byte a, b=2, char x='Y', y='Z') { ... }
Foo(1,2,'a','b'); // Normal usage
Foo(1,2,'a'); // Short for Foo(1,2,'a','Z');
Foo(1,2); // Short for Foo(1,2,'Y','Z');
Foo(1); // Short for Foo(1,2,'Y','Z');
each(a,b,c) = x; // Instead of (a=b=c=x;)
each(a,b,c),d = x,y; // Instead of (a=b=c=x; d=y;) or (a,_ = b,_ = c,d = x,y;)
NOTE: A more likely case than (...=x,y;) is a function which returns multiple values (...=twoValues();).struct Foo { func Bar(...) { ... } } // declared internally
func Foo.Bar(...) { ... } // declared externally
word cursor@"curcol"; // cursor will refer to "currow" in the assembly code.
byte curCo1@cursor; // curCol now refers to the leading byte of cursor
byte curRow@cursor+1; // refers to the trailing byte of cursor. WE CAN DISCUSS THIS SYNTAX
func() f@"routine"; // f() now calls an assembly routine directly
// Embedding variables within an array:
values := [6]byte{0,1,2,3,4,5};
byte a@values[2]; // a is literally stored at value[2]
byte b@values[3]; // b is literally stored at value[3]
a = 1; // values[2] is now 1;
values[3] = 2; // b is now 2;
// More compact way of embedding variables in an array can be one of these:
values := [6]byte{0, 1, a=2, b=3, 4};
values := [6]byte{0, 1, a@2, b@3, 4};
func(byte a, b):byte // Takes two bytes (a and b) and returns a byte
func(byte a, b):(byte,byte) // ...Returns TWO bytes
func():char // Takes nothing, returns a char
func(A, func(B : C) : func( : func())) // Old "inner" setup
func(A, func(B) : C) : func() : func() // New "outer" setup
// A simpler example (can you tell what it does by looking?):
func( : func( : func(A : B))) // Old "inner" setup
func() : func() : func(A) : B // New "outer" setup
byte a, b = c, d; // same as: byte a, (b = c), d;
byte (a, b) = (c, d); // same as: byte a = c, b = d;
byte a = (((b))); // same as: byte a = b; (as expected)
(a,b,c) = ((x,y),z); // same as: (a,b,c) = (x,y,z)
(a,b) = ((x,y),z); // INCORRECT: (x,y) is still TWO items
byte a = 5, b = a+7;
if(a+b < 15) { doA(); }
else { doB(); }
while(a < 100) { a += a; }
$while(b < 100) { b += b; }
return $(a+b);
// The compiler precomputes that code, resulting in THIS code:
byte a = 5; // b = 12, but is never DIRECTLY needed, so it is removed
doB(); // 5+12 is clearly not less than 15
while(a < 100) { a += a; } // The compiler leaves loop alone,
// ... unless TOLD ($) to: b=12+12 ...24+24 ...96+96 ... b is 192
return $(a+192); // ERROR! (value of a is unknown; cannot evaluate)
// Current setup:
[]byte // Points to a byte-array
[5]byte // IS a (static) byte-array (5 values)
[_]byte // Same as above, but size is determined from usage
*[5]byte // Same as []byte, but requires/assumes the underlying array is 5 bytes
[5][5]byte // An array of five [5]byte values (no pointers!)
[5][]byte // (static) Array of five []byte values (five pointers)
[][5]byte // Points to an array of five [5]byte values
[][]byte // Points to an array of pointers to arrays
// Hypothetical way to embed size-information:
[byte]byte // A (static) byte-array, prefixed by a byte to store the size
[word]byte // A (static) byte-array, prefixed by a word to store the size
*[byte]byte // Pointer to such an array
[]byte // Array of (some determinable number of) bytes (previously indicated as [_]byte)
[5]byte // Array of 5 bytes
[byte]byte // Array of bytes (prefixed with a byte to indicate the size)
*[]byte // Pointer to an array of bytes
*[5]byte // Pointer to an array of 5 bytes
*[byte]byte // Pointer to a (byte-) size-prefixed array of bytes
[][]byte // Array of *[]byte values.
[5][5]byte // Array of five *[5]byte values.
[5,5]byte // Array of five [5]byte values (no pointers)
[5][5,5]byte // Array of five *[5,5]byte values
[5,5,5,5]byte // A 4-Dimensional array of bytes (no pointers)
[5,5][5,5]byte // 2D array of pointers to [5,5]byte values
[,]byte // Static 2D array (NOTE: the sizes have to be determinable!)
*[,]byte // ERROR! (cannot even COMPUTE where [d,e] is without the inner dimension)
*[,5]byte // Pointer to a [?,5]byte (ok: [d,e] is [5*d+e])
*[,byte]byte // (also ok, since [d,e] is [SIZE*d+e], and size is stored as a leading byte)
*[byte,]byte // ERROR! (It's the inner dimension that matters)
*[5,5]byte // Pointer to a [5,5]byte -- Very ok
[3,4,5]byte arr;
*[3,4,5]byte p3 = &arr; // p3[d,e,f] refers to arr[d,e,f]
*[,5]byte p2 = &arr; // treat arr as a [12,5]byte (e.g. p2[4*d + e, f])
*[]byte p1 = &arr; // treat arr as a [60]byte (e.g. p1[5*4*d + 5*e + f])
// I'd like NOT to allow this:
arr[d] // gives the dth [4,5]byte value
arr[d,e] // gives the [d,e]th [5]byte value
// ... because it means that arr[d][e][f] is the same as arr[d,e,f]
Multi arrays with [] seems like a must. Instead of tuples, can't you just return an array?
byte (x,y) = foo(1,2);
func foo(byte a, b): (byte,byte) {
return (m,n);
}
[2]byte xy = bar([2]byte{1,2});
func foo([2]byte ab): [2]byte {
return [2]{m,n};
}
[1]byte z = boo([1]byte{1});
func boo([1]byte c) {
return [1]byte{m};
}
...but I think tuples are a worthwhile feature :) (and yes, they are loose enough to be equivalent to a list of function arguments).By the way, even if you make the language similar to standard languages, when this comes out, you should really start writing a good tutorial for it. Preferably make one for people with no coding experience, so that for BASIC coders (the ones usually looking for alternatives) can easily pick up OPIA.
[5][Y][Z]T // Static array of 5 [Y][Z]T values
[ ][Y][Z]T // Pointer to array of (some number N of) [Y][Z]T values
[5,5][Y][Z]T // Static array of 5x5 [Y][Z]T values
[ ,5][Y][Z]T // Pointer to an array of Nx5 [Y][Z]T values
*[5][Y][Z]T // Pointer to (because of *) array of 5 [Y][Z]T values
*[ ][Y][Z]T // Pointer to pointer to array of N [Y][Z]T values
[5][ ]T // Array of 5 (pointer to array of T) values
[5][5]T // Array of 5 (pointer to array of 5 T) values
[L,M,N]T arr; // An LxMxN array of T values
[ ,M,N]T p3 = arr; // Pointer to an ?xMxN array (3D)
[ ,N]T p2 = arr; // Pointer to an ?xN array (2D)
[ ]T p1 = arr; // Pointer to an (N) array (1D)
p3[x,y,z] == p2[x*M+y, z] == p1[(x*M+y)*N+z] == arr[x,y,z]
By byte- and uint-prefixed strings, do you mean that number would be the length of the string?
It looks good to me. Though I can't help but point at that examples are always a good idea, even if you think it's readable. Not everyone is you. What you and Kerm basically just did is an example of intellectual exceptionalism--you more-or-less said "it's easy to understand if you're not dumb".Thanks for bringing this to light, because the fact that it appears that way means that I was not clear about what I posted and why (oops!): The Grammar is something that I posted as a necessary aid in designing the compiler, rather than as a medium I chose to explain the language to everybody (that's what the overview is for). However, in the spirit of open source development (and to involve the community), I document everything and try to keep it as clear as possible for what it is (e.g. I try to keep the source code easy to read if you know Java).
Hazzah for lambda expressions and anonymous functions! :D
One question, though: I just noticed bit rotation. Is that where 0b00100110 >>> 3 == 0b11000100?
That is a much better name than before. OPIA always made me think of what the plural version of opium would be. :/Unfortunately that's how it was for me <_< Antelope's a nice name. I like it. Language names don't have to have anything to do with what they're like—heck, just look at Python (http://docs.python.org/tutorial/appetite.html) and Lua (http://www.lua.org/about.html) :D (By the way, what did OPIA even stand for? I don't see it anywhere except possibly that long list of acronyms in your post about possible names, but that list doesn't contain "OPIA." But then I haven't read much of this topic yet, so sorry if it's in some random place.)
Woah, looks like I missed a few posts. I don't know why, but I wasn't receiving emails.Awesome, you guys! Thanks!
Anywho, the grammar looks good! I'll have to take a gander at the source sometime soon ;)
interface Printer { Print(); }
struct Foo { []Printer data; }
interface Printer { Print(); }
struct Foo<T:Printer> { []T data; }
Foo<int> list; // list may only work with ints (and stores an int.Print pointer)
Foo<Bar> list2; // list2 only works with Bars (stores a single Bar.Print pointer)
interface Printer { Print(); }
struct Foo<T:Printer> { []T data; }
could just as easily be this:interface Printer { Print(); }
struct Foo<Printer> { []Printer data; }
because inheriting from Printer would automatically make it a Printer type. Having something like struct Foo<T:Printer> ... is far more complicated than necessary because if something has a Print() method, but is not yet a Printer, it would be just as easy to simply make it inherit from Printer.If it's my understanding, then "struct Foo<T:Printer> { []T data; }" and "struct Foo<Printer> { []Printer data; }" are basically the same. ... Not every Print() method would be the same for every Printer.
interface Printer { Print(); }
// Using "mixed" Printers ("<>" is unnecessary)
struct Mixed { []Printer data; }
Mixed m = {{someBar, someCar, someDar}};
//m is: {{Printer{someBar,Bar.Print}, Printer{someCar,Car.Print}, Printer{someDar,Dar.Print}}}
m.data[i].Print(); // Calls the "Print" of data[i] on the Printer-object of data[i]
// Using only same-typed Printers:
struct Same<T:Printer> { []T data; }
Same<Bar> s = {{bar1, bar2, bar3}};
// s is: {{bar1,bar2,bar3}, Bar.Print}
s.data[i].Print(); // Calls the single embedded Bar.Print on data[i]
1) you could just make Printer an abstract class with a default method
2) Also, are you sure Foo and Foo<T> would have the same methods?
3) Foo<T>'s type would probably be something like IEnumerable<T>.
struct abstract {
int x, y;
meth := func(this, int a, int b) { x=a; y=b; };
}
a1 = abstract{1, 2}; // uses defualt method
a2 = abstract{1, 2, someOtherMethod};
struct Foo<T> { *T value; } // always the same size
func Foo<T>.Bar<T>() { ... } // always the same everything
func Foo<int>.Bar() { ... } // THIS one is specific to ints.
c := Foo<char>{'5'};
b := Foo<bool>{true};
i := Foo<int>{123};
c.Bar(); // inferred as c.Bar<char>, or Foo<T>.Bar<T> where T = char
b.Bar(); // inferred as b.Bar<bool>, or Foo<T>.Bar<T> where T = bool
i.Bar(); // inferred as i.Bar<int>, or the separate Foo<int>.Bar
// --- Another just to clarify: ---
func A<T>(T arg) { ... } // Different per type-size (and per differing operations on T)
func B<T>(*T arg) { ... } // Same per type, so long as nothing type-specific happens
struct Tree<T> { T value; *Tree<T> left, right; }
cofunc Tree<T>.InOrder<T>():T {
if(left != null)
for(int i : new left.InOrder{}) { yield i; }
yield value;
if(right != null)
for(int i : new right.InOrder{}) { yield i; }
}
Tree<int> tree; // Tree of int values
...
for(int i : tree.InOrder{}) { Print(i); }
struct Tree<T> { T value; *Tree<T> left, right; }
func Tree<T>.InOrder<T>(func(T) process) {
if(left != null) { left.InOrder(process); }
process(value);
if(right != null) { right.InOrder(process); }
}
Tree<int> tree;
...
tree.InOrder(Print);
// Or: tree.InOrder(i => somethingElse(i));
(SEE MY NEXT POST)
// Old setup:
struct Counter { int n; cofunc( ):int { n++; return n; } }
c := Counter{5};
c(); c(); c(); // 6, 7, 8
// New setup:
cofunc Counter { int n; } ( ):int { n++; return n; }
c := Counter{5};
c(); c(); c(); // 6, 7, 8
struct Link<T> { T value; *Link<T> next = null; }
struct List<T> { *Link<T> head = null, tail = null; length = 0; }
func Tree<T>.Size<T>():int { return length; }
func Tree<T>.add<T>(T val) {
l := new Link<T>{val};
if(head == null) { head = tail = l; }
else { tail = tail.next = l; }
length++;
}
List<int> list; // an int list
list.add(1); ist.add(1); ist.add(3);
If it were "*T value" rather than "T value" in Link, then there'd only one version of everything, since pointers are the same size. Also, you can be specific, such that a "func List<int>.Foo()" would take ONLY int-lists (which is why the extra "<T>" is needed in "List<T>.add<T>").List<int> list;
cofunc List<T>.Iterator<T>():*T {
for(l := head; l != null; l = l.next) yield l.value;
}
func List<T>.Each<T>(func(*T) process) {
for(l := head; l != null; l = l.next) process(l.value);
}
// Cofunc iteration (uses two loops!)
for(int i: list.Iterator{}) { ...do stuff with i... }
// Func-pointer iteration (much more efficient)
list.Each(i => {...do stuff with i...});
- No more embedding of funcs or cofuncs within structs or cofuncs (enforces clarity)
struct Foo { ... }
func Foo::Bar() : byte { ... }
Does this mean we have to use C++ style method declaration? I.e.struct Foo { ... }
func Foo::Bar() : byte { ... }
struct Foo {
func(*Foo,...) fp; // embedded function pointer
func(this,...) mp; // just like fp, but counts as a "method"
}
func f(*Foo,...) { ... } // function (not embedded in Foo)
func Foo.m(...) { ... } // method (same as f, but is a method)
Foo foo, foo2;
foo.fp(foo,...); // foo EXPLICITLY passed as 1st arg
foo.fp(foo2,...); // same deal (can pass ANY Foo)
foo.mp(...); // "method" = foo IMPLICITLY passed as 1st arg ("this")
f(foo,...); // same deal as fp, but not a func-pointer
foo.m(...); // same deal as mp, but not a func-pointer
// They all STORED as func(*Foo,...) though;
// and since fp and mp are pointers, you can do this:
foo.fp = foo.mp = f; foo.fp = foo.mp = m;
Cool. I like the preprocessor directives especially since I'm starting to understand their usefulness (remember, I have mostly confined myself to C# where they are largely unnecessary). Do you plan to add #pragma, or would that not be needed?No pragma, because I intend on having my compiler be "the" compiler for Antelope (like how java uses "javac" as the core compiler, but various IDE's can be built off of it). Instead, by exposing the guts of the compiler as an API, other tools can "intercept" code between phases and do whatever else they want (including intercepting a "#pragma" anyway).
PSUEDO-JAVA/C# | | ANTELOPE (new syntax) | | ANTELOPE (old syntax)
========================================================================
class Fooer { | | struct Fooer { | | ...
void Foo() { <1> func Foo() { <1> func(this) Foo = func(this) {
Print("Foo "); | | Print("Foo"); | | ...
} | | } | | }
} | | } | | ...
| | | | ...
class FooID : Fooer { <2> struct FooID { <2> ...
int id; | | int id; | | ...
| | | | ...
<3> Fooer { <3> Fooer = Fooer {
void Foo() { <4> func Foo() { <4> func(this) {
Print("Foo "); | | Print("Foo "); | | ...
Print(id); | | Print(id); | | ...
} | | } | | }
| | } | | }
} | | } | | ...
1) I'd previously given the "func(this..." syntax for declaring function-pointers "as methods" (i.e. "f.Foo(f)" shortens to "f.Foo()"), which allowed "virtual" (late-bound) methods to be "simulated". However, letting these be declared directly as "internal methods" looks much cleaner, matches the common OOP idiom/paradigm, and still gives control over where they are stored in the struct.PSUEDO-JAVA/C# | | ANTELOPE (new syntax) | | ANTELOPE (old syntax)
======================================================================
Fooer foo = Fooer(); <5> Fooer foo = Fooer{}; <5> ...
FooID fid = FooID(5); | | FooID fid = FooID{5}; | | ...
| | | | ...
Fooer moo = Fooer() { <6> Fooer moo = Fooer { <6> ...
void Foo() { | | func Foo() { | | func(*Fooer f) {
super.Foo(); | | Fooer.Foo(this); | | Fooer.Foo(f);
Print(" Moo!"); | | Print(" Moo!"); | | ...
} | | } | | }
} | | } | | ...
5) Initialization values are given directly "{...}" rather than via a constructor "(...)". The first variable declaration can be simplified to either "Fooer foo = {5};" or "foo := Fooer{5};" for conciseness.PSUEDO-JAVA/C# | | ANTELOPE (nothing new here)
=============================================================
void FooFooer(Foo f) { <7> func FooFooer(*Foo f) {
f.Foo(); | | f.Foo();
} | | }
| |
FooFooer(foo); // "Foo" <8> FooFooer(foo); // "Foo"
FooFooer(fid); // "Foo 5" | | FooFooer(fid); // "Foo 5"
FooFooer(moo); // "Foo Moo!" | | FooFooer(moo); // "Foo Moo!"
7) In Java/C#, pointers ("references") are automatic. In Antelope you have to use them specifically (and you should, because they are more efficient).trait Wargler {
Foo(int);
Bar():int;
Wargle() { Foo(Bar()); }
Talk() { Print("Wargle!"); }
}
trait Fargler {
Boo();
Bar() = Moo(5); // { return Moo(5); }
Fargle() { Moo(Bar()); }
Talk() { Print("Fargle!"); }
}
struct A : Wargler { ... }
struct B : Fargler { ... }
struct C : Wargler, Fargler { ... }
func A.Foo(int) { ... }
func A.Bar():int { ... }
func B.Boo() { ... }
func C.Foo(int) { ... }
func C.Bar():int { ... }
func C.Boo() { ... }
func C.Talk() { ... } // Overlaps both traits
func A.Wargle() { Foo(Bar()); } // func Wargle(A* a) { A.Foo(a, A.Bar(a)); }
func C.Wargle() { Foo(Bar()); } // ...etc...
func B.Fargle() { Moo(Bar()); }
func C.Fargle() { Moo(Bar()); }
func A.Talk() { Print("Wargle!"); }
func B.Talk() { Print("Fargle!"); }
func Warg(Wargler w) { w.Wargle(); }
func Farg(Fargler f) { f.Fargle(); }
(a,b,c) := (A{...}, B{...}, C{...});
Warg(a); // wa := Wargler{a}; Warg(wa);
Farg(b); // ...etc...
Warg(c);
Farg(c);
struct Foo {
int x; int y;
new(int xArg, int yArg = 5) { // Constructor
x = xArg;
y = yArg + x; // Now you can map values differently
}
delete { ... } // Destructor
}
f1 := Foo(1,2); // x=1, y=1+2
f2 := Foo(1); // x=1, y=1+(5)
struct Bar { int x,y,z; } // Constructors not required.
b := Bar(1,2,3);
(Another option would be Kotlin's approach (http://www.youtube.com/watch?v=sP9R9Nc3sRA#t=21m40s))// OLD:
struct A { int x, y; }
struct B { A; int y; }
b := B(A(1,2),3);
// b.x == b.A.x == 1
// b.A.y == 2
// b.y == 3
// NEW:
struct B { A A; int y; }
// ...Everything else is the same
Great to hear this isn't dead. I'm still quite excited to see how programming in this language is gonna be :)