Re: best way to write a source code generator?



In article <es6rplqa92xh$.y3h4w7qivo94.dlg@xxxxxxxxxx>,
Frank Buss <fb@xxxxxxxxxxxxx> wrote:

If I want to create Java files and files in other languages like this:

public class Test {
public int foo;
public String bar;
public int getFoo() {
return foo;
}
public void setFoo(int foo) {
this.foo = foo;
}
public String getBar() {
return bar;
}
public void setBar(String bar) {
this.bar = bar;
}
public String serialize() {
return "foo=" + foo + " bar=" + Utils.escapeString(bar);
}
public static Test deserialize(String data) {
Test result = new Test();
String elements[] = data.split(" ");
for (String element in elements) {
String pair = element.split("=");
String name = pair[0];
String value = pair[1];
if (name.equals("foo")) {
foo = Integer.parseInt(value);
} else if (name.equals("bar")) {
bar = value;
}
}
}
}

How do I write a generator for this in Lisp? I've started with this:

(define-class "Test" ()
(("foo" number)
("bar" string)))

Why start with a macro? Start with the data. Define the data
and write procedures to process the data.
A macro can be added later. In this early design stage it
makes debugging hard. Think functions.

Start with one java class. Process the components of the class.
You really need to think about bottom up design in Lisp.


With this definitions:

(defparameter *classes* (make-hash-table :test 'equal))

(defmacro define-class (name base-class fields)
`(setf (gethash ,name *classes*) (cons (car ',base-class) ',fields)))

But then it starts getting ugly:


Again, output adds just another complexity in early design.
Output to files can be added later. Start with writing to
a stream, for example just standard output. Iteration over
a data structure is also to be added later.

write-java-class (class stream)
write-java-class-component ...
write-java-class-component ...
write-java-class-component ...
write-java-class-component ...

write-java-classes (classes stream)

write-java-classes-to-file (classes pathname)

Build a language of operators that work for what you want
to do. Build these operators so that they can be easily
combined (FUNCTIONS) or extended (GENERIC FUNCTIONS).

(defun write-java-classes ()
(loop for class being the hash-keys in *classes* using (hash-value value)
do
(let* ((filename (concatenate 'string *java-directory* class
".java"))
(custom-section (read-custom-section filename)))
(format t "writing file ~a~%" filename)
(with-open-file (s (concatenate 'string filename)
:direction :output
:if-exists :supersede)
(destructuring-bind (base-class . fields) value
(format s "public class ~a ~a implements Foo {~%"
class
(if base-class (format nil "extends ~a" base-class)
""))
(write-custom-section s custom-section)
(loop for (name type) in fields do
(format s " public ~a ~a = ~a;~%"
(get-java-type-name type)
name
(get-default-value type)))
(format s " public String serialize() throws Exception {~%")
...

Lots of formats and difficult to read code, because of mixed target
language code within format strings and Lisp code, which is difficult to
maintain. How can I improve this?

CL-HTTP has several examples for languages embedded in Lisp.
For example to write some HTML versions. It uses an approach
to define functions and macros representing code generators
for the target language. In the Lisp user code you would then
use the macros and functions to generate the target code. There
you won't see any FORMAT statements anymore. Macros would be
used to create scope (WITH-JAVA-CLASS , ...) and functions
would be used to create output (WRITE-JAVA-PARAMETER-LIST, ...).
The code is optimized to cons as little as possible.
.



Relevant Pages