예제 #1
0
        static void Main(string[] args)
        {
            var fileCfg = ParseCommandLine(args, out string cfgFilename);

            var stackCfg = new StackCfg();

            if (cfgFilename != null)
            {
                try
                {
                    stackCfg = StackCfg.LoadJson(cfgFilename);
                }
                catch (Exception e)
                {
                    var defaultCfgFile = "whomst-default.json";
                    var excMessage     = $"Could not load config file {cfgFilename}.";
                    var helpfulMessage = excMessage
                                         + $"Generating {defaultCfgFile}. Use it with /cfg={defaultCfgFile})";

                    Console.WriteLine(helpfulMessage);
                    File.WriteAllText(
                        defaultCfgFile,
                        JsonConvert.SerializeObject(StackCfg.Default(), new JsonSerializerSettings
                    {
                        Formatting        = Formatting.Indented,
                        NullValueHandling = NullValueHandling.Ignore,
                    }));
                    throw new AggregateException(excMessage, e);
                }
            }

            // NOTE cfg.PrepareForUse unnecessary, will get called in WhomstJob ctor
            WhomstGlobalJob.Execute(fileCfg, stackCfg);
        }
예제 #2
0
        public void RunJob(FileCfg fileCfg, StackCfg stackCfg)
        {
            var job = new WhomstJob
            {
                SharedStackCfg = stackCfg,
                SharedFileCfg  = fileCfg,
            };

            {
                job.StackCfg = StackCfg.Default().MergeOverWith(job.SharedStackCfg);
                job.FileCfg  = job.StackCfg.GetProtoFileCfg(job.SharedFileCfg.InputFileExt)
                               .MergeOverWith(job.SharedFileCfg);
                job.FileCfg.Validate();
                jobStack.Push(job);
                jobWriteList.Add(job);

                if (ScriptState == null)
                {
                    ScriptState = CSharpScript.RunAsync(
                        "",
                        ScriptOptions.Default
                        .WithMetadataResolver(ScriptMetadataResolver.Default
                                              .WithBaseDirectory(Environment.CurrentDirectory)
                                              .WithSearchPaths(RuntimeEnvironment.GetRuntimeDirectory())
                                              .WithSearchPaths(job.StackCfg.Includes)
                                              ).WithEmitDebugInformation(true),
                        this, typeof(IWhomstGlobals)).Result;
                }
                else
                {
                    foreach (var incl in job.StackCfg.Includes.Where(i => Includes.Add(i)))
                    {
                        Util.AssertWarning(false,
                                           $"Could not add include directory {incl} midway through Whomst execution");
                    }
                }

                StringBuilder setupCode = new StringBuilder();

                foreach (var assm in job.StackCfg.Assemblies.Where(a => Assemblies.Add(a)))
                {
                    setupCode.AppendLine($"#r\"{assm}\"");
                }

                foreach (var using1 in job.StackCfg.Usings.Where(u => Usings.Add(u)))
                {
                    setupCode.AppendLine($"using {using1};");
                }

                Util.AssertWarning(
                    InputFiles.Add(job.FileCfg.InputFilePath),
                    $"{job.FileCfg.InputFilePath} is getting preprocessed twice.");

                Util.AssertWarning(
                    OutputFiles.Add(job.FileCfg.OutputFilePath),
                    $"{job.FileCfg.OutputFilePath} is getting written twice.");

                ScriptState = ScriptState.ContinueWithAsync(setupCode.ToString()).Result;
            }

            var fmt = job.FileCfg.FormatCfg;

            {
                var text    = File.ReadAllText(job.FileCfg.InputFilePath, fmt.Encoding);
                var linesIn = text.EzSplit(fmt.NewL);

                void AddLineGroup(int s, int e, LineGroupType t) =>
                job.LineGroups.Add(new LineGroup {
                    InclStart = s, ExclEnd = e, Type = t
                });

                AddLineGroup(0, -1, LineGroupType.Content);

                for (int index = 0; index < linesIn.Length; index++)
                {
                    var line = linesIn[index];

                    void Assert(bool b, string s) => Util.Assert(
                        b, s,
                        job.FileCfg.InputFileName, index + 1);

                    switch (job.LineGroups.Last().Type)
                    {
                    case LineGroupType.Content:
                        var startTokenInd = line.IndexOf(fmt.StartCodeToken);
                        if (0 <= startTokenInd)
                        {
                            job.LineGroups.Last().ExclEnd = index;
                            var endTokenInd = line.IndexOf(fmt.StartOutputToken);
                            if (0 <= endTokenInd)
                            {
                                Assert(startTokenInd < endTokenInd,
                                       $"{fmt.StartOutputToken} came before {fmt.StartCodeToken}");
                                AddLineGroup(index, index + 1, LineGroupType.TwoLiner);
                                AddLineGroup(index + 1, -1, LineGroupType.GenOutput);
                            }
                            else
                            {
                                AddLineGroup(index, index + 1, LineGroupType.StartCode);
                                AddLineGroup(index + 1, -1, LineGroupType.WhomstCode);
                            }
                        }

                        var startOneLinerInd = line.IndexOf(fmt.OneLinerStartCodeToken);

                        if (0 <= startOneLinerInd && (startTokenInd < 0))
                        {
                            var midOneLinerInd = line.IndexOf(fmt.OneLinerStartOutputToken);
                            var endOneLinerInd = line.IndexOf(fmt.OneLinerEndOutputToken);

                            Assert(
                                startOneLinerInd < midOneLinerInd,
                                $"{fmt.OneLinerStartCodeToken} must be followed with {fmt.OneLinerStartOutputToken}");
                            Assert(
                                midOneLinerInd < endOneLinerInd,
                                $"{fmt.OneLinerStartOutputToken} must be followed with {fmt.OneLinerEndOutputToken}");

                            job.LineGroups.Last().ExclEnd = index;
                            AddLineGroup(index, index + 1, LineGroupType.OneLiner);
                            AddLineGroup(index + 1, -1, LineGroupType.Content);
                        }
                        break;

                    case LineGroupType.WhomstCode:
                        if (line.Contains(fmt.StartOutputToken))
                        {
                            job.LineGroups.Last().ExclEnd = index;
                            AddLineGroup(index, index + 1, LineGroupType.StartOutput);
                            AddLineGroup(index + 1, -1, LineGroupType.GenOutput);
                        }
                        break;

                    case LineGroupType.GenOutput:
                        if (line.Contains(fmt.EndOutputToken))
                        {
                            job.LineGroups.Last().ExclEnd = index;
                            AddLineGroup(index, index + 1, LineGroupType.EndOutput);
                            AddLineGroup(index + 1, -1, LineGroupType.Content);
                        }
                        break;

                    default:
                        throw new ArgumentOutOfRangeException();
                    }
                }

                job.LineGroups.Last().ExclEnd = linesIn.Length;
                var endingType = job.LineGroups.Last().Type;

                Util.Assert(
                    endingType == LineGroupType.Content || endingType == LineGroupType.OneLiner,
                    $"Document ends with unclosed {endingType} (missing \"{fmt.EndOutputToken}\"??)",
                    job.FileCfg.InputFileName, job.LineGroups.Last().InclStart + 1);

                List <LineGroup> remove = new List <LineGroup>();

                foreach (var lg in job.LineGroups)
                {
                    lg.InLines = linesIn.Slice(lg.InclStart, lg.ExclEnd);
                    //if (lg.InLines.Count == 0) { remove.Add(lg); }
                }

                foreach (var rem in remove)
                {
                    job.LineGroups.Remove(rem);
                }
            }

            {
                string indent  = null;
                string oldHash = null;

                foreach (var lineGroup in job.LineGroups)
                {
                    string firstLine = lineGroup.InLines.FirstOrDefault();

                    switch (lineGroup.Type)
                    {
                    case LineGroupType.Content:
                    case LineGroupType.OneLiner:
                    {
                        lineGroup.Indent            = "";
                        lineGroup.DeIndentedInLines = lineGroup.InLines;
                    } break;

                    case LineGroupType.StartCode:
                    {
                        var tokenInd = firstLine.IndexOf(fmt.StartCodeToken);
                        Util.Assert(
                            tokenInd + fmt.StartCodeToken.Length == firstLine.Length,
                            $"Line cannot contain anything after {fmt.StartCodeToken}",
                            job.FileCfg.InputFileName, lineGroup.StartingLineNumber);
                        indent = firstLine.Substring(0, tokenInd);
                    } break;

                    case LineGroupType.TwoLiner:
                    {
                        var tokenInd = firstLine.IndexOf(fmt.StartCodeToken);
                        indent = firstLine.Substring(0, tokenInd);
                    } break;

                    case LineGroupType.WhomstCode: {
                    } break;

                    case LineGroupType.StartOutput: {
                        Util.Assert(
                            firstLine == $"{indent}{fmt.StartOutputToken}",
                            "Line with {fmt.StartOutputToken} should contain it, indent, and nothign else",
                            job.FileCfg.InputFileName, lineGroup.StartingLineNumber);
                    } break;

                    case LineGroupType.GenOutput: {
                        lineGroup.Indent = Regex.Replace(indent, @"[^\s]", " ");
                    } break;

                    case LineGroupType.EndOutput: {
                        Util.Assert(
                            firstLine.StartsWith($"{indent}{fmt.EndOutputToken}"),
                            "Indent mismatch",
                            job.FileCfg.InputFileName, lineGroup.StartingLineNumber);
                        string restOfLine = firstLine.Substring(indent.Length + fmt.EndOutputToken.Length).Trim();

                        Util.Assert(!restOfLine.StartsWith(fmt.HashInfix) || restOfLine.Contains(oldHash),
                                    "Code preceding hash does not match hash, which means that generated code has been edited.\n" +
                                    "Undo changes or remove Hash",
                                    job.FileCfg.InputFileName, lineGroup.StartingLineNumber);
                    } break;

                    default:
                        throw new ArgumentOutOfRangeException();
                    }

                    lineGroup.Indent            = lineGroup.Indent ?? indent;
                    lineGroup.DeIndentedInLines = lineGroup.DeIndentedInLines
                                                  ?? lineGroup.InLines.Select((l, ind) =>
                    {
                        Util.Assert(l.StartsWith(lineGroup.Indent), "Line not indented",
                                    job.FileCfg.InputFileName, lineGroup.StartingLineNumber + ind);
                        return(l.Substring(lineGroup.Indent.Length));
                    }).ToList();

                    if (lineGroup.Type == LineGroupType.GenOutput)
                    {
                        oldHash = Util.CalculateMD5Hash(lineGroup.DeIndentedInLines.AggLines(fmt.NewL));
                    }
                }
            }

            {
                string RunAndRtrn(string code, string file, int lineNumber)
                {
                    ScriptState = Eval(code, file, lineNumber);
                    var returnValue            = ScriptState.ReturnValue;
                    var returnValueScriptState = returnValue as OneTimeScriptState;

                    // WhomstLoad or WhomstEval
                    if (returnValueScriptState != null)
                    {
                        ScriptState = returnValueScriptState.ScriptState;
                    }
                    else if (returnValue != null)
                    {
                        return(returnValue.ToString());
                    }

                    return(null);
                }

                int prevCodeStartLine = -1;

                foreach (var lineGroup in job.LineGroups)
                {
                    switch (lineGroup.Type)
                    {
                    case LineGroupType.Content: {
                        job.PrevContentLines = lineGroup.InLines;
                    } break;

                    case LineGroupType.OneLiner:
                    {
                        var firstLine     = lineGroup.InLines[0];
                        var startInd      = firstLine.IndexOf(fmt.OneLinerStartCodeToken);
                        var midInd        = firstLine.IndexOf(fmt.OneLinerStartOutputToken);
                        var endInd        = firstLine.IndexOf(fmt.OneLinerEndOutputToken);
                        var startIndRight = startInd + fmt.OneLinerStartCodeToken.Length;
                        var midIndRight   = midInd + fmt.OneLinerStartOutputToken.Length;
                        var endIndRight   = endInd + fmt.OneLinerEndOutputToken.Length;
                        var code          = firstLine.Substring(startIndRight, midInd - startIndRight);
                        var oldOutput     = firstLine.Substring(midIndRight, endInd - midIndRight);
                        var output        = RunAndRtrn(code, job.FileCfg.InputFileName, lineGroup.StartingLineNumber)
                                            ?.Replace(fmt.NewL, ", ");
                        // TODO this is shameful
                        lineGroup.DeIndentedOutLines = new[]
                        {
                            firstLine.Substring(0, startInd),
                            code,
                            output,
                            firstLine.Substring(endIndRight, firstLine.Length - endIndRight),
                        };
                        // TODO this is shameful
                        lineGroup.DeIndentedInLines = new[]
                        {
                            firstLine.Substring(0, startInd),
                            code,
                            oldOutput ?? "",
                            firstLine.Substring(endIndRight, firstLine.Length - endIndRight),
                        };
                    } break;

                    case LineGroupType.StartCode: {
                        lineGroup.DeIndentedOutLines = fmt.StartCodeToken.Array1();
                    } break;

                    case LineGroupType.TwoLiner:
                    {
                        var firstLine     = lineGroup.InLines[0];
                        var startInd      = firstLine.IndexOf(fmt.StartCodeToken);
                        var midInd        = firstLine.IndexOf(fmt.StartOutputToken);
                        var startIndRight = startInd + fmt.StartCodeToken.Length;
                        var code          = firstLine.Substring(startIndRight, midInd - startIndRight);
                        lineGroup.DeIndentedOutLines = code.Array1();
                        job.PrevCodeLines            = lineGroup.DeIndentedOutLines;
                        prevCodeStartLine            = lineGroup.StartingLineNumber;
                    } break;

                    case LineGroupType.WhomstCode: {
                        job.PrevCodeLines = lineGroup.DeIndentedInLines;
                        prevCodeStartLine = lineGroup.StartingLineNumber;
                    } break;

                    case LineGroupType.StartOutput:
                        break;

                    case LineGroupType.GenOutput: {
                        // TODO past output
                        job.WhomstOut = new StringWriter();
                        var codeSb     = new StringBuilder();
                        int lineNumber = prevCodeStartLine;

                        foreach (var ind in Enumerable.Range(0, job.PrevCodeLines.Count))
                        {
                            var lineCpy = job.PrevCodeLines[ind];

                            if (lineCpy.Contains(fmt.ForceDirective))
                            {
                                lineCpy = lineCpy.Replace(fmt.ForceDirective, "");
                            }
                            else if (job.FileCfg.ShallSkipCompute)
                            {
                                continue;
                            }

                            if (lineCpy.Contains(fmt.YieldDirective) || ind == job.PrevCodeLines.Count - 1)
                            {
                                codeSb.AppendLine(lineCpy.Replace(fmt.YieldDirective, ""));
                                var output = RunAndRtrn(codeSb.ToString(), fileCfg.InputFileName, lineNumber);
                                if (output != null)
                                {
                                    job.WhomstOut.WriteLine(output);
                                }
                                codeSb.Clear();
                                lineNumber = prevCodeStartLine + ind + 1;
                            }
                            else
                            {
                                codeSb.AppendLine(lineCpy);
                            }
                        }

                        var outputUnindented = job.WhomstOut.GetStringBuilder().ToString();

                        if (outputUnindented.EndsWith(Environment.NewLine))
                        {
                            outputUnindented =
                                outputUnindented.Substring(0, outputUnindented.Length - Environment.NewLine.Length);
                        }

                        job.WhomstOut.Dispose();
                        job.WhomstOut = null;
                        lineGroup.DeIndentedOutLines = outputUnindented.EzSplit(fmt.NewL).ToList();
                        job.PrevOutputLines          = lineGroup.DeIndentedOutLines;
                    } break;

                    case LineGroupType.EndOutput:
                    {
                        var hash = Util.CalculateMD5Hash(job.PrevOutputLines.AggLines(fmt.NewL));
                        lineGroup.DeIndentedOutLines = $"{fmt.EndOutputToken}{fmt.HashInfix} {hash}".Array1();
                    } break;

                    default:
                        throw new ArgumentOutOfRangeException();
                    }
                    lineGroup.DeIndentedOutLines = lineGroup.DeIndentedOutLines ?? lineGroup.DeIndentedInLines;
                    OneTimeScriptState.CheckInstancesDisposed();
                }
            }

            // Invalidating stuff which I know I don't want to be used again
            job.WhomstOut        = null;
            job.PrevCodeLines    = null;
            job.PrevContentLines = null;
            job.PrevOutputLines  = null;
            jobStack.Pop();
        }