import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.Reader;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.logging.Level;
//-----------------------------------------------------------------------//
// Composer-specific collections from the parse tree:
// (adds composer-specific {@link Object#toString()} methods)
//-----------------------------------------------------------------------//
public class BaliRulesData {
final public static int COMMENT_COLUMN = 32 ;
public void generateClasses( File directory, String layerName ) {
initializeGeneratedSet() ;
this.layerName = layerName ;
for ( Iterator p = entrySet().iterator() ; p.hasNext() ; ) {
Map.Entry entry = ( Map.Entry ) p.next() ;
String ruleName = ( String ) entry.getKey() ;
List productions = ( List ) entry.getValue() ;
for ( Iterator q = productions.iterator() ; q.hasNext() ; ) {
Production production = ( Production ) q.next() ;
productionClass( directory, ruleName, production ) ;
}
}
for ( Iterator p = entrySet().iterator() ; p.hasNext() ; ) {
Map.Entry entry = ( Map.Entry ) p.next() ;
String ruleName = ( String ) entry.getKey() ;
ruleClass( directory, ruleName ) ;
}
generatedSet.clear() ;
}
protected void initializeGeneratedSet() {
generatedSet.clear() ;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - //
// Methods to generate rule classes:
// (all use variable "generatedSet" to avoid redundant generation)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - //
final protected Set generatedSet = new HashSet() ;
private void abstractSubClass( File dir, String base, String sub ) {
if ( ! generatedSet.contains( sub ) ) {
ClassBuilder builder = new ClassBuilder() ;
builder.addModifier( Modifier.ABSTRACT ) ;
builder.addModifier( Modifier.PUBLIC ) ;
builder.setLayerName( layerName ) ;
builder.setClassName( sub ) ;
builder.setSuperName( base ) ;
Files.toFile( builder.toString(), new File( dir, sub+".jak" ) ) ;
generatedSet.add( sub ) ;
}
}
/**
* Generates a class for productions with named classes. These are
* concrete classes with inner data that corresponds to a sequence of
* non-terminals and terminals. The productions in these cases are
* guaranteed to derive as follows:
* <blockquote>
* Production
* -> Rewrite
* -> PrimitiveRewrite
* -> PatternNode
* -> Pattern
* -> Primitive
* </blockquote>
*
* @layer<bali2jak>
*/
private void concreteSubClass( File dir, String base, String sub, Production prod ) {
if ( ! generatedSet.contains( sub ) ) {
ClassBuilder builder = new ClassBuilder() ;
builder.addModifier( Modifier.PUBLIC ) ;
builder.setLayerName( layerName ) ;
builder.setClassName( sub ) ;
builder.setSuperName( base ) ;
List primitives = new ArrayList() ;
AstNode node = ( PrimitiveRewriteNode ) prod.arg [1] ;
primitives.add( node.arg [0] ) ;
AstList list = ( Pattern ) node.arg[1].arg[0].arg [0] ;
if ( list != null )
primitives.addAll( list.toList() ) ;
MethodBuilder setParms = new MethodBuilder() ;
setParms.addModifier( Modifier.PUBLIC ) ;
setParms.setReturn( "setParms", sub ) ;
String code = "arg = new AstNode [ARG_LENGTH] ;" ;
setParms.endLine().append( code ) ;
code = "tok = new AstTokenInterface [TOK_LENGTH] ;" ;
setParms.endLine().append( code ) ;
setParms.endLine() ;
MethodBuilder method = new MethodBuilder() ;
int arg = 0 ;
int tok = 0 ;
List interlace = new ArrayList() ;
for ( Iterator p = primitives.iterator() ; p.hasNext() ; ) {
Primitive prim = ( Primitive ) p.next() ;
if ( prim instanceof BaliTokenNode ) {
String name = prim.tok[0].getTokenName() ;
setParms.addParameter( "tok" + tok, "AstToken" ) ;
interlace.add( Boolean.TRUE ) ;
code = "tok [" + tok + "] = tok" + tok + " ;" ;
setParms.endLine().append( code ) ;
setParms.spaceToColumn( COMMENT_COLUMN ) ;
setParms.append( "/* " + prim.toString().trim() + " */" ) ;
code = "return (AstToken) tok [" + tok + "] ;" ;
method.clear() ;
method.endLine().append( code ) ;
method.addModifier( Modifier.PUBLIC ) ;
method.setReturn( "get" + name, "AstToken" ) ;
builder.addMethod( method ) ;
++ tok ;
continue ;
}
if ( prim instanceof IdentifierNode ) {
String name = prim.tok[0].getTokenName() ;
String type = getType( name ) ;
setParms.addParameter( "arg" + arg, type ) ;
interlace.add( Boolean.FALSE ) ;
code = "arg [" + arg + "] = arg" + arg + " ;" ;
setParms.endLine().append( code ) ;
setParms.spaceToColumn( COMMENT_COLUMN ) ;
setParms.append( "/* " + prim.toString().trim() + " */" ) ;
code = "return (" + type + ") arg [" + arg + "] ;" ;
method.clear() ;
method.endLine().append( code ) ;
method.addModifier( Modifier.PUBLIC ) ;
method.setReturn( "get" + name, type ) ;
builder.addMethod( method ) ;
++ arg ;
continue ;
}
if ( prim instanceof OptionalNode ) {
Terminal term = ( Terminal ) prim.arg [1] ;
if ( term instanceof IdentifierNode ) {
setParms.addParameter( "arg"+arg, "AstOptNode" ) ;
interlace.add( Boolean.FALSE ) ;
code = "arg [" + arg + "] = arg" + arg + " ;" ;
String name = term.tok[0].getTokenName() ;
String type = getType( name ) ;
method.clear() ;
method.appendLines( "\nAstNode node = arg[" + arg + "].arg [0] ;"
+ "\nreturn (node != null) ? (" + type + ") "
+ "node : null ;" ) ;
method.addModifier( Modifier.PUBLIC ) ;
method.setReturn( "get" + name, type ) ;
builder.addMethod( method ) ;
++ arg ;
}
else
if ( term instanceof BaliTokenNode ) {
setParms.addParameter( "tok"+tok, "AstOptToken" ) ;
interlace.add( Boolean.TRUE ) ;
code = "tok [" + tok + "] = tok" + tok + " ;" ;
String name = term.tok[0].getTokenName() ;
method.clear() ;
method.endLine() ;
method.append( "return (AstToken)"
+ " ((AstNode) tok ["
+ tok
+ "]) . tok [0] ;" ) ;
method.addModifier( Modifier.PUBLIC ) ;
method.setReturn( "get" + name, "AstToken" ) ;
builder.addMethod( method ) ;
++ tok ;
}
else {
setParms.addParameter( "tok"+tok, "AstOptToken" ) ;
interlace.add( Boolean.TRUE ) ;
code = "tok [" + tok + "] = tok" + arg + " ;" ;
++ tok ;
}
setParms.endLine().append( code ) ;
setParms.spaceToColumn( COMMENT_COLUMN ) ;
setParms.append( "/* " + prim.toString().trim() + " */" ) ;
continue ;
}
if ( prim instanceof StringNode ) {
setParms.addParameter( "tok" + tok, "AstToken" ) ;
interlace.add( Boolean.TRUE ) ;
code = "tok [" + tok + "] = tok" + tok + " ;" ;
setParms.endLine().append( code ) ;
setParms.spaceToColumn( COMMENT_COLUMN ) ;
setParms.append( "/* " + prim.toString().trim() + " */" ) ;
++ tok ;
continue ;
}
String msg = "class " + prim.getClass().getName() ;
throw new IllegalStateException( msg ) ;
}
// Apparently, some previously written code assumes that
// "arg[0]" is always a valid reference. So, the array size of
// "arg" is bumped up to a minimum of "1". A note about this
// is included in the generated source code.
//
code =
"final public static int ARG_LENGTH = "
+ ( arg < 1 ? "1 /* Kludge! */" : String.valueOf( arg ) )
+ " ;" ;
builder.endLine().append( code ) ;
code =
"final public static int TOK_LENGTH = "
+ ( tok < 1 ? "1 /* Kludge! */" : String.valueOf( tok ) )
+ " ;" ;
builder.endLine().append( code ) ;
setParms.endLine().endLine() ;
setParms.append( "InitChildren () ;" ) ;
setParms.endLine() ;
setParms.append( "return (" + sub + ") this ;" ) ;
builder.addMethod( setParms ) ;
MethodBuilder printOrder = new MethodBuilder() ;
printOrder.addModifier( Modifier.PUBLIC ) ;
printOrder.setReturn( "printorder", "boolean[]" ) ;
code =
"return new boolean[] {"
+ listToString( interlace )
+ "} ;" ;
printOrder.endLine().append( code ) ;
builder.addMethod( printOrder ) ;
Files.toFile( builder.toString(), new File( dir, sub+".jak" ) ) ;
generatedSet.add( sub ) ;
}
}
/**
* Generates a class inheriting from <code>AstList</code>, then returns
* a {@link ClassBuilder} with the basic setup for an element class
* inheriting from <code>AstListNode</code>. If the return value is
* <code>null</code>, then the element class has previously been
* generated.
*
* @layer<bali2jak>
*/
private ClassBuilder listClass( File dir, String base ) {
ClassBuilder build = new ClassBuilder() ;
build.addModifier( Modifier.PUBLIC ) ;
build.setLayerName( layerName ) ;
if ( ! generatedSet.contains( base ) ) {
build.setClassName( base ) ;
build.setSuperName( "AstList" ) ;
Files.toFile( build.toString(), new File( dir, base+".jak" ) ) ;
generatedSet.add( base ) ;
}
base += "Elem" ;
if ( generatedSet.contains( base ) )
return null ;
build.setClassName( base ) ;
build.setSuperName( "AstListNode" ) ;
generatedSet.add( base ) ;
return build ;
}
private ClassBuilder listClass( File dir, String base, Primitive prim ) {
ClassBuilder build = listClass( dir, base ) ;
if ( build == null )
return null ;
if ( ! ( prim instanceof IdentifierNode ) )
throw new IllegalStateException( "invalid primitive in list" ) ;
String argType = prim.tok[0].getTokenName() ;
String elem = base + "Elem" ;
MethodBuilder method = new MethodBuilder() ;
method.addModifier( Modifier.PUBLIC ) ;
method.setReturn( "setParms", elem ) ;
method.addParameter( "arg0", argType ) ;
method.endLine().append( "super.setParms (arg0) ;" ) ;
method.spaceToColumn( COMMENT_COLUMN ) ;
method.append( "/* " + prim.toString().trim() + " */" ) ;
method.endLine() ;
method.append( "return (" + elem + ") this ;" ) ;
build.addMethod( method ) ;
method.clear() ;
method.addModifier( Modifier.PUBLIC ) ;
method.setReturn( "get" + argType, argType ) ;
method.endLine().append( "return (" + argType + ") arg [0] ;" ) ;
build.addMethod( method ) ;
return build ;
}
private void listClasses( File dir, String base, Primitive prim ) {
ClassBuilder build = listClass( dir, base, prim ) ;
if ( build == null )
return ;
Files.toFile( build.toString(), new File( dir, base+"Elem.jak" ) ) ;
}
private void listClasses( File dir, String base, Primitive one, Primitive two ) {
ClassBuilder build = listClass( dir, base, two ) ;
if ( build == null )
return ;
if ( one.tok.length < 1 )
throw new IllegalStateException( "missing token in list" ) ;
String twoType = two.tok[0].getTokenName() ;
String elem = base + "Elem" ;
MethodBuilder method = new MethodBuilder() ;
method.addModifier( Modifier.PUBLIC ) ;
method.setReturn( "setParms", elem ) ;
method.addParameter( "tok0", "AstToken" ) ;
method.addParameter( "arg0", twoType ) ;
method.endLine().append( "tok = new AstToken [1] ;" ) ;
method.endLine() ;
method.append( "tok [0] = tok0 ;" ) ;
method.spaceToColumn( COMMENT_COLUMN ) ;
method.append( "/* " + one.toString().trim() + " */" ) ;
method.endLine().append( "return setParms (arg0) ;" ) ;
method.spaceToColumn( COMMENT_COLUMN ) ;
method.append( "/* " + two.toString().trim() + " */" ) ;
build.addMethod( method ) ;
if ( one instanceof BaliTokenNode ) {
String name = one.tok[0].getTokenName() ;
method.clear() ;
method.addModifier( Modifier.PUBLIC ) ;
method.setReturn( "get" + name, "AstToken" ) ;
method.append( "return (AstToken) tok [0] ;" ) ;
build.addMethod( method ) ;
}
Files.toFile( build.toString(), new File( dir, base+"Elem.jak" ) ) ;
}
/**
* Returns a comma-separated list of strings representing the
* {@link Object} instances held in a given {@link List}.
*
* @layer<bali2jak>
*/
private String listToString( List objects ) {
if ( objects.size() < 1 )
return "" ;
StringBuffer buffer = new StringBuffer() ;
buffer.append( objects.get( 0 ) ) ;
for ( int n = 1 ; n < objects.size() ; ++n )
buffer.append( ", " ).append( objects.get( n ) ) ;
return buffer.toString() ;
}
private void productionClass( File dir, String base, Production prod ) {
// The production is a (item)+ list:
// (generate a List class and a ListElem class)
//
ProductionNode prodNode = ( ProductionNode ) prod ;
Rewrite rewrite = ( Rewrite ) prodNode.arg [1] ;
if ( rewrite instanceof SimpleListNode ) {
Primitive prim = ( Primitive ) rewrite.arg [1] ;
listClasses( dir, base, prim ) ;
return ;
}
// The production is a "item1 (item2)*" list:
// (generate a List class and a ListElem class)
//
PrimitiveRewriteNode prn = ( PrimitiveRewriteNode ) rewrite ;
PrimitiveRewrite pr = ( PrimitiveRewrite ) prn.arg [1] ;
if ( pr instanceof ComplexListNode ) {
Primitive one = ( Primitive ) pr.arg [1] ;
Primitive two = ( Primitive ) pr.arg [2] ;
listClasses( dir, base, one, two ) ;
return ;
}
// Named pattern (i.e., ":: NodeName" at production's end):
// (generate a concrete subclass of base rule class)
//
PatternNode patternNode = ( PatternNode ) pr ;
AstNode classNameNode = patternNode.arg[1].arg [0] ;
if ( classNameNode != null ) {
ClassNameNode node = ( ClassNameNode ) classNameNode ;
String name = node.tok[1].getTokenName() ;
concreteSubClass( dir, base, name, prod ) ;
return ;
}
// Don't generate a class for unnamed pattern *sequences*:
// (are these errors?)
//
if ( patternNode.arg[0].arg [0] != null )
throw new IllegalStateException( "unnamed rule with right-hand sequence: " + base ) ;
// What remains are productions with a singleton fields.
// If the field is a rule name, it's an unnamed rule:
//
Primitive prim = ( Primitive ) prn.arg [0] ;
if ( prim instanceof IdentifierNode ) {
String name = prim.tok[0].getTokenName() ;
abstractSubClass( dir, base, name ) ;
return ;
}
// No classes generated for any other alternative:
//
return ;
}
private void ruleClass( File dir, String base ) {
abstractSubClass( dir, "AstNode", base ) ;
}
protected String layerName = null ;
}