//: Phase 1 of running mu code: load it from a textual representation.

:(scenarios load)  // use 'load' instead of 'run' in all scenarios in this layer
:(scenario first_recipe)
recipe main [
  1:number <- copy 23
]
+parse: instruction: copy
+parse:   ingredient: {"23": "literal"}
+parse:   product: {"1": "number"}

:(code)
vector<recipe_ordinal> load(string form) {
  istringstream in(form);
  in >> std::noskipws;
  return load(in);
}

vector<recipe_ordinal> load(istream& in) {
  in >> std::noskipws;
  vector<recipe_ordinal> result;
  while (!in.eof()) {
    skip_whitespace_and_comments(in);
    if (in.eof()) break;
    string command = next_word(in);
    // Command Handlers
    if (command == "recipe") {
      result.push_back(slurp_recipe(in));
    }
    else if (command == "recipe!") {
      Disable_redefine_warnings = true;
      result.push_back(slurp_recipe(in));
      Disable_redefine_warnings = false;
    }
    // End Command Handlers
    else {
      raise_error << "unknown top-level command: " << command << '\n' << end();
    }
  }
  return result;
}

long long int slurp_recipe(istream& in) {
  recipe result;
  result.name = next_word(in);
  skip_whitespace_and_comments(in);
  // End recipe Refinements
  if (result.name.empty())
    raise_error << "empty result.name\n" << end();
  trace(9991, "parse") << "--- defining " << result.name << end();
  if (!contains_key(Recipe_ordinal, result.name))pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
#
#
#            Nim's Runtime Library
#        (c) Copyright 2020 Andreas Rumpf
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

## This module allows adding hooks to program exit.

import locks
when defined(js) and not defined(nodejs):
  import std/assertions

type
  FunKind = enum kClosure, kNoconv # extend as needed
  Fun = object
    case kind: FunKind
    of kClosure: fun1: proc () {.closure.}
    of kNoconv: fun2: proc () {.noconv.}

var
  gFunsLock: Lock
  gFuns {.cursor.}: seq[Fun] #Intentionally use the cursor to break up the lifetime trace and make it compatible with JS.

initLock(gFunsLock)

when defined(js):
  proc addAtExit(quitProc: proc() {.noconv.}) =
    when defined(nodejs):
      asm """
        process.on('exit', `quitProc`);
      """
    elif defined(js):
      asm """
        window.onbeforeunload = `quitProc`;
      """
else:
  proc addAtExit(quitProc: proc() {.noconv.}) {.
    importc: "atexit", header: "<stdlib.h>".}

proc callClosures() {.noconv.} =
  withLock gFunsLock:
    for i in countdown(gFuns.len-1, 0):
      let fun = gFuns[i]
      case fun.kind
      of kClosure: fun.fun1()
      of kNoconv: fun.fun2()
    gFuns.setLen(0)

template fun() =
  if gFuns.len == 0:
    addAtExit(callClosures)

proc addExitProc*(cl: proc () {.closure.}) =
  ## Adds/registers a quit procedure. Each call to `addExitProc` registers
  ## another quit procedure. They are executed on a last-in, first-out basis.
  # Support for `addExitProc` is done by Ansi C's facilities here.
  # In case of an unhandled exception the exit handlers should
  # not be called explicitly! The user may decide to do this manually though.
  withLock gFunsLock:
    fun()
    gFuns.add Fun(kind: kClosure, fun1: cl)

proc addExitProc*(cl: proc() {.noconv.}) =
  ## overload for `noconv` procs.
  withLock gFunsLock:
    fun()
    gFuns.add Fun(kind: kNoconv, fun2: cl)

when not defined(nimscript) and (not defined(js) or defined(nodejs)):
  proc getProgramResult*(): int =
    when defined(js) and defined(nodejs):
      asm """
`result` = process.exitCode;
"""
    else:
      result = programResult

  proc setProgramResult*(a: int) =
    when defined(js) and defined(nodejs):
      asm """
process.exitCode = `a`;
"""
    else:
      programResult = a
span class="Delimiter">()) || Ignore.find(in.peek()) != string::npos) { in.get(); } } void skip_whitespace_and_comments(istream& in) { while (true) { if (in.eof()) break; if (isspace(in.peek())) in.get(); else if (in.peek() == ',') in.get(); else if (in.peek() == '#') skip_comment(in); else break; } } void skip_comment(istream& in) { if (!in.eof() && in.peek() == '#') { in.get(); while (!in.eof() && in.peek() != '\n') in.get(); } } //: Warn if a recipe gets redefined, because large codebases can accidentally //: step on their own toes. But there'll be many occasions later where //: we'll want to disable the warnings. :(before "End Globals") bool Disable_redefine_warnings = false; :(before "End Setup") Disable_redefine_warnings = false; :(code) bool warn_on_redefine(const string& recipe_name) { if (Disable_redefine_warnings) return false; return true; } // for debugging :(code) void show_rest_of_stream(istream& in) { cerr << '^'; char c; while (in >> c) { cerr << c; } cerr << "$\n"; exit(0); } //: Have tests clean up any recipes they added. :(before "End Globals") vector<recipe_ordinal> recently_added_recipes; long long int Reserved_for_tests = 1000; :(before "End Setup") for (long long int i = 0; i < SIZE(recently_added_recipes); ++i) { if (recently_added_recipes.at(i) >= Reserved_for_tests // don't renumber existing recipes, like 'interactive' && contains_key(Recipe, recently_added_recipes.at(i))) // in case previous test had duplicate definitions Recipe_ordinal.erase(get(Recipe, recently_added_recipes.at(i)).name); Recipe.erase(recently_added_recipes.at(i)); } // Clear Other State For recently_added_recipes recently_added_recipes.clear(); :(code) :(scenario parse_comment_outside_recipe) # this comment will be dropped by the tangler, so we need a dummy recipe to stop that recipe f1 [ ] # this comment will go through to 'load' recipe main [ 1:number <- copy 23 ] +parse: instruction: copy +parse: ingredient: {"23": "literal"} +parse: product: {"1": "number"} :(scenario parse_comment_amongst_instruction) recipe main [ # comment 1:number <- copy 23 ] +parse: instruction: copy +parse: ingredient: {"23": "literal"} +parse: product: {"1": "number"} :(scenario parse_comment_amongst_instruction_2) recipe main [ # comment 1:number <- copy 23 # comment ] +parse: instruction: copy +parse: ingredient: {"23": "literal"} +parse: product: {"1": "number"} :(scenario parse_comment_amongst_instruction_3) recipe main [ 1:number <- copy 23 # comment 2:number <- copy 23 ] +parse: instruction: copy +parse: ingredient: {"23": "literal"} +parse: product: {"1": "number"} +parse: instruction: copy +parse: ingredient: {"23": "literal"} +parse: product: {"2": "number"} :(scenario parse_comment_after_instruction) recipe main [ 1:number <- copy 23 # comment ] +parse: instruction: copy +parse: ingredient: {"23": "literal"} +parse: product: {"1": "number"} :(scenario parse_label) recipe main [ +foo ] +parse: label: +foo :(scenario parse_dollar_as_recipe_name) recipe main [ $foo ] +parse: instruction: $foo :(scenario parse_multiple_properties) recipe main [ 1:number <- copy 23/foo:bar:baz ] +parse: instruction: copy +parse: ingredient: {"23": "literal", "foo": <"bar" : <"baz" : <>>>} +parse: product: {"1": "number"} :(scenario parse_multiple_products) recipe main [ 1:number, 2:number <- copy 23 ] +parse: instruction: copy +parse: ingredient: {"23": "literal"} +parse: product: {"1": "number"} +parse: product: {"2": "number"} :(scenario parse_multiple_ingredients) recipe main [ 1:number, 2:number <- copy 23, 4:number ] +parse: instruction: copy +parse: ingredient: {"23": "literal"} +parse: ingredient: {"4": "number"} +parse: product: {"1": "number"} +parse: product: {"2": "number"} :(scenario parse_multiple_types) recipe main [ 1:number, 2:address:number <- copy 23, 4:number ] +parse: instruction: copy +parse: ingredient: {"23": "literal"} +parse: ingredient: {"4": "number"} +parse: product: {"1": "number"} +parse: product: {"2": <"address" : <"number" : <>>>} :(scenario parse_properties) recipe main [ 1:number:address/lookup <- copy 23 ] +parse: product: {"1": <"number" : <"address" : <>>>, "lookup": <>} //: this test we can't represent with a scenario :(code) void test_parse_comment_terminated_by_eof() { Trace_file = "parse_comment_terminated_by_eof"; load("recipe main [\n" " a:number <- copy 34\n" "]\n" "# abc"); // no newline after comment cerr << "."; // termination = success } :(scenario warn_on_redefine) % Hide_warnings = true; recipe main [ 1:number <- copy 23 ] recipe main [ 1:number <- copy 24 ] +warn: redefining recipe main :(scenario redefine_without_warning) % Hide_warnings = true; recipe main [ 1:number <- copy 23 ] recipe! main [ 1:number <- copy 24 ] -warn: redefining recipe main $warn: 0