Show Posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.


Messages - shkaboinka

Pages: 1 ... 3 4 [5] 6
61
TI Z80 / Re: OPIA - A full OOP language for z80
« on: January 10, 2012, 01:46:15 am »
What's more, it can also tell when dereferencing (*) and addressing (&) is implied. If you give the current overview a good look, you might find some surprises like that (I am a bit brief in it sometimes) :)

http://code.google.com/p/opia/wiki/Overview

EDIT: Here is another change:
   // ===== 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};

62
TI Z80 / Re: OPIA - A full OOP language for z80
« on: January 09, 2012, 09:05:27 pm »
Actually, they do need to (and did) have their types declared, just like any other variable. The ":=" operator tells the compiler that a variable is being both declared and initialized, and to infer the type from the initialization value. At first I was not sure about having both "=" and ":=" operators, but I've found that it indeed makes it easier to code! :)

Code: [Select]
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) :)

63
TI Z80 / Re: OPIA - A full OOP language for z80
« on: January 09, 2012, 03:06:19 am »
The core language will only contain the mechanisms that are fundamental to the language. In other words, there will be no built-in math, input/output, or other utility functions etc. built in. The only exception might be some string-manipulation (there should be a post somewhere about a "StringBuilder" which can be used to concatenate strings) and possibly some array-copy stuff.

HOWEVER, once the CORE language is finished, then libraries can be written for that kind of stuff. ... And that is where I will need a lot of help (or just let it loose and see what flies) :)

EDIT: On a side note, IS NOBODY DISAPPOINTED THAT I DROPPED THE JAVA/C#-ish VERSION OF OPIA? (I just want to make sure everyone is still good with it; or be able to convince otherwise). I do intend for OPIA to be much faster and more versatile (and much more optimizing) than Axe or Grammer, or anything else aside from pure assembly :)

I have recommitted my source with some error-fixes. Preprocessing should not be complete, with errors marked correctly. The result is a list of tokens (which the syntax-tree-building portion of the compiler is to process), containing (in order):
 - Error tokens (if any)
 - #allocate and #assembly statements (as applicable to environment)
 - all other source tokens (as applicable to environment)
 - Token.EOS (the "end of source" token).

EDIT-EDIT: I adapted some code from Google Go into OPIA code as a demonstration of what OPIA can do (and how to adapt their "gofuncs" and channels into cofuncs, and how cofuncs can be used as asynchronous processes). This is a prime-number sieve:
Code: [Select]
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()); }
}

64
TI Z80 / Re: OPIA - A full OOP language for z80
« on: January 07, 2012, 02:04:09 pm »
Sort of... they are different types altogether. A 'Foo' can be assigned values from the enum declaration (or from other Foo variables). A 'switch(Foo)' just borrows the labels of Foo, but assigns separate values (i.e. addresses of actual cases in a switch somewhere). In fact, given two 'switch(Foo)' variables g1 and g2, g1 and g2 use DIFFERENT values because they are associated with different switches. That is, g1.X is the address of "case X" where g1 is switched on (switch(g1) { case X: ... }), and g2.X is the address of "case X" where g2 is switched on. Therefor, though they appear to have the same type, g1 and g2 cannot be assigned each other's values, neither can they take values from a 'Foo'.

Code: [Select]
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"

... There is really no point in defining an enum just to make a switch variable on it. The code above might as well have just said 'switch{X,Y,Z} g1, g2;'. The only reason I allow it is that, supposing you already HAD an enum defined, and the cases were related conceptually to the enum, then you might as well just use the same labels (and rather than copying each label, you can just plop the enum name into the switch-variable type). Again, a switch "on" an enum only mimics that enum by using the same labels for its values. ... But since it's only a mimic (switch variables cannot interact with other variables because they each use a unique set of case-addresses), is it worth allowing the mimic at all? That is, I could just allow the switch{X,Y,Z} g1 type of declaration if that would be less confusing.

