Brad Phelan
5/2/2007 4:15:00 PM
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