Free cookie consent management tool by TermsFeed Policy Generator

source: branches/HeuristicLab.Problems.GPDL/CodeGenerator/ProblemCodeGen.cs @ 10386

Last change on this file since 10386 was 10386, checked in by gkronber, 10 years ago

#2026 refactoring

File size: 17.3 KB
Line 
1using System.Collections.Generic;
2using System.Diagnostics;
3using System.IO;
4using System.Linq;
5using HeuristicLab.Grammars;
6using Attribute = HeuristicLab.Grammars.Attribute;
7
8namespace CodeGenerator {
9  // code generator for problem class
10  public class ProblemCodeGen {
11    private const string usings = @"
12using System.Collections.Generic;
13using System.Linq;
14using System;
15using System.Text.RegularExpressions;
16";
17
18    private const string problemTemplate = @"
19namespace ?PROBLEMNAME? {
20  public sealed class ?IDENT?Problem {
21   
22   public ?IDENT?Problem() {
23      Initialize();
24    }   
25
26    private void Initialize() {
27      // the following is the source code from the INIT section of the problem definition
28#region INIT section
29?INITSOURCE?
30#endregion
31    }
32
33    private Tree _t;
34    public double Evaluate(Tree _t) {
35      this._t = _t;
36#region objective function (MINIMIZE / MAXIMIZE section)
37?FITNESSFUNCTION?
38#endregion
39    }
40    public bool IsBetter(double a, double b) {
41      return ?MAXIMIZATION? ? a > b : a < b;
42    }
43
44// additional code from the problem definition (CODE section)
45#region additional code
46?ADDITIONALCODE?
47#endregion
48
49#region generated source for interpretation
50?INTERPRETERSOURCE?
51#endregion
52
53#region generated code for the constraints for terminals
54?CONSTRAINTSSOURCE?
55#endregion
56  }
57
58#region class definitions for tree
59  public class Tree {
60    public int altIdx;
61    public Tree[] subtrees;
62    protected Tree() {
63      // leave subtrees uninitialized
64    }
65    public Tree(int altIdx, Tree[] subtrees) {
66      this.altIdx = altIdx;
67      this.subtrees = subtrees;
68    }
69  }
70
71  ?TERMINALNODECLASSDEFINITIONS?
72#endregion
73
74#region helper class for the grammar representation
75  public class Grammar {
76    public static readonly Dictionary<int, int[]> transition = new Dictionary<int, int[]>() {
77?TRANSITIONTABLE?
78    };
79    public static readonly Dictionary<int, int> subtreeCount = new Dictionary<int, int>() {
80       { -1, 0 }, // terminals
81?SUBTREECOUNTTABLE?
82    };
83    public static readonly string[] symb = new string[] { ?SYMBOLNAMES? };
84   
85    public static Tree CreateTerminalNode(int state, Random random, ?IDENT?Problem problem) {
86      switch(state) {
87        ?CREATETERMINALNODECODE?
88        default: { throw new ArgumentException(""Unknown state index"" + state); }
89      }
90    }
91  } 
92#endregion
93}";
94
95
96    /// <summary>
97    /// Generates the source code for a brute force searcher that can be compiled with a C# compiler
98    /// </summary>
99    /// <param name="ast">An abstract syntax tree for a GPDL file</param>
100    public void Generate(GPDefNode ast) {
101      var problemSourceCode = new SourceBuilder();
102      problemSourceCode.AppendLine(usings);
103
104      GenerateProblemSource(ast, problemSourceCode);
105      GenerateSolvers(ast, problemSourceCode);
106
107      problemSourceCode
108        .Replace("?PROBLEMNAME?", ast.Name)
109        .Replace("?IDENT?", ast.Name);
110
111      // write the source file to disk
112      using (var stream = new StreamWriter(ast.Name + ".cs")) {
113        stream.WriteLine(problemSourceCode.ToString());
114      }
115    }
116
117    private void GenerateProblemSource(GPDefNode ast, SourceBuilder problemSourceCode) {
118      var grammar = CreateGrammarFromAst(ast);
119      problemSourceCode
120        .AppendLine(problemTemplate)
121        .Replace("?FITNESSFUNCTION?", ast.FitnessFunctionNode.SrcCode)
122        .Replace("?MAXIMIZATION?", ast.FitnessFunctionNode.Maximization.ToString().ToLowerInvariant())
123        .Replace("?INITSOURCE?", ast.InitCodeNode.SrcCode)
124        .Replace("?ADDITIONALCODE?", ast.ClassCodeNode.SrcCode)
125        .Replace("?INTERPRETERSOURCE?", GenerateInterpreterSource(grammar))
126        .Replace("?CONSTRAINTSSOURCE?", GenerateConstraintMethods(ast.Terminals))
127        .Replace("?TERMINALNODECLASSDEFINITIONS?", GenerateTerminalNodeClassDefinitions(ast.Terminals.OfType<TerminalNode>()))
128        .Replace("?SYMBOLNAMES?", grammar.Symbols.Select(s => s.Name).Aggregate(string.Empty, (str, symb) => str + "\"" + symb + "\", "))
129        .Replace("?TRANSITIONTABLE?", GenerateTransitionTable(grammar))
130        .Replace("?CREATETERMINALNODECODE?", GenerateCreateTerminalCode(grammar))
131        .Replace("?SUBTREECOUNTTABLE?", GenerateSubtreeCountTable(grammar))
132       ;
133    }
134
135    private void GenerateSolvers(GPDefNode ast, SourceBuilder solverSourceCode) {
136      var grammar = CreateGrammarFromAst(ast);
137      var randomSearchCodeGen = new RandomSearchCodeGen();
138      randomSearchCodeGen.Generate(grammar, ast.FitnessFunctionNode.Maximization, solverSourceCode);
139      //var bruteForceSearchCodeGen = new BruteForceCodeGen();
140      //bruteForceSearchCodeGen.Generate(grammar, ast.FitnessFunctionNode.Maximization, solverSourceCode);
141    }
142
143    #region create grammar instance from AST
144    // should be refactored so that we can directly query the AST
145    private AttributedGrammar CreateGrammarFromAst(GPDefNode ast) {
146
147      var nonTerminals = ast.NonTerminals
148        .Select(t => new Symbol(t.Ident, GetSymbolAttributes(t.FormalParameters)))
149        .ToArray();
150      var terminals = ast.Terminals
151        .Select(t => new Symbol(t.Ident, GetSymbolAttributes(t.FormalParameters)))
152        .ToArray();
153      string startSymbolName = ast.Rules.First().NtSymbol;
154
155      // create startSymbol
156      var startSymbol = nonTerminals.Single(s => s.Name == startSymbolName);
157      var g = new AttributedGrammar(startSymbol, nonTerminals, terminals);
158
159      // add all production rules
160      foreach (var rule in ast.Rules) {
161        var ntSymbol = nonTerminals.Single(s => s.Name == rule.NtSymbol);
162        foreach (var alt in GetAlternatives(rule.Alternatives, nonTerminals.Concat(terminals))) {
163          g.AddProductionRule(ntSymbol, alt);
164        }
165        // local initialization code
166        if (!string.IsNullOrEmpty(rule.LocalCode)) g.AddLocalDefinitions(ntSymbol, rule.LocalCode);
167      }
168      return g;
169    }
170
171    private IEnumerable<IAttribute> GetSymbolAttributes(string formalParameters) {
172      return (from fieldDef in Util.ExtractParameters(formalParameters)
173              select new Attribute(fieldDef.Identifier, fieldDef.Type, AttributeType.Parse(fieldDef.RefOrOut)))
174              .ToList();
175    }
176
177    private IEnumerable<Sequence> GetAlternatives(AlternativesNode altNode, IEnumerable<ISymbol> allSymbols) {
178      foreach (var alt in altNode.Alternatives) {
179        yield return GetSequence(alt.Sequence, allSymbols);
180      }
181    }
182
183    private Sequence GetSequence(IEnumerable<RuleExprNode> sequence, IEnumerable<ISymbol> allSymbols) {
184      Debug.Assert(sequence.All(s => s is CallSymbolNode || s is RuleActionNode));
185      var l = new List<ISymbol>();
186      foreach (var node in sequence) {
187        var callSymbolNode = node as CallSymbolNode;
188        var actionNode = node as RuleActionNode;
189        if (callSymbolNode != null) {
190          Debug.Assert(allSymbols.Any(s => s.Name == callSymbolNode.Ident));
191          // create a new symbol with actual parameters
192          l.Add(new Symbol(callSymbolNode.Ident, GetSymbolAttributes(callSymbolNode.ActualParameter)));
193        } else if (actionNode != null) {
194          l.Add(new SemanticSymbol("SEM", actionNode.SrcCode));
195        }
196      }
197      return new Sequence(l);
198    }
199    #endregion
200
201    #region helper methods for terminal symbols
202    // produces helper methods for the attributes of all terminal nodes
203    private string GenerateConstraintMethods(IEnumerable<SymbolNode> symbols) {
204      var sb = new SourceBuilder();
205      var terminals = symbols.OfType<TerminalNode>();
206      foreach (var t in terminals) {
207        GenerateConstraintMethods(t, sb);
208      }
209      return sb.ToString();
210    }
211
212
213    // generates helper methods for the attributes of a given terminal node
214    private void GenerateConstraintMethods(TerminalNode t, SourceBuilder sb) {
215      foreach (var c in t.Constraints) {
216        var fieldType = t.FieldDefinitions.First(d => d.Identifier == c.Ident).Type;
217        if (c.Type == ConstraintNodeType.Range) {
218          sb.AppendFormat("public {0} GetMax{1}_{2}() {{ return {3}; }}", fieldType, t.Ident, c.Ident, c.RangeMaxExpression).AppendLine();
219          sb.AppendFormat("public {0} GetMin{1}_{2}() {{ return {3}; }}", fieldType, t.Ident, c.Ident, c.RangeMinExpression).AppendLine();
220        } else if (c.Type == ConstraintNodeType.Set) {
221          sb.AppendFormat("public IEnumerable<{0}> GetAllowed{1}_{2}() {{ return {3}; }}", fieldType, t.Ident, c.Ident, c.SetExpression).AppendLine();
222        }
223      }
224    }
225    #endregion
226
227    private string GenerateTerminalNodeClassDefinitions(IEnumerable<TerminalNode> terminals) {
228      var sb = new SourceBuilder();
229
230      foreach (var terminal in terminals) {
231        GenerateTerminalNodeClassDefinitions(terminal, sb);
232      }
233      return sb.ToString();
234    }
235
236    private void GenerateTerminalNodeClassDefinitions(TerminalNode terminal, SourceBuilder sb) {
237      sb.AppendFormat("public class {0}Tree : Tree {{", terminal.Ident).BeginBlock();
238      foreach (var att in terminal.FieldDefinitions) {
239        sb.AppendFormat("public {0} {1};", att.Type, att.Identifier).AppendLine();
240      }
241      sb.AppendFormat(" public {0}Tree(Random random, ?IDENT?Problem problem) : base() {{", terminal.Ident).BeginBlock();
242      foreach (var constr in terminal.Constraints) {
243        if (constr.Type == ConstraintNodeType.Set) {
244          sb.Append("{").BeginBlock();
245          sb.AppendFormat("var elements = problem.GetAllowed{0}_{1}().ToArray();", terminal.Ident, constr.Ident).AppendLine();
246          sb.AppendFormat("{0} = elements[random.Next(elements.Length)]; ", constr.Ident).AppendLine();
247          sb.Append("}").EndBlock();
248        } else {
249          sb.Append("{").BeginBlock();
250          sb.AppendFormat(" var max = problem.GetMax{0}_{1}();", terminal.Ident, constr.Ident).AppendLine();
251          sb.AppendFormat(" var min = problem.GetMin{0}_{1}();", terminal.Ident, constr.Ident).AppendLine();
252          sb.AppendFormat("{0} = random.NextDouble() * (max - min) + min;", constr.Ident).EndBlock();
253          sb.Append("}").EndBlock();
254        }
255      }
256      sb.Append("}").EndBlock();
257      sb.AppendLine("}");
258    }
259
260    private string GenerateInterpreterSource(AttributedGrammar grammar) {
261      var sb = new SourceBuilder();
262      GenerateInterpreterStart(grammar, sb);
263
264      // generate methods for all nonterminals and terminals using the grammar instance
265      foreach (var s in grammar.NonTerminalSymbols) {
266        GenerateInterpreterMethod(grammar, s, sb);
267      }
268      foreach (var s in grammar.TerminalSymbols) {
269        GenerateTerminalInterpreterMethod(s, sb);
270      }
271      return sb.ToString();
272    }
273
274    private void GenerateInterpreterStart(AttributedGrammar grammar, SourceBuilder sb) {
275      var s = grammar.StartSymbol;
276      // create the method which can be called from the fitness function
277      if (!s.Attributes.Any())
278        sb.AppendFormat("private void {0}() {{", s.Name).BeginBlock();
279      else
280        sb.AppendFormat("private void {0}({1}) {{", s.Name, s.GetAttributeString()).BeginBlock();
281
282      // get formal parameters of start symbol
283      var attr = s.Attributes;
284
285      // actual parameter are the same as formalparameter only without type identifier
286      string actualParameter;
287      if (attr.Any())
288        actualParameter = attr.Skip(1).Aggregate(attr.First().AttributeType + " " + attr.First().Name, (str, a) => str + ", " + a.AttributeType + " " + a.Name);
289      else
290        actualParameter = string.Empty;
291      sb.AppendFormat("{0}(_t, {1});", s.Name, actualParameter).AppendLine();
292      sb.AppendLine("}").EndBlock();
293    }
294
295    private void GenerateInterpreterMethod(AttributedGrammar g, ISymbol s, SourceBuilder sb) {
296      if (!s.Attributes.Any())
297        sb.AppendFormat("private void {0}(Tree _t) {{", s.Name).BeginBlock();
298      else
299        sb.AppendFormat("private void {0}(Tree _t, {1}) {{", s.Name, s.GetAttributeString()).BeginBlock();
300
301      // generate local definitions
302      sb.AppendLine(g.GetLocalDefinitions(s));
303
304      var altsWithSemActions = g.GetAlternativesWithSemanticActions(s).ToArray();
305
306      if (altsWithSemActions.Length > 1) {
307        GenerateSwitchStatement(altsWithSemActions, sb);
308      } else {
309        int i = 0;
310        foreach (var altSymb in altsWithSemActions.Single()) {
311          GenerateSourceForAction(i, altSymb, sb);
312          if (!(altSymb is SemanticSymbol)) i++;
313        }
314      }
315      sb.Append("}").EndBlock();
316    }
317
318    private void GenerateSwitchStatement(IEnumerable<Sequence> alts, SourceBuilder sb)
319    {
320      sb.Append("switch(_t.altIdx) {").BeginBlock();
321      // generate a case for each alternative
322      int altIdx = 0;
323      foreach (var alt in alts) {
324        sb.AppendFormat("case {0}: {{ ", altIdx).BeginBlock();
325
326        // this only works for alternatives with a single non-terminal symbol (ignoring semantic symbols)!
327        // a way to handle this is through grammar transformation (the examplary grammars all have the correct from)
328        Debug.Assert(alt.Count(symb => !(symb is SemanticSymbol)) == 1);
329        foreach (var altSymb in alt) {
330          GenerateSourceForAction(0, altSymb, sb); // index is always 0 because of the assertion above
331        }
332        altIdx++;
333        sb.AppendLine("break;").Append("}").EndBlock();
334      }
335      sb.AppendLine("default: throw new System.InvalidOperationException();").Append("}").EndBlock();
336    }
337
338    // helper for generating calls to other symbol methods
339    private void GenerateSourceForAction(int idx, ISymbol s, SourceBuilder sb) {
340      var action = s as SemanticSymbol;
341      if (action != null)
342        sb.Append(action.Code + ";");
343      else if (!s.Attributes.Any())
344        sb.AppendFormat("{1}(_t.subtrees[{0}]);", idx, s.Name);
345      else sb.AppendFormat("{1}(_t.subtrees[{0}], {2}); ", idx, s.Name, s.GetAttributeString());
346      sb.AppendLine();
347    }
348
349    private void GenerateTerminalInterpreterMethod(ISymbol s, SourceBuilder sb) {
350      // if the terminal symbol has attributes then we must samples values for these attributes
351      if (!s.Attributes.Any())
352        sb.AppendFormat("private void {0}(Tree _t) {{", s.Name).BeginBlock();
353      else
354        sb.AppendFormat("private void {0}(Tree _t, {1}) {{", s.Name, s.GetAttributeString()).BeginBlock();
355
356      // each field must match a formal parameter, assign a value for each parameter
357      int i = 0;
358      foreach (var element in s.Attributes) {
359        sb.AppendFormat("{0} = (_t as {1}Tree).{0};", element.Name, s.Name).AppendLine();
360      }
361      sb.Append("}").EndBlock();
362    }
363
364    private string GenerateCreateTerminalCode(IGrammar grammar) {
365      Debug.Assert(grammar.Symbols.First().Equals(grammar.StartSymbol));
366      var sb = new SourceBuilder();
367      var allSymbols = grammar.Symbols.ToList();
368      foreach (var s in grammar.Symbols) {
369        if (grammar.IsTerminal(s)) {
370          sb.AppendFormat("case {0}: {{ return new {1}Tree(random, problem); }}", allSymbols.IndexOf(s), s.Name).AppendLine();
371        }
372      }
373      return sb.ToString();
374    }
375
376    private string GenerateTransitionTable(IGrammar grammar) {
377      Debug.Assert(grammar.Symbols.First().Equals(grammar.StartSymbol));
378      var sb = new SourceBuilder();
379
380      // state idx = idx of the corresponding symbol in the grammar
381      var allSymbols = grammar.Symbols.ToList();
382      foreach (var s in grammar.Symbols) {
383        var targetStates = new List<int>();
384        if (grammar.IsTerminal(s)) {
385        } else {
386          if (grammar.NumberOfAlternatives(s) > 1) {
387            foreach (var alt in grammar.GetAlternatives(s)) {
388              // only single-symbol alternatives are supported
389              Debug.Assert(alt.Count() == 1);
390              targetStates.Add(allSymbols.IndexOf(alt.Single()));
391            }
392          } else {
393            // rule is a sequence of symbols
394            var seq = grammar.GetAlternatives(s).Single();
395            targetStates.AddRange(seq.Select(symb => allSymbols.IndexOf(symb)));
396          }
397        }
398
399        var targetStateString = targetStates.Aggregate(string.Empty, (str, state) => str + state + ", ");
400
401        var idxOfSourceState = allSymbols.IndexOf(s);
402        sb.AppendFormat("// {0}", s).AppendLine();
403        sb.AppendFormat("{{ {0} , new int[] {{ {1} }} }},", idxOfSourceState, targetStateString).AppendLine();
404      }
405      return sb.ToString();
406    }
407    private string GenerateSubtreeCountTable(IGrammar grammar) {
408      Debug.Assert(grammar.Symbols.First().Equals(grammar.StartSymbol));
409      var sb = new SourceBuilder();
410
411      // state idx = idx of the corresponding symbol in the grammar
412      var allSymbols = grammar.Symbols.ToList();
413      foreach (var s in grammar.Symbols) {
414        int subtreeCount = 0;
415        if (grammar.IsTerminal(s)) {
416        } else {
417          if (grammar.NumberOfAlternatives(s) > 1) {
418            Debug.Assert(grammar.GetAlternatives(s).All(alt => alt.Count() == 1));
419            subtreeCount = 1;
420          } else {
421            subtreeCount = grammar.GetAlternative(s, 0).Count();
422          }
423        }
424
425        sb.AppendFormat("// {0}", s).AppendLine();
426        sb.AppendFormat("{{ {0} , {1} }},", allSymbols.IndexOf(s), subtreeCount).AppendLine();
427      }
428
429      return sb.ToString();
430    }
431
432  }
433}
Note: See TracBrowser for help on using the repository browser.