context: // Generated from MatchCode.ecs by LeMP custom tool. LeMP version: 2.9.0.3
  // Note: you can give command-line arguments to the tool via 'Custom Tool Namespace':
  // --no-out-header       Suppress this message
  // --verbose             Allow verbose messages (shown by VS as 'warnings')
  // --timeout=X           Abort processing thread after X seconds (default: 10)
  // --macros=FileName.dll Load macros from FileName.dll, path relative to this file 
  // Use #importMacros to use macros in a given namespace, e.g. #importMacros(Loyc.LLPG);
  using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Text;
  using Loyc;
  using Loyc.Syntax;
  using Loyc.Collections;
  using static LeMP.StandardMacros;
  using S = Loyc.Syntax.CodeSymbols;
  
  namespace LeMP.ecs
  {
  	partial class StandardMacros
  	{
  		[LexicalMacro("matchCode (var) { case ...: ... }; // In LES, use a => b instead of case a: b", 
  		"Attempts to match and deconstruct a Loyc tree against a series of cases with patterns, e.g. " + 
  		"`case $a + $b:` expects a tree that calls `+` with two parameters, placed in new variables called a and b. " + 
  		"`break` is not required or recognized at the end of each case's handler (code block). " + 
  		"Use `$(...x)` to gather zero or more parameters into a list `x`. " + 
  		"Use `case pattern1, pattern2:` in EC# to handle multiple cases with the same handler.")] 
  		public static LNode matchCode(LNode node, IMacroContext context)
  		{
  			if (node.AttrNamed(S.Static) != null)
  				return null;	// this case is handled by static_matchCode macro
  			var args_body = context.GetArgsAndBody(false);
  			LNodeList args = args_body.Item1, body = args_body.Item2;
  			if (args.Count != 1 || body.Count < 1)
  				return null;
  			var cases = GetCases(body, context.Sink);
  			if (cases.IsEmpty)
  				return null;
  
  			var output = new WList<LNode>();
  			var var = MaybeAddTempVarDecl(context, args[0], output);
  
  			var ifClauses = new List<Pair<LNode, LNode>>();
  			var cmc = new CodeMatchContext { 
  				Context = context
  			};
  
  			foreach (var @case in cases)
  			{
  				cmc.ThenClause.Clear();
  				// e.g. case [$(..._)] Foo($x + 1, $y) => 
  				//      LNode x, y, tmp9; 
  				//      if (var.Calls((Symbol) "Foo", 2) && (tmp9 = var.Args[0]).Calls(CodeSymbols.Plus, 2)
  				//          && (x = tmp9.Args[0]) != null // this will never be null, but we want to put it the assignment in the 'if' statement
  				//          && 1.Equals(tmp9.Args[1].Value) && (y = var.Args[1]) != null) { ... }
  				LNode testExpr = null;
  				if (@case .Key.Count > 0) {
  					if (cmc.IsMultiCase = @case .Key.Count > 1) {
  						cmc.UsageCounters.Clear();
  						testExpr = @case .Key.Aggregate((LNode) null, (test, pattern) => {
  							test = LNode.MergeBinary(test, cmc.MakeTopTestExpr(pattern, var), S.Or);
  							return test;
  						});
  						foreach (var pair in cmc.UsageCounters.Where(p => p.Value < @case .Key.Count)) {
  							if (cmc.NodeVars.ContainsKey(pair.Key))
  								cmc.NodeVars[pair.Key] = true;
  							if (cmc.ListVars.ContainsKey(pair.Key))
  								cmc.ListVars[pair.Key] = true;
  						}
  					} else
  						testExpr = cmc.MakeTopTestExpr(@case .Key[0], var);
  				}
  				var handler = F.Braces(@case .Value);
  				if (cmc.ThenClause.Count > 0)
  					handler = LNode.MergeLists(F.Braces(cmc.ThenClause), handler, S.Braces);
  				ifClauses.Add(Pair.Create(testExpr, handler));
  			}
  
  			LNode ifStmt = null;
  			for (int i = ifClauses.Count - 1; i >= 0; i--)
  			{
  				if (ifClauses[i].Item1 == null) {
  					if (ifStmt == null)
  						ifStmt = ifClauses[i].Item2;
  					else
  						context.Sink.Error(node, "The default case must appear last, and there can be only one.");
  				} else {
  					if (ifStmt == null)
  						ifStmt = F.Call(S.If, ifClauses[i].Item1, ifClauses[i].Item2);
  					else
  						ifStmt = F.Call(S.If, ifClauses[i].Item1, ifClauses[i].Item2, ifStmt);
  				}
  			}
  
  			if (cmc.NodeVars.Count > 0)
  				output.Add(F.Call(S.Var, ListExt.Single(F.Id("LNode")).Concat(
  				cmc.NodeVars.OrderBy(v => v.Key.Name).Select(kvp => kvp.Value ? F.Call(S.Assign, F.Id(kvp.Key), F.Null) : F.Id(kvp.Key)))));
  			if (cmc.ListVars.Count > 0) {
  				LNode type = LNode.Id((Symbol) "LNodeList");
  				output.Add(F.Call(S.Var, ListExt.Single(type).Concat(
  				cmc.ListVars.OrderBy(v => v.Key.Name).Select(kvp => kvp.Value ? LNode.Call(CodeSymbols.Assign, LNode.List(F.Id(kvp.Key), LNode.Call(CodeSymbols.Default, LNode.List(type)))).SetStyle(NodeStyle.Operator) : F.Id(kvp.Key)))));
  			}
  			if (output.Count == 0)
  				return ifStmt.IncludingTriviaFrom(node);
  			else {
  				output.Add(ifStmt);
  				return F.Braces(output.ToVList()).IncludingTriviaFrom(node);
  			}
  		}
  
  		class CodeMatchContext
  		{
  			HashSet<Symbol> DuplicateDetector = new HashSet<Symbol>();
  			public Dictionary<Symbol, int> UsageCounters = new Dictionary<Symbol, int>();
  			// The boolean value indicates whether the variable needs initialization due to multi-case
  			public Dictionary<Symbol, bool> NodeVars = new Dictionary<Symbol, bool>();	// LNode vars to declare
  			public Dictionary<Symbol, bool> ListVars = new Dictionary<Symbol, bool>();	// LNodeList vars to declare
  			public LNodeList ThenClause = new LNodeList();	// stuff to do at the start of the {block} inside the if-statement
  			public IMacroContext Context;
  			public bool IsMultiCase;	// true if building a case statement with multiple patterns
  			public WList<LNode> Tests = new WList<LNode>();	// list of tests involved in matching the current pattern
  			internal LNode MakeTopTestExpr(LNode pattern, LNode var)
  			{
  				DuplicateDetector.Clear();
  				Tests.Clear();
  				MakeTestExpr(pattern, var);
  
  				LNode result = null;
  				foreach (var test in Tests)
  					result = LNode.MergeBinary(result, test, S.And);
  				return result;
  			}
  
  			private void MakeTestExpr(LNode pattern, LNode candidate)
  			{
  				Symbol varArgSym;
  				LNode varArgCond;
  				MakeTestExpr(pattern, candidate, out varArgSym, out varArgCond);
  				if (varArgSym != null)
  					Context.Sink.Error(pattern, "A list cannot be matched in this context. Remove '...' or 'params'.");
  			}
  			private void MakeTestExpr(LNode pattern, LNode candidate, out Symbol varArgSym, out LNode varArgCond)
  			{
  				varArgSym = null; varArgCond = null;
  
  				// is this a $substitutionVar?
  				LNode condition;
  				bool isParams, refExistingVar;
  				var nodeVar = DecodeSubstitutionExpr(pattern, out condition, out isParams, out refExistingVar);
  
  				// Unless the candidate is a simple variable name, avoid repeating 
  				// it by creating a temporary variable to hold its value
  				int predictedTests = pattern.Attrs.Count + 
  				(nodeVar != null ? 0 : pattern.Args.Count) + 
  				(!pattern.HasSimpleHeadWithoutPAttrs() ? 1 : 0);
  				if (predictedTests > 1)
  					candidate = MaybePutCandidateInTempVar(candidate.IsCall, candidate);
  
  				MatchAttributes(pattern, candidate);	// Look for @[$(...var)]
  				// case $_
  				if (nodeVar != null) {
  					if (nodeVar != __ || condition != null) {
  						if (!refExistingVar)
  							AddVar(nodeVar, isParams, errAt: pattern);
  						if (!isParams) {
  							var assignment = LNode.Call(CodeSymbols.Assign, LNode.List(F.Id(nodeVar), candidate)).SetStyle(NodeStyle.Operator);
  							Tests.Add(LNode.Call(CodeSymbols.NotEq, LNode.List(assignment.PlusAttrs(LNode.List(LNode.InParensTrivia)), LNode.Literal(null))).SetStyle(NodeStyle.Operator));
  							Tests.Add(condition);
  						}
  					}
  					if (isParams) {
  						varArgSym = nodeVar;
  						varArgCond = condition;
  						return;
  					}
  				} else if (pattern.IsId) {
  					Tests.Add(LNode.Call(LNode.Call(CodeSymbols.Dot, LNode.List(candidate, LNode.Id((Symbol) "IsIdNamed"))).SetStyle(NodeStyle.Operator), LNode.List(LNode.Call(CodeSymbols.Cast, LNode.List(F.Literal(pattern.Name.Name), LNode.Id((Symbol) "Symbol"))).SetStyle(NodeStyle.Operator))));
  				} else if (pattern.IsLiteral) {
  					if (pattern.Value == null)
  						Tests.Add(LNode.Call(CodeSymbols.Eq, LNode.List(LNode.Call(CodeSymbols.Dot, LNode.List(candidate, LNode.Id((Symbol) "Value"))).SetStyle(NodeStyle.Operator), LNode.Literal(null))).SetStyle(NodeStyle.Operator));
  					else
  						Tests.Add(LNode.Call(LNode.Call(CodeSymbols.Dot, LNode.List(pattern, LNode.Id((Symbol) "Equals"))).SetStyle(NodeStyle.Operator), LNode.List(LNode.Call(CodeSymbols.Dot, LNode.List(candidate, LNode.Id((Symbol) "Value"))).SetStyle(NodeStyle.Operator))));
  				} else {	// call(...)
  					int? varArgAt;
  					int fixedArgC = GetFixedArgCount(pattern.Args, out varArgAt);
  
  					// Test if the call target matches
  					var pTarget = pattern.Target;
  					if (pTarget.IsId && !pTarget.HasPAttrs()) {
  						var quoteTarget = QuoteSymbol(pTarget.Name);
  						LNode targetTest;
  						if (varArgAt.HasValue && fixedArgC == 0)
  							targetTest = LNode.Call(LNode.Call(CodeSymbols.Dot, LNode.List(candidate, LNode.Id((Symbol) "Calls"))).SetStyle(NodeStyle.Operator), LNode.List(quoteTarget));
  						else if (varArgAt.HasValue)
  							targetTest = LNode.Call(LNode.Call(CodeSymbols.Dot, LNode.List(candidate, LNode.Id((Symbol) "CallsMin"))).SetStyle(NodeStyle.Operator), LNode.List(quoteTarget, F.Literal(fixedArgC)));
  						else
  							targetTest = LNode.Call(LNode.Call(CodeSymbols.Dot, LNode.List(candidate, LNode.Id((Symbol) "Calls"))).SetStyle(NodeStyle.Operator), LNode.List(quoteTarget, F.Literal(fixedArgC)));
  						Tests.Add(targetTest);
  					} else {
  						if (fixedArgC == 0) {
  							Tests.Add(LNode.Call(CodeSymbols.Dot, LNode.List(candidate, LNode.Id((Symbol) "IsCall"))).SetStyle(NodeStyle.Operator));
  							if (!varArgAt.HasValue)
  								Tests.Add(LNode.Call(CodeSymbols.Eq, LNode.List(LNode.Call(CodeSymbols.Dot, LNode.List(LNode.Call(CodeSymbols.Dot, LNode.List(candidate, LNode.Id((Symbol) "Args"))).SetStyle(NodeStyle.Operator), LNode.Id((Symbol) "Count"))).SetStyle(NodeStyle.Operator), LNode.Literal(0))).SetStyle(NodeStyle.Operator));
  						} else {
  							var op = varArgAt.HasValue ? S.GE : S.Eq;
  							Tests.Add(LNode.Call(op, LNode.List(LNode.Call(CodeSymbols.Dot, LNode.List(LNode.Call(CodeSymbols.Dot, LNode.List(candidate, LNode.Id((Symbol) "Args"))).SetStyle(NodeStyle.Operator), LNode.Id((Symbol) "Count"))).SetStyle(NodeStyle.Operator), F.Literal(fixedArgC))));
  						}
  						int i = Tests.Count;
  						MakeTestExpr(pTarget, LNode.Call(CodeSymbols.Dot, LNode.List(candidate, LNode.Id((Symbol) "Target"))).SetStyle(NodeStyle.Operator));
  					}
  
  					MakeArgListTests(pattern.Args, ref candidate);
  				}
  			}
  
  			// Used for optimization, to avoid writing complicated expressions in the output:
  			// e.g. instead of  code.Args[0].Target.Args.Count == 1 && code.Args[0].Target.Args[0].IsIdNamed((Symbol) "_")
  			//   we might write (tmp_5 = code.Args[0].Target) != null && tmp_5.Args.Count == 1 && tmp_5.Args[0].IsIdNamed((Symbol) "_")
  			LNode MaybePutCandidateInTempVar(bool condition, LNode candidate)
  			{
  				if (condition) {
  					var targetTmp = NextTempName(Context);
  					var targetTmpId = F.Id(targetTmp);
  					AddVar(targetTmp, false, errAt: candidate);
  					Tests.Add(LNode.Call(CodeSymbols.NotEq, LNode.List(LNode.Call(LNode.List(LNode.InParensTrivia), CodeSymbols.Assign, LNode.List(targetTmpId, candidate)).SetStyle(NodeStyle.Operator), LNode.Literal(null))).SetStyle(NodeStyle.Operator));
  					return targetTmpId;
  				} else {
  					return candidate;
  				}
  			}
  
  			private void AddVar(Symbol varName, bool isList, LNode errAt)
  			{
  				if (!DuplicateDetector.Add(varName))
  					Context.Sink.Error(errAt, "'{0}': Each matched $variable must have a unique name.", varName);
  				var vars = isList ? ListVars : NodeVars;
  				if (!vars.ContainsKey(varName))
  					vars[varName] = false;
  				UsageCounters[varName] = UsageCounters.TryGetValue(varName, 0) + 1;
  			}
  
  			private void MatchAttributes(LNode pattern, LNode candidate)
  			{
  				LNode condition;
  				bool isParams, refExistingVar;
  				Symbol listVar;
  				var pAttrs = pattern.PAttrs();
  				if (pAttrs.Count == 1 && (listVar = DecodeSubstitutionExpr(pAttrs[0], out condition, out isParams, out refExistingVar)) != null && isParams) {
  					if (listVar != __ || condition != null) {
  						if (!refExistingVar)
  							AddVar(listVar, true, errAt: pattern);
  						Tests.Add(LNode.Call(CodeSymbols.OrBits, LNode.List(LNode.Call(CodeSymbols.Dot, LNode.List(LNode.Call(LNode.List(LNode.InParensTrivia), CodeSymbols.Assign, LNode.List(F.Id(listVar), LNode.Call(CodeSymbols.Dot, LNode.List(candidate, LNode.Id((Symbol) "Attrs"))).SetStyle(NodeStyle.Operator))).SetStyle(NodeStyle.Operator), LNode.Id((Symbol) "IsEmpty"))).SetStyle(NodeStyle.Operator), LNode.Literal(true))).SetStyle(NodeStyle.Operator));
  						if (condition != null)
  							Tests.Add(condition);
  					}
  				} else if (pAttrs.Count != 0)
  					Context.Sink.Error(pAttrs[0], "Currently, Attribute matching is very limited; you can only use `[$(...varName)]`");
  			}
  
  			private int GetFixedArgCount(LNodeList patternArgs, out int? varArgAt)
  			{
  				varArgAt = null;
  				int argc = 0;
  				for (int i = 0; i < patternArgs.Count; i++) {
  					LNode condition;
  					bool isParams, _;
  					var nodeVar = DecodeSubstitutionExpr(patternArgs[i], out condition, out isParams, out _);
  					if (isParams)
  						varArgAt = i;
  					else
  						argc++;
  				}
  				return argc;
  			}
  
  			private void MakeArgListTests(LNodeList patternArgs, ref LNode candidate)
  			{
  				// Note: at this point we can assume that the quantity of 
  				// arguments has already been checked and is not too small.
  				Symbol varArgSym = null;
  				LNode varArgCond = null;
  				int i;
  
gt: 				for (i = 0; i < patternArgs.Count; i++) {