65
TI Z80 / Re: OPIA - A full OOP language for z80
« on: January 07, 2012, 01:20:28 pm »
How do you feel about using both though (i.e. the "EDIT-EDIT" section)? That way, an enum can be used as an enum, but also for a switch (so long as it only receives values from its own value-set). By stating a variable as a "switch" rather than an enum, you are saying very specifically "this is for a switch" (or that the selection part of a particular switch is being treated as a directly-modifiable variable). The cool thing is that this functionality exists on the functional level in the form of function-pointers :)

66
TI Z80 / Re: OPIA - A full OOP language for z80
« on: January 05, 2012, 04:24:37 pm »
EDIT: All you really need to look at is this table (and yes, I did just totally scrap the old post for this):
   switch(value) { case 1: ... 2: ... 3: ... }

   ---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)
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:
enum eFoo {X,Y,Z} ... eFoo a; a = eFoo.X;
selector(X,Y,Z) sFoo; sFoo = X;

(My previous suggestion about modeling a switch as a function with multiple entry points comes from the idea that modifying an address directly and jumping to it sounds just like a function-pointer; but since switch-cases can fall through to the next case, perhaps a function with multiple entry-points, being several functions smashed together but with an entry-table as well (stored as a function-pointer-array) ... but I resolved that by just using a look-up-table instead).

EDIT-EDIT: Here is a possible solution: I could provide both simply by providing the enum, and "switch on an enum" variables:
   // 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)
   }

The switch "on enum" variables only use the values of the specified enum as labels for addresses of a specific switch-construct. Therefor, each switch variable has its own set of values, and thus they cannot be assigned each other's values (hence the "varA = varA.value", and the type is singular to each variable). Each switch variable may only be used in ONE switch-statement.

Note: The parser can tell the difference between each use of "switch" by the placement of parenthesis, brackets, and identifiers:
 - switch(ident) ident
 - switch{list} ident
 - switch(ident) {

67
TI Z80 / Re: OPIA - A full OOP language for z80
« on: January 03, 2012, 05:25:50 pm »
I have corrected the spelling of "volatile", thanks :) (I really thought it was spelled that way!)

As for my "cofunctions", you will actually get a more accurate explanation if you look up "coroutine". The reason I chose "cofunctions" anyway was that "cofunc" looked more intuitive to me than "coro", since they can be used a "funcs". ... But it is more accurately a coroutine. A coroutine is a function which can do something, pause and return a value, and then continue where it left off when it is called again. It's like having a subprogram which you can switch between! ...and there are many other uses, one of which is that of an iterator/generator. This is a superior approach to making an iterator object as a class, because those have to store information as well, but also reenter the same methods and check the same things again, etc. The reason that coroutines have to be created as variables is that this allows for multiple "instances" of them to be in use at one time (and the same mechanisms are required anyway; so might as well just attach the data to a variable).

A "closure" is when a function is declared inside of another function, and it uses some of the local variables that were declared inside of the outer function. Languages that support this can do one of two things: (1) allow the variables to be modified directly, (2) make a copy of those variables, and that function gets it's own copy of them to modify as it pleases. Closures are used in class-less languages like JavaScript to simulate OOP-ish structures, or to create specific functions (e.g. a function-making function).

Because coroutines and closures both require additional storage, I combined them into what I call "cofunctions" (which are really just coroutines). These can be used as closures by, instead of capturing variables in the outer-context automatically, you just pass those variables (or values) to the "initialization arguments" of the cofunction. The "initialization" arguments are stored as part of the cofunction object, and can be used and modified each time (and they are used to create an instance variable of the confunctions); and the per-call arugments are the arguments that are passed each time the cofunction is called. Rather than have a stange { a,b = yield stuff; } syntax, I just have the per-call arguments have variable names attached to them (just like how return values do NOT have names attached to them).

I hope that helps; but if not, look up coroutines, generators, and closures. There is also a very nice (but very in depth) analysis of coroutines ("Revisiting Coroutines") which convinced me to allow them and helped me decide the best way to implement them, which I made a short-link for as: http://tinyurl.com/revcor

