tree transformation insight required

L

Luke Graham

Hi list,

I was wondering if anyone would have some insight into a problem Ive got.

Im taking an xml document (looks very much like a w3c schema with some
extra attributes) and transforming it into c++ code. The method I use looks
something like...

def recurse(file, func, element)
case func
when "declare"
file << element.attributes["type"] << element.attributes["name"]
when "instantiate"
file << element.attributes["name"] << " = new " << element.attributes["type"]
do wierd stuff based on type and state
end
element.each_child { |c|
recurse file, func, c
}
end

headerfile << "some template"
doc.each_child { |c|
recurse headerfile, "declare", c
}
headerfile << "some more template"

cppfile << "some template"
doc.each_child { |c|
recurse cppfile, "instantiate", c
}
cppfile << "some more template"

(I know doc can only have one child but I use the xpath bit of rexml,
like doc.elements.each("/*/foo"), just couldnt be bothered typing it)

Needless to say, recurse is now ~500 lines, and has more than a few
oddities like a begin-ensure block, special cases that dont recurse,
about 4 places where it calls itself, some of which dont use the
same function they were passed, lots of places that do strange things
to the state and so on.

Id like to get the logic out of the recurse statement for three reasons.
First of all, Im starting recurse up more times than Ive shown
above, because I have four separate sets of header/cpp templates,
not to mention I would like to add other templates that arent c++ code.
It would be good to know that each case could be handled safely
without interfering with each others output and special cases.
Secondly, someone else would like to write their own templated
code, and their required output would be different. Thirdly, recurse is
simply becoming scary.

Can anyone suggest a good way to get the c++-producing code out
of recurse and into some sort of template language, or into functions
that get passed into simplified recurse? I figured those functions
would need to return functions (usually themselves) to simulate
some state changes, ala...

def recurse(file, func, element)
file << func.call "pre", element
element.each_child { |c|
recurse file, func.call("getfunc",element), c
}
file << func.call "post", element
end

Or would I be better doing this...

def sampleDeclare(file, element)
...
end
def startDeclare(file, element)
file << "pre declare"
element.each_child { |c|
sampleDeclare.call file, c # choose different funcs based on state
}
file << "post declare"
end
startDeclare headerfile, doc.root

I think that dragging state around might get hard after a while tho and
the recursion would be specified many times.

Sorry for the long post and thanks for reading this far!
 
R

Robert Klemme

Luke Graham said:
Hi list,

I was wondering if anyone would have some insight into a problem Ive got.

Im taking an xml document (looks very much like a w3c schema with some
extra attributes) and transforming it into c++ code. The method I use looks
something like...

def recurse(file, func, element)
case func
when "declare"
file << element.attributes["type"] << element.attributes["name"]
when "instantiate"
file << element.attributes["name"] << " = new " << element.attributes["type"]
do wierd stuff based on type and state
end
element.each_child { |c|
recurse file, func, c
}
end

headerfile << "some template"
doc.each_child { |c|
recurse headerfile, "declare", c
}
headerfile << "some more template"

cppfile << "some template"
doc.each_child { |c|
recurse cppfile, "instantiate", c
}
cppfile << "some more template"

(I know doc can only have one child but I use the xpath bit of rexml,
like doc.elements.each("/*/foo"), just couldnt be bothered typing it)

Needless to say, recurse is now ~500 lines, and has more than a few
oddities like a begin-ensure block, special cases that dont recurse,
about 4 places where it calls itself, some of which dont use the
same function they were passed, lots of places that do strange things
to the state and so on.

Id like to get the logic out of the recurse statement for three reasons.
First of all, Im starting recurse up more times than Ive shown
above, because I have four separate sets of header/cpp templates,
not to mention I would like to add other templates that arent c++ code.
It would be good to know that each case could be handled safely
without interfering with each others output and special cases.
Secondly, someone else would like to write their own templated
code, and their required output would be different. Thirdly, recurse is
simply becoming scary.

Can anyone suggest a good way to get the c++-producing code out
of recurse and into some sort of template language, or into functions
that get passed into simplified recurse? I figured those functions
would need to return functions (usually themselves) to simulate
some state changes, ala...

