public static Symbol NextTempName(IMacroContext ctx, LNode value) { string prefix = value.Name.Name; prefix = EcsValidators.IsPlainCsIdentifier(prefix) ? prefix + "_" : "tmp_"; return(NextTempName(ctx, prefix)); }
public void SanitizeIdentifierTests() { AreEqual("I_aposd", EcsValidators.SanitizeIdentifier("I'd")); AreEqual("_123", EcsValidators.SanitizeIdentifier("123")); AreEqual("_plus5", EcsValidators.SanitizeIdentifier("+5")); AreEqual("__empty__", EcsValidators.SanitizeIdentifier("")); AreEqual("_lt_gt", EcsValidators.SanitizeIdentifier("<>")); }
public static LNode methodStyleMacro(LNode node, IMacroContext context) { if (EcsValidators.MethodDefinitionKind(node, out var defineKw, out var macroName, out var args, out var body) == S.Fn && body != null && defineKw.IsIdNamed("macro")) { var pattern = args.WithTarget(macroName); return(CompileMacro(pattern, body, context, node.Attrs)); } return(null); }
public static LNode useSymbols(LNode input, IMacroContext context) { var args_body = context.GetArgsAndBody(true); // Decode options (TODO: invent a simpler approach) string prefix = "sy_"; var inherited = new HashSet <Symbol>(); foreach (var pair in MacroContext.GetOptions(args_body.A)) { if (pair.Key.Name == "prefix" && pair.Value.IsId) { prefix = pair.Value.Name.Name; } else if (pair.Key.Name == "inherit" && pair.Value.Value is Symbol) { inherited.Add((Symbol)pair.Value.Value); } else if (pair.Key.Name == "inherit" && (pair.Value.Calls(S.Braces) || pair.Value.Calls(S.Tuple)) && pair.Value.Args.All(n => n.Value is Symbol)) { foreach (var arg in pair.Value.Args) { inherited.Add((Symbol)arg.Value); } } else { context.Sink.Write(Severity.Warning, pair.Value, "Unrecognized parameter. Expected prefix:id or inherit:{@@A; @@B; ...})"); } } // Replace all symbols while collecting a list of them var symbols = new Dictionary <Symbol, LNode>(); VList <LNode> output = args_body.B.SmartSelect(stmt => stmt.ReplaceRecursive(n => { var sym = n.Value as Symbol; if (n.IsLiteral && sym != null) { return(symbols[sym] = LNode.Id(prefix + EcsValidators.SanitizeIdentifier(sym.Name))); } return(null); })); // Return updated code with variable declaration at the top for all non-inherit symbols used. var _Symbol = F.Id("Symbol"); var vars = (from sym in symbols where !inherited.Contains(sym.Key) select F.Call(S.Assign, sym.Value, F.Call(S.Cast, F.Literal(sym.Key.Name), _Symbol))).ToList(); if (vars.Count > 0) { output.Insert(0, F.Call(S.Var, ListExt.Single(_Symbol).Concat(vars)) .WithAttrs(input.Attrs.Add(F.Id(S.Static)).Add(F.Id(S.Readonly)))); } return(F.Call(S.Splice, output)); }
public static LNode useSymbols(LNode input, IMacroContext context) { bool inType = context.Ancestors.Any(parent => { var kind = EcsValidators.SpaceDefinitionKind(parent); return(kind != null && kind != S.Namespace); }); var args_body = context.GetArgsAndBody(true); return(UseSymbolsCore(input.Attrs, args_body.A, args_body.B, context, inType)); }
public static LNode @nameof(LNode nameof, IMacroContext context) { if (nameof.ArgCount != 1) { return(null); } Symbol expr = EcsValidators.KeyNameComponentOf(nameof.Args[0]); return(F.Literal(expr.Name)); }
public static LNode replaceFn(LNode node, IMacroContext context1) { var retType = node.Args[0, LNode.Missing].Name; if (retType != _replace && retType != _define) { return(null); } LNode replaceKw, macroName, args, body; if (EcsValidators.MethodDefinitionKind(node, out replaceKw, out macroName, out args, out body, allowDelegate: false) != S.Fn || body == null) { return(null); } MacroMode mode, modes = 0; var leftoverAttrs = node.Attrs.SmartWhere(attr => { if (attr.IsId && Loyc.Compatibility.EnumStatic.TryParse(attr.Name.Name, out mode)) { modes |= mode; return(false); } return(true); }); LNode pattern = F.Call(macroName, args.Args).PlusAttrs(leftoverAttrs); LNode replacement = body.AsList(S.Braces).AsLNode(S.Splice).PlusAttrs(replaceKw.Attrs); replacement.Style &= ~NodeStyle.OneLiner; WarnAboutMissingDollarSigns(args, context1, pattern, replacement); // Note: we could fill out the macro's Syntax and Description with the // pattern and replacement converted to strings, but it's generally a // waste of CPU time as those strings are usually not requested. var lma = new LexicalMacroAttribute( string.Concat(macroName.Name, "(", args.Args.Count.ToString(), " args)"), "", macroName.Name.Name); var macroInfo = new MacroInfo(null, lma, (candidate, context2) => { MMap <Symbol, LNode> captures = new MMap <Symbol, LNode>(); VList <LNode> unmatchedAttrs; if (candidate.MatchesPattern(pattern, ref captures, out unmatchedAttrs)) { return(ReplaceCaptures(replacement, captures).PlusAttrsBefore(unmatchedAttrs)); } return(null); }) { Mode = modes }; context1.RegisterMacro(macroInfo); return(F.Splice()); }
private static bool IsVar(LNode arg, out LNode type, out Symbol name, out LNode defaultValue) { name = null; LNode nameNode; if (!EcsValidators.IsVariableDeclExpr(arg, out type, out nameNode, out defaultValue)) { return(false); } name = nameNode.Name; return(nameNode.IsId); }
public void IsPlainCsIdentifierTests() { IsTrue(EcsValidators.IsPlainCsIdentifier("x")); IsTrue(EcsValidators.IsPlainCsIdentifier("aAzZ_")); IsTrue(EcsValidators.IsPlainCsIdentifier("_19aAzZ")); IsFalse(EcsValidators.IsPlainCsIdentifier("19aAzZ")); IsFalse(EcsValidators.IsPlainCsIdentifier("_<>_")); IsFalse(EcsValidators.IsPlainCsIdentifier("I'd")); IsFalse(EcsValidators.IsPlainCsIdentifier("C#")); IsFalse(EcsValidators.IsPlainCsIdentifier("#5")); IsFalse(EcsValidators.IsPlainCsIdentifier("")); }
static LNode TempVarDecl(LNode value, out LNode tmpId) { string prefix = value.Name.Name; if (!EcsValidators.IsPlainCsIdentifier(prefix)) { prefix = "tmp_"; } else { prefix += "_"; } return(TempVarDecl(value, out tmpId, prefix)); }
public static LNode Constructor(LNode cons, IMacroContext context) { if (cons.ArgCount >= 3 && cons.Args[1].IsIdNamed(S.This)) { var anc = context.Ancestors; LNode space = anc.TryGet(anc.Count - 3, LNode.Missing), typeName; Symbol type = EcsValidators.SpaceStatementKind(space); if (type != null && anc[anc.Count - 2] == space.Args[2]) { typeName = space.Args[0]; return(cons.WithArgChanged(1, F.Id(KeyNameComponentOf(typeName)))); } } return(null); }
private static LNode CompileTimeMacro(string macroName, LNode node, IMacroContext context, bool alsoRuntime, bool wantPreprocess = true) { if (node.ArgCount != 1 || !node[0].Calls(S.Braces)) { context.Error(node.Target, "{0} should have a single argument: a braced block.", macroName); return(null); } LNodeList code = node[0].Args; if (wantPreprocess) { if (context.Ancestors.Take(context.Ancestors.Count - 1).Any( n => n.Name.IsOneOf(S.Class, S.Struct, S.Enum, S.Namespace) || n.Name.IsOneOf(S.Constructor, S.Fn, S.Property, S.Var))) { context.Error(node.Target, "{0} is designed for use only at the top level of the file. It will be executed as though it is at the top level: any outer scopes will be ignored.", macroName); } code = context.PreProcess(code); } WriteHeaderCommentInSessionLog(node, context.Sink); // Remove namespace blocks (not supported by Roslyn scripting) LNode namespaceBlock = null; var codeSansNamespaces = code.RecursiveReplace(RemoveNamespaces); LNodeList?RemoveNamespaces(LNode n) { if (EcsValidators.SpaceDefinitionKind(n, out _, out _, out LNode body) == S.Namespace) { namespaceBlock = n; return(body.Args.RecursiveReplace(RemoveNamespaces)); } return(null); } if (namespaceBlock != null && wantPreprocess) { context.Warning(namespaceBlock, "The C# scripting engine does not support namespaces. They will be ignored when running at compile time."); } RunCSharpCodeWithRoslyn(node, codeSansNamespaces, context); _roslynSessionLog?.Flush(); return(alsoRuntime ? F.Splice(code) : F.Splice()); }
public static LNode defineId(LNode node, IMacroContext context) { if (node.Args[0, LNode.Missing].Name != _define) { return(null); } LNode defineKw, macroId, args, body, initialValue; if (!EcsValidators.IsPropertyDefinition(node, out defineKw, out macroId, out args, out body, out initialValue) || body == null || args.ArgCount != 0 || initialValue != null) { return(null); } return(RegisterSimpleMacro(node.Attrs, macroId, body, context)); }
void CheckIsComplexIdentifier(bool?result, LNode expr) { _testNum++; var isCI = EcsValidators.IsComplexIdentifier(expr, ICI.Default, EcsValidators.Pedantics.Strict); if (result == null && !isCI) { return; } else if (result == isCI) { return; } Assert.Fail(string.Format( "IsComplexIdentifier: fail on test #{0} '{1}'. Expected {2}, got {3}", _testNum, expr.ToString(), result, isCI)); }
public static LNode DetectCurrentNamespace(LNode node, IMacroContext context) { if (EcsValidators.SpaceDefinitionKind(context.Parent, out LNode name, out _, out _) == S.Namespace) { var newNamespace = name.Print(ParsingMode.Expressions); var currentNamespace = context.ScopedProperties.TryGetValue(_currentNamespace, null) as Symbol; if (currentNamespace == null) { currentNamespace = (Symbol)newNamespace; } else { currentNamespace = (Symbol)(currentNamespace.Name + "." + newNamespace); } context.OpenMacroNamespaces.Add(currentNamespace); context.ScopedProperties[_currentNamespace] = currentNamespace; } return(null); // don't alter output }
void CheckIsComplexIdentifier(bool?result, LNode expr) { var np = new EcsNodePrinter(expr, null); _testNum++; var isCI = EcsValidators.IsComplexIdentifier(expr); if (result == null && !isCI) { return; } else if (result == isCI) { return; } Assert.Fail(string.Format( "IsComplexIdentifier: fail on test #{0} '{1}'. Expected {2}, got {3}", _testNum, expr.ToString(), result, isCI)); }
public static LNode rule(LNode node, IMacroContext context) { bool isToken; if ((isToken = node.Calls(_token, 2)) || node.Calls(_rule, 2)) { node = context.PreProcessChildren(); LNode sig = node.Args[0]; // Ugh. Because the rule has been macro-processed, "rule X::Y ..." // has become "rule #var(Y,X) ...". We must allow this, because in // case of something like "rule X(arg::int)::Y" we actually do want // the argument to become `#var(int, arg)`; so just reverse the // transform that we didn't want. if (sig.Calls(S.Var, 2)) { sig = F.Call(S.ColonColon, sig.Args[1], sig.Args[0]); } LNode name = sig, returnType = F.Void; if (sig.Calls(S.ColonColon, 2)) { returnType = sig.Args[1]; name = sig.Args[0]; } if (EcsValidators.IsComplexIdentifier(name)) { name = F.Call(name); // def requires an argument list } LNodeList args = name.Args; name = name.Target; LNode newBody = ParseRuleBody(node.Args[1], context); if (newBody != null) { return(node.With(isToken ? _hash_token : _hash_rule, returnType, name, F.AltList(args), newBody)); } } return(null); }
private static object RunCSharpCodeWithRoslyn(LNode parent, LNodeList code, IMacroContext context, ParsingMode printMode = null) { // Note: when using compileTimeAndRuntime, the transforms here affect the version // sent to Roslyn, but not the runtime version placed in the output file. code = code.SmartSelectMany(stmt => { // Ensure #r gets an absolute path; I don't know what Roslyn does with a // relative path (maybe WithMetadataResolver would let me control this, // but it's easier not to) if ((stmt.Calls(S.CsiReference, 1) || stmt.Calls(S.CsiLoad, 1)) && stmt[0].Value is string fileName) { fileName = fileName.Trim().WithoutPrefix("\"").WithoutSuffix("\""); var inputFolder = context.ScopedProperties.TryGetValue((Symbol)"#inputFolder", "").ToString(); var fullPath = Path.Combine(inputFolder, fileName); return(LNode.List(stmt.WithArgChanged(0, stmt[0].WithValue("\"" + fullPath + "\"")))); } // For each (top-level) LexicalMacro method, call #macro_context.RegisterMacro(). LNode attribute = null; if ((attribute = stmt.Attrs.FirstOrDefault( attr => AppearsToCall(attr, "LeMP", nameof(LexicalMacroAttribute).WithoutSuffix("Attribute")) || AppearsToCall(attr, "LeMP", nameof(LexicalMacroAttribute)))) != null && EcsValidators.MethodDefinitionKind(stmt, out _, out var macroName, out _, out _) == S.Fn) { var setters = SeparateAttributeSetters(ref attribute); attribute = attribute.WithTarget((Symbol)nameof(LexicalMacroAttribute)); setters.Insert(0, attribute); var newAttribute = F.Call(S.New, setters); var registrationCommand = F.Call(F.Dot(__macro_context, nameof(IMacroContext.RegisterMacro)), F.Call(S.New, F.Call(nameof(MacroInfo), F.Null, newAttribute, macroName))); return(LNode.List(stmt, registrationCommand)); } return(LNode.List(stmt)); }); var outputLocationMapper = new LNodeRangeMapper(); var options = new LNodePrinterOptions { IndentString = " ", SaveRange = outputLocationMapper.SaveRange }; string codeText = EcsLanguageService.WithPlainCSharpPrinter.Print(code, context.Sink, printMode, options); _roslynSessionLog?.WriteLine(codeText); _roslynSessionLog?.Flush(); _roslynScriptState.GetVariable(__macro_context_sanitized).Value = context; try { // Allow users to write messages via MessageSink.Default using (MessageSink.SetDefault(new MessageSinkFromDelegate((sev, ctx, msg, args) => { _roslynSessionLog?.Write("{0} from user ({1}): ", sev, MessageSink.GetLocationString(ctx)); _roslynSessionLog?.WriteLine(msg, args); context.Sink.Write(sev, ctx, msg, args); }))) { _roslynScriptState = _roslynScriptState.ContinueWithAsync(codeText).Result; } return(_roslynScriptState.ReturnValue); } catch (CompilationErrorException e) when(e.Diagnostics.Length > 0 && e.Diagnostics[0].Location.IsInSource) { // Determine the best location in the source code at which to report the error. // Keep in mind that the error may have occurred in a synthetic location not // found in the original code, and we cannot report such a location. Microsoft.CodeAnalysis.Text.TextSpan range = e.Diagnostics[0].Location.SourceSpan; var errorLocation = new IndexRange(range.Start, range.Length); var mappedErrorLocation = outputLocationMapper.FindRelatedNodes(errorLocation, 10) .FirstOrDefault(p => !p.A.Range.Source.Text.IsEmpty); string locationCaveat = ""; if (mappedErrorLocation.A != null) { bool mappedIsEarly = mappedErrorLocation.B.EndIndex <= errorLocation.StartIndex; if (mappedIsEarly || mappedErrorLocation.B.StartIndex >= errorLocation.EndIndex) { locationCaveat = "; " + "The error occurred at a location ({0}) that doesn't seem to exist in the original code.".Localized( mappedIsEarly ? "after the location indicated".Localized() : "before the location indicated".Localized()); } } // Extract the line where the error occurred, for inclusion in the error message int column = e.Diagnostics[0].Location.GetLineSpan().StartLinePosition.Character; int lineStart = range.Start - column; int lineEnd = codeText.IndexOf('\n', lineStart); if (lineEnd < lineStart) { lineEnd = codeText.Length; } string line = codeText.Substring(lineStart, lineEnd - lineStart); string errorMsg = e.Message + " - in «{0}»{1}".Localized(line, locationCaveat); context.Sink.Error(mappedErrorLocation.A ?? parent, errorMsg); LogRoslynError(e, context.Sink, parent, compiling: true); } catch (Exception e) { while (e is AggregateException ae && ae.InnerExceptions.Count == 1) { e = ae.InnerExceptions[0]; } context.Sink.Error(parent, "An exception was thrown from your code:".Localized() + " " + e.ExceptionMessageAndType()); LogRoslynError(e, context.Sink, parent, compiling: false); } return(NoValue.Value); }
Pair <VList <LNode>, LNode> BubbleUp_GeneralCall2(LNode expr) { var target = expr.Target; var args = expr.Args; var combinedSequence = LNode.List(); target = BubbleUpBlocks(target); if (target.CallsMin(__numrunSequence, 1)) { combinedSequence = target.Args.WithoutLast(1); expr = expr.WithTarget(target.Args.Last); } var isAssignment = EcsValidators.IsAssignmentOperator(expr.Name); if (isAssignment) { LNode lhs = BubbleUpBlocks(expr.Args[0]); LNode rhs = BubbleUpBlocks(expr.Args[1]); args = LNode.List(lhs, rhs); } else { args = args.SmartSelect(arg => BubbleUpBlocks(arg)); } int lastRunSeq = args.LastIndexWhere(a => a.CallsMin(__numrunSequence, 1)); if (lastRunSeq >= 0) { int lastRunSeqImpure = args.First(lastRunSeq + 1).LastIndexWhere(a => a.CallsMin(__numrunSequence, 1) && a.AttrNamed(_trivia_pure.Name) == null); if (lastRunSeq > 0 && (args.Count == 2 && (target.IsIdNamed(S.And) || target.IsIdNamed(S.Or)) || args.Count == 3 && target.IsIdNamed(S.QuestionMark))) { Context.Write(Severity.Error, target, "#useSequenceExpressions is not designed to support sequences or variable declarations on the right-hand side of the `&&`, `||` or `?` operators. The generated code will be incorrect."); } var argsW = args.ToList(); for (int i = 0; i <= lastRunSeq; i++) { LNode arg = argsW[i]; if (!arg.IsLiteral) { if (arg.CallsMin(__numrunSequence, 1)) { combinedSequence.AddRange(arg.Args.WithoutLast(1)); argsW[i] = arg = arg.Args.Last; } if (i < lastRunSeqImpure) { if (i == 0 && (expr.CallsMin(S.IndexBracks, 1) || expr.CallsMin(S.NullIndexBracks, 1))) { } else { if (isAssignment || arg.Attrs.Any(a => a.IsIdNamed(S.Ref) || a.IsIdNamed(S.Out))) { argsW[i] = MaybeCreateTemporaryForLValue(arg, ref combinedSequence); } else { LNode tmpVarName, tmpVarDecl = TempVarDecl(Context, arg, out tmpVarName); combinedSequence.Add(tmpVarDecl); argsW[i] = tmpVarName.PlusAttr(_trivia_isTmpVar); } } } } } expr = expr.WithArgs(LNode.List(argsW)); } return(Pair.Create(combinedSequence, expr)); }
public LNode EliminateSequenceExpressions(LNode stmt, bool isDeclContext) { LNode retType, name, argList, bases, body, initValue; if (EcsValidators.SpaceDefinitionKind(stmt, out name, out bases, out body) != null) { // Space definition: class, struct, etc. return(body == null ? stmt : stmt.WithArgChanged(2, EliminateSequenceExpressions(body, true))); } else if (EcsValidators.MethodDefinitionKind(stmt, out retType, out name, out argList, out body, true) != null) { // Method definition return(body == null ? stmt : stmt.WithArgChanged(3, EliminateSequenceExpressionsInLambdaExpr(body, retType))); } else if (EcsValidators.IsPropertyDefinition(stmt, out retType, out name, out argList, out body, out initValue)) { // Property definition stmt = stmt.WithArgChanged(3, body.WithArgs(part => { if (part.ArgCount == 1 && part[0].Calls(S.Braces)) { part = part.WithArgChanged(0, EliminateSequenceExpressions(part[0], false)); } return(part); })); if (initValue != null) { var initMethod = EliminateRunSeqFromInitializer(retType, name, ref initValue); if (initMethod != null) { stmt = stmt.WithArgChanged(4, initValue); return(LNode.Call((Symbol)"#runSequence", LNode.List(stmt, initMethod))); } } return(stmt); } else if (stmt.Calls(CodeSymbols.Braces)) { return(stmt.WithArgs(EliminateSequenceExpressions(stmt.Args, isDeclContext))); } else if (!isDeclContext) { return(EliminateSequenceExpressionsInExecStmt(stmt)); } else if (stmt.CallsMin(S.Var, 2)) { // Eliminate blocks from field member var results = new List <LNode> { stmt }; var vars = stmt.Args; var varType = vars[0]; for (int i = 1; i < vars.Count; i++) { var @var = vars[i]; if (@var.Calls(CodeSymbols.Assign, 2) && (name = @var.Args[0]) != null && (initValue = @var.Args[1]) != null) { var initMethod = EliminateRunSeqFromInitializer(varType, name, ref initValue); if (initMethod != null) { results.Add(initMethod); vars[i] = vars[i].WithArgChanged(1, initValue); } } } if (results.Count > 1) { results[0] = stmt.WithArgs(vars); return(LNode.List(results).AsLNode(__numrunSequence)); } return(stmt); } else { return(stmt); } }
static string LiteralToIdent(object literal) { return(EcsValidators.SanitizeIdentifier((literal ?? "null").ToString())); }
public LNode EliminateBlockExprs(LNode stmt, bool isDeclContext) { LNode retType, name, argList, bases, body, initValue; if (EcsValidators.SpaceDefinitionKind(stmt, out name, out bases, out body) != null) { return(body == null ? stmt : stmt.WithArgChanged(2, EliminateBlockExprs(body, true))); } else if (EcsValidators.MethodDefinitionKind(stmt, out retType, out name, out argList, out body, true) != null) { return(body == null ? stmt : stmt.WithArgChanged(3, EliminateBlockExprs(body, false))); } else if (EcsValidators.IsPropertyDefinition(stmt, out retType, out name, out argList, out body, out initValue)) { stmt = stmt.WithArgChanged(3, EliminateBlockExprs(body, false)); if (initValue != null) { var initMethod = EliminateRunSeqFromInitializer(retType, name, ref initValue); if (initMethod != null) { stmt = stmt.WithArgChanged(4, initValue); return(LNode.Call(CodeSymbols.Splice, LNode.List(stmt, initMethod))); } } return(stmt); } else if (!isDeclContext) { return(EliminateBlockExprsInExecStmt(stmt)); } else if (stmt.CallsMin(S.Var, 2)) { var results = new List <LNode> { stmt }; var vars = stmt.Args; var varType = vars[0]; for (int i = 1; i < vars.Count; i++) { { var tmp_1 = vars[i]; if (tmp_1.Calls(CodeSymbols.Assign, 2) && (name = tmp_1.Args[0]) != null && (initValue = tmp_1.Args[1]) != null) { var initMethod = EliminateRunSeqFromInitializer(varType, name, ref initValue); if (initMethod != null) { results.Add(initMethod); vars[i] = vars[i].WithArgChanged(1, initValue); } } } } if (results.Count > 1) { results[0] = stmt.WithArgs(vars); return(LNode.List(results).AsLNode(S.Splice)); } return(stmt); } else { return(stmt); } }
public static LNode CompileMacro(LNode pattern, LNode body, IMacroContext context, LNodeList attrs) { var modeNodes = attrs.Where(a => Enum.TryParse(a.Name.Name, out MacroMode _)); // unwrap braces (they're not part of the pattern, they just enable statement syntax in EC#) var pattern_apos = pattern.UnwrapBraces(); MacroMode modes = GetMacroMode(ref attrs, pattern_apos); // compileTime {...} can recognize macro method definitions. // Take advantage of this by generating a macro method which it will register for us. LNode macroName = pattern_apos.Target ?? pattern_apos; LNode syntax = F.Literal(pattern_apos.ToString()); LNode description = attrs.FirstOrDefault(a => a.Value is string) ?? F.Literal("User-defined macro at {0}".Localized(pattern.Range.Start)); attrs = attrs.SmartWhere(a => !(a.Value is string)); // remove docstring, if any var extraArgs = LNode.List(); if (macroName.IsId) { extraArgs.Add(F.Literal(macroName.Name.Name)); } else { Debug.Assert((modes & (MacroMode.MatchEveryCall | MacroMode.MatchEveryIdentifier | MacroMode.MatchEveryLiteral)) != 0); } // ensure operator macros like `'+` are not printed as `operator+` which C# will reject if (EcsValidators.IsOperator(macroName.Name)) { macroName = F.Id(EcsValidators.SanitizeIdentifier(macroName.Name.Name)); } LNode modesExpr = null; foreach (LNode mode in modeNodes) { modesExpr = LNode.MergeBinary(modesExpr, LNode.Call(CodeSymbols.Dot, LNode.List(LNode.Call(CodeSymbols.Dot, LNode.List(LNode.Call(CodeSymbols.ColonColon, LNode.List(LNode.Id((Symbol)"global"), LNode.Id((Symbol)"LeMP"))).SetStyle(NodeStyle.Operator), LNode.Id((Symbol)"MacroMode"))).SetStyle(NodeStyle.Operator), mode)).SetStyle(NodeStyle.Operator), S.OrBits); } if (modesExpr != null) { extraArgs.Add(LNode.Call(CodeSymbols.Assign, LNode.List(LNode.Id((Symbol)"Mode"), modesExpr)).SetStyle(NodeStyle.Operator)); } LNode lmAttribute = LNode.Call(LNode.Call(CodeSymbols.Dot, LNode.List(LNode.Call(CodeSymbols.ColonColon, LNode.List(LNode.Id((Symbol)"global"), LNode.Id((Symbol)"LeMP"))).SetStyle(NodeStyle.Operator), LNode.Id((Symbol)"LexicalMacroAttribute"))).SetStyle(NodeStyle.Operator), LNode.List().Add(syntax).Add(description).AddRange(extraArgs)); if (!body.Calls(S.Braces)) { body = LNode.Call(CodeSymbols.Braces, LNode.List(LNode.Call(CodeSymbols.Return, LNode.List(body)))).SetStyle(NodeStyle.StatementBlock); } body = context.PreProcess(body); // Look for "using" statements above the macro() call LNodeList usingDirectives = LNode.List(context.PreviousSiblings.Where(n => n.Calls(S.Import))); // Look for "using" and "#r" statements at the beginning of the body if (body.Calls(S.Braces)) { var bodyUsings = body.Args.TakeNowWhile(stmt => stmt.Calls(S.Import) || stmt.Calls(S.CsiReference)); usingDirectives.AddRange(bodyUsings); body = body.WithArgs(body.Args.Slice(bodyUsings.Count)); } // Create a matchCode statement unless the pattern is MacroName($(.._)), which always matches if (!(pattern_apos.HasSimpleHeadWithoutPAttrs() && pattern_apos.Target.IsId && pattern_apos.ArgCount == 1 && pattern_apos[0].Equals(LNode.Call(CodeSymbols.Substitute, LNode.List(LNode.Call(CodeSymbols.DotDot, LNode.List(LNode.Id((Symbol)"_"))).SetStyle(NodeStyle.Operator))).SetStyle(NodeStyle.Operator)))) { // Note: the body is already preprocessed; #noLexicalMacros prevents double-processing body = LNode.Call(CodeSymbols.Braces, LNode.List(LNode.Call((Symbol)"matchCode", LNode.List(LNode.Id((Symbol)"#node"), LNode.Call(CodeSymbols.Braces, LNode.List(LNode.Call(CodeSymbols.Case, LNode.List(pattern)), LNode.Call((Symbol)"#noLexicalMacros", LNode.List(body.AsList(S.Braces))))).SetStyle(NodeStyle.StatementBlock))).SetStyle(NodeStyle.Special), LNode.Call(CodeSymbols.Return, LNode.List(LNode.Literal(null))))).SetStyle(NodeStyle.StatementBlock); } return(LNode.Call((Symbol)"compileTime", LNode.List(LNode.Call(CodeSymbols.Braces, LNode.List().AddRange(usingDirectives).Add(LNode.Call(LNode.List().Add(lmAttribute).AddRange(attrs).Add(LNode.Id(CodeSymbols.Public)).Add(LNode.Id(CodeSymbols.Static)), CodeSymbols.Fn, LNode.List(LNode.Id((Symbol)"LNode"), macroName, LNode.Call(CodeSymbols.AltList, LNode.List(LNode.Call(CodeSymbols.Var, LNode.List(LNode.Id((Symbol)"LNode"), LNode.Id((Symbol)"#node"))), LNode.Call(CodeSymbols.Var, LNode.List(LNode.Call(CodeSymbols.Dot, LNode.List(LNode.Call(CodeSymbols.ColonColon, LNode.List(LNode.Id((Symbol)"global"), LNode.Id((Symbol)"LeMP"))).SetStyle(NodeStyle.Operator), LNode.Id((Symbol)"IMacroContext"))).SetStyle(NodeStyle.Operator), LNode.Id((Symbol)"#context"))))), body)))).SetStyle(NodeStyle.StatementBlock))).SetStyle(NodeStyle.Special)); }
// Bubbles up a call. The returned pair consists of // 1. A sequence of statements to run before the call // 2. The call with all (outer) #runSequences removed Pair <VList <LNode>, LNode> BubbleUp_GeneralCall2(LNode expr) { var target = expr.Target; var args = expr.Args; var combinedSequence = LNode.List(); // Bubbe up target target = BubbleUpBlocks(target); if (target.CallsMin(__numrunSequence, 1)) { combinedSequence = target.Args.WithoutLast(1); expr = expr.WithTarget(target.Args.Last); } // Bubble up each argument var isAssignment = EcsValidators.IsAssignmentOperator(expr.Name); if (isAssignment) { LNode lhs = BubbleUpBlocks(expr.Args[0]); LNode rhs = BubbleUpBlocks(expr.Args[1]); args = LNode.List(lhs, rhs); } else // most common case { args = args.SmartSelect(arg => BubbleUpBlocks(arg)); } int lastRunSeq = args.LastIndexWhere(a => a.CallsMin(__numrunSequence, 1)); if (lastRunSeq >= 0) { // last index of #runSequence that is not marked pure int lastRunSeqImpure = args.First(lastRunSeq + 1).LastIndexWhere(a => a.CallsMin(__numrunSequence, 1) && a.AttrNamed(_trivia_pure.Name) == null); if (lastRunSeq > 0 && (args.Count == 2 && (target.IsIdNamed(S.And) || target.IsIdNamed(S.Or)) || args.Count == 3 && target.IsIdNamed(S.QuestionMark))) { Context.Sink.Error(target, "#useSequenceExpressions is not designed to support sequences or variable declarations on the right-hand side of the `&&`, `||` or `?` operators. The generated code will be incorrect."); } var argsW = args.ToList(); for (int i = 0; i <= lastRunSeq; i++) { LNode arg = argsW[i]; if (!arg.IsLiteral) { if (arg.CallsMin(__numrunSequence, 1)) { combinedSequence.AddRange(arg.Args.WithoutLast(1)); argsW[i] = arg = arg.Args.Last; } if (i < lastRunSeqImpure) { if (i == 0 && (expr.CallsMin(S.IndexBracks, 1) || expr.CallsMin(S.NullIndexBracks, 1))) { // Consider foo[#runSequence(f(), i)]. In case this appears in // an lvalue context and `foo` is a struct, we cannot store `foo` in // a temporary, as this may silently change the code's behavior. // Better to take the risk of evaluating `foo` after `f()`. } else { if (isAssignment || arg.Attrs.Any(a => a.IsIdNamed(S.Ref) || a.IsIdNamed(S.Out))) { argsW[i] = MaybeCreateTemporaryForLValue(arg, ref combinedSequence); } else { // Create a temporary variable to hold this argument LNode tmpVarName, tmpVarDecl = TempVarDecl(Context, arg, out tmpVarName); combinedSequence.Add(tmpVarDecl); argsW[i] = tmpVarName.PlusAttr(_trivia_isTmpVar); } } } } } expr = expr.WithArgs(LNode.List(argsW)); } return(Pair.Create(combinedSequence, expr)); }
private static bool DetectSetOrCreateMember(LNode arg, out Symbol relevantAttribute, out Symbol fieldName, out Symbol paramName, out LNode plainArg, out LNode propOrFieldDecl) { relevantAttribute = null; fieldName = null; paramName = null; plainArg = null; propOrFieldDecl = null; LNode _, type, name, defaultValue, propArgs; if (EcsValidators.IsPropertyDefinition(arg, out type, out name, out propArgs, out _, out defaultValue) && propArgs.ArgCount == 0) { // #property(Type, Name<T>, {...}) relevantAttribute = S.Property; fieldName = EcsNodePrinter.KeyNameComponentOf(name); paramName = ChooseArgName(fieldName); if (defaultValue != null) // initializer is Args[4] { plainArg = F.Var(type, paramName, defaultValue); propOrFieldDecl = arg.WithArgs(arg.Args.First(4)); } else { plainArg = F.Var(type, paramName); propOrFieldDecl = arg; } return(true); } else if (IsVar(arg, out type, out paramName, out defaultValue)) { int a_i = 0; foreach (var attr in arg.Attrs) { if (attr.IsId) { var a = attr.Name; if (a == _set || a == S.Public || a == S.Internal || a == S.Protected || a == S.Private || a == S.ProtectedIn || a == S.Static || a == S.Partial) { relevantAttribute = a; fieldName = paramName; paramName = ChooseArgName(fieldName); if (a == _set) { plainArg = F.Var(type, paramName, defaultValue).WithAttrs(arg.Attrs.Without(attr)); } else { // in case of something like "[A] public params T arg = value", // assume that "= value" represents a default value, not a field // initializer, that [A] belongs on the field, except `params` // which stays on the argument. plainArg = F.Var(type, paramName, defaultValue); propOrFieldDecl = arg; if (arg.Args[1].Calls(S.Assign, 2)) { propOrFieldDecl = arg.WithArgChanged(1, arg.Args[1].Args[0]); } int i_params = arg.Attrs.IndexWithName(S.Params); if (i_params > -1) { plainArg = plainArg.PlusAttr(arg.Attrs[i_params]); propOrFieldDecl = propOrFieldDecl.WithAttrs(propOrFieldDecl.Attrs.RemoveAt(i_params)); } } break; } } a_i++; } return(plainArg != null); } return(false); }
public static LNodeList UseSymbolsCore(LNodeList symbolAttrs, LNodeList options, LNodeList body, IMacroContext context, bool inType) { // Decode options (TODO: invent a simpler approach) string prefix = "sy_"; var inherited = new HashSet <Symbol>(); foreach (var pair in MacroContext.GetOptions(options)) { if (pair.Key.Name.Name == "prefix" && pair.Value.IsId) { prefix = pair.Value.Name.Name; } else if (pair.Key.Name.Name == "inherit" && pair.Value.Value is Symbol) { inherited.Add((Symbol)pair.Value.Value); } else if (pair.Key.Name.Name == "inherit" && (pair.Value.Calls(S.Braces) || pair.Value.Calls(S.Tuple)) && pair.Value.Args.All(n => n.Value is Symbol)) { foreach (var arg in pair.Value.Args) { inherited.Add((Symbol)arg.Value); } } else { context.Sink.Warning(pair.Key, "Unrecognized parameter. Expected prefix:id or inherit:{@@A; @@B; ...})"); } } // Replace all symbols while collecting a list of them var symbols = new Dictionary <Symbol, LNode>(); LNodeList output = body.SmartSelect(stmt => stmt.ReplaceRecursive(n => { if (!inType && n.ArgCount == 3) { // Since we're outside any type, we must avoid creating symbol // fields. When we cross into a type then we can start making // Symbols by calling ourself recursively with inType=true var kind = EcsValidators.SpaceDefinitionKind(n); if (kind == S.Class || kind == S.Struct || kind == S.Interface || kind == S.Alias || kind == S.Trait) { var body2 = n.Args[2]; return(n.WithArgChanged(2, body2.WithArgs(UseSymbolsCore(symbolAttrs, options, body2.Args, context, true)))); } } var sym = n.Value as Symbol; if (n.IsLiteral && sym != null) { return(symbols[sym] = LNode.Id(prefix + sym.Name)); } return(null); }) ); // Return updated code with variable declaration at the top for all non-inherit symbols used. var _Symbol = F.Id("Symbol"); var vars = (from sym in symbols where !inherited.Contains(sym.Key) select F.Call(S.Assign, sym.Value, F.Call(S.Cast, F.Literal(sym.Key.Name), _Symbol))).ToList(); if (vars.Count > 0) { output.Insert(0, F.Call(S.Var, ListExt.Single(_Symbol).Concat(vars)) .WithAttrs(symbolAttrs.Add(F.Id(S.Static)).Add(F.Id(S.Readonly)))); } return(output); }
private static bool DetectSetOrCreateMember(LNode arg, out Symbol relevantAttribute, out Symbol fieldName, out Symbol paramName, out LNode newArg, out LNode propOrFieldDecl) { relevantAttribute = null; fieldName = null; paramName = null; newArg = null; propOrFieldDecl = null; LNode _, type, name, defaultValue, propArgs; if (EcsValidators.IsPropertyDefinition(arg, out type, out name, out propArgs, out _, out defaultValue) && propArgs.ArgCount == 0) { // #property(Type, Name<T>, {...}) relevantAttribute = S.Property; fieldName = EcsNodePrinter.KeyNameComponentOf(name); paramName = ChooseArgName(fieldName); if (defaultValue != null) // initializer is Args[4] { newArg = LNode.Call(S.Var, LNode.List(type, F.Assign(paramName, defaultValue)), arg); propOrFieldDecl = arg.WithArgs(arg.Args.Initial(4)); } else { newArg = LNode.Call(S.Var, LNode.List(type, F.Id(paramName)), arg); propOrFieldDecl = arg; } DSOCM_DistributeAttributes(arg.Attrs, ref newArg, ref propOrFieldDecl); return(true); } else if (IsVar(arg, out type, out paramName, out defaultValue)) { int a_i = 0; foreach (var attr in arg.Attrs) { if (attr.IsId) { var a = attr.Name; if (a == _set || FieldCreationAttributes.Contains(a)) { relevantAttribute = a; fieldName = paramName; paramName = ChooseArgName(fieldName); if (a == _set) { newArg = F.Var(type, paramName, defaultValue).WithAttrs(arg.Attrs.Without(attr)); } else { // in case of something like "[A] public params T arg = value", // assume that "= value" represents a default value, not a field // initializer. Most attributes stay on the argument. newArg = arg.WithArgChanged(1, defaultValue != null ? F.Assign(paramName, defaultValue) : F.Id(paramName)); propOrFieldDecl = LNode.Call(S.Var, LNode.List(type, F.Id(fieldName)), arg); DSOCM_DistributeAttributes(arg.Attrs, ref newArg, ref propOrFieldDecl); } break; } } a_i++; } return(newArg != null); } return(false); }
Pred NodeToPredCore(LNode expr, Context ctx = Context.Rule) { if (expr.IsCall) { bool slash = false, not; var name = expr.Name; if (name == S.Tuple) { // sequence: (a, b, c) if (expr.Calls(S.Tuple, 1)) { return(NodeToPred(expr.Args[0], ctx)); } return(TranslateToSeq(expr, ctx)); } else if (name == S.Braces) { // User action {block} if (ctx == Context.And || ctx == Context.GateLeft) { _sink.Error(expr, ctx == Context.And ? "Cannot use an action block inside an '&' or '&!' predicate; these predicates are for prediction only." : "Cannot use an action block on the left side of a '=>' gate; the left side is for prediction only."); } return(new ActionPred(expr, expr.Args)); } else if (expr.Calls(S.OrBits, 2) || (slash = expr.Calls(S.Div, 2))) { // alternatives: a | b, a || b, a / b LNode lhs = expr.Args[0], rhs = expr.Args[1]; BranchMode lhsMode, rhsMode; Pred left = BranchToPred(lhs, out lhsMode, ctx); Pred right = BranchToPred(rhs, out rhsMode, ctx); return(Pred.Or(left, right, slash, expr, lhsMode, rhsMode, _sink)); } else if (expr.Calls(_Star, 1) || expr.Calls(_Plus, 1) || expr.Calls(_Opt, 1)) { // loop (a+, a*) or optional (a?) return(TranslateLoopExpr(expr, ctx)); } else if (expr.Calls(_Gate, 1) || expr.Calls(_EqGate, 1)) { // => foo (LES-based parser artifact) return(new Gate(expr, new Seq(F.Missing), NodeToPred(expr.Args[0], Context.GateRight)) { IsEquivalency = expr.Calls(_EqGate) }); } else if (expr.Calls(_Gate, 2) || expr.Calls(_EqGate, 2)) { if (ctx == Context.GateLeft) { _sink.Error(expr, "Cannot use a gate in the left-hand side of another gate"); } return(new Gate(expr, NodeToPred(expr.Args[0], Context.GateLeft), NodeToPred(expr.Args[1], Context.GateRight)) { IsEquivalency = expr.Calls(_EqGate) }); } else if ((not = expr.Calls(_AndNot, 1)) || expr.Calls(_And, 1)) { return(TranslateAndPred(expr, not)); } else if (expr.Calls(S.NotBits, 1)) { var subpred = NodeToPred(expr.Args[0], ctx); if (subpred is TerminalPred) { var term = (TerminalPred)subpred; term.Set = term.Set.Inverted().WithoutEOF(); return(term); } else { _sink.Error(expr, "The set-inversion operator ~ can only be applied to a single terminal, not a '{0}'", subpred.GetType().Name); return(subpred); } } else if ((name.Name.EndsWith(":") || name.Name.EndsWith("=")) && expr.ArgCount == 2) { return(TranslateLabeledExpr(expr, ctx)); } else if (expr.Calls(_Any, 2) && expr.Args[0].IsId) { return(Translate_any_in_Expr(expr, ctx)); } } // expr is an Id, literal, or non-special call Rule rule = TryGetRule(expr); if (rule != null) { LNode _, args; if (EcsValidators.MethodDefinitionKind(rule.Basis, out _, out _, out args, out _, false) == S.Fn) { if (expr.ArgCount > args.ArgCount) // don't complain about too few args, in case there are default args (I'm too lazy to check) { _sink.Error(expr, "Rule '{0}' takes {1} arguments ({2} given)", rule.Name, args.ArgCount, expr.ArgCount); } } return(new RuleRef(expr, rule) { Params = expr.Args }); } string errorMsg = null; Pred terminal = _helper.CodeToTerminalPred(expr, ref errorMsg); if (terminal == null) { errorMsg = errorMsg ?? "LLLPG: unrecognized expression"; terminal = new TerminalPred(expr, _helper.EmptySet); _sink.Error(expr, errorMsg); } else if (errorMsg != null) { _sink.Warning(expr, errorMsg); } return(terminal); }
/// <summary>Retrieves the "key" name component for the nameof(...) macro.</summary> /// <remarks> /// The key name component of <c>global::Foo!int.Bar!T(x)</c> (in C# notation /// global::Foo<int>.Bar<T>(x)) is <c>Bar</c>. This example tree has the /// structure <c>((((global::Foo)!int).Bar)!T)(x)</c>). /// </remarks> public static Symbol KeyNameComponentOf(LNode name) { return(EcsValidators.KeyNameComponentOf(name)); }