Anonymous functions are just nameless functions which are immediately assigned to function-pointers. It's the equivalent to creating a "new" function, in the same way someone would create a "new" anything else in Java/C#/C++. This is convenient because you can pass a literal function declaration to a function-pointer argument, rather than having to declare a function elsewhere and then reference it. This is why I am allowing anonymous functions to be declared in other functions (where else are they going to go?), but I am on the fence as to whether or not I will allow this for other functions as well, or whether or not either of them can refer to local variables declared in their surrounding functions (if they CAN, then they will modify them directly, since (1) that is useful and most efficient in some cases, and (2) variables can already be "captured" by passing them to the initialization arguments of a "cofunc").

SIDE NOTE/QUESTION: What are people's feelings about my changes from the Java/C# style to the Go-ish style? (Compare the old and current overviews from http://code.google.com/p/opia/w/list )

68
TI Z80 / Re: OPIA - A full OOP language for z80
« on: January 02, 2012, 06:06:51 pm »
...And there it is. This forum should now be caught up (though much discussion is missing, I provided all the key points). The changes from Java/C# style (btw, I think C# is the best OOP language yet...) to a more Go-ish style (..but Go seemed more simplified and flexible, and more powerful at the low-level; and therefor more suitable for a z80 language) results in less overhead, MUCH less keywords, and NO TYPE HIERARCHY (as inheritance imposes)! In keeping with this simplification, I removed the public/private/protected mechanisms, and simply provide that lowercase entities are only visible within the namespace they are declared in (files may start with "namespace BLAH;"), structs & interfaces etc. may not be declared within eachother, nor may they contain "static" variables/entities (instead, these are declared directly within the same namespace). I was going to disallow nested namespaces and remove the "using [someNamespace]" mechanisms, but I think I can allow that anyway, since it would be the only real way to nest "static" things in a modular way ... but without having to use the "static" keyword :)

As always, all current documentation can be found at http://tinyurl.com/z80opia , but I will try to keep up to date here as well. You can view the source there and test it as you please (which would be much appreciated). Thanks everyone for participating :)

69
TI Z80 / Re: OPIA - A full OOP language for z80
« on: January 02, 2012, 05:56:39 pm »
COFUNCTIONS (Closures, Iterators, Coroutines):

Cofunctions are function-objects which are stored like objects (structs), but invoked (called) like functions. The yield command is used to return values, but continue from the same spot on the next call. Cofunctions are declared with colons separating the initializer arguments (for creating instances), invokation arguments (for passing values on each call), and return type(s):

   // simulating a closure on x
   cofunc counter(byte x : : byte) {
      x = x+1;
      return x;
   }

   counter c1, c2 = counter(3), counter(1);
   c1(); c1(); c1(); // 4,5,6
   c2(); c2(); c2(); // 2,3,4

   // using yield to simulate a generator
   cofunc rotatingSeq( : byte add : byte) {
      byte last = add; // last will be stored internally
      yield add;
      yield add+last;
      return add-last; // next call starts back at top
   }

   rotatingSeq r = rotatingSeq();
   r(1); r(2); r(3); // 1,3,2
   r(4); r(5); r(6); // 4,9,2
   r(7); // 7, (see f3 below)

   // Cofunctions are valid as function-pointers:

   func(:byte) f1,f2 = c1,counter(0);
   f1(); f1(); f1(); // 7,8,9
   f2(); f2(); f2(); // 1,2,3

   func(byte:byte) f3,f4 = r,rotatingSeq();
   f3(1); f3(2); f3(3); // continuing from r: 8,-5,3
   f4(1); f4(2); f4(3); // starting fresh: 1,3,2

The compiler accomplishes all of this by making the following modifications:
(1) The underlying function is modified to take a pointer to a cofunction-instance in addition to its per-call arguments.
(2) Each confunction-instance is stored as a tail-call (a "goto") to the underlying function (thus allowing the cofunction to be called as if it was the function itself), followed by any information needing to be stored between calls.
(3) When the yield command is used, the underlying function directly modifies the tail-call in the confunction to jump to where the function left off (rather than to the start of the function).

