packages feed

zeolite-lang 0.5.0.0 → 0.6.0.0

raw patch · 67 files changed

+2326/−2755 lines, 67 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- CompilerCxx.CategoryContext: ScopeContext :: CategoryMap c -> CategoryName -> Positional (ValueParam c) -> Positional (ValueParam c) -> [DefinedMember c] -> [ParamFilter c] -> [ParamFilter c] -> Map FunctionName (ScopedFunction c) -> Map VariableName (VariableValue c) -> ScopeContext c
- CompilerCxx.CategoryContext: [scCategories] :: ScopeContext c -> CategoryMap c
- CompilerCxx.CategoryContext: [scExternalFilters] :: ScopeContext c -> [ParamFilter c]
- CompilerCxx.CategoryContext: [scExternalParams] :: ScopeContext c -> Positional (ValueParam c)
- CompilerCxx.CategoryContext: [scFunctions] :: ScopeContext c -> Map FunctionName (ScopedFunction c)
- CompilerCxx.CategoryContext: [scInternalFilters] :: ScopeContext c -> [ParamFilter c]
- CompilerCxx.CategoryContext: [scInternalparams] :: ScopeContext c -> Positional (ValueParam c)
- CompilerCxx.CategoryContext: [scMembers] :: ScopeContext c -> [DefinedMember c]
- CompilerCxx.CategoryContext: [scName] :: ScopeContext c -> CategoryName
- CompilerCxx.CategoryContext: [scVariables] :: ScopeContext c -> Map VariableName (VariableValue c)
- CompilerCxx.CategoryContext: data ScopeContext c
+ Cli.CompileMetadata: [rmExprMap] :: ModuleConfig -> [(String, Expression SourcePos)]
+ Cli.Compiler: [ltExprMap] :: LoadedTests -> ExprMap SourcePos
+ Cli.Compiler: [msExprMap] :: ModuleSpec -> ExprMap SourcePos
+ Cli.ProcessMetadata: checkMetadataFreshness :: FilePath -> CompileMetadata -> IO Bool
+ Cli.ProcessMetadata: getExprMap :: FilePath -> ModuleConfig -> IO (ExprMap SourcePos)
+ Cli.ProcessMetadata: mapMetadata :: [CompileMetadata] -> MetadataMap
+ Cli.ProcessMetadata: type MetadataMap = Map FilePath CompileMetadata
+ Compilation.CompilerState: ccExprLookup :: CompilerContext c m s a => a -> [c] -> String -> m (Expression c)
+ Compilation.CompilerState: ccGetNoTrace :: CompilerContext c m s a => a -> m Bool
+ Compilation.CompilerState: ccSetNoTrace :: CompilerContext c m s a => a -> Bool -> m a
+ Compilation.CompilerState: csExprLookup :: CompilerContext c m s a => [c] -> String -> CompilerState a m (Expression c)
+ Compilation.CompilerState: csGetNoTrace :: CompilerContext c m s a => CompilerState a m Bool
+ Compilation.CompilerState: csSetNoTrace :: CompilerContext c m s a => Bool -> CompilerState a m ()
+ Compilation.ProcedureContext: [pcExprMap] :: ProcedureContext c -> ExprMap c
+ Compilation.ProcedureContext: [pcNoTrace] :: ProcedureContext c -> Bool
+ Compilation.ProcedureContext: type ExprMap c = Map String (Expression c)
+ Compilation.ScopeContext: [scExprMap] :: ScopeContext c -> ExprMap c
+ CompilerCxx.Category: [lmExprMap] :: LanguageModule c -> ExprMap c
+ CompilerCxx.Code: captureCreationTrace :: String
+ CompilerCxx.Code: showCreationTrace :: String
+ Config.Paths: isSystemModule :: PathResolver r => r -> FilePath -> FilePath -> IO Bool
+ Parser.Pragma: parseMacroName :: Parser String
+ Parser.Pragma: pragmaExprLookup :: Parser (Pragma SourcePos)
+ Parser.Pragma: pragmaNoTrace :: Parser (Pragma SourcePos)
+ Parser.Pragma: pragmaTraceCreation :: Parser (Pragma SourcePos)
+ Types.Pragma: NoTrace :: TraceType
+ Types.Pragma: PragmaExprLookup :: [c] -> String -> Pragma c
+ Types.Pragma: PragmaTracing :: [c] -> TraceType -> Pragma c
+ Types.Pragma: TraceCreation :: TraceType
+ Types.Pragma: [pelContext] :: Pragma c -> [c]
+ Types.Pragma: [pelName] :: Pragma c -> String
+ Types.Pragma: [ptContext] :: Pragma c -> [c]
+ Types.Pragma: [ptType] :: Pragma c -> TraceType
+ Types.Pragma: data TraceType
+ Types.Pragma: instance GHC.Show.Show Types.Pragma.TraceType
+ Types.Pragma: isExprLookup :: Pragma c -> Bool
+ Types.Pragma: isNoTrace :: Pragma c -> Bool
+ Types.Pragma: isTraceCreation :: Pragma c -> Bool
+ Types.Procedure: NamedMacro :: [c] -> String -> ExpressionStart c
+ Types.Procedure: [epPragmas] :: ExecutableProcedure c -> [Pragma c]
+ Types.Procedure: assignableName :: Assignable c -> VariableName
- Cli.CompileMetadata: ModuleConfig :: FilePath -> FilePath -> [FilePath] -> [FilePath] -> [ExtraSource] -> [FilePath] -> CompileMode -> ModuleConfig
+ Cli.CompileMetadata: ModuleConfig :: FilePath -> FilePath -> [(String, Expression SourcePos)] -> [FilePath] -> [FilePath] -> [ExtraSource] -> [FilePath] -> CompileMode -> ModuleConfig
- Cli.Compiler: LoadedTests :: FilePath -> FilePath -> CompileMetadata -> [CompileMetadata] -> [CompileMetadata] -> LoadedTests
+ Cli.Compiler: LoadedTests :: FilePath -> FilePath -> CompileMetadata -> ExprMap SourcePos -> [CompileMetadata] -> [CompileMetadata] -> LoadedTests
- Cli.Compiler: ModuleSpec :: FilePath -> FilePath -> [FilePath] -> [FilePath] -> [FilePath] -> [FilePath] -> [FilePath] -> [ExtraSource] -> [FilePath] -> CompileMode -> ForceMode -> ModuleSpec
+ Cli.Compiler: ModuleSpec :: FilePath -> FilePath -> ExprMap SourcePos -> [FilePath] -> [FilePath] -> [FilePath] -> [FilePath] -> [FilePath] -> [ExtraSource] -> [FilePath] -> CompileMode -> ForceMode -> ModuleSpec
- Cli.ProcessMetadata: loadMetadata :: FilePath -> IO CompileMetadata
+ Cli.ProcessMetadata: loadMetadata :: MetadataMap -> FilePath -> IO CompileMetadata
- Cli.ProcessMetadata: loadPrivateDeps :: VersionHash -> [CompileMetadata] -> IO (Bool, [CompileMetadata])
+ Cli.ProcessMetadata: loadPrivateDeps :: VersionHash -> MetadataMap -> [CompileMetadata] -> IO (Bool, [CompileMetadata])
- Cli.ProcessMetadata: loadPublicDeps :: VersionHash -> [FilePath] -> IO (Bool, [CompileMetadata])
+ Cli.ProcessMetadata: loadPublicDeps :: VersionHash -> MetadataMap -> [FilePath] -> IO (Bool, [CompileMetadata])
- Cli.ProcessMetadata: loadTestingDeps :: VersionHash -> CompileMetadata -> IO (Bool, [CompileMetadata])
+ Cli.ProcessMetadata: loadTestingDeps :: VersionHash -> MetadataMap -> CompileMetadata -> IO (Bool, [CompileMetadata])
- Compilation.ProcedureContext: ProcedureContext :: SymbolScope -> CategoryName -> Positional (ValueParam c) -> Positional (ValueParam c) -> [DefinedMember c] -> CategoryMap c -> ParamFilters -> [ParamFilter c] -> [ParamFilter c] -> Map ParamName SymbolScope -> Map FunctionName (ScopedFunction c) -> Map VariableName (VariableValue c) -> ReturnValidation c -> [ReturnVariable] -> Set CategoryName -> [String] -> Bool -> LoopSetup [String] -> CleanupSetup (ProcedureContext c) [String] -> ProcedureContext c
+ Compilation.ProcedureContext: ProcedureContext :: SymbolScope -> CategoryName -> Positional (ValueParam c) -> Positional (ValueParam c) -> [DefinedMember c] -> CategoryMap c -> ParamFilters -> [ParamFilter c] -> [ParamFilter c] -> Map ParamName SymbolScope -> Map FunctionName (ScopedFunction c) -> Map VariableName (VariableValue c) -> ReturnValidation c -> [ReturnVariable] -> Set CategoryName -> [String] -> Bool -> LoopSetup [String] -> CleanupSetup (ProcedureContext c) [String] -> ExprMap c -> Bool -> ProcedureContext c
- Compilation.ScopeContext: ScopeContext :: CategoryMap c -> CategoryName -> Positional (ValueParam c) -> Positional (ValueParam c) -> [DefinedMember c] -> [ParamFilter c] -> [ParamFilter c] -> Map FunctionName (ScopedFunction c) -> Map VariableName (VariableValue c) -> ScopeContext c
+ Compilation.ScopeContext: ScopeContext :: CategoryMap c -> CategoryName -> Positional (ValueParam c) -> Positional (ValueParam c) -> [DefinedMember c] -> [ParamFilter c] -> [ParamFilter c] -> Map FunctionName (ScopedFunction c) -> Map VariableName (VariableValue c) -> ExprMap c -> ScopeContext c
- Compilation.ScopeContext: getProcedureScopes :: (Show c, CompileErrorM m, MergeableM m) => CategoryMap c -> DefinedCategory c -> m [ProcedureScope c]
+ Compilation.ScopeContext: getProcedureScopes :: (Show c, CompileErrorM m, MergeableM m) => CategoryMap c -> ExprMap c -> DefinedCategory c -> m [ProcedureScope c]
- CompilerCxx.Category: LanguageModule :: [Namespace] -> [Namespace] -> [Namespace] -> [AnyCategory c] -> [AnyCategory c] -> [AnyCategory c] -> [AnyCategory c] -> [AnyCategory c] -> [AnyCategory c] -> [CategoryName] -> LanguageModule c
+ CompilerCxx.Category: LanguageModule :: [Namespace] -> [Namespace] -> [Namespace] -> [AnyCategory c] -> [AnyCategory c] -> [AnyCategory c] -> [AnyCategory c] -> [AnyCategory c] -> [AnyCategory c] -> [CategoryName] -> ExprMap c -> LanguageModule c
- CompilerCxx.Category: compileConcreteDefinition :: (Show c, CompileErrorM m, MergeableM m) => CategoryMap c -> [Namespace] -> Maybe [ValueRefine c] -> DefinedCategory c -> m CxxOutput
+ CompilerCxx.Category: compileConcreteDefinition :: (Show c, CompileErrorM m, MergeableM m) => CategoryMap c -> ExprMap c -> [Namespace] -> Maybe [ValueRefine c] -> DefinedCategory c -> m CxxOutput
- CompilerCxx.CategoryContext: getContextForInit :: (Show c, CompileErrorM m, MergeableM m) => CategoryMap c -> AnyCategory c -> DefinedCategory c -> SymbolScope -> m (ProcedureContext c)
+ CompilerCxx.CategoryContext: getContextForInit :: (Show c, CompileErrorM m, MergeableM m) => CategoryMap c -> ExprMap c -> AnyCategory c -> DefinedCategory c -> SymbolScope -> m (ProcedureContext c)
- CompilerCxx.CategoryContext: getMainContext :: CompileErrorM m => CategoryMap c -> m (ProcedureContext c)
+ CompilerCxx.CategoryContext: getMainContext :: CompileErrorM m => CategoryMap c -> ExprMap c -> m (ProcedureContext c)
- CompilerCxx.Procedure: compileMainProcedure :: (Show c, CompileErrorM m, MergeableM m) => CategoryMap c -> Expression c -> m (CompiledData [String])
+ CompilerCxx.Procedure: compileMainProcedure :: (Show c, CompileErrorM m, MergeableM m) => CategoryMap c -> ExprMap c -> Expression c -> m (CompiledData [String])
- Types.Procedure: ExecutableProcedure :: [c] -> [c] -> FunctionName -> ArgValues c -> ReturnValues c -> Procedure c -> ExecutableProcedure c
+ Types.Procedure: ExecutableProcedure :: [c] -> [Pragma c] -> [c] -> FunctionName -> ArgValues c -> ReturnValues c -> Procedure c -> ExecutableProcedure c

Files