def recurse(file, func, element)
file << func.call "pre", element
element.each_child { |c|
recurse file, func.call("getfunc",element), c
}
file << func.call "post", element
end

Or would I be better doing this...

def sampleDeclare(file, element)
...
end
def startDeclare(file, element)
file << "pre declare"
element.each_child { |c|
sampleDeclare.call file, c # choose different funcs based on state
}
file << "post declare"
end
startDeclare headerfile, doc.root

I think that dragging state around might get hard after a while tho and
the recursion would be specified many times.

Sorry for the long post and thanks for reading this far!

Sounds like a use case for the visitor pattern. The traversal is already
implemented in REXML so you don't need to do it yourself. Just write a
visitor class that is notified of each node visited and can act
accordingly. Rough outline:

class Visitor
def initialize(dom)
@dom=dom
@visitors = {"foo" => lambda {|n| ... }, "bar" => lambda {|n|...}}
end

def visit_all() @dom.each("xpath") {|n| @visitors[n.name].call(n)}

# other methods for dealing with nodes...
end

Kind regards

robert
 
S

Steven Jenkins

Luke said:
Hi list,

I was wondering if anyone would have some insight into a problem Ive got.

Im taking an xml document (looks very much like a w3c schema with some
extra attributes) and transforming it into c++ code. The method I use looks
something like...

def recurse(file, func, element)
case func
when "declare"
file << element.attributes["type"] << element.attributes["name"]
when "instantiate"
file << element.attributes["name"] << " = new " << element.attributes["type"]
do wierd stuff based on type and state
end
element.each_child { |c|
recurse file, func, c
}
end

[....]

Id like to get the logic out of the recurse statement for three reasons.
First of all, Im starting recurse up more times than Ive shown
above, because I have four separate sets of header/cpp templates,
not to mention I would like to add other templates that arent c++ code.
It would be good to know that each case could be handled safely
without interfering with each others output and special cases.
Secondly, someone else would like to write their own templated
code, and their required output would be different. Thirdly, recurse is
simply becoming scary.

I've done something similar to your first alternative, but using blocks.
That's somewhat more Rubyesque, IMO. (This is not for XML, but the
idea's the same.)

def descend_with_indicators(depth, &block)
yield :item, depth, self.item
unless children.empty?
yield :down, depth, nil
children.each do |c|
c.descend_with_indicators(depth + 1, &block)
end
yield :up, depth, nil
end
end

Invoke it as

root.descend_with_indicators(0) do |ind, dpth, item|
# ind will be one of :down, :item, or :up
# :down and :up indicate change in depth
# :item indicates an actual node in the tree
# if you don't need depth, you can omit it everywhere
end

Steve
 
L

Luke Graham

Steve,

thanks for your reply. I ended up creating a little template language instead.
Heres the code if youre interested. I can post it to RAA/Rubyforge/whatever
if people are finding it useful, so if you do use it, send me an email please.
Lexer is the LittleLexer package written by John Carter, and can be found
at http://littlelexer.rubyforge.org/

Known bug: placing a '.' in the INPUT line (and possibly elsewhere) confuses
the parser.

EXAMPLE TEMPLATE
%% COMMENT This is a comment and will be ignored %%
%% INPUT input %%