70
TI Z80 / Re: OPIA - A full OOP language for z80
« on: January 02, 2012, 05:53:24 pm »
=== AND NOW FOR A MAJOR CHANGE TO THE LANGUAGE ===

THIS IS WHERE I CHANGED THE LANGUAGE SIGNIFICANTLY TO BE
MORE LIKE GOOGLE'S "GO" LANGUAGE AND LESS LIKE JAVA/C#:

Basically I replaced classes & inheritance with a more powerful, flexible, light-weight model for interfaces & composition. This is a what makes Google's Go language a "fast, statically typed, compiled language that feels like a dynamically typed, interpreted language." It's a different approach to polymorphism, involving these changes:

(1) Replace classes with plain structs, but allow them to embed "anonymous" types which simulate a some inheritance using composition.
(2) Methods are declared separately (externally), and can thus be declared as needed and on ANY type.
(3) A datatype automatically implements an interface simply by having the required methods. For example, a function could taking a "Writer" interface could be given ANYTHING that has a "write()" method (like "duck typing"). This allows datatypes (structs) to implement interfaces without even "knowing" it, and without any overhead added to the type (struct).

It would have been silly NOT to do (2) and (3), since they simplify and add power to the language without any major underlying changes. The argument for (1) is that the "class" is removed because it becomes redundant: If you remove the method table from a class and store it separately, you have an "interface" (see chart below).

Class-based model (* denotes a pointer):

struct := [all the data of the struct]
table := [*method, *method, *method, ... ]
class := [*table, struct]
interface := [*table, *class]

New model: (same, but without "class", and *class becomes *object)

This new model provides better access to the underlying mechanisms, and results in simpler code! At the most fundamental level, polymorphism simply involves the use of method-pointers, which is what "virtual" methods are anyway. Thus, one could do it all manually just by storing or passing method-pointers directly (and given (2), method pointers would just be function-pointers, since the "this" argument would be explicit). However, passing an object along with it's methods is EXACTLY what the "interface" is already!

Here is some example code, with some obvious syntax changes made:

   byte x,y,z = a,b,c; // assignment in parallel
   *byte ptr; // ptr is a pointer-to-byte variable
   [5]byte arr; // arr is a static array of 5 elements (not a reference)
   []byte arrP; // arrP is a reference to an array (unassigned)
   *[]byte p2a; // pointer to array
   []*byte aop; // array of pointers
   a,b,c := 1,"yo",ptr; // declaration with type-inference (byte, []char, *byte).

   func f1(byte a, b : char) { ... } // function f1 takes bytes a & b, and returns a char
   func f2( : byte, char) { ... } // function f2 takes nothing, returns a byte and char
   func f3(byte a, b) { ... } // function f3 takes bytes a & b, returns nothing
   func(byte,byte,byte) x; // pointer to some func(byte a,b,c)
   func(byte a,b,c) y; // Same as above (using name-holders)
   x = func(byte a,b,c) { ... }; // anonymous function is declared and assigned to x

   struct A { byte x; } // an A has an x (someA.x)
   struct B { A; byte y; } // Composure: someB.x is short for someB.A.x

   interface foo { // interface foo defined as anything containing:
      foo1(:byte); // a method called foo1 which returns a byte
      foo2(byte); // ...foo2 which takes a byte and returns nothing
   }

   func A.foo1(:byte) { ... } // method for A implementing foo.foo1
   func A.foo2(byte b) { ... } // ... implementing foo.foo2

   A a; ... foo f = foo(a); // interface-instances are constructed around valid vars
   B b; ... f = foo(b); // Invalid, because b.foo1 is really b.A.foo1
   f = foo(blah,blahX,blahY); // overloaded interface instance:
   f.foo1(); f.foo2(5); // calls blah.blahX() and blah.blahY(5)

