From: Brad Phelan Date: 2007-05-03T01:15:11+09:00 Subject: C DSL anyone? Just curious, is anybody working on a C language DSL that could generate C code. I'm kinda interested in how it may be possible to use RSpec to unit test C code in a nice way. My first attempt at a C DSL allows the following example. require 'cexpression.rb' code = CTools::CCode.new code = CTools::CCode.open do _decl :a, :ubyte2 _decl :b, :ubyte2 _decl :c, :ubyte2 _typedef :Point do _struct do _decl :x, :ubyte4 _decl :y, :ubyte2 _decl :z, :ubyte1 end end _decl :point, :Point _for(a.assign(0), a < 1, a.inc(1)) do _while(b < 1) do _for(c.assign(0), c < 1, c.inc(1)) do _let b, b + 1 _printf "%d %d %d", point.x, point.y, point.z end end end end puts code # generates ubyte2 a; ubyte2 b; ubyte2 c; typedef struct { ubyte4 x; ubyte2 y; ubyte1 z; }Point; Point point; for (a = 0; a < 1; a += 1){ while (b < 1){ for (c = 0; c < 1; c += 1){ b = b + 1 printf("%d %d %d", point.x, point.y, point.z ); } } } =========================== library cexpression.rb is quite simple =========================== module CTools # Indent a block of C code def CTools.c_indent(text) indent_text = " " indent = 0; cont = false; out_text = [] text.each_line do |line| line.gsub!(/^\s*/,"") if line =~ /\{.*\}/ line.gsub!(/^/,indent_text[1..indent]) else if line =~ /\}/ indent = indent - 3 end line.gsub!(/^/,indent_text[1..indent]) if line =~ /\{/ indent = indent + 3 end # Convert "/**/" into blank lines line.gsub!(/^\s*\/\*\*\//,'') end # Indent on unmatched round brackets indent = indent + ( line.count("(") - line.count(")") ) * 3 # Indent on backslash continuation if cont if line !~ /\\$/ indent = indent - 6 cont = false end else if line =~ /\\$/ indent = indent + 6 cont = true end end out_text << line end out_text.join end class CExpr # Operator attr_reader :op # Arguments ( Sub expressions ) attr_reader :args def initialize(*args) @args = args end def to_s "#{@op}(" + args.collect { |a| a.to_s }.join(', ') + ")" end ##### Operators and Calls ########## def assign(val) method_missing "=", val end def inc( val ) method_missing "+=", val end def decr( val ) method_missing "-=", val end def method_missing(meth_id, *args) case meth_id.to_s when '=', '+=', '-=', '>=', '<=', '<', '>', '+', '-', '*', '/' BinOp.new(meth_id, self, *args) when '[]' ArrayOp.new(self, *args) else BinOp.new(".", self, CExpr.new(meth_id, *args)) end end end class BinOp < CExpr def initialize(op, *args) @op = op @args = args if args.length != 2 raise :BadNumberOfArgs else end end def to_s case @op when '.' "(#{args[0]}.#{CTools.debracket(args[1].to_s)})" else "(#{args[0]} #{@op} #{args[1]})" end end end class ArrayOp < CExpr def initialize(op, *args) @op = op @args = args end def to_s "#{op}[" + args.join(', ') + "]" end end class CVar < CExpr attr :name attr :type def initialize(name, type) @name = name; @type = type; end def decl "#{type} #{name};" end def to_s name.to_s end end def self.debracket(str) (str.gsub(/^\(/,'')).gsub(/\)$/,'') end class BlankSlate instance_methods.each { |m| case m when /^__/ when /instance_eval/ else undef_method m end } end class CCode private def initialize @buffer = "" end def new end public def self.open &block code = CCode.new code.instance_eval &block code.to_s end def method_missing(meth, *args) @buffer << meth.to_s.gsub(/^_/,'') << "(" << args.collect{ |a| case a when String # Literal strings are output quoted '"' + a + '"' else # Other objects are considered names CTools.debracket(a.to_s) end }.join(', ') << " );\n" end def scope( lf=true) @buffer << "{\n" yield @buffer << "}" @buffer << "\n" if lf end def <<( e ) s = CTools::debracket(e.to_s) @buffer << s << ";\n" end def _if(cond, &block) @buffer << "if (#{cond})" scope &block end def _else(&block) @buffer << "else" scope &block end def _let(a, b) @buffer << "#{a} = #{CTools.debracket(b.to_s)}\n" end def _for(init, cond, inc, &block) init = CTools.debracket(init.to_s) cond = CTools.debracket(cond.to_s) inc = CTools.debracket(inc.to_s) @buffer << "for (#{init}; #{cond}; #{inc})" scope &block end def _while(cond, &block) cond = CTools.debracket(cond.to_s) @buffer << "while (#{cond})" scope &block end def _typedef name, &block @buffer << "typedef " yield @buffer << name.to_s << ";\n" end def _struct (name="", &block) @buffer << "struct #{name}" # Evaluate the struct declarations in a new # scope @buffer << CTools::CCode.open do scope false do instance_eval &block end end @buffer << ";\n" if name != "" end # Declare a variable in scope and # add an instance method to retrieve # the symbol def _decl name, type="void *" var = CVar.new(name, type) @buffer << var.decl << "\n" self.class.send(:define_method, name){ var } end def to_s CTools.c_indent @buffer end end end