;;;; AST-based C# pretty-printer demo ;;;; Written by Ivan Shvedunov ;;;; (ivan4th/fionbio, ivan4th AT gmail DOT com), 2005 ;;;; ;;;; Inspired by DPP (Dylan Pretty-Printer, a part of Peter Norvig's ;;;; Lisp to Dylan Converter - http://www.norvig.com/ltd/doc/ltd.html) ;;;; and by Dick Waters' paper "Some Useful Lisp Algorithms: Part 2" ;;;; (http://www.merl.com/publications/TR1993-017/) ;;;; See also: XP pretty-printer documentation in XP distribution ;;;; http://www-2.cs.cmu.edu/afs/cs.cmu.edu/project/ai-repository/ai/lang/lisp/code/io/xp/xp.tgz ;;;; ;;;; Requires standard-conforming pretty printer. ;;;; Will not work on GNU CLisp. Tested with SBCL (0.9.2, 0.8.16), ;;;; Allegro CL 6.2 and LispWorks 4.3. ;;;; ;;;; This software is "as is", and has no warranty of any kind. The ;;;; author assumes no responsibility for the consequences of any use ;;;; of this software. (in-package :cl-user) ;;; AST printer, much more concise than your CodeDOM and perhaps R# :-P ;;; Can be easily extended to support other language constructs & ;;; several languages simultaneously ;; pretty-print dispatch table: type -> printing func correspondence (defvar *dispatch* (copy-pprint-dispatch)) (defun ast-print (x &optional (stream *standard-output*)) "Print the expression x using AST dispatch rules" (write x :pretty t :pprint-dispatch *dispatch* :stream stream)) (defun ast-print-form (stream form) "Print the AST node (form)" (if (and (symbolp (first form)) (get (first form) 'ast-form)) (funcall (get (first form) 'ast-form) stream form) (error "unknown form: ~S" form))) ;; set dispatch function for cons type in our pretty-print dispatch table (set-pprint-dispatch 'cons #'ast-print-form 0 *dispatch*) (defmacro defprinter (name args &body body) "Define a printer for specified AST node type" (let ((func-name (intern (format nil "PRINT-FORM-~A" name))) (item (gensym))) `(progn (defun ,func-name (stream ,item) (destructuring-bind ,args (rest ,item) ,@body)) (setf (get ',name 'ast-form) ',func-name)))) ;;; AST printing specs for C# ;;; (write other specs, get VB.NET, Java, C++, Fortran, etc.) ;; please note that the following function could be writen in a more ;; clear way using pprint-* funcs, but now I'm just too lazy (defun print-statement-with-body (stream body two-newlines fmt &rest args) "Print the statement with brace-enclosed body" (format stream (if two-newlines "~?~:@_~@<{~4i~{~:@_~w~^~:@_~}~i~:@_}~:>" "~?~:@_~@<{~4i~{~:@_~w~}~i~:@_}~:>") fmt args body)) ;; identifier - printed literally (defprinter :id (id) (write-string id stream)) ;; private int x; (defprinter :field (type name) (format stream "private ~a ~a;" type name)) ;; public int SomeProp { ... } (defprinter :property (type name &rest body) (print-statement-with-body stream body nil "public ~a ~a" type name)) ;; get { ... } (defprinter :get (&rest body) (print-statement-with-body stream body nil "get")) ;; set { ... } (defprinter :set (&rest body) (print-statement-with-body stream body nil "set")) ;; return x; (defprinter :return (expr) (format stream "return ~w;" expr)) ;; argument (defprinter :arg (type name) (format stream "~a ~a" type name)) ;; constructor (defprinter :constructor (name args &rest body) (print-statement-with-body stream body nil "~a(~@<~{~w~^, ~_~}~:>)" name args)) ;; a = b; (defprinter :setf (lvalue rvalue) (format stream "~w = ~w;" lvalue rvalue)) ;; public class SomeClass { ... } (defprinter :class (name &rest body) (print-statement-with-body stream body t "public class ~a" name)) ;; namespace SomeNS { ... } (defprinter :namespace (name &rest body) (print-statement-with-body stream body t "namespace ~a" name)) ;; comment (defprinter :comment (text) (format stream "// ~a" text)) ;;; Data class generation (defun generate-csharp-class (object) "Generate a C# class from DSL spec" (destructuring-bind (class-name . properties) (rest object) (loop for (nil prop-name prop-type) in properties for sharp-prop-name = (format nil "~:(~a~)" prop-name) ; C# property name for field-name = (concatenate 'string "_" prop-name) ; C# field name collect `(:arg ,prop-type ,prop-name) into init-args collect `(:setf (:id ,field-name) (:id ,prop-name)) into init nconc `((:field ,prop-type ,field-name) (:property ,prop-type ,sharp-prop-name (:get (:return (:id ,field-name))) (:set (:setf (:id ,field-name) (:id "value"))))) into props finally (return `(:class ,class-name (:constructor ,class-name () (:comment "NOOP")) (:constructor ,class-name ,init-args ,@init) ,@props))))) (defun generate-csharp-classes (data) "Generate C# classes from DSL spec" (destructuring-bind (ns-name . classes) (rest data) `(:namespace ,ns-name ,@(mapcar #'generate-csharp-class classes)))) ;;; Facade (defun make-data-class-file (data file-name) "Generate a C# source file from data class descriptions" (with-open-file (s file-name :direction :output :if-does-not-exist :create :if-exists :supersede) (ast-print (generate-csharp-classes data) s) nil)) ;;; Test data (defparameter *tst-ast* '(:namespace "MyTestNS" (:class "MyTestClass" (:field "int" "_someprop") (:constructor "MyTestClass" ((:arg "int" "someprop")) (:comment "This is a test") (:setf (:id "_someprop") (:id "someprop"))) (:property "int" "SomeProp" (:get (:return (:id "_someprop"))) (:set (:setf (:id "_someprop") (:id "value")))) (:property "int" "AnotherProp" (:get (:return 0)))))) (defparameter *tst-data* '(namespace "MyNS" (object "Obj1" (property "prop1" "int") (property "prop2" "string") (property "dblprop" "double")) (object "Obj2" (property "someprop" "object[]")))) ;; try: (make-data-class-file *tst-data* "c:\somefile.cs") |