I later decided that I could allow functions to be declared within structs to designate "virtual functions". By doing so, a function-pointer is actually stored within the struct, with a default value pointing to the provided method body. Virtual methods CAN be used to fulfill interface requirements:

   struct Thing {
      byte x, y;
      func act(); // no method body (in list)
      func compute(:byte) {
         return x*y;
      }
   }

   func Thing.add(:byte) { return x+y; }
   func Thing.sub(:byte) { return x-y; }

   a := Thing{1,2,Thing.add};
   b := Thing(3,4,Thing.sub};

   a.act();   // calls a.add()
   b.act();    // calls b.sub()
   a.compute(); // calls Thing.compute() on a
   a.compute = Thing.add;
   a.compute(); // now calls Thing.add() on a
   b.act = a.compute;
   b.act();   // calls Thing.add on b

71
TI Z80 / Re: OPIA - A full OOP language for z80
« on: January 02, 2012, 05:11:09 pm »
REGARDING PASSING VALUES (to functions):

Previously, function parameters were to be stored (in reverse order) in a static block of memory just before the function body / entry point, so as to be locate-able even from a function-pointer. However, I found that it would be more efficient to allocate the arguments per call and pass the address of the entire allocation. In other words, here is the difference in the old and new process of passing values:

old method:
- Store arguments 1,2,...,n in (func_address - 1), (... - 2), ..., (... - n)
- Call the function
- Arguments are accessed within the function directly, since they are stored in predetermined addresses

new method:
- Store arguments in some static memory location (unique to each call), and store the address of this in the IX register
- Call the function
- Arguments are accessed as offsets of the address stored in IX

This might seem like it takes up more memory because the arguments are allocated per call. The reason this is efficient is that (1) only one value needs to actually be passed, (2) arguments which can be resolved to predetermined values can be preloaded at compile time, (3) once the values are extracted from IX, they can live in registers (i.e. the compiler will convert expressions to SSA form anyway), and (4) this means that when a function is called via a function-pointer, the arguments can be loaded statically rather than determining their location as an offset of the function-address.

Originally, I decided that function arguments should go into static memory addresses (like other variables) because I was under the assumption that the compiler must pick a location for a variable and stick with it. However, the compiler will let the "address" of a variable shift around as it moves from register to register (and to memory as needed). I avoid the loop-hole of "what if a variable is pointed-to by a pointer?" conundrum by not allowing variables to be pointed-to, except when passed to "ref" or "out" parameters (i.e. passed "by reference", which assumes a saved state until the function returns). To clarify, pointers are allowed, and they can point to new allocations and allocations referenced by other pointers; but not to other variables of the same base-type (i.e. without the "pointer" part), unless that variable is declared "final".

I have to credit Ben Ryves for suggesting to use IX rather than HL for passing an address (to allow for offset access), which he says is the way that BBC BASIC passes values.

I do plan to actually pass values within registers, but that is a bit complex, and I want to get it working before I hammer out a complex way of determining which registers to use for which scenarios ... for now though, I can easily let functions which take a single byte or word to use A or HL though (since this is how values are returned anyway).

72
TI Z80 / Re: OPIA - A full OOP language for z80
« on: January 02, 2012, 05:07:50 pm »
REGARDING STRINGS (and a good sample of code prior to the Go-style reformation of the language):

There is no "string" type, but a character array (char[]) is used instead. String literals automatically convert to character arrays followed by a null value (zero). I wanted to have the + operator concatenate strings cleanly, and cleanly means not allocating a whole new array for EVERY concatenation. Java solves this using a "StringBuilder", which I could code (using OPIA code) as follows:

class StringBuilder {
    private class Node {
        public char[] string;
        public Node next;
        public Node(char[] s) { string = s; next = null; }
    }
     
    private Node head, tail;
    private uword length;

    public StringBuilder() { head = tail = null; length = 0; }

    public void concatenate(char[] string) {
        Node n = new Node(string);
        if(head == null) { head = tail = n; }
        else { tail = tail.next = n; }
        for(uword i = 0; string[0] != 0; i++) { }
        length += i;
    }

