Lx0: Prototyp -- Klasse
Die Deklaration von Klassen und der Aufruf von Methoden sind im Prototyp meiner Programmiersprache Lx0 hinzugekommen. Womit nun folgendes funktioniert:
public class MyCounter extends Root
private uword $count;
private uword $mycount;
public foo()
helloworld;
end foo
public bar(ubyte $dip)
print $dip;
end bar
endclass MyCounter
MyCounter $obj;
$obj := new MyCounter();
$obj.foo();
$obj.bar(42);
Der für die Klassen generierte Programmcode entspricht dem in OOP in C beschriebenen.
Im Compiler ist für die Verarbeitung der Klassen und insbesonders der Codegenerierung der zwei Dateien pro Klasse (.h- und .c-Datei) einiges hinzugekommen.
.c und .h
Zur Generierung der .h- und .c-Datei einer Klasse wird der AST zweimal durchlaufen. Einmal mit den C_h.stg- und ein zweites Mal mit den C_c.stg-Templates. Diese Templategruppen erben alle Templates aus C.stg und fügen weitere hinzu. Die Vererbung geschieht mit group C_h: C;
group C_h: C;
class(modifier,name,extends,attrs,methodsPointer,methodsDecl) ::= <<
#ifndef _<name; format="toUpper">_CLASS_H
#define _<name; format="toUpper">_CLASS_H
#ifdef __cplusplus
extern "C" {
#endif
typedef struct <name>_Attributes {
struct <name>_Methods *vtable;
<attrs; separator="\n">
} <name; format="toUpper">;
typedef struct <name>_Methods {
void *super; // vtable of super class
char *classname;
void (*constructor) (<name; format="toUpper"> *this);
<methodsPointer; separator="\n">
} <name>_Methods;
void <name>_constructor(<name; format="toUpper"> *this);
<methodsDecl; separator="\n">
extern <name>_Methods <name>_vtable;
#ifdef __cplusplus
}
#endif
#endif /* <name; format="toUpper"> */
>>
attrDecl(type,id) ::= <<
<type> <id>;
>>
methodDeclPointer(classname,name,pars,block) ::= <<
void (*<name>) (<classname; format="toUpper"> *this<if(pars)>, <pars; separator=", "><endif>);
>>
methodDecl(classname,name,pars,block) ::= <<
void <classname>_<name>(<classname; format="toUpper"> *this<if(pars)>, <pars; separator=", "><endif>);
>>
Die so generierten Quelltexte werden in public List<ClassSource> classes = new ArrayList<ClassSource>(); vom Treewalker gesammelt und innerhalb von Compiler.java auf die Festplatte geschrieben:
// .h file
TreeWalker walker = new TreeWalker(nodes);
walker.setTemplateLib(templates_h);
String csrc = walker.program().toString();
if(walker.errors > 0) {
System.err.println(walker.errors + " error(s).");
System.exit(1);
}
for(ClassSource cs: walker.classes) {
System.out.println(cs.name);
//System.out.println(cs.source.toString());
// Write .h file
writeFile(cs.name + "_class.h", cs.source.toString());
}
Eine Alternative zu 2 Templates ist die Nutzung einer if-Abfrage innerhalb eines Templates:
methodDecl(classname,name,pars,block,isPointer) ::= << <if(isPointer)> ... <else> ... <endif> >>
methodDeclPointer() und methodDecl()
Eine zweite Besonderheit ist, dass auch pro Methode jeweils zwei unterschiedliche Quelltexte in der .h-Datei generiert werden müssen. Aus
public foo()
helloworld;
end foo
wird wird die .h-Datei:
typedef struct MyCounter_Methods {
void *super; // vtable of super class
char *classname;
void (*constructor) (MYCOUNTER *this);
void (*foo) (MYCOUNTER *this);
void (*bar) (MYCOUNTER *this, unsigned char dip);
} MyCounter_Methods;
void MyCounter_constructor(MYCOUNTER *this);
void MyCounter_foo(MYCOUNTER *this);
void MyCounter_bar(MYCOUNTER *this, unsigned char dip);
Dies wird mit den zwei Templates methodDeclPointer() und methodDecl() gelöst. Da ANTLR meines Wissens darauf ausgelegt ist im Treewalker nur ein Template pro Regel zu benutzen, wird das zweite Template per direkten Aufruf von templateLib.getInstanceOf("methodDeclPointer", ... benutzt und als zusätzlicher Rückgabewert angegeben: Treewalker.g
methodDecl returns [StringTemplate methodPointer]
: ^(modifier nameBegin=METHODNAME
[...]
$methodPointer = templateLib.getInstanceOf("methodDeclPointer",
new STAttrMap().put("classname", $classDecl::classname)
.put("name", $nameBegin.text)
.put("pars", $pars)
.put("block", $block.st));
}
-> methodDecl(classname={$classDecl::classname},name={$nameBegin.text},pars={$pars},block={$block.st})
;
format="toUpper" für den Klassennamen
An einigen Stellen wird der Klassenname ClassName durchgehend in Großbuchstaben benötigt CLASSNAME. Dies ist mit dem AttributeRenderer von StringTemplate umgesetzt. Innerhalb der Templates steht:
<classname; format="toUpper">
Damit das toUpper eine Funktion erhält muss es in einem eigenen AttributeRenderer definiert und dieser Renderer für die String-Klasse registriert werden. Beides geschieht in Compiler.java:
class TemplateFormatRenderer implements AttributeRenderer {
public String toString(Object o) {
return o.toString();
}
public String toString(Object o, String formatName) {
if (formatName.equals("toUpper")) {
return o.toString().toUpperCase();
} else {
throw new IllegalArgumentException("Unsupported format name");
}
}
}
[...]
templates.registerRenderer(String.class, new TemplateFormatRenderer());
Als nächstes: Aufbau des Lx0-Compilers
Im Zusammenhang mit Klassen fehlt noch einiges:
- Vererbung
- Typüberprüfung beim Methodenaufruf
- Konstruktor deklarieren und aufrufen
- Destruktor
- Eigenschaften lesen/setzen
- Symbole aus Klassen anderer Quelltexte einlesen
- private, public verarbeiten
- lx0lib.a
Bevor das umgesetzt wird, macht es jetzt erst einmal Sinn zu überlegen wie der Compiler im Ganzen aufgebaut ist und die Klassen, Typen, generierten Quelltexte, ... verwaltet werden, um einen Compiler zu ergeben, der aus vielen einzelnen Lx0-Quelltexten die passenden C-Quelltexte für ein ausführbares Programm erzeugt.
Quelltext
Der komplette Quelltext des Lx0-Compilers steht in Subversion.