ChangeLog.md view
@@ -1,5 +1,52 @@ # Revision history for zeolite-lang +## 0.6.0.0  -- 2020-05-14++### Compiler CLI++* **[behavior]** Improves error messages for type mismatches and return-count+  mismatches.++* **[behavior]** Improves the efficiency of loading metadata for dependencies.++* **[breaking]** Fixes module-staleness check when running tests (`-t`).++* **[breaking]** Prevents binaries from being created from main categories that+  are defined in `$TestsOnly$` sources. (This was the original intention, but it+  was missed the first time around.)++### Language++* **[new]** Allows `@category` members in `concrete` categories to refer to each+  other during initialization.++* **[new]** Adds the `$ExprLookup[`*`MACRO_NAME`*`]$` pragma, which allows the+  user to define expression substitutions in `.zeolite-module`.++* **[new]** Adds the `$NoTrace$` pragma, which skips generating stack-trace+  information for specific functions. This is useful for deeply-recursive+  functions whose full trace would not be useful.++* **[new]** Adds the `$TraceCreation$` pragma, which appends a trace for the+  creation of a value when there is a crash in one of its functions. This is+  useful when the crash stems from value initialization.++* **[fix]** Fixes parsing of the `'"'` `Char`-literal.++### Libraries++* **[new]** Adds the `ErrorOr<#x>` category to `lib/util`. This allows functions+  to return either the expected type or an error message.++* **[new]** Adds the `Void` category to `lib/util`. This can be used to ignore+  type parameters when a value still needs to be instantiated.++* **[new]** Adds the `readAll` function to `TextReader` (in `lib/util`) to+  support reading an entire file at once.++* **[breaking]** Adds crashes when attempting to read `RawFileReader` or write+  `RawFileWriter` if there is a preexisting file error.+ ## 0.5.0.0  -- 2020-05-12  * **[new]** Adds compiler support for pragmas, which will allow compiler
base/logging.cpp view
@@ -40,11 +40,21 @@       std::cerr << " '" << condition_ << "'";     }     std::cerr << ": " << output_.str() << std::endl;-    for (const auto& trace : TraceContext::GetTrace()) {+    const TraceList call_trace = TraceContext::GetTrace();+    for (const auto& trace : call_trace) {       if (!trace.empty()) {         std::cerr << "  " << trace << std::endl;       }     }+    const TraceList creation_trace = TraceCreation::GetTrace();+    if (!creation_trace.empty()) {+      std::cerr << TraceCreation::GetType() << " value originally created at:" << std::endl;+      for (const auto& trace : creation_trace) {+        if (!trace.empty()) {+          std::cerr << "  " << trace << std::endl;+        }+      }+    }     std::raise(signal_);   } }@@ -92,8 +102,8 @@ }  -std::list<std::string> TraceContext::GetTrace() {-  std::list<std::string> trace;+TraceList TraceContext::GetTrace() {+  TraceList trace;   const TraceContext* current = GetCurrent();   while (current) {     current->AppendTrace(trace);@@ -107,7 +117,7 @@   at_ = at; } -void SourceContext::AppendTrace(std::list<std::string>& trace) const {+void SourceContext::AppendTrace(TraceList& trace) const {   std::ostringstream output;   if (at_ == nullptr || at_[0] == 0x00) {     output << "From " << name_;@@ -126,7 +136,7 @@   at_ = at; } -void CleanupContext::AppendTrace(std::list<std::string>& trace) const {+void CleanupContext::AppendTrace(TraceList& trace) const {   std::ostringstream output;   if (at_ == nullptr || at_[0] == 0x00) {     output << "In cleanup block";
base/logging.hpp view
@@ -73,6 +73,12 @@   #define PRED_CONTEXT_POINT(point) \     TraceContext::SetContext(point), +  #define CAPTURE_CREATION \+    CreationTrace creation_context_;++  #define TRACE_CREATION \+    TraceCreation trace_creation(TypeInstance::TypeName(parent), creation_context_);+ #else    #define TRACE_FUNCTION(name)@@ -83,11 +89,19 @@    #define PRED_CONTEXT_POINT(point) +  #define CAPTURE_CREATION++  #define TRACE_CREATION+ #endif ++using TraceList = std::list<std::string>;++ class TraceContext : public capture_thread::ThreadCapture<TraceContext> {  public:-  static std::list<std::string> GetTrace();+  static TraceList GetTrace();    template<int S>   static inline void SetContext(const char(&at)[S]) {@@ -101,7 +115,7 @@   private:   virtual void SetLocal(const char*) = 0;-  virtual void AppendTrace(std::list<std::string>& trace) const = 0;+  virtual void AppendTrace(TraceList& trace) const = 0;   virtual const TraceContext* GetNext() const = 0; }; @@ -113,7 +127,7 @@   private:   void SetLocal(const char*) final;-  void AppendTrace(std::list<std::string>& trace) const final;+  void AppendTrace(TraceList& trace) const final;   const TraceContext* GetNext() const final;    const char* at_;@@ -128,7 +142,7 @@   private:   void SetLocal(const char*) final;-  void AppendTrace(std::list<std::string>& trace) const final;+  void AppendTrace(TraceList& trace) const final;   const TraceContext* GetNext() const final;    const char* at_;@@ -156,6 +170,46 @@   const std::vector<std::string>& GetArgs() const final;    const std::vector<std::string> argv_;+  const ScopedCapture capture_to_;+};++class CreationTrace {+ public:+  inline CreationTrace() : trace_(TraceContext::GetTrace()) {}++  inline const TraceList& GetTrace() const {+    return trace_;+  }++ private:+  const TraceList trace_;+};++class TraceCreation : public capture_thread::ThreadCapture<TraceCreation> {+ public:+  inline TraceCreation(std::string type, const CreationTrace& trace)+    : type_(type), trace_(trace), capture_to_(this) {}++  static inline std::string GetType() {+    if (GetCurrent()) {+      return GetCurrent()->type_;+    } else {+      return std::string();+    }+  }++  static inline TraceList GetTrace() {+    if (GetCurrent()) {+      return GetCurrent()->trace_.GetTrace();+    } else {+      return TraceList();+    }+  }++ private:++  const std::string type_;+  const CreationTrace& trace_;   const ScopedCapture capture_to_; }; 
+ example/parser/.zeolite-module view
@@ -0,0 +1,9 @@+root: "../.."+path: "example/parser"+public_deps: [+  "lib/util"+]+private_deps: [+  "lib/file"+]+mode: incremental {}
+ example/parser/README.md view
@@ -0,0 +1,81 @@+# Zeolite Parser Example++*Also see+[a highlighted version of the example code](https://ta0kira.github.io/zeolite/example/parser).*++This example demonstrates parsing text using a parser-combinator approach+inspired by [`parsec`][parsec]. (The Haskell library that the `zeolite` compiler+uses to parse source and config files.)++## Notes++This example highlights several distinguishing features of the+[Zeolite language][zeolite]:++- **Encapsulation.** Data encapsulation is mandatory in Zeolite. This forces the+  code author to think about usage patterns before data representation.++- **Factory Functions.** There is no "default construction" in Zeolite; the code+  author must explicitly expose factory functions if needed. In this example,+  most of the factory functions return an interface, rather than the actual+  `concrete` type being constructed.++- **Internal Inheritance.** `ParseState` inherits `ParseContext` internally+  (see `parser.0rp` and `parser.0rx`), which allows `ParseState` to+  conditionally expose the `ParseContext` interface to other functions.++- **Function Merging.** `ParseState` and `ParseContext` declare some of the same+  functions in `parser.0rp`. When `ParseState` inherits `ParseContext`+  internally in `parser.0rx`, functions with the same names are merged by the+  compiler. This allows `ParseState` to expose a subset of `ParseContext`+  without needing a superfluous interface with the shared functions.++- **Parameter Variance.** Several categories used in this example (e.g.,+  `ParseState` and `ParseContext`) have a *covariant* parameter, which allows+  their parameters to be converted to other types. Specifically:++  - Any `ParseContext<#x>` can be converted to `ParseContext<any>` when calling+    `Parser.run`. (In C++ this would require a `template` and in Java it would+    require using `<?>`.)++  - The `ParseState<all>` returned from `ParseContext.setBrokenInput` can be+    converted to all other `ParseState<#x>`. (In C++ or Java this would require+    the caller to explicitly pass a type parameter to `setBrokenInput`.)++- **Multiple Returns.** Many of the functions in this example return more than+  one value to the caller. (In C++ and Java this would require a data structure+  for grouping objects together.)++- **Test Sources.** The `parser-test.0rt` source file contains a `testcase` that+  the compiler itself can execute.++- **Expression Pragmas.** `parser-test.0rt` uses the `$ExprLookup[MODULE_PATH]$`+  pragma to get the absolute path to its own module in order to locate a data+  file. This might otherwise require hard-coding a path or relying on the tests+  being executed from the module path.++- **Module Config.** The `.zeolite-module` file contains the build configuration+  for this example. This means that building and running the example does not+  require special instructions, scripting, or `Makefile`s.++## Running++(Included with compiler package starting with version `0.6.0.0`.)++To run the example:++```shell+# This is just to locate the example code. Not for normal use!+ZEOLITE_PATH=$(zeolite --get-path)++# Compile the example.+zeolite -p "$ZEOLITE_PATH" -r example/parser++# Run the unit tests.+zeolite -p "$ZEOLITE_PATH" -t example/parser+```++(There is currently no binary target in this example.)++[parsec]: https://hackage.haskell.org/package/parsec+[zeolite]: https://github.com/ta0kira/zeolite
+ example/parser/parse-text.0rp view
@@ -0,0 +1,27 @@+/* Parses a fixed string.+ */+concrete StringParser {+  @type create (String) -> (Parser<String>)+}++/* Parses a string containing a limited set of characters.+ */+concrete SequenceOfParser {+  @type create (String, Int /*min*/, Int /*max*/) -> (Parser<String>)+}++/* Parses a fixed character.+ */+concrete CharParser {+  @type create (Char) -> (Parser<Char>)+}++/* Parser combinators.+ */+concrete Parse {+  @type const<#x> (#x)                     -> (Parser<#x>)+  @type try<#x>   (Parser<#x>)             -> (Parser<#x>)+  @type or<#x>    (Parser<#x>,Parser<#x>)  -> (Parser<#x>)+  @type left<#x>  (Parser<#x>,Parser<any>) -> (Parser<#x>)+  @type right<#x> (Parser<any>,Parser<#x>) -> (Parser<#x>)+}
+ example/parser/parse-text.0rx view
@@ -0,0 +1,254 @@+define StringParser {+  refines Parser<String>++  @value String match++  run (contextOld) {+    if (contextOld.hasAnyError()) {+      return contextOld.convertError<String>()+    }+    String message <- "Failed to match \"" + match + "\" at " + contextOld.getPosition()+    ParseContext<any> context <- contextOld+    scoped {+      Int index <- 0+    } in while (index < match.readSize()) {+      if (context.atEof() || context.current() != match.readPosition(index)) {+        if (index > 0) {+          // Partial match => set error context.+          return context.setBrokenInput(message)+        } else {+          return context.setValue<String>(ErrorOr$$error(message))+        }+      }+    } update {+      index <- index+1+      context <- context.advance()+    }+    return context.setValue<String>(ErrorOr$$value<String>(match))+  }++  create (match) {+    return StringParser{ match }+  }+}++define SequenceOfParser {+  refines Parser<String>++  @value String matches+  @value Int    min+  @value Int    max++  run (contextOld) {+    if (contextOld.hasAnyError()) {+      return contextOld.convertError<String>()+    }+    String message <- "Failed to match [" + matches + "]{" ++                      min.formatted() + "," + max.formatted() + "} at " ++                      contextOld.getPosition()+    ParseContext<any> context <- contextOld+    Builder<String> builder <- String$builder()+    Int count <- 0+    while (!context.atEof() && (max == 0 || count < max)) {+      Bool found <- false+      scoped {+        Int index <- 0+      } in while (index < matches.readSize()) {+        if (context.current() == matches.readPosition(index)) {+          \ builder.append(matches.readPosition(index).formatted())+          found <- true+          break+        }+      } update {+        index <- index+1+      }+      if (!found) {+        break+      }+    } update {+      count <- count+1+      context <- context.advance()+    }+    if (count >= min) {+      return context.setValue<String>(ErrorOr$$value<String>(builder.build()))+    } elif (count > 0) {+      // Partial match => set error context.+      return context.setBrokenInput(message)+    } else {+      return context.setValue<String>(ErrorOr$$error(message))+    }+  }++  create (matches,min,max) {+    return SequenceOfParser{ matches, min, max }+  }+}++define CharParser {+  refines Parser<Char>++  @value Char match++  run (contextOld) {+    if (contextOld.hasAnyError()) {+      return contextOld.convertError<Char>()+    }+    if (contextOld.atEof() || contextOld.current() != match) {+      String message <- "Failed to match '" + match.formatted() + "' at " + contextOld.getPosition()+      return contextOld.setValue<Char>(ErrorOr$$error(message))+    } else {+      return contextOld.advance().setValue<Char>(ErrorOr$$value<Char>(match))+    }+  }++  create (match) {+    return CharParser{ match }+  }+}++concrete ConstParser<#x> {+  @type create (#x) -> (Parser<#x>)+}++define ConstParser {+  refines Parser<#x>++  @value #x value++  run (contextOld) {+    return contextOld.setValue<#x>(ErrorOr$$value<#x>(value))+  }++  create (value) {+    return ConstParser<#x>{ value }+  }+}++concrete TryParser<#x> {+  @type create (Parser<#x>) -> (Parser<#x>)+}++define TryParser {+  refines Parser<#x>++  @value Parser<#x> parser++  run (contextOld) {+    if (contextOld.hasAnyError()) {+      return contextOld.convertError<#x>()+    }+    ParseContext<#x> context <- contextOld.run<#x>(parser)+    if (context.hasAnyError()) {+      return contextOld.setValue<#x>(context.getValue().convertError())+    } else {+      return context.toState()+    }+  }++  create (parser) {+    return TryParser<#x>{ parser }+  }+}++concrete OrParser<#x> {+  @type create (Parser<#x>,Parser<#x>) -> (Parser<#x>)+}++define OrParser {+  refines Parser<#x>++  @value Parser<#x> parser1+  @value Parser<#x> parser2++  run (contextOld) {+    if (contextOld.hasAnyError()) {+      return contextOld.convertError<#x>()+    }+    ParseContext<#x> context <- contextOld.run<#x>(parser1)+    if (context.hasAnyError() && !context.hasBrokenInput()) {+      return contextOld.run<#x>(parser2).toState()+    } else {+      return context.toState()+    }+  }++  create (parser1,parser2) {+    return OrParser<#x>{ parser1, parser2 }+  }+}++concrete LeftParser<#x> {+  @type create (Parser<#x>,Parser<any>) -> (Parser<#x>)+}++define LeftParser {+  refines Parser<#x>++  @value Parser<#x>  parser1+  @value Parser<any> parser2++  run (contextOld) {+    if (contextOld.hasAnyError()) {+      return contextOld.convertError<#x>()+    }+    ParseContext<#x> context <- contextOld.run<#x>(parser1)+    if (!context.hasAnyError()) {+      return context.run<any>(parser2).setValue<#x>(context.getValue())+    } else {+      return context.toState()+    }+  }++  create (parser1,parser2) {+    return LeftParser<#x>{ parser1, parser2 }+  }+}++concrete RightParser<#x> {+  @type create (Parser<any>,Parser<#x>) -> (Parser<#x>)+}++define RightParser {+  refines Parser<#x>++  @value Parser<any> parser1+  @value Parser<#x>  parser2++  run (contextOld) {+    if (contextOld.hasAnyError()) {+      return contextOld.convertError<#x>()+    }+    ParseContext<any> context <- contextOld.run<any>(parser1)+    if (!context.hasAnyError()) {+      return context.run<#x>(parser2).toState()+    } else {+      return context.convertError<#x>()+    }+  }++  create (parser1,parser2) {+    return RightParser<#x>{ parser1, parser2 }+  }+}++define Parse {+  const (value) {+    return ConstParser<#x>$create(value)+  }++  try (parser) {+    return TryParser<#x>$create(parser)+  }++  or (parser1,parser2) {+    return OrParser<#x>$create(parser1,parser2)+  }++  left (parser1,parser2) {+    return LeftParser<#x>$create(parser1,parser2)+  }++  right (parser1,parser2) {+    return RightParser<#x>$create(parser1,parser2)+  }+}
+ example/parser/parser-test.0rt view
@@ -0,0 +1,34 @@+testcase "parse data from a file" {+  success Test$execute()+}++concrete Test {+  @type execute () -> ()+}++define Test {+  execute () {+    TestData data <- TestData$parseFrom(loadTestData()).getValue()++    if (data.getName() != "example data") {+      fail(data.getName())+    }++    if (data.getDescription() != "THIS_IS_A_TOKEN") {+      fail(data.getDescription())+    }++    if (data.getBoolean() != false) {+      fail(data.getBoolean())+    }+  }++  @type loadTestData () -> (String)+  loadTestData () {+    scoped {+      RawFileReader reader <- RawFileReader$open($ExprLookup[MODULE_PATH]$ + "/test-data.txt")+    } cleanup {+      \ reader.freeResource()+    } in return TextReader$readAll(reader)+  }+}
+ example/parser/parser.0rp view
@@ -0,0 +1,51 @@+/* Manages state for parsing operations outside of a Parser.+ *+ * The state is immutable; all update operations return a new state.+ *+ * Since #x is covariant, any ParseState can convert to ParseState<any>, and+ * ParseState<all> can convert to all other ParseState.+ */+concrete ParseState<|#x> {+  @category new (String) -> (ParseState<any>)++  @value run<#y>       (Parser<#y>) -> (ParseState<#y>)+  @value runAndGet<#y> (Parser<#y>) -> (ParseState<any>,ErrorOr<#y>)++  @value atEof       () -> (Bool)+  @value hasAnyError () -> (Bool)+  @value getError    () -> (Formatted)+}++/* A self-contained parser operation.+ *+ * Since #x is covariant, any Parser can convert to Parser<any>, and Parser<all>+ * can convert to all other Parser.+ */+@value interface Parser<|#x> {+  run (ParseContext<any>) -> (ParseState<#x>)+}++/* Parser context available when running a Parser.+ *+ * Since #x is covariant, any ParseContext can convert to ParseContext<any>, and+ * ParseContext<all> can convert to all other ParseContext.+ */+@value interface ParseContext<|#x> {+  run<#y>       (Parser<#y>) -> (ParseContext<#y>)+  runAndGet<#y> (Parser<#y>) -> (ParseContext<any>,ErrorOr<#y>)++  convertError<#y> ()            -> (ParseState<#y>)+  getValue         ()            -> (ErrorOr<#x>)+  setValue<#y>     (ErrorOr<#y>) -> (ParseState<#y>)+  setBrokenInput   (Formatted)   -> (ParseState<all>)+  toState          ()            -> (ParseState<#x>)++  getPosition () -> (String)++  atEof          () -> (Bool)+  hasAnyError    () -> (Bool)+  hasBrokenInput () -> (Bool)++  current () -> (Char)+  advance () -> (ParseContext<#x>)+}
+ example/parser/parser.0rx view
@@ -0,0 +1,120 @@+define ParseState {+  // This internal inheritance takes advantage of function merging.+  //+  // Notice that several functions (e.g., run, hasAnyError) appear to be+  // declared separately in ParseState and ParseContext in parser.0rp. Since the+  // function types are compatible, the compiler automatically merges them. This+  // allows ParseState to expose a subset of ParseContext without needing a+  // superfluous interface that both categories refine.+  refines ParseContext<#x>++  @value String             data+  @value Int                index+  @value Int                line+  @value Int                char+  @value ErrorOr<#x>        value+  @value optional Formatted error++  new (data) {+    return ParseState<any>{ data, 0, 0, 0, ErrorOr$$value<Void>(Void$void()), empty }+  }++  run (parser) {+    return parser.run(self)+  }++  runAndGet (parser) {+    ParseState<#y> context <- parser.run(self)+    return context, context.getValue()+  }++  convertError () {+    return ParseState<#y>{ data, index, line, char, ErrorOr$$error(getError()), error }+  }++  getValue () {+    if (present(error)) {+      return ErrorOr$$error(require(error))+    } else {+      return value+    }+  }++  setValue (value2) {+    if (hasBrokenInput()) {+      return convertError<#y>()+    } else {+      return ParseState<#y>{ data, index, line, char, value2, error }+    }+  }++  setBrokenInput (message) {+    return ParseState<all>{ data, index, line, char, ErrorOr$$error(message), message }+  }++  toState () {+    return self+  }++  getPosition () {+    return String$builder()+        .append("(")+        .append(line.formatted())+        .append(",")+        .append(char.formatted())+        .append(")")+        .build()+  }++  atEof () {+    return index >= data.readSize()+  }++  hasAnyError () {+    return present(error) || value.isError()+  }++  hasBrokenInput () {+    return present(error)+  }++  current () {+    \ sanityCheck()+    return data.readPosition(index)+  }++  advance () {+    \ sanityCheck()+    if (data.readPosition(index) == '\n') {+      return ParseState<#x>{ data, index+1, line+1, 0, value, error }+    } else {+      return ParseState<#x>{ data, index+1, line, char+1, value, error }+    }+  }++  getError () {+    if (present(error)) {+      return require(error)+    } else {+      return value.getError()+    }+  }++  @value sanityCheck () -> ()+  sanityCheck () {+    if (hasBrokenInput()) {+      \ LazyStream<Formatted>$new()+          .append("Error at ")+          .append(getPosition())+          .append(": ")+          .append(getError())+          .writeTo(SimpleOutput$error())+    }+    if (atEof()) {+      \ LazyStream<Formatted>$new()+          .append("Reached end of input at ")+          .append(getPosition())+          .writeTo(SimpleOutput$error())+    }+  }+}
+ example/parser/test-data.0rp view
@@ -0,0 +1,11 @@+$TestsOnly$++/* Parses the test-data.txt file using a one-off format.+ */+concrete TestData {+  @type parseFrom (String) -> (ErrorOr<TestData>)++  @value getName        () -> (String)+  @value getDescription () -> (String)+  @value getBoolean     () -> (Bool)+}
+ example/parser/test-data.0rx view
@@ -0,0 +1,85 @@+$TestsOnly$++define TestData {+  @category Parser<any> whitespace <- SequenceOfParser$create(" \n\t",1,0)++  @category Parser<String> sentence        <- SentenceParser$create()+  @category Parser<String> token           <- SequenceOfParser$create("ABCDEFGHIJKLMNOPQRSTUVWXYZ_",1,0)+  @category Parser<String> sentenceOrToken <- sentence `Parse$or<String>` token `Parse$left<String>` whitespace++  @category Parser<Bool> acronym           <- StringParser$create("acronym")  `Parse$right<Bool>` Parse$const<Bool>(true)+  @category Parser<Bool> aardvark          <- StringParser$create("aardvark") `Parse$right<Bool>` Parse$const<Bool>(false)+  @category Parser<Bool> acronymOrAardvark <- `Parse$try<Bool>` acronym `Parse$or<Bool>` aardvark `Parse$left<Bool>` whitespace++  @category Parser<any> fileStart      <- StringParser$create("file_start")   `Parse$left<any>` whitespace+  @category Parser<any> fileEnd        <- StringParser$create("file_end")     `Parse$left<any>` whitespace+  @category Parser<any> nameTag        <- StringParser$create("name:")        `Parse$left<any>` whitespace+  @category Parser<any> descriptionTag <- StringParser$create("description:") `Parse$left<any>` whitespace+  @category Parser<any> aWordTag       <- StringParser$create("a_word:")      `Parse$left<any>` whitespace++  @value String name+  @value String description+  @value Bool   boolean++  parseFrom (data) {+    ParseState<any> state <- ParseState$$new(data)+    state                              <- state.run<any>(fileStart)+    state                              <- state.run<any>(nameTag)+    state, ErrorOr<String> name        <- state.runAndGet<String>(sentenceOrToken)+    state                              <- state.run<any>(descriptionTag)+    state, ErrorOr<String> description <- state.runAndGet<String>(sentenceOrToken)+    state                              <- state.run<any>(aWordTag)+    state, ErrorOr<Bool> boolean       <- state.runAndGet<Bool>(acronymOrAardvark)+    state                              <- state.run<any>(fileEnd)++    if (state.hasAnyError()) {+      return ErrorOr$$error(state.getError())+    } elif (!state.atEof()) {+      return ErrorOr$$error("Parsing did not consume all of the data")+    } else {+      return ErrorOr$$value<TestData>(TestData{ name.getValue(),+                                                description.getValue(),+                                                boolean.getValue() })+    }+  }++  getName () {+    return name+  }++  getDescription () {+    return description+  }++  getBoolean () {+    return boolean+  }+}++concrete SentenceParser {+  @type create () -> (Parser<String>)+}++define SentenceParser {+  refines Parser<String>++  @category String sentenceChars <- "ABCDEFGHIJKLMNOPQRSTUVWXYZ" ++                                    "abcdefghijklmnopqrstuvwxyz" ++                                    "., !?-"+  @category Parser<String> string <- SequenceOfParser$create(sentenceChars,0,0)++  run (contextOld) {+    if (contextOld.hasAnyError()) {+      return contextOld.convertError<String>()+    }+    ParseContext<any> context <- contextOld+    context                       <- context.run<any>(CharParser$create('"'))+    context, ErrorOr<String> text <- context.runAndGet<String>(string)+    context                       <- context.run<any>(CharParser$create('"'))+    return context.setValue<String>(text)+  }++  create () {+    return SentenceParser{ }+  }+}
+ example/parser/test-data.txt view
@@ -0,0 +1,5 @@+file_start+  name:        "example data"+  description: THIS_IS_A_TOKEN+  a_word:      aardvark+file_end
− example/regex/README.md
@@ -1,20 +0,0 @@-# Zeolite Regex Example--*Also see-[a highlighted version of the example code](https://ta0kira.github.io/zeolite/example/regex).*--To run the example:--```shell-# This is just to locate the example code. Not for normal use!-ZEOLITE_PATH=$(zeolite --get-path)--# Compile the example.-zeolite -p "$ZEOLITE_PATH" -i lib/util -m RegexDemo example/regex--# Run the unit tests.-zeolite -p "$ZEOLITE_PATH" -t example/regex--# Execute the compiled binary.-$ZEOLITE_PATH/example/regex/RegexDemo-```
− example/regex/char-regex.0rp
@@ -1,4 +0,0 @@-concrete CharRegex {-  @type parse (String) -> (optional MatcherTemplate<Char>)-  @type match (MatcherTemplate<Char>,String) -> (Bool)-}
− example/regex/char-regex.0rx
@@ -1,199 +0,0 @@-define CharRegex {-  parse (pattern) (matcher) {-    ReadIterator<Char> p <- ReadIterator$$fromReadPosition<Char>(pattern)-    // TODO: Needs error handling.-    _, matcher <- parseExpression(p)-  }--  match (template,data) {-    ReadIterator<Char> p <- ReadIterator$$fromReadPosition<Char>(data)-    Matcher<Char> matcher <- template.newMatcher()-    while (!p.pastForwardEnd()) {-      MatchState state <- matcher.tryNextMatch(p.readCurrent())-      if (state `MatchState$equals` MatchState$matchFail()) {-        break-      }-      p <- p.forward()-      if (state `MatchState$equals` MatchState$matchComplete()) {-        break-      }-    }-    return p.pastForwardEnd() && matcher.matchSatisfied()-  }--  @type parseSequence (ReadIterator<Char>) ->-                      (ReadIterator<Char>,optional ReadSequence<MatcherTemplate<Char>>)-  parseSequence (p) {-    if (p.pastForwardEnd()) {-      return p, empty-    }-    ReadIterator<Char> p2, optional MatcherTemplate<Char> matcher <- parseNonSequence(p)-    if (!p2.pastForwardEnd() && (p2.readCurrent() == '|' || p2.readCurrent() == ')')) {-      // Requires choice matching or the end of a subexpression.-      if (present(matcher)) {-        return p2, LinkedNode<MatcherTemplate<Char>>$create(require(matcher),empty)-      } else {-        // TODO: Disregards errors from parseNonSequence.-        return p2, LinkedNode<MatcherTemplate<Char>>$create(MatchEmpty$create(),empty)-      }-    } if (!present(matcher)) {-      return p2, empty-    } else {-      p2, optional ReadSequence<MatcherTemplate<Char>> sequence <- parseSequence(p2)-      return p2, LinkedNode<MatcherTemplate<Char>>$create(require(matcher),sequence)-    }-  }--  @type parseNonSequence (ReadIterator<Char>) ->-                         (ReadIterator<Char>,optional MatcherTemplate<Char>)-  parseNonSequence (p) (p2,matcher) {-    p2 <- p-    matcher <- empty-    while (!p2.pastForwardEnd()) {-      Char c <- p2.readCurrent()-      if (c == '|' || c == ')') {-        // Requires choice matching or the end of a subexpression.-        return _-      } elif (c == '*') {-        // TODO: Needs error handling.-        return p2.forward(),-               MatchBranches<Char>$create(BranchRepeat<Char>$createZeroPlus(require(matcher)))-      } elif (c == '+') {-        // TODO: Needs error handling.-        return p2.forward(),-               MatchBranches<Char>$create(BranchRepeat<Char>$createOnePlus(require(matcher)))-      } elif (c == '{') {-        p2, Int min, Int max <- parseRange(p2.forward())-        if (p2.pastForwardEnd() || p2.readCurrent() != '}') {-          // TODO: Needs error handling.-          fail("missing }")-        }-        return p2.forward(),-               MatchBranches<Char>$create(BranchRepeat<Char>$createRange(min,max,require(matcher)))-      } elif (present(matcher)) {-        return _-      } elif (c == '[') {-        p2, matcher <- parseCharChoices(p2.forward())-        if (p2.pastForwardEnd() || p2.readCurrent() != ']') {-          // TODO: Needs error handling.-          fail("missing ]")-        }-        p2 <- p2.forward()-      } elif (c == '(') {-        p2, matcher <- parseExpression(p2.forward())-        if (p2.pastForwardEnd() || p2.readCurrent() != ')') {-          // TODO: Needs error handling.-          fail("missing )")-        }-        p2 <- p2.forward()-      } else {-        p2, matcher <- parseSingleChar(p2)-      }-    }-  }--  @type parseRange (ReadIterator<Char>) -> (ReadIterator<Char>,Int,Int)-  parseRange (p) (p2,min,max) {-    max <- 0-    p2, min <- parseCount(p)-    if (p2.pastForwardEnd() || p2.readCurrent() != ',') {-      max <- min-    } else {-      p2, max <- parseCount(p2.forward())-    }-  }--  @type parseCount (ReadIterator<Char>) -> (ReadIterator<Char>,Int)-  parseCount (p) (p2,count) {-    // TODO: Needs error handling.-    count <- 0-    p2 <- p-    while (!p2.pastForwardEnd()) {-      Char c <- p2.readCurrent()-      if (c >= '0' && c <= '9') {-        count <- 10*count + (c - '0')-      } else {-        break-      }-    } update {-      p2 <- p2.forward()-    }-  }--  @type parseExpression (ReadIterator<Char>) ->-                        (ReadIterator<Char>,optional MatcherTemplate<Char>)-  parseExpression (p) (p2,matcher) {-    optional ReadSequence<MatcherTemplate<Char>> choices <- empty-    p2 <- p-    while (!p2.pastForwardEnd()) {-      p2, optional ReadSequence<MatcherTemplate<Char>> sequence <- parseSequence(p2)-      if (!present(sequence)) {-        break-      }-      choices <- LinkedNode<MatcherTemplate<Char>>$create(-          MatchBranches<Char>$create(BranchSequence<Char>$create(sequence)),choices)-      if (p2.pastForwardEnd() || p2.readCurrent() != '|') {-        break-      }-      p2 <- p2.forward()-    }-    if (!present(choices)) {-      matcher <- MatchEmpty$create()-    } else {-      matcher <- MatchChoices<Char>$create(choices)-    }-  }--  @type parseSingleChar (ReadIterator<Char>) ->-                        (ReadIterator<Char>,optional MatcherTemplate<Char>)-  parseSingleChar (p) (p2,matcher) {-    // TODO: Needs error handling.-    Char c <- p.readCurrent()-    p2 <- p.forward()-    if (c == '\\') {-      matcher <- MatchSingle<Char>$create(p2.readCurrent())-      p2 <- p.forward()-    } elif (c == '.') {-      matcher <- MatchAny$create()-    } else {-      matcher <- MatchSingle<Char>$create(c)-    }-  }--  @type parseCharChoices (ReadIterator<Char>) ->-                         (ReadIterator<Char>,optional MatcherTemplate<Char>)-  parseCharChoices (p) (p2,matcher) {-    p2 <- p-    matcher <- empty-    optional Char previous <- empty-    Bool doRange <- false-    optional ReadSequence<MatcherTemplate<Char>> choices <- empty-    while (!p2.pastForwardEnd()) {-      Char c <- p2.readCurrent()-      if (c == '\\') {-        p2 <- p2.forward()-        c <- p2.readCurrent()-      }-      if (c == ']') {-        break-      } elif (c == '-' && present(previous) && !doRange) {-        doRange <- true-      } elif (doRange) {-        choices <- LinkedNode<MatcherTemplate<Char>>$create(MatchRange<Char>$create(require(previous),c),choices)-        previous <- empty-        doRange <- false-      } elif (present(previous)) {-        choices <- LinkedNode<MatcherTemplate<Char>>$create(MatchSingle<Char>$create(require(previous)),choices)-        previous <- c-      } else {-        previous <- c-      }-    } update {-      p2 <- p2.forward()-    }-    if (present(previous)) {-      choices <- LinkedNode<MatcherTemplate<Char>>$create(MatchSingle<Char>$create(require(previous)),choices)-    }-    matcher <- MatchChoices<Char>$create(choices)-  }-}
− example/regex/regex-demo.0rx
@@ -1,92 +0,0 @@-concrete RegexDemo {-  @type run () -> ()-}--define RegexDemo {-  run () {-    \ testMatch("","",true)-    \ testMatch("","a",false)-    \ testMatch(".","a",true)-    \ testMatch(".","aa",false)-    \ testMatch("a",".",false)-    \ testMatch(".+","",false)-    \ testMatch(".+","a",true)-    \ testMatch(".+","ab",true)-    \ testMatch(".*.","a",true)-    \ testMatch(".*.","ab",true)-    \ testMatch("(ab)*","",true)-    \ testMatch("(ab)*","abab",true)-    \ testMatch("(ab)*","a",false)-    \ testMatch("(ab)*ac","abac",true)-    \ testMatch("(ab)*ac","ac",true)-    \ testMatch("(ab)*ac","aca",false)-    \ testMatch("(ab)*ac","acab",false)-    \ testMatch("(ab)*|ac","abab",true)-    \ testMatch("(ab)*|ac","ac",true)-    \ testMatch("(ab)*|ac","acac",false)-    \ testMatch("(ab)*|ac","abac",false)-    \ testMatch("a{2}","a",false)-    \ testMatch("a{2}","aa",true)-    \ testMatch("a{2}","aaa",false)-    \ testMatch("a{2,3}","a",false)-    \ testMatch("a{2,3}","aa",true)-    \ testMatch("a{2,3}","aaa",true)-    \ testMatch("a{2,3}","aaaa",false)-    \ testMatch("(a+){4}","aaa",false)-    \ testMatch("(a+){4}","aaaa",true)-    \ testMatch("(a+){4}","aaaaa",true)-    \ testMatch("(a+){4}","aaaaaa",true)-    \ testMatch("[a-z?]","q",true)-    \ testMatch("[a-z?]","-",false)-    \ testMatch("[a-z?]","?",true)-    \ testMatch("[a-z?]*","abcde?",true)-    \ testMatch("[a-z?]*","ab-cde",false)--    // A few malicious regexes from https://en.wikipedia.org/wiki/ReDoS.-    //-    // The first three below don't finish, due to repeat branching. The-    // branching of "a+" is O(sqrt(2)^n) and "(a+)+" is therefore O(2^n).-    //-    // The first two could still be matched using non-branching repeats, since-    // there is a complete overlap between the tail of one repeat and the next-    // repeat, and repeats past 1 don't matter. The third could also be if the-    // first 30 were expanded at the beginning.-    //-    // Overall, the safer solution would be to avoid repeat branching, and have-    // a disclaimer about matching accuracy with repeated unbounded patterns-    // that overlap themselves.--    // \ testMatch("(a+)+","aaaaaaaaaaaaaaaaaaaaaaaa!",false)-    // \ testMatch("(a+)+","aaaaaaaaaaaaaaaaaaaaaaaa!",false)-    // \ testMatch("([a-zA-Z]+)*","aaaaaaaaaaaaaaaaaaaaaaaa!",false)-    // \ testMatch("(.*a){30}","aaaaaaaaaaaaaaaaaaaaaaaa!",false)-    \ testMatch("(a|aa)+","aaaaaaaaaaaaaaaaaaaaaaaa!",false)--    // This causes a ton of branching, but it actually finishes. Given m-    // unbounded groups in a sequence, the branching is O(n^m). Since n^m-    // increases rapidly with n, most sequence branches at any given time will-    // not contain repeat branches within them. The exception is that each of-    // the m groups will be in separate sequence branches from the beginning,-    // *adding* O(sqrt(2)^n) branches. This means the overall complexity is-    // O(n^m)+O(sqrt(2)^n): O(n^m) for smaller n, O(sqrt(2)^n) for larger n.-    \ testMatch("[ab]*[ac]*[ad]*[ae]*[af]*",-                "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaad",true)-  }--  @type testMatch (String,String,Bool) -> ()-  testMatch (pattern,data,expected) {-    MatcherTemplate<Char> template <- require(CharRegex$parse(pattern))-    \ LazyStream<Formatted>$new()-        .append("Trying: regex=\"")-        .append(pattern)-        .append("\" data=\"")-        .append(data)-        .append("\" -> ")-        .append(expected)-        .append("...\n")-        .writeTo(SimpleOutput$stderr())-    if (CharRegex$match(template,data) != expected) {-      fail("Incorrect match result!")-    }-  }-}
− example/regex/regex-test.0rt
@@ -1,1338 +0,0 @@-testcase "MatchSingle match" {-  success Test$execute()-}--concrete Test {-  @type execute () -> ()-}--define Test {-  execute () {-    // Pattern: "x"-    MatcherTemplate<Char> template <- MatchSingle<Char>$create('x')-    Matcher<Char> matcher <- template.newMatcher()--    if (template.matchesEmpty()) {-      fail("matches empty")-    }-    if (matcher.matchSatisfied()) {-      fail("match satisfied")-    }--    MatchState state <- matcher.tryNextMatch('x')-    if (!(state `MatchState$equals` MatchState$matchComplete())) {-      fail(state)-    }-    if (!matcher.matchSatisfied()) {-      fail("match not satisfied")-    }--    // Should only match once.-    state <- matcher.tryNextMatch('x')-    if (!(state `MatchState$equals` MatchState$matchFail())) {-      fail(state)-    }-    if (matcher.matchSatisfied()) {-      fail("match satisfied")-    }--    // Should match again after reset.-    matcher <- template.newMatcher()-    state <- matcher.tryNextMatch('x')-    if (!(state `MatchState$equals` MatchState$matchComplete())) {-      fail(state)-    }-  }-}---testcase "MatchSingle non-match" {-  success Test$execute()-}--concrete Test {-  @type execute () -> ()-}--define Test {-  execute () {-    // Pattern: "x"-    MatcherTemplate<Char> template <- MatchSingle<Char>$create('x')-    Matcher<Char> matcher <- template.newMatcher()--    MatchState state <- matcher.tryNextMatch('y')-    if (!(state `MatchState$equals` MatchState$matchFail())) {-      fail(state)-    }--    // Should keep failing to match.-    state <- matcher.tryNextMatch('x')-    if (!(state `MatchState$equals` MatchState$matchFail())) {-      fail(state)-    }-  }-}---testcase "MatchRange match" {-  success Test$execute()-}--concrete Test {-  @type execute () -> ()-}--define Test {-  execute () {-    // Pattern: "[w-y]"-    MatcherTemplate<Char> template <- MatchRange<Char>$create('w','y')-    Matcher<Char> matcher <- template.newMatcher()--    if (template.matchesEmpty()) {-      fail("matches empty")-    }-    if (matcher.matchSatisfied()) {-      fail("match satisfied")-    }--    MatchState state <- matcher.tryNextMatch('x')-    if (!(state `MatchState$equals` MatchState$matchComplete())) {-      fail(state)-    }-    if (!matcher.matchSatisfied()) {-      fail("match not satisfied")-    }--    // Should only match once.-    state <- matcher.tryNextMatch('x')-    if (!(state `MatchState$equals` MatchState$matchFail())) {-      fail(state)-    }-    if (matcher.matchSatisfied()) {-      fail("match satisfied")-    }--    // Should match again after reset.-    matcher <- template.newMatcher()-    state <- matcher.tryNextMatch('x')-    if (!(state `MatchState$equals` MatchState$matchComplete())) {-      fail(state)-    }-  }-}---testcase "MatchRange non-match" {-  success Test$execute()-}--concrete Test {-  @type execute () -> ()-}--define Test {-  execute () {-    // Pattern: "[w-y]"-    MatcherTemplate<Char> template <- MatchRange<Char>$create('w','y')-    Matcher<Char> matcher <- template.newMatcher()--    MatchState state <- matcher.tryNextMatch('a')-    if (!(state `MatchState$equals` MatchState$matchFail())) {-      fail(state)-    }--    // Should keep failing to match.-    state <- matcher.tryNextMatch('x')-    if (!(state `MatchState$equals` MatchState$matchFail())) {-      fail(state)-    }-  }-}---testcase "MatchAny" {-  success Test$execute()-}--concrete Test {-  @type execute () -> ()-}--define Test {-  execute () {-    // Pattern: "."-    MatcherTemplate<Char> template <- MatchAny$create()-    Matcher<Char> matcher <- template.newMatcher()--    if (template.matchesEmpty()) {-      fail("matches empty")-    }-    if (matcher.matchSatisfied()) {-      fail("match satisfied")-    }--    MatchState state <- matcher.tryNextMatch('x')-    if (!(state `MatchState$equals` MatchState$matchComplete())) {-      fail(state)-    }-    if (!matcher.matchSatisfied()) {-      fail("match not satisfied")-    }--    // Should only match once.-    state <- matcher.tryNextMatch('x')-    if (!(state `MatchState$equals` MatchState$matchFail())) {-      fail(state)-    }-    if (matcher.matchSatisfied()) {-      fail("match satisfied")-    }--    // Should match again after reset.-    matcher <- template.newMatcher()-    state <- matcher.tryNextMatch('x')-    if (!(state `MatchState$equals` MatchState$matchComplete())) {-      fail(state)-    }-  }-}---testcase "MatchEmpty" {-  success Test$execute()-}--concrete Test {-  @type execute () -> ()-}--define Test {-  execute () {-    // Pattern: ""-    MatcherTemplate<Char> template <- MatchEmpty$create()-    Matcher<Char> matcher <- template.newMatcher()--    if (!template.matchesEmpty()) {-      fail("doesn't match empty")-    }-    if (!matcher.matchSatisfied()) {-      fail("match not satisfied")-    }--    MatchState state <- matcher.tryNextMatch('x')-    if (!(state `MatchState$equals` MatchState$matchFail())) {-      fail(state)-    }-    if (matcher.matchSatisfied()) {-      fail("match satisfied")-    }--    // Should match again after reset.-    matcher <- template.newMatcher()-    if (!matcher.matchSatisfied()) {-      fail("match not satisfied")-    }-  }-}---testcase "MatchRepeat match" {-  success Test$execute()-}--concrete Test {-  @type execute () -> ()-}--define Test {-  execute () {-    // Pattern: "[w-y]{2,3}"-    MatcherTemplate<Char> template <--        MatchRepeat<Char>$createRange(2,3,MatchRange<Char>$create('w','y'))-    Matcher<Char> matcher <- template.newMatcher()--    if (template.matchesEmpty()) {-      fail("matches empty")-    }-    if (matcher.matchSatisfied()) {-      fail("match satisfied")-    }--    MatchState state <- MatchState$matchFail()--    // First character.-    state <- matcher.tryNextMatch('x')-    if (!(state `MatchState$equals` MatchState$matchContinue())) {-      fail(state)-    }-    if (matcher.matchSatisfied()) {-      fail("match satisfied")-    }--    // Second character.-    state <- matcher.tryNextMatch('y')-    if (!(state `MatchState$equals` MatchState$matchContinue())) {-      fail(state)-    }-    if (!matcher.matchSatisfied()) {-      fail("match not satisfied")-    }--    // Third character.-    state <- matcher.tryNextMatch('x')-    if (!(state `MatchState$equals` MatchState$matchComplete())) {-      fail(state)-    }--    // Fourth character.-    state <- matcher.tryNextMatch('x')-    if (!(state `MatchState$equals` MatchState$matchFail())) {-      fail(state)-    }-    if (!matcher.matchSatisfied()) {-      fail("match not satisfied")-    }--    // Reset to reuse the matcher.-    matcher <- template.newMatcher()--    // Fourth character.-    state <- matcher.tryNextMatch('w')-    if (!(state `MatchState$equals` MatchState$matchContinue())) {-      fail(state)-    }-    if (matcher.matchSatisfied()) {-      fail("match satisfied")-    }--    // Fifth character.-    state <- matcher.tryNextMatch('w')-    if (!(state `MatchState$equals` MatchState$matchContinue())) {-      fail(state)-    }-    if (!matcher.matchSatisfied()) {-      fail("match not satisfied")-    }-  }-}---testcase "MatchRepeat nested" {-  success Test$execute()-}--concrete Test {-  @type execute () -> ()-}--define Test {-  execute () {-    // Pattern: "(x*){2,}"-    MatcherTemplate<Char> template <--        MatchRepeat<Char>$createRange(2,0,-            MatchRepeat<Char>$createZeroPlus(MatchSingle<Char>$create('x')))-    Matcher<Char> matcher <- template.newMatcher()--    if (!template.matchesEmpty()) {-      fail("doesn't match empty")-    }-    if (!matcher.matchSatisfied()) {-      fail("match not satisfied")-    }--    MatchState state <- MatchState$matchFail()--    // First character.-    state <- matcher.tryNextMatch('x')-    if (!(state `MatchState$equals` MatchState$matchContinue())) {-      fail(state)-    }-    if (!matcher.matchSatisfied()) {-      fail("match not satisfied")-    }--    // Second character.-    state <- matcher.tryNextMatch('x')-    if (!(state `MatchState$equals` MatchState$matchContinue())) {-      fail(state)-    }-    if (!matcher.matchSatisfied()) {-      fail("match not satisfied")-    }--    // Third character.-    state <- matcher.tryNextMatch('x')-    if (!(state `MatchState$equals` MatchState$matchContinue())) {-      fail(state)-    }-    if (!matcher.matchSatisfied()) {-      fail("match not satisfied")-    }-  }-}---testcase "MatchRepeat non-match" {-  success Test$execute()-}--concrete Test {-  @type execute () -> ()-}--define Test {-  execute () {-    // Pattern: "[w-y]{2,3}"-    MatcherTemplate<Char> template <--        MatchRepeat<Char>$createRange(2,3,MatchRange<Char>$create('w','y'))-    Matcher<Char> matcher <- template.newMatcher()--    MatchState state <- MatchState$matchFail()--    // First character.-    state <- matcher.tryNextMatch('x')-    if (!(state `MatchState$equals` MatchState$matchContinue())) {-      fail(state)-    }-    if (matcher.matchSatisfied()) {-      fail("match satisfied")-    }--    // Second character.-    state <- matcher.tryNextMatch('q')-    if (!(state `MatchState$equals` MatchState$matchFail())) {-      fail(state)-    }-    if (matcher.matchSatisfied()) {-      fail("match satisfied")-    }--    // Should keep failing to match.-    state <- matcher.tryNextMatch('x')-    if (!(state `MatchState$equals` MatchState$matchFail())) {-      fail(state)-    }-  }-}---testcase "MatchRepeat optional suffix" {-  success Test$execute()-}--concrete Test {-  @type execute () -> ()-}--define Test {-  execute () {-    // Pattern: "(ab*)*"-    MatcherTemplate<Char> template <--        MatchRepeat<Char>$createZeroPlus(-            MatchBranches<Char>$create(-                BranchSequence<Char>$create(-                    LinkedNode<MatcherTemplate<Char>>$create(MatchSingle<Char>$create('a'),-                        LinkedNode<MatcherTemplate<Char>>$create(-                            MatchRepeat<Char>$createZeroPlus(MatchSingle<Char>$create('b')),empty)))))-    Matcher<Char> matcher <- template.newMatcher()--    if (!matcher.matchSatisfied()) {-      fail("match not satisfied")-    }--    MatchState state <- MatchState$matchFail()--    // First character.-    state <- matcher.tryNextMatch('a')-    if (!(state `MatchState$equals` MatchState$matchContinue())) {-      fail(state)-    }-    if (!matcher.matchSatisfied()) {-      fail("match not satisfied")-    }--    // Second character.-    state <- matcher.tryNextMatch('b')-    if (!(state `MatchState$equals` MatchState$matchContinue())) {-      fail(state)-    }-    if (!matcher.matchSatisfied()) {-      fail("match not satisfied")-    }--    // Third character.-    state <- matcher.tryNextMatch('a')-    if (!(state `MatchState$equals` MatchState$matchContinue())) {-      fail(state)-    }-    if (!matcher.matchSatisfied()) {-      fail("match not satisfied")-    }-  }-}---testcase "MatchChoices match" {-  success Test$execute()-}--concrete Test {-  @type execute () -> ()-}--define Test {-  execute () {-    // Pattern: "[w-y]|a*"-    MatcherTemplate<Char> template <--        MatchChoices<Char>$create(-            LinkedNode<MatcherTemplate<Char>>$create(MatchRange<Char>$create('w','y'),-                LinkedNode<MatcherTemplate<Char>>$create(-                    MatchBranches<Char>$create(-                        BranchRepeat<Char>$createZeroPlus(MatchSingle<Char>$create('a'))),empty)))-    Matcher<Char> matcher <- template.newMatcher()--    MatchState state <- MatchState$matchFail()--    if (!template.matchesEmpty()) {-      fail("doesn't match empty")-    }-    if (!matcher.matchSatisfied()) {-      fail("match not satisfied")-    }--    // First character.-    state <- matcher.tryNextMatch('a')-    if (!(state `MatchState$equals` MatchState$matchContinue())) {-      fail(state)-    }--    // Second character.-    state <- matcher.tryNextMatch('a')-    if (!(state `MatchState$equals` MatchState$matchContinue())) {-      fail(state)-    }--    // Third character.-    state <- matcher.tryNextMatch('x')-    if (!(state `MatchState$equals` MatchState$matchFail())) {-      fail(state)-    }--    // Reset to reuse the matcher.-    matcher <- template.newMatcher()--    // First character.-    state <- matcher.tryNextMatch('x')-    if (!(state `MatchState$equals` MatchState$matchComplete())) {-      fail(state)-    }-  }-}---testcase "MatchChoices non-match" {-  success Test$execute()-}--concrete Test {-  @type execute () -> ()-}--define Test {-  execute () {-    // Pattern: "[w-y]|a*"-    MatcherTemplate<Char> template <--        MatchChoices<Char>$create(-            LinkedNode<MatcherTemplate<Char>>$create(MatchRange<Char>$create('w','y'),-                LinkedNode<MatcherTemplate<Char>>$create(-                    MatchBranches<Char>$create(-                        BranchRepeat<Char>$createZeroPlus(MatchSingle<Char>$create('a'))),empty)))-    Matcher<Char> matcher <- template.newMatcher()--    if (!template.matchesEmpty()) {-      fail("doesn't match empty")-    }-    if (!matcher.matchSatisfied()) {-      fail("match not satisfied")-    }--    MatchState state <- MatchState$matchFail()--    // First character.-    state <- matcher.tryNextMatch('a')-    if (!(state `MatchState$equals` MatchState$matchContinue())) {-      fail(state)-    }--    // Should keep failing to match.-    state <- matcher.tryNextMatch('x')-    if (!(state `MatchState$equals` MatchState$matchFail())) {-      fail(state)-    }-  }-}---testcase "MatchChoices empty choice" {-  success Test$execute()-}--concrete Test {-  @type execute () -> ()-}--define Test {-  execute () {-    // Pattern: "[w-y]|a{2,2}|"-    MatcherTemplate<Char> template <--        MatchChoices<Char>$create(-            LinkedNode<MatcherTemplate<Char>>$create(MatchRange<Char>$create('w','y'),-                LinkedNode<MatcherTemplate<Char>>$create(-                    MatchBranches<Char>$create(-                        BranchRepeat<Char>$createRange(2,2,MatchSingle<Char>$create('a'))),-                    LinkedNode<MatcherTemplate<Char>>$create(-                        MatchEmpty$create(),empty))))-    Matcher<Char> matcher <- template.newMatcher()--    if (!template.matchesEmpty()) {-      fail("doesn't match empty")-    }-    if (!matcher.matchSatisfied()) {-      fail("match not satisfied")-    }--    MatchState state <- MatchState$matchFail()--    // First character.-    state <- matcher.tryNextMatch('a')-    if (!(state `MatchState$equals` MatchState$matchContinue())) {-      fail(state)-    }-    if (matcher.matchSatisfied()) {-      fail("match satisfied")-    }-  }-}---testcase "BranchRepeat match" {-  success Test$execute()-}--concrete Test {-  @type execute () -> ()-}--define Test {-  execute () {-    // Pattern: "[w-y]{2,3}"-    MatchBrancherTemplate<Char> template <--        BranchRepeat<Char>$createRange(2,3,MatchRange<Char>$create('w','y'))-    MatchBrancher<Char> brancher <- template.newBrancher()--    if (template.matchesEmpty()) {-      fail("matches empty")-    }-    if (brancher.matchSatisfied()) {-      fail("match satisfied")-    }--    MatchState state <- MatchState$matchFail()-    optional ReadSequence<MatchBrancher<Char>> branches <- empty--    // First character.-    state, branches <- brancher.tryBranches('x')-    if (!(state `MatchState$equals` MatchState$matchContinue())) {-      fail(state)-    }-    if (!present(branches)) {-      fail("branch missing")-    }--    brancher <- require(branches).value()-    if (brancher.matchSatisfied()) {-      fail("match satisfied")-    }-    branches <- require(branches).next()-    if (present(branches)) {-      fail("branch present")-    }--    // Second character.-    state, branches <- brancher.tryBranches('y')-    if (!(state `MatchState$equals` MatchState$matchContinue())) {-      fail(state)-    }-    if (!present(branches)) {-      fail("branch missing")-    }-    if (!brancher.matchSatisfied()) {-      fail("match not satisfied")-    }--    brancher <- require(branches).value()-    branches <- require(branches).next()-    if (present(branches)) {-      fail("branch present")-    }--    // Third character.-    state, branches <- brancher.tryBranches('x')-    if (!(state `MatchState$equals` MatchState$matchComplete())) {-      fail(state)-    }-    if (present(branches)) {-      fail("branch present")-    }--    // Reset to reuse the brancher.-    brancher <- template.newBrancher()--    // Fourth character.-    state, branches <- brancher.tryBranches('w')-    if (!(state `MatchState$equals` MatchState$matchContinue())) {-      fail(state)-    }-    if (!present(branches)) {-      fail("branch missing")-    }-  }-}---testcase "BranchRepeat nested" {-  success Test$execute()-}--concrete Test {-  @type execute () -> ()-}--define Test {-  execute () {-    // Pattern: "(x*){2,}"-    MatchBrancherTemplate<Char> template <--        BranchRepeat<Char>$createRange(2,0,-            MatchBranches<Char>$create(-                BranchRepeat<Char>$createZeroPlus(MatchSingle<Char>$create('x'))))-    MatchBrancher<Char> brancher <- template.newBrancher()--    if (!template.matchesEmpty()) {-      fail("doesn't match empty")-    }-    if (!brancher.matchSatisfied()) {-      fail("match not satisfied")-    }--    MatchState state <- MatchState$matchFail()-    optional ReadSequence<MatchBrancher<Char>> branches <- empty--    // First character.-    state, branches <- brancher.tryBranches('x')-    if (!(state `MatchState$equals` MatchState$matchContinue())) {-      fail(state)-    }-    if (!present(branches)) {-      fail("branch missing")-    }--    brancher <- require(branches).value()-    if (!brancher.matchSatisfied()) {-      fail("match not satisfied")-    }-    branches <- require(branches).next()-    if (present(branches)) {-      fail("branch present")-    }--    // Second character.-    state, branches <- brancher.tryBranches('x')-    if (!(state `MatchState$equals` MatchState$matchContinue())) {-      fail(state)-    }-    if (!present(branches)) {-      fail("branch missing")-    }--    brancher <- require(branches).value()-    if (!brancher.matchSatisfied()) {-      fail("match not satisfied")-    }-    // Expect one additional branch to continue "xx*".-    branches <- require(branches).next()-    if (!present(branches)) {-      fail("branch missing")-    }--    // Third character, branch 1.-    state, _ <- brancher.tryBranches('x')-    if (!(state `MatchState$equals` MatchState$matchContinue())) {-      fail(state)-    }--    brancher <- require(branches).value()-    if (!brancher.matchSatisfied()) {-      fail("match not satisfied")-    }-    // No more branches expected.-    branches <- require(branches).next()-    if (present(branches)) {-      fail("branch present")-    }--    // Third character, branch 2.-    state, _ <- brancher.tryBranches('x')-    if (!(state `MatchState$equals` MatchState$matchContinue())) {-      fail(state)-    }-  }-}---testcase "BranchRepeat non-match" {-  success Test$execute()-}--concrete Test {-  @type execute () -> ()-}--define Test {-  execute () {-    // Pattern: "[w-y]{2,3}"-    MatchBrancherTemplate<Char> template <--        BranchRepeat<Char>$createRange(2,3,MatchRange<Char>$create('w','y'))-    MatchBrancher<Char> brancher <- template.newBrancher()--    MatchState state <- MatchState$matchFail()-    optional ReadSequence<MatchBrancher<Char>> branches <- empty--    // First character.-    state, branches <- brancher.tryBranches('x')-    if (!(state `MatchState$equals` MatchState$matchContinue())) {-      fail(state)-    }-    if (!present(branches)) {-      fail("branch missing")-    }--    brancher <- require(branches).value()-    if (brancher.matchSatisfied()) {-      fail("match satisfied")-    }-    branches <- require(branches).next()-    if (present(branches)) {-      fail("branch present")-    }--    // Second character.-    state, branches <- brancher.tryBranches('q')-    if (!(state `MatchState$equals` MatchState$matchFail())) {-      fail(state)-    }-    if (present(branches)) {-      fail("branch present")-    }-  }-}---testcase "BranchSequence match" {-  success Test$execute()-}--concrete Test {-  @type execute () -> ()-}--define Test {-  execute () {-    // Pattern: "[w-y]a"-    MatchBrancherTemplate<Char> template <--        BranchSequence<Char>$create(-            LinkedNode<MatcherTemplate<Char>>$create(MatchRange<Char>$create('w','y'),-                LinkedNode<MatcherTemplate<Char>>$create(MatchSingle<Char>$create('a'),empty)))-    MatchBrancher<Char> brancher <- template.newBrancher()--    if (template.matchesEmpty()) {-      fail("matches empty")-    }-    if (brancher.matchSatisfied()) {-      fail("match satisfied")-    }--    MatchState state <- MatchState$matchFail()-    optional ReadSequence<MatchBrancher<Char>> branches <- empty--    // First character.-    state, branches <- brancher.tryBranches('x')-    if (!(state `MatchState$equals` MatchState$matchContinue())) {-      fail(state)-    }-    if (!present(branches)) {-      fail("branch missing")-    }--    brancher <- require(branches).value()-    branches <- require(branches).next()-    if (present(branches)) {-      fail("branch present")-    }--    // Second character.-    state, branches <- brancher.tryBranches('a')-    if (!(state `MatchState$equals` MatchState$matchComplete())) {-      fail(state)-    }-    if (!present(branches)) {-      fail("branch missing")-    }--    // The final branch should just be a record of completeness.-    if (!require(branches).value().matchSatisfied()) {-      fail("match not satisfied")-    }-    state, branches <- require(branches).value().tryBranches('a')-    if (!(state `MatchState$equals` MatchState$matchFail())) {-      fail(state)-    }--    // Reset to reuse the brancher.-    brancher <- template.newBrancher()--    // Third character.-    state, branches <- brancher.tryBranches('y')-    if (!(state `MatchState$equals` MatchState$matchContinue())) {-      fail(state)-    }-    if (!present(branches)) {-      fail("branch missing")-    }-  }-}---testcase "BranchSequence non-match" {-  success Test$execute()-}--concrete Test {-  @type execute () -> ()-}--define Test {-  execute () {-    // Pattern: "[w-y]a"-    MatchBrancherTemplate<Char> template <--        BranchSequence<Char>$create(-            LinkedNode<MatcherTemplate<Char>>$create(MatchRange<Char>$create('w','y'),-                LinkedNode<MatcherTemplate<Char>>$create(MatchSingle<Char>$create('a'),empty)))-    MatchBrancher<Char> brancher <- template.newBrancher()--    MatchState state <- MatchState$matchFail()-    optional ReadSequence<MatchBrancher<Char>> branches <- empty--    // First character.-    state, branches <- brancher.tryBranches('q')-    if (!(state `MatchState$equals` MatchState$matchFail())) {-      fail(state)-    }-    if (present(branches)) {-      fail("branch present")-    }--    // Should keep failing to match.-    state, branches <- brancher.tryBranches('x')-    if (!(state `MatchState$equals` MatchState$matchFail())) {-      fail(state)-    }-  }-}---testcase "BranchSequence skip empty failed" {-  success Test$execute()-}--concrete Test {-  @type execute () -> ()-}--define Test {-  execute () {-    // Pattern: "a*b"-    MatchBrancherTemplate<Char> template <--        BranchSequence<Char>$create(-            LinkedNode<MatcherTemplate<Char>>$create(-                MatchBranches<Char>$create(-                    BranchRepeat<Char>$createZeroPlus(MatchSingle<Char>$create('a'))),-                LinkedNode<MatcherTemplate<Char>>$create(-                    MatchSingle<Char>$create('b'),empty)))-    MatchBrancher<Char> brancher <- template.newBrancher()--    if (template.matchesEmpty()) {-      fail("matches empty")-    }-    if (brancher.matchSatisfied()) {-      fail("match satisfied")-    }--    MatchState state <- MatchState$matchFail()-    optional ReadSequence<MatchBrancher<Char>> branches <- empty--    // First character.-    state, branches <- brancher.tryBranches('b')-    if (!(state `MatchState$equals` MatchState$matchComplete())) {-      fail(state)-    }-    if (!present(branches)) {-      fail("branch missing")-    }-  }-}---testcase "BranchSequence fail non-empty field" {-  success Test$execute()-}--concrete Test {-  @type execute () -> ()-}--define Test {-  execute () {-    // Pattern: "a+b"-    MatchBrancherTemplate<Char> template <--        BranchSequence<Char>$create(-            LinkedNode<MatcherTemplate<Char>>$create(-                MatchBranches<Char>$create(-                    BranchRepeat<Char>$createOnePlus(MatchSingle<Char>$create('a'))),-                LinkedNode<MatcherTemplate<Char>>$create(-                    MatchSingle<Char>$create('b'),empty)))-    MatchBrancher<Char> brancher <- template.newBrancher()--    if (template.matchesEmpty()) {-      fail("matches empty")-    }-    if (brancher.matchSatisfied()) {-      fail("match satisfied")-    }--    MatchState state <- MatchState$matchFail()-    optional ReadSequence<MatchBrancher<Char>> branches <- empty--    // First character.-    state, branches <- brancher.tryBranches('b')-    if (!(state `MatchState$equals` MatchState$matchFail())) {-      fail(state)-    }-    if (present(branches)) {-      fail("branch present")-    }-  }-}---testcase "BranchSequence branch sequence multi-match" {-  success Test$execute()-}--concrete Test {-  @type execute () -> ()-}--define Test {-  execute () {-    // Pattern: "a*a{0,2}"-    MatchBrancherTemplate<Char> template <--        BranchSequence<Char>$create(-            LinkedNode<MatcherTemplate<Char>>$create(-                MatchBranches<Char>$create(-                    BranchRepeat<Char>$createZeroPlus(MatchSingle<Char>$create('a'))),-                LinkedNode<MatcherTemplate<Char>>$create(-                    MatchBranches<Char>$create(-                        BranchRepeat<Char>$createRange(0,2,MatchSingle<Char>$create('a'))),empty)))-    MatchBrancher<Char> brancher <- template.newBrancher()--    if (!template.matchesEmpty()) {-      fail("doesn't match empty")-    }-    if (!brancher.matchSatisfied()) {-      fail("match not satisfied")-    }--    MatchState state <- MatchState$matchFail()-    optional ReadSequence<MatchBrancher<Char>> branches <- empty--    // First character.-    state, branches <- brancher.tryBranches('a')-    if (!(state `MatchState$equals` MatchState$matchContinue())) {-      fail(state)-    }-    if (!present(branches)) {-      fail("branch missing")-    }--    brancher <- require(branches).value()-    branches <- require(branches).next()-    if (!present(branches)) {-      fail("branch missing")-    }--    // TODO: This assumes that the branches are ordered from longest to shortest-    // remaining sequence.--    // Second character, branch 1.-    state, _ <- brancher.tryBranches('a')-    if (!(state `MatchState$equals` MatchState$matchContinue())) {-      fail(state)-    }--    brancher <- require(branches).value()-    branches <- require(branches).next()-    if (present(branches)) {-      fail("branch present")-    }--    // Second character, branch 2.-    state, _ <- brancher.tryBranches('a')-    if (!(state `MatchState$equals` MatchState$matchComplete())) {-      fail(state)-    }-  }-}---testcase "MatchBranches match" {-  success Test$execute()-}--concrete Test {-  @type execute () -> ()-}--define Test {-  execute () {-    // Pattern: "[w-y]a"-    MatcherTemplate<Char> template <--        MatchBranches<Char>$create(-            BranchSequence<Char>$create(-                LinkedNode<MatcherTemplate<Char>>$create(MatchRange<Char>$create('w','y'),-                    LinkedNode<MatcherTemplate<Char>>$create(MatchSingle<Char>$create('a'),empty))))-    Matcher<Char> matcher <- template.newMatcher()--    if (template.matchesEmpty()) {-      fail("matches empty")-    }-    if (matcher.matchSatisfied()) {-      fail("match satisfied")-    }--    MatchState state <- MatchState$matchFail()--    // First character.-    state <- matcher.tryNextMatch('x')-    if (!(state `MatchState$equals` MatchState$matchContinue())) {-      fail(state)-    }-    if (matcher.matchSatisfied()) {-      fail("match satisfied")-    }--    // Second character.-    state <- matcher.tryNextMatch('a')-    if (!(state `MatchState$equals` MatchState$matchComplete())) {-      fail(state)-    }-    if (!matcher.matchSatisfied()) {-      fail("match not satisfied")-    }--    // Reset to reuse the matcher.-    matcher <- template.newMatcher()--    // Third character.-    state <- matcher.tryNextMatch('y')-    if (!(state `MatchState$equals` MatchState$matchContinue())) {-      fail(state)-    }-  }-}---testcase "MatchBranches non-match" {-  success Test$execute()-}--concrete Test {-  @type execute () -> ()-}--define Test {-  execute () {-    // Pattern: "[w-y]a"-    MatcherTemplate<Char> template <--        MatchBranches<Char>$create(-            BranchSequence<Char>$create(-                LinkedNode<MatcherTemplate<Char>>$create(MatchRange<Char>$create('w','y'),-                    LinkedNode<MatcherTemplate<Char>>$create(MatchSingle<Char>$create('a'),empty))))-    Matcher<Char> matcher <- template.newMatcher()--    if (template.matchesEmpty()) {-      fail("matches empty")-    }-    if (matcher.matchSatisfied()) {-      fail("match satisfied")-    }--    MatchState state <- MatchState$matchFail()--    // First character.-    state <- matcher.tryNextMatch('x')-    if (!(state `MatchState$equals` MatchState$matchContinue())) {-      fail(state)-    }-    if (matcher.matchSatisfied()) {-      fail("match satisfied")-    }--    // Second character.-    state <- matcher.tryNextMatch('q')-    if (!(state `MatchState$equals` MatchState$matchFail())) {-      fail(state)-    }-    if (matcher.matchSatisfied()) {-      fail("match satisfied")-    }--    // Should keep failing to match.-    state <- matcher.tryNextMatch('y')-    if (!(state `MatchState$equals` MatchState$matchFail())) {-      fail(state)-    }-  }-}---testcase "MatchBranches branched match" {-  success Test$execute()-}--concrete Test {-  @type execute () -> ()-}--define Test {-  execute () {-    // Pattern: "ab"-    MatcherTemplate<Char> templateAB <--        MatchBranches<Char>$create(-            BranchSequence<Char>$create(-                LinkedNode<MatcherTemplate<Char>>$create(MatchSingle<Char>$create('a'),-                    LinkedNode<MatcherTemplate<Char>>$create(MatchSingle<Char>$create('b'),empty))))-    // Pattern: "ac"-    MatcherTemplate<Char> templateAC <--        MatchBranches<Char>$create(-            BranchSequence<Char>$create(-                LinkedNode<MatcherTemplate<Char>>$create(MatchSingle<Char>$create('a'),-                    LinkedNode<MatcherTemplate<Char>>$create(MatchSingle<Char>$create('c'),empty))))-    // Pattern: "(ab)*ac"-    MatcherTemplate<Char> template <--        MatchBranches<Char>$create(-            BranchSequence<Char>$create(-                LinkedNode<MatcherTemplate<Char>>$create(-                    MatchBranches<Char>$create(-                        BranchRepeat<Char>$createZeroPlus(templateAB)),-                    LinkedNode<MatcherTemplate<Char>>$create(templateAC,empty))))-    Matcher<Char> matcher <- template.newMatcher()--    MatchState state <- MatchState$matchFail()--    // First character.-    state <- matcher.tryNextMatch('a')-    if (!(state `MatchState$equals` MatchState$matchContinue())) {-      fail(state)-    }-    if (matcher.matchSatisfied()) {-      fail("match satisfied")-    }--    // Second character.-    state <- matcher.tryNextMatch('c')-    if (!(state `MatchState$equals` MatchState$matchComplete())) {-      fail(state)-    }-    if (!matcher.matchSatisfied()) {-      fail("match not satisfied")-    }--    // Reset to reuse the matcher.-    matcher <- template.newMatcher()--    // First character.-    state <- matcher.tryNextMatch('a')-    if (!(state `MatchState$equals` MatchState$matchContinue())) {-      fail(state)-    }-    if (matcher.matchSatisfied()) {-      fail("match satisfied")-    }--    // Second character.-    state <- matcher.tryNextMatch('b')-    if (!(state `MatchState$equals` MatchState$matchContinue())) {-      fail(state)-    }-    if (matcher.matchSatisfied()) {-      fail("match satisfied")-    }-  }-}---testcase "MatchBranches partial repeat" {-  success Test$execute()-}--concrete Test {-  @type execute () -> ()-}--define Test {-  execute () {-    // Pattern: "(ab)*"-    MatcherTemplate<Char> template <--        MatchBranches<Char>$create(-            BranchRepeat<Char>$createZeroPlus(-                MatchBranches<Char>$create(-                    BranchSequence<Char>$create(-                        LinkedNode<MatcherTemplate<Char>>$create(MatchSingle<Char>$create('a'),-                            LinkedNode<MatcherTemplate<Char>>$create(MatchSingle<Char>$create('b'),empty))))))-    Matcher<Char> matcher <- template.newMatcher()--    if (!matcher.matchSatisfied()) {-      fail("match not satisfied")-    }--    MatchState state <- MatchState$matchFail()--    // First character.-    state <- matcher.tryNextMatch('a')-    if (!(state `MatchState$equals` MatchState$matchContinue())) {-      fail(state)-    }-    if (matcher.matchSatisfied()) {-      fail("match satisfied")-    }--    // Second character.-    state <- matcher.tryNextMatch('b')-    if (!(state `MatchState$equals` MatchState$matchContinue())) {-      fail(state)-    }-    if (!matcher.matchSatisfied()) {-      fail("match not satisfied")-    }-  }-}
− example/regex/regex.0rp
@@ -1,127 +0,0 @@-concrete MatchState {-  refines Formatted-  defines Equals<MatchState>-  defines LessThan<MatchState>--  // Ordered from low to high.--  @type matchFail     () -> (MatchState) // Matching has failed.-  @type matchComplete () -> (MatchState) // End of the pattern.-  @type matchContinue () -> (MatchState) // Matching must continue.-}--@value interface Matcher<#c|> {-  // Returns true if the pattern is complete without additional data.-  matchSatisfied () -> (Bool)--  // This can be called as long as it returns matchContinue(). The matcher does-  // not branch; each call updates it with the next data point in the input.-  //-  // Returns:-  // - matchFail(): The pattern cannot be completed.-  // - matchContinue(): Matching can continue; check matchSatisfied() to see if-  //                    more data is required to complete the pattern.-  // - matchComplete(): The pattern has been completed and cannot match any-  //                    additional data.-  tryNextMatch (#c) -> (MatchState)-}--@value interface MatcherTemplate<#c|> {-  // Returns true if the pattern can match an empty sequence.-  matchesEmpty () -> (Bool)-  newMatcher () -> (Matcher<#c>)-}--@value interface MatchBrancher<#c|> {-  // Returns true if the pattern is complete without additional data.-  matchSatisfied () -> (Bool)--  // NOTE: Calling this invalidates the MatchBrancher. Subsequent calls should-  // be done against the returned branches.-  // Returns:-  // - state: The highest state of the branches checked.-  // - branches: All branches with a status of matchContinue().-  tryBranches (#c) -> (MatchState /*state*/,-                       optional ReadSequence<MatchBrancher<#c>> /*branches*/)-}--@value interface MatchBrancherTemplate<#c|> {-  // Returns true if the pattern can match an empty sequence.-  matchesEmpty () -> (Bool)-  newBrancher () -> (MatchBrancher<#c>)-}--concrete MatchSingle<#c> {-  #c defines Equals<#c>--  refines MatcherTemplate<#c>--  @type create (#c) -> (MatchSingle<#c>)-}--concrete MatchRange<#c> {-  #c defines LessThan<#c>--  refines MatcherTemplate<#c>--  @type create (#c,#c) -> (MatchRange<#c>)-}--concrete MatchAny {-  refines MatcherTemplate<any>--  @type create () -> (MatchAny)-}--concrete MatchEmpty {-  refines MatcherTemplate<any>--  @type create () -> (MatchEmpty)-}--concrete MatchRepeat<#c> {-  refines MatcherTemplate<#c>--  @type createZeroPlus (MatcherTemplate<#c>) -> (MatchRepeat<#c>)-  @type createOnePlus (MatcherTemplate<#c>) -> (MatchRepeat<#c>)-  @type createRange (Int,Int,MatcherTemplate<#c>) -> (MatchRepeat<#c>)-}--concrete MatchChoices<#c> {-  refines MatcherTemplate<#c>--  @type create (optional ReadSequence<MatcherTemplate<#c>>) -> (MatchChoices<#c>)-}--concrete MatchBranches<#c> {-  refines MatcherTemplate<#c>--  @type create (MatchBrancherTemplate<#c>) -> (MatcherTemplate<#c>)-}--concrete BranchRepeat<#c> {-  refines MatchBrancherTemplate<#c>--  @type createZeroPlus (MatcherTemplate<#c>) -> (BranchRepeat<#c>)-  @type createOnePlus (MatcherTemplate<#c>) -> (BranchRepeat<#c>)-  @type createRange (Int,Int,MatcherTemplate<#c>) -> (BranchRepeat<#c>)-}--concrete BranchSequence<#c> {-  refines MatchBrancherTemplate<#c>--  @type create (optional ReadSequence<MatcherTemplate<#c>>) -> (BranchSequence<#c>)-}--@value interface ReadSequence<|#c> {-  value () -> (#c)-  next () -> (optional ReadSequence<#c>)-}--concrete LinkedNode<#x> {-  refines ReadSequence<#x>--  @type create (#x,optional ReadSequence<#x>) -> (ReadSequence<#x>)-  @type concatSequences (optional ReadSequence<#x>,optional ReadSequence<#x>) ->-                        (optional ReadSequence<#x>)-}
− example/regex/regex.0rx
@@ -1,724 +0,0 @@-define MatchState {-  @value Int enum-  @value String name-  @category MatchState matchFailVal     <- MatchState{ 0, "matchFail" }-  @category MatchState matchCompleteVal <- MatchState{ 1, "matchComplete" }-  @category MatchState matchContinueVal <- MatchState{ 2, "matchContinue" }--  formatted () {-    return "MatchState$" + name + "()"-  }--  equals (x,y) {-    return x.getEnum() == y.getEnum()-  }--  lessThan (x,y) {-    return x.getEnum() < y.getEnum()-  }--  matchFail () {-    return matchFailVal-  }--  matchComplete () {-    return matchCompleteVal-  }--  matchContinue () {-    return matchContinueVal-  }--  @value getEnum () -> (Int)-  getEnum () {-    return enum-  }-}--define MatchSingle {-  @value #c match--  create (match) {-    return MatchSingle<#c>{ match }-  }--  matchesEmpty () {-    return false-  }--  newMatcher () {-    return MatchSingleMatcher<#c>$create(match)-  }-}--concrete MatchSingleMatcher<#c> {-  #c defines Equals<#c>--  refines Matcher<#c>--  @type create (#c) -> (MatchSingleMatcher<#c>)-}--define MatchSingleMatcher {-  @value #c match-  @value MatchState state--  create (match) {-    return MatchSingleMatcher<#c>{ match, MatchState$matchContinue() }-  }--  matchSatisfied () {-    return state `MatchState$equals` MatchState$matchComplete()-  }--  tryNextMatch (data) {-    if (!(state `MatchState$equals` MatchState$matchContinue())) {-      return (state <- MatchState$matchFail())-    }-    if (data `#c$equals` match) {-      return (state <- MatchState$matchComplete())-    } else {-      return (state <- MatchState$matchFail())-    }-  }-}--define MatchRange {-  @value #c minMatch-  @value #c maxMatch--  create (minMatch,maxMatch) {-    return MatchRange<#c>{ minMatch, maxMatch }-  }--  matchesEmpty () {-    return false-  }--  newMatcher () {-    return MatchRangeMatcher<#c>$create(minMatch,maxMatch)-  }-}--concrete MatchRangeMatcher<#c> {-  #c defines LessThan<#c>--  refines Matcher<#c>--  @type create (#c,#c) -> (MatchRangeMatcher<#c>)-}--define MatchRangeMatcher {-  @value #c minMatch-  @value #c maxMatch-  @value MatchState state--  create (minMatch,maxMatch) {-    return MatchRangeMatcher<#c>{ minMatch, maxMatch, MatchState$matchContinue() }-  }--  matchSatisfied () {-    return state `MatchState$equals` MatchState$matchComplete()-  }--  tryNextMatch (data) {-    if (!(state `MatchState$equals` MatchState$matchContinue())) {-      return (state <- MatchState$matchFail())-    }-    if (!(data `#c$lessThan` minMatch) && !(maxMatch `#c$lessThan` data)) {-      return (state <- MatchState$matchComplete())-    } else {-      return (state <- MatchState$matchFail())-    }-  }-}--define MatchAny {-  create () {-    return MatchAny{ }-  }--  matchesEmpty () {-    return false-  }--  newMatcher () {-    return MatchAnyMatcher$create()-  }-}--concrete MatchAnyMatcher {-  refines Matcher<any>--  @type create () -> (MatchAnyMatcher)-}--define MatchAnyMatcher {-  @value MatchState state--  create () {-    return MatchAnyMatcher{ MatchState$matchContinue() }-  }--  matchSatisfied () {-    return state `MatchState$equals` MatchState$matchComplete()-  }--  tryNextMatch (data) {-    if (!(state `MatchState$equals` MatchState$matchContinue())) {-      return (state <- MatchState$matchFail())-    }-    return (state <- MatchState$matchComplete())-  }-}--define MatchEmpty {-  create () {-    return MatchEmpty{ }-  }--  matchesEmpty () {-    return true-  }--  newMatcher () {-    return MatchEmptyMatcher$create()-  }-}--concrete MatchEmptyMatcher {-  refines Matcher<any>--  @type create () -> (MatchEmptyMatcher)-}--define MatchEmptyMatcher {-  @value Bool failed--  create () {-    return MatchEmptyMatcher{ false }-  }--  matchSatisfied () {-    return !failed-  }--  tryNextMatch (data) {-    failed <- true-    return MatchState$matchFail()-  }-}--define MatchRepeat {-  @value MatcherTemplate<#c> template-  @value Int minCount-  @value Int maxCount--  createZeroPlus (template) {-    return createRange(0,0,template)-  }--  createOnePlus (template) {-    return createRange(1,0,template)-  }--  createRange (minCount,maxCount,template) {-    return MatchRepeat<#c>{ template, minCount, maxCount }-  }--  matchesEmpty () {-    return minCount <= 0 || template.matchesEmpty()-  }--  newMatcher () {-    return MatchRepeatMatcher<#c>$create(minCount,maxCount,template)-  }-}--concrete MatchRepeatMatcher<#c> {-  refines Matcher<#c>--  @type create (Int,Int,MatcherTemplate<#c>) -> (MatchRepeatMatcher<#c>)-}--define MatchRepeatMatcher {-  @value MatcherTemplate<#c> template-  @value Matcher<#c> matcher-  @value Int minCount-  @value Int maxCount // 0 means unlimited.-  @value Int repetitionCount-  @value MatchState lastState--  create (minCount,maxCount,template) {-    return MatchRepeatMatcher<#c>{ template, template.newMatcher(), minCount,-                                   maxCount, 0, MatchState$matchComplete() }-  }--  matchSatisfied () {-    if (lastState `MatchState$equals` MatchState$matchFail()) {-      return false-    }-    if (lastState `MatchState$equals` MatchState$matchContinue() &&-        (repetitionCount+1 >= minCount || template.matchesEmpty()) &&-        matcher.matchSatisfied()) {-      // The rest of the current repetition can be ignored.-      return true-    }-    if (lastState `MatchState$equals` MatchState$matchComplete() &&-        (repetitionCount >= minCount || template.matchesEmpty())) {-      return true-    }-    return false-  }--  tryNextMatch (data) (state) {-    if (atMax() || lastState `MatchState$equals` MatchState$matchFail()) {-      return MatchState$matchFail()-    }-    Bool canSkip <- matcher.matchSatisfied()-    state <- matcher.tryNextMatch(data)-    if (state `MatchState$equals` MatchState$matchFail() &&-        lastState `MatchState$equals` MatchState$matchContinue() && canSkip) {-      // Handle the case where an optional end to the pattern can be skipped.-      \ incrementMatch()-      \ startRepeat()-      state <- matcher.tryNextMatch(data)-    }-    lastState <- state-    if (state `MatchState$equals` MatchState$matchComplete()) {-      \ incrementMatch()-      \ startRepeat()-      if (atMax()) {-        state <- MatchState$matchComplete()-      } else {-        state <- MatchState$matchContinue()-      }-    }-  }--  @value incrementMatch () -> ()-  incrementMatch () {-    repetitionCount <- repetitionCount+1-  }--  @value atMax () -> (Bool)-  atMax () {-    return maxCount > 0 && repetitionCount >= maxCount-  }--  @value startRepeat () -> ()-  startRepeat () {-    matcher <- template.newMatcher()-  }-}--define MatchChoices {-  @value optional ReadSequence<MatcherTemplate<#c>> choices--  create (choices) {-    return MatchChoices<#c>{ choices }-  }--  matchesEmpty () {-    scoped {-      optional ReadSequence<MatcherTemplate<#c>> current <- choices-    } in while (present(current)) {-      if (require(current).value().matchesEmpty()) {-        return true-      }-    } update {-      current <- require(current).next()-    }-    return false-  }--  newMatcher () {-    return MatchChoicesMatcher<#c>$create(recursiveNewMatcher(choices))-  }--  @type recursiveNewMatcher (optional ReadSequence<MatcherTemplate<#c>>) ->-                            (optional ReadSequence<Matcher<#c>>)-  recursiveNewMatcher (choices) {-    if (!present(choices)) {-      return empty-    }-    return LinkedNode<Matcher<#c>>$create(require(choices).value().newMatcher(),-                                          recursiveNewMatcher(require(choices).next()))-  }-}--concrete MatchChoicesMatcher<#c> {-  refines Matcher<#c>--  @type create (optional ReadSequence<Matcher<#c>>) -> (MatchChoicesMatcher<#c>)-}--define MatchChoicesMatcher {-  @value optional ReadSequence<Matcher<#c>> choices--  create (choices) {-    return MatchChoicesMatcher<#c>{ choices }-  }--  matchSatisfied () {-    scoped {-      optional ReadSequence<Matcher<#c>> current <- choices-    } in while (present(current)) {-      if (require(current).value().matchSatisfied()) {-        return true-      }-    } update {-      current <- require(current).next()-    }-    return false-  }--  tryNextMatch (data) (state) {-    state <- MatchState$matchFail()-    scoped {-      optional ReadSequence<Matcher<#c>> current <- choices-    } in while (present(current)) {-      // NOTE: Failing matchers are left in for simplicity, under the assumption-      // that they will keep failing.-      MatchState state2 <- require(current).value().tryNextMatch(data)-      if (state `MatchState$lessThan` state2) {-        state <- state2-      }-    } update {-      current <- require(current).next()-    }-  }-}--define MatchBranches {-  @value MatchBrancherTemplate<#c> template--  create (template) {-    return MatchBranches<#c>{ template }-  }--  matchesEmpty () {-    return template.matchesEmpty()-  }--  newMatcher () {-    return MatchBranchesMatcher<#c>$create(template.newBrancher())-  }-}--concrete MatchBranchesMatcher<#c> {-  refines Matcher<#c>--  @type create (MatchBrancher<#c>) -> (MatchBranchesMatcher<#c>)-}--define MatchBranchesMatcher {-  @value optional ReadSequence<MatchBrancher<#c>> branches-  @value Bool complete--  create (brancher) {-    return MatchBranchesMatcher<#c>{ LinkedNode<MatchBrancher<#c>>$create(brancher,empty), false }-  }--  matchSatisfied () {-    if (complete) {-      return true-    }-    scoped {-      optional ReadSequence<MatchBrancher<#c>> current <- branches-    } in while (present(current)) {-      if (require(current).value().matchSatisfied()) {-        return true-      }-    } update {-      current <- require(current).next()-    }-    return false-  }--  tryNextMatch (data) (state) {-    state <- MatchState$matchFail()-    optional ReadSequence<MatchBrancher<#c>> current <- branches-    branches <- empty-    complete <- false-    scoped {-    } in while(present(current)) {-      MatchState state2, optional ReadSequence<MatchBrancher<#c>> branches2 <--          require(current).value().tryBranches(data)-      if (state2 `MatchState$equals` MatchState$matchComplete()) {-        complete <- true-      }-      if (state `MatchState$lessThan` state2) {-        state <- state2-      }-      branches <- LinkedNode<MatchBrancher<#c>>$concatSequences(branches2,branches)-    } update {-      current <- require(current).next()-    }-  }-}--concrete BranchRepeatMatcher<#c> {-  refines MatchBrancher<#c>--  @type create (Int,Int,MatcherTemplate<#c>) -> (BranchRepeatMatcher<#c>)-  @value tryBranches (#c) -> (MatchState,optional ReadSequence<BranchRepeatMatcher<#c>>)-}--define BranchRepeatMatcher {-  @value MatcherTemplate<#c> template-  @value Matcher<#c> matcher-  @value Int minCount-  @value Int maxCount // 0 means unlimited.-  @value Int repetitionCount-  @value MatchState lastState--  create (minCount,maxCount,template) {-    return BranchRepeatMatcher<#c>{ template, template.newMatcher(), minCount,-                                    maxCount, 0, MatchState$matchComplete() }-  }--  matchSatisfied () {-    if (lastState `MatchState$equals` MatchState$matchFail()) {-      return false-    }-    if (lastState `MatchState$equals` MatchState$matchContinue() &&-        (repetitionCount+1 >= minCount || template.matchesEmpty()) &&-        matcher.matchSatisfied()) {-      // The rest of the current repetition can be ignored.-      return true-    }-    if (lastState `MatchState$equals` MatchState$matchComplete() &&-        (repetitionCount >= minCount || template.matchesEmpty())) {-      return true-    }-    return false-  }--  tryBranches (data) (state,branch) {-    state <- MatchState$matchFail()-    branch <- empty-    if (atMax() || lastState `MatchState$equals` MatchState$matchFail()) {-      return _-    }-    if (lastState `MatchState$equals` MatchState$matchContinue()) {-      // Handle a possible single branch.-      Bool canSkip <- matcher.matchSatisfied()-      state <- matcher.tryNextMatch(data)-      if (canSkip) {-        if (state `MatchState$equals` MatchState$matchContinue()) {-          // Add a branch for the optional suffix.-          branch <- LinkedNode<BranchRepeatMatcher<#c>>$create(-              BranchRepeatMatcher<#c>{ template, matcher, minCount,-                                       maxCount, repetitionCount,-                                       MatchState$matchContinue() },empty)-        }-        // Continue, skipping over the optional suffix.-        \ incrementMatch()-        \ startRepeat()-        if (atMax()) {-          // Skipping the branch puts us at the max.-          return _-        }-        state <- matcher.tryNextMatch(data)-      }-    } else {-      // Branching is not possible.-      state <- matcher.tryNextMatch(data)-    }-    lastState <- state-    if (state `MatchState$equals` MatchState$matchComplete()) {-      \ incrementMatch()-      \ startRepeat()-      if (!atMax()) {-        branch <- LinkedNode<BranchRepeatMatcher<#c>>$create(self,branch)-      }-    } elif (state `MatchState$equals` MatchState$matchContinue()) {-      branch <- LinkedNode<BranchRepeatMatcher<#c>>$create(self,branch)-    }-    if (present(branch)) {-      state <- MatchState$matchContinue()-    }-  }--  @value incrementMatch () -> ()-  incrementMatch () {-    repetitionCount <- repetitionCount+1-  }--  @value atMax () -> (Bool)-  atMax () {-    return maxCount > 0 && repetitionCount >= maxCount-  }--  @value startRepeat () -> ()-  startRepeat () {-    matcher <- template.newMatcher()-  }-}--define BranchSequence {-  @value optional ReadSequence<MatcherTemplate<#c>> sequence--  create (sequence) {-    return BranchSequence<#c>{ sequence }-  }--  matchesEmpty () {-    scoped {-      optional ReadSequence<MatcherTemplate<#c>> current <- sequence-    } in while (present(current)) {-      if (!require(current).value().matchesEmpty()) {-        return false-      }-    } update {-      current <- require(current).next()-    }-    return true-  }--  newBrancher () {-    return BranchSequenceMatcher<#c>$create(sequence)-  }-}--concrete BranchSequenceMatcher<#c> {-  refines MatchBrancher<#c>--  @type create (optional ReadSequence<MatcherTemplate<#c>>) -> (BranchSequenceMatcher<#c>)-  @value tryBranches (#c) -> (MatchState,optional ReadSequence<BranchSequenceMatcher<#c>>)-}--define BranchSequenceMatcher {-  @value optional Matcher<#c> continued-  @value optional ReadSequence<MatcherTemplate<#c>> sequence-  @value Bool consumed--  create (sequence) {-    return BranchSequenceMatcher<#c>{ empty, sequence, false }-  }--  matchSatisfied () {-    if (consumed) {-      return false-    }-    if (present(continued) && !require(continued).matchSatisfied()) {-      return false-    }-    scoped {-      optional ReadSequence<MatcherTemplate<#c>> current <- sequence-    } in while (present(current)) {-      if (!require(current).value().matchesEmpty()) {-        return false-      }-    } update {-      current <- require(current).next()-    }-    return true-  }--  tryBranches (data) {-    if (consumed) {-      return MatchState$matchFail(), empty-    }-    consumed <- true-    return recursiveBranch(data,continued,sequence)-  }--  @type recursiveBranch (#c,optional Matcher<#c>,optional ReadSequence<MatcherTemplate<#c>>) ->-                        (MatchState,optional ReadSequence<BranchSequenceMatcher<#c>>)-  recursiveBranch (data,continued,sequence) (state,branch) {-    state <- MatchState$matchFail()-    branch <- empty-    if (!present(continued) && !present(sequence)) {-      return _-    }-    optional Matcher<#c> continued2 <- continued-    optional ReadSequence<MatcherTemplate<#c>> sequence2 <- sequence-    if (!present(continued2)) {-      continued2 <- require(sequence2).value().newMatcher()-      sequence2 <- require(sequence2).next()-    }-    if (require(continued2).matchSatisfied()) {-      MatchState state2, branch <- recursiveBranch(data,empty,sequence2)-      if (state `MatchState$lessThan` state2) {-        state <- state2-      }-    }-    MatchState state2 <- require(continued2).tryNextMatch(data)-    if (state `MatchState$lessThan` state2) {-      state <- state2-    }-    if (state2 `MatchState$equals` MatchState$matchComplete()) {-      if (present(sequence2)) {-        // Add a branch to continue this sequence.-        branch <- LinkedNode<BranchSequenceMatcher<#c>>$create(-            BranchSequenceMatcher<#c>{ empty, sequence2, false },branch)-        state <- MatchState$matchContinue()-      } else {-        // Add a branch to serve as a record of completeness. It will fail to-        // match anything, but matchSatisfied() will return true.-        branch <- LinkedNode<BranchSequenceMatcher<#c>>$create(-            BranchSequenceMatcher<#c>{ empty, empty, false },branch)-      }-    } elif (state `MatchState$equals` MatchState$matchContinue()) {-        // Add a branch to continue this sequence.-        branch <- LinkedNode<BranchSequenceMatcher<#c>>$create(-            BranchSequenceMatcher<#c>{ continued2, sequence2, false },branch)-    }-  }-}--define BranchRepeat {-  @value MatcherTemplate<#c> template-  @value Int minCount-  @value Int maxCount--  createZeroPlus (template) {-    return createRange(0,0,template)-  }--  createOnePlus (template) {-    return createRange(1,0,template)-  }--  createRange (minCount,maxCount,template) {-    return BranchRepeat<#c>{ template, minCount, maxCount }-  }--  matchesEmpty () {-    return minCount <= 0 || template.matchesEmpty()-  }--  newBrancher () {-    return BranchRepeatMatcher<#c>$create(minCount,maxCount,template)-  }-}--define LinkedNode {-  @value optional ReadSequence<#x> nextNode-  @value #x data--  create (data,nextNode) {-    return LinkedNode<#x>{ nextNode, data }-  }--  concatSequences (head1,head2) {-    if (!present(head2)) {-      return head1-    } elif (present(head1)) {-      return LinkedNode<#x>$create(require(head1).value(),-                                   concatSequences(require(head1).next(),head2))-    } else {-      return head2-    }-  }--  value () {-    return data-  }--  next () {-    return nextNode-  }-}
lib/file/Category_RawFileReader.cpp view
@@ -114,7 +114,8 @@ } struct Value_RawFileReader : public TypeValue {   Value_RawFileReader(Type_RawFileReader& p, const ParamTuple& params, const ValueTuple& args)-    : parent(p), file(new std::ifstream(args.At(0)->AsString(), std::ios::in | std::ios::binary)) {}+    : parent(p), filename(args.At(0)->AsString()),+    file(new std::ifstream(filename, std::ios::in | std::ios::binary)) {}    ReturnTuple Dispatch(const S<TypeValue>& self, const ValueFunction& label, const ParamTuple& params,const ValueTuple& args) final {     using CallType = ReturnTuple(Value_RawFileReader::*)(const S<TypeValue>&, const ParamTuple&, const ValueTuple&);@@ -156,7 +157,9 @@   Type_RawFileReader& parent;    std::mutex mutex;+  const std::string filename;   R<std::istream> file;+  CAPTURE_CREATION }; S<TypeValue> CreateValue(Type_RawFileReader& parent, const ParamTuple& params, const ValueTuple& args) {   return S_get(new Value_RawFileReader(parent, params, args));@@ -199,10 +202,14 @@  ReturnTuple Value_RawFileReader::Call_readBlock(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {   TRACE_FUNCTION("RawFileReader.readBlock")+  TRACE_CREATION   std::lock_guard<std::mutex> lock(mutex);   const PrimInt Var_arg1 = (args.At(0))->AsInt();   if (Var_arg1 < 0) {     FAIL() << "Read size " << Var_arg1 << " is invalid";+  }+  if (!file || file->rdstate() != std::ios::goodbit) {+    FAIL() << "Error reading file \"" << filename << "\"";   }   std::string buffer(Var_arg1, '\x00');   int read_size = 0;
lib/file/Category_RawFileWriter.cpp view
@@ -114,7 +114,8 @@ } struct Value_RawFileWriter : public TypeValue {   Value_RawFileWriter(Type_RawFileWriter& p, const ParamTuple& params, const ValueTuple& args)-    : parent(p), file(new std::ofstream(args.At(0)->AsString(), std::ios::out | std::ios::binary | std::ios::trunc | std::ios::ate)) {}+    : parent(p), filename(args.At(0)->AsString()),+    file(new std::ofstream(filename, std::ios::out | std::ios::binary | std::ios::trunc | std::ios::ate)) {}    ReturnTuple Dispatch(const S<TypeValue>& self, const ValueFunction& label, const ParamTuple& params,const ValueTuple& args) final {     using CallType = ReturnTuple(Value_RawFileWriter::*)(const S<TypeValue>&, const ParamTuple&, const ValueTuple&);@@ -154,7 +155,9 @@   Type_RawFileWriter& parent;    std::mutex mutex;+  const std::string filename;   R<std::ostream> file;+  CAPTURE_CREATION }; S<TypeValue> CreateValue(Type_RawFileWriter& parent, const ParamTuple& params, const ValueTuple& args) {   return S_get(new Value_RawFileWriter(parent, params, args));@@ -188,8 +191,12 @@ } ReturnTuple Value_RawFileWriter::Call_writeBlock(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {   TRACE_FUNCTION("RawFileWriter.writeBlock")+  TRACE_CREATION   std::lock_guard<std::mutex> lock(mutex);   const PrimString& Var_arg1 = args.At(0)->AsString();+  if (!file || file->rdstate() != std::ios::goodbit) {+    FAIL() << "Error writing file \"" << filename << "\"";+  }   int write_size = 0;   if (file) {     file->write(&Var_arg1[0], Var_arg1.size());
lib/file/tests.0rt view
@@ -103,17 +103,24 @@     if (!present(reader.getFileError())) {       fail("expected file error")     }+  }+} -    String data <- reader.readBlock(10)-    if (data != "") {-      fail("\"" + data + "\"")-    }-    if (!present(reader.getFileError())) {-      fail("expected file error")-    }-    if (!reader.pastEnd()) {-      fail("more data in file")-    }+concrete Test {+  @type run () -> ()+}+++testcase "read missing file crash" {+  crash Test$run()+  require "do-not-create-this-file"+  require "RawFileReader .*originally created"+}++define Test {+  run () {+    RawFileReader reader <- RawFileReader$open("do-not-create-this-file")+    \ reader.readBlock(0)   } } @@ -128,10 +135,30 @@  define Test {   run () {-    RawFileWriter writer <- RawFileWriter$open("/")+    RawFileWriter writer <- RawFileWriter$open("testfile")+    \ writer.freeResource()     if (!present(writer.getFileError())) {       fail("expected file error")     }+  }+}++concrete Test {+  @type run () -> ()+}+++testcase "unwritable file crash" {+  crash Test$run()+  require "testfile"+  require "RawFileWriter .*originally created"+}++define Test {+  run () {+    RawFileWriter writer <- RawFileWriter$open("testfile")+    \ writer.freeResource()+    \ writer.writeBlock("")   } } 
lib/util/.zeolite-module view
@@ -1,5 +1,14 @@ root: ".." path: "util"+expression_map: [+  expression_macro {+    name: BLOCK_READ_SIZE+    expression: \x1000+  }+]+private_deps: [+  "../../tests"+] extra_files: [   category_source {     source: "util/Category_Argv.cpp"
lib/util/tests.0rt view
@@ -217,3 +217,143 @@ concrete Test {   @type run () -> () }+++testcase "TextReader readAll" {+  success Test$run()+}++concrete FakeFile {+  refines BlockReader<String>++  @type create () -> (FakeFile)+}++define FakeFile {+  @value Int counter++  create () {+    return FakeFile{ 0 }+  }++  readBlock (_) {+    cleanup {+      counter <- counter+1+    } in if (counter == 0) {+      return "this is a "+    } elif (counter == 1) {+      return "partial line\nwith "+    } elif (counter == 2) {+      return "some more data\nand\nmore lines"+    } elif (counter == 3) {+      return "\nunfinished"+    } else {+      fail("too many reads")+    }+  }++  pastEnd () {+    return counter > 3+  }+}++define Test {+  run () {+    String allContents <- TextReader$readAll(FakeFile$create())+    \ Testing$check<String>(allContents,"this is a partial line\nwith some more data\nand\nmore lines\nunfinished")+  }+}++concrete Test {+  @type run () -> ()+}+++testcase "ErrorOr value" {+  success Test$run()+}++define Test {+  run () {+    ErrorOr<Int> value <- ErrorOr$$value<Int>(10)+    if (value.isError()) {+      fail("Failed")+    }+    \ Testing$check<Int>(value.getValue(),10)+  }+}++concrete Test {+  @type run () -> ()+}+++testcase "ErrorOr getError() crashes with value" {+  crash Test$run()+  require "empty"+}++define Test {+  run () {+    ErrorOr<Int> value <- ErrorOr$$value<Int>(10)+    \ value.getError()+  }+}++concrete Test {+  @type run () -> ()+}+++testcase "ErrorOr error" {+  success Test$run()+}++define Test {+  run () {+    ErrorOr<String> value <- ErrorOr$$error("error message")+    if (!value.isError()) {+      fail("Failed")+    }+    \ Testing$check<String>(value.getError().formatted(),"error message")+  }+}++concrete Test {+  @type run () -> ()+}+++testcase "ErrorOr getValue() crashes with error" {+  crash Test$run()+  require "error message"+}++define Test {+  run () {+    ErrorOr<String> value <- ErrorOr$$error("error message")+    \ value.getValue()+  }+}++concrete Test {+  @type run () -> ()+}+++testcase "ErrorOr convert error" {+  success Test$run()+}++define Test {+  run () {+    scoped {+      ErrorOr<String> error <- ErrorOr$$error("error message")+    } in ErrorOr<Int> value <- error.convertError()+    \ Testing$check<String>(value.getError().formatted(),"error message")+  }+}++concrete Test {+  @type run () -> ()+}
lib/util/util.0rp view
@@ -18,6 +18,15 @@  // TODO: Split this up, or otherwise organize it better. +concrete Void {+  refines Formatted++  defines Equals<Void>+  defines LessThan<Void>++  @type void () -> (Void)+}+ @value interface ReadCurrent<|#x> {   readCurrent () -> (#x) }@@ -88,6 +97,18 @@  concrete TextReader {   @type fromBlockReader (BlockReader<String>) -> (TextReader)+  @type readAll (BlockReader<String>) -> (String)   @value readNextLine () -> (String)   @value pastEnd () -> (Bool)+}++concrete ErrorOr<|#x> {+  @category value<#x> (#x)        -> (ErrorOr<#x>)+  @category error     (Formatted) -> (ErrorOr<all>)++  @value isError () -> (Bool)++  @value getValue () -> (#x)+  @value getError () -> (Formatted)+  @value convertError () -> (ErrorOr<all>) }
lib/util/util.0rx view
@@ -16,6 +16,26 @@  // Author: Kevin P. Barry [ta0kira@gmail.com] +define Void {+  @category Void singleton <- Void{ }++  formatted () {+    return "void"+  }++  equals (_,_) {+    return true+  }++  lessThan (_,_) {+    return true+  }++  void () {+    return singleton+  }+}+ define ReadIterator {   @value ReadPosition<#x> reader   @value Int position@@ -99,6 +119,14 @@     return TextReader{ reader, "" }   } +  readAll (reader) {+    Builder<String> builder <- String$builder()+    while (!reader.pastEnd()) {+      \ builder.append(reader.readBlock($ExprLookup[BLOCK_READ_SIZE]$))+    }+    return builder.build()+  }+   readNextLine () {     while (true) {       Int newline <- findNewline()@@ -108,8 +136,7 @@             buffer <- ""           } in return buffer         } else {-          // TODO: Maybe the block size should be a factory argument.-          buffer <- buffer+reader.readBlock(\x100)+          buffer <- buffer+reader.readBlock($ExprLookup[BLOCK_READ_SIZE]$)         }       } else {         return takeLine(newline)@@ -143,5 +170,38 @@     String data <- buffer.subSequence(0,position)     buffer <- buffer.subSequence(position+1,buffer.readSize()-position-1)     return data+  }+}++define ErrorOr {+  @value optional #x maybeValue+  @value optional Formatted maybeError++  value (x) {+    return ErrorOr<#x>{ x, empty }+  }++  error (message) {+    return ErrorOr<all>{ empty, message }+  }++  isError () {+    return !present(maybeValue) && present(maybeError)+  }++  getValue () {+    if (present(maybeValue)) {+      return require(maybeValue)+    } else {+      fail(require(maybeError))+    }+  }++  getError () {+    return require(maybeError)+  }++  convertError () {+    return ErrorOr<all>{ empty, maybeError }   } }
src/Cli/CompileMetadata.hs view
@@ -28,9 +28,11 @@ ) where  import Data.List (nub)+import Text.Parsec (SourcePos)  import Cli.CompileOptions import Config.Programs (VersionHash)+import Types.Procedure (Expression) import Types.TypeCategory (Namespace) import Types.TypeInstance (CategoryName) @@ -91,10 +93,23 @@   ModuleConfig {     rmRoot :: FilePath,     rmPath :: FilePath,+    rmExprMap :: [(String,Expression SourcePos)],     rmPublicDeps :: [FilePath],     rmPrivateDeps :: [FilePath],     rmExtraFiles :: [ExtraSource],     rmExtraPaths :: [FilePath],     rmMode :: CompileMode   }-  deriving (Eq,Show)+  deriving (Show)++instance Eq ModuleConfig where+  (ModuleConfig pA dA _ isA is2A esA epA mA) == (ModuleConfig pB dB _ isB is2B esB epB mB) =+    all id [+        pA == pB,+        dA == dB,+        isA == isB,+        is2A == is2B,+        esA == esB,+        epA == epB,+        mA == mB+      ]
src/Cli/Compiler.hs view
@@ -33,6 +33,7 @@ import System.Posix.Temp (mkstemps) import System.IO import Text.Parsec (SourcePos)+import qualified Data.Map as Map import qualified Data.Set as Set  import Base.CompileError@@ -41,6 +42,7 @@ import Cli.ProcessMetadata import Cli.TestRunner -- Not safe, due to Text.Regex.TDFA. import Compilation.CompileInfo+import Compilation.ProcedureContext (ExprMap) import CompilerCxx.Category import CompilerCxx.Naming import Config.LoadConfig@@ -58,6 +60,7 @@   ModuleSpec {     msRoot :: FilePath,     msPath :: FilePath,+    msExprMap :: ExprMap SourcePos,     msPublicDeps :: [FilePath],     msPrivateDeps :: [FilePath],     msPublicFiles :: [FilePath],@@ -75,20 +78,24 @@     ltRoot :: FilePath,     ltPath :: FilePath,     ltMetadata :: CompileMetadata,+    ltExprMap :: ExprMap SourcePos,     ltPublicDeps :: [CompileMetadata],     ltPrivateDeps :: [CompileMetadata]   }   deriving (Show)  compileModule :: ModuleSpec -> IO ()-compileModule (ModuleSpec p d is is2 ps xs ts es ep m f) = do+compileModule (ModuleSpec p d em is is2 ps xs ts es ep m f) = do   (backend,resolver) <- loadConfig   let hash = getCompilerHash backend   as  <- fmap fixPaths $ sequence $ map (resolveModule resolver (p </> d)) is   as2 <- fmap fixPaths $ sequence $ map (resolveModule resolver (p </> d)) is2-  (fr1,deps1) <- loadPublicDeps hash as+  let ca0 = Map.empty+  (fr1,deps1) <- loadPublicDeps hash ca0 as+  let ca1 = ca0 `Map.union` mapMetadata deps1   checkAllowedStale fr1 f-  (fr2,deps2) <- loadPublicDeps hash as2+  (fr2,deps2) <- loadPublicDeps hash ca1 as2+  let ca2 = ca1 `Map.union` mapMetadata deps2   checkAllowedStale fr2 f   base <- resolveBaseModule resolver   actual <- resolveModule resolver p d@@ -97,12 +104,12 @@   deps1' <- if isBase                then return deps1                else do-                 (fr3,bpDeps) <- loadPublicDeps hash [base]+                 (fr3,bpDeps) <- loadPublicDeps hash ca2 [base]                  checkAllowedStale fr3 f                  return $ bpDeps ++ deps1   ns0 <- createPublicNamespace p d   let ex = concat $ map getSourceCategories es-  cm <- loadLanguageModule p ns0 ex ps deps1' deps2+  cm <- loadLanguageModule p ns0 ex em ps deps1' deps2   xa <- fmap collectAllOrErrorM $ sequence $ map (loadPrivateSource p) xs   let fs = compileAll cm xa   eraseCachedData (p </> d)@@ -243,7 +250,7 @@           hPutStr h $ concat $ map (++ "\n") content           hClose h           base <- resolveBaseModule r-          (fr,deps2)  <- loadPrivateDeps (getCompilerHash b) deps+          (fr,deps2)  <- loadPrivateDeps (getCompilerHash b) (mapMetadata deps) deps           checkAllowedStale fr f           let lf' = lf ++ getLinkFlagsForDeps deps2           let paths' = fixPaths $ paths ++ base:(getIncludePathsForDeps deps)@@ -264,7 +271,7 @@ createModuleTemplates p d deps1 deps2 = do   ns0 <- createPublicNamespace p d   (ps,xs,_) <- findSourceFiles p d-  cm <- fmap (fmap fst) $ loadLanguageModule p ns0 [] ps deps1 deps2+  cm <- fmap (fmap fst) $ loadLanguageModule p ns0 [] Map.empty ps deps1 deps2   xs' <- zipWithContents p xs   let ts = createTemplates cm xs'   if isCompileError ts@@ -277,7 +284,7 @@         formatWarnings ts         sequence_ $ map writeTemplate $ getCompileSuccess ts where   createTemplates cm xs = do-    (LanguageModule _ _ _ cs0 ps0 ts0 cs1 ps1 ts1 _) <- cm+    (LanguageModule _ _ _ cs0 ps0 ts0 cs1 ps1 ts1 _ _) <- cm     ds <- collectAllOrErrorM $ map parseInternalSource xs     let ds2 = concat $ map (\(_,_,d2) -> d2) ds     tm <- foldM includeNewTypes defaultCategories [cs0,cs1,ps0,ps1,ts0,ts1]@@ -295,12 +302,12 @@           writeFile n' $ concat $ map (++ "\n") content  runModuleTests :: CompilerBackend b => b -> FilePath -> [FilePath] -> LoadedTests -> IO [((Int,Int),CompileInfo ())]-runModuleTests b base tp (LoadedTests p d m deps1 deps2) = do+runModuleTests b base tp (LoadedTests p d m em deps1 deps2) = do   let paths = base:(getIncludePathsForDeps deps1)   mapM_ showSkipped $ filter (not . isTestAllowed) $ cmTestFiles m   ts' <- zipWithContents p $ map (d </>) $ filter isTestAllowed $ cmTestFiles m   path <- canonicalizePath (p </> d)-  cm <- fmap (fmap fst) $ loadLanguageModule path NoNamespace [] [] deps1 []+  cm <- fmap (fmap fst) $ loadLanguageModule path NoNamespace [] em [] deps1 []   if isCompileError cm       then return [((0,0),cm >> return ())]       else sequence $ map (runSingleTest b (getCompileSuccess cm) path paths (m:deps2)) ts' where@@ -333,17 +340,19 @@     let testing = any isTestsOnly pragmas     return $ PrivateSource ns testing cs' ds -loadLanguageModule :: CompileErrorM m => FilePath -> Namespace -> [CategoryName] ->-  [FilePath] -> [CompileMetadata] -> [CompileMetadata] ->-  IO (m (LanguageModule SourcePos,([CategoryName],[CategoryName])))-loadLanguageModule p ns2 ex fs deps1 deps2 = do+loadLanguageModule :: CompileErrorM m => FilePath -> Namespace ->+  [CategoryName] -> ExprMap SourcePos -> [FilePath] -> [CompileMetadata] ->+  [CompileMetadata] -> IO (m (LanguageModule SourcePos,([CategoryName],[CategoryName])))+loadLanguageModule p ns2 ex em fs deps1 deps2 = do+  let public = Set.fromList $ map cmPath deps1+  let deps2' = filter (\cm -> not $ cmPath cm `Set.member` public) deps2+  let ns0 = filter (not . isNoNamespace) $ getNamespacesForDeps deps1+  let ns1 = filter (not . isNoNamespace) $ getNamespacesForDeps deps2'   m0 <- fmap merge $ sequence $ map processAll deps1-  m1 <- fmap merge $ sequence $ map processAll deps2+  m1 <- fmap merge $ sequence $ map processAll deps2'   m2 <- loadAllPublic "" fs-  return $ construct m0 m1 m2 where-    ns0 = filter (not . isNoNamespace) $ getNamespacesForDeps deps1-    ns1 = filter (not . isNoNamespace) $ getNamespacesForDeps deps2-    construct m0 m1 m2 = do+  return $ construct m0 m1 m2 ns0 ns1 where+    construct m0 m1 m2 ns0 ns1 = do       (ps0,_,tsA0,_)      <- m0       (ps1,_,tsA1,_)      <- m1       (ps2,xs2,tsA2,tsB2) <- m2@@ -357,7 +366,8 @@           lmPublicLocal = map (setCategoryNamespace ns2) ps2,           lmPrivateLocal = map (setCategoryNamespace ns2) xs2,           lmTestingLocal = map (setCategoryNamespace ns2) $ tsA2 ++ tsB2,-          lmExternal = ex+          lmExternal = ex,+          lmExprMap = em         }       return (cm,(map getCategoryName $ ps2++tsA2,map getCategoryName $ xs2++tsB2))     loadPublic p2 p3 = parsePublicSource p3 >>= return . uncurry (partition p2)
src/Cli/ParseCompileOptions.hs view
@@ -29,6 +29,7 @@ import System.Exit import System.IO import Text.Regex.TDFA -- Not safe!+import qualified Data.Map as Map  import Base.CompileError import Cli.CompileMetadata@@ -103,7 +104,7 @@   exitSuccess where     showDeps p = do       p' <- canonicalizePath p-      m <- loadMetadata p'+      m <- loadMetadata Map.empty p'       hPutStrLn stdout $ show p'       mapM_ showDep (cmObjectFiles m)     showDep (CategoryObjectFile c ds _) = do
src/Cli/ParseMetadata.hs view
@@ -31,9 +31,12 @@ import Cli.CompileOptions import Config.Programs (VersionHash(..)) import Parser.Common+import Parser.Procedure ()+import Parser.Pragma (parseMacroName) import Parser.TypeCategory () import Parser.TypeInstance () import Text.Regex.TDFA -- Not safe!+import Types.Procedure (Expression) import Types.TypeCategory (FunctionName(..),Namespace(..)) import Types.TypeInstance (CategoryName(..)) @@ -265,20 +268,26 @@  instance ConfigFormat ModuleConfig where   readConfig = do-      p <-   parseOptional "root:"          "" parseQuoted-      d <-   parseRequired "path:"             parseQuoted-      is <-  parseOptional "public_deps:"   [] (parseList parseQuoted)-      is2 <- parseOptional "private_deps:"  [] (parseList parseQuoted)-      es <-  parseOptional "extra_files:"   [] (parseList readConfig)-      ep <-  parseOptional "include_paths:" [] (parseList parseQuoted)-      m <-   parseRequired "mode:"             readConfig-      return (ModuleConfig p d is is2 es ep m)+      p   <- parseOptional "root:"           "" parseQuoted+      d   <- parseRequired "path:"              parseQuoted+      em  <- parseOptional "expression_map:" [] (parseList parseExprMacro)+      is  <- parseOptional "public_deps:"    [] (parseList parseQuoted)+      is2 <- parseOptional "private_deps:"   [] (parseList parseQuoted)+      es  <- parseOptional "extra_files:"    [] (parseList readConfig)+      ep  <- parseOptional "include_paths:"  [] (parseList parseQuoted)+      m   <- parseRequired "mode:"              readConfig+      return (ModuleConfig p d em is is2 es ep m)   writeConfig m = do     extra    <- fmap concat $ collectAllOrErrorM $ map writeConfig $ rmExtraFiles m     mode <- writeConfig (rmMode m)+    when (not $ null $ rmExprMap m) $ compileError "Only empty expression maps are allowed when writing"     return $ [         "root: " ++ show (rmRoot m),         "path: " ++ show (rmPath m),+        "expression_map: [",+        -- NOTE: expression_map isn't output because that would require making+        -- all Expression serializable.+        "]",         "public_deps: ["       ] ++ indents (map show $ rmPublicDeps m) ++ [         "]",@@ -362,3 +371,12 @@       ]   writeConfig CompileUnspecified = writeConfig (CompileIncremental [])   writeConfig _ = compileError "Invalid compile mode"++parseExprMacro :: Parser (String,Expression SourcePos)+parseExprMacro = do+  sepAfter (string_ "expression_macro")+  structOpen+  n <- parseRequired "name:"       parseMacroName+  e <- parseRequired "expression:" sourceParser+  structClose+  return (n,e)
src/Cli/ProcessMetadata.hs view
@@ -17,7 +17,9 @@ -- Author: Kevin P. Barry [ta0kira@gmail.com]  module Cli.ProcessMetadata (+  MetadataMap,   checkAllowedStale,+  checkMetadataFreshness,   createCachePath,   eraseCachedData,   findSourceFiles,@@ -25,6 +27,7 @@   fixPaths,   getCachedPath,   getCacheRelativePath,+  getExprMap,   getIncludePathsForDeps,   getLinkFlagsForDeps,   getNamespacesForDeps,@@ -38,6 +41,7 @@   loadPublicDeps,   loadTestingDeps,   loadMetadata,+  mapMetadata,   resolveCategoryDeps,   resolveObjectDeps,   sortCompiledFiles,@@ -55,6 +59,7 @@ import System.Exit (exitFailure) import System.FilePath import System.IO+import Text.Parsec (SourcePos) import qualified Data.Map as Map import qualified Data.Set as Set @@ -63,8 +68,10 @@ import Cli.CompileOptions import Cli.ParseMetadata -- Not safe, due to Text.Regex.TDFA. import Compilation.CompileInfo+import Compilation.ProcedureContext (ExprMap) import CompilerCxx.Category (CxxOutput(..)) import Config.Programs (VersionHash(..))+import Types.Procedure (Expression(Literal),ValueLiteral(..)) import Types.TypeCategory import Types.TypeInstance @@ -85,30 +92,42 @@                        "Recompile them or use -f to force."     exitFailure -loadMetadata :: FilePath -> IO CompileMetadata-loadMetadata p = do-  let f = p </> cachedDataPath </> metadataFilename-  isFile <- doesFileExist p-  when isFile $ do-    hPutStrLn stderr $ "Path \"" ++ p ++ "\" is not a directory."-    exitFailure-  isDir <- doesDirectoryExist p-  when (not isDir) $ do-    hPutStrLn stderr $ "Path \"" ++ p ++ "\" does not exist."-    exitFailure-  filePresent <- doesFileExist f-  when (not filePresent) $ do-    hPutStrLn stderr $ "Module \"" ++ p ++ "\" has not been compiled yet."-    exitFailure-  c <- readFile f-  let m = autoReadConfig f c-  if isCompileError m-     then do-       hPutStrLn stderr $ "Could not parse metadata from \"" ++ p ++ "\"; please recompile."-       hPutStrLn stderr $ show (getCompileError m)-       exitFailure-     else return (getCompileSuccess m)+type MetadataMap = Map.Map FilePath CompileMetadata +mapMetadata :: [CompileMetadata] -> MetadataMap+mapMetadata cs = Map.fromList $ zip (map cmPath cs) cs++loadMetadata :: MetadataMap -> FilePath -> IO CompileMetadata+loadMetadata ca p = do+  path <- canonicalizePath p+  case path `Map.lookup` ca of+       Just cm -> return cm+       Nothing -> do+         let f = p </> cachedDataPath </> metadataFilename+         isFile <- doesFileExist p+         when isFile $ do+           hPutStrLn stderr $ "Path \"" ++ p ++ "\" is not a directory."+           exitFailure+         isDir <- doesDirectoryExist p+         when (not isDir) $ do+           hPutStrLn stderr $ "Path \"" ++ p ++ "\" does not exist."+           exitFailure+         filePresent <- doesFileExist f+         when (not filePresent) $ do+           hPutStrLn stderr $ "Module \"" ++ p ++ "\" has not been compiled yet."+           exitFailure+         c <- readFile f+         let m = autoReadConfig f c+         if isCompileError m+            then do+              hPutStrLn stderr $ "Could not parse metadata from \"" ++ p ++ "\"; please recompile."+              hPutStrLn stderr $ show (getCompileError m)+              exitFailure+            else return (getCompileSuccess m)++checkMetadataFreshness :: FilePath -> CompileMetadata -> IO Bool+checkMetadataFreshness = checkModuleFreshness False+ tryLoadMetadata :: FilePath -> IO (Maybe CompileMetadata) tryLoadMetadata p = tryLoadData $ (p </> cachedDataPath </> metadataFilename) @@ -136,7 +155,7 @@   case m of        Nothing -> return False        Just _ -> do-         (fr,_) <- loadDepsCommon True h Set.empty (\m2 -> cmPublicDeps m2 ++ cmPrivateDeps m2) [p]+         (fr,_) <- loadDepsCommon True h Map.empty Set.empty (\m2 -> cmPublicDeps m2 ++ cmPrivateDeps m2) [p]          return fr  isPathConfigured :: FilePath -> IO Bool@@ -214,6 +233,12 @@   let ts = filter (isSuffixOf ".0rt") ds   return (ps,xs,ts) +getExprMap :: FilePath -> ModuleConfig -> IO (ExprMap SourcePos)+getExprMap p m = do+  path <- canonicalizePath (p </> rmRoot m </> rmPath m)+  let defaults = [("MODULE_PATH",Literal (StringLiteral [] path))]+  return $ Map.fromList $ rmExprMap m ++ defaults+ getRealPathsForDeps :: [CompileMetadata] -> [FilePath] getRealPathsForDeps = map cmPath @@ -233,35 +258,40 @@ getObjectFilesForDeps :: [CompileMetadata] -> [ObjectFile] getObjectFilesForDeps = concat . map cmObjectFiles -loadPublicDeps :: VersionHash -> [FilePath] -> IO (Bool,[CompileMetadata])-loadPublicDeps h = loadDepsCommon False h Set.empty cmPublicDeps+loadPublicDeps :: VersionHash -> MetadataMap -> [FilePath] -> IO (Bool,[CompileMetadata])+loadPublicDeps h ca = loadDepsCommon False h ca Set.empty cmPublicDeps -loadTestingDeps :: VersionHash -> CompileMetadata -> IO (Bool,[CompileMetadata])-loadTestingDeps h m = loadDepsCommon False h (Set.fromList [cmPath m]) cmPublicDeps (cmPublicDeps m ++ cmPrivateDeps m)+loadTestingDeps :: VersionHash -> MetadataMap -> CompileMetadata -> IO (Bool,[CompileMetadata])+loadTestingDeps h ca m = loadDepsCommon False h ca (Set.fromList [cmPath m]) cmPublicDeps (cmPublicDeps m ++ cmPrivateDeps m) -loadPrivateDeps :: VersionHash -> [CompileMetadata] -> IO (Bool,[CompileMetadata])-loadPrivateDeps h ms = do-  (fr,new) <- loadDepsCommon False h pa (\m -> cmPublicDeps m ++ cmPrivateDeps m) paths+loadPrivateDeps :: VersionHash -> MetadataMap -> [CompileMetadata] -> IO (Bool,[CompileMetadata])+loadPrivateDeps h ca ms = do+  (fr,new) <- loadDepsCommon False h ca pa (\m -> cmPublicDeps m ++ cmPrivateDeps m) paths   return (fr,ms ++ new) where     paths = concat $ map (\m -> cmPublicDeps m ++ cmPrivateDeps m) ms     pa = Set.fromList $ map cmPath ms -loadDepsCommon :: Bool -> VersionHash -> Set.Set FilePath ->+loadDepsCommon :: Bool -> VersionHash -> MetadataMap -> Set.Set FilePath ->   (CompileMetadata -> [FilePath]) -> [FilePath] -> IO (Bool,[CompileMetadata])-loadDepsCommon s h pa0 f ps = fmap snd $ fixedPaths >>= collect (pa0,(True,[])) where+loadDepsCommon s h ca pa0 f ps = fmap snd $ fixedPaths >>= collect (pa0,(True,[])) where   fixedPaths = sequence $ map canonicalizePath ps   collect xa@(pa,(fr,xs)) (p:ps2)     | p `Set.member` pa = collect xa ps2     | otherwise = do-        when (not s) $ hPutStrLn stderr $ "Loading metadata for dependency \"" ++ p ++ "\"."-        m <- loadMetadata p-        fresh <- checkModuleFreshness s p m-        when (not s && not fresh) $-          hPutStrLn stderr $ "Module \"" ++ p ++ "\" is out of date and should be recompiled."-        let sameVersion = checkModuleVersionHash h m-        when (not s && not sameVersion) $-          hPutStrLn stderr $ "Module \"" ++ p ++ "\" was compiled with a different compiler setup."-        collect (p `Set.insert` pa,(sameVersion && fresh && fr,xs ++ [m])) (ps2 ++ f m)+        (m,fr2) <-+          case p `Map.lookup` ca of+               Just m2 -> return (m2,True)+               Nothing -> do+                 when (not s) $ hPutStrLn stderr $ "Loading metadata for dependency \"" ++ p ++ "\"."+                 m2 <- loadMetadata ca p+                 fresh <- checkModuleFreshness s p m2+                 when (not s && not fresh) $+                   hPutStrLn stderr $ "Module \"" ++ p ++ "\" is out of date and should be recompiled."+                 let sameVersion = checkModuleVersionHash h m2+                 when (not s && not sameVersion) $+                   hPutStrLn stderr $ "Module \"" ++ p ++ "\" was compiled with a different compiler setup."+                 return (m2,sameVersion && fresh)+        collect (p `Set.insert` pa,(fr2 && fr,xs ++ [m])) (ps2 ++ f m)   collect xa _ = return xa  fixPath :: FilePath -> FilePath
src/Cli/RunCompiler.hs view
@@ -27,6 +27,7 @@ import System.FilePath import System.Posix.Temp (mkdtemp) import System.IO+import qualified Data.Map as Map import qualified Data.Set as Set  import Base.CompileError@@ -45,21 +46,34 @@ runCompiler (CompileOptions _ _ _ ds _ _ p (ExecuteTests tp) f) = do   (backend,resolver) <- loadConfig   base <- resolveBaseModule resolver-  ts <- sequence $ map (preloadTests backend base) ds+  ts <- fmap snd $ foldM (preloadTests backend base) (Map.empty,[]) ds   checkTestFilters ts   allResults <- fmap concat $ sequence $ map (runModuleTests backend base tp) ts   let passed = sum $ map (fst . fst) allResults   let failed = sum $ map (snd . fst) allResults   processResults passed failed (mergeAllM $ map snd allResults) where-    preloadTests b base d = do-      m <- loadMetadata (p </> d)-      (fr0,deps0) <- loadPublicDeps (getCompilerHash b) [base]+    preloadTests b base (ca,ms) d = do+      m <- loadMetadata ca (p </> d)+      let ca2 = ca `Map.union` mapMetadata [m]+      fr <- checkMetadataFreshness (p </> d) m+      checkAllowedStale fr f+      rm <- tryLoadRecompile (p </> d)+      rm' <- case rm of+                  Just rm2 -> return rm2+                  Nothing -> do+                    hPutStr stderr $ "Module config for " ++ d ++ " is missing."+                    exitFailure+      (fr0,deps0) <- loadPublicDeps (getCompilerHash b) ca2 [base]+      let ca3 = ca2 `Map.union` mapMetadata deps0       checkAllowedStale fr0 f-      (fr1,deps1) <- loadTestingDeps (getCompilerHash b) m+      (fr1,deps1) <- loadTestingDeps (getCompilerHash b) ca3 m+      let ca4 = ca3 `Map.union` mapMetadata deps1       checkAllowedStale fr1 f-      (fr2,deps2) <- loadPrivateDeps (getCompilerHash b) (deps0++[m]++deps1)+      (fr2,deps2) <- loadPrivateDeps (getCompilerHash b) ca4 (deps0++[m]++deps1)+      let ca5 = ca4 `Map.union` mapMetadata deps2       checkAllowedStale fr2 f-      return $ LoadedTests p d m (deps0++[m]++deps1) deps2+      em <- getExprMap (p </> d) rm'+      return (ca5,ms ++ [LoadedTests p d m em (deps0++[m]++deps1) deps2])     checkTestFilters ts = do       let possibleTests = Set.fromList $ concat $ map (cmTestFiles . ltMetadata) ts       case Set.toList $ (Set.fromList tp) `Set.difference` possibleTests of@@ -82,9 +96,21 @@   dir <- mkdtemp "/tmp/zfast_"   absolute <- canonicalizePath p   f2' <- canonicalizePath (p </> f2)+  let rm = ModuleConfig {+    rmRoot = "",+    rmPath = ".",+    rmExprMap = [],+    rmPublicDeps = [],+    rmPrivateDeps = [],+    rmExtraFiles = [],+    rmExtraPaths = [],+    rmMode = CompileUnspecified+  }+  em <- getExprMap p rm   let spec = ModuleSpec {     msRoot = absolute,     msPath = dir,+    msExprMap = em,     msPublicDeps = is,     msPrivateDeps = is2,     msPublicFiles = [],@@ -99,22 +125,29 @@   removeDirectoryRecursive dir  runCompiler (CompileOptions h _ _ ds _ _ p CompileRecompileRecursive f) = do-  foldM recursive Set.empty ds >> return () where-    recursive da d0 = do-      d <- canonicalizePath (p </> d0)-      rm <- tryLoadRecompile d-      case rm of-           Nothing -> do-             hPutStrLn stderr $ "Path " ++ d ++ " does not have a valid configuration."-             exitFailure-           Just m ->-             if rmPath m `Set.member` da-                then return da-                else do-                  let ds3 = map (\d2 -> d </> d2) (rmPublicDeps m ++ rmPrivateDeps m)-                  da' <- foldM recursive (rmPath m `Set.insert` da) ds3-                  runCompiler (CompileOptions h [] [] [d] [] [] p CompileRecompile f)-                  return da'+  (_,resolver) <- loadConfig+  foldM (recursive resolver) Set.empty ds >> return () where+    recursive r da d0 = do+      isSystem <- isSystemModule r p d0+      if isSystem+         then do+           hPutStrLn stderr $ "Skipping system module " ++ d0 ++ "."+           exitFailure+         else do+           d <- canonicalizePath (p </> d0)+           rm <- tryLoadRecompile d+           case rm of+                Nothing -> do+                  hPutStrLn stderr $ "Path " ++ d ++ " does not have a valid configuration."+                  exitFailure+                Just m ->+                  if rmPath m `Set.member` da+                     then return da+                     else do+                       let ds3 = map (\d2 -> d </> d2) (rmPublicDeps m ++ rmPrivateDeps m)+                       da' <- foldM (recursive r) (rmPath m `Set.insert` da) ds3+                       runCompiler (CompileOptions h [] [] [d] [] [] p CompileRecompile f)+                       return da'  runCompiler (CompileOptions _ _ _ ds _ _ p CompileRecompile f) = do   (backend,_) <- loadConfig@@ -130,15 +163,17 @@         maybeCompile (Just rm') upToDate           | f < ForceAll && upToDate = hPutStrLn stderr $ "Path " ++ d0 ++ " is up to date."           | otherwise = do-              let (ModuleConfig p2 d is is2 es ep m) = rm'+              let (ModuleConfig p2 d _ is is2 es ep m) = rm'               -- In case the module is manually configured with a p such as "..",               -- since the absolute path might not be known ahead of time.               absolute <- canonicalizePath (p </> d0)               let fixed = fixPath (absolute </> p2)               (ps,xs,ts) <- findSourceFiles fixed d+              em <- getExprMap (p </> d0) rm'               let spec = ModuleSpec {                 msRoot = fixed,                 msPath = d,+                msExprMap = em,                 msPublicDeps = is,                 msPrivateDeps = is2,                 msPublicFiles = ps,@@ -157,9 +192,9 @@     base <- resolveBaseModule resolver     as  <- fmap fixPaths $ sequence $ map (resolveModule resolver (p </> d)) is     as2 <- fmap fixPaths $ sequence $ map (resolveModule resolver (p </> d)) is2-    (fr1,deps1) <- loadPublicDeps (getCompilerHash backend) (base:as)+    (fr1,deps1) <- loadPublicDeps (getCompilerHash backend) Map.empty (base:as)     checkAllowedStale fr1 f-    (fr2,deps2) <- loadPublicDeps (getCompilerHash backend) as2+    (fr2,deps2) <- loadPublicDeps (getCompilerHash backend) (mapMetadata deps1) as2     checkAllowedStale fr2 f     path <- canonicalizePath p     createModuleTemplates path d deps1 deps2@@ -178,6 +213,7 @@     let rm = ModuleConfig {       rmRoot = absolute,       rmPath = d,+      rmExprMap = [],       rmPublicDeps = as,       rmPrivateDeps = as2,       rmExtraFiles = es,
src/Compilation/CompileInfo.hs view
@@ -112,7 +112,7 @@     result (es,_,ws) = CompileFail ws $ CompileMessage "" es   collectOneOrErrorM = result . splitErrorsAndData where     result (_,x:_,ws) = CompileSuccess ws x-    result ([],_,ws)  = CompileFail ws $ CompileMessage "No choices found" []+    result ([],_,ws)  = CompileFail ws $ CompileMessage "" []     result (es,_,ws)  = CompileFail ws $ CompileMessage "" es   reviseErrorM x@(CompileSuccess _ _) _ = x   reviseErrorM (CompileFail w (CompileMessage [] ms)) s = CompileFail w $ CompileMessage s ms@@ -122,7 +122,7 @@ instance MergeableM CompileInfo where   mergeAnyM = result . splitErrorsAndData where     result (_,xs@(_:_),ws) = CompileSuccess ws $ mergeAny xs-    result ([],_,ws)       = CompileFail ws $ CompileMessage "No choices found" []+    result ([],_,ws)       = CompileFail ws $ CompileMessage "" []     result (es,_,ws)       = CompileFail ws $ CompileMessage "" es   mergeAllM = result . splitErrorsAndData where     result ([],xs,ws) = CompileSuccess ws $ mergeAll xs
src/Compilation/CompilerState.hs view
@@ -36,9 +36,11 @@   csCheckVariableInit,   csClearOutput,   csCurrentScope,+  csExprLookup,   csGetCategoryFunction,   csGetCleanup,   csGetLoop,+  csGetNoTrace,   csGetOutput,   csGetParamScope,   csGetTypeFunction,@@ -53,6 +55,7 @@   csResolver,   csSameType,   csSetNoReturn,+  csSetNoTrace,   csStartLoop,   csUpdateAssigned,   csWrite,@@ -108,6 +111,9 @@   ccGetLoop :: a -> m (LoopSetup s)   ccPushCleanup :: a -> CleanupSetup a s -> m a   ccGetCleanup :: a -> m (CleanupSetup a s)+  ccExprLookup :: a -> [c] -> String -> m (Expression c)+  ccSetNoTrace :: a -> Bool -> m a+  ccGetNoTrace :: a -> m Bool  type ExpressionType = Positional ValueType @@ -232,6 +238,15 @@  csGetCleanup :: CompilerContext c m s a => CompilerState a m (CleanupSetup a s) csGetCleanup = fmap ccGetCleanup get >>= lift++csExprLookup :: CompilerContext c m s a => [c] -> String -> CompilerState a m (Expression c)+csExprLookup c n = fmap (\x -> ccExprLookup x c n) get >>= lift++csSetNoTrace :: CompilerContext c m s a => Bool -> CompilerState a m ()+csSetNoTrace t = fmap (\x -> ccSetNoTrace x t) get >>= lift >>= put++csGetNoTrace :: CompilerContext c m s a => CompilerState a m Bool+csGetNoTrace = fmap ccGetNoTrace get >>= lift  data CompiledData s =   CompiledData {
src/Compilation/ProcedureContext.hs view
@@ -21,6 +21,7 @@ {-# LANGUAGE Safe #-}  module Compilation.ProcedureContext (+  ExprMap,   ProcedureContext(..),   ReturnValidation(..),   updateArgVariables,@@ -62,9 +63,13 @@     pcOutput :: [String],     pcDisallowInit :: Bool,     pcLoopSetup :: LoopSetup [String],-    pcCleanupSetup :: CleanupSetup (ProcedureContext c) [String]+    pcCleanupSetup :: CleanupSetup (ProcedureContext c) [String],+    pcExprMap :: ExprMap c,+    pcNoTrace :: Bool   } +type ExprMap c = Map.Map String (Expression c)+ data ReturnValidation c =   ValidatePositions {     vpReturns :: Positional (PassedValue c)@@ -106,7 +111,9 @@       pcOutput = pcOutput ctx,       pcDisallowInit = pcDisallowInit ctx,       pcLoopSetup = pcLoopSetup ctx,-      pcCleanupSetup = pcCleanupSetup ctx+      pcCleanupSetup = pcCleanupSetup ctx,+      pcExprMap = pcExprMap ctx,+      pcNoTrace = pcNoTrace ctx     }   ccGetRequired = return . pcRequiredTypes   ccGetCategoryFunction ctx c Nothing n = ccGetCategoryFunction ctx c (Just $ pcType ctx) n@@ -134,8 +141,9 @@     getFunction (Just t2@(TypeMerge MergeUnion _)) =       compileError $ "Use explicit type conversion to call " ++ show n ++ " for union type " ++                      show t2 ++ formatFullContextBrace c-    getFunction (Just (TypeMerge MergeIntersect ts)) =-      collectOneOrErrorM $ map getFunction $ map Just ts+    getFunction (Just ta@(TypeMerge MergeIntersect ts)) =+      collectOneOrErrorM (map getFunction $ map Just ts) `reviseError`+        ("Function " ++ show n ++ " not available for type " ++ show ta ++ formatFullContextBrace c)     getFunction (Just (SingleType (JustParamName p))) = do       fa <- ccAllFilters ctx       fs <- case p `Map.lookup` fa of@@ -143,10 +151,8 @@                 _ -> compileError $ "Param " ++ show p ++ " does not exist"       let ts = map tfType $ filter isRequiresFilter fs       let ds = map dfType $ filter isDefinesFilter  fs-      collectOneOrErrorM $-        [compileError $ "Function " ++ show n ++ " not available for param " ++ show p ++-         formatFullContextBrace c] ++-        (map getFunction $ map (Just . SingleType) ts) ++ (map checkDefine ds)+      collectOneOrErrorM (map (getFunction . Just . SingleType) ts ++ map checkDefine ds) `reviseError`+        ("Function " ++ show n ++ " not available for param " ++ show p ++ formatFullContextBrace c)     getFunction (Just (SingleType (JustTypeInstance t2)))       -- Same category as the procedure itself.       | tiName t2 == pcType ctx =@@ -245,7 +251,9 @@         pcOutput = pcOutput ctx,         pcDisallowInit = pcDisallowInit ctx,         pcLoopSetup = pcLoopSetup ctx,-        pcCleanupSetup = pcCleanupSetup ctx+        pcCleanupSetup = pcCleanupSetup ctx,+        pcExprMap = pcExprMap ctx,+        pcNoTrace = pcNoTrace ctx       }   ccCheckVariableInit ctx c n =     case pcReturns ctx of@@ -272,7 +280,9 @@       pcOutput = pcOutput ctx ++ ss,       pcDisallowInit = pcDisallowInit ctx,       pcLoopSetup = pcLoopSetup ctx,-      pcCleanupSetup = pcCleanupSetup ctx+      pcCleanupSetup = pcCleanupSetup ctx,+      pcExprMap = pcExprMap ctx,+      pcNoTrace = pcNoTrace ctx     }   ccGetOutput = return . pcOutput   ccClearOutput ctx = return $ ProcedureContext {@@ -294,7 +304,9 @@         pcOutput = [],         pcDisallowInit = pcDisallowInit ctx,         pcLoopSetup = pcLoopSetup ctx,-        pcCleanupSetup = pcCleanupSetup ctx+        pcCleanupSetup = pcCleanupSetup ctx,+        pcExprMap = pcExprMap ctx,+        pcNoTrace = pcNoTrace ctx       }   ccUpdateAssigned ctx n = update (pcReturns ctx) where     update (ValidateNames ts ra) = return $ ProcedureContext {@@ -316,7 +328,9 @@         pcOutput = pcOutput ctx,         pcDisallowInit = pcDisallowInit ctx,         pcLoopSetup = pcLoopSetup ctx,-        pcCleanupSetup = pcCleanupSetup ctx+        pcCleanupSetup = pcCleanupSetup ctx,+        pcExprMap = pcExprMap ctx,+        pcNoTrace = pcNoTrace ctx       }     update _ = return ctx   ccInheritReturns ctx cs = return $ ProcedureContext {@@ -338,7 +352,9 @@       pcOutput = pcOutput ctx,       pcDisallowInit = pcDisallowInit ctx,       pcLoopSetup = pcLoopSetup ctx,-      pcCleanupSetup = pcCleanupSetup ctx+      pcCleanupSetup = pcCleanupSetup ctx,+      pcExprMap = pcExprMap ctx,+      pcNoTrace = pcNoTrace ctx     }     where       inherited = foldr combineParallel UnreachableCode (map pcReturns cs)@@ -373,13 +389,18 @@         pcOutput = pcOutput ctx,         pcDisallowInit = pcDisallowInit ctx,         pcLoopSetup = pcLoopSetup ctx,-        pcCleanupSetup = pcCleanupSetup ctx+        pcCleanupSetup = pcCleanupSetup ctx,+        pcExprMap = pcExprMap ctx,+        pcNoTrace = pcNoTrace ctx       }     where       check (ValidatePositions rs) = do         let vs' = case vs of                        Nothing -> Positional []                        Just vs2 -> vs2+        -- Check for a count match first, to avoid the default error message.+        processPairs_ alwaysPair (fmap pvType rs) vs' `reviseError`+          ("In procedure return at " ++ formatFullContext c)         processPairs_ checkReturnType rs (Positional $ zip ([0..] :: [Int]) $ pValues vs') `reviseError`           ("In procedure return at " ++ formatFullContext c)         return ()@@ -425,7 +446,9 @@         pcOutput = pcOutput ctx,         pcDisallowInit = pcDisallowInit ctx,         pcLoopSetup = pcLoopSetup ctx,-        pcCleanupSetup = pcCleanupSetup ctx+        pcCleanupSetup = pcCleanupSetup ctx,+        pcExprMap = pcExprMap ctx,+        pcNoTrace = pcNoTrace ctx       }   ccStartLoop ctx l =     return $ ProcedureContext {@@ -447,7 +470,9 @@         pcOutput = pcOutput ctx,         pcDisallowInit = pcDisallowInit ctx,         pcLoopSetup = l,-        pcCleanupSetup = pcCleanupSetup ctx+        pcCleanupSetup = pcCleanupSetup ctx,+        pcExprMap = pcExprMap ctx,+        pcNoTrace = pcNoTrace ctx       }   ccGetLoop = return . pcLoopSetup   ccPushCleanup ctx (CleanupSetup cs ss) =@@ -471,9 +496,40 @@         pcDisallowInit = pcDisallowInit ctx,         pcLoopSetup = pcLoopSetup ctx,         pcCleanupSetup = CleanupSetup (cs ++ (csReturnContext $ pcCleanupSetup ctx))-                                      (ss ++ (csCleanup $ pcCleanupSetup ctx))+                                      (ss ++ (csCleanup $ pcCleanupSetup ctx)),+        pcExprMap = pcExprMap ctx,+        pcNoTrace = pcNoTrace ctx       }   ccGetCleanup = return . pcCleanupSetup+  ccExprLookup ctx c n =+    case n `Map.lookup` pcExprMap ctx of+         Nothing -> compileError $ "Env expression " ++ n ++ " is not defined" ++ formatFullContextBrace c+         Just e -> return e+  ccSetNoTrace ctx t =+    return $ ProcedureContext {+        pcScope = pcScope ctx,+        pcType = pcType ctx,+        pcExtParams = pcExtParams ctx,+        pcIntParams = pcIntParams ctx,+        pcMembers = pcMembers ctx,+        pcCategories = pcCategories ctx,+        pcAllFilters = pcAllFilters ctx,+        pcExtFilters = pcExtFilters ctx,+        pcIntFilters = pcIntFilters ctx,+        pcParamScopes = pcParamScopes ctx,+        pcFunctions = pcFunctions ctx,+        pcVariables = pcVariables ctx,+        pcReturns = pcReturns ctx,+        pcPrimNamed = pcPrimNamed ctx,+        pcRequiredTypes = pcRequiredTypes ctx,+        pcOutput = pcOutput ctx,+        pcDisallowInit = pcDisallowInit ctx,+        pcLoopSetup = pcLoopSetup ctx,+        pcCleanupSetup = pcCleanupSetup ctx,+        pcExprMap = pcExprMap ctx,+        pcNoTrace = t+      }+  ccGetNoTrace = return . pcNoTrace  updateReturnVariables :: (Show c, CompileErrorM m, MergeableM m) =>   (Map.Map VariableName (VariableValue c)) ->
src/Compilation/ScopeContext.hs view
@@ -32,6 +32,7 @@  import Base.CompileError import Base.Mergeable+import Compilation.ProcedureContext import Types.DefinedCategory import Types.GeneralType import Types.Positional@@ -50,7 +51,8 @@     scExternalFilters :: [ParamFilter c],     scInternalFilters :: [ParamFilter c],     scFunctions :: Map.Map FunctionName (ScopedFunction c),-    scVariables :: Map.Map VariableName (VariableValue c)+    scVariables :: Map.Map VariableName (VariableValue c),+    scExprMap :: ExprMap c   }  data ProcedureScope c =@@ -64,8 +66,8 @@ applyProcedureScope f (ProcedureScope ctx fs) = map (uncurry (f ctx)) fs  getProcedureScopes :: (Show c, CompileErrorM m, MergeableM m) =>-  CategoryMap c -> DefinedCategory c -> m [ProcedureScope c]-getProcedureScopes ta (DefinedCategory c n pi _ _ fi ms ps fs) = do+  CategoryMap c -> ExprMap c -> DefinedCategory c -> m [ProcedureScope c]+getProcedureScopes ta em (DefinedCategory c n pi _ _ fi ms ps fs) = do   (_,t) <- getConcreteCategory ta (c,n)   let params = Positional $ getCategoryParams t   let params2 = Positional pi@@ -87,9 +89,9 @@   let cv = Map.union cm0 cm'   let tv = Map.union tm0 tm'   let vv = Map.union vm0 vm'-  let ctxC = ScopeContext ta n params params2 vm filters filters2 fa cv-  let ctxT = ScopeContext ta n params params2 vm filters filters2 fa tv-  let ctxV = ScopeContext ta n params params2 vm filters filters2 fa vv+  let ctxC = ScopeContext ta n params params2 vm filters filters2 fa cv em+  let ctxT = ScopeContext ta n params params2 vm filters filters2 fa tv em+  let ctxV = ScopeContext ta n params params2 vm filters filters2 fa vv em   return [ProcedureScope ctxC cp,ProcedureScope ctxT tp,ProcedureScope ctxV vp]   where     builtins t s0 = Map.filter ((<= s0) . vvScope) $ builtinVariables t
src/CompilerCxx/Category.hs view
@@ -41,6 +41,7 @@ import Base.CompileError import Base.Mergeable import Compilation.CompilerState+import Compilation.ProcedureContext (ExprMap) import Compilation.ScopeContext import CompilerCxx.CategoryContext import CompilerCxx.Code@@ -50,6 +51,7 @@ import Types.DefinedCategory import Types.GeneralType import Types.Positional+import Types.Pragma import Types.Procedure import Types.TypeCategory import Types.TypeInstance@@ -78,7 +80,8 @@     lmPublicLocal :: [AnyCategory c],     lmPrivateLocal :: [AnyCategory c],     lmTestingLocal :: [AnyCategory c],-    lmExternal :: [CategoryName]+    lmExternal :: [CategoryName],+    lmExprMap :: ExprMap c   }  data PrivateSource c =@@ -91,8 +94,11 @@  compileLanguageModule :: (Show c, CompileErrorM m, MergeableM m) =>   LanguageModule c -> [PrivateSource c] -> m ([CxxOutput],[CxxOutput])-compileLanguageModule (LanguageModule ns0 ns1 ns2 cs0 ps0 ts0 cs1 ps1 ts1 ex) xa = do+compileLanguageModule (LanguageModule ns0 ns1 ns2 cs0 ps0 ts0 cs1 ps1 ts1 ex em) xa = do   checkSupefluous $ Set.toList $ (Set.fromList ex) `Set.difference` ca+  -- Check public sources up front so that error messages aren't duplicated for+  -- every source file.+  _ <- tmTesting   (hxx1,cxx1) <- fmap mergeGeneratedP $ collectAllOrErrorM $ map (compileSourceP tmPublic  nsPublic)  cs1   (hxx2,cxx2) <- fmap mergeGeneratedP $ collectAllOrErrorM $ map (compileSourceP tmPrivate nsPrivate) ps1   (hxx3,cxx3) <- fmap mergeGeneratedP $ collectAllOrErrorM $ map (compileSourceP tmTesting nsTesting) ts1@@ -145,7 +151,7 @@     compileDefinition tm ns4 d = do       tm' <- mergeInternalInheritance tm d       let refines = dcName d `Map.lookup` tm >>= return . getCategoryRefines-      compileConcreteDefinition tm' ns4 refines d+      compileConcreteDefinition tm' em ns4 refines d     mapByName = Map.fromListWith (++) . map (\d -> (dcName d,[d]))     ca = Set.fromList $ map getCategoryName $ filter isValueConcrete (cs1 ++ ps1 ++ ts1)     checkLocals ds cs2 = mergeAllM $ map (checkLocal $ Set.fromList cs2) ds@@ -180,25 +186,25 @@  compileTestMain :: (Show c, CompileErrorM m, MergeableM m) =>   LanguageModule c -> PrivateSource c -> Expression c -> m CxxOutput-compileTestMain (LanguageModule ns0 ns1 ns2 cs0 ps0 ts0 cs1 ps1 ts1 _) ts2 e = do+compileTestMain (LanguageModule ns0 ns1 ns2 cs0 ps0 ts0 cs1 ps1 ts1 _ em) ts2 e = do   tm' <- tm-  (req,main) <- createTestFile tm' e+  (req,main) <- createTestFile tm' em e   return $ CxxOutput Nothing testFilename NoNamespace ([psNamespace ts2]++ns0++ns1++ns2) req main where   tm = foldM includeNewTypes defaultCategories [cs0,cs1,ps0,ps1,ts0,ts1,psCategory ts2]  compileModuleMain :: (Show c, CompileErrorM m, MergeableM m) =>   LanguageModule c -> [PrivateSource c] -> CategoryName -> FunctionName -> m CxxOutput-compileModuleMain (LanguageModule ns0 ns1 ns2 cs0 ps0 ts0 cs1 ps1 ts1 _) xa n f = do-  let resolved = filter (\d -> dcName d == n) $ concat $ map psDefine xa+compileModuleMain (LanguageModule ns0 ns1 ns2 cs0 ps0 _ cs1 ps1 _ _ em) xa n f = do+  let resolved = filter (\d -> dcName d == n) $ concat $ map psDefine $ filter (not . psTesting) xa   reconcile resolved   tm' <- tm   let cs = filter (\c -> getCategoryName c == n) $ concat $ map psCategory xa   tm'' <- includeNewTypes tm' cs-  (ns,main) <- createMainFile tm'' n f+  (ns,main) <- createMainFile tm'' em n f   return $ CxxOutput Nothing mainFilename NoNamespace ([ns]++ns0++ns1++ns2) [n] main where-    tm = foldM includeNewTypes defaultCategories [cs0,cs1,ps0,ps1,ts0,ts1]+    tm = foldM includeNewTypes defaultCategories [cs0,cs1,ps0,ps1]     reconcile [_] = return ()-    reconcile []  = compileErrorM $ "No matches for main category " ++ show n+    reconcile []  = compileErrorM $ "No matches for main category " ++ show n ++ " ($TestsOnly$ sources excluded)"     reconcile ds  =       flip reviseError ("Multiple matches for main category " ++ show n) $         mergeAllM $ map (\d -> compileError $ "Defined at " ++ formatFullContext (dcContext d)) ds@@ -259,7 +265,7 @@   CategoryMap c -> CategoryName -> m CxxOutput compileConcreteTemplate ta n = do   (_,t) <- getConcreteCategory ta ([],n)-  compileConcreteDefinition ta [] Nothing (defined t) `reviseError` ("In generated template for " ++ show n) where+  compileConcreteDefinition ta Map.empty [] Nothing (defined t) `reviseError` ("In generated template for " ++ show n) where     defined t = DefinedCategory {         dcContext = [],         dcName = getCategoryName t,@@ -273,6 +279,7 @@       }     defaultFail f = ExecutableProcedure {         epContext = [],+        epPragmas = [],         epEnd = [],         epName = sfName f,         epArgs = ArgValues [] $ Positional $ map createArg [1..(length $ pValues $ sfArgs f)],@@ -287,11 +294,12 @@     funcName f = show (sfType f) ++ "." ++ show (sfName f)  compileConcreteDefinition :: (Show c, CompileErrorM m, MergeableM m) =>-  CategoryMap c -> [Namespace] -> Maybe [ValueRefine c] -> DefinedCategory c -> m CxxOutput-compileConcreteDefinition ta ns rs dd@(DefinedCategory c n pi _ _ fi ms _ fs) = do+  CategoryMap c -> ExprMap c -> [Namespace] -> Maybe [ValueRefine c] ->+  DefinedCategory c -> m CxxOutput+compileConcreteDefinition ta em ns rs dd@(DefinedCategory c n pi _ _ fi ms _ fs) = do   (_,t) <- getConcreteCategory ta (c,n)   let r = CategoryResolver ta-  [cp,tp,vp] <- getProcedureScopes ta dd+  [cp,tp,vp] <- getProcedureScopes ta em dd   cf <- collectAllOrErrorM $ applyProcedureScope compileExecutableProcedure cp   tf <- collectAllOrErrorM $ applyProcedureScope compileExecutableProcedure tp   vf <- collectAllOrErrorM $ applyProcedureScope compileExecutableProcedure vp@@ -323,6 +331,7 @@       fmap indentCompiled $ mergeAllM $ map (createMember r allFilters) vm,       fmap indentCompiled $ createParams,       return $ indentCompiled $ onlyCode $ typeName n ++ "& parent;",+      return $ indentCompiled $ onlyCodes $ traceCreation (psProcedures vp),       return $ onlyCode "};"     ]   bottom <- mergeAllM $ [@@ -359,7 +368,7 @@         "CycleCheck<" ++ n2 ++ "> marker(*this);"       ]     categoryConstructor t ms2 = do-      ctx <- getContextForInit ta t dd CategoryScope+      ctx <- getContextForInit ta em t dd CategoryScope       initMembers <- runDataCompiler (sequence $ map compileLazyInit ms2) ctx       let initMembersStr = intercalate ", " $ cdOutput initMembers       let initColon = if null initMembersStr then "" else " : "@@ -378,7 +387,7 @@       let initParent = "parent(p)"       let initPassed = map (\(i,p) -> paramName p ++ "(*std::get<" ++ show i ++ ">(params))") $ zip ([0..] :: [Int]) ps2       let allInit = intercalate ", " $ initParent:initPassed-      ctx <- getContextForInit ta t dd TypeScope+      ctx <- getContextForInit ta em t dd TypeScope       initMembers <- runDataCompiler (sequence $ map initMember ms2) ctx       mergeAllM [           return $ onlyCode $ typeName n ++ "(" ++ allArgs ++ ") : " ++ allInit ++ " {",@@ -433,6 +442,9 @@           "const ParamTuple& params," ++           "const ValueTuple& args) final {"         ] ++ createFunctionDispatch n ValueScope fs2 ++ ["}"]+    traceCreation vp+      | any isTraceCreation $ concat $ map (epPragmas . snd) vp = [captureCreationTrace]+      | otherwise = []  commonDefineAll :: MergeableM m =>   AnyCategory c -> [Namespace] -> Maybe [ValueRefine c] -> CompiledData [String] ->@@ -758,9 +770,9 @@                            Set.toList req2  createMainFile :: (Show c, CompileErrorM m, MergeableM m) =>-  CategoryMap c -> CategoryName -> FunctionName -> m (Namespace,[String])-createMainFile tm n f = flip reviseError ("In the creation of the main binary procedure") $ do-  ca <- fmap indentCompiled (compileMainProcedure tm expr)+  CategoryMap c -> ExprMap c -> CategoryName -> FunctionName -> m (Namespace,[String])+createMainFile tm em n f = flip reviseError ("In the creation of the main binary procedure") $ do+  ca <- fmap indentCompiled (compileMainProcedure tm em expr)   let file = createMainCommon "main" ca   (_,t) <- getConcreteCategory tm ([],n)   return (getCategoryNamespace t,file) where@@ -769,10 +781,10 @@     expr = Expression [] (TypeCall [] mainType funcCall) []  createTestFile :: (Show c, CompileErrorM m, MergeableM m) =>-  CategoryMap c -> Expression c -> m ([CategoryName],[String])-createTestFile tm e = flip reviseError ("In the creation of the test binary procedure") $ do-  ca@(CompiledData req _) <- fmap indentCompiled (compileMainProcedure tm e)-  let file = createMainCommon "main" ca+  CategoryMap c -> ExprMap c  -> Expression c -> m ([CategoryName],[String])+createTestFile tm em e = flip reviseError ("In the creation of the test binary procedure") $ do+  ca@(CompiledData req _) <- fmap indentCompiled (compileMainProcedure tm em e)+  let file = createMainCommon "test" ca   return (Set.toList req,file)  getCategoryMentions :: AnyCategory c -> [CategoryName]
src/CompilerCxx/CategoryContext.hs view
@@ -19,7 +19,6 @@ {-# LANGUAGE Safe #-}  module CompilerCxx.CategoryContext (-  ScopeContext(..),   getContextForInit,   getMainContext,   getProcedureContext,@@ -44,8 +43,9 @@   getContextForInit :: (Show c, CompileErrorM m, MergeableM m) =>-  CategoryMap c -> AnyCategory c -> DefinedCategory c -> SymbolScope -> m (ProcedureContext c)-getContextForInit tm t d s = do+  CategoryMap c -> ExprMap c -> AnyCategory c -> DefinedCategory c ->+  SymbolScope -> m (ProcedureContext c)+getContextForInit tm em t d s = do   let ps = Positional $ getCategoryParams t   -- NOTE: This is always ValueScope for initializer checks.   let ms = filter ((== ValueScope) . dmScope) $ dcMembers d@@ -57,9 +57,7 @@   fa <- setInternalFunctions r t (dcFunctions d)   let typeInstance = TypeInstance (getCategoryName t) $ fmap (SingleType . JustParamName . vpParam) ps   let builtin = Map.filter ((== LocalScope) . vvScope) $ builtinVariables typeInstance-  -- Using < ensures that variables can only be referenced after initialization.-  -- TODO: This doesn't really help if access is done via a function.-  members <- mapMembers $ filter ((< s) . dmScope) (dcMembers d)+  members <- mapMembers $ filter ((<= s) . dmScope) (dcMembers d)   return $ ProcedureContext {       pcScope = s,       pcType = getCategoryName t,@@ -79,14 +77,16 @@       pcOutput = [],       pcDisallowInit = True,       pcLoopSetup = NotInLoop,-      pcCleanupSetup = CleanupSetup [] []+      pcCleanupSetup = CleanupSetup [] [],+      pcExprMap = em,+      pcNoTrace = False     }  getProcedureContext :: (Show c, CompileErrorM m, MergeableM m) =>   ScopeContext c -> ScopedFunction c -> ExecutableProcedure c -> m (ProcedureContext c)-getProcedureContext (ScopeContext tm t ps pi ms pa fi fa va)+getProcedureContext (ScopeContext tm t ps pi ms pa fi fa va em)                     ff@(ScopedFunction _ _ _ s as1 rs1 ps1 fs _)-                    (ExecutableProcedure _ _ _ as2 rs2 _) = do+                    (ExecutableProcedure _ _ _ _ as2 rs2 _) = do   rs' <- if isUnnamedReturns rs2             then return $ ValidatePositions rs1             else fmap (ValidateNames rs1 . Map.fromList) $ processPairs pairOutput rs1 (nrNames rs2)@@ -135,13 +135,15 @@       pcOutput = [],       pcDisallowInit = False,       pcLoopSetup = NotInLoop,-      pcCleanupSetup = CleanupSetup [] []+      pcCleanupSetup = CleanupSetup [] [],+      pcExprMap = em,+      pcNoTrace = False     }   where     pairOutput (PassedValue c1 t2) (OutputValue c2 n2) = return $ (n2,PassedValue (c2++c1) t2) -getMainContext :: CompileErrorM m => CategoryMap c -> m (ProcedureContext c)-getMainContext tm = return $ ProcedureContext {+getMainContext :: CompileErrorM m => CategoryMap c -> ExprMap c -> m (ProcedureContext c)+getMainContext tm em = return $ ProcedureContext {     pcScope = LocalScope,     pcType = CategoryNone,     pcExtParams = Positional [],@@ -160,5 +162,7 @@     pcOutput = [],     pcDisallowInit = False,     pcLoopSetup = NotInLoop,-    pcCleanupSetup = CleanupSetup [] []+    pcCleanupSetup = CleanupSetup [] [],+    pcExprMap = em,+    pcNoTrace = False   }
src/CompilerCxx/Code.hs view
@@ -22,6 +22,7 @@   ExprValue(..),   PrimitiveType(..),   categoryBase,+  captureCreationTrace,   clearCompiled,   emptyCode,   escapeChar,@@ -36,6 +37,7 @@   predTraceContext,   readStoredVariable,   setTraceContext,+  showCreationTrace,   startCleanupTracing,   startFunctionTracing,   typeBase,@@ -95,6 +97,12 @@ predTraceContext c   | null c = ""   | otherwise = "PRED_CONTEXT_POINT(" ++ escapeChars (formatFullContext c) ++ ")"++captureCreationTrace :: String+captureCreationTrace = "CAPTURE_CREATION"++showCreationTrace :: String+showCreationTrace = "TRACE_CREATION"  data PrimitiveType =   PrimBool |
src/CompilerCxx/Procedure.hs view
@@ -42,6 +42,8 @@ import Base.CompileError import Base.Mergeable import Compilation.CompilerState+import Compilation.ProcedureContext (ExprMap)+import Compilation.ScopeContext import CompilerCxx.CategoryContext import CompilerCxx.Code import CompilerCxx.Naming@@ -50,6 +52,7 @@ import Types.Function import Types.GeneralType import Types.Positional+import Types.Pragma import Types.Procedure import Types.TypeCategory import Types.TypeInstance@@ -59,23 +62,26 @@   ScopeContext c -> ScopedFunction c -> ExecutableProcedure c ->   m (CompiledData [String],CompiledData [String]) compileExecutableProcedure ctx ff@(ScopedFunction _ _ _ s as1 rs1 ps1 _ _)-                               pp@(ExecutableProcedure _ c n as2 rs2 p) = do+                               pp@(ExecutableProcedure c0 pragmas c n as2 rs2 p) = do   ctx' <- getProcedureContext ctx ff pp   output <- runDataCompiler compileWithReturn ctx'-  return (onlyCode header,wrapProcedure output)+  procedureTrace <- setProcedureTrace+  creationTrace  <- setCreationTrace+  return (onlyCode header,wrapProcedure output procedureTrace creationTrace)   where     t = scName ctx     compileWithReturn = do-      ctx0 <- getCleanContext+      ctx0 <- getCleanContext >>= lift . flip ccSetNoTrace (any isNoTrace pragmas)       compileProcedure ctx0 p >>= put       unreachable <- csIsUnreachable       when (not unreachable) $         doImplicitReturn [] `reviseErrorStateT`           ("In implicit return from " ++ show n ++ formatFullContextBrace c)-    wrapProcedure output =+    wrapProcedure output pt ct =       mergeAll $ [           onlyCode header2,-          indentCompiled $ onlyCode setProcedureTrace,+          indentCompiled $ onlyCodes pt,+          indentCompiled $ onlyCodes ct,           indentCompiled $ onlyCodes defineReturns,           indentCompiled $ onlyCodes nameParams,           indentCompiled $ onlyCodes nameArgs,@@ -101,7 +107,15 @@         "(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {"       | otherwise = undefined     returnType = "ReturnTuple"-    setProcedureTrace = startFunctionTracing $ show t ++ "." ++ show n+    setProcedureTrace+      | any isNoTrace pragmas = return []+      | otherwise             = return [startFunctionTracing $ show t ++ "." ++ show n]+    setCreationTrace+      | not $ any isTraceCreation pragmas = return []+      | s /= ValueScope =+          (compileWarningM $ "Creation tracing ignored for " ++ show s +++             " functions" ++ formatFullContextBrace c0) >> return []+      | otherwise = return [showCreationTrace]     defineReturns       | isUnnamedReturns rs2 = []       | otherwise            = [returnType ++ " returns(" ++ show (length $ pValues rs1) ++ ");"]@@ -125,7 +139,10 @@ compileCondition ctx c e = do   (e',ctx') <- lift $ runStateT compile ctx   lift (ccGetRequired ctx') >>= csRequiresTypes-  return $ predTraceContext c ++ e'+  noTrace <- csGetNoTrace+  if noTrace+     then return e'+     else return $ predTraceContext c ++ e'   where     compile = flip reviseErrorStateT ("In condition at " ++ formatFullContext c) $ do       (ts,e') <- compileExpression e@@ -151,11 +168,18 @@                                     formatFullContext (getStatementContext s) ++                                     " is unreachable" +maybeSetTrace :: (Show c, CompileErrorM m, MergeableM m,+                  CompilerContext c m [String] a) =>+  [c] -> CompilerState a m ()+maybeSetTrace c = do+  noTrace <- csGetNoTrace+  when (not noTrace) $ csWrite $ setTraceContext c+ compileStatement :: (Show c, CompileErrorM m, MergeableM m,                      CompilerContext c m [String] a) =>   Statement c -> CompilerState a m () compileStatement (EmptyReturn c) = do-  csWrite $ setTraceContext c+  maybeSetTrace c   doImplicitReturn c compileStatement (ExplicitReturn c es) = do   es' <- sequence $ map compileExpression $ pValues es@@ -164,7 +188,7 @@     -- Single expression, but possibly multi-return.     getReturn [(_,(Positional ts,e))] = do       csRegisterReturn c $ Just (Positional ts)-      csWrite $ setTraceContext c+      maybeSetTrace c       autoPositionalCleanup e     -- Multi-expression => must all be singles.     getReturn rs = do@@ -172,7 +196,7 @@         ("In return at " ++ formatFullContext c)       csRegisterReturn c $ Just $ Positional $ map (head . pValues . fst . snd) rs       let e = OpaqueMulti $ "ReturnTuple(" ++ intercalate "," (map (useAsUnwrapped . snd . snd) rs) ++ ")"-      csWrite $ setTraceContext c+      maybeSetTrace c       autoPositionalCleanup e     checkArity (_,Positional [_]) = return ()     checkArity (i,Positional ts)  =@@ -202,19 +226,22 @@   lift $ (checkValueTypeMatch r fa t0 formattedRequiredValue) `reviseError`     ("In fail call at " ++ formatFullContext c)   csSetNoReturn-  csWrite $ setTraceContext c+  maybeSetTrace c   csWrite ["BUILTIN_FAIL(" ++ useAsUnwrapped e0 ++ ")"] compileStatement (IgnoreValues c e) = do   (_,e') <- compileExpression e-  csWrite $ setTraceContext c+  maybeSetTrace c   csWrite ["(void) (" ++ useAsWhatever e' ++ ");"] compileStatement (Assignment c as e) = do   (ts,e') <- compileExpression e   r <- csResolver   fa <- csAllFilters+  -- Check for a count match first, to avoid the default error message.+  _ <- processPairsT alwaysPair (fmap assignableName as) ts `reviseErrorStateT`+    ("In assignment at " ++ formatFullContext c)   _ <- processPairsT (createVariable r fa) as ts `reviseErrorStateT`     ("In assignment at " ++ formatFullContext c)-  csWrite $ setTraceContext c+  maybeSetTrace c   variableTypes <- sequence $ map (uncurry getVariableType) $ zip (pValues as) (pValues ts)   assignAll (zip3 ([0..] :: [Int]) variableTypes (pValues as)) e'   where@@ -275,7 +302,7 @@   let Positional [t2] = ts   lift $ (checkValueTypeMatch r fa t2 t1) `reviseError`     ("In initialization of " ++ show n ++ " at " ++ formatFullContext c)-  csWrite [variableName n ++ "([]() { return " ++ writeStoredVariable t1 e' ++ "; })"]+  csWrite [variableName n ++ "([this]() { return " ++ writeStoredVariable t1 e' ++ "; })"]  compileVoidExpression :: (Show c, CompileErrorM m, MergeableM m,                          CompilerContext c m [String] a) =>@@ -367,9 +394,12 @@            ctx0' <- lift $ ccClearOutput ctxP            ctxCl <- compileProcedure ctx0' p2            p2' <- lift $ ccGetOutput ctxCl+           noTrace <- csGetNoTrace            -- TODO: It might be helpful to add a new trace-context line for this            -- so that the line that triggered the cleanup is still in the trace.-           let p2'' = ["{",startCleanupTracing] ++ p2' ++ ["}"]+           let p2'' = if noTrace+                      then []+                      else ["{",startCleanupTracing] ++ p2' ++ ["}"]            ctxP' <- lift $ ccPushCleanup ctxP (CleanupSetup [ctxCl] p2'')            return (ctxP',p2'',ctxCl)          Nothing -> return (ctxP,[],ctxP)@@ -595,7 +625,7 @@     fa <- csAllFilters     let vt = ValueType RequiredValue $ SingleType $ JustTypeInstance t     lift $ (checkValueTypeMatch r fa t' vt) `reviseError`-      ("In conversion at " ++ formatFullContext c)+      ("In converted call at " ++ formatFullContext c)     f' <- lookupValueFunction vt f     compileFunctionCall (Just $ useAsUnwrapped e') f' f   transform e (ValueCall c f) = do@@ -629,6 +659,9 @@   scoped <- autoScope s   let lazy = s == CategoryScope   return (Positional [t],readStoredVariable lazy t (scoped ++ variableName n))+compileExpressionStart (NamedMacro c n) = do+  e <- csExprLookup c n+  compileExpression e `reviseErrorStateT` ("In env lookup at " ++ formatFullContext c) compileExpressionStart (CategoryCall c t f@(FunctionCall _ n _ _)) = do   f' <- csGetCategoryFunction c (Just t) n   csRequiresTypes $ Set.fromList [t,sfType f']@@ -799,12 +832,12 @@     checkArity (i,Positional ts)  =       compileError $ "Return position " ++ show i ++ " has " ++ show (length ts) ++ " values but should have 1"     checkArg r fa t0 (i,t1) = do-      checkValueTypeMatch r fa t1 t0 `reviseError` ("In argument " ++ show i)+      checkValueTypeMatch r fa t1 t0 `reviseError` ("In argument " ++ show i ++ " to " ++ show (sfName f))  compileMainProcedure :: (Show c, CompileErrorM m, MergeableM m) =>-  CategoryMap c -> Expression c -> m (CompiledData [String])-compileMainProcedure tm e = do-  ctx <- getMainContext tm+  CategoryMap c -> ExprMap c -> Expression c -> m (CompiledData [String])+compileMainProcedure tm em e = do+  ctx <- getMainContext tm em   runDataCompiler compiler ctx where     procedure = Procedure [] [IgnoreValues [] e]     compiler = do
src/Config/LoadConfig.hs view
@@ -34,6 +34,7 @@ import Control.Monad (when) import Data.Hashable (hash) import Data.List (intercalate,isPrefixOf,isSuffixOf)+import Data.Maybe (isJust) import Data.Version (showVersion,versionBranch) import GHC.IO.Handle import Numeric (showHex)@@ -166,17 +167,16 @@        _ -> exitFailure  instance PathResolver Resolver where-  resolveModule (SimpleResolver ls ps) p m = do-    let allowGlobal = not (".." `elem` components)-    m0 <- if allowGlobal && any (\l -> isPrefixOf (l ++ "/") m) ls-             then getDataFileName m >>= return . (:[])-             else return []-    let m2 = if allowGlobal-                then map (</> m) ps-                else []-    firstExisting m $ [p</>m] ++ m0 ++ m2 where-      components = map stripSlash $ splitPath m-      stripSlash = reverse . dropWhile (== '/') . reverse+  resolveModule r p m = do+    ps2 <- potentialSystemPaths r m+    firstExisting m $ [p</>m] ++ ps2+  isSystemModule r p m = do+    isDir <- doesDirectoryExist (p</>m)+    if isDir+       then return False+       else do+         ps2 <- potentialSystemPaths r m+         findModule ps2 >>= return . not . isJust   resolveBaseModule _ = do     let m = "base"     m0 <- getDataFileName m@@ -185,13 +185,32 @@     b <- resolveBaseModule r     return (f == b) +potentialSystemPaths :: Resolver -> FilePath -> IO [FilePath]+potentialSystemPaths (SimpleResolver ls ps) m = do+  let allowGlobal = not (".." `elem` components)+  m0 <- if allowGlobal && any (\l -> isPrefixOf (l ++ "/") m) ls+           then getDataFileName m >>= return . (:[])+           else return []+  let m2 = if allowGlobal+              then map (</> m) ps+              else []+  return $ m0 ++ m2 where+    components = map stripSlash $ splitPath m+    stripSlash = reverse . dropWhile (== '/') . reverse+ firstExisting :: FilePath -> [FilePath] -> IO FilePath-firstExisting n [] = do-  -- TODO: Allow error recovery here.-  hPutStrLn stderr $ "Could not find path " ++ n-  exitFailure-firstExisting n (p:ps) = do+firstExisting m ps = do+  p <- findModule ps+  case p of+       Nothing -> do+         hPutStrLn stderr $ "Could not find path " ++ m+         exitFailure+       Just p2 -> return p2++findModule :: [FilePath] -> IO (Maybe FilePath)+findModule [] = return Nothing+findModule (p:ps) = do   isDir <- doesDirectoryExist p   if isDir-     then canonicalizePath p-     else firstExisting n ps+     then fmap Just $ canonicalizePath p+     else findModule ps
src/Config/Paths.hs view
@@ -25,5 +25,6 @@  class PathResolver r where   resolveModule :: r -> FilePath -> FilePath -> IO FilePath+  isSystemModule :: r -> FilePath -> FilePath -> IO Bool   resolveBaseModule :: r -> IO FilePath   isBaseModule :: r -> FilePath -> IO Bool
src/Parser/Pragma.hs view
@@ -19,10 +19,14 @@ {-# LANGUAGE Safe #-}  module Parser.Pragma (+  parseMacroName,   parsePragmas,   pragmaComment,+  pragmaExprLookup,+  pragmaNoTrace,   pragmaModuleOnly,-  pragmaTestsOnly+  pragmaTestsOnly,+  pragmaTraceCreation, ) where  import Control.Monad (when)@@ -40,6 +44,27 @@ pragmaModuleOnly = autoPragma "ModuleOnly" $ Left parseAt where   parseAt c = PragmaVisibility [c] ModuleOnly +parseMacroName :: Parser String+parseMacroName = labeled "macro name" $ do+  h <- upper <|> char '_'+  t <- many (upper <|> digit <|> char '_')+  optionalSpace+  return (h:t)++pragmaExprLookup :: Parser (Pragma SourcePos)+pragmaExprLookup = autoPragma "ExprLookup" $ Right parseAt where+  parseAt c = do+    name <- parseMacroName+    return $ PragmaExprLookup [c] name++pragmaNoTrace :: Parser (Pragma SourcePos)+pragmaNoTrace = autoPragma "NoTrace" $ Left parseAt where+  parseAt c = PragmaTracing [c] NoTrace++pragmaTraceCreation :: Parser (Pragma SourcePos)+pragmaTraceCreation = autoPragma "TraceCreation" $ Left parseAt where+  parseAt c = PragmaTracing [c] TraceCreation+ pragmaTestsOnly :: Parser (Pragma SourcePos) pragmaTestsOnly = autoPragma "TestsOnly" $ Left parseAt where   parseAt c = PragmaVisibility [c] TestsOnly@@ -61,13 +86,14 @@ autoPragma :: String -> Either (SourcePos -> a) (SourcePos -> Parser a) -> Parser a autoPragma p f = do   c <- getPosition-  try $ pragmaStart >> string_ p+  try $ pragmaStart >> string_ p >> notFollowedBy alphaNum   hasArgs <- (pragmaArgsStart >> optionalSpace >> return True) <|> return False   x <- delegate hasArgs f c   if hasArgs      then do        extra <- manyTill anyChar (string_ "]$")        when (not $ null extra) $ fail $ "Content unused by pragma " ++ p ++ ": " ++ extra+       optionalSpace      else sepAfter pragmaEnd   return x where     delegate False (Left f2)  c = return $ f2 c
src/Parser/Procedure.hs view
@@ -28,8 +28,10 @@ import qualified Data.Set as Set  import Parser.Common+import Parser.Pragma import Parser.TypeCategory () import Parser.TypeInstance ()+import Types.Pragma import Types.Positional import Types.Procedure import Types.TypeCategory@@ -43,10 +45,11 @@     as <- sourceParser     rs <- sourceParser     sepAfter (string_ "{")+    pragmas <- parsePragmas [pragmaNoTrace,pragmaTraceCreation]     pp <- sourceParser     c2 <- getPosition     sepAfter (string_ "}")-    return $ ExecutableProcedure [c] [c2] n as rs pp+    return $ ExecutableProcedure [c] pragmas [c2] n as rs pp  instance ParseFromSource (ArgValues SourcePos) where   sourceParser = labeled "procedure arguments" $ do@@ -413,6 +416,7 @@                  variableOrUnqualified <|>                  builtinCall <|>                  builtinValue <|>+                 exprLookup <|>                  try typeOrCategoryCall <|>                  typeCall where     parens = do@@ -440,6 +444,11 @@       c <- getPosition       n <- builtinValues       return $ NamedVariable (OutputValue [c] (VariableName n))+    exprLookup = do+      pragma <- pragmaExprLookup+      case pragma of+           (PragmaExprLookup c name) -> return $ NamedMacro c name+           _ -> undefined  -- Should be caught above.     variableOrUnqualified = do       c <- getPosition       n <- sourceParser :: Parser VariableName@@ -488,7 +497,7 @@     charLiteral = do       c <- getPosition       string_ "'"-      ch <- stringChar+      ch <- stringChar <|> char '"'       string_ "'"       optionalSpace       return $ CharLiteral [c] ch
src/Test/ParseMetadata.hs view
@@ -27,6 +27,10 @@ import Cli.CompileOptions import Cli.ParseMetadata import Config.Programs (VersionHash(..))+import System.FilePath+import Test.Common+import Types.Positional+import Types.Procedure import Types.TypeCategory (FunctionName(..),Namespace(..)) import Types.TypeInstance (CategoryName(..)) @@ -192,6 +196,7 @@     checkWriteThenRead $ ModuleConfig {       rmRoot = "/home/projects",       rmPath = "special",+      rmExprMap = [],       rmPublicDeps = [         "/home/project/public-dep1",         "/home/project/public-dep2"@@ -228,6 +233,19 @@       }     }, +    checkWriteFail "empty.+map" $ ModuleConfig {+      rmRoot = "/home/projects",+      rmPath = "special",+      rmExprMap = [("MACRO",Literal (StringLiteral [] "something"))],+      rmPublicDeps = [],+      rmPrivateDeps = [],+      rmExtraFiles = [],+      rmExtraPaths = [],+      rmMode = CompileIncremental {+        ciLinkFlags = []+      }+    },+     checkWriteFail "bad category" $ CategorySource {       csSource = "extra1.cpp",       csCategories = [@@ -297,7 +315,21 @@     checkWriteFail "compile mode" $ ExecuteTests { etInclude = [] },     checkWriteFail "compile mode" $ CompileRecompile,     checkWriteFail "compile mode" $ CompileRecompileRecursive,-    checkWriteFail "compile mode" $ CreateTemplates+    checkWriteFail "compile mode" $ CreateTemplates,++    checkParsesAs ("testfiles" </> "module-config.txt")+      (\m -> case rmExprMap m of+                  [("MY_MACRO",+                    Expression _ (BuiltinCall _+                      (FunctionCall _ BuiltinRequire (Positional [])+                        (Positional [Literal (EmptyLiteral _)]))) []),+                   ("MY_OTHER_MACRO",+                    Expression _+                      (TypeCall _ _+                        (FunctionCall _ (FunctionName "execute") (Positional [])+                          (Positional [Literal (StringLiteral _ "this is a string\n")]))) [])+                    ] -> True+                  _ -> False)   ]  checkWriteThenRead :: (Eq a, Show a, ConfigFormat a) => a -> IO (CompileInfo ())@@ -323,3 +355,16 @@             compileError $ "Expected pattern " ++ show p ++ " in error output but got\n" ++ text       | otherwise =           compileError $ "Expected write failure but got\n" ++ getCompileSuccess c++checkParsesAs :: (Show a, ConfigFormat a) => String -> (a -> Bool) -> IO (CompileInfo ())+checkParsesAs f m = do+  contents <- loadFile f+  let parsed = autoReadConfig f contents+  return $ check parsed contents+  where+    check x contents = do+      x' <- x `reviseError` ("While parsing " ++ f)+      when (not $ m x') $+        compileError $ "Failed to match after write/read\n" +++                       "Unparsed:\n" ++ contents ++ "\n" +++                       "Parsed:\n" ++ show x' ++ "\n"
src/Test/Pragma.hs view
@@ -42,11 +42,26 @@                   [PragmaVisibility _ TestsOnly] -> True                   _ -> False), +    checkParsesAs "$NoTrace$" (fmap (:[]) pragmaNoTrace)+      (\e -> case e of+                  [PragmaTracing _ NoTrace] -> True+                  _ -> False),++    checkParsesAs "$TraceCreation$" (fmap (:[]) pragmaTraceCreation)+      (\e -> case e of+                  [PragmaTracing _ TraceCreation] -> True+                  _ -> False),+     checkParsesAs "$Comment[ \"this is a pragma with args\" ]$" (fmap (:[]) pragmaComment)       (\e -> case e of                   [PragmaComment _ "this is a pragma with args"] -> True                   _ -> False), +    checkParsesAs "$ExprLookup[ \nMODULE_PATH /*comment*/\n ]$" (fmap (:[]) pragmaExprLookup)+      (\e -> case e of+                  [PragmaExprLookup _ "MODULE_PATH"] -> True+                  _ -> False),+     checkParsesAs "/*only comments*/" (parsePragmas [pragmaModuleOnly,pragmaTestsOnly])       (\e -> case e of                   [] -> True@@ -73,7 +88,9 @@      checkParseError "$TestsOnly[ extra ]$" "does not allow arguments" pragmaTestsOnly, -    checkParseError "$Comment$" "requires arguments" pragmaComment+    checkParseError "$Comment$" "requires arguments" pragmaComment,++    checkParseError "$ExprLookup[ \"bad stuff\" ]$" "macro name" pragmaExprLookup   ]  checkParsesAs :: String -> Parser [Pragma SourcePos] -> ([Pragma SourcePos] -> Bool) -> IO (CompileInfo ())
src/Test/Procedure.hs view
@@ -210,6 +210,11 @@     checkShortParseFail " x <- 'xx'",     checkShortParseSuccess " x <- \"'xx\"", +    checkParsesAs "'\"'"+                  (\e -> case e of+                              (Literal (CharLiteral _ '"')) -> True+                              _ -> False),+     checkParsesAs "1 + 2 < 4 && 3 >= 1 * 2 + 1 || true"                   (\e -> case e of                               (InfixExpression _
+ src/Test/testfiles/module-config.txt view
@@ -0,0 +1,12 @@+path: "testfiles"+expression_map: [+  expression_macro {+    name: MY_MACRO+    expression: require(empty)+  }+  expression_macro {+    name: MY_OTHER_MACRO+    expression: Type<Int>$execute("this is a string\012")+  }+]+mode: incremental {}
src/Types/Positional.hs view
@@ -46,8 +46,7 @@ processPairs f (Positional ps1) (Positional ps2)   | length ps1 == length ps2 =     collectAllOrErrorM $ map (uncurry f) (zip ps1 ps2)-  | otherwise =-    compileError $ "Parameter count mismatch: " ++ show ps1 ++ " vs. " ++ show ps2+  | otherwise = mismatchError ps1 ps2  processPairs_ :: (Show a, Show b, CompileErrorM m) =>   (a -> b -> m c) -> Positional a -> Positional b -> m ()@@ -58,5 +57,8 @@ processPairsT f (Positional ps1) (Positional ps2)   | length ps1 == length ps2 =     sequence $ map (uncurry f) (zip ps1 ps2)-  | otherwise =-    lift $ compileError $ "Parameter count mismatch: " ++ show ps1 ++ " vs. " ++ show ps2+  | otherwise = lift $ mismatchError ps1 ps2++mismatchError :: (Show a, Show b, CompileErrorM m) => [a] -> [b] -> m c+mismatchError ps1 ps2 = compileError $ "Count mismatch: " ++ show ps1 +++                                       " (expected) vs. " ++ show ps2 ++ " (actual)"
src/Types/Pragma.hs view
@@ -21,19 +21,33 @@ module Types.Pragma (   CodeVisibility(..),   Pragma(..),+  TraceType(..),   getPragmaContext,+  isExprLookup,   isModuleOnly,+  isNoTrace,   isTestsOnly,+  isTraceCreation, ) where   data CodeVisibility = ModuleOnly | TestsOnly deriving (Show) +data TraceType = NoTrace | TraceCreation deriving (Show)+ data Pragma c =   PragmaVisibility {     pvContext :: [c],     pvScopes :: CodeVisibility   } |+  PragmaExprLookup {+    pelContext :: [c],+    pelName :: String+  } |+  PragmaTracing {+    ptContext :: [c],+    ptType :: TraceType+  } |   -- This is mostly for testing purposes.   PragmaComment {     pcContext :: [c],@@ -43,11 +57,25 @@  getPragmaContext :: Pragma c -> [c] getPragmaContext (PragmaVisibility c _) = c+getPragmaContext (PragmaExprLookup c _) = c+getPragmaContext (PragmaTracing c _)    = c getPragmaContext (PragmaComment c _)    = c  isModuleOnly :: Pragma c -> Bool isModuleOnly (PragmaVisibility _ ModuleOnly) = True isModuleOnly _                               = False++isExprLookup :: Pragma c -> Bool+isExprLookup (PragmaExprLookup _ _) = True+isExprLookup _                      = False++isNoTrace :: Pragma c -> Bool+isNoTrace (PragmaTracing _ NoTrace) = True+isNoTrace _                         = False++isTraceCreation :: Pragma c -> Bool+isTraceCreation (PragmaTracing _ TraceCreation) = True+isTraceCreation _                               = False  isTestsOnly :: Pragma c -> Bool isTestsOnly (PragmaVisibility _ TestsOnly) = True
src/Types/Procedure.hs view
@@ -40,6 +40,7 @@   VariableName(..),   VoidExpression(..),   WhileLoop(..),+  assignableName,   getExpressionContext,   getStatementContext,   isDiscardedInput,@@ -48,6 +49,7 @@  import Data.List (intercalate) +import Types.Pragma import Types.Positional import Types.TypeCategory import Types.TypeInstance@@ -58,6 +60,7 @@ data ExecutableProcedure c =   ExecutableProcedure {     epContext :: [c],+    epPragmas :: [Pragma c],     epEnd :: [c],     epName :: FunctionName,     epArgs :: ArgValues c,@@ -118,6 +121,9 @@ isDiscardedInput (DiscardInput _) = True isDiscardedInput _                = False +discardInputName :: VariableName+discardInputName = VariableName "_"+ instance Show c => Show (InputValue c) where   show (InputValue c v) = show v ++ " /*" ++ formatFullContext c ++ "*/"   show (DiscardInput c) = "_" ++ " /*" ++ formatFullContext c ++ "*/"@@ -162,6 +168,11 @@   ExistingVariable (InputValue c)   deriving (Show) +assignableName :: Assignable c -> VariableName+assignableName (CreateVariable _ _ n)              = n+assignableName (ExistingVariable (InputValue _ n)) = n+assignableName _                                   = discardInputName+ data VoidExpression c =   Conditional (IfElifElse c) |   Loop (WhileLoop c) |@@ -222,6 +233,7 @@  data ExpressionStart c =   NamedVariable (OutputValue c) |+  NamedMacro [c] String |   CategoryCall [c] CategoryName (FunctionCall c) |   TypeCall [c] TypeInstanceOrParam (FunctionCall c) |   UnqualifiedCall [c] (FunctionCall c) |
src/Types/TypeCategory.hs view
@@ -165,13 +165,7 @@     formatValue v = show (pfParam v) ++ " " ++ show (pfFilter v) ++                     " " ++ formatContext (pfContext v)     formatInterfaceFunc f = showFunctionInContext "" "  " f-    formatConcreteFunc f = showFunctionInContext (showScope (sfScope f) ++ " ") "  " f--showScope :: SymbolScope -> String-showScope CategoryScope = "@category"-showScope TypeScope     = "@type"-showScope ValueScope    = "@value"-showScope LocalScope    = "@local"+    formatConcreteFunc f = showFunctionInContext (show (sfScope f) ++ " ") "  " f  getCategoryName :: AnyCategory c -> CategoryName getCategoryName (ValueInterface _ _ n _ _ _ _)  = n@@ -358,8 +352,14 @@   CategoryScope |   TypeScope |   ValueScope-  deriving (Eq,Ord,Show)+  deriving (Eq,Ord) +instance Show SymbolScope where+  show CategoryScope = "@category"+  show TypeScope     = "@type"+  show ValueScope    = "@value"+  show LocalScope    = "@local"+ partitionByScope :: (a -> SymbolScope) -> [a] -> ([a],[a],[a]) partitionByScope f = foldr bin empty where   empty = ([],[],[])@@ -846,8 +846,8 @@         where           checkMerge r3 fm3 f1 f2             | sfScope f1 /= sfScope f2 =-              compileError $ "Cannot merge " ++ showScope (sfScope f2) ++ " with " ++-                             showScope (sfScope f1) ++ " in function merge:\n---\n" +++              compileError $ "Cannot merge " ++ show (sfScope f2) ++ " with " +++                             show (sfScope f1) ++ " in function merge:\n---\n" ++                              show f2 ++ "\n  ->\n" ++ show f1             | otherwise =               flip reviseError ("In function merge:\n---\n" ++ show f2 ++@@ -889,7 +889,7 @@   }  instance Show c => Show (ScopedFunction c) where-  show f = showFunctionInContext (showScope (sfScope f) ++ " ") "" f+  show f = showFunctionInContext (show (sfScope f) ++ " ") "" f  showFunctionInContext :: Show c => String -> String -> ScopedFunction c -> String showFunctionInContext s indent (ScopedFunction cs n t _ as rs ps fa ms) =
src/Types/TypeInstance.hs view
@@ -275,7 +275,8 @@ checkValueTypeMatch r f ts1@(ValueType r1 t1) ts2@(ValueType r2 t2)   | r1 < r2 =     compileError $ "Cannot convert " ++ show ts1 ++ " to " ++ show ts2-  | otherwise = checkGeneralMatch r f Covariant t1 t2+  | otherwise = checkGeneralMatch r f Covariant t1 t2 `reviseError`+      ("Cannot convert " ++ show ts1 ++ " to " ++ show ts2)  checkGeneralMatch :: (MergeableM m, CompileErrorM m, TypeResolver r) =>   r -> ParamFilters -> Variance ->
tests/.zeolite-module view
@@ -1,5 +1,20 @@ root: ".." path: "tests"+expression_map: [+  // See expr-lookup.0rt and expr-lookup.0rx.+  expression_macro {+    name: INT_EXPR+    expression: 1+3+  }+  expression_macro {+    name: LOCAL_VAR+    expression: macroLocalVar+  }+  expression_macro {+    name: META_VAR+    expression: $ExprLookup[INT_EXPR]$*5+  }+] private_deps: [   "visibility"   "visibility2"
tests/cli-tests.sh view
@@ -78,6 +78,16 @@ }  +test_tests_only2() {+  local output=$(do_zeolite -p "$ZEOLITE_PATH" -r tests/tests-only2 || true)+  if ! echo "$output" | egrep -q 'main.+ Testing'; then+    show_message 'Expected Testing definition error from tests/tests-only:'+    echo "$output" 1>&2+    return 1+  fi+}++ test_module_only() {   local output=$(do_zeolite -p "$ZEOLITE_PATH" -R tests/module-only || true)   if ! echo "$output" | egrep -q 'Type1 not found'; then@@ -115,6 +125,7 @@ define $category {   run () {     \ LazyStream<Formatted>\$new()+        .append(\$ExprLookup[MODULE_PATH]\$ + "\n")         .append("Hello World\n")         .writeTo(SimpleOutput\$stdout())   }@@ -127,6 +138,11 @@     echo "$output" 1>&2     return 1   fi+  if ! echo "$output" | fgrep -xq "$PWD"; then+    show_message 'Expected $PWD in program output:'+    echo "$output" 1>&2+    return 1+  fi   execute rm -r "$temp" "$PWD/$category" || true } @@ -165,22 +181,44 @@ }  +test_example_hello() {+  do_zeolite -p "$ZEOLITE_PATH" -i lib/util -m HelloDemo example/hello -f+  do_zeolite -p "$ZEOLITE_PATH" -t example/hello+}+++test_example_tree() {+  do_zeolite -p "$ZEOLITE_PATH" -i lib/util -m TreeDemo example/tree -f+  do_zeolite -p "$ZEOLITE_PATH" -t example/tree+}+++test_example_parser() {+  do_zeolite -p "$ZEOLITE_PATH" -r example/parser -f+  do_zeolite -p "$ZEOLITE_PATH" -t example/parser+}++ run_all() {   ZEOLITE_PATH=$(do_zeolite --get-path | grep '^/')   echo 1>&2   local failed=0+  local list=()   for t in "$@"; do     show_message "Testing $t >>>"     echo 1>&2     if ! "$t"; then       failed=1+      list=("${list[@]}" "$t")     fi     echo 1>&2     show_message "<<< Testing $t"     echo 1>&2   done   if (($failed)); then-    show_message 'One or more tests failed.'+    for t in "${list[@]}"; do+      show_message "*** $t FAILED ***"+    done     return 1   else     show_message 'All tests passed.'@@ -190,11 +228,15 @@ ALL_TESTS=(   test_check_defs   test_tests_only+  test_tests_only2   test_module_only   test_templates   test_fast   test_bad_system_include   test_global_include+  test_example_hello+  test_example_tree+  test_example_parser )  run_all "${ALL_TESTS[@]}" 1>&2
+ tests/expr-lookup.0rp view
@@ -0,0 +1,26 @@+/* -----------------------------------------------------------------------------+Copyright 2020 Kevin P. Barry++Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at++    http://www.apache.org/licenses/LICENSE-2.0++Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+----------------------------------------------------------------------------- */++// Author: Kevin P. Barry [ta0kira@gmail.com]++$ModuleOnly$+$TestsOnly$++concrete ExprLookup {+  @type modulePath () -> (String)+  @type intExpr () -> (Int)+  @type localVar () -> (Int)+}
+ tests/expr-lookup.0rt view
@@ -0,0 +1,205 @@+/* -----------------------------------------------------------------------------+Copyright 2020 Kevin P. Barry++Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at++    http://www.apache.org/licenses/LICENSE-2.0++Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+----------------------------------------------------------------------------- */++// Author: Kevin P. Barry [ta0kira@gmail.com]++testcase "bad macro name" {+  error+  require "UNKNOWN_STRING .+not defined"+}++define Test {+  run () {+    String value <- $ExprLookup[UNKNOWN_STRING]$+  }+}++concrete Test {+  @type run () -> ()+}+++testcase "MODULE_PATH is absolute" {+  crash Test$run()+  require "Failed condition: /.+/tests"+}++define Test {+  run () {+    fail($ExprLookup[MODULE_PATH]$)+  }+}++concrete Test {+  @type run () -> ()+}+++testcase "MODULE_PATH from regular source" {+  crash Test$run()+  require "Failed condition: /.+/tests"+}++define Test {+  run () {+    fail(ExprLookup$modulePath())+  }+}++concrete Test {+  @type run () -> ()+}+++testcase "mismatched macro type" {+  error+  require "String.+Int"+}++define Test {+  run () {+    Int value <- $ExprLookup[MODULE_PATH]$+  }+}++concrete Test {+  @type run () -> ()+}+++testcase "macro used inline in expressions" {+  crash Test$run()+  require "Failed condition: /.+/tests is the path"+}++define Test {+  run () {+    fail($ExprLookup[MODULE_PATH]$ + " is the path")+  }+}++concrete Test {+  @type run () -> ()+}+++testcase "constant defined in .zeolite-module" {+  success Test$run()+}++define Test {+  run () {+    \ Testing$check<Int>($ExprLookup[INT_EXPR]$,4)+  }+}++concrete Test {+  @type run () -> ()+}+++testcase "constant defined in .zeolite-module from regular source" {+  success Test$run()+}++define Test {+  run () {+    \ Testing$check<Int>(ExprLookup$intExpr(),4)+  }+}++concrete Test {+  @type run () -> ()+}+++testcase "in context defined in .zeolite-module" {+  success Test$run()+}++define Test {+  @category String macroLocalVar <- "hello"++  run () {+    \ Testing$check<String>($ExprLookup[LOCAL_VAR]$,"hello")+  }+}++concrete Test {+  @type run () -> ()+}+++testcase "undefined in context defined in .zeolite-module" {+  error+  require "\.zeolite-module"+  require "macroLocalVar"+}++define Test {+  run () {+    String value <- $ExprLookup[LOCAL_VAR]$+  }+}++concrete Test {+  @type run () -> ()+}+++testcase "in context defined in .zeolite-module from regular source" {+  success Test$run()+}++define Test {+  run () {+    \ Testing$check<Int>(ExprLookup$localVar(),99)+  }+}++concrete Test {+  @type run () -> ()+}+++testcase "nested expression defined in .zeolite-module" {+  success Test$run()+}++define Test {+  run () {+    \ Testing$check<Int>($ExprLookup[META_VAR]$,20)+  }+}++concrete Test {+  @type run () -> ()+}+++testcase "function called on expression" {+  success Test$run()+}++define Test {+  run () {+    \ Testing$check<String>($ExprLookup[INT_EXPR]$.formatted(),"4")+  }+}++concrete Test {+  @type run () -> ()+}
+ tests/expr-lookup.0rx view
@@ -0,0 +1,34 @@+/* -----------------------------------------------------------------------------+Copyright 2020 Kevin P. Barry++Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at++    http://www.apache.org/licenses/LICENSE-2.0++Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+----------------------------------------------------------------------------- */++// Author: Kevin P. Barry [ta0kira@gmail.com]++$TestsOnly$++define ExprLookup {+  modulePath () {+    return $ExprLookup[MODULE_PATH]$+  }++  intExpr () {+    return $ExprLookup[INT_EXPR]$+  }++  localVar () {+    Int macroLocalVar <- 99+    return $ExprLookup[LOCAL_VAR]$+  }+}
tests/member-init.0rt view
@@ -93,6 +93,29 @@ }  +testcase "@category member refers to @category member" {+  success Test$run()+}++define Test {+  @category Int value1 <- 1+  @category Int value2 <- value1+1++  @category get () -> (Int)+  get () {+    return value2+  }++  run () {+    \ Testing$check<Int>(get(),2)+  }+}++concrete Test {+  @type run () -> ()+}++ testcase "@category member is lazy" {   success Test$run() }
tests/tests-only/README.md view
@@ -1,4 +1,4 @@-# `$TestsOnly$` Pragma Test+# `$TestsOnly$` Visibility Test - Incremental  Compiling this module should **always fail**. It tests that the `$TestsOnly$` pragma limits visibility to only `.0rx` sources that also have the `$TestsOnly$`
+ tests/tests-only2/.zeolite-module view
@@ -0,0 +1,6 @@+root: "../.."+path: "tests/tests-only2"+mode: binary {+  category: Testing+  function: run+}
+ tests/tests-only2/README.md view
@@ -0,0 +1,18 @@+# `$TestsOnly$` Visibility Test - Binaries++Compiling this module should **always fail**. It tests that the `$TestsOnly$`+pragma limits visibility of `.0rx` categories to only test binaries.++To compile:++```shell+ZEOLITE_PATH=$(zeolite --get-path)+zeolite -p $ZEOLITE_PATH -r tests/tests-only2+```++The compiler errors should look something like this:++```text+Compiler errors:+No matches for main category Testing ($TestsOnly$ sources excluded)+```
+ tests/tests-only2/private.0rx view
@@ -0,0 +1,23 @@+/* -----------------------------------------------------------------------------+Copyright 2020 Kevin P. Barry++Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at++    http://www.apache.org/licenses/LICENSE-2.0++Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+----------------------------------------------------------------------------- */++// Author: Kevin P. Barry [ta0kira@gmail.com]++$TestsOnly$++define Testing {+  run () {}+}
+ tests/tests-only2/public.0rp view
@@ -0,0 +1,23 @@+/* -----------------------------------------------------------------------------+Copyright 2020 Kevin P. Barry++Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at++    http://www.apache.org/licenses/LICENSE-2.0++Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+----------------------------------------------------------------------------- */++// Author: Kevin P. Barry [ta0kira@gmail.com]++$TestsOnly$++concrete Testing {+  @type run () -> ()+}
+ tests/tracing.0rt view
@@ -0,0 +1,177 @@+/* -----------------------------------------------------------------------------+Copyright 2020 Kevin P. Barry++Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at++    http://www.apache.org/licenses/LICENSE-2.0++Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+----------------------------------------------------------------------------- */++// Author: Kevin P. Barry [ta0kira@gmail.com]++testcase "NoTrace skips tracing" {+  crash Test$run()+  require "message"+  require "Test\.run"+  require "Test\.error"+  exclude "Test\.noTrace"+}++define Test {+  run () {+    \ noTrace()+  }++  @type noTrace () -> ()+  noTrace () { $NoTrace$+    \ error()+  }++  @type error () -> ()+  error () {+    fail("message")+  }+}++concrete Test {+  @type run () -> ()+}+++testcase "TraceCreation captures trace" {+  crash Test$run()+  require "message"+  require "Type.+created at"+}++concrete Type {+  @type create () -> (Type)+  @value call () -> ()+}++define Type {+  create () {+    return Type{ }+  }++  call ()  { $TraceCreation$+    fail("message")+  }+}++define Test {+  run () {+    Type value <- Type$create()+    \ value.call()+  }+}++concrete Test {+  @type run () -> ()+}+++testcase "TraceCreation ignored in @type" {+  success Test$run()+  require compiler "tracing ignored"+}++define Test {+  run () { $TraceCreation$ }+}++concrete Test {+  @type run () -> ()+}+++testcase "TraceCreation still works with NoTrace" {+  crash Test$run()+  require "message"+  require "Type.+created at"+  exclude "Type\.call"+}++concrete Type {+  @type create () -> (Type)+  @value call () -> ()+}++define Type {+  create () {+    return Type{ }+  }++  call ()  { $NoTrace$ $TraceCreation$+    fail("message")+  }+}++define Test {+  run () {+    Type value <- Type$create()+    \ value.call()+  }+}++concrete Test {+  @type run () -> ()+}+++testcase "TraceCreation only uses the latest" {+  crash Test$run()+  require "message"+  require "Type1.+created at"+  exclude "Type2.+created at"+}++concrete Type1 {+  @type create () -> (Type1)+  @value call () -> ()+}++define Type1 {+  create () {+    return Type1{ }+  }++  call ()  { $TraceCreation$+    fail("message")+  }+}++concrete Type2 {+  @type create () -> (Type2)+  @value call () -> ()+}++define Type2 {+  @value Type1 value++  create () {+    return Type2{ Type1$create() }+  }++  call ()  { $TraceCreation$+    \ value.call()+  }+}++define Test {+  run () {+    Type2 value <- Type2$create()+    \ value.call()+  }+}++concrete Test {+  @type run () -> ()+}
zeolite-lang.cabal view
@@ -1,7 +1,7 @@ cabal-version:       2.2  name:                zeolite-lang-version:             0.5.0.0+version:             0.6.0.0 synopsis:            Zeolite is a statically-typed, general-purpose programming language.  description:@@ -62,7 +62,8 @@ extra-source-files:  ChangeLog.md,                      src/Test/testfiles/*.0rp,                      src/Test/testfiles/*.0rt,-                     src/Test/testfiles/*.0rx+                     src/Test/testfiles/*.0rx,+                     src/Test/testfiles/*.txt  data-files:          base/.zeolite-module,                      base/*.0rp,@@ -72,10 +73,12 @@                      base/*.h,                      example/hello/README.md,                      example/hello/*.0rx,-                     example/regex/README.md,-                     example/regex/*.0rp,-                     example/regex/*.0rt,-                     example/regex/*.0rx,+                     example/parser/.zeolite-module,+                     example/parser/README.md,+                     example/parser/*.0rp,+                     example/parser/*.0rt,+                     example/parser/*.0rx,+                     example/parser/*.txt,                      example/tree/README.md,                      example/tree/*.0rp,                      example/tree/*.0rt,@@ -115,6 +118,10 @@                      tests/tests-only/.zeolite-module,                      tests/tests-only/*.0rp,                      tests/tests-only/*.0rx,+                     tests/tests-only2/README.md,+                     tests/tests-only2/.zeolite-module,+                     tests/tests-only2/*.0rp,+                     tests/tests-only2/*.0rx,                      tests/visibility/.zeolite-module,                      tests/visibility/*.0rp,                      tests/visibility/*.0rx,