    public void merge(StringBuilder s) {
        if(s.head == null) { return; }
        if(head == null) { head = s.head; }
        else { tail.next = s.head; }
        tail = s.tail;
        s.head = s.tail = null;
    }

    public char[] toString() {
        uword pos = 0;
        char[] string = new char[length];
        for(Node n = head; n != null; n = n.next) {
            for(uword i = 0; n.string != 0; i++) {
                string[pos++] = n.string;
            }
        }
        return string;
    }
}

abstract class Object {
    public abstract void printTo([char[]] handler);

    public StringBuilder toString() { // intentionally misleading
        StringBuilder s = new StringBuilder();
        printTo(s.concatenate);
        return s;
    }
}

interface Comparable<T> where T : Comparable<T> {
    byte compareTo(T other); // interface members are inherently public abstract


// The compiler then makes these conversions (and pretends that the resulting StringBuilder is "returned" by each):

char[] str;
StringBuilder sb;
...
sb  + sb;  // => sb.merge(sb);
sb  + str; // => sb.concatenate(str);
str + str; // => str.toString().concatenate(str);
str + sb;  // => str.toString().merge(sb);
str = sb;  // => str = sb.toString();
sb  = str; // => sb = new StringBuilder(); sb.concatenate(str);

73
TI Z80 / Re: OPIA - A full OOP language for z80
« on: January 02, 2012, 05:03:46 pm »
REGARDING OBJECT-CONSTRUCTION, INTERPRETED ASPECTS, TYPE-CASTING:

At one point, I provided 3 mechanisms for object (class instance) construction. Given some class called Foo:

Foo f; // No construction used (can be assigned later)
f = Foo(...); // Constructed into a static (anonymous) allocation and then assigned to f
f = new Foo(...); // Constructed into a dynamically allocated location and then assigned to f
f = static Foo(...); // A Pre-constructed (embedded directly into the program) instance is assigned to f
static Foo f = ... // NOT the same as previous statement (static var vs. static instance assigned to var)

This is long gone, because I decided that the compiler can determine when to embed and when/how to allocate as needed. I had also had a $ and a $$ operator, which both told the compiler to interpret certain things, and how strictly. For example, a construct (e.g. a while-loop) marked with $ would be interpreted, but a $$ would mean to also interpret everything (e.g. inner loops) inside of it. A $ function would be inlined, and a $$ function would be inlined AND completely interpreted ($$ was a "deep"/recursive command) ... However, that is a mess! ... Most of the code is pre-interpreted and optimized automatically by tracing values through the code, skipping variables when the same value is known to reside elsewhere, etc. One can still use $ to state that something MUST be interpreted (and with a higher priority), or else an error is given if it is not possible (e.g. values cannot be predicted as needed).

For Type-Casting, I restorted to (expression -> type) syntax. Eventually I replaced all uses of the -> operator ("returns" or "cast") with a colon. Type-casting is not an operation in itself, but more of a guideline to the compiler such that the expression or value is to be TREATED as the specified type. The cast applies to all code previous to the colon. For example, (a+b:byte) performs the addition in such a way that it results in a byte value (i.e. rather than performing it however, and then converting the result to a byte); and (a+(b:byte)) performs the addition is such a way that b is REGARDED as a byte (i.e. only the lower-byte of b is used).

74
TI Z80 / Re: OPIA - A full OOP language for z80
« on: January 02, 2012, 04:40:54 pm »
A random list of points discussed previously on other forums (modified):

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.

MEMORY MANAGEMENT - No Garbage collector will be built-in. However, functions can be written which will enable the "new" (and "delete") operators for the language. The idea is that any kind of memory management can be plugged in (whether coded manually, or already part of the platform/OS). As for static allocations, there are preprocessor directives whuch can be used to signify sections of "same RAM" which the compiler will use for larger things that lack initialization values.

VIRTUAL FUNCTIONS - I was originally going to have the compiler just KNOW when functions should be virtual/late-bound because of the overridden definitions (like Java). However, requiring one to explicitly say which functions are virtual (like C#/C++) means that a the internal representation of a class can be determined without having to look at other code. This is important because OPIA allows you to specify an address where something is already stored (e.g. precompiled or otherwise already existing) rather than allocating space for it otherwise.

ENVIRONMENT DEPENDENCIES - If a program is completely generic (i.e. nothing platform-specific), then it will run anywhere (directly on the bare hardware). Standard representations of functions, arrays, etc. also allow things to be picked out explicitly and used with new code. The whole compilation process is going to be very modularized, such that it can be used an API by other tools (e.g. an IDE which uses the compiler itself to highlight syntax and find errors, but without doing a full compile).

FEATURES - OPIA will ONLY include features which would be difficult to do with the language otherwise, which can be implemented using close to the same overhead NECESSARY as if done manually, and which do not add unnecessary complexity to the language. For example, I was going to have classes & interfaces (I will post more about that later) because one would HAVE to use late-bound functions and virtual tables to simulate that anyway; but I have even replaced classes & inheritance with structs and simpler interfaces because it makes the language simpler, adds more power/flexibility, and provides a much closer representation of the underlying mechanisms used. OPIA will NOT include operator overloading, bounds-checks (or any built-in error handling), excpetions, or assertions (though interpreted aspects can be used to compile code dynamically). I was going to provide parametric types (using type-erasure, since that provides a single representation for each thing); but with the new Go-style interfaces, OPIA might be fine without them ... maybe in the future, but I'm not promising it.

VALUE PASSING - Always pass-by-value. I was going to provide reference-types, which use pointers internally, but now that I've switched to a simpler Go-like setup, one can just use pointers directly as needed.

NAMING - I really do not see what it wrong with the name "OPIA" (except for the similar spelling to "opium"); but the only other name suggested so far that I even like at all was "Candu" (Malay for "opium"). No Z++, because there are too many letters and pluses and sharps etc. already. No "TI" anything, because it will run on ANY z80 machine (though heck yes, TI's are the focus) ... but seriously, is "OPIA" such a bad name? (Object-oriented Pre-Interpreted Antidisassemblage ... though admittedly, the Go-style is polymorphic and object-based without being truely object-oriented in the traditional sense).

75
TI Z80 / Re: OPIA - A full OOP language for z80
« on: January 02, 2012, 01:37:20 am »
It's hard to say. The vast majority of the time spent on it (more than 95%) has been conversational and hammering out all the theory. Look up "Antidisassemblage" (but make sure you find version 1.6 alpha), which is a precursor. I spent 2ish years on it, and roughly 6 months of that involved coding what it is; and not more than 20% of that time was actual coding. OPIA is me deciding to scrap that altogether and start all over again, but with much of the same ideas ... so you could say I've been at it for 6 years (not counting a 2 year leave). OPIA was GOING to be very Java/C#-ish, and was VERY nearly all figured out; but then I came across the design of Google's "Go" language, which offers the same polymorphism in a more flexible and simplified form. Thus, just over a month ago, I redesigned the language (again (again (...))); but I have some code in place now, and I spent perhaps a month on the Tokenizer (between school etc.), and basically the last week writing the Preprocessor (finished), and I have yet to write the syntax-tree stuff (at least 2 weeks), the code analysis stuff (at least a month), the assembly conversion (at least a month), other aspects between those (a week), and other touch ups etc. (another week) ...

... So my guess is at LEAST 3 months from now; and that is assuming that school and moving etc. do not push it way to the back (which is likely) and that I do not run into other major changes I want to make (I can scarcely think of any way to improve it more) ... but supposing there are delays, probably 6 months ... I want to have it pretty well set within this year though.

However, even now, there are working & testable aspects of the compiler, and I'd like people to try things out and give me feedback if they want to assist. ... Random thought: if I finish this up, and there is a demand for it, perhaps I can make another version that uses the Java/C#-ish form instead (it would be very similar though and use a lot of the same compiler parts) ... but I don't know if that is likely. I chose the new design because it was simpler and more powerful and lighter weight, which I felt was appropriate for a z80/embedded-ish language.

Pages: 1 ... 3 4 [5] 6