%</foo/*/*
// %hop% %hop%

%</foo/*
void %tagname% {
%<./jim // %hop% can hop %parentheight% high >%
%<./jim // %hop% can hop %parentwidth% wide >%
int %width% = %height%;
}

EXAMPLE XML DATA (input.xml as listed in the templates INPUT cmd)
<foo size="10">
<bar width="bb" height="11">
<jim hop="bunny"/>
</bar>
<bar2 width="rr" height="21">
<jim hop="rabbity"/>
</bar2>
</foo

EXAMPLE OUTPUT
// bunny bunny

// rabbity rabbity



void bar {
// bunny can hop 11 high
// bunny can hop bb wide
int bb = 11;
}

void bar2 {
// rabbity can hop 21 high
// rabbity can hop rr wide
int rr = 21;
}


CODE
#!/usr/bin/ruby

require "rexml/document"
include REXML # namespace
require "digest/md5"
require "ftools"
require "Lexer"

commands = ["COMMENT", "INPUT"]
$inputfiles = []

$lexer = Lexer.new [
[%r{\A\s*%%\s*(#{commands.join "|"})[^%]*%%},?i ], # Command + args
[%r{\A%<\s*[^\s]*},?o ], # Open brace and xpath
[%r{\A>%},?c ], # Close brace
[%r{\A%\w+%},?m ], # Macro
[%r{\A[^%?<?%>?]*},?j ], # Junk
[%r{\A\s+}m, ?\s], # Whitespace ' '
]

$cmdlexer = Lexer.new [
[%r{\A\s*%%\sINPUT\s},?i ], # Input
[%r{\A\s*%%\sCOMMENT\s},?c ], # Input
[%r{\A\s*%%},?e ], # End of cmd
[%r{\A\w+},?a ], # Argument
[%r{\A\s+}m, ?\s], # Whitespace
]

$xpathlexer = Lexer.new [
[%r{\A%<}mx,?o ], # Open brace
[%r{\A\s*[^\s]+\s*},?x ], # Anything else is part of the xpath
[%r{\A\s+}m, ?\s], # Whitespace
]

def expand(string, node)
braces = []
inputfiles = []
xpath = []
expansion = ""
index = 0

tokenstr, result = $lexer.scan string
tokens = tokenstr.split("")

while index < tokens.size
begin
token = tokens[index]
case token
when 'i' # Command
ctokens, cresult = $cmdlexer.scan result[index]
cmode = false
ctokens.split("").each_with_index { |t,i|
(cmode = t; next) if t != 'a'
case cmode
when 'i' # Include xml data file
inputfiles << cresult
end
}
when 'o' # Open brace
next if !node
# Xpath
xtokens, xresult = $xpathlexer.scan result[index]
xtokens.split("").each_with_index { |t,i|
xpath << xresult if t == 'x'
}
braces << index + 1
when 'c' # Close brace
next if !node
return expansion if node and braces.empty?
brace = braces.pop
tmp = result.indices(brace..index).join ""
node.elements.each(xpath.pop) { |e|
expansion << expand(tmp, e)
}
when 'm' # Macro
next if !node or !braces.empty?
attrname = result[index].gsub(/%/, "")
search = (attrname =~ /^parent/ ? (attrname.gsub!
/^parent/,""; node.parent) : node)
if attrname == "tagname"
expansion << search.name
elsif search.attributes.key? attrname
expansion << search.attributes[attrname]
else
puts "No such attribute #{attrname} in #{search}"
expansion << result[index]
end
when 'j' # Junk
next if !node or !braces.empty?
expansion << result[index]
end
ensure
index += 1
end
end

return (node ? expansion : inputfiles)
end

# Pass 1 extracts input commands
(puts "Please supply a template file on the cmd line"; exit) if !$*[0]
puts "First pass"
inputfiles = expand IO.readlines($*[0]).to_s, nil

# Pass 2 expands the template
puts "Second pass"
inputfiles.each { |filename|
file = File.new(filename + ".xml", File::RDONLY)
document = Document.new file
file.close
outfile = File.new(filename + ".out", File::CREAT|File::TRUNC|File::WRONLY)
outfile << expand(IO.readlines($*[0]).to_s, document)
outfile.close
}



Luke said:
Hi list,

I was wondering if anyone would have some insight into a problem Ive got.

Im taking an xml document (looks very much like a w3c schema with some
extra attributes) and transforming it into c++ code. The method I use looks
something like...

def recurse(file, func, element)
case func
when "declare"
file << element.attributes["type"] << element.attributes["name"]
when "instantiate"
file << element.attributes["name"] << " = new " << element.attributes["type"]
do wierd stuff based on type and state
end
element.each_child { |c|
recurse file, func, c
}
end

[....]

Id like to get the logic out of the recurse statement for three reasons.
First of all, Im starting recurse up more times than Ive shown
above, because I have four separate sets of header/cpp templates,
not to mention I would like to add other templates that arent c++ code.
It would be good to know that each case could be handled safely
without interfering with each others output and special cases.
Secondly, someone else would like to write their own templated
code, and their required output would be different. Thirdly, recurse is
simply becoming scary.

I've done something similar to your first alternative, but using blocks.
That's somewhat more Rubyesque, IMO. (This is not for XML, but the
idea's the same.)

def descend_with_indicators(depth, &block)
yield :item, depth, self.item
unless children.empty?
yield :down, depth, nil
children.each do |c|
c.descend_with_indicators(depth + 1, &block)
end
yield :up, depth, nil
end
end

Invoke it as

root.descend_with_indicators(0) do |ind, dpth, item|
# ind will be one of :down, :item, or :up
# :down and :up indicate change in depth
# :item indicates an actual node in the tree
# if you don't need depth, you can omit it everywhere
end

Steve
 
L

Luke Graham

Fixed the bug...
Change the argument line in cmdlexer to
[%r{\A[^\s%]+},?a ], # Argument
and change
file = File.new(filename + ".xml", File::RDONLY)
to
file = File.new(filename, File::RDONLY)


Steve,

thanks for your reply. I ended up creating a little template language instead.
Heres the code if youre interested. I can post it to RAA/Rubyforge/whatever
if people are finding it useful, so if you do use it, send me an email please.
Lexer is the LittleLexer package written by John Carter, and can be found
at http://littlelexer.rubyforge.org/

Known bug: placing a '.' in the INPUT line (and possibly elsewhere) confuses
the parser.

EXAMPLE TEMPLATE
%% COMMENT This is a comment and will be ignored %%
%% INPUT input %%

%</foo/*/*
// %hop% %hop%

%</foo/*
void %tagname% {
%<./jim // %hop% can hop %parentheight% high >%
%<./jim // %hop% can hop %parentwidth% wide >%
int %width% = %height%;
}

EXAMPLE XML DATA (input.xml as listed in the templates INPUT cmd)
<foo size="10">
<bar width="bb" height="11">
<jim hop="bunny"/>
</bar>
<bar2 width="rr" height="21">
<jim hop="rabbity"/>
</bar2>
</foo

EXAMPLE OUTPUT
// bunny bunny

// rabbity rabbity

void bar {
// bunny can hop 11 high
// bunny can hop bb wide
int bb = 11;
}

void bar2 {
// rabbity can hop 21 high
// rabbity can hop rr wide
int rr = 21;
}

CODE
#!/usr/bin/ruby

require "rexml/document"
include REXML # namespace
require "digest/md5"
require "ftools"
require "Lexer"

commands = ["COMMENT", "INPUT"]
$inputfiles = []

$lexer = Lexer.new [
[%r{\A\s*%%\s*(#{commands.join "|"})[^%]*%%},?i ], # Command + args
[%r{\A%<\s*[^\s]*},?o ], # Open brace and xpath
[%r{\A>%},?c ], # Close brace
[%r{\A%\w+%},?m ], # Macro
[%r{\A[^%?<?%>?]*},?j ], # Junk
[%r{\A\s+}m, ?\s], # Whitespace ' '
]

$cmdlexer = Lexer.new [
[%r{\A\s*%%\sINPUT\s},?i ], # Input
[%r{\A\s*%%\sCOMMENT\s},?c ], # Input
[%r{\A\s*%%},?e ], # End of cmd
[%r{\A\w+},?a ], # Argument
[%r{\A\s+}m, ?\s], # Whitespace
]

$xpathlexer = Lexer.new [
[%r{\A%<}mx,?o ], # Open brace
[%r{\A\s*[^\s]+\s*},?x ], # Anything else is part of the xpath
[%r{\A\s+}m, ?\s], # Whitespace
]

def expand(string, node)
braces = []
inputfiles = []
xpath = []
expansion = ""
index = 0

tokenstr, result = $lexer.scan string
tokens = tokenstr.split("")

while index < tokens.size
begin
token = tokens[index]
case token
when 'i' # Command
ctokens, cresult = $cmdlexer.scan result[index]
cmode = false
ctokens.split("").each_with_index { |t,i|
(cmode = t; next) if t != 'a'
case cmode
when 'i' # Include xml data file
inputfiles << cresult
end
}
when 'o' # Open brace
next if !node
# Xpath
xtokens, xresult = $xpathlexer.scan result[index]
xtokens.split("").each_with_index { |t,i|
xpath << xresult if t == 'x'
}
braces << index + 1
when 'c' # Close brace
next if !node
return expansion if node and braces.empty?
brace = braces.pop
tmp = result.indices(brace..index).join ""
node.elements.each(xpath.pop) { |e|
expansion << expand(tmp, e)
}
when 'm' # Macro
next if !node or !braces.empty?
attrname = result[index].gsub(/%/, "")
search = (attrname =~ /^parent/ ? (attrname.gsub!
/^parent/,""; node.parent) : node)
if attrname == "tagname"
expansion << search.name
elsif search.attributes.key? attrname
expansion << search.attributes[attrname]
else
puts "No such attribute #{attrname} in #{search}"
expansion << result[index]
end
when 'j' # Junk
next if !node or !braces.empty?
expansion << result[index]
end
ensure
index += 1
end
end

return (node ? expansion : inputfiles)
end

# Pass 1 extracts input commands
(puts "Please supply a template file on the cmd line"; exit) if !$*[0]
puts "First pass"
inputfiles = expand IO.readlines($*[0]).to_s, nil

# Pass 2 expands the template
puts "Second pass"
inputfiles.each { |filename|
file = File.new(filename + ".xml", File::RDONLY)
document = Document.new file
file.close
outfile = File.new(filename + ".out", File::CREAT|File::TRUNC|File::WRONLY)
outfile << expand(IO.readlines($*[0]).to_s, document)
outfile.close
}

Luke said:
Hi list,

I was wondering if anyone would have some insight into a problem Ive got.

Im taking an xml document (looks very much like a w3c schema with some
extra attributes) and transforming it into c++ code. The method I use looks
something like...

def recurse(file, func, element)
case func
when "declare"
file << element.attributes["type"] << element.attributes["name"]
when "instantiate"
file << element.attributes["name"] << " = new " << element.attributes["type"]
do wierd stuff based on type and state
end
element.each_child { |c|
recurse file, func, c
}
end

[....]

Id like to get the logic out of the recurse statement for three reasons.
First of all, Im starting recurse up more times than Ive shown
above, because I have four separate sets of header/cpp templates,
not to mention I would like to add other templates that arent c++ code.
It would be good to know that each case could be handled safely
without interfering with each others output and special cases.
Secondly, someone else would like to write their own templated
code, and their required output would be different. Thirdly, recurse is
simply becoming scary.

I've done something similar to your first alternative, but using blocks.
That's somewhat more Rubyesque, IMO. (This is not for XML, but the
idea's the same.)

def descend_with_indicators(depth, &block)
yield :item, depth, self.item
unless children.empty?
yield :down, depth, nil
children.each do |c|
c.descend_with_indicators(depth + 1, &block)
end
yield :up, depth, nil
end
end

Invoke it as

root.descend_with_indicators(0) do |ind, dpth, item|
# ind will be one of :down, :item, or :up
# :down and :up indicate change in depth
# :item indicates an actual node in the tree
# if you don't need depth, you can omit it everywhere
end

Steve
 
L

Luke Graham

Ive just discovered XSLT templates can take parameters... which is the thing
Ive just added to my templating language that I thought they couldnt do. Im
going to have a go at using XSLT to do this.


Fixed the bug...
Change the argument line in cmdlexer to
[%r{\A[^\s%]+},?a ], # Argument
and change
file = File.new(filename + ".xml", File::RDONLY)
to
file = File.new(filename, File::RDONLY)


Steve,

thanks for your reply. I ended up creating a little template language instead.
Heres the code if youre interested. I can post it to RAA/Rubyforge/whatever
if people are finding it useful, so if you do use it, send me an email please.
Lexer is the LittleLexer package written by John Carter, and can be found
at http://littlelexer.rubyforge.org/

Known bug: placing a '.' in the INPUT line (and possibly elsewhere) confuses
the parser.

EXAMPLE TEMPLATE
%% COMMENT This is a comment and will be ignored %%
%% INPUT input %%

%</foo/*/*
// %hop% %hop%

%</foo/*
void %tagname% {
%<./jim // %hop% can hop %parentheight% high >%
%<./jim // %hop% can hop %parentwidth% wide >%
int %width% = %height%;
}

EXAMPLE XML DATA (input.xml as listed in the templates INPUT cmd)
<foo size="10">
<bar width="bb" height="11">
<jim hop="bunny"/>
</bar>
<bar2 width="rr" height="21">
<jim hop="rabbity"/>
</bar2>
</foo

EXAMPLE OUTPUT
// bunny bunny

// rabbity rabbity

void bar {
// bunny can hop 11 high
// bunny can hop bb wide
int bb = 11;
}

void bar2 {
// rabbity can hop 21 high
// rabbity can hop rr wide
int rr = 21;
}

CODE
#!/usr/bin/ruby

require "rexml/document"
include REXML # namespace
require "digest/md5"
require "ftools"
require "Lexer"

commands = ["COMMENT", "INPUT"]
$inputfiles = []

$lexer = Lexer.new [
[%r{\A\s*%%\s*(#{commands.join "|"})[^%]*%%},?i ], # Command + args
[%r{\A%<\s*[^\s]*},?o ], # Open brace and xpath
[%r{\A>%},?c ], # Close brace
[%r{\A%\w+%},?m ], # Macro
[%r{\A[^%?<?%>?]*},?j ], # Junk
[%r{\A\s+}m, ?\s], # Whitespace ' '
]

$cmdlexer = Lexer.new [
[%r{\A\s*%%\sINPUT\s},?i ], # Input
[%r{\A\s*%%\sCOMMENT\s},?c ], # Input
[%r{\A\s*%%},?e ], # End of cmd
[%r{\A\w+},?a ], # Argument
[%r{\A\s+}m, ?\s], # Whitespace
]

$xpathlexer = Lexer.new [
[%r{\A%<}mx,?o ], # Open brace
[%r{\A\s*[^\s]+\s*},?x ], # Anything else is part of the xpath
[%r{\A\s+}m, ?\s], # Whitespace
]

def expand(string, node)
braces = []
inputfiles = []
xpath = []
expansion = ""
index = 0

tokenstr, result = $lexer.scan string
tokens = tokenstr.split("")

while index < tokens.size
begin
token = tokens[index]
case token
when 'i' # Command
ctokens, cresult = $cmdlexer.scan result[index]
cmode = false
ctokens.split("").each_with_index { |t,i|
(cmode = t; next) if t != 'a'
case cmode
when 'i' # Include xml data file
inputfiles << cresult
end
}
when 'o' # Open brace
next if !node
# Xpath
xtokens, xresult = $xpathlexer.scan result[index]
xtokens.split("").each_with_index { |t,i|
xpath << xresult if t == 'x'
}
braces << index + 1
when 'c' # Close brace
next if !node
return expansion if node and braces.empty?
brace = braces.pop
tmp = result.indices(brace..index).join ""
node.elements.each(xpath.pop) { |e|
expansion << expand(tmp, e)
}
when 'm' # Macro
next if !node or !braces.empty?
attrname = result[index].gsub(/%/, "")
search = (attrname =~ /^parent/ ? (attrname.gsub!
/^parent/,""; node.parent) : node)
if attrname == "tagname"
expansion << search.name
elsif search.attributes.key? attrname
expansion << search.attributes[attrname]
else
puts "No such attribute #{attrname} in #{search}"
expansion << result[index]
end
when 'j' # Junk
next if !node or !braces.empty?
expansion << result[index]
end
ensure
index += 1
end
end

return (node ? expansion : inputfiles)
end

# Pass 1 extracts input commands
(puts "Please supply a template file on the cmd line"; exit) if !$*[0]
puts "First pass"
inputfiles = expand IO.readlines($*[0]).to_s, nil

# Pass 2 expands the template
puts "Second pass"
inputfiles.each { |filename|
file = File.new(filename + ".xml", File::RDONLY)
document = Document.new file
file.close
outfile = File.new(filename + ".out", File::CREAT|File::TRUNC|File::WRONLY)
outfile << expand(IO.readlines($*[0]).to_s, document)
outfile.close
}

Luke Graham wrote:
Hi list,

I was wondering if anyone would have some insight into a problem Ive got.

Im taking an xml document (looks very much like a w3c schema with some
extra attributes) and transforming it into c++ code. The method I use looks
something like...

def recurse(file, func, element)
case func
when "declare"
file << element.attributes["type"] << element.attributes["name"]
when "instantiate"
file << element.attributes["name"] << " = new " << element.attributes["type"]
do wierd stuff based on type and state
end
element.each_child { |c|
recurse file, func, c
}
end

[....]

Id like to get the logic out of the recurse statement for three reasons.
First of all, Im starting recurse up more times than Ive shown
above, because I have four separate sets of header/cpp templates,
not to mention I would like to add other templates that arent c++ code.
It would be good to know that each case could be handled safely
without interfering with each others output and special cases.
Secondly, someone else would like to write their own templated
code, and their required output would be different. Thirdly, recurse is
simply becoming scary.

I've done something similar to your first alternative, but using blocks.
That's somewhat more Rubyesque, IMO. (This is not for XML, but the
idea's the same.)

def descend_with_indicators(depth, &block)
yield :item, depth, self.item
unless children.empty?
yield :down, depth, nil
children.each do |c|
c.descend_with_indicators(depth + 1, &block)
end
yield :up, depth, nil
end
end

Invoke it as

root.descend_with_indicators(0) do |ind, dpth, item|
# ind will be one of :down, :item, or :up
# :down and :up indicate change in depth
# :item indicates an actual node in the tree
# if you don't need depth, you can omit it everywhere
end

Steve

 
S

Simon Strandgaard

Can anyone suggest a good way to get the c++-producing code out
of recurse and into some sort of template language, or into functions
that get passed into simplified recurse? I figured those functions
would need to return functions (usually themselves) to simulate
some state changes, ala...
[snip]

I have had good results with visitor design pattern and interpreter
design pattern.

For instance have a look at the bottom most class in this file
http://rubyforge.org/cgi-bin/viewcv...=aeditor&content-type=text/vnd.viewcvs-markup
 
L

Luke Graham

Can anyone suggest a good way to get the c++-producing code out
of recurse and into some sort of template language, or into functions
that get passed into simplified recurse? I figured those functions
would need to return functions (usually themselves) to simulate
some state changes, ala...
[snip]

I have had good results with visitor design pattern and interpreter
design pattern.

For instance have a look at the bottom most class in this file
http://rubyforge.org/cgi-bin/viewcv...=aeditor&content-type=text/vnd.viewcvs-markup

Thats not a million miles from my first cut at an XSLT template. The
lightning bolt
was when I saw templates could take params.. in other words, they were
functions.
Everything fell into place then and I saw XSLT for what it really
is... a completely
average pure-functional programming language designed by committee. Its good
in the sense that its templating via XPath, but missing (IMHO) vital
things like I/O,
built-in higher-order functions and of course terse syntax. My little
language shows how
trivial the core ideas of XSLT really are, and how it is XPath that
does the real
heavy lifting.

My executive decision for the day is to finish the first version using
my big nasty
script, because its good enough to get something out there. Version 2 will have
better intermediate code because the templating style will suit the
problem better,
but I will just use XSLT from now on. Despite the ugly language, its
hard to write

<repeat for some vars>
class <varname> {
<varname>();
~<varname>();

<repeat for some other vars>
int <varname>;
</repeat>
};
</repeat>

better in any other style of programming.

Has anyone heard of an alternate syntax for XSLT? I used %< ... >% for nesting,
%var% for attributes because they were the common case, and %% ... %% for
instructions like I/O.

Ok, enough non-Ruby talk now :D
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,482
Members
44,901
Latest member
Noble71S45

Latest Threads

Top