private static void DoDeconstruct(LNode arg, IMacroContext context, bool printErrorOnFailure) { LNode patternSpec = arg.Args[0, LNode.Missing], input = arg.Args[1, LNode.Missing]; if (!arg.Calls(S.Assign, 2)) { if (arg.Calls(S.Lambda, 2)) { G.Swap(ref patternSpec, ref input); } else { context.Sink.Error(arg, "expected an assignment (`patterns = input`)"); return; } } // Build list of patterns out of the binary operator series p1 | p2 | p3 var patterns = new FVList <LNode>(); while (patternSpec.Calls(S.OrBits, 2) && !patternSpec.IsParenthesizedExpr()) { patterns.Add(patternSpec[1]); patternSpec = patternSpec[0]; } patterns.Add(patternSpec); // Remove outer braces, then run macros patterns = patterns.SmartSelect(p => p.Calls(S.Braces, 1) ? p[0] : p); input = input.Calls(S.Braces, 1) ? input[0] : input; input = context.PreProcess(input); // Perform matching & capturing foreach (var pattern in patterns) { IDictionary <Symbol, LNode> captures; if (LNodeExt.MatchesPattern(input, pattern, out captures)) { if (captures.Count == 0) { context.Write(printErrorOnFailure ? Severity.Warning : Severity.Error, pattern, "This pattern has no effect, since it does not use `$` to capture any variables."); } SetSyntaxVariables(captures, context); return; } } if (printErrorOnFailure) { context.Sink.Error(arg, "Deconstruction failed."); } }
public static bool IsComplexIdentifier(LNode n, ICI f = ICI.Default, Pedantics p = Pedantics.Lax) { // Returns true if 'n' is printable as a complex identifier. // // To be printable, a complex identifier in EC# must not contain // attributes ((p & Pedantics.DropNonDeclAttrs) != 0 to override) and must be // 1. A simple symbol // 2. A substitution expression // 3. A dotted expr (a.b), where 'a' is a complex identifier and 'b' // is (1) or (2); structures like #.(a, b, c) and #.(a, b<c>) do // not count as complex identifiers. Note that a.b<c> is // structured #of(#.(a, b), c), not #.(a, #of(b, c)). A dotted // expression that starts with a dot, such as .a.b, is structured // (.a).b rather than .(a.b); unary . has high precedence. // 4. An #of expr a<b,...>, where // - 'a' is a complex identifier and not itself an #of expr // - each arg 'b' is a complex identifier (if printing in C# style) // // Type names have the same structure, with the following patterns for // arrays, pointers, nullables and typeof<>: // // Foo* <=> #of(@*, Foo) // Foo[] <=> #of(@`[]`, Foo) // Foo[,] <=> #of(#`[,]`, Foo) // Foo? <=> #of(@?, Foo) // typeof<X> <=> #of(#typeof, X) // // Note that we can't just use #of(Nullable, Foo) for Foo? because it // doesn't work if System is not imported. It's reasonable to allow #? // as a synonym for global::System.Nullable, since we have special // symbols for types like #int32 anyway. // // (a.b<c>.d<e>.f is structured ((((a.b)<c>).d)<e>).f or #.(#of(#.(#of(#.(a,b), c), d), e), f) if ((f & ICI.AllowAttrs) == 0 && ((f & ICI.AllowParensAround) != 0 ? HasPAttrs(n, p) : HasPAttrsOrParens(n, p))) { // Attribute(s) are illegal, except 'in', 'out' and 'where' when // TypeParamDefinition inside <...> return((f & (ICI.NameDefinition | ICI.InOf)) == (ICI.NameDefinition | ICI.InOf) && IsPrintableTypeParam(n)); } if (n.IsId) { return(true); } if (CallsWPAIH(n, S.Substitute, 1, p)) { return(true); } if (CallsMinWPAIH(n, S.Of, 1, p) && (f & ICI.DisallowOf) == 0) { var baseName = n.Args[0]; if (!IsComplexIdentifier(baseName, (f & (ICI.DisallowDotted)) | ICI.DisallowOf, p)) { return(false); } if ((f & ICI.AllowAnyExprInOf) != 0) { return(true); } return(OfHasNormalArgs(n, (f & ICI.NameDefinition) != 0, p)); } if (CallsWPAIH(n, S.Dot, p) && (f & ICI.DisallowDotted) == 0 && Range.IsInRange(n.ArgCount, 1, 2)) { var args = n.Args; LNode lhs = args[0], rhs = args.Last; // right-hand argument must be simple var rhsFlags = (f & ICI.ExprMode) | ICI.DisallowOf | ICI.DisallowDotted; if ((f & ICI.ExprMode) != 0) { rhsFlags |= ICI.AllowParensAround; } if (!IsComplexIdentifier(args.Last, rhsFlags, p)) { return(false); } if ((f & ICI.ExprMode) != 0 && lhs.IsParenthesizedExpr() || (lhs.IsCall && !lhs.Calls(S.Dot) && !lhs.Calls(S.Of))) { return(true); } return(IsComplexIdentifier(args[0], (f & ICI.ExprMode), p)); } return(false); }
public bool AutoPrintComplexIdentOperator(Precedence precedence, Precedence context, Ambiguity flags) { // Handles #of and @`.`, including array types int argCount = _n.ArgCount; Symbol name = _n.Name; Debug.Assert((name == S.Of || name == S.Dot) && _n.IsCall); var first = _n.Args[0, null]; if (first == null) { return(false); // no args } bool needParens, needSpecialOfNotation = false; if (!CanAppearIn(precedence, context, out needParens) || needParens) { return(false); // this only happens inside $ operator, e.g. $(a.b) } if (name == S.Dot) { // The trouble with the dot is its high precedence; because of // this, arguments after a dot cannot use prefix notation as a // fallback. For example "@.(a, b(c))" cannot be printed "a.b(c)" // since that means @.(a, b)(c)". The first argument to non- // unary "." can use prefix notation safely though, e.g. // "@.(b(c), a)" can (and must) be printed "b(c).a". if (argCount > 2) { return(false); } if (HasPAttrs(first)) { return(false); } LNode afterDot = _n.Args.Last; // Unary dot is no problem: .(a) is parsed the same as .a, i.e. // the parenthesis are ignored, so we can print an expr like // @`.`(a+b) as .(a+b), but the parser counts parens on binary // dot, so in that case the argument after the dot must not be any // kind of call (except substitution) and must not have attributes, // unless it is in parens. if (argCount == 2 && !afterDot.IsParenthesizedExpr()) { if (HasPAttrs(afterDot)) { return(false); } if (afterDot.IsCall && afterDot.Name != S.Substitute) { return(false); } } else if ((flags & Ambiguity.CastRhs) != 0) { return(false); // cannot print (Foo) @`.`(x) as (Foo) .x } } else if (name == S.Of) { var ici = ICI.Default | ICI.AllowAttrs; if ((flags & Ambiguity.InDefinitionName) != 0) { ici |= ICI.NameDefinition; } if (!IsComplexIdentifier(_n, ici)) { if (IsComplexIdentifier(_n, ici | ICI.AllowAnyExprInOf)) { needSpecialOfNotation = true; } else { return(false); } } } if (name == S.Dot) { if (argCount == 1) { _out.Write('.', true); PrintExpr(first, EP.Substitute); } else { PrintExpr(first, precedence.LeftContext(context), flags & Ambiguity.TypeContext); _out.Write('.', true); PrintExpr(_n.Args[1], precedence.RightContext(context)); } } else if (_n.Name == S.Of) { // Check for special type names such as Foo? or Foo[] Symbol stk = SpecialTypeKind(_n, flags, context); if (stk != null) { if (S.IsArrayKeyword(stk)) { // We do something very strange in case of arrays of arrays: // the order of the square brackets must be reversed when // arrays are nested. For example, an array of two-dimensional // arrays of int is written int[][,], rather than int[,][] // which would be much easier to handle. var stack = InternalList <Symbol> .Empty; var innerType = _n; do { stack.Add(stk); innerType = innerType.Args[1]; } while (S.IsArrayKeyword(stk = SpecialTypeKind(innerType, flags, context) ?? GSymbol.Empty)); PrintType(innerType, EP.Primary.LeftContext(context), (flags & Ambiguity.AllowPointer)); for (int i = 0; i < stack.Count; i++) { _out.Write(stack[i].Name, true); // e.g. [] or [,] } } else { PrintType(_n.Args[1], EP.Primary.LeftContext(context), (flags & Ambiguity.AllowPointer)); _out.Write(stk == S._Pointer ? '*' : '?', true); } return(true); } PrintExpr(first, precedence.LeftContext(context)); _out.Write(needSpecialOfNotation ? "!(" : "<", true); for (int i = 1; i < argCount; i++) { if (i > 1) { WriteThenSpace(',', SpaceOpt.AfterCommaInOf); } PrintType(_n.Args[i], StartExpr, Ambiguity.InOf | Ambiguity.AllowPointer | (flags & Ambiguity.InDefinitionName)); } _out.Write(needSpecialOfNotation ? ')' : '>', true); } else { Debug.Assert(_n.Name == S.Substitute); G.Verify(AutoPrintOperator(ContinueExpr, 0)); } return(true); }