You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@daffodil.apache.org by sl...@apache.org on 2023/02/06 21:01:39 UTC

[daffodil] 02/02: Refactor for OSGI compliance

This is an automated email from the ASF dual-hosted git repository.

slawrence pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/daffodil.git

commit 15cb34330e5b9e4981d4b1cc1373c2cbca3e8b31
Author: Mike McGann <mm...@owlcyberdefense.com>
AuthorDate: Mon Feb 6 10:54:27 2023 -0500

    Refactor for OSGI compliance
    
    Daffodil shares package namespaces across different libraries such that
    library A can have a class named foo.bar.One and library B can have a
    class named foo.bar.Two. This can cause issues with class loaders that
    prohibit different jars from owning the same package (i.e., in an
    OSGI container). This factoring ensures that each package is owned by
    a single jar.
    
    DAFFODIL-2683
---
 .../org/apache/daffodil/cliTest/TestCLIUdfs.scala  |    4 +-
 .../scala/org/apache/daffodil/InfosetTypes.scala   |  710 ------
 .../src/main/scala/org/apache/daffodil/Main.scala  | 1528 ------------
 .../org/apache/daffodil/cli/InfosetTypes.scala     |  710 ++++++
 .../main/scala/org/apache/daffodil/cli/Main.scala  | 1528 ++++++++++++
 .../daffodil/cli/debugger/CLIDebuggerRunner.scala  |  165 ++
 .../daffodil/debugger/CLIDebuggerRunner.scala      |  163 --
 daffodil-cli/src/templates/bash-template           |    2 +-
 daffodil-cli/src/templates/bat-template            |    2 +-
 .../daffodil/{CLI => cli}/ABC_IBM_invalid.dfdl.xsd |    0
 .../daffodil/{CLI => cli}/bits_parsing.dfdl.xsd    |    0
 .../{CLI => cli}/blob_backtracking.dfdl.xsd        |    0
 .../{CLI => cli}/charClassEntities.dfdl.xsd        |    0
 .../daffodil/{CLI => cli}/cli_schema.dfdl.xsd      |    0
 .../daffodil/{CLI => cli}/cli_schema_02.dfdl.xsd   |    0
 .../daffodil/{CLI => cli}/cli_schema_03.dfdl.xsd   |    0
 .../daffodil/{CLI => cli}/cli_schema_04.dfdl.xsd   |    0
 .../daffodil/{CLI => cli}/complex_types.dfdl.xsd   |    0
 .../daffodil/{CLI => cli}/global_element.dfdl.xsd  |    0
 .../{CLI => cli}/global_element_import.dfdl.xsd    |    0
 .../apache/daffodil/{CLI => cli}/input/gen_blob.py |    0
 .../apache/daffodil/{CLI => cli}/input/hextest.txt |  Bin
 .../apache/daffodil/{CLI => cli}/input/input1.txt  |    0
 .../daffodil/{CLI => cli}/input/input1.txt.xml     |    0
 .../apache/daffodil/{CLI => cli}/input/input10.txt |    0
 .../apache/daffodil/{CLI => cli}/input/input11.txt |    0
 .../apache/daffodil/{CLI => cli}/input/input12.txt |    0
 .../apache/daffodil/{CLI => cli}/input/input13.txt |    0
 .../apache/daffodil/{CLI => cli}/input/input14.exi |    0
 .../daffodil/{CLI => cli}/input/input14.exisa      |    0
 .../apache/daffodil/{CLI => cli}/input/input14.txt |    0
 .../apache/daffodil/{CLI => cli}/input/input15.txt |    0
 .../apache/daffodil/{CLI => cli}/input/input16.txt |    0
 .../apache/daffodil/{CLI => cli}/input/input18.exi |    0
 .../daffodil/{CLI => cli}/input/input18.exisa      |    0
 .../daffodil/{CLI => cli}/input/input18.json       |    0
 .../apache/daffodil/{CLI => cli}/input/input18.txt |    0
 .../apache/daffodil/{CLI => cli}/input/input19.txt |  Bin
 .../apache/daffodil/{CLI => cli}/input/input2.txt  |    0
 .../apache/daffodil/{CLI => cli}/input/input3.txt  |    0
 .../apache/daffodil/{CLI => cli}/input/input4.txt  |    0
 .../apache/daffodil/{CLI => cli}/input/input5.txt  |    0
 .../apache/daffodil/{CLI => cli}/input/input6.txt  |    0
 .../apache/daffodil/{CLI => cli}/input/input7.txt  |    0
 .../apache/daffodil/{CLI => cli}/input/input8.txt  |    0
 .../apache/daffodil/{CLI => cli}/input/input9.txt  |    0
 .../daffodil/{CLI => cli}/input/input9.txt.xml     |    0
 .../daffodil/{CLI => cli}/input/inputBig1M.txt     |    0
 .../apache/daffodil/{CLI => cli}/input/prefix.txt  |    0
 .../daffodil/{CLI => cli}/input/test_DFDL-714.txt  |    0
 .../apache/daffodil/{CLI => cli}/input/uuid.txt    |    0
 .../daffodil/{CLI => cli}/large_blob.dfdl.xsd      |    0
 .../daffodil/{CLI => cli}/prefixed_length.dfdl.xsd |    0
 .../apache/daffodil/{CLI => cli}/single.dfdl.xsd   |    0
 .../daffodil/{CLI => cli}/single_conf_bad.txt      |    0
 .../{CLI => cli}/suppressWarnTest.dfdl.xsd         |    0
 .../testNonCompatibleImplementation.tdml           |    0
 .../daffodil/{CLI => cli}/trace_input.dfdl.xsd     |    0
 .../{CLI => cli}/unqualified_path_step.dfdl.xsd    |    0
 .../{CLI => cli}/xcatalog_import_failure.dfdl.xsd  |    0
 .../daffodil/{CLI => cli}/xcatalog_invalid.xml     |    0
 .../test/scala/org/apache/daffodil/CLI/Util.scala  |  629 -----
 .../org/apache/daffodil/cli/cliTest/TestBlob.scala |  178 ++
 .../daffodil/cli/cliTest/TestCLIDebugger.scala     | 1125 +++++++++
 .../daffodil/cli/cliTest/TestCLIGenerateC.scala    |  134 ++
 .../daffodil/cli/cliTest/TestCLIParsing.scala      |  719 ++++++
 .../daffodil/cli/cliTest/TestCLIPerformance.scala  |  177 ++
 .../daffodil/cli/cliTest/TestCLISaveParser.scala   |  269 +++
 .../daffodil/cli/cliTest/TestCLITunables.scala     |  199 ++
 .../daffodil/cli/cliTest/TestCLIUnparsing.scala    |  341 +++
 .../apache/daffodil/cli/cliTest/TestCLItdml.scala  |  127 +
 .../daffodil/cli/cliTest/TestEXIEncodeDecode.scala |  110 +
 .../cli/cliTest/TestValidatorPatterns.scala        |   51 +
 .../org/apache/daffodil/cli/cliTest/Util.scala     |  629 +++++
 .../cli/cliTest/schematron/TestEmbedded.scala      |  138 ++
 .../cli/cliTest/schematron/TestSvrlOutput.scala    |  168 ++
 .../cli/cliTest/schematron/TestValidating.scala    |   59 +
 .../org/apache/daffodil/cliTest/TestBlob.scala     |  178 --
 .../apache/daffodil/cliTest/TestCLIDebugger.scala  | 1125 ---------
 .../apache/daffodil/cliTest/TestCLIGenerateC.scala |  134 --
 .../apache/daffodil/cliTest/TestCLIParsing.scala   |  719 ------
 .../daffodil/cliTest/TestCLIPerformance.scala      |  177 --
 .../daffodil/cliTest/TestCLISaveParser.scala       |  269 ---
 .../apache/daffodil/cliTest/TestCLITunables.scala  |  199 --
 .../apache/daffodil/cliTest/TestCLIUnparsing.scala |  341 ---
 .../org/apache/daffodil/cliTest/TestCLItdml.scala  |  127 -
 .../daffodil/cliTest/TestEXIEncodeDecode.scala     |  110 -
 .../daffodil/cliTest/TestValidatorPatterns.scala   |   51 -
 .../daffodil/cliTest/schematron/TestEmbedded.scala |  138 --
 .../cliTest/schematron/TestSvrlOutput.scala        |  168 --
 .../cliTest/schematron/TestValidating.scala        |   59 -
 .../org/apache/daffodil/compiler/Compiler.scala    |  382 ---
 .../org/apache/daffodil/compiler/RootSpec.scala    |   48 -
 .../apache/daffodil/core/compiler/Compiler.scala   |  382 +++
 .../apache/daffodil/core/compiler/RootSpec.scala   |   48 +
 .../apache/daffodil/core/dpath/Conversions.scala   |  282 +++
 .../daffodil/core/dpath/DFDLExpressionParser.scala |  386 +++
 .../apache/daffodil/core/dpath/Expression.scala    | 2540 ++++++++++++++++++++
 .../apache/daffodil/core/dpath/NodeInfoUtils.scala |  139 ++
 .../core/dsom/AnnotatedSchemaComponent.scala       |  494 ++++
 .../apache/daffodil/core/dsom/ChoiceGroup.scala    |  219 ++
 .../daffodil/core/dsom/CompiledExpression.scala    |  266 ++
 .../apache/daffodil/core/dsom/ComplexTypes.scala   |  109 +
 .../apache/daffodil/core/dsom/DFDLAnnotation.scala |   61 +
 .../apache/daffodil/core/dsom/DFDLAssertion.scala  |  161 ++
 .../daffodil/core/dsom/DFDLDefineFormat.scala      |   45 +
 .../daffodil/core/dsom/DFDLDefineVariable.scala    |  169 ++
 .../core/dsom/DFDLDefiningAnnotation.scala         |   38 +
 .../daffodil/core/dsom/DFDLEscapeScheme.scala      |  194 ++
 .../org/apache/daffodil/core/dsom/DFDLFormat.scala |   75 +
 .../daffodil/core/dsom/DFDLFormatAnnotation.scala  |  311 +++
 .../apache/daffodil/core/dsom/DFDLProperty.scala   |  108 +
 .../apache/daffodil/core/dsom/DFDLSchemaFile.scala |  222 ++
 .../apache/daffodil/core/dsom/DFDLStatement.scala  |   33 +
 .../daffodil/core/dsom/DFDLStatementMixin.scala    |  231 ++
 .../apache/daffodil/core/dsom/ElementBase.scala    | 1210 ++++++++++
 .../daffodil/core/dsom/ElementDeclMixin.scala      |  162 ++
 .../org/apache/daffodil/core/dsom/ElementRef.scala |  107 +
 .../daffodil/core/dsom/EscapeSchemeRefMixin.scala  |   71 +
 .../org/apache/daffodil/core/dsom/Facets.scala     |  546 +++++
 .../daffodil/core/dsom/GlobalElementDecl.scala     |   52 +
 .../org/apache/daffodil/core/dsom/GroupDef.scala   |  170 ++
 .../org/apache/daffodil/core/dsom/GroupRef.scala   |  174 ++
 .../org/apache/daffodil/core/dsom/IIBase.scala     |  291 +++
 .../org/apache/daffodil/core/dsom/Import.scala     |  144 ++
 .../org/apache/daffodil/core/dsom/Include.scala    |   67 +
 .../core/dsom/InitiatedTerminatedMixin.scala       |  122 +
 .../daffodil/core/dsom/LocalElementDecl.scala      |   98 +
 .../daffodil/core/dsom/LocalElementMixin.scala     |  113 +
 .../org/apache/daffodil/core/dsom/ModelGroup.scala |  369 +++
 .../org/apache/daffodil/core/dsom/NamedMixin.scala |  215 ++
 .../org/apache/daffodil/core/dsom/Nesting.scala    |  142 ++
 .../apache/daffodil/core/dsom/ParticleMixin.scala  |  234 ++
 .../apache/daffodil/core/dsom/PropProviders.scala  |  254 ++
 .../RawCommonRuntimeValuedPropertiesMixin.scala    |  114 +
 .../apache/daffodil/core/dsom/RealTermMixin.scala  |   54 +
 .../apache/daffodil/core/dsom/ReptypeMixins.scala  |   65 +
 .../daffodil/core/dsom/RestrictionUnion.scala      |  534 ++++
 .../scala/org/apache/daffodil/core/dsom/Root.scala |  167 ++
 .../daffodil/core/dsom/RuntimePropertyMixins.scala |  870 +++++++
 .../daffodil/core/dsom/SchemaComponent.scala       |  320 +++
 .../core/dsom/SchemaComponentFactory.scala         |  102 +
 .../SchemaComponentIncludesAndImportsMixin.scala   |   38 +
 .../dsom/SchemaDocIncludesAndImportsMixin.scala    |  165 ++
 .../apache/daffodil/core/dsom/SchemaDocument.scala |  262 ++
 .../org/apache/daffodil/core/dsom/SchemaSet.scala  |  677 ++++++
 .../dsom/SchemaSetIncludesAndImportsMixins.scala   |   84 +
 .../apache/daffodil/core/dsom/SequenceGroup.scala  |  367 +++
 .../apache/daffodil/core/dsom/SimpleTypes.scala    |  592 +++++
 .../scala/org/apache/daffodil/core/dsom/Term.scala |  549 +++++
 .../daffodil/core/dsom/TermEncodingMixin.scala     |  231 ++
 .../apache/daffodil/core/dsom/UnparserInfo.scala   |   39 +
 .../org/apache/daffodil/core/dsom/package.scala    |  118 +
 .../core/dsom/walker/AbstractDSOMWalker.scala      |  176 ++
 .../daffodil/core/grammar/AlignedMixin.scala       |  362 +++
 .../daffodil/core/grammar/BitOrderMixin.scala      |   35 +
 .../daffodil/core/grammar/ByteOrderMixin.scala     |   35 +
 .../daffodil/core/grammar/ChoiceGrammarMixin.scala |   35 +
 .../core/grammar/ElementBaseGrammarMixin.scala     | 1402 +++++++++++
 .../core/grammar/ElementDeclGrammarMixin.scala     |   33 +
 .../org/apache/daffodil/core/grammar/Grammar.scala |  140 ++
 .../daffodil/core/grammar/GrammarMixin.scala       |   51 +
 .../apache/daffodil/core/grammar/GrammarTerm.scala |  128 +
 .../core/grammar/HasStatementsGrammarMixin.scala   |   38 +
 .../core/grammar/LocalElementGrammarMixin.scala    |   25 +
 .../core/grammar/ModelGroupGrammarMixin.scala      |  102 +
 .../apache/daffodil/core/grammar/Production.scala  |  105 +
 .../core/grammar/SchemaSetGrammarMixin.scala       |   43 +
 .../core/grammar/SequenceGrammarMixin.scala        |  206 ++
 .../daffodil/core/grammar/TermGrammarMixin.scala   |   80 +
 .../org/apache/daffodil/core/grammar/package.scala |  121 +
 .../core/grammar/primitives/ChoiceCombinator.scala |  280 +++
 .../primitives/DelimiterAndEscapeRelated.scala     |  106 +
 .../grammar/primitives/ElementCombinator.scala     |  409 ++++
 .../grammar/primitives/HiddenGroupCombinator.scala |   37 +
 .../core/grammar/primitives/LayeredSequence.scala  |   73 +
 .../grammar/primitives/NilEmptyCombinators.scala   |   59 +
 .../daffodil/core/grammar/primitives/Padded.scala  |  134 ++
 .../core/grammar/primitives/PatternChecker.scala   |   68 +
 .../core/grammar/primitives/Primitives.scala       |   37 +
 .../core/grammar/primitives/PrimitivesBCD.scala    |   98 +
 .../primitives/PrimitivesBinaryBoolean.scala       |   52 +
 .../primitives/PrimitivesBinaryNumber.scala        |  147 ++
 .../grammar/primitives/PrimitivesDateTime.scala    |  284 +++
 .../grammar/primitives/PrimitivesDelimiters.scala  |  104 +
 .../grammar/primitives/PrimitivesExpressions.scala |  367 +++
 .../grammar/primitives/PrimitivesFraming.scala     |   86 +
 .../primitives/PrimitivesIBM4690Packed.scala       |   96 +
 .../grammar/primitives/PrimitivesLengthKind.scala  |  344 +++
 .../core/grammar/primitives/PrimitivesNil.scala    |   63 +
 .../PrimitivesNonBaseTenTextNumber.scala           |   42 +
 .../core/grammar/primitives/PrimitivesPacked.scala |  103 +
 .../grammar/primitives/PrimitivesTextBoolean.scala |   41 +
 .../grammar/primitives/PrimitivesTextNumber.scala  |  596 +++++
 .../core/grammar/primitives/PrimitivesZoned.scala  |  172 ++
 .../core/grammar/primitives/SequenceChild.scala    |  564 +++++
 .../grammar/primitives/SequenceCombinator.scala    |  204 ++
 .../core/grammar/primitives/SpecifiedLength.scala  |  225 ++
 .../core/runtime1/ChoiceTermRuntime1Mixin.scala    |  206 ++
 .../core/runtime1/ElementBaseRuntime1Mixin.scala   |  231 ++
 .../daffodil/core/runtime1/GramRuntime1Mixin.scala |   62 +
 .../LocalElementDeclBaseRuntime1Mixin.scala        |   25 +
 .../core/runtime1/ModelGroupRuntime1Mixin.scala    |   50 +
 .../runtime1/SchemaComponentRuntime1Mixin.scala    |   48 +
 .../core/runtime1/SchemaSetRuntime1Mixin.scala     |   97 +
 .../core/runtime1/SequenceTermRuntime1Mixin.scala  |   82 +
 .../core/runtime1/SimpleTypeRuntime1Mixin.scala    |   62 +
 .../daffodil/core/runtime1/TermRuntime1Mixin.scala |  612 +++++
 .../core/runtime1/VariableMapFactory.scala         |   30 +
 .../core/runtime1/VariableRuntime1Mixin.scala      |  110 +
 .../apache/daffodil/core/runtime1/package.scala    |   31 +
 .../org/apache/daffodil/dpath/Conversions.scala    |  280 ---
 .../daffodil/dpath/DFDLExpressionParser.scala      |  384 ---
 .../org/apache/daffodil/dpath/Expression.scala     | 2538 -------------------
 .../org/apache/daffodil/dpath/NodeInfoUtils.scala  |  137 --
 .../daffodil/dsom/AnnotatedSchemaComponent.scala   |  494 ----
 .../org/apache/daffodil/dsom/ChoiceGroup.scala     |  219 --
 .../apache/daffodil/dsom/CompiledExpression.scala  |  262 --
 .../org/apache/daffodil/dsom/ComplexTypes.scala    |  109 -
 .../org/apache/daffodil/dsom/DFDLAnnotation.scala  |   61 -
 .../org/apache/daffodil/dsom/DFDLAssertion.scala   |  161 --
 .../apache/daffodil/dsom/DFDLDefineFormat.scala    |   45 -
 .../apache/daffodil/dsom/DFDLDefineVariable.scala  |  169 --
 .../daffodil/dsom/DFDLDefiningAnnotation.scala     |   38 -
 .../apache/daffodil/dsom/DFDLEscapeScheme.scala    |  193 --
 .../org/apache/daffodil/dsom/DFDLFormat.scala      |   75 -
 .../daffodil/dsom/DFDLFormatAnnotation.scala       |  311 ---
 .../org/apache/daffodil/dsom/DFDLProperty.scala    |  108 -
 .../org/apache/daffodil/dsom/DFDLSchemaFile.scala  |  220 --
 .../org/apache/daffodil/dsom/DFDLStatement.scala   |   33 -
 .../apache/daffodil/dsom/DFDLStatementMixin.scala  |  229 --
 .../org/apache/daffodil/dsom/ElementBase.scala     | 1208 ----------
 .../apache/daffodil/dsom/ElementDeclMixin.scala    |  162 --
 .../org/apache/daffodil/dsom/ElementRef.scala      |  107 -
 .../daffodil/dsom/EscapeSchemeRefMixin.scala       |   71 -
 .../scala/org/apache/daffodil/dsom/Facets.scala    |  544 -----
 .../apache/daffodil/dsom/GlobalElementDecl.scala   |   52 -
 .../scala/org/apache/daffodil/dsom/GroupDef.scala  |  170 --
 .../scala/org/apache/daffodil/dsom/GroupRef.scala  |  174 --
 .../scala/org/apache/daffodil/dsom/IIBase.scala    |  291 ---
 .../scala/org/apache/daffodil/dsom/Import.scala    |  144 --
 .../scala/org/apache/daffodil/dsom/Include.scala   |   67 -
 .../daffodil/dsom/InitiatedTerminatedMixin.scala   |  122 -
 .../apache/daffodil/dsom/LocalElementDecl.scala    |   98 -
 .../apache/daffodil/dsom/LocalElementMixin.scala   |  113 -
 .../org/apache/daffodil/dsom/ModelGroup.scala      |  369 ---
 .../org/apache/daffodil/dsom/NamedMixin.scala      |  215 --
 .../scala/org/apache/daffodil/dsom/Nesting.scala   |  142 --
 .../org/apache/daffodil/dsom/ParticleMixin.scala   |  234 --
 .../org/apache/daffodil/dsom/PropProviders.scala   |  254 --
 .../RawCommonRuntimeValuedPropertiesMixin.scala    |  114 -
 .../org/apache/daffodil/dsom/RealTermMixin.scala   |   52 -
 .../org/apache/daffodil/dsom/ReptypeMixins.scala   |   65 -
 .../apache/daffodil/dsom/RestrictionUnion.scala    |  532 ----
 .../main/scala/org/apache/daffodil/dsom/Root.scala |  167 --
 .../daffodil/dsom/RuntimePropertyMixins.scala      |  868 -------
 .../org/apache/daffodil/dsom/SchemaComponent.scala |  318 ---
 .../daffodil/dsom/SchemaComponentFactory.scala     |  102 -
 .../SchemaComponentIncludesAndImportsMixin.scala   |   38 -
 .../dsom/SchemaDocIncludesAndImportsMixin.scala    |  165 --
 .../org/apache/daffodil/dsom/SchemaDocument.scala  |  262 --
 .../scala/org/apache/daffodil/dsom/SchemaSet.scala |  677 ------
 .../dsom/SchemaSetIncludesAndImportsMixins.scala   |   84 -
 .../org/apache/daffodil/dsom/SequenceGroup.scala   |  367 ---
 .../org/apache/daffodil/dsom/SimpleTypes.scala     |  592 -----
 .../main/scala/org/apache/daffodil/dsom/Term.scala |  549 -----
 .../apache/daffodil/dsom/TermEncodingMixin.scala   |  229 --
 .../org/apache/daffodil/dsom/UnparserInfo.scala    |   39 -
 .../scala/org/apache/daffodil/dsom/package.scala   |  118 -
 .../daffodil/dsom/walker/AbstractDSOMWalker.scala  |  174 --
 .../org/apache/daffodil/grammar/AlignedMixin.scala |  362 ---
 .../apache/daffodil/grammar/BitOrderMixin.scala    |   35 -
 .../apache/daffodil/grammar/ByteOrderMixin.scala   |   35 -
 .../daffodil/grammar/ChoiceGrammarMixin.scala      |   35 -
 .../daffodil/grammar/ElementBaseGrammarMixin.scala | 1402 -----------
 .../daffodil/grammar/ElementDeclGrammarMixin.scala |   33 -
 .../org/apache/daffodil/grammar/Grammar.scala      |  140 --
 .../org/apache/daffodil/grammar/GrammarMixin.scala |   51 -
 .../org/apache/daffodil/grammar/GrammarTerm.scala  |  128 -
 .../grammar/HasStatementsGrammarMixin.scala        |   38 -
 .../grammar/LocalElementGrammarMixin.scala         |   25 -
 .../daffodil/grammar/ModelGroupGrammarMixin.scala  |  102 -
 .../org/apache/daffodil/grammar/Production.scala   |  105 -
 .../daffodil/grammar/SchemaSetGrammarMixin.scala   |   43 -
 .../daffodil/grammar/SequenceGrammarMixin.scala    |  206 --
 .../apache/daffodil/grammar/TermGrammarMixin.scala |   80 -
 .../org/apache/daffodil/grammar/package.scala      |  121 -
 .../grammar/primitives/ChoiceCombinator.scala      |  278 ---
 .../primitives/DelimiterAndEscapeRelated.scala     |  105 -
 .../grammar/primitives/ElementCombinator.scala     |  409 ----
 .../grammar/primitives/HiddenGroupCombinator.scala |   37 -
 .../grammar/primitives/LayeredSequence.scala       |   73 -
 .../grammar/primitives/NilEmptyCombinators.scala   |   59 -
 .../daffodil/grammar/primitives/Padded.scala       |  134 --
 .../grammar/primitives/PatternChecker.scala        |   68 -
 .../daffodil/grammar/primitives/Primitives.scala   |   37 -
 .../grammar/primitives/PrimitivesBCD.scala         |   98 -
 .../primitives/PrimitivesBinaryBoolean.scala       |   52 -
 .../primitives/PrimitivesBinaryNumber.scala        |  147 --
 .../grammar/primitives/PrimitivesDateTime.scala    |  284 ---
 .../grammar/primitives/PrimitivesDelimiters.scala  |  104 -
 .../grammar/primitives/PrimitivesExpressions.scala |  364 ---
 .../grammar/primitives/PrimitivesFraming.scala     |   86 -
 .../primitives/PrimitivesIBM4690Packed.scala       |   96 -
 .../grammar/primitives/PrimitivesLengthKind.scala  |  344 ---
 .../grammar/primitives/PrimitivesNil.scala         |   63 -
 .../PrimitivesNonBaseTenTextNumber.scala           |   42 -
 .../grammar/primitives/PrimitivesPacked.scala      |  103 -
 .../grammar/primitives/PrimitivesTextBoolean.scala |   41 -
 .../grammar/primitives/PrimitivesTextNumber.scala  |  596 -----
 .../grammar/primitives/PrimitivesZoned.scala       |  172 --
 .../grammar/primitives/SequenceChild.scala         |  563 -----
 .../grammar/primitives/SequenceCombinator.scala    |  202 --
 .../grammar/primitives/SpecifiedLength.scala       |  223 --
 .../runtime1/ChoiceTermRuntime1Mixin.scala         |  206 --
 .../runtime1/ElementBaseRuntime1Mixin.scala        |  231 --
 .../daffodil/runtime1/GramRuntime1Mixin.scala      |   62 -
 .../LocalElementDeclBaseRuntime1Mixin.scala        |   25 -
 .../runtime1/ModelGroupRuntime1Mixin.scala         |   50 -
 .../runtime1/SchemaComponentRuntime1Mixin.scala    |   48 -
 .../daffodil/runtime1/SchemaSetRuntime1Mixin.scala |   97 -
 .../runtime1/SequenceTermRuntime1Mixin.scala       |   82 -
 .../runtime1/SimpleTypeRuntime1Mixin.scala         |   62 -
 .../daffodil/runtime1/TermRuntime1Mixin.scala      |  610 -----
 .../daffodil/runtime1/VariableMapFactory.scala     |   30 -
 .../daffodil/runtime1/VariableRuntime1Mixin.scala  |  110 -
 .../org/apache/daffodil/runtime1/package.scala     |   31 -
 .../scala/org/apache/daffodil/api/TestAPI.scala    |   58 -
 .../scala/org/apache/daffodil/api/TestAPI1.scala   |  567 -----
 .../apache/daffodil/api/TestDsomCompiler3.scala    |   89 -
 .../org/apache/daffodil/api/TestForHeapDump.scala  |  188 --
 .../daffodil/api/TestParseIndividualMessages.scala |  208 --
 .../org/apache/daffodil/core/api/TestAPI.scala     |   58 +
 .../org/apache/daffodil/core/api/TestAPI1.scala    |  569 +++++
 .../daffodil/core/api/TestDsomCompiler3.scala      |   89 +
 .../apache/daffodil/core/api/TestForHeapDump.scala |  190 ++
 .../core/api/TestParseIndividualMessages.scala     |  210 ++
 .../core/dpath/TestDFDLExpressionEvaluation.scala  |  158 ++
 .../core/dpath/TestDFDLExpressionTree.scala        |  373 +++
 .../org/apache/daffodil/core/dpath/TestDPath.scala |   89 +
 .../daffodil/core/dsom/TestAppinfoSyntax.scala     |  243 ++
 .../daffodil/core/dsom/TestBinaryInput_01.scala    |  284 +++
 .../daffodil/core/dsom/TestDsomCompiler.scala      | 1112 +++++++++
 .../core/dsom/TestDsomCompilerUnparse1.scala       |  141 ++
 .../daffodil/core/dsom/TestExternalVariables.scala |  352 +++
 .../daffodil/core/dsom/TestInputValueCalc.scala    |   77 +
 .../dsom/TestInteriorAlignmentElimination.scala    |  111 +
 .../daffodil/core/dsom/TestIsScannable.scala       |  139 ++
 .../core/dsom/TestMiddleEndAttributes.scala        |  234 ++
 .../core/dsom/TestMiddleEndAttributes2.scala       |   58 +
 .../core/dsom/TestMiddleEndAttributes3.scala       |  122 +
 .../TestPolymorphicUpwardRelativeExpressions.scala |  268 +++
 .../daffodil/core/dsom/TestPropertyScoping.scala   |   40 +
 .../org/apache/daffodil/core/dsom/TestRefMap.scala |  905 +++++++
 .../daffodil/core/dsom/TestSimpleTypeUnions.scala  |  393 +++
 .../daffodil/core/dsom/walker/BasicWalker.scala    |   45 +
 .../daffodil/core/dsom/walker/TestDSOMWalker.scala |  179 ++
 .../core/externalvars/TestExternalVariables.scala  |   56 +
 .../daffodil/core/general/TestDFDLReaders.scala    |   29 +
 .../daffodil/core/general/TestPrimitives.scala     |  227 ++
 .../daffodil/core/general/TestPrimitives2.scala    |   52 +
 .../core/general/TestRuntimeProperties.scala       |  230 ++
 .../daffodil/core/general/TestTunables.scala       |   98 +
 .../apache/daffodil/core/grammar/TestGrammar.scala |  104 +
 .../core/grammar/primitives/TestPrimitives.scala   |  276 +++
 .../apache/daffodil/core/infoset/TestInfoset.scala |  419 ++++
 .../daffodil/core/infoset/TestInfoset2.scala       |   67 +
 .../daffodil/core/infoset/TestInfosetCursor.scala  |  624 +++++
 .../daffodil/core/infoset/TestInfosetCursor1.scala |  229 ++
 .../core/infoset/TestInfosetCursorFromReader.scala |  547 +++++
 .../infoset/TestInfosetCursorFromReader2.scala     |  130 +
 .../daffodil/core/infoset/TestInfosetFree.scala    |  283 +++
 .../apache/daffodil/core/layers/TestLayers.scala   |  424 ++++
 .../TestOutputValueCalcAndAlignment.scala          |  147 ++
 .../TestOutputValueCalcForwardReference.scala      |  175 ++
 .../daffodil/core/processor/TestSAXParseAPI.scala  |  640 +++++
 .../core/processor/TestSAXParseUnparseAPI.scala    |  201 ++
 .../core/processor/TestSAXUnparseAPI.scala         |  199 ++
 .../daffodil/core/processor/TestSAXUtils.scala     |  360 +++
 .../runtime1/TestDelimiterFinalBacktracking.scala  |   89 +
 .../TestStreamingUnparserCompilerAttributes.scala  |  576 +++++
 .../daffodil/core/runtime1/TestUnparseHidden.scala |  285 +++
 .../annotation/props/TestPropertyRuntime.scala     |  136 ++
 .../org/apache/daffodil/core/util/TestUtils.scala  |  456 ++++
 .../core/xml/TestXMLLoaderWithLocation.scala       |   86 +
 .../dpath/TestDFDLExpressionEvaluation.scala       |  156 --
 .../daffodil/dpath/TestDFDLExpressionTree.scala    |  369 ---
 .../org/apache/daffodil/dpath/TestDPath.scala      |   89 -
 .../apache/daffodil/dsom/TestAppinfoSyntax.scala   |  243 --
 .../apache/daffodil/dsom/TestBinaryInput_01.scala  |  284 ---
 .../apache/daffodil/dsom/TestDsomCompiler.scala    | 1110 ---------
 .../daffodil/dsom/TestDsomCompilerUnparse1.scala   |  139 --
 .../daffodil/dsom/TestExternalVariables.scala      |  352 ---
 .../apache/daffodil/dsom/TestInputValueCalc.scala  |   75 -
 .../dsom/TestInteriorAlignmentElimination.scala    |  109 -
 .../org/apache/daffodil/dsom/TestIsScannable.scala |  137 --
 .../daffodil/dsom/TestMiddleEndAttributes.scala    |  234 --
 .../daffodil/dsom/TestMiddleEndAttributes2.scala   |   58 -
 .../daffodil/dsom/TestMiddleEndAttributes3.scala   |  122 -
 .../TestPolymorphicUpwardRelativeExpressions.scala |  266 --
 .../apache/daffodil/dsom/TestPropertyScoping.scala |   38 -
 .../org/apache/daffodil/dsom/TestRefMap.scala      |  905 -------
 .../daffodil/dsom/TestSimpleTypeUnions.scala       |  389 ---
 .../apache/daffodil/dsom/walker/BasicWalker.scala  |   45 -
 .../daffodil/dsom/walker/TestDSOMWalker.scala      |  177 --
 .../externalvars/TestExternalVariables.scala       |   56 -
 .../apache/daffodil/general/TestDFDLReaders.scala  |   29 -
 .../apache/daffodil/general/TestPrimitives.scala   |  227 --
 .../apache/daffodil/general/TestPrimitives2.scala  |   52 -
 .../daffodil/general/TestRuntimeProperties.scala   |  230 --
 .../org/apache/daffodil/general/TestTunables.scala |   98 -
 .../org/apache/daffodil/grammar/TestGrammar.scala  |  104 -
 .../grammar/primitives/TestPrimitives.scala        |  276 ---
 .../org/apache/daffodil/infoset/TestInfoset.scala  |  417 ----
 .../org/apache/daffodil/infoset/TestInfoset2.scala |   65 -
 .../daffodil/infoset/TestInfosetCursor.scala       |  622 -----
 .../daffodil/infoset/TestInfosetCursor1.scala      |  227 --
 .../infoset/TestInfosetCursorFromReader.scala      |  545 -----
 .../infoset/TestInfosetCursorFromReader2.scala     |  128 -
 .../apache/daffodil/infoset/TestInfosetFree.scala  |  281 ---
 .../org/apache/daffodil/layers/TestLayers.scala    |  424 ----
 .../TestOutputValueCalcAndAlignment.scala          |  147 --
 .../TestOutputValueCalcForwardReference.scala      |  175 --
 .../daffodil/processor/TestSAXParseAPI.scala       |  640 -----
 .../processor/TestSAXParseUnparseAPI.scala         |  201 --
 .../daffodil/processor/TestSAXUnparseAPI.scala     |  199 --
 .../apache/daffodil/processor/TestSAXUtils.scala   |  360 ---
 .../runtime1/TestDelimiterFinalBacktracking.scala  |   89 -
 .../TestStreamingUnparserCompilerAttributes.scala  |  576 -----
 .../daffodil/runtime1/TestUnparseHidden.scala      |  285 ---
 .../annotation/props/TestPropertyRuntime.scala     |  134 --
 .../scala/org/apache/daffodil/util/TestUtils.scala |  453 ----
 .../daffodil/xml/TestXMLLoaderWithLocation.scala   |   84 -
 ...dil.io.processors.charset.BitsCharsetDefinition |   53 +
 ...ffodil.processors.charset.BitsCharsetDefinition |   53 -
 .../org/apache/daffodil/io/DataInputStream.scala   |    8 +-
 .../daffodil/io/DataInputStreamImplMixin.scala     |    4 +-
 .../org/apache/daffodil/io/DataOutputStream.scala  |    4 +-
 .../daffodil/io/DataOutputStreamImplMixin.scala    |   20 +-
 .../org/apache/daffodil/io/DataStreamCommon.scala  |    2 +-
 .../daffodil/io/DataStreamCommonImplMixin.scala    |   10 +-
 .../io/DirectOrBufferedDataOutputStream.scala      |   18 +-
 .../main/scala/org/apache/daffodil/io/Dump.scala   |    6 +-
 .../scala/org/apache/daffodil/io/FormatInfo.scala  |   20 +-
 .../scala/org/apache/daffodil/io/InputSource.scala |    6 +-
 .../daffodil/io/InputSourceDataInputStream.scala   |   16 +-
 .../daffodil/io/LimitingJavaIinputStreams.scala    |    2 +-
 .../scala/org/apache/daffodil/io/LocalBuffer.scala |    8 +-
 .../io/StringDataInputStreamForUnparse.scala       |    8 +-
 .../org/apache/daffodil/io/ThreadCheckMixin.scala  |    4 +-
 .../main/scala/org/apache/daffodil/io/Utils.scala  |    2 +-
 .../io/processors/charset/AISPayloadArmoring.scala |   49 +
 .../daffodil/io/processors/charset/Base4.scala     |   46 +
 .../daffodil/io/processors/charset/Binary.scala    |   50 +
 .../io/processors/charset/BitsCharset.scala        |  198 ++
 .../io/processors/charset/BitsCharsetDecoder.scala |  213 ++
 .../processors/charset/BitsCharsetDefinition.scala |   29 +
 .../charset/BitsCharsetDefinitionRegistry.scala    |   36 +
 .../charset/BitsCharsetNonByteSize.scala           |  322 +++
 .../io/processors/charset/CharsetUtils.scala       |  292 +++
 .../daffodil/io/processors/charset/Hex.scala       |   50 +
 .../daffodil/io/processors/charset/IBM037.scala    |   62 +
 .../daffodil/io/processors/charset/ISO88591.scala  |   41 +
 .../io/processors/charset/ISO885918BitPacked.scala |   50 +
 .../daffodil/io/processors/charset/Octal.scala     |   50 +
 .../daffodil/io/processors/charset/USASCII.scala   |   46 +
 .../io/processors/charset/USASCII5BitPacked.scala  |   43 +
 .../io/processors/charset/USASCII6BitPacked.scala  |   49 +
 .../io/processors/charset/USASCII7BitPacked.scala  |   35 +
 .../daffodil/io/processors/charset/UTF16BE.scala   |   61 +
 .../daffodil/io/processors/charset/UTF16LE.scala   |   59 +
 .../daffodil/io/processors/charset/UTF32BE.scala   |   56 +
 .../daffodil/io/processors/charset/UTF32LE.scala   |   53 +
 .../daffodil/io/processors/charset/UTF8.scala      |  124 +
 .../io/processors/charset/X_DFDL_MIL_STD.scala     |  133 +
 .../processors/charset/AISPayloadArmoring.scala    |   49 -
 .../apache/daffodil/processors/charset/Base4.scala |   46 -
 .../daffodil/processors/charset/Binary.scala       |   50 -
 .../daffodil/processors/charset/BitsCharset.scala  |  198 --
 .../processors/charset/BitsCharsetDecoder.scala    |  213 --
 .../processors/charset/BitsCharsetDefinition.scala |   29 -
 .../charset/BitsCharsetDefinitionRegistry.scala    |   36 -
 .../charset/BitsCharsetNonByteSize.scala           |  322 ---
 .../daffodil/processors/charset/CharsetUtils.scala |  292 ---
 .../apache/daffodil/processors/charset/Hex.scala   |   50 -
 .../daffodil/processors/charset/IBM037.scala       |   62 -
 .../daffodil/processors/charset/ISO88591.scala     |   41 -
 .../processors/charset/ISO885918BitPacked.scala    |   50 -
 .../apache/daffodil/processors/charset/Octal.scala |   50 -
 .../daffodil/processors/charset/USASCII.scala      |   46 -
 .../processors/charset/USASCII5BitPacked.scala     |   43 -
 .../processors/charset/USASCII6BitPacked.scala     |   49 -
 .../processors/charset/USASCII7BitPacked.scala     |   35 -
 .../daffodil/processors/charset/UTF16BE.scala      |   61 -
 .../daffodil/processors/charset/UTF16LE.scala      |   59 -
 .../daffodil/processors/charset/UTF32BE.scala      |   56 -
 .../daffodil/processors/charset/UTF32LE.scala      |   53 -
 .../apache/daffodil/processors/charset/UTF8.scala  |  124 -
 .../processors/charset/X_DFDL_MIL_STD.scala        |  133 -
 .../apache/daffodil/io/FormatInfoForUnitTest.scala |   26 +-
 .../org/apache/daffodil/io/SocketPairTestRig.scala |    2 +-
 .../io/TestAISPayloadArmoringEncoder.scala         |    4 +-
 .../apache/daffodil/io/TestDataOutputStream.scala  |    4 +-
 .../apache/daffodil/io/TestDataOutputStream2.scala |    2 +-
 .../apache/daffodil/io/TestDataOutputStream3.scala |    6 +-
 .../apache/daffodil/io/TestDataOutputStream4.scala |    4 +-
 .../io/TestDirectOrBufferedDataOutputStream.scala  |    4 +-
 .../org/apache/daffodil/io/TestDumpDisplay.scala   |    6 +-
 .../apache/daffodil/io/TestFastAsciiConvert.scala  |    2 +-
 .../io/TestInputSourceDataInputStream.scala        |   16 +-
 .../io/TestInputSourceDataInputStream2.scala       |    6 +-
 .../io/TestInputSourceDataInputStream3Bit.scala    |   12 +-
 .../io/TestInputSourceDataInputStream4.scala       |    2 +-
 .../io/TestInputSourceDataInputStream5.scala       |    4 +-
 .../io/TestInputSourceDataInputStream6.scala       |   12 +-
 .../io/TestInputSourceDataInputStream7.scala       |   12 +-
 .../io/TestInputSourceDataInputStream8.scala       |    2 +-
 .../io/TestNonByteSizedCharsetDecoders1Bit.scala   |   12 +-
 .../io/TestNonByteSizedCharsetDecoders3Bit.scala   |   10 +-
 .../io/TestNonByteSizedCharsetDecoders8Bit.scala   |    8 +-
 .../io/TestNonByteSizedCharsetEncoders1Bit.scala   |    4 +-
 .../io/TestNonByteSizedCharsetEncoders3Bit.scala   |    4 +-
 .../io/TestNonByteSizedCharsetEncoders8Bit.scala   |    4 +-
 .../org/apache/daffodil/layers/TestBase64.scala    |    6 +-
 .../apache/daffodil/layers/TestJavaIOStreams.scala |    4 +-
 .../layers/TestLimitingJavaIOStreams.scala         |    4 +-
 .../scala/org/apache/daffodil/japi/Daffodil.scala  |   56 +-
 .../org/apache/daffodil/japi/infoset/Infoset.scala |   40 +-
 .../daffodil/japi/packageprivate/Utils.scala       |    8 +-
 .../daffodil/example/TestCustomDebuggerAPI.java    |    8 +-
 .../apache/daffodil/example/TestInfosetEvent.java  |   12 +-
 .../daffodil/example/TestInfosetInputter.java      |    4 +-
 .../daffodil/example/TestInfosetOutputter.java     |    6 +-
 .../daffodil/example/ValidatorApiExample.java      |    2 +-
 .../daffodil/example/ValidatorSpiExample.java      |    4 +-
 .../example/validation/FailingValidator.java       |    8 +-
 .../validation/FailingValidatorFactory.java        |    4 +-
 .../example/validation/PassingValidator.java       |    8 +-
 .../validation/PassingValidatorFactory.java        |    4 +-
 .../daffodil/japi/SAXErrorHandlerForJAPITest.java  |    2 +-
 ...> org.apache.daffodil.lib.api.ValidatorFactory} |    0
 .../org.apache.daffodil.api.ValidatorFactory       |   16 -
 .../org.apache.daffodil.lib.api.ValidatorFactory   |   16 +
 .../main/scala/org/apache/daffodil/Implicits.scala |   84 -
 .../scala/org/apache/daffodil/TypedEquality.scala  |  129 -
 .../org/apache/daffodil/api/DaffodilConfig.scala   |   87 -
 .../apache/daffodil/api/DaffodilSchemaSource.scala |  188 --
 .../daffodil/api/DaffodilTunablesStaticMixin.scala |   43 -
 .../scala/org/apache/daffodil/api/Diagnostic.scala |  275 ---
 .../org/apache/daffodil/api/ValidationMode.scala   |   31 -
 .../scala/org/apache/daffodil/api/Validator.scala  |  103 -
 .../apache/daffodil/calendar/DFDLCalendar.scala    |  401 ---
 .../daffodil/calendar/DFDLCalendarConversion.scala |  401 ---
 .../daffodil/calendar/TextCalendarConstants.scala  |   23 -
 .../org/apache/daffodil/cookers/Converter.scala    |   72 -
 .../org/apache/daffodil/cookers/Cookers.scala      |  113 -
 .../apache/daffodil/cookers/EntityReplacer.scala   |  839 -------
 .../org/apache/daffodil/exceptions/Assert.scala    |  187 --
 .../daffodil/exceptions/SchemaFileLocatable.scala  |  143 --
 .../org/apache/daffodil/exceptions/ThrowsSDE.scala |  135 --
 .../org/apache/daffodil/externalvars/Binding.scala |  128 -
 .../scala/org/apache/daffodil/lib/Implicits.scala  |   84 +
 .../org/apache/daffodil/lib/TypedEquality.scala    |  129 +
 .../apache/daffodil/lib/api/DaffodilConfig.scala   |   87 +
 .../daffodil/lib/api/DaffodilSchemaSource.scala    |  188 ++
 .../lib/api/DaffodilTunablesStaticMixin.scala      |   43 +
 .../org/apache/daffodil/lib/api/Diagnostic.scala   |  275 +++
 .../apache/daffodil/lib/api/ValidationMode.scala   |   31 +
 .../org/apache/daffodil/lib/api/Validator.scala    |  103 +
 .../daffodil/lib/calendar/DFDLCalendar.scala       |  401 +++
 .../lib/calendar/DFDLCalendarConversion.scala      |  401 +++
 .../lib/calendar/TextCalendarConstants.scala       |   23 +
 .../apache/daffodil/lib/cookers/Converter.scala    |   72 +
 .../org/apache/daffodil/lib/cookers/Cookers.scala  |  113 +
 .../daffodil/lib/cookers/EntityReplacer.scala      |  839 +++++++
 .../apache/daffodil/lib/exceptions/Assert.scala    |  187 ++
 .../lib/exceptions/SchemaFileLocatable.scala       |  143 ++
 .../apache/daffodil/lib/exceptions/ThrowsSDE.scala |  135 ++
 .../apache/daffodil/lib/externalvars/Binding.scala |  128 +
 .../org/apache/daffodil/lib/oolag/OOLAG.scala      |  792 ++++++
 .../lib/schema/annotation/props/ByHandMixins.scala |  490 ++++
 .../lib/schema/annotation/props/Properties.scala   |  219 ++
 .../schema/annotation/props/PropertyScoping.scala  |  255 ++
 .../org/apache/daffodil/lib/util/BitOrder.scala    |  283 +++
 .../daffodil/lib/util/ByteBufferOutputStream.scala |   37 +
 .../daffodil/lib/util/CharacterSetRemapper.scala   |  136 ++
 .../org/apache/daffodil/lib/util/Coroutines.scala  |  239 ++
 .../org/apache/daffodil/lib/util/Cursor.scala      |  204 ++
 .../org/apache/daffodil/lib/util/DPathUtil.scala   |   34 +
 .../apache/daffodil/lib/util/DecimalUtils.scala    |  518 ++++
 .../scala/org/apache/daffodil/lib/util/Delay.scala |  149 ++
 .../scala/org/apache/daffodil/lib/util/Enum.scala  |   61 +
 .../org/apache/daffodil/lib/util/Indentable.scala  |   99 +
 .../daffodil/lib/util/IteratorWithPeekImpl.scala   |   22 +
 .../org/apache/daffodil/lib/util/ListUtils.scala   |   45 +
 .../org/apache/daffodil/lib/util/Logger.scala      |   46 +
 .../org/apache/daffodil/lib/util/MStack.scala      |  346 +++
 .../scala/org/apache/daffodil/lib/util/Math.scala  |   24 +
 .../scala/org/apache/daffodil/lib/util/Maybe.scala |  193 ++
 .../org/apache/daffodil/lib/util/MaybeFloat.scala  |   78 +
 .../org/apache/daffodil/lib/util/MaybeInt.scala    |  167 ++
 .../org/apache/daffodil/lib/util/MaybeULong.scala  |   89 +
 .../scala/org/apache/daffodil/lib/util/Misc.scala  |  657 +++++
 .../scala/org/apache/daffodil/lib/util/Named.scala |   64 +
 .../daffodil/lib/util/NonAllocatingMap.scala       |   72 +
 .../org/apache/daffodil/lib/util/Numbers.scala     |  332 +++
 .../org/apache/daffodil/lib/util/OKOrError.scala   |   46 +
 .../org/apache/daffodil/lib/util/OnStack.scala     |  134 ++
 .../scala/org/apache/daffodil/lib/util/Pool.scala  |  107 +
 .../daffodil/lib/util/ScalaAnnotations.scala       |   27 +
 .../org/apache/daffodil/lib/util/SchemaUtils.scala |  134 ++
 .../org/apache/daffodil/lib/util/Serialize.scala   |   86 +
 .../lib/util/SimpleNamedServiceLoader.scala        |   64 +
 .../scala/org/apache/daffodil/lib/util/Timer.scala |  261 ++
 .../daffodil/lib/util/TransitiveClosure.scala      |   51 +
 .../apache/daffodil/lib/util/UniquenessCache.scala |   72 +
 .../daffodil/lib/validation/Validators.scala       |   62 +
 .../daffodil/lib/validation/XercesValidator.scala  |  166 ++
 .../lib/xml/DaffodilConstructingLoader.scala       |  456 ++++
 .../lib/xml/DaffodilSAXParserFactory.scala         |   58 +
 .../daffodil/lib/xml/DaffodilXMLLoader.scala       |  727 ++++++
 .../org/apache/daffodil/lib/xml/JDOMUtils.scala    |  110 +
 .../org/apache/daffodil/lib/xml/Namespaces.scala   |  130 +
 .../org/apache/daffodil/lib/xml/PUARemappers.scala |  128 +
 .../apache/daffodil/lib/xml/PrettyPrinter.scala    |  197 ++
 .../org/apache/daffodil/lib/xml/QNameBase.scala    |  600 +++++
 .../scala/org/apache/daffodil/lib/xml/QNames.scala |   88 +
 .../org/apache/daffodil/lib/xml/XMLUtils.scala     | 1317 ++++++++++
 .../scala/org/apache/daffodil/oolag/OOLAG.scala    |  792 ------
 .../schema/annotation/props/ByHandMixins.scala     |  490 ----
 .../schema/annotation/props/Properties.scala       |  219 --
 .../schema/annotation/props/PropertyScoping.scala  |  255 --
 .../scala/org/apache/daffodil/util/BitOrder.scala  |  283 ---
 .../daffodil/util/ByteBufferOutputStream.scala     |   37 -
 .../daffodil/util/CharacterSetRemapper.scala       |  136 --
 .../org/apache/daffodil/util/Coroutines.scala      |  239 --
 .../scala/org/apache/daffodil/util/Cursor.scala    |  204 --
 .../scala/org/apache/daffodil/util/DPathUtil.scala |   34 -
 .../org/apache/daffodil/util/DecimalUtils.scala    |  518 ----
 .../scala/org/apache/daffodil/util/Delay.scala     |  149 --
 .../main/scala/org/apache/daffodil/util/Enum.scala |   61 -
 .../org/apache/daffodil/util/Indentable.scala      |   99 -
 .../daffodil/util/IteratorWithPeekImpl.scala       |   22 -
 .../scala/org/apache/daffodil/util/ListUtils.scala |   45 -
 .../scala/org/apache/daffodil/util/Logger.scala    |   46 -
 .../scala/org/apache/daffodil/util/MStack.scala    |  346 ---
 .../main/scala/org/apache/daffodil/util/Math.scala |   24 -
 .../scala/org/apache/daffodil/util/Maybe.scala     |  193 --
 .../org/apache/daffodil/util/MaybeFloat.scala      |   78 -
 .../scala/org/apache/daffodil/util/MaybeInt.scala  |  167 --
 .../org/apache/daffodil/util/MaybeULong.scala      |   89 -
 .../main/scala/org/apache/daffodil/util/Misc.scala |  657 -----
 .../scala/org/apache/daffodil/util/Named.scala     |   64 -
 .../apache/daffodil/util/NonAllocatingMap.scala    |   72 -
 .../scala/org/apache/daffodil/util/Numbers.scala   |  332 ---
 .../scala/org/apache/daffodil/util/OKOrError.scala |   46 -
 .../scala/org/apache/daffodil/util/OnStack.scala   |  134 --
 .../main/scala/org/apache/daffodil/util/Pool.scala |  107 -
 .../apache/daffodil/util/ScalaAnnotations.scala    |   27 -
 .../org/apache/daffodil/util/SchemaUtils.scala     |  134 --
 .../scala/org/apache/daffodil/util/Serialize.scala |   86 -
 .../daffodil/util/SimpleNamedServiceLoader.scala   |   64 -
 .../scala/org/apache/daffodil/util/Timer.scala     |  261 --
 .../apache/daffodil/util/TransitiveClosure.scala   |   51 -
 .../org/apache/daffodil/util/UniquenessCache.scala |   72 -
 .../apache/daffodil/validation/Validators.scala    |   62 -
 .../daffodil/validation/XercesValidator.scala      |  166 --
 .../daffodil/xml/DaffodilConstructingLoader.scala  |  456 ----
 .../daffodil/xml/DaffodilSAXParserFactory.scala    |   58 -
 .../apache/daffodil/xml/DaffodilXMLLoader.scala    |  727 ------
 .../scala/org/apache/daffodil/xml/JDOMUtils.scala  |  110 -
 .../scala/org/apache/daffodil/xml/Namespaces.scala |  130 -
 .../org/apache/daffodil/xml/PUARemappers.scala     |  128 -
 .../org/apache/daffodil/xml/PrettyPrinter.scala    |  197 --
 .../scala/org/apache/daffodil/xml/QNameBase.scala  |  600 -----
 .../scala/org/apache/daffodil/xml/QNames.scala     |   88 -
 .../scala/org/apache/daffodil/xml/XMLUtils.scala   | 1317 ----------
 .../org.apache.daffodil.api.ValidatorFactory       |   17 -
 .../org.apache.daffodil.lib.api.ValidatorFactory   |   17 +
 .../scala/org/apache/daffodil/HowToUseJUnit.scala  |   92 -
 .../apache/daffodil/TestBitOrderByteOrder.scala    |   58 -
 .../scala/org/apache/daffodil/TestImplicits.scala  |   59 -
 .../apache/daffodil/api/StringSchemaSource.scala   |   29 -
 .../api/TestDaffodilTunablesAndConfig.scala        |   52 -
 .../daffodil/exceptions/TestExceptions.scala       |   33 -
 .../functionality/icu/TestBigInteger.scala         |   78 -
 .../org/apache/daffodil/lib/HowToUseJUnit.scala    |   92 +
 .../daffodil/lib/TestBitOrderByteOrder.scala       |   58 +
 .../org/apache/daffodil/lib/TestImplicits.scala    |   59 +
 .../daffodil/lib/api/StringSchemaSource.scala      |   29 +
 .../lib/api/TestDaffodilTunablesAndConfig.scala    |   52 +
 .../daffodil/lib/exceptions/TestExceptions.scala   |   33 +
 .../lib/functionality/icu/TestBigInteger.scala     |   78 +
 .../daffodil/lib/macros/TestAssertMacros.scala     |  113 +
 .../org/apache/daffodil/lib/oolag/TestOOLAG.scala  |  279 +++
 .../annotation/props/TestGeneratedProperties.scala |  235 ++
 .../lib/testEquality/TestEqualityOperators.scala   |   79 +
 .../daffodil/lib/util/IteratorFromCursor.scala     |   40 +
 .../org/apache/daffodil/lib/util/TestBits.scala    |   67 +
 .../org/apache/daffodil/lib/util/TestBits2.scala   |  104 +
 .../apache/daffodil/lib/util/TestCoroutines.scala  |  173 ++
 .../daffodil/lib/util/TestDecimalUtils.scala       | 1765 ++++++++++++++
 .../org/apache/daffodil/lib/util/TestListMap.scala |   51 +
 .../daffodil/lib/util/TestListSerialization.scala  |  102 +
 .../apache/daffodil/lib/util/TestListUtils.scala   |   79 +
 .../org/apache/daffodil/lib/util/TestMStack.scala  |  138 ++
 .../org/apache/daffodil/lib/util/TestMaybe.scala   |   58 +
 .../daffodil/lib/util/TestMaybeInlineForeach.scala |  141 ++
 .../daffodil/lib/util/TestMaybeTakPerf.scala       |  187 ++
 .../org/apache/daffodil/lib/util/TestMisc.scala    |   45 +
 .../org/apache/daffodil/lib/util/TestNamed.scala   |   41 +
 .../apache/daffodil/lib/util/TestNumberStuff.scala |  200 ++
 .../org/apache/daffodil/lib/util/TestNumbers.scala |  107 +
 .../apache/daffodil/lib/util/TestPUARemapper.scala |  151 ++
 .../apache/daffodil/lib/util/TestSchemaUtils.scala |   75 +
 .../lib/util/TestSerializationAndLazy.scala        |   69 +
 .../org/apache/daffodil/lib/util/TestUtil.scala    |   71 +
 .../lib/util/TestXMLCatalogAndValidate.scala       |  532 ++++
 .../lib/validation/TestValidatorsSPI.scala         |   70 +
 .../lib/validation/TestXercesValidator.scala       |   35 +
 .../lib/validation/ValidatorSPISupport.scala       |   49 +
 .../lib/xml/test/unit/TestNamespaces.scala         |  123 +
 .../daffodil/lib/xml/test/unit/TestQName.scala     |  105 +
 .../test/unit/TestUTF8AndUTF16Conversions.scala    |  106 +
 .../lib/xml/test/unit/TestUnicodeXMLI18N.scala     |  112 +
 .../lib/xml/test/unit/TestXMLLiterals.scala        |   56 +
 .../daffodil/lib/xml/test/unit/TestXMLLoader.scala |  262 ++
 .../lib/xml/test/unit/TestXMLPrettyPrinter.scala   |  189 ++
 .../daffodil/lib/xml/test/unit/TestXMLUtils.scala  |  341 +++
 .../apache/daffodil/macros/TestAssertMacros.scala  |  113 -
 .../org/apache/daffodil/oolag/TestOOLAG.scala      |  279 ---
 .../annotation/props/TestGeneratedProperties.scala |  235 --
 .../testEquality/TestEqualityOperators.scala       |   79 -
 .../apache/daffodil/util/IteratorFromCursor.scala  |   40 -
 .../scala/org/apache/daffodil/util/TestBits.scala  |   67 -
 .../scala/org/apache/daffodil/util/TestBits2.scala |  104 -
 .../org/apache/daffodil/util/TestCoroutines.scala  |  173 --
 .../apache/daffodil/util/TestDecimalUtils.scala    | 1765 --------------
 .../org/apache/daffodil/util/TestListMap.scala     |   51 -
 .../daffodil/util/TestListSerialization.scala      |  102 -
 .../org/apache/daffodil/util/TestListUtils.scala   |   79 -
 .../org/apache/daffodil/util/TestMStack.scala      |  138 --
 .../scala/org/apache/daffodil/util/TestMaybe.scala |   58 -
 .../daffodil/util/TestMaybeInlineForeach.scala     |  141 --
 .../apache/daffodil/util/TestMaybeTakPerf.scala    |  187 --
 .../scala/org/apache/daffodil/util/TestMisc.scala  |   45 -
 .../scala/org/apache/daffodil/util/TestNamed.scala |   41 -
 .../org/apache/daffodil/util/TestNumberStuff.scala |  200 --
 .../org/apache/daffodil/util/TestNumbers.scala     |  107 -
 .../org/apache/daffodil/util/TestPUARemapper.scala |  151 --
 .../org/apache/daffodil/util/TestSchemaUtils.scala |   75 -
 .../daffodil/util/TestSerializationAndLazy.scala   |   69 -
 .../scala/org/apache/daffodil/util/TestUtil.scala  |   71 -
 .../daffodil/util/TestXMLCatalogAndValidate.scala  |  532 ----
 .../daffodil/validation/TestValidatorsSPI.scala    |   70 -
 .../daffodil/validation/TestXercesValidator.scala  |   35 -
 .../daffodil/validation/ValidatorSPISupport.scala  |   49 -
 .../daffodil/xml/test/unit/TestNamespaces.scala    |  123 -
 .../apache/daffodil/xml/test/unit/TestQName.scala  |  105 -
 .../test/unit/TestUTF8AndUTF16Conversions.scala    |  106 -
 .../xml/test/unit/TestUnicodeXMLI18N.scala         |  112 -
 .../daffodil/xml/test/unit/TestXMLLiterals.scala   |   56 -
 .../daffodil/xml/test/unit/TestXMLLoader.scala     |  262 --
 .../xml/test/unit/TestXMLPrettyPrinter.scala       |  189 --
 .../daffodil/xml/test/unit/TestXMLUtils.scala      |  341 ---
 .../apache/daffodil/exceptions/AssertMacros.scala  |  102 -
 .../org/apache/daffodil/exceptions/SDEMacros.scala |  107 -
 .../scala/org/apache/daffodil/io/IOMacros.scala    |    2 +-
 .../daffodil/lib/exceptions/AssertMacros.scala     |  102 +
 .../apache/daffodil/lib/exceptions/SDEMacros.scala |  107 +
 .../org/apache/daffodil/lib/util/TimerMacros.scala |   67 +
 .../parsers/PointOfUncertaintyMacros.scala         |   97 -
 .../parsers/PointOfUncertaintyMacros.scala         |   97 +
 .../org/apache/daffodil/util/TimerMacros.scala     |   67 -
 .../daffodil/propGen/PropertyGenerator.scala       |   10 +-
 .../apache/daffodil/propGen/TunableGenerator.scala |   16 +-
 .../apache/daffodil/propGen/WarnIDGenerator.scala  |    8 +-
 .../daffodil/propGen/TestPropertyGenerator.scala   |    6 +-
 .../org.apache.daffodil.layers.LayerCompiler       |   23 -
 ...g.apache.daffodil.runtime1.layers.LayerCompiler |   23 +
 .../apache/daffodil/layers/Base64Transformer.scala |   90 -
 .../daffodil/layers/ByteSwapTransformer.scala      |  214 --
 .../apache/daffodil/layers/GZipTransformer.scala   |  153 --
 .../daffodil/layers/LineFoldedTransformer.scala    |  481 ----
 .../layers/runtime1/Base64Transformer.scala        |   92 +
 .../layers/runtime1/ByteSwapTransformer.scala      |  216 ++
 .../daffodil/layers/runtime1/GZipTransformer.scala |  155 ++
 .../layers/runtime1/LineFoldedTransformer.scala    |  483 ++++
 .../daffodil/layers/TestByteSwapStream.scala       |   76 -
 .../TestLengthLimitedLineFoldingStreams.scala      |  218 --
 .../daffodil/layers/TestLineFoldingStreams.scala   |  232 --
 .../layers/runtime1/TestByteSwapStream.scala       |   76 +
 .../TestLengthLimitedLineFoldingStreams.scala      |  218 ++
 .../layers/runtime1/TestLineFoldingStreams.scala   |  232 ++
 .../processors/unparsers/BCDUnparsers.scala        |  147 --
 .../unparsers/BinaryBooleanUnparsers.scala         |  129 -
 .../unparsers/BinaryNumberUnparsers.scala          |  223 --
 .../processors/unparsers/BlobLengthUnparser.scala  |  110 -
 .../unparsers/ChoiceAndOtherVariousUnparsers.scala |  193 --
 .../unparsers/ConvertBinaryCalendarUnparser.scala  |   84 -
 .../ConvertNonBaseTenTextNumberUnparser.scala      |   60 -
 .../unparsers/ConvertTextBooleanUnparser.scala     |   56 -
 .../unparsers/ConvertTextCalendarUnparser.scala    |  115 -
 .../ConvertTextStandardNumberUnparser.scala        |   96 -
 .../unparsers/ConvertZonedNumberUnparser.scala     |   76 -
 .../processors/unparsers/DelimitedUnparsers.scala  |  127 -
 .../processors/unparsers/DelimiterUnparsers.scala  |   82 -
 .../processors/unparsers/ElementUnparser.scala     |  691 ------
 .../unparsers/ExpressionEvaluatingUnparsers.scala  |  176 --
 .../processors/unparsers/FramingUnparsers.scala    |   88 -
 .../daffodil/processors/unparsers/HasPadding.scala |  100 -
 .../unparsers/HexBinaryLengthUnparser.scala        |  144 --
 .../unparsers/HiddenGroupCombinatorUnparser.scala  |   45 -
 .../unparsers/IBM4690PackedDecimalUnparsers.scala  |  151 --
 .../unparsers/LayeredSequenceUnparser.scala        |  101 -
 .../processors/unparsers/NadaUnparser.scala        |   35 -
 .../unparsers/NilEmptyCombinatorUnparsers.scala    |   64 -
 .../processors/unparsers/NilUnparsers.scala        |   35 -
 .../unparsers/PackedBinaryUnparserTraits.scala     |  117 -
 .../unparsers/PackedDecimalUnparsers.scala         |  160 --
 .../unparsers/SeparatedSequenceUnparsers.scala     |  613 -----
 .../unparsers/SequenceChildUnparsers.scala         |  235 --
 .../unparsers/SequenceUnparserBases.scala          |   37 -
 .../processors/unparsers/SpecifiedLength2.scala    |  832 -------
 .../unparsers/SpecifiedLengthUnparsers.scala       |  450 ----
 .../processors/unparsers/StreamSplitterMixin.scala |  123 -
 .../unparsers/StringLengthUnparsers.scala          |  276 ---
 .../unparsers/StringLiteralForUnparser.scala       |   73 -
 .../unparsers/SuppressableSeparatorUnparser.scala  |  218 --
 .../unparsers/UnseparatedSequenceUnparsers.scala   |  192 --
 .../processors/unparsers/ZeroLengthDetector.scala  |  288 ---
 .../daffodil/unparsers/runtime1/BCDUnparsers.scala |  149 ++
 .../runtime1/BinaryBooleanUnparsers.scala          |  131 +
 .../unparsers/runtime1/BinaryNumberUnparsers.scala |  225 ++
 .../unparsers/runtime1/BlobLengthUnparser.scala    |  112 +
 .../runtime1/ChoiceAndOtherVariousUnparsers.scala  |  195 ++
 .../runtime1/ConvertBinaryCalendarUnparser.scala   |   86 +
 .../ConvertNonBaseTenTextNumberUnparser.scala      |   62 +
 .../runtime1/ConvertTextBooleanUnparser.scala      |   58 +
 .../runtime1/ConvertTextCalendarUnparser.scala     |  117 +
 .../ConvertTextStandardNumberUnparser.scala        |   98 +
 .../runtime1/ConvertZonedNumberUnparser.scala      |   78 +
 .../unparsers/runtime1/DelimitedUnparsers.scala    |  129 +
 .../unparsers/runtime1/DelimiterUnparsers.scala    |   84 +
 .../unparsers/runtime1/ElementUnparser.scala       |  693 ++++++
 .../runtime1/ExpressionEvaluatingUnparsers.scala   |  178 ++
 .../unparsers/runtime1/FramingUnparsers.scala      |   90 +
 .../daffodil/unparsers/runtime1/HasPadding.scala   |  100 +
 .../runtime1/HexBinaryLengthUnparser.scala         |  146 ++
 .../runtime1/HiddenGroupCombinatorUnparser.scala   |   47 +
 .../runtime1/IBM4690PackedDecimalUnparsers.scala   |  153 ++
 .../runtime1/LayeredSequenceUnparser.scala         |  103 +
 .../daffodil/unparsers/runtime1/NadaUnparser.scala |   37 +
 .../runtime1/NilEmptyCombinatorUnparsers.scala     |   66 +
 .../daffodil/unparsers/runtime1/NilUnparsers.scala |   37 +
 .../runtime1/PackedBinaryUnparserTraits.scala      |  119 +
 .../runtime1/PackedDecimalUnparsers.scala          |  162 ++
 .../runtime1/SeparatedSequenceUnparsers.scala      |  615 +++++
 .../runtime1/SequenceChildUnparsers.scala          |  237 ++
 .../unparsers/runtime1/SequenceUnparserBases.scala |   39 +
 .../unparsers/runtime1/SpecifiedLength2.scala      |  834 +++++++
 .../runtime1/SpecifiedLengthUnparsers.scala        |  452 ++++
 .../unparsers/runtime1/StreamSplitterMixin.scala   |  125 +
 .../unparsers/runtime1/StringLengthUnparsers.scala |  278 +++
 .../runtime1/StringLiteralForUnparser.scala        |   73 +
 .../runtime1/SuppressableSeparatorUnparser.scala   |  220 ++
 .../runtime1/UnseparatedSequenceUnparsers.scala    |  194 ++
 .../unparsers/runtime1/ZeroLengthDetector.scala    |  288 +++
 .../scala/org/apache/daffodil/BasicComponent.scala |   55 -
 .../apache/daffodil/api/DFDLParserUnparser.scala   |  306 ---
 .../org/apache/daffodil/debugger/Debugger.scala    |   23 -
 .../daffodil/debugger/InteractiveDebugger.scala    | 2030 ----------------
 .../daffodil/debugger/TraceDebuggerRunner.scala    |   55 -
 .../org/apache/daffodil/dpath/ArrayRelated.scala   |   88 -
 .../org/apache/daffodil/dpath/ComparisonOps.scala  |  293 ---
 .../org/apache/daffodil/dpath/ConverterOps.scala   |  284 ---
 .../org/apache/daffodil/dpath/ConverterOps2.scala  |   90 -
 .../org/apache/daffodil/dpath/ConverterOps3.scala  |   95 -
 .../dpath/DFDLCheckConstraintsFunction.scala       |   58 -
 .../apache/daffodil/dpath/DFDLConstructors.scala   |  228 --
 .../org/apache/daffodil/dpath/DFDLFunctions.scala  |  185 --
 .../org/apache/daffodil/dpath/DFDLFunctions2.scala |   92 -
 .../org/apache/daffodil/dpath/DFDLXFunctions.scala |  289 ---
 .../daffodil/dpath/DFDLXTypeCalcFunctions.scala    |   45 -
 .../scala/org/apache/daffodil/dpath/DPath.scala    |  394 ---
 .../org/apache/daffodil/dpath/DPathRuntime.scala   |  338 ---
 .../scala/org/apache/daffodil/dpath/DState.scala   |  445 ----
 .../scala/org/apache/daffodil/dpath/FNBases.scala  |  226 --
 .../org/apache/daffodil/dpath/FNFunctions.scala    | 1092 ---------
 .../daffodil/dpath/HexBinaryConversions.scala      |   46 -
 .../org/apache/daffodil/dpath/MATHFunctions.scala  |   39 -
 .../scala/org/apache/daffodil/dpath/NodeInfo.scala |  852 -------
 .../org/apache/daffodil/dpath/NumericOps.scala     |  339 ---
 .../daffodil/dpath/SuspendableExpression.scala     |   69 -
 .../org/apache/daffodil/dpath/UpDownMoves.scala    |  142 --
 .../daffodil/dpath/UserDefinedFunctionBase.scala   |   71 -
 .../org/apache/daffodil/dpath/XSConstructors.scala |   85 -
 .../org/apache/daffodil/dpath/XSHexBinary.scala    |   69 -
 .../apache/daffodil/dsom/CompiledExpression1.scala |  572 -----
 .../org/apache/daffodil/dsom/EncodingLattice.scala |   60 -
 .../apache/daffodil/dsom/ExpressionCompiler.scala  |   40 -
 .../scala/org/apache/daffodil/dsom/Facets1.scala   |   44 -
 .../main/scala/org/apache/daffodil/dsom/SDE.scala  |  221 --
 .../apache/daffodil/dsom/walker/PrimTypeView.scala |   48 -
 .../apache/daffodil/events/ParseEventHandler.scala |  195 --
 .../externalvars/ExternalVariablesLoader.scala     |   89 -
 .../daffodil/infoset/ChoiceBranchEvent.scala       |   73 -
 .../org/apache/daffodil/infoset/DataValue.scala    |  234 --
 .../org/apache/daffodil/infoset/Infoset.scala      |  103 -
 .../org/apache/daffodil/infoset/InfosetImpl.scala  | 1765 --------------
 .../apache/daffodil/infoset/InfosetInputter.scala  |  713 ------
 .../apache/daffodil/infoset/InfosetOutputter.scala |  193 --
 .../apache/daffodil/infoset/InfosetWalker.scala    |  555 -----
 .../apache/daffodil/infoset/InvalidInfoset.scala   |   27 -
 .../daffodil/infoset/JDOMInfosetInputter.scala     |  193 --
 .../daffodil/infoset/JDOMInfosetOutputter.scala    |  106 -
 .../daffodil/infoset/JsonInfosetInputter.scala     |  192 --
 .../daffodil/infoset/JsonInfosetOutputter.scala    |  163 --
 .../daffodil/infoset/NullInfosetInputter.scala     |  119 -
 .../daffodil/infoset/NullInfosetOutputter.scala    |   39 -
 .../infoset/PartialNextElementResolver.scala       |  315 ---
 .../daffodil/infoset/SAXInfosetInputter.scala      |  247 --
 .../daffodil/infoset/SAXInfosetOutputter.scala     |  262 --
 .../daffodil/infoset/ScalaXMLInfosetInputter.scala |  204 --
 .../infoset/ScalaXMLInfosetOutputter.scala         |  141 --
 .../daffodil/infoset/TeeInfosetOutputter.scala     |   68 -
 .../daffodil/infoset/W3CDOMInfosetInputter.scala   |  211 --
 .../daffodil/infoset/W3CDOMInfosetOutputter.scala  |  117 -
 .../daffodil/infoset/XMLInfosetOutputter.scala     |   58 -
 .../daffodil/infoset/XMLTextEscapeStyle.scala      |   33 -
 .../daffodil/infoset/XMLTextInfosetInputter.scala  |  402 ----
 .../daffodil/infoset/XMLTextInfosetOutputter.scala |  233 --
 .../org/apache/daffodil/layers/LayerCompiler.scala |  124 -
 .../daffodil/layers/LayerCompilerRegistry.scala    |   46 -
 .../apache/daffodil/layers/LayerTransformer.scala  |  405 ----
 .../daffodil/layers/LayerTransformerFactory.scala  |   34 -
 .../apache/daffodil/processors/BCDParsers.scala    |  117 -
 .../apache/daffodil/processors/DFDLDelimiter.scala |  626 -----
 .../processors/DFDLRegularExpressions.scala        |  161 --
 .../DaffodilParseOutputStreamContentHandler.scala  |  309 ---
 .../processors/DaffodilParseXMLReader.scala        |  209 --
 .../DaffodilUnparseContentHandlerImpl.scala        |  470 ----
 .../org/apache/daffodil/processors/DataLoc.scala   |  138 --
 .../apache/daffodil/processors/DataProcessor.scala |  633 -----
 .../daffodil/processors/DelimiterIterator.scala    |  146 --
 .../processors/DelimiterStackUnparseNode.scala     |   45 -
 .../daffodil/processors/EncodingRuntimeData.scala  |  145 --
 .../apache/daffodil/processors/EscapeScheme.scala  |   97 -
 .../apache/daffodil/processors/EvBinaryFloat.scala |   32 -
 .../apache/daffodil/processors/EvByteOrder.scala   |  102 -
 .../daffodil/processors/EvCalendarLanguage.scala   |   90 -
 .../apache/daffodil/processors/EvDelimiters.scala  |  139 --
 .../org/apache/daffodil/processors/EvElement.scala |  291 ---
 .../apache/daffodil/processors/EvEncoding.scala    |  164 --
 .../daffodil/processors/EvEscapeSchemes.scala      |  161 --
 .../apache/daffodil/processors/EvFieldDFA.scala    |   45 -
 .../apache/daffodil/processors/EvLayering.scala    |   52 -
 .../apache/daffodil/processors/EvTextNumber.scala  |  266 --
 .../apache/daffodil/processors/Evaluatable.scala   |  490 ----
 .../processors/IBM4690PackedDecimalParsers.scala   |  121 -
 .../daffodil/processors/PackedBinaryTraits.scala   |  182 --
 .../daffodil/processors/PackedDecimalParsers.scala |  127 -
 .../daffodil/processors/ProcessingError.scala      |   59 -
 .../daffodil/processors/ProcessorBases.scala       |  163 --
 .../daffodil/processors/ProcessorResult.scala      |   39 -
 .../daffodil/processors/ProcessorStateBases.scala  |  628 -----
 .../apache/daffodil/processors/RuntimeData.scala   |  905 -------
 .../daffodil/processors/SchemaSetRuntimeData.scala |   63 -
 .../daffodil/processors/SuspendableOperation.scala |   97 -
 .../apache/daffodil/processors/Suspension.scala    |  279 ---
 .../daffodil/processors/SuspensionTracker.scala    |  122 -
 .../daffodil/processors/TextJustification.scala    |   36 -
 .../daffodil/processors/TypeCalculator.scala       |  525 ----
 .../apache/daffodil/processors/VariableMap1.scala  |  514 ----
 .../processors/dfa/CreateDelimiterDFA.scala        |  154 --
 .../daffodil/processors/dfa/CreateFieldDFA.scala   |   90 -
 .../daffodil/processors/dfa/CreatePaddingDFA.scala |   65 -
 .../apache/daffodil/processors/dfa/Parser.scala    |   86 -
 .../apache/daffodil/processors/dfa/Registers.scala |  178 --
 .../org/apache/daffodil/processors/dfa/Rules.scala |  793 ------
 .../apache/daffodil/processors/dfa/Runtime.scala   |  162 --
 .../processors/dfa/TextDelimitedParser.scala       |  368 ---
 .../processors/dfa/TextDelimitedUnparser.scala     |  369 ---
 .../processors/dfa/TextPaddingParser.scala         |   53 -
 .../daffodil/processors/dfa/TextParser.scala       |   67 -
 .../apache/daffodil/processors/dfa/Unparser.scala  |   70 -
 .../processors/parsers/AssertPatternParsers.scala  |   96 -
 .../processors/parsers/BinaryBooleanParsers.scala  |  123 -
 .../processors/parsers/BinaryNumberParsers.scala   |  183 --
 .../processors/parsers/BinaryNumberTraits.scala    |  138 --
 .../processors/parsers/BlobLengthParsers.scala     |  115 -
 .../parsers/ConvertTextStandardNumberParser.scala  |  245 --
 .../processors/parsers/DelimitedParsers.scala      |  248 --
 .../processors/parsers/DelimiterParsers.scala      |  128 -
 .../processors/parsers/ElementCombinator1.scala    |  316 ---
 .../processors/parsers/ElementKindParsers.scala    |  237 --
 .../parsers/ExpressionEvaluatingParsers.scala      |  239 --
 .../processors/parsers/FramingParsers.scala        |   54 -
 .../processors/parsers/HasVariableLength.scala     |   38 -
 .../parsers/HexBinaryLengthParsers.scala           |  109 -
 .../parsers/HiddenGroupCombinatorParser.scala      |   45 -
 .../parsers/InitiatedContentParsers.scala          |   87 -
 .../processors/parsers/LayeredSequenceParser.scala |   61 -
 .../parsers/NilEmptyCombinatorParsers.scala        |   55 -
 .../processors/parsers/NilMatcherMixin.scala       |  149 --
 .../daffodil/processors/parsers/NilParsers.scala   |  112 -
 .../parsers/NonBaseTenTextNumberParser.scala       |   78 -
 .../daffodil/processors/parsers/PState.scala       |  765 ------
 .../processors/parsers/PaddingRuntimeMixin.scala   |   42 -
 .../daffodil/processors/parsers/ParseErrors.scala  |  117 -
 .../daffodil/processors/parsers/Parser.scala       |  284 ---
 .../processors/parsers/PrimitivesDateTime1.scala   |  226 --
 .../processors/parsers/SeparatedParseHelper.scala  |  267 --
 .../SeparatedSequenceChildParseResultHelper.scala  |  324 ---
 .../parsers/SeparatedSequenceParsers.scala         |  130 -
 .../processors/parsers/SequenceChildBases.scala    |  559 -----
 .../parsers/SequenceChildParseResultHelper.scala   |  488 ----
 .../processors/parsers/SequenceParserBases.scala   |  506 ----
 .../parsers/SpecifiedLengthParsers.scala           |  324 ---
 .../processors/parsers/StringLengthParsers.scala   |  109 -
 .../processors/parsers/TextBooleanParser.scala     |   61 -
 ...UnseparatedSequenceChildParseResultHelper.scala |   62 -
 .../parsers/UnseparatedSequenceParsers.scala       |   76 -
 .../processors/parsers/ZonedTextParsers.scala      |  117 -
 .../daffodil/processors/unparsers/UState.scala     |  696 ------
 .../processors/unparsers/UnparseError.scala        |   41 -
 .../daffodil/processors/unparsers/Unparser.scala   |  208 --
 .../apache/daffodil/runtime1/BasicComponent.scala  |   55 +
 .../daffodil/runtime1/api/DFDLParserUnparser.scala |  308 +++
 .../daffodil/runtime1/debugger/Debugger.scala      |   23 +
 .../runtime1/debugger/InteractiveDebugger.scala    | 2030 ++++++++++++++++
 .../runtime1/debugger/TraceDebuggerRunner.scala    |   55 +
 .../daffodil/runtime1/dpath/ArrayRelated.scala     |   88 +
 .../daffodil/runtime1/dpath/ComparisonOps.scala    |  293 +++
 .../daffodil/runtime1/dpath/ConverterOps.scala     |  284 +++
 .../daffodil/runtime1/dpath/ConverterOps2.scala    |   90 +
 .../daffodil/runtime1/dpath/ConverterOps3.scala    |   95 +
 .../dpath/DFDLCheckConstraintsFunction.scala       |   58 +
 .../daffodil/runtime1/dpath/DFDLConstructors.scala |  228 ++
 .../daffodil/runtime1/dpath/DFDLFunctions.scala    |  185 ++
 .../daffodil/runtime1/dpath/DFDLFunctions2.scala   |   92 +
 .../daffodil/runtime1/dpath/DFDLXFunctions.scala   |  289 +++
 .../runtime1/dpath/DFDLXTypeCalcFunctions.scala    |   45 +
 .../org/apache/daffodil/runtime1/dpath/DPath.scala |  394 +++
 .../daffodil/runtime1/dpath/DPathRuntime.scala     |  338 +++
 .../apache/daffodil/runtime1/dpath/DState.scala    |  445 ++++
 .../apache/daffodil/runtime1/dpath/FNBases.scala   |  226 ++
 .../daffodil/runtime1/dpath/FNFunctions.scala      | 1092 +++++++++
 .../runtime1/dpath/HexBinaryConversions.scala      |   46 +
 .../daffodil/runtime1/dpath/MATHFunctions.scala    |   39 +
 .../apache/daffodil/runtime1/dpath/NodeInfo.scala  |  853 +++++++
 .../daffodil/runtime1/dpath/NumericOps.scala       |  339 +++
 .../runtime1/dpath/SuspendableExpression.scala     |   69 +
 .../daffodil/runtime1/dpath/UpDownMoves.scala      |  142 ++
 .../runtime1/dpath/UserDefinedFunctionBase.scala   |   71 +
 .../daffodil/runtime1/dpath/XSConstructors.scala   |   85 +
 .../daffodil/runtime1/dpath/XSHexBinary.scala      |   69 +
 .../runtime1/dsom/CompiledExpression1.scala        |  572 +++++
 .../daffodil/runtime1/dsom/EncodingLattice.scala   |   60 +
 .../runtime1/dsom/ExpressionCompiler.scala         |   40 +
 .../apache/daffodil/runtime1/dsom/Facets1.scala    |   44 +
 .../org/apache/daffodil/runtime1/dsom/SDE.scala    |  221 ++
 .../runtime1/dsom/walker/PrimTypeView.scala        |   48 +
 .../runtime1/events/ParseEventHandler.scala        |  195 ++
 .../externalvars/ExternalVariablesLoader.scala     |   91 +
 .../runtime1/infoset/ChoiceBranchEvent.scala       |   73 +
 .../daffodil/runtime1/infoset/DataValue.scala      |  234 ++
 .../apache/daffodil/runtime1/infoset/Infoset.scala |  103 +
 .../daffodil/runtime1/infoset/InfosetImpl.scala    | 1765 ++++++++++++++
 .../runtime1/infoset/InfosetInputter.scala         |  713 ++++++
 .../runtime1/infoset/InfosetOutputter.scala        |  193 ++
 .../daffodil/runtime1/infoset/InfosetWalker.scala  |  555 +++++
 .../daffodil/runtime1/infoset/InvalidInfoset.scala |   27 +
 .../runtime1/infoset/JDOMInfosetInputter.scala     |  193 ++
 .../runtime1/infoset/JDOMInfosetOutputter.scala    |  106 +
 .../runtime1/infoset/JsonInfosetInputter.scala     |  192 ++
 .../runtime1/infoset/JsonInfosetOutputter.scala    |  163 ++
 .../runtime1/infoset/NullInfosetInputter.scala     |  119 +
 .../runtime1/infoset/NullInfosetOutputter.scala    |   39 +
 .../infoset/PartialNextElementResolver.scala       |  315 +++
 .../runtime1/infoset/SAXInfosetInputter.scala      |  247 ++
 .../runtime1/infoset/SAXInfosetOutputter.scala     |  262 ++
 .../runtime1/infoset/ScalaXMLInfosetInputter.scala |  204 ++
 .../infoset/ScalaXMLInfosetOutputter.scala         |  141 ++
 .../runtime1/infoset/TeeInfosetOutputter.scala     |   68 +
 .../runtime1/infoset/W3CDOMInfosetInputter.scala   |  211 ++
 .../runtime1/infoset/W3CDOMInfosetOutputter.scala  |  117 +
 .../runtime1/infoset/XMLInfosetOutputter.scala     |   58 +
 .../runtime1/infoset/XMLTextEscapeStyle.scala      |   33 +
 .../runtime1/infoset/XMLTextInfosetInputter.scala  |  402 ++++
 .../runtime1/infoset/XMLTextInfosetOutputter.scala |  233 ++
 .../daffodil/runtime1/layers/LayerCompiler.scala   |  124 +
 .../runtime1/layers/LayerCompilerRegistry.scala    |   46 +
 .../runtime1/layers/LayerTransformer.scala         |  405 ++++
 .../runtime1/layers/LayerTransformerFactory.scala  |   34 +
 .../daffodil/runtime1/processors/BCDParsers.scala  |  117 +
 .../runtime1/processors/DFDLDelimiter.scala        |  626 +++++
 .../processors/DFDLRegularExpressions.scala        |  161 ++
 .../DaffodilParseOutputStreamContentHandler.scala  |  309 +++
 .../processors/DaffodilParseXMLReader.scala        |  209 ++
 .../DaffodilUnparseContentHandlerImpl.scala        |  470 ++++
 .../daffodil/runtime1/processors/DataLoc.scala     |  138 ++
 .../runtime1/processors/DataProcessor.scala        |  633 +++++
 .../runtime1/processors/DelimiterIterator.scala    |  146 ++
 .../processors/DelimiterStackUnparseNode.scala     |   45 +
 .../runtime1/processors/EncodingRuntimeData.scala  |  145 ++
 .../runtime1/processors/EscapeScheme.scala         |   97 +
 .../runtime1/processors/EvBinaryFloat.scala        |   32 +
 .../daffodil/runtime1/processors/EvByteOrder.scala |  102 +
 .../runtime1/processors/EvCalendarLanguage.scala   |   90 +
 .../runtime1/processors/EvDelimiters.scala         |  139 ++
 .../daffodil/runtime1/processors/EvElement.scala   |  291 +++
 .../daffodil/runtime1/processors/EvEncoding.scala  |  164 ++
 .../runtime1/processors/EvEscapeSchemes.scala      |  161 ++
 .../daffodil/runtime1/processors/EvFieldDFA.scala  |   45 +
 .../daffodil/runtime1/processors/EvLayering.scala  |   52 +
 .../runtime1/processors/EvTextNumber.scala         |  266 ++
 .../daffodil/runtime1/processors/Evaluatable.scala |  490 ++++
 .../processors/IBM4690PackedDecimalParsers.scala   |  121 +
 .../runtime1/processors/PackedBinaryTraits.scala   |  182 ++
 .../runtime1/processors/PackedDecimalParsers.scala |  127 +
 .../runtime1/processors/ProcessingError.scala      |   59 +
 .../runtime1/processors/ProcessorBases.scala       |  163 ++
 .../runtime1/processors/ProcessorResult.scala      |   39 +
 .../runtime1/processors/ProcessorStateBases.scala  |  628 +++++
 .../daffodil/runtime1/processors/RuntimeData.scala |  905 +++++++
 .../runtime1/processors/SchemaSetRuntimeData.scala |   63 +
 .../runtime1/processors/SuspendableOperation.scala |   97 +
 .../daffodil/runtime1/processors/Suspension.scala  |  279 +++
 .../runtime1/processors/SuspensionTracker.scala    |  122 +
 .../runtime1/processors/TextJustification.scala    |   36 +
 .../runtime1/processors/TypeCalculator.scala       |  525 ++++
 .../runtime1/processors/VariableMap1.scala         |  514 ++++
 .../processors/dfa/CreateDelimiterDFA.scala        |  154 ++
 .../runtime1/processors/dfa/CreateFieldDFA.scala   |   90 +
 .../runtime1/processors/dfa/CreatePaddingDFA.scala |   65 +
 .../daffodil/runtime1/processors/dfa/Parser.scala  |   86 +
 .../runtime1/processors/dfa/Registers.scala        |  178 ++
 .../daffodil/runtime1/processors/dfa/Rules.scala   |  793 ++++++
 .../daffodil/runtime1/processors/dfa/Runtime.scala |  162 ++
 .../processors/dfa/TextDelimitedParser.scala       |  368 +++
 .../processors/dfa/TextDelimitedUnparser.scala     |  369 +++
 .../processors/dfa/TextPaddingParser.scala         |   53 +
 .../runtime1/processors/dfa/TextParser.scala       |   67 +
 .../runtime1/processors/dfa/Unparser.scala         |   70 +
 .../processors/parsers/AssertPatternParsers.scala  |   96 +
 .../processors/parsers/BinaryBooleanParsers.scala  |  123 +
 .../processors/parsers/BinaryNumberParsers.scala   |  183 ++
 .../processors/parsers/BinaryNumberTraits.scala    |  138 ++
 .../processors/parsers/BlobLengthParsers.scala     |  115 +
 .../parsers/ConvertTextStandardNumberParser.scala  |  245 ++
 .../processors/parsers/DelimitedParsers.scala      |  248 ++
 .../processors/parsers/DelimiterParsers.scala      |  128 +
 .../processors/parsers/ElementCombinator1.scala    |  316 +++
 .../processors/parsers/ElementKindParsers.scala    |  237 ++
 .../parsers/ExpressionEvaluatingParsers.scala      |  239 ++
 .../processors/parsers/FramingParsers.scala        |   54 +
 .../processors/parsers/HasVariableLength.scala     |   38 +
 .../parsers/HexBinaryLengthParsers.scala           |  109 +
 .../parsers/HiddenGroupCombinatorParser.scala      |   45 +
 .../parsers/InitiatedContentParsers.scala          |   87 +
 .../processors/parsers/LayeredSequenceParser.scala |   61 +
 .../parsers/NilEmptyCombinatorParsers.scala        |   55 +
 .../processors/parsers/NilMatcherMixin.scala       |  149 ++
 .../runtime1/processors/parsers/NilParsers.scala   |  112 +
 .../parsers/NonBaseTenTextNumberParser.scala       |   78 +
 .../runtime1/processors/parsers/PState.scala       |  765 ++++++
 .../processors/parsers/PaddingRuntimeMixin.scala   |   42 +
 .../runtime1/processors/parsers/ParseErrors.scala  |  117 +
 .../runtime1/processors/parsers/Parser.scala       |  284 +++
 .../processors/parsers/PrimitivesDateTime1.scala   |  226 ++
 .../processors/parsers/SeparatedParseHelper.scala  |  267 ++
 .../SeparatedSequenceChildParseResultHelper.scala  |  324 +++
 .../parsers/SeparatedSequenceParsers.scala         |  130 +
 .../processors/parsers/SequenceChildBases.scala    |  559 +++++
 .../parsers/SequenceChildParseResultHelper.scala   |  488 ++++
 .../processors/parsers/SequenceParserBases.scala   |  506 ++++
 .../parsers/SpecifiedLengthParsers.scala           |  324 +++
 .../processors/parsers/StringLengthParsers.scala   |  109 +
 .../processors/parsers/TextBooleanParser.scala     |   61 +
 ...UnseparatedSequenceChildParseResultHelper.scala |   62 +
 .../parsers/UnseparatedSequenceParsers.scala       |   76 +
 .../processors/parsers/ZonedTextParsers.scala      |  117 +
 .../runtime1/processors/unparsers/UState.scala     |  696 ++++++
 .../processors/unparsers/UnparseError.scala        |   41 +
 .../runtime1/processors/unparsers/Unparser.scala   |  208 ++
 .../{ => runtime1}/reflection/FieldFinder.scala.md |    0
 .../runtime1/udf/UserDefinedFunctionErrors.scala   |   61 +
 .../runtime1/udf/UserDefinedFunctionService.scala  |  270 +++
 .../daffodil/udf/UserDefinedFunctionErrors.scala   |   61 -
 .../daffodil/udf/UserDefinedFunctionService.scala  |  268 ---
 .../org/apache/daffodil/dpath/TestNodeInfo.scala   |   30 -
 .../org/apache/daffodil/dpath/TestRounding.scala   |   69 -
 .../apache/daffodil/dsom/TestEntityReplacer.scala  |  202 --
 .../daffodil/parser/TestCharsetBehavior.scala      |  459 ----
 .../daffodil/parser/TestCharsetDecoder2.scala      |   29 -
 .../input/TestDFDLRegularExpressions.scala         |  716 ------
 .../apache/daffodil/processors/input/TestICU.scala |  216 --
 .../daffodil/processors/input/TestRegex.scala      | 1812 --------------
 .../daffodil/runtime1/dpath/TestNodeInfo.scala     |   30 +
 .../daffodil/runtime1/dpath/TestRounding.scala     |   69 +
 .../runtime1/dsom/TestEntityReplacer.scala         |  202 ++
 .../runtime1/parser/TestCharsetBehavior.scala      |  459 ++++
 .../runtime1/parser/TestCharsetDecoder2.scala      |   29 +
 .../input/TestDFDLRegularExpressions.scala         |  716 ++++++
 .../runtime1/processors/input/TestICU.scala        |  216 ++
 .../runtime1/processors/input/TestRegex.scala      | 1812 ++++++++++++++
 .../reflection/TestFieldFinder.scala.md            |    0
 .../apache/daffodil/runtime2/CodeGenerator.scala   |   12 +-
 .../daffodil/runtime2/Runtime2CodeGenerator.scala  |   52 +-
 .../daffodil/runtime2/Runtime2DataProcessor.scala  |   34 +-
 .../generators/AlignmentFillCodeGenerator.scala    |    2 +-
 .../generators/BinaryBooleanCodeGenerator.scala    |    6 +-
 .../generators/BinaryFloatCodeGenerator.scala      |    2 +-
 .../BinaryIntegerKnownLengthCodeGenerator.scala    |    2 +-
 .../generators/BinaryValueCodeGenerator.scala      |   10 +-
 .../runtime2/generators/CodeGeneratorState.scala   |   18 +-
 .../generators/HexBinaryCodeGenerator.scala        |    6 +-
 .../daffodil/runtime2/TestCodeGenerator.scala      |   10 +-
 .../scala/org/apache/daffodil/sapi/Daffodil.scala  |   54 +-
 .../org/apache/daffodil/sapi/infoset/Infoset.scala |   40 +-
 .../daffodil/sapi/infoset/XMLTextEscapeStyle.scala |   66 +-
 .../daffodil/sapi/packageprivate/Utils.scala       |    8 +-
 ...> org.apache.daffodil.lib.api.ValidatorFactory} |    0
 .../daffodil/example/TestCustomDebuggerAPI.scala   |    8 +-
 .../example/TestInfosetInputterOutputter.scala     |   20 +-
 .../org/apache/daffodil/example/TestScalaAPI.scala |    2 +-
 .../example/ValidatorExamplesSupport.scala         |   10 +-
 .../daffodil/example/ValidatorSpiExample.scala     |    2 +-
 .../daffodil/sapi/SAXErrorHandlerForSAPITest.scala |    2 +-
 ...> org.apache.daffodil.lib.api.ValidatorFactory} |    0
 .../schematron/ClassPathUriResolver.scala          |    2 +-
 .../validation/schematron/Schematron.scala         |    2 +-
 .../validation/schematron/SchematronResult.scala   |    6 +-
 .../schematron/SchematronValidator.scala           |    8 +-
 .../schematron/SchematronValidatorFactory.scala    |    6 +-
 .../validation/schematron/EmbeddedTesting.scala    |    2 +-
 .../validation/schematron/TestSpiLoading.scala     |    2 +-
 .../validation/schematron/TestUriResolver.scala    |    4 +-
 .../schematron/TestValidatorFactory.scala          |    4 +-
 .../src/main/scala/org/apache/daffodil/Tak.scala   |   76 -
 .../main/scala/org/apache/daffodil/tak/Tak.scala   |   76 +
 .../org/apache/daffodil/tdml/RunnerFactory.scala   |    6 +-
 .../org/apache/daffodil/tdml/TDMLException.scala   |    6 +-
 .../org/apache/daffodil/tdml/TDMLRunner.scala      |   70 +-
 .../tdml/processor/TDMLDFDLProcessor.scala         |   10 +-
 .../charset/TestLSBFirstAndUSASCII7BitPacked.scala |   10 +-
 .../apache/daffodil/tdml/UnitTestTDMLRunner.scala  |   10 +-
 .../processor/tdml/DaffodilTDMLDFDLProcessor.scala |  432 ++++
 .../processor/tdml/Runtime2TDMLDFDLProcessor.scala |  195 ++
 .../processor/tdml/TDMLInfosetInputter.scala       |  140 ++
 .../processor/tdml/TDMLInfosetOutputter.scala      |   63 +
 .../apache/daffodil/tdml/TDMLInfosetInputter.scala |  138 --
 .../daffodil/tdml/TDMLInfosetOutputter.scala       |   63 -
 .../tdml/processor/DaffodilTDMLDFDLProcessor.scala |  430 ----
 .../tdml/processor/Runtime2TDMLDFDLProcessor.scala |  193 --
 .../daffodil/processor/tdml/TestRunnerFactory.java |  118 +
 .../apache/daffodil/tdml/TestRunnerFactory.java    |  114 -
 .../daffodil/processor/tdml/TestExtVars1.scala     |   44 +
 .../processor/tdml/TestTDMLCrossTest.scala         |   66 +
 .../processor/tdml/TestTDMLRoundTrips.scala        |  386 +++
 .../daffodil/processor/tdml/TestTDMLRunner.scala   |  943 ++++++++
 .../daffodil/processor/tdml/TestTDMLRunner2.scala  |  532 ++++
 .../tdml/TestTDMLRunnerCommentSyntax.scala         |   98 +
 .../processor/tdml/TestTDMLRunnerConfig.scala      |  216 ++
 .../processor/tdml/TestTDMLRunnerTutorial.scala    |  112 +
 .../processor/tdml/TestTDMLRunnerWarnings.scala    |  248 ++
 .../processor/tdml/TestTDMLUnparseCases.scala      |   60 +
 .../org/apache/daffodil/tdml/TestExtVars1.scala    |   42 -
 .../apache/daffodil/tdml/TestTDMLCrossTest.scala   |   62 -
 .../apache/daffodil/tdml/TestTDMLRoundTrips.scala  |  382 ---
 .../org/apache/daffodil/tdml/TestTDMLRunner.scala  |  937 --------
 .../org/apache/daffodil/tdml/TestTDMLRunner2.scala |  528 ----
 .../tdml/TestTDMLRunnerCommentSyntax.scala         |   96 -
 .../daffodil/tdml/TestTDMLRunnerConfig.scala       |  214 --
 .../daffodil/tdml/TestTDMLRunnerTutorial.scala     |  110 -
 .../daffodil/tdml/TestTDMLRunnerWarnings.scala     |  246 --
 .../daffodil/tdml/TestTDMLUnparseCases.scala       |   56 -
 ...il.io.processors.charset.BitsCharsetDefinition} |    0
 .../org.apache.daffodil.layers.LayerCompiler       |   34 -
 ...g.apache.daffodil.runtime1.layers.LayerCompiler |   34 +
 .../daffodil/charsets/ISO_8859_1_Reverse.scala     |    6 +-
 .../apache/daffodil/charsets/TestISO_8859_13.scala |    6 +-
 .../extensions/TestChoiceBranchKeyRanges.scala     |    2 +-
 .../apache/daffodil/infoset/TestStringAsXml.scala  |   14 +-
 .../apache/daffodil/layers/AISTransformer.scala    |  196 --
 .../org/apache/daffodil/layers/CheckDigit.scala    |   79 -
 .../daffodil/layers/CheckDigitLayerCompiler.scala  |   76 -
 .../org/apache/daffodil/layers/IPv4Checksum.scala  |   91 -
 .../layers/IPv4ChecksumLayerCompiler.scala         |   68 -
 .../scala/org/apache/daffodil/layers/TestAIS.scala |   43 -
 .../apache/daffodil/layers/TestAISStreams.scala    |   91 -
 .../apache/daffodil/layers/TestCheckDigit.scala    |   43 -
 .../org/apache/daffodil/layers/TestIPv4.scala      |   51 -
 .../org/apache/daffodil/layers/TestLayers.scala    |   44 -
 .../daffodil/runtime1/layers/AISTransformer.scala  |  196 ++
 .../daffodil/runtime1/layers/CheckDigit.scala      |   79 +
 .../runtime1/layers/CheckDigitLayerCompiler.scala  |   76 +
 .../daffodil/runtime1/layers/IPv4Checksum.scala    |   91 +
 .../layers/IPv4ChecksumLayerCompiler.scala         |   68 +
 .../apache/daffodil/runtime1/layers/TestAIS.scala  |   43 +
 .../daffodil/runtime1/layers/TestAISStreams.scala  |   91 +
 .../daffodil/runtime1/layers/TestCheckDigit.scala  |   43 +
 .../apache/daffodil/runtime1/layers/TestIPv4.scala |   51 +
 .../daffodil/runtime1/layers/TestLayers.scala      |   44 +
 .../apache/daffodil/runtime2/TestCollisions.scala  |    2 +-
 .../daffodil/runtime2/TestEgressXdccBw.scala       |    2 +-
 .../org/apache/daffodil/runtime2/TestExNums.scala  |    2 +-
 .../daffodil/runtime2/TestIngressXdccBw.scala      |    2 +-
 .../runtime2/TestIsrmGreenToOrange60000.scala      |    2 +-
 .../runtime2/TestIsrmOrangeToGreen60002.scala      |    2 +-
 .../runtime2/TestMpuGreenToOrange60004.scala       |    2 +-
 .../runtime2/TestMpuOrangeToGreen60006.scala       |    2 +-
 .../org/apache/daffodil/runtime2/TestNested.scala  |    2 +-
 .../org/apache/daffodil/runtime2/TestOrion.scala   |    2 +-
 .../org/apache/daffodil/runtime2/TestPadTest.scala |    2 +-
 .../apache/daffodil/runtime2/TestVariableLen.scala |    2 +-
 .../section00/general/TestDisallowDocType.scala    |   10 +-
 .../general/TestExpressionPropertyWarnings.scala   |   82 +-
 .../daffodil/section00/general/TestGeneral.scala   |    2 +-
 .../daffodil/section00/general/TestTextBidi.scala  |   86 +-
 .../section07/variables/TestVariables.scala        |    4 +-
 .../length_properties/TestLengthProperties.scala   |    2 +-
 .../section13/validation_errors/PadCharacter.scala |    2 +-
 .../StringFunctions/StringFunctionsProvider.scala  |    2 +-
 .../StringFunctions/StringFunctionsProvider.scala  |    2 +-
 .../StringFunctions/StringFunctionsProvider.scala  |    2 +-
 .../StringFunctions/StringFunctionsProvider.scala  |    2 +-
 project/Rat.scala                                  |   66 +-
 1329 files changed, 123753 insertions(+), 123515 deletions(-)

diff --git a/daffodil-cli/src/it/scala/org/apache/daffodil/cliTest/TestCLIUdfs.scala b/daffodil-cli/src/it/scala/org/apache/daffodil/cliTest/TestCLIUdfs.scala
index 396e6d0e3..81056339d 100644
--- a/daffodil-cli/src/it/scala/org/apache/daffodil/cliTest/TestCLIUdfs.scala
+++ b/daffodil-cli/src/it/scala/org/apache/daffodil/cliTest/TestCLIUdfs.scala
@@ -19,8 +19,8 @@ package org.apache.daffodil.cliTest
 
 import java.nio.file.Path
 import org.junit.Test
-import org.apache.daffodil.CLI.Util._
-import org.apache.daffodil.Main.ExitCode
+import org.apache.daffodil.cli.cliTest.Util._
+import org.apache.daffodil.cli.Main.ExitCode
 
 class TestCLIUdfs {
 
diff --git a/daffodil-cli/src/main/scala/org/apache/daffodil/InfosetTypes.scala b/daffodil-cli/src/main/scala/org/apache/daffodil/InfosetTypes.scala
deleted file mode 100644
index 375286689..000000000
--- a/daffodil-cli/src/main/scala/org/apache/daffodil/InfosetTypes.scala
+++ /dev/null
@@ -1,710 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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.
- */
-
-package org.apache.daffodil
-
-import java.io.ByteArrayInputStream
-import java.io.InputStream
-import java.io.OutputStream
-import java.net.URI
-import java.nio.charset.StandardCharsets
-import javax.xml.parsers.DocumentBuilderFactory
-import javax.xml.transform.TransformerFactory
-import javax.xml.transform.dom.DOMSource
-import javax.xml.transform.stream.StreamResult
-
-import scala.collection.mutable.ArrayBuffer
-import scala.xml.SAXParser
-
-import com.siemens.ct.exi.core.EXIFactory
-import com.siemens.ct.exi.core.helpers.DefaultEXIFactory
-import com.siemens.ct.exi.grammars.GrammarFactory
-import com.siemens.ct.exi.main.api.sax.EXIResult
-import com.siemens.ct.exi.main.api.sax.EXISource
-
-import org.apache.commons.io.IOUtils
-
-import org.xml.sax.Attributes
-import org.xml.sax.ContentHandler
-import org.xml.sax.InputSource
-import org.xml.sax.XMLReader
-import org.xml.sax.Locator
-import org.xml.sax.helpers.AttributesImpl
-import org.xml.sax.helpers.DefaultHandler
-
-import org.apache.daffodil.api.DFDL
-import org.apache.daffodil.api.DFDL.DataProcessor
-import org.apache.daffodil.api.DFDL.DaffodilUnparseErrorSAXException
-import org.apache.daffodil.api.DFDL.ParseResult
-import org.apache.daffodil.api.DFDL.UnparseResult
-import org.apache.daffodil.infoset.InfosetInputter
-import org.apache.daffodil.infoset.InfosetOutputter
-import org.apache.daffodil.infoset.JDOMInfosetInputter
-import org.apache.daffodil.infoset.JDOMInfosetOutputter
-import org.apache.daffodil.infoset.JsonInfosetInputter
-import org.apache.daffodil.infoset.JsonInfosetOutputter
-import org.apache.daffodil.infoset.NullInfosetInputter
-import org.apache.daffodil.infoset.NullInfosetOutputter
-import org.apache.daffodil.infoset.ScalaXMLInfosetInputter
-import org.apache.daffodil.infoset.ScalaXMLInfosetOutputter
-import org.apache.daffodil.infoset.W3CDOMInfosetInputter
-import org.apache.daffodil.infoset.W3CDOMInfosetOutputter
-import org.apache.daffodil.infoset.XMLTextInfosetInputter
-import org.apache.daffodil.infoset.XMLTextInfosetOutputter
-import org.apache.daffodil.io.InputSourceDataInputStream
-import org.apache.daffodil.processors.DaffodilParseOutputStreamContentHandler
-import org.apache.daffodil.xml.DFDLCatalogResolver
-import org.apache.daffodil.xml.DaffodilSAXParserFactory
-import org.apache.daffodil.xml.XMLUtils
-
-object InfosetType extends Enumeration {
-  type Type = Value
-
-  val EXI = Value("exi")
-  val EXISA = Value("exisa")
-  val JDOM = Value("jdom")
-  val JSON = Value("json")
-  val NULL = Value("null")
-  val SAX = Value("sax")
-  val SCALA_XML = Value("scala-xml")
-  val W3CDOM = Value("w3cdom")
-  val XML = Value("xml")
-
-  /**
-   * Get an InfosetHandler, with the goal of doing as much initialization/work
-   * as needed prior to calling the parse() or unparse() methods to improve
-   * accuracy of performance metrics
-   *
-   * @param infosetType the type of InfosetHandler to create
-   * @param dataProcessor the dataProcessor that the InfosetHandler should use
-   *   during parse/unparse operations
-   * @param schemaUri only used for EXISA, to support schema aware
-   *   parsing/unparsing
-   * @param forPerformance only used for SAX. If true, the
-   *   SAXInfosetHandler will drop all SAX events on parse, and will
-   *   pre-process the infoset into an array of SAX events and replay them on
-   *   unparse. If false, it directly parses and unparses to/from XML
-   *   text--this allows the caller to visualize the SAX as XML, similar
-   *   to how JDOM, SCALA_XML, etc can be serialized to strings
-   */
-  def getInfosetHandler(
-    infosetType: InfosetType.Type,
-    dataProcessor: DFDL.DataProcessor,
-    schemaUri: Option[URI],
-    forPerformance: Boolean): InfosetHandler = {
-
-    infosetType match {
-      case InfosetType.EXI => EXIInfosetHandler(dataProcessor)
-      case InfosetType.EXISA => EXIInfosetHandler(dataProcessor, schemaUri.get)
-      case InfosetType.JDOM => JDOMInfosetHandler(dataProcessor)
-      case InfosetType.JSON => JsonInfosetHandler(dataProcessor)
-      case InfosetType.NULL => NULLInfosetHandler(dataProcessor)
-      case InfosetType.SAX => SAXInfosetHandler(dataProcessor, forPerformance)
-      case InfosetType.SCALA_XML => ScalaXMLInfosetHandler(dataProcessor)
-      case InfosetType.W3CDOM => W3CDOMInfosetHandler(dataProcessor)
-      case InfosetType.XML => XMLTextInfosetHandler(dataProcessor)
-    }
-  }
-}
-
-sealed trait InfosetHandler {
-
-   /**
-    * Parse data from the InputSourceDataInputStream with the provided
-    * DataProcessor and return a new InfosetParseResult instance. Depending on
-    * the provided InfosetType, may or may not write an infoset instance to the
-    * OutputStream during parsing. This method will be called in a performance
-    * loop, so InfosetHandler constructors should do as much preprocessing and
-    * initialization as possible to save time in this method.
-    *
-    * Some InfosetHandlers will not write anything to the OutputStream here
-    * because they just create objects, call interface functions, or create
-    * nothing (e.g., JDOM, SCALA_XML, NULL). However, Daffodil's CLI wants all
-    * infoset types except NULL to output an infoset during parsing, so
-    * InfosetHandlers return an InfosetParseResult which can serialize the
-    * infoset to a string and write the string to the OutputStream. The CLI
-    * does not want to output the infoset in a performance loop, so we put such
-    * serialization and output code into InfosetParseResult, not
-    * InfosetHandler.
-    *
-    * @param input Data to be parsed into an infoset
-    * @param os OutputStream to write an infoset to
-    */
-  def parse(input: InputSourceDataInputStream, os: OutputStream): InfosetParseResult
-
-  /**
-   * Convert an Array of Bytes to whatever form the unparse() function expects
-   * to use, preferably as close to the native format for the infoset type as
-   * possible
-   *
-   * The return value of the Array[Byte] variant must be thread safe and
-   * immutable since it could potentially be shared among other threaded calls
-   * to unparse()
-   *
-   * This function is guaranteed to be called outside of a performance loop, so
-   * expensive operations to create the native infoset representation should
-   * take place here instead of the unparse() function.
-   */
-  def dataToInfoset(bytes: Array[Byte]): AnyRef
-
-  /**
-   * Convert an InputStream to whatever form the unparse() function expects to
-   * use, preferably as close to the native format for the infoset type as
-   * possible
-   *
-   * With this InputStream variant, we can assume the caller knows that the
-   * infoset represented by this InputStream will only be unparsed once and so
-   * it is acceptable if the result is mutable or non-thread safe. For
-   * InfosetHandlers that easily support InputStreams, it is recommended to
-   * simply return the same InputStream since it avoids reading the entire
-   * infoset into memory and making it possible to unparse large infosets.
-   * However, for InfosetHandlers that do not accept InputStreams,
-   * implementations must read in the entire InputStream and convert it to
-   * whatever they expect (e.g. Scala XML Node for "scala-xml"). Supporting
-   * large inputs with such infoset types is not possible.
-   *
-   * This function is guaranteed to be called outside of a performance loop, so
-   * expensive operations to create the native infoset representation should
-   * take place here instead of the unparse() function.
-   */
-  def dataToInfoset(stream: InputStream): AnyRef
-
-  /**
-   * Unparse an infoset to the given OutputStream. This method will be called
-   * in a performance loop, so InfosetHandler constructors and/or dataToInfoset()
-   * functions should perform as much preprocessing and initialization as possible
-   * to save time in this method.
-   *
-   * @param infoset Infoset representation returned by dataToInfoset() functions
-   * @param output Output channel to write the unparse data to
-   */
-  def unparse(infoset: AnyRef, output: DFDL.Output): UnparseResult
-
-  /**
-   * DataProcessor to be used for parse/unparse calls
-   */
-  protected def dataProcessor: DataProcessor
-
-  /**
-   * Helper function to parse data using the Daffodil InfosetOutputter API
-   */
-  final protected def parseWithInfosetOutputter(input: InputSourceDataInputStream, output: InfosetOutputter): ParseResult = {
-    output.setBlobAttributes(Main.blobDir, null, Main.blobSuffix)
-    val pr = dataProcessor.parse(input, output)
-    pr
-  }
-
-  /**
-   * Helper function to unparse data using the Daffodil InfosetInputter API
-   */
-  final protected def unparseWithInfosetInputter(input: InfosetInputter, output: DFDL.Output): UnparseResult = {
-    val ur = dataProcessor.unparse(input, output)
-    ur
-  }
-
-  /**
-   * Helper function to parse data using the Daffodil SAX API
-   */
-  final protected def parseWithSax(input: InputSourceDataInputStream, contentHandler: ContentHandler): ParseResult = {
-    val xmlReader = dataProcessor.newXMLReaderInstance
-    // SAX_NAMESPACE_PREFIXES_FEATURE is needed to preserve nil attributes with EXI
-    xmlReader.setFeature(XMLUtils.SAX_NAMESPACE_PREFIXES_FEATURE, true)
-    xmlReader.setProperty(XMLUtils.DAFFODIL_SAX_URN_BLOBDIRECTORY, Main.blobDir)
-    xmlReader.setProperty(XMLUtils.DAFFODIL_SAX_URN_BLOBSUFFIX, Main.blobSuffix)
-    xmlReader.setContentHandler(contentHandler)
-    xmlReader.parse(input)
-    val pr = xmlReader.getProperty(XMLUtils.DAFFODIL_SAX_URN_PARSERESULT).asInstanceOf[ParseResult]
-    pr
-  }
-
-  /**
-   * Helper function to unparse data using the Daffodil SAX API
-   */
-  final protected def unparseWithSax(xmlReader: XMLReader, input: InputStream, output: DFDL.Output): UnparseResult = {
-    val contentHandler = dataProcessor.newContentHandlerInstance(output)
-    xmlReader.setContentHandler(contentHandler)
-    xmlReader.setFeature(XMLUtils.SAX_NAMESPACES_FEATURE, true)
-    xmlReader.setFeature(XMLUtils.SAX_NAMESPACE_PREFIXES_FEATURE, true)
-    try {
-      xmlReader.parse(new InputSource(input))
-    } catch {
-      case _: DaffodilUnparseErrorSAXException => // do nothing, unparseResult has error info
-    }
-    val ur = contentHandler.getUnparseResult
-    ur
-  }
-
-}
-
-/**
- * Wrapper around a ParseResult that allows for serializing the ParseResult to
- * an XML string for easy visualization for caller.
- *
- * Some InfosetHandlers do not write to the parse() OutputStream by default
- * because they just create Scala/Java objects or just call interface functions
- * (e.g. SCALA_XML, JDOM). However, for usability, it can be useful to
- * serialize such objects to a String and write to an OutputStream in some
- * circumstances. We do not want to do that in a performance critical loop, so
- * the InfosetHandler parse() function can return a custom InfosetParseResult
- * with a write() implementation to serialize the infoset result to a string
- * and write it to a given OutputStream. Note that this write() function may
- * not ever be called and is guaranteed to occur outside of a performance loop.
- */
-sealed class InfosetParseResult(val parseResult: ParseResult) {
-  def write(os: OutputStream): Unit = {}
-}
-
-
-/**
- * InfosetType.XML
- */
-case class XMLTextInfosetHandler(dataProcessor: DataProcessor)
-  extends InfosetHandler {
-
-  def parse(input: InputSourceDataInputStream, os: OutputStream): InfosetParseResult = {
-    val output = new XMLTextInfosetOutputter(os, pretty = true)
-    val pr = parseWithInfosetOutputter(input, output)
-    new InfosetParseResult(pr)
-  }
-
-  def unparse(data: AnyRef, output: DFDL.Output): UnparseResult = {
-    val is = data match {
-      case bytes: Array[Byte] => new ByteArrayInputStream(bytes)
-      case is: InputStream => is
-    }
-    val input = new XMLTextInfosetInputter(is)
-    val ur = unparseWithInfosetInputter(input, output)
-    ur
-  }
-
-  def dataToInfoset(bytes: Array[Byte]): AnyRef = bytes
-
-  def dataToInfoset(stream: InputStream): AnyRef = stream
-}
-
-/**
- * InfosetType.JSON
- */
-case class JsonInfosetHandler(dataProcessor: DataProcessor)
-  extends InfosetHandler {
-
-  def parse(input: InputSourceDataInputStream, os: OutputStream): InfosetParseResult = {
-    val output = new JsonInfosetOutputter(os, pretty = true)
-    val pr = parseWithInfosetOutputter(input, output)
-    new InfosetParseResult(pr)
-  }
-
-  def unparse(data: AnyRef, output: DFDL.Output): UnparseResult = {
-    val is = data match {
-      case bytes: Array[Byte] => new ByteArrayInputStream(bytes)
-      case is: InputStream => is
-    }
-    val input = new JsonInfosetInputter(is)
-    val ur = unparseWithInfosetInputter(input, output)
-    ur
-  }
-
-  def dataToInfoset(bytes: Array[Byte]): AnyRef = bytes
-
-  def dataToInfoset(stream: InputStream): AnyRef = stream
-}
-
-/**
- * InfosetType.JDOM
- */
-case class JDOMInfosetHandler(dataProcessor: DataProcessor)
-  extends InfosetHandler {
-
-  def parse(input: InputSourceDataInputStream, os: OutputStream): InfosetParseResult = {
-    val output = new JDOMInfosetOutputter()
-    val pr = parseWithInfosetOutputter(input, output)
-    new JDOMInfosetParseResult(pr, output)
-  }
-
-  def unparse(data: AnyRef, output: DFDL.Output): UnparseResult = {
-    val doc = data.asInstanceOf[org.jdom2.Document]
-    val input = new JDOMInfosetInputter(doc)
-    val ur = unparseWithInfosetInputter(input, output)
-    ur
-  }
-
-  def dataToInfoset(bytes: Array[Byte]): AnyRef = dataToInfoset(new ByteArrayInputStream(bytes))
-
-  def dataToInfoset(stream: InputStream): AnyRef = {
-    val builder = new org.jdom2.input.SAXBuilder() {
-      override protected def createParser(): XMLReader = {
-        val rdr = super.createParser()
-        XMLUtils.setSecureDefaults(rdr)
-        rdr
-      }
-    }
-    val doc = builder.build(stream)
-    doc
-  }
-}
-
-class JDOMInfosetParseResult(parseResult: ParseResult, output: JDOMInfosetOutputter)
-  extends InfosetParseResult(parseResult) {
-
-  override def write(os: OutputStream): Unit = {
-    new org.jdom2.output.XMLOutputter().output(output.getResult, os)
-  }
-}
-
-/**
- * InfosetType.SCALA_XML
- */
-case class ScalaXMLInfosetHandler(dataProcessor: DataProcessor)
-  extends InfosetHandler {
-
-  def parse(input: InputSourceDataInputStream, os: OutputStream): InfosetParseResult = {
-    val output = new ScalaXMLInfosetOutputter()
-    val pr = parseWithInfosetOutputter(input, output)
-    new ScalaXMLInfosetParseResult(pr, output)
-  }
-
-  def unparse(data: AnyRef, output: DFDL.Output): UnparseResult = {
-    val node = data.asInstanceOf[scala.xml.Node]
-    val input = new ScalaXMLInfosetInputter(node)
-    val ur = unparseWithInfosetInputter(input, output)
-    ur
-  }
-
-  def dataToInfoset(bytes: Array[Byte]): AnyRef = dataToInfoset(new ByteArrayInputStream(bytes))
-
-  def dataToInfoset(stream: InputStream): AnyRef = {
-    val parser: SAXParser = {
-      val f = DaffodilSAXParserFactory()
-      f.setNamespaceAware(false)
-      val p = f.newSAXParser()
-      p
-    }
-    val node = scala.xml.XML.withSAXParser(parser).load(stream)
-    node
-  }
-}
-
-class ScalaXMLInfosetParseResult(parseResult: ParseResult, output: ScalaXMLInfosetOutputter)
-  extends InfosetParseResult(parseResult) {
-
-  override def write(os: OutputStream): Unit = {
-    val writer = new java.io.OutputStreamWriter(os, StandardCharsets.UTF_8)
-    scala.xml.XML.write(writer, output.getResult, "UTF-8", true, null)
-    writer.flush()
-  }
-}
-
-/**
- * InfosetType.W3CDOM
- */
-case class W3CDOMInfosetHandler(dataProcessor: DataProcessor)
-  extends InfosetHandler {
-
-  def parse(input: InputSourceDataInputStream, os: OutputStream): InfosetParseResult = {
-    val output = new W3CDOMInfosetOutputter()
-    val pr = parseWithInfosetOutputter(input, output)
-    new W3CDOMInfosetParseResult(pr, output)
-  }
-
-  def unparse(data: AnyRef, output: DFDL.Output): UnparseResult = {
-    val doc = data.asInstanceOf[ThreadLocal[org.w3c.dom.Document]].get
-    val input = new W3CDOMInfosetInputter(doc)
-    val ur = unparseWithInfosetInputter(input, output)
-    ur
-  }
-
-  def dataToInfoset(bytes: Array[Byte]): AnyRef = {
-    // W3C Documents are not thread safe. So create a ThreadLocal so each
-    // thread gets its own DOM tree. This has the unfortunate downside that we
-    // don't actually convert the XML bytes to DOM until the first call to
-    // unparse(), and we'll parse it multiple times if there are multiple
-    // threads.
-    val doc = new ThreadLocal[org.w3c.dom.Document] {
-      override def initialValue = {
-        val dbf = DocumentBuilderFactory.newInstance()
-        dbf.setNamespaceAware(true)
-        dbf.setFeature(XMLUtils.XML_DISALLOW_DOCTYPE_FEATURE, true)
-        val db = dbf.newDocumentBuilder()
-        db.parse(new ByteArrayInputStream(bytes))
-      }
-    }
-    doc
-  }
-
-  def dataToInfoset(stream: InputStream): AnyRef = dataToInfoset(IOUtils.toByteArray(stream))
-}
-
-class W3CDOMInfosetParseResult(parseResult: ParseResult, output: W3CDOMInfosetOutputter)
-  extends InfosetParseResult(parseResult) {
-
-  override def write(os: OutputStream): Unit = {
-    val tf = TransformerFactory.newInstance()
-    val transformer = tf.newTransformer()
-    val result = new StreamResult(os)
-    val source = new DOMSource(output.getResult)
-    transformer.transform(source, result)
-  }
-}
-
-/**
- * InfosetType.NULL
- */
-case class NULLInfosetHandler(dataProcessor: DataProcessor)
-  extends InfosetHandler {
-
-  def parse(input: InputSourceDataInputStream, os: OutputStream): InfosetParseResult = {
-    val output = new NullInfosetOutputter()
-    val pr = parseWithInfosetOutputter(input, output)
-    new InfosetParseResult(pr)
-  }
-
-  def unparse(data: AnyRef, output: DFDL.Output): UnparseResult = {
-    val events = data.asInstanceOf[Array[NullInfosetInputter.Event]]
-    val input = new NullInfosetInputter(events)
-    val ur = unparseWithInfosetInputter(input, output)
-    ur
-  }
-
-  def dataToInfoset(bytes: Array[Byte]): AnyRef = dataToInfoset(new ByteArrayInputStream(bytes))
-
-  def dataToInfoset(stream: InputStream): AnyRef = {
-    val events = NullInfosetInputter.toEvents(stream)
-    events
-  }
-}
-
-/**
- * InfosetType.SAX
- */
-case class SAXInfosetHandler(dataProcessor: DataProcessor, forPerformance: Boolean)
-  extends InfosetHandler {
-
-  def parse(input: InputSourceDataInputStream, os: OutputStream): InfosetParseResult = {
-    val contentHandler =
-      if (forPerformance) {
-        new DefaultHandler() // ignores all SAX events
-      } else {
-        new DaffodilParseOutputStreamContentHandler(os, pretty=true)
-      }
-
-    val pr = parseWithSax(input, contentHandler)
-    new InfosetParseResult(pr)
-  }
-
-  def unparse(data: AnyRef, output: DFDL.Output): UnparseResult = {
-    val ur =
-      if (forPerformance) {
-        val xmlReader = new ReplayingXmlReader(data.asInstanceOf[Array[SaxEvent]])
-        unparseWithSax(xmlReader, null, output)
-      } else {
-        val input = data match {
-          case bytes: Array[Byte] => new ByteArrayInputStream(bytes)
-          case is: InputStream => is
-        }
-        val xmlReader = DaffodilSAXParserFactory().newSAXParser.getXMLReader
-        unparseWithSax(xmlReader, input, output)
-      }
-    ur
-  }
-
-  def dataToInfoset(bytes: Array[Byte]): AnyRef = {
-    if (forPerformance) {
-      dataToInfoset(new ByteArrayInputStream(bytes))
-    } else {
-      bytes
-    }
-  }
-
-  def dataToInfoset(stream: InputStream): AnyRef = {
-    if (forPerformance) {
-      val contentHandler = new SavingContentHandler
-      val xmlReader = DaffodilSAXParserFactory().newSAXParser.getXMLReader
-      xmlReader.setContentHandler(contentHandler)
-      xmlReader.setFeature(XMLUtils.SAX_NAMESPACES_FEATURE, true)
-      xmlReader.setFeature(XMLUtils.SAX_NAMESPACE_PREFIXES_FEATURE, true)
-      xmlReader.parse(new InputSource(stream))
-      contentHandler.events.toArray
-    } else {
-      stream
-    }
-  }
-
-  class ReplayingXmlReader(events: Array[SaxEvent]) extends XMLReader {
-    private var _contentHandler: ContentHandler = _
-
-    override def setContentHandler(contentHandler: ContentHandler): Unit = _contentHandler = contentHandler
-
-    override def parse(source: InputSource): Unit = {
-      for (event <- events) event.replay(_contentHandler)
-    }
-
-    // these functions will never be called by the Daffodil XMLReader used for
-    // unparsing with the SAX API, or if they are used it doesn't change how we
-    // replay the events. Just leave them unimplemented or no-ops
-    //$COVERAGE-OFF$
-    override def getContentHandler(): org.xml.sax.ContentHandler = ???
-    override def getDTDHandler(): org.xml.sax.DTDHandler = ???
-    override def getEntityResolver(): org.xml.sax.EntityResolver = ???
-    override def getErrorHandler(): org.xml.sax.ErrorHandler = ???
-    override def getFeature(name: String): Boolean = ???
-    override def getProperty(name: String): Object = ???
-    override def parse(systemId: String): Unit = ???
-    override def setDTDHandler(handler: org.xml.sax.DTDHandler): Unit = {}
-    override def setEntityResolver(resolver: org.xml.sax.EntityResolver): Unit = {}
-    override def setErrorHandler(handler: org.xml.sax.ErrorHandler): Unit = {}
-    override def setFeature(name: String, value: Boolean): Unit = {}
-    override def setProperty(name: String, value: Any): Unit = {}
-    //$COVERAGE-ON$
-  }
-
-  class SavingContentHandler extends ContentHandler {
-    val events = new ArrayBuffer[SaxEvent]()
-
-    override def characters(ch: Array[Char], start: Int, length: Int): Unit =
-      events += SaxEventCharacters(ch.clone(), start, length)
-
-    override def endDocument(): Unit =
-      events += SaxEventEndDocument()
-
-    override def endElement(uri: String, localName: String, qName: String): Unit =
-      events += SaxEventEndElement(uri, localName, qName)
-
-    override def endPrefixMapping(prefix: String): Unit =
-      events += SaxEventEndPrefixMapping(prefix)
-
-    override def ignorableWhitespace(ch: Array[Char], start: Int, length: Int): Unit =
-      events += SaxEventIgnorableWhitespace(ch.clone(), start, length)
-
-    override def processingInstruction(target: String, data: String): Unit =
-      events += SaxEventProcessingInstruction(target, data)
-
-    override def skippedEntity(name: String): Unit =
-      events += SaxEventSkippedEntity(name)
-
-    override def startDocument(): Unit =
-      events += SaxEventStartDocument()
-
-    override def startElement(uri: String, localName: String, qName: String, atts: Attributes): Unit =
-      events += SaxEventStartElement(uri, localName, qName, new AttributesImpl(atts))
-
-    override def startPrefixMapping(prefix: String, uri: String): Unit =
-      events += SaxEventStartPrefixMapping(prefix, uri)
-
-    override def setDocumentLocator(locator: Locator): Unit = {}
-  }
-
-  trait SaxEvent {
-    def replay(h: ContentHandler): Unit
-  }
-
-  case class SaxEventCharacters(ch: Array[Char], start: Int, length: Int) extends SaxEvent {
-    def replay(h: ContentHandler): Unit = h.characters(ch, start, length)
-  }
-
-  case class SaxEventEndDocument() extends SaxEvent {
-    def replay(h: ContentHandler): Unit = h.endDocument()
-  }
-
-  case class SaxEventEndElement(uri: String, localName: String, qName: String) extends SaxEvent {
-    def replay(h: ContentHandler): Unit = h.endElement(uri, localName, qName)
-  }
-
-  case class SaxEventEndPrefixMapping(prefix: String) extends SaxEvent {
-    def replay(h: ContentHandler): Unit = h.endPrefixMapping(prefix)
-  }
-
-  case class SaxEventIgnorableWhitespace(ch: Array[Char], start: Int, length: Int) extends SaxEvent {
-    def replay(h: ContentHandler): Unit = h.ignorableWhitespace(ch, start, length)
-  }
-
-  case class SaxEventProcessingInstruction(target: String, data: String) extends SaxEvent {
-    def replay(h: ContentHandler): Unit = h.processingInstruction(target, data)
-  }
-
-  case class SaxEventSkippedEntity(name: String) extends SaxEvent {
-    def replay(h: ContentHandler): Unit = h.skippedEntity(name)
-  }
-
-  case class SaxEventStartDocument() extends SaxEvent {
-    def replay(h: ContentHandler): Unit = h.startDocument()
-  }
-
-  case class SaxEventStartElement(uri: String, localName: String, qName: String, atts: Attributes) extends SaxEvent {
-    def replay(h: ContentHandler): Unit = h.startElement(uri, localName, qName, atts)
-  }
-
-  case class SaxEventStartPrefixMapping(prefix: String, uri: String) extends SaxEvent {
-    def replay(h: ContentHandler): Unit = h.startPrefixMapping(prefix, uri)
-  }
-}
-
-
-/**
- * InfosetType.EXI and InfosetType.EXISA
- */
-object EXIInfosetHandler {
-
-  /** non-schema aware EXI **/
-  def apply(dataProcessor: DataProcessor): InfosetHandler = {
-    val exiFactory = createEXIFactory(None)
-    EXIInfosetHandler(dataProcessor, exiFactory)
-  }
-
-  /** schema aware EXI **/
-  def apply(dataProcessor: DataProcessor, schemaUri: URI): InfosetHandler = {
-    val exiFactory = createEXIFactory(Some(schemaUri))
-    EXIInfosetHandler(dataProcessor, exiFactory)
-  }
-
-  def createEXIFactory(optSchema: Option[URI]): EXIFactory = {
-    val exiFactory = DefaultEXIFactory.newInstance
-    if (optSchema.isDefined) {
-      val grammarFactory = GrammarFactory.newInstance
-      val grammar = grammarFactory.createGrammars(optSchema.get.toString, DFDLCatalogResolver.get)
-      exiFactory.setGrammars(grammar)
-    }
-    exiFactory
-  }
-}
-
-case class EXIInfosetHandler(dataProcessor: DataProcessor, exiFactory: EXIFactory)
-  extends InfosetHandler {
-
-  def parse(input: InputSourceDataInputStream, os: OutputStream): InfosetParseResult = {
-    val exiResult = new EXIResult(exiFactory)
-    exiResult.setOutputStream(os)
-    val contentHandler = exiResult.getHandler
-
-    val pr = parseWithSax(input, contentHandler)
-    new InfosetParseResult(pr)
-  }
-
-  def unparse(data: AnyRef, output: DFDL.Output): UnparseResult = {
-    val input = data match {
-      case bytes: Array[Byte] => new ByteArrayInputStream(bytes)
-      case is: InputStream => is
-    }
-    val exiSource = new EXISource(exiFactory)
-    val xmlReader = exiSource.getXMLReader
-    val ur = unparseWithSax(xmlReader, input, output)
-    ur
-  }
-
-  def dataToInfoset(bytes: Array[Byte]): AnyRef = bytes
-
-  def dataToInfoset(stream: InputStream): AnyRef = stream
-}
diff --git a/daffodil-cli/src/main/scala/org/apache/daffodil/Main.scala b/daffodil-cli/src/main/scala/org/apache/daffodil/Main.scala
deleted file mode 100644
index 3e63d590d..000000000
--- a/daffodil-cli/src/main/scala/org/apache/daffodil/Main.scala
+++ /dev/null
@@ -1,1528 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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.
- */
-
-package org.apache.daffodil
-
-import java.io.File
-import java.io.FileInputStream
-import java.io.FileOutputStream
-import java.io.InputStream
-import java.io.PrintStream
-import java.net.URI
-import java.nio.ByteBuffer
-import java.nio.channels.Channels
-import java.nio.file.Paths
-import java.util.Scanner
-import java.util.concurrent.Executors
-import javax.xml.transform.TransformerFactory
-import javax.xml.transform.TransformerException
-import javax.xml.transform.stream.StreamResult
-
-import scala.concurrent.Await
-import scala.concurrent.ExecutionContext
-import scala.concurrent.Future
-import scala.concurrent.duration.Duration
-import scala.util.matching.Regex
-
-import com.typesafe.config.ConfigFactory
-
-import org.apache.commons.io.output.NullOutputStream
-
-import org.apache.logging.log4j.Level
-import org.apache.logging.log4j.core.config.Configurator
-
-import org.rogach.scallop
-import org.rogach.scallop.ArgType
-import org.rogach.scallop.ScallopOption
-import org.rogach.scallop.ValueConverter
-import org.rogach.scallop.exceptions.GenericScallopException
-
-import org.xml.sax.InputSource
-import org.xml.sax.SAXParseException
-import org.xml.sax.helpers.XMLReaderFactory
-
-import com.siemens.ct.exi.core.EXIFactory
-import com.siemens.ct.exi.core.exceptions.EXIException
-import com.siemens.ct.exi.main.api.sax.EXIResult
-import com.siemens.ct.exi.main.api.sax.EXISource
-
-import org.apache.daffodil.api.DFDL
-import org.apache.daffodil.api.DaffodilConfig
-import org.apache.daffodil.api.DaffodilConfigException
-import org.apache.daffodil.api.DaffodilTunables
-import org.apache.daffodil.api.TDMLImplementation
-import org.apache.daffodil.api.URISchemaSource
-import org.apache.daffodil.api.ValidationMode
-import org.apache.daffodil.api.WithDiagnostics
-import org.apache.daffodil.compiler.Compiler
-import org.apache.daffodil.compiler.InvalidParserException
-import org.apache.daffodil.debugger.CLIDebuggerRunner
-import org.apache.daffodil.debugger.DebuggerExitException
-import org.apache.daffodil.debugger.InteractiveDebugger
-import org.apache.daffodil.debugger.TraceDebuggerRunner
-import org.apache.daffodil.dsom.ExpressionCompilers
-import org.apache.daffodil.exceptions.Assert
-import org.apache.daffodil.exceptions.NotYetImplementedException
-import org.apache.daffodil.exceptions.UnsuppressableException
-import org.apache.daffodil.externalvars.Binding
-import org.apache.daffodil.externalvars.BindingException
-import org.apache.daffodil.externalvars.ExternalVariablesLoader
-import org.apache.daffodil.io.DataDumper
-import org.apache.daffodil.io.FormatInfo
-import org.apache.daffodil.io.InputSourceDataInputStream
-import org.apache.daffodil.processors.DataLoc
-import org.apache.daffodil.processors.ExternalVariableException
-import org.apache.daffodil.schema.annotation.props.gen.BitOrder
-import org.apache.daffodil.tdml.Runner
-import org.apache.daffodil.tdml.TDMLException
-import org.apache.daffodil.tdml.TDMLTestNotCompatibleException
-import org.apache.daffodil.udf.UserDefinedFunctionFatalErrorException
-import org.apache.daffodil.util.Logger
-import org.apache.daffodil.util.Misc
-import org.apache.daffodil.util.Timer
-import org.apache.daffodil.validation.Validators
-import org.apache.daffodil.xml.QName
-import org.apache.daffodil.xml.RefQName
-
-
-class CLIConf(arguments: Array[String]) extends scallop.ScallopConf(arguments) {
-
-  /**
-   * This is used when the flag is optional and so is its
-   * argument.
-   *
-   * Let's use --debug [file] as an example.
-   *
-   * This optionalValueConverter first determines if the
-   * --debug flag was given.  If it wasn't, Right(None)
-   * is returned.
-   *
-   * If the flag was given we then need to determine
-   * if 'file' was also given.  If file was not given, Right(Some(None))
-   * is returned meaning that the --debug flag was there but 'file'
-   * was not.  If the file was given, Right(Some(Some(conv(file)))) is
-   * returned.  This means that the --debug flag was there as well as
-   * a filename for 'file'.
-   *
-   * conv(mode) simply performs the necessary conversion
-   * of the string to the type [A].
-   *
-   */
-  def optionalValueConverter[A](conv: String => A): scallop.ValueConverter[Option[A]] =
-    new scallop.ValueConverter[Option[A]] {
-
-      // From the Scallop wiki:
-      //
-      // parse is a method, that takes a list of arguments to all option invocations:
-      // for example, "-a 1 2 -a 3 4 5" would produce List(List(1,2),List(3,4,5)).
-      // parse returns Left with error message, if there was an error while parsing
-      // if no option was found, it returns Right(None)
-      // and if option was found, it returns Right(...)
-      def parse(s: List[(String, List[String])]): Either[String, Option[Option[A]]] = {
-        s match {
-          case Nil => Right(None) // flag was not present
-          case (_, Nil) :: Nil => Right(Some(None)) // flag was present but had no parameter
-          case (_, v :: Nil) :: Nil => { // flag was present with a parameter, convert the parameter
-            try {
-              Right(Some(Some(conv(v))))
-            } catch {
-              case s: scala.util.control.ControlThrowable => throw s
-              case u: UnsuppressableException => throw u
-              case e: Exception => {
-                Left(e.getMessage())
-              }
-            }
-          }
-          case _ => Left("you should provide no more than one argument for this option") // Error because we expect there to be at most one flag
-        }
-      }
-      val argType = scallop.ArgType.LIST
-      override def argFormat(name: String): String = "[" + name + "]"
-    }
-
-  implicit def validateConverter = singleArgConverter[ValidationMode.Type]((s: String) => {
-    import ValidatorPatterns._
-    s match {
-      case "on" => ValidationMode.Full
-      case "limited" => ValidationMode.Limited
-      case "off" => ValidationMode.Off
-      case DefaultArgPattern(name, arg) if Validators.isRegistered(name) =>
-        val config = if(arg.endsWith(".conf")) ConfigFactory.parseFile(new File(arg)) else ConfigFactory.parseString(s"$name=$arg")
-        ValidationMode.Custom(Validators.get(name).make(config))
-      case NoArgsPattern(name) if Validators.isRegistered(name) =>
-        ValidationMode.Custom(Validators.get(name).make(ConfigFactory.empty))
-      case _ => throw new Exception("Unrecognized ValidationMode %s.  Must be 'on', 'limited', 'off', or name of spi validator.".format(s))
-    }
-  })
-
-  implicit def infosetTypeConverter = singleArgConverter[InfosetType.Type]((s: String) => {
-    try {
-      InfosetType.withName(s.toLowerCase)
-    } catch {
-      case _: NoSuchElementException => throw new Exception("Unrecognized infoset type: %s.  Must be one of %s".format(s, InfosetType.values.mkString(", ")))
-    }
-  })
-
-  implicit def implementationConverter = singleArgConverter[TDMLImplementation]((s: String) => {
-    val optImplementation = TDMLImplementation.optionStringToEnum("implementation", s)
-    if (!optImplementation.isDefined) {
-      throw new Exception("Unrecognized TDML implementation '%s'.  Must be one of %s"
-        .format(s, TDMLImplementation.values.mkString(", ")))
-    }
-    optImplementation.get
-  })
-
-  def qnameConvert(s: String): RefQName = {
-    val eQN = QName.refQNameFromExtendedSyntax(s)
-    eQN.get
-  }
-
-  def singleArgConverter[A](conv: String => A) = new ValueConverter[A] {
-    def parse(s: List[(String, List[String])]) = {
-      s match {
-        case (_, i :: Nil) :: Nil =>
-          try {
-            Right(Some(conv(i)))
-          } catch {
-            case s: scala.util.control.ControlThrowable => throw s
-            case u: UnsuppressableException => throw u
-            case e: Throwable => Left(e.getMessage())
-          }
-        case Nil => Right(None)
-        case _ => Left("you should provide exactly one argument for this option")
-      }
-    }
-    val argType = ArgType.SINGLE
-  }
-
-  implicit def rootNSConverter = org.rogach.scallop.singleArgConverter[RefQName](qnameConvert _)
-
-  implicit def fileResourceURIConverter = singleArgConverter[URI]((s: String) => {
-    val file = new File(s)
-    val uri =
-      if (file.isFile()) {
-        Some(file.toURI)
-      } else {
-        Misc.getResourceRelativeOption(s, None)
-      }
-    uri.getOrElse(throw new Exception("Could not find file or resource %s" format s))
-  })
-
-  printedName = "Apache Daffodil"
-
-  val width = 87
-  helpWidth(width)
-
-  errorMessageHandler = { message =>
-    val msg =
-      if (message.indexOf("Wrong format for option 'schema'") >= 0) {
-        // the 'wrong format' error only occurs on --schema when options are
-        // provided after the trailing arg, so let's give a more helpful error
-        // message
-        "Options are not allowed after a trailing argument"
-      } else {
-        message
-      }
-    throw new GenericScallopException(msg)
-  }
-
-  banner("""|Usage: daffodil [GLOBAL_OPTS] <subcommand> [SUBCOMMAND_OPTS]
-            |
-            |Global Options:""".stripMargin)
-
-  footer("""|
-            |Run 'daffodil <subcommand> --help' for subcommand specific options""".stripMargin)
-
-  version({
-    val version = Misc.getDaffodilVersion
-    val strVers = "%s %s".format(printedName, version)
-    strVers
-  })
-
-  shortSubcommandsHelp()
-
-  // Global Options
-  val debug = opt[Option[String]](argName = "file", descr = "Enable the interactive debugger. Optionally, read initial debugger commands from [file] if provided.")(optionalValueConverter[String](a => a))
-  val trace = opt[Boolean](descr = "Run this program with verbose trace output")
-  val verbose = tally(descr = "Increment verbosity level, one level for each -v")
-  val version = opt[Boolean](descr = "Show Daffodil's version")
-
-  // Parse Subcommand Options
-  object parse extends scallop.Subcommand("parse") {
-    banner("""|Usage: daffodil parse (-s <schema> [-r <root>] | -P <parser>)
-              |                      [-c <file>] [-D<variable>=<value>...] [-I <infoset_type>]
-              |                      [-o <output>] [--stream] [-T<tunable>=<value>...] [-V <mode>]
-              |                      [infile]
-              |
-              |Parse a file, using either a DFDL schema or a saved parser
-              |
-              |Parse Options:""".stripMargin)
-
-    descr("Parse data to a DFDL infoset")
-    helpWidth(width)
-
-    val config = opt[File](short = 'c', argName = "file", descr = "XML file containing configuration items")
-    val vars = props[String](name = 'D', keyName = "variable", valueName = "value", descr = "Variables to be used when parsing. Can be prefixed with {namespace}.")
-    val infosetType = opt[InfosetType.Type](short = 'I', argName = "infoset_type", descr = "Infoset type to output. Type can be: " + InfosetType.values.mkString(", ") + ". Defaults to 'xml'.", default = Some(InfosetType.XML))
-    val output = opt[String](argName = "file", descr = "Output file to write infoset to. If not given or is -, infoset is written to stdout.")
-    val parser = opt[File](short = 'P', argName = "file", descr = "Previously saved parser to reuse")
-    val path = opt[String](argName = "path", descr = "Path from root element to node from which to start parsing", hidden = true)
-    val rootNS = opt[RefQName]("root", argName = "node", descr = "Root element to use. Can be prefixed with {namespace}. Must be a top-level element. Defaults to first top-level element of DFDL schema.")
-    val schema = opt[URI]("schema", argName = "file", descr = "DFDL schema to use to create parser")(fileResourceURIConverter)
-    val stream = toggle(noshort = true, default = Some(false), descrYes = "When left over data exists, parse again with remaining data, separating infosets by a NUL character", descrNo = "Stop after the first parse, throwing an error if left over data exists")
-    val tunables = props[String](name = 'T', keyName = "tunable", valueName = "value", descr = "Tunable configuration options to change Daffodil's behavior")
-    val validate: ScallopOption[ValidationMode.Type] = opt[ValidationMode.Type](short = 'V', default = Some(ValidationMode.Off), argName = "mode", descr = "Validation mode. Use 'on', 'limited', 'off', or a validator plugin name.")
-
-    val infile = trailArg[String](required = false, descr = "Input file to parse. If not specified, or a value of -, reads from stdin.")
-
-    requireOne(schema, parser) // must have one of --schema or --parser
-    conflicts(parser, List(rootNS)) // if --parser is provided, cannot also provide --root
-    validateFileIsFile(config) // --config must be a file that exists
-
-    validateOpt(debug, infile) {
-      case (Some(_), Some("-")) | (Some(_), None) => Left("Input must not be stdin during interactive debugging")
-      case _ => Right(Unit)
-    }
-
-    validateOpt(parser, validate) {
-      case (Some(_), Some(ValidationMode.Full)) => Left("The validation mode must be 'limited' or 'off' when using a saved parser.")
-      case _ => Right(Unit)
-    }
-
-    validateOpt(infosetType, stream, schema) {
-      case (Some(InfosetType.EXI), Some(true), _) => Left("Streaming mode is not currently supported with EXI infosets.")
-      case (Some(InfosetType.EXISA), Some(true), _) => Left("Streaming mode is not currently supported with EXI infosets.")
-      case (Some(InfosetType.EXISA), _, None) => Left("A schema must be specified to use schema-aware compression with EXI")
-      case _ => Right(Unit)
-    }
-  }
-
-  // Unparse Subcommand Options
-  object unparse extends scallop.Subcommand("unparse") {
-    banner("""|Usage: daffodil unparse (-s <schema> [-r <root>] | -P <parser>)
-              |                        [-c <file>] [-D<variable>=<value>...] [-I <infoset_type>]
-              |                        [-o <output>] [--stream] [-T<tunable>=<value>...] [-V <mode>]
-              |                        [infile]
-              |
-              |Unparse an infoset file, using either a DFDL schema or a saved parser
-              |
-              |Unparse Options:""".stripMargin)
-
-    descr("Unparse a DFDL infoset")
-    helpWidth(width)
-
-    val config = opt[File](short = 'c', argName = "file", descr = "XML file containing configuration items")
-    val vars = props[String](name = 'D', keyName = "variable", valueName = "value", descr = "Variables to be used when parsing. Can be prefixed with {namespace}.")
-    val infosetType = opt[InfosetType.Type](short = 'I', argName = "infoset_type", descr = "Infoset type to output. Type can be: " + InfosetType.values.mkString(", ") + ". Defaults to 'xml'.", default = Some(InfosetType.XML))
-    val output = opt[String](argName = "file", descr = "Output file to write data to. If not given or is -, data is written to stdout.")
-    val parser = opt[File](short = 'P', argName = "file", descr = "Previously saved parser to reuse")
-    val path = opt[String](argName = "path", descr = "Path from root element to node from which to start unparsing", hidden = true)
-    val rootNS = opt[RefQName]("root", argName = "node", descr = "Root element to use. Can be prefixed with {namespace}. Must be a top-level element. Defaults to first top-level element of DFDL schema.")
-    val schema = opt[URI]("schema", argName = "file", descr = "DFDL schema to use to create parser")(fileResourceURIConverter)
-    val stream = toggle(noshort = true, default = Some(false), descrYes = "Split the input data on the NUL character, and unparse each chuck separately", descrNo = "Treat the entire input data as one infoset")
-    val tunables = props[String](name = 'T', keyName = "tunable", valueName = "value", descr = "Tunable configuration options to change Daffodil's behavior")
-    val validate: ScallopOption[ValidationMode.Type] = opt[ValidationMode.Type](short = 'V', default = Some(ValidationMode.Off), argName = "mode", descr = "Validation mode. Use 'on', 'limited', 'off', or a validator plugin name.")
-
-    val infile = trailArg[String](required = false, descr = "Input file to unparse. If not specified, or a value of -, reads from stdin.")
-
-    requireOne(schema, parser) // must have one of --schema or --parser
-    conflicts(parser, List(rootNS)) // if --parser is provided, cannot also provide --root
-    validateFileIsFile(config) // --config must be a file that exists
-
-    validateOpt(debug, infile) {
-      case (Some(_), Some("-")) | (Some(_), None) => Left("Input must not be stdin during interactive debugging")
-      case _ => Right(Unit)
-    }
-
-    validateOpt(infosetType, stream, schema) {
-      case (Some(InfosetType.EXI), Some(true), _) => Left("Streaming mode is not currently supported with EXI infosets.")
-      case (Some(InfosetType.EXISA), Some(true), _) => Left("Streaming mode is not currently supported with EXI infosets.")
-      case (Some(InfosetType.EXISA), _, None) => Left("A schema must be specified to use schema-aware compression with EXI")
-      case _ => Right(Unit)
-    }
-  }
-
-  // Save Parser Subcommand Options
-  object save extends scallop.Subcommand("save-parser") {
-    banner("""|Usage: daffodil save-parser -s <schema> [-r <root>]
-              |                            [-c <file>] [-D<variable>=<value>...] [-T<tunable>=<value>...]
-              |                            [outfile]
-              |
-              |Create and save a parser using a DFDL schema
-              |
-              |Save Parser Options:""".stripMargin)
-
-    descr("Save a Daffodil parser for reuse")
-    helpWidth(width)
-
-    val config = opt[File](short = 'c', argName = "file", descr = "XML file containing configuration items")
-    val vars = props[String](name = 'D', keyName = "variable", valueName = "value", descr = "Variables to be used when parsing. Can be prefixed with {namespace}.")
-    val path = opt[String](argName = "path", descr = "Path from root element to node from which to start parsing", hidden = true)
-    val rootNS = opt[RefQName]("root", argName = "node", descr = "Root element to use. Can be prefixed with {namespace}. Must be a top-level element. Defaults to first top-level element of DFDL schema.")
-    val schema = opt[URI]("schema", required = true, argName = "file", descr = "DFDL schema to use to create parser")(fileResourceURIConverter)
-    val tunables = props[String](name = 'T', keyName = "tunable", valueName = "value", descr = "Tunable configuration options to change Daffodil's behavior")
-
-    val outfile = trailArg[String](required = false, descr = "Output file to save parser to. If not specified, or a value of -, saves to stdout.")
-
-    requireOne(schema) // --schema must be provided
-    validateFileIsFile(config) // --config must be a file that exists
-  }
-
-  // Test Subcommand Options
-  object test extends scallop.Subcommand("test") {
-    banner("""|Usage: daffodil test [-I <implementation>] [-l] [-r] [-i] <tdmlfile> [testnames...]
-              |
-              |List or execute tests in a TDML file
-              |
-              |Test Options:""".stripMargin)
-
-    descr("List or execute TDML tests")
-    helpWidth(width)
-
-    val implementation = opt[TDMLImplementation](short = 'I', argName = "implementation",
-      descr = "Implementation to run TDML tests. Choose one of %s. Defaults to %s."
-        .format(TDMLImplementation.values.mkString(", "), TDMLImplementation.Daffodil.toString),
-      default = None)
-    val info = tally(descr = "Increment test result information output level, one level for each -i")
-    val list = opt[Boolean](descr = "Show names and descriptions instead of running test cases")
-    val regex = opt[Boolean](descr = "Treat <testnames...> as regular expressions")
-    val tdmlfile = trailArg[String](required = true, descr = "Test Data Markup Language (TDML) file")
-    val testnames = trailArg[List[String]](required = false, descr = "Name(s) of test cases in tdmlfile. If not given, all tests in tdmlfile are run.")
-  }
-
-  // Performance Subcommand Options
-  object performance extends scallop.Subcommand("performance") {
-    banner("""|Usage: daffodil performance (-s <schema> [-r <root>] | -P <parser>)
-              |                            [-c <file>] [-D<variable>=<value>...] [-I <infoset_type>]
-              |                            [-N <number>] [-t <threads>] [-T<tunable>=<value>...]
-              |                            [-u] [-V <mode>]
-              |                            <infile>
-              |
-              |Run a performance test, using either a DFDL schema or a saved parser
-              |
-              |Performance Options:""".stripMargin)
-
-    descr("Run performance test")
-    helpWidth(width)
-
-    val config = opt[File](short = 'c', argName = "file", descr = "XML file containing configuration items")
-    val vars = props[String](name = 'D', keyName = "variable", valueName = "value", descr = "Variables to be used when parsing. Can be prefixed with {namespace}.")
-    val infosetType = opt[InfosetType.Type](short = 'I', argName = "infoset_type", descr = "Infoset type to output. Type can be: " + InfosetType.values.mkString(", ") + ". Defaults to 'xml'.", default = Some(InfosetType.XML))
-    val number = opt[Int](short = 'N', argName = "number", default = Some(1), descr = "Total number of files to process. Defaults to 1.")
-    val parser = opt[File](short = 'P', argName = "file", descr = "Previously saved parser to reuse")
-    val path = opt[String](argName = "path", descr = "Path from root element to node from which to start parsing or unparsing", hidden = true)
-    val rootNS = opt[RefQName]("root", argName = "node", descr = "Root element to use. Can be prefixed with {namespace}. Must be a top-level element. Defaults to first top-level element of DFDL schema.")
-    val schema = opt[URI]("schema", argName = "file", descr = "DFDL schema to use to create parser")(fileResourceURIConverter)
-    val threads = opt[Int](short = 't', argName = "threads", default = Some(1), descr = "Number of threads to use. Defaults to 1.")
-    val tunables = props[String](name = 'T', keyName = "tunable", valueName = "value", descr = "Tunable configuration options to change Daffodil's behavior")
-    val unparse = opt[Boolean](default = Some(false), descr = "Perform unparse instead of parse for performance test")
-    val validate: ScallopOption[ValidationMode.Type] = opt[ValidationMode.Type](short = 'V', default = Some(ValidationMode.Off), argName = "mode", descr = "Validation mode. Use 'on', 'limited', 'off', or a validator plugin name.")
-
-    val infile = trailArg[String](required = true, descr = "Input file or directory containing input files to parse or unparse")
-
-    requireOne(schema, parser) // must have one of --schema or --parser
-    conflicts(parser, List(rootNS)) // if --parser is provided, cannot also provide --root
-    validateFileIsFile(config) // --config must be a file that exists
-
-    validateOpt(infosetType, schema) {
-      case (Some(InfosetType.EXISA), None) => Left("A schema must be specified to use schema-aware compression with EXI")
-      case _ => Right(Unit)
-    }
-  }
-
-  // Generate Subcommand Options
-  object generate extends scallop.Subcommand("generate") {
-    descr("Generate <language> code from a DFDL schema")
-
-    banner("""|Usage: daffodil [GLOBAL_OPTS] generate <language> [SUBCOMMAND_OPTS]
-              |""".stripMargin)
-    shortSubcommandsHelp()
-    footer("""|
-              |Run 'daffodil generate <language> --help' for subcommand specific options""".stripMargin)
-
-    // Takes language by name so we can pass it to scallop.Subcommand and interpolate it into
-    // strings without getting a runtime java.lang.ClassCastException on Scala 2.12 (class
-    // scala.collection.mutable.WrappedArray$ofRef cannot be cast to class java.lang.String)
-    // @param languageArg Typed on command line & passed to Compiler.forLanguage
-    // @param languageName Formal descriptive name of language for help/usage messages
-    class LanguageConf(languageArg: => String, val languageName: String)
-      extends scallop.Subcommand(languageArg) {
-
-      val language = languageArg
-      banner(s"""|Usage: daffodil generate $language -s <schema> [-r <root>]
-                 |                              [-c <file>] [-T<tunable>=<value>...]
-                 |                              [outdir]
-                 |
-                 |Generate $languageName code from a DFDL schema to parse or unparse data
-                 |
-                 |Generate Options:""".stripMargin)
-      descr(s"Generate $languageName code from a DFDL schema")
-      helpWidth(width)
-
-      val config = opt[File](short = 'c', argName = "file", descr = "XML file containing configuration items")
-      val rootNS = opt[RefQName]("root", argName = "node", descr = "Root element to use. Can be prefixed with {namespace}. Must be a top-level element. Defaults to first top-level element of DFDL schema.")
-      val schema = opt[URI]("schema", required = true, argName = "file", descr = "DFDL schema to use to create parser")(fileResourceURIConverter)
-      val tunables = props[String](name = 'T', keyName = "tunable", valueName = "value", descr = "Tunable configuration options to change Daffodil's behavior")
-
-      val outdir = trailArg[String](required = false, descr = s"Output directory in which to create '$language' subdirectory. If not specified, uses current directory.")
-
-      requireOne(schema) // --schema must be provided
-      validateFileIsFile(config) // --config must be a file that exists
-    }
-
-    object c extends LanguageConf("c", "C")
-
-    addSubcommand(c)
-
-    requireSubcommand()
-  }
-
-  // Encode or decode EXI Subcommand Options
-  object exi extends scallop.Subcommand("exi") {
-    banner("""|Usage: daffodil exi [-d] [-s <schema>] [-o <output>] [infile]
-              |
-              |Encode/decode an XML file with EXI. If a schema is specified, it will use schema aware encoding/decoding.
-              |
-              |EncodeEXI Options:""".stripMargin)
-
-    descr("Encode an XML file with EXI")
-    helpWidth(width)
-
-    val output = opt[String](argName = "file", descr = "Output file to write the encoded/decoded file to. If not given or is -, data is written to stdout.")
-    val schema = opt[URI]("schema", argName = "file", descr = "DFDL schema to use for schema aware encoding/decoding.")(fileResourceURIConverter)
-    val decode = opt[Boolean](default = Some(false), descr = "Decode input file from EXI to XML.")
-    val infile = trailArg[String](required = false, descr = "Input XML file to encode. If not specified, or a value of -, reads from stdin.")
-  }
-
-  addSubcommand(parse)
-  addSubcommand(unparse)
-  addSubcommand(save)
-  addSubcommand(test)
-  addSubcommand(performance)
-  addSubcommand(generate)
-  addSubcommand(exi)
-
-  mutuallyExclusive(trace, debug) // cannot provide both --trace and --debug
-  requireSubcommand()
-
-  verify()
-}
-
-object ValidatorPatterns {
-  val NoArgsPattern: Regex = "(.+?)".r.anchored
-  val DefaultArgPattern: Regex = "(.+?)=(.+)".r.anchored
-}
-
-object Main {
-
-  var STDIN: InputStream = System.in
-  var STDOUT: PrintStream = System.out
-  var STDERR: PrintStream = System.err
-
-  /**
-   * Allows changing where the input/output of the CLI goes to. This defaults
-   * to the normal System.in/out/err, but can be changed to support easier
-   * testing. This is not thread safe, but this Main object is not really
-   * thread safe anyways if it is configured to read/write from stdin/stout.
-   */
-  def setInputOutput(in: InputStream, out: PrintStream, err: PrintStream): Unit = {
-    STDIN = in
-    STDOUT = out
-    STDERR = err
-  }
-
-  val traceCommands = Seq(
-    "display info parser",
-    "display info data",
-    "display info infoset",
-    "display info diff",
-    "trace")
-
-  /* indents a multi-line string */
-  def indent(str: String, pad: Int): String = {
-    val lines = str.split("\n")
-    val prefix = " " * pad
-    val indented = lines.map(prefix + _)
-    indented.mkString("\n")
-  }
-
-
-  /**
-   * Overrides bindings specified via the configuration file with those
-   * specified via the -D command.
-   *
-   * @param bindings A sequence of Bindings (external variables)
-   * @param bindingsToOverride The sequence of Bindings that could be overridden.
-   */
-  def overrideBindings(bindings: Seq[Binding], bindingsToOverride: Seq[Binding]) = {
-    val inBoth = bindings.intersect(bindingsToOverride).distinct
-    val bindingsMinusBoth = bindings.diff(inBoth)
-    val bindingsToOverrideMinusBoth = bindingsToOverride.diff(inBoth)
-    val bindingsWithCorrectValues = bindings.filter(b => inBoth.exists(p => b.hashCode == p.hashCode))
-
-    val bindingsMinusUpdates = bindingsMinusBoth.union(bindingsToOverrideMinusBoth)
-    val bindingsWithUpdates = bindingsMinusUpdates.union(bindingsWithCorrectValues)
-
-    bindingsWithUpdates
-  }
-
-  def displayDiagnostics(pr: WithDiagnostics): Unit = {
-    pr.getDiagnostics.foreach { d =>
-      if (d.isError) {
-        Logger.log.error(d.getMessage())
-      } else {
-        Logger.log.warn(d.getMessage())
-      }
-    }
-  }
-
-  /**
-   * Retrieves all external variables specified via the command line interface.
-   *
-   * @param vars The individual variables input via the command line using the -D command.
-   * @param optDafConfig DaffodilConfig object from config file (if any)
-   */
-  def combineExternalVariables(vars: Map[String, String], optDafConfig: Option[DaffodilConfig]): Seq[Binding] = {
-    val configFileVars: Seq[Binding] = optDafConfig.map{ _.externalVariableBindings }.getOrElse(Seq())
-
-    val individualVars = ExternalVariablesLoader.mapToBindings(vars)
-
-    val bindings = overrideBindings(individualVars, configFileVars)
-    bindings
-  }
-
-  def createProcessorFromParser(savedParser: File, path: Option[String], mode: ValidationMode.Type) = {
-    try {
-      val compiler = Compiler()
-      val processor = Timer.getResult("reloading", compiler.reload(savedParser))
-      // note that we do not display the diagnostics that were saved as part of the
-      // saved processor. Those are from compile time, are all warnings, and
-      // are just noise in a production system where we're reloading a parser.
-      if (!processor.isError) {
-        Some(processor.withValidationMode(mode))
-      } else {
-        None
-      }
-    } catch {
-      case e: InvalidParserException => {
-        Logger.log.error(e.getMessage())
-        None
-      }
-    }
-  }
-
-  def withDebugOrTrace(proc: DFDL.DataProcessor, conf: CLIConf): DFDL.DataProcessor = {
-    (conf.trace() || conf.debug.isDefined) match {
-      case true => {
-        val runner =
-          if (conf.trace()) {
-            new TraceDebuggerRunner(STDOUT)
-          } else {
-            if (System.console == null) {
-              Logger.log.warn(s"Using --debug on a non-interactive console may result in display issues")
-            }
-            conf.debug() match {
-              case Some(f) => new CLIDebuggerRunner(new File(f), STDIN, STDOUT)
-              case None => new CLIDebuggerRunner(STDIN, STDOUT)
-            }
-          }
-        val id = new InteractiveDebugger(runner, ExpressionCompilers)
-        proc.withDebugger(id).withDebugging(true)
-      }
-      case false => proc
-    }
-  }
-
-  def createProcessorFromSchema(schema: URI, rootNS: Option[RefQName], path: Option[String],
-    tunablesMap: Map[String, String],
-    mode: ValidationMode.Type): Option[DFDL.DataProcessor] = {
-    val compiler = {
-      val c = Compiler().withTunables(tunablesMap)
-      rootNS match {
-        case None => c
-        case Some(RefQName(_, root, ns)) => c.withDistinguishedRootNode(root, ns.toStringOrNullIfNoNS)
-      }
-    }
-
-    // Wrap timing around the whole of compilation
-    //
-    // compilation extends from the call to compile
-    // to also include the call to pf.onPath. (which is the last phase
-    // of compilation, where it asks for the parser)
-    //
-    val schemaSource = URISchemaSource(schema)
-    val res = Timer.getResult("compiling", {
-      val processorFactory = compiler.compileSource(schemaSource)
-      if (!processorFactory.isError) {
-        val processor = processorFactory.onPath(path.getOrElse("/")).withValidationMode(mode)
-        displayDiagnostics(processor)
-        if (processor.isError) {
-          None
-        } else {
-          Some(processor)
-        }
-      } else {
-        displayDiagnostics(processorFactory)
-        None
-      }
-    })
-    res
-  }
-
-  def createGeneratorFromSchema(schema: URI, rootNS: Option[RefQName], tunables: Map[String, String],
-                                language: String): Option[DFDL.CodeGenerator] = {
-    val compiler = {
-      val c = Compiler().withTunables(tunables)
-      rootNS match {
-        case None => c
-        case Some(RefQName(_, root, ns)) => c.withDistinguishedRootNode(root, ns.toStringOrNullIfNoNS)
-      }
-    }
-
-    val schemaSource = URISchemaSource(schema)
-    val cg = Timer.getResult("compiling", {
-      val processorFactory = compiler.compileSource(schemaSource)
-      if (!processorFactory.isError) {
-        val generator = processorFactory.forLanguage(language)
-        displayDiagnostics(generator)
-        Some(generator)
-      } else {
-        displayDiagnostics(processorFactory)
-        None
-      }
-    })
-    cg
-  }
-
-  // write blobs to $PWD/daffodil-blobs/*.bin
-  val blobDir = Paths.get(System.getProperty("user.dir"), "daffodil-blobs")
-  val blobSuffix = ".bin"
-
-
-  def setLogLevel(verbose: Int): Unit = {
-    val verboseLevel = verbose match {
-      case 0 => Level.WARN
-      case 1 => Level.INFO
-      case 2 => Level.DEBUG
-      case _ => Level.TRACE
-    }
-    Configurator.setLevel("org.apache.daffodil", verboseLevel)
-  }
-
-  def runIgnoreExceptions(arguments: Array[String]): ExitCode.Value = {
-    val conf = new CLIConf(arguments)
-
-    setLogLevel(conf.verbose())
-
-    val ret: ExitCode.Value = conf.subcommand match {
-
-      case Some(conf.parse) => {
-        val parseOpts = conf.parse
-
-        val validate = parseOpts.validate.toOption.get
-
-        val optDafConfig = parseOpts.config.toOption.map{ DaffodilConfig.fromFile(_) }
-
-        val processor: Option[DFDL.DataProcessor] = {
-          if (parseOpts.parser.isDefined) {
-            createProcessorFromParser(parseOpts.parser(), parseOpts.path.toOption, validate)
-          } else {
-            val tunables = DaffodilTunables.configPlusMoreTunablesMap(parseOpts.tunables, optDafConfig)
-            createProcessorFromSchema(parseOpts.schema(), parseOpts.rootNS.toOption, parseOpts.path.toOption, tunables, validate)
-          }
-        }.map{ _.withExternalVariables(combineExternalVariables(parseOpts.vars, optDafConfig)) }
-         .map{ _.withValidationMode(validate) }
-         .map{ withDebugOrTrace(_, conf) }
-
-        val rc = processor match {
-          case None => ExitCode.UnableToCreateProcessor
-          case Some(processor) => {
-            Assert.invariant(!processor.isError)
-            val input = parseOpts.infile.toOption match {
-              case Some("-") | None => STDIN
-              case Some(file) => {
-                val f = new File(file)
-                new FileInputStream(f)
-              }
-            }
-            val inStream = InputSourceDataInputStream(input)
-
-            val output = parseOpts.output.toOption match {
-              case Some("-") | None => STDOUT
-              case Some(file) => new FileOutputStream(file)
-            }
-
-            val infosetType = parseOpts.infosetType.toOption.get
-            val infosetHandler = InfosetType.getInfosetHandler(
-              parseOpts.infosetType.toOption.get,
-              processor,
-              parseOpts.schema.toOption,
-              forPerformance = false)
-
-            var lastParseBitPosition = 0L
-            var keepParsing = true
-            var exitCode = ExitCode.Success
-
-            while (keepParsing) {
-              val infosetResult = Timer.getResult("parsing", infosetHandler.parse(inStream, output))
-              val parseResult = infosetResult.parseResult
-
-              val finfo = parseResult.resultState.asInstanceOf[FormatInfo]
-              val loc = parseResult.resultState.currentLocation.asInstanceOf[DataLoc]
-              displayDiagnostics(parseResult)
-
-              if (parseResult.isProcessingError || parseResult.isValidationError) {
-                keepParsing = false
-                exitCode = ExitCode.ParseError
-              } else {
-                // Success. Some InfosetHandlers do not write the result to the output
-                // stream when parsing (e.g. they just create Scala objects). Since we are
-                // parsing, ask the InfosetHandler to serialize the result if it has an
-                // implementation so that the user can see an XML represenation regardless
-                // of the infoset type.
-                infosetResult.write(output)
-                output.flush()
-
-                if (!inStream.hasData()) {
-                  // not even 1 more bit is available.
-                  // do not try to keep parsing, nothing left to parse
-                  keepParsing = false
-                } else {
-                  // There is more data available.
-                  if (parseOpts.stream.toOption.get) {
-                    // Streaming mode
-                    if (lastParseBitPosition == loc.bitPos0b) {
-                      // this parse consumed no data, that means this would get
-                      // stuck in an infinite loop if we kept trying to stream,
-                      // so we need to quit
-                      val remainingBits =
-                        if (loc.bitLimit0b.isDefined) {
-                          (loc.bitLimit0b.get - loc.bitPos0b).toString
-                        } else {
-                          "at least " + (inStream.inputSource.bytesAvailable * 8)
-                        }
-                      Logger.log.error(s"Left over data after consuming 0 bits while streaming. Stopped after consuming ${loc.bitPos0b} bit(s) with ${remainingBits} bit(s) remaining.")
-                      keepParsing = false
-                      exitCode = ExitCode.LeftOverData
-                    } else {
-                      // last parse did consume data, and we know there is more
-                      // data to come, so try to parse again.
-                      lastParseBitPosition = loc.bitPos0b
-                      keepParsing = true
-                      output.write(0) // NUL-byte separates streams
-                    }
-                  } else {
-                    // not streaming mode, and there is more data available,
-                    // so show left over data warning
-                    val Dump = new DataDumper
-                    val bitsAlreadyConsumed = loc.bitPos0b % 8
-                    val firstByteString = if (bitsAlreadyConsumed != 0) {
-                      val bitsToDisplay = 8 - bitsAlreadyConsumed
-                      val pbp = inStream.inputSource.position + 1
-                      val firstByteBitArray = inStream.getByteArray(bitsToDisplay, finfo)
-                      val fbs = firstByteBitArray(0).toBinaryString.takeRight(8).reverse.padTo(8, '0').reverse
-                      val bits = if (finfo.bitOrder == BitOrder.MostSignificantBitFirst) {
-                        "x" * bitsAlreadyConsumed + fbs.dropRight(bitsAlreadyConsumed)
-                      } else {
-                        fbs.takeRight(bitsToDisplay) + "x" * bitsAlreadyConsumed
-                      }
-                      val dumpString = f"\nLeft over data starts with partial byte. Left over data (Binary) at byte $pbp is: (0b$bits)"
-                      dumpString
-                    } else ""
-                    val curBytePosition1b = inStream.inputSource.position + 1
-                    val bytesAvailable = inStream.inputSource.bytesAvailable
-                    val bytesLimit = math.min(8, bytesAvailable).toInt
-                    val destArray = new Array[Byte](bytesLimit)
-                    val destArrayFilled = inStream.inputSource.get(destArray, 0, bytesLimit)
-                    val dumpString = if (destArrayFilled) Dump.dump(Dump.TextOnly(Some("utf-8")), 0, destArray.length * 8, ByteBuffer.wrap(destArray), includeHeadingLine = false).mkString("\n") else ""
-                    val dataText = if (destArrayFilled) s"\nLeft over data (UTF-8) starting at byte ${curBytePosition1b} is: (${dumpString}...)" else ""
-                    val dataHex = if (destArrayFilled) s"\nLeft over data (Hex) starting at byte ${curBytePosition1b} is: (0x${destArray.map { a => f"$a%02x" }.mkString}...)" else ""
-                    val remainingBits =
-                      if (loc.bitLimit0b.isDefined) {
-                        (loc.bitLimit0b.get - loc.bitPos0b).toString
-                      } else {
-                        "at least " + (bytesAvailable * 8)
-                      }
-                    val leftOverDataMessage = s"Left over data. Consumed ${loc.bitPos0b} bit(s) with ${remainingBits} bit(s) remaining." + firstByteString + dataHex + dataText
-                    Logger.log.error(leftOverDataMessage)
-                    keepParsing = false
-                    exitCode = ExitCode.LeftOverData
-                  }
-                }
-              }
-            }
-          exitCode
-          }
-        }
-        rc
-      }
-
-      case Some(conf.performance) => {
-        val performanceOpts = conf.performance
-
-        val validate = performanceOpts.validate.toOption.get
-
-        val optDafConfig = performanceOpts.config.toOption.map{ DaffodilConfig.fromFile(_) }
-
-        val processor = {
-          if (performanceOpts.parser.isDefined) {
-            createProcessorFromParser(performanceOpts.parser(), performanceOpts.path.toOption, validate)
-          } else {
-            val tunables = DaffodilTunables.configPlusMoreTunablesMap(performanceOpts.tunables, optDafConfig)
-            createProcessorFromSchema(performanceOpts.schema(), performanceOpts.rootNS.toOption, performanceOpts.path.toOption, tunables, validate)
-          }
-        }.map{ _.withExternalVariables(combineExternalVariables(performanceOpts.vars, optDafConfig)) }
-         .map{ _.withValidationMode(validate) }
-
-        val rc: ExitCode.Value = processor match {
-          case None => ExitCode.UnableToCreateProcessor
-          case Some(processor) => {
-            val infile = new java.io.File(performanceOpts.infile())
-
-            val files = {
-              if (infile.isDirectory()) {
-                infile.listFiles.filter(!_.isDirectory)
-              } else {
-                Array(infile)
-              }
-            }
-
-            val infosetType = performanceOpts.infosetType.toOption.get
-            val infosetHandler = InfosetType.getInfosetHandler(
-              infosetType,
-              processor,
-              performanceOpts.schema.toOption,
-              forPerformance = true)
-
-            val dataSeq: Seq[Either[AnyRef, Array[Byte]]] = files.map { filePath =>
-              // For performance testing, we want everything in memory so as to
-              // remove I/O from consideration. Additionally, for both parse
-              // and unparse we need immutable inputs since we could parse the
-              // same input data multiple times in different performance runs.
-              // So read the file data into an Array[Byte], and use that for
-              // everything.
-              val input = (new FileInputStream(filePath))
-              val dataSize = filePath.length()
-              val bytes = new Array[Byte](dataSize.toInt)
-              input.read(bytes)
-              val data = performanceOpts.unparse() match {
-                case true => Left(infosetHandler.dataToInfoset(bytes))
-                case false => Right(bytes)
-              }
-              data
-            }
-
-            val inputs = (0 until performanceOpts.number()).map { n =>
-              val index = n % dataSeq.length
-              dataSeq(index)
-            }
-            val inputsWithIndex = inputs.zipWithIndex
-
-            implicit val executionContext = new ExecutionContext {
-              val threadPool = Executors.newFixedThreadPool(performanceOpts.threads())
-
-              def execute(runnable: Runnable): Unit = {
-                threadPool.submit(runnable)
-              }
-
-              def reportFailure(t: Throwable): Unit = {
-                //do nothing
-              }
-            }
-
-            val nullChannelForUnparse = Channels.newChannel(NullOutputStream.NULL_OUTPUT_STREAM)
-            val nullOutputStreamForParse = NullOutputStream.NULL_OUTPUT_STREAM
-
-            val NSConvert = 1000000000.0
-            val (totalTime, results) = Timer.getTimeResult({
-              val tasks = inputsWithIndex.map {
-                case (inData, n) =>
-                  val task: Future[(Int, Long, Boolean)] = Future {
-                    val (time, result) = inData match {
-                      case Left(anyRef) => Timer.getTimeResult({
-                        val unparseResult = infosetHandler.unparse(anyRef, nullChannelForUnparse)
-                        unparseResult
-                      })
-                      case Right(bytes) => Timer.getTimeResult({
-                        val input = InputSourceDataInputStream(bytes)
-                        val infosetResult = infosetHandler.parse(input, nullOutputStreamForParse)
-                        val parseResult = infosetResult.parseResult
-                        parseResult
-                      })
-                    }
-
-                    (n, time, result.isError)
-                  }
-                  task
-              }
-              val results = tasks.map { Await.result(_, Duration.Inf) }
-              results
-            })
-
-            val rates = results.map { results =>
-              val (runNum: Int, nsTime: Long, error: Boolean) = results
-              val rate = 1 / (nsTime / NSConvert)
-              val status = if (error) "fail" else "pass"
-              Logger.log.info(s"run: ${runNum}, seconds: ${nsTime / NSConvert}, rate: ${rate}, status: ${status}")
-              rate
-            }
-
-            val numFailures = results.map { _._3 }.filter { e => e }.length
-            if (numFailures > 0) {
-              Logger.log.error(s"${numFailures} failures found\n")
-            }
-
-            val sec = totalTime / NSConvert
-            val action = performanceOpts.unparse() match {
-              case true => "unparse"
-              case false => "parse"
-            }
-            STDOUT.println(s"total $action time (sec): $sec")
-            STDOUT.println(s"min rate (files/sec): ${rates.min.toFloat}")
-            STDOUT.println(s"max rate (files/sec): ${rates.max.toFloat}")
-            STDOUT.println(s"avg rate (files/sec): ${(performanceOpts.number() / sec).toFloat}")
-
-            if (numFailures == 0) ExitCode.Success else ExitCode.PerformanceTestError
-          }
-
-        }
-        rc
-      }
-
-      case Some(conf.unparse) => {
-        val unparseOpts = conf.unparse
-
-        val validate = unparseOpts.validate.toOption.get
-
-        val optDafConfig = unparseOpts.config.toOption.map{ DaffodilConfig.fromFile(_) }
-
-        val processor = {
-          if (unparseOpts.parser.isDefined) {
-            createProcessorFromParser(unparseOpts.parser(), unparseOpts.path.toOption, validate)
-          } else {
-            val tunables = DaffodilTunables.configPlusMoreTunablesMap(unparseOpts.tunables, optDafConfig)
-            createProcessorFromSchema(unparseOpts.schema(), unparseOpts.rootNS.toOption, unparseOpts.path.toOption, tunables, validate)
-          }
-        }.map{ _.withExternalVariables(combineExternalVariables(unparseOpts.vars, optDafConfig)) }
-         .map{ _.withValidationMode(validate) }
-         .map{ withDebugOrTrace(_, conf) }
-
-        val output = unparseOpts.output.toOption match {
-          case Some("-") | None => STDOUT
-          case Some(file) => new FileOutputStream(file)
-        }
-
-        val outChannel = Channels.newChannel(output)
-        //
-        // We are not loading a schema here, we're loading the infoset to unparse.
-        //
-        val is = unparseOpts.infile.toOption match {
-          case Some("-") | None => STDIN
-          case Some(fileName) => new FileInputStream(fileName)
-        }
-
-        val rc = processor match {
-          case None => ExitCode.UnableToCreateProcessor
-          case Some(processor) => {
-            Assert.invariant(processor.isError == false)
-
-            val maybeScanner =
-              if (unparseOpts.stream.toOption.get) {
-                val scnr = new Scanner(is)
-                scnr.useDelimiter("\u0000")
-                Some(scnr)
-              } else {
-                None
-              }
-
-            var keepUnparsing = maybeScanner.isEmpty || maybeScanner.get.hasNext
-            var exitCode = ExitCode.Success
-
-            val infosetType = unparseOpts.infosetType.toOption.get
-            val infosetHandler = InfosetType.getInfosetHandler(
-              unparseOpts.infosetType.toOption.get,
-              processor,
-              unparseOpts.schema.toOption,
-              forPerformance = false)
-
-            while (keepUnparsing) {
-
-              val inputterData =
-                if (maybeScanner.isDefined) {
-                  // The scanner reads the entire infoset up unto the delimiter
-                  // into memory. No way around that with the --stream option.
-                  val bytes = maybeScanner.get.next().getBytes()
-                  infosetHandler.dataToInfoset(bytes)
-                } else {
-                  // We are not using the --stream option and won't need to
-                  // unparse the infoset more than once. So pass the
-                  // InputStream into dataToInfoset. For some cases, such as
-                  // "xml" or "json", we can create an InfosetInputter directly
-                  // on this stream so that we can avoid reading the entire
-                  // InputStream into memory
-                  infosetHandler.dataToInfoset(is)
-                }
-              val unparseResult = Timer.getResult("unparsing", infosetHandler.unparse(inputterData, outChannel))
-
-              displayDiagnostics(unparseResult)
-
-              if (unparseResult.isError) {
-                keepUnparsing = false
-                exitCode = ExitCode.UnparseError
-              } else {
-                keepUnparsing = maybeScanner.isDefined && maybeScanner.get.hasNext
-              }
-            }
-            exitCode
-          }
-        }
-
-        is.close()
-        outChannel.close()
-
-        rc
-      }
-
-      case Some(conf.save) => {
-        val saveOpts = conf.save
-
-        val validate = ValidationMode.Off
-        val optDafConfig = saveOpts.config.toOption.map{ DaffodilConfig.fromFile(_) }
-
-        val tunables = DaffodilTunables.configPlusMoreTunablesMap(saveOpts.tunables, optDafConfig)
-        val tunablesObj = DaffodilTunables(tunables)
-
-        val processor = createProcessorFromSchema(saveOpts.schema(), saveOpts.rootNS.toOption, saveOpts.path.toOption, tunables, validate)
-
-        val output = saveOpts.outfile.toOption match {
-          case Some("-") | None => Channels.newChannel(STDOUT)
-          case Some(file) => new FileOutputStream(file).getChannel()
-        }
-
-        val rc = processor match {
-          case Some(processor) => {
-            Assert.invariant(processor.isError == false)
-            Timer.getResult("saving", processor.save(output))
-            ExitCode.Success
-          }
-          case None => ExitCode.UnableToCreateProcessor
-        }
-        rc
-      }
-
-      case Some(conf.test) => {
-        val testOpts = conf.test
-
-        val tdmlFile = testOpts.tdmlfile()
-        val optTDMLImplementation = testOpts.implementation.toOption
-        val tdmlRunner = Runner(tdmlFile, optTDMLImplementation)
-
-        val tests = {
-          if (testOpts.testnames.isDefined) {
-            testOpts.testnames().flatMap(testName => {
-              if (testOpts.regex()) {
-                val regex = testName.r
-                val matches = tdmlRunner.testCases.filter(testCase => regex.pattern.matcher(testCase.tcName).matches)
-                matches.map(testCase => (testCase.tcName, Some(testCase)))
-              } else {
-                List((testName, tdmlRunner.testCases.find(_.tcName == testName)))
-              }
-            })
-          } else {
-            tdmlRunner.testCases.map(test => (test.tcName, Some(test)))
-          }
-        }.distinct.sortBy(_._1)
-
-        tdmlRunner.reset
-
-        if (testOpts.list()) {
-          if (testOpts.info() > 0) {
-            // determine the max lengths of the various pieces of a test
-            val headers = List("Name", "Model", "Root", "Description")
-            val maxCols = tests.foldLeft(headers.map(_.length)) {
-              (maxVals, testPair) =>
-                {
-                  testPair match {
-                    case (name, None) => List(
-                      maxVals(0).max(name.length),
-                      maxVals(1),
-                      maxVals(2),
-                      maxVals(3))
-                    case (name, Some(test)) => List(
-                      maxVals(0).max(name.length),
-                      maxVals(1).max(test.model.length),
-                      maxVals(2).max(test.rootName.length),
-                      maxVals(3).max(test.description.length))
-                  }
-                }
-            }
-            val formatStr = maxCols.map(max => "%" + -max + "s").mkString("  ")
-            STDOUT.println(formatStr.format(headers: _*))
-            tests.foreach { testPair =>
-              testPair match {
-                case (name, Some(test)) => STDOUT.println(formatStr.format(name, test.model, test.rootName, test.description))
-                case (name, None) => STDOUT.println(formatStr.format(name, "[Not Found]", "", ""))
-              }
-            }
-          } else {
-            tests.foreach { testPair =>
-              testPair match {
-                case (name, Some(test)) => STDOUT.println(name)
-                case (name, None) => STDOUT.println("%s  [Not Found]".format(name))
-              }
-            }
-          }
-          ExitCode.Success
-        } else {
-          var pass = 0
-          var fail = 0
-          var notfound = 0
-          tests.foreach { testPair =>
-            testPair match {
-              case (name, Some(test)) => {
-                try {
-                  test.run()
-                  STDOUT.println("[Pass] %s".format(name))
-                  pass += 1
-                } catch {
-                  case s: scala.util.control.ControlThrowable => throw s
-                  case u: UnsuppressableException => throw u
-                  case e: TDMLTestNotCompatibleException => {
-                    STDOUT.println("[Skipped] %s (not compatible with implementation: %s)"
-                      .format(name, e.implementation.getOrElse("<none>")))
-                  }
-                  case e: Throwable => {
-                    e.getCause match {
-                      case e: TDMLTestNotCompatibleException => {
-                        // if JUnit is on the classpath (e.g. possible in integration tests), then
-                        // the TDML runner throws an AssumptionViolatedException where the cause is
-                        // a TDMLTestNotCompatibleException. In that case we should output the test
-                        // skipped message. The way we match here avoids having the CLI to require
-                        // JUnit as a dependency
-                        STDOUT.println("[Skipped] %s (not compatible with implementation: %s)"
-                          .format(name, e.implementation.getOrElse("<none>")))
-                      }
-                      case _ => {
-                        STDOUT.println("[Fail] %s".format(name))
-                        fail += 1
-                        if (testOpts.info() > 0) {
-                          STDOUT.println("  Failure Information:")
-                          STDOUT.println(indent(e.getMessage(), 4))
-                        }
-                        if (testOpts.info() > 1) {
-                          STDOUT.println("  Backtrace:")
-                          e.getStackTrace.foreach { st => STDOUT.println(indent(st.toString, 4)) }
-                        }
-                      }
-                    }
-                  }
-                }
-              }
-
-              case (name, None) => {
-                STDOUT.println("[Not Found] %s".format(name))
-                notfound += 1
-              }
-            }
-          }
-          STDOUT.println("")
-          STDOUT.println("Total: %d, Pass: %d, Fail: %d, Not Found: %s".format(pass + fail + notfound, pass, fail, notfound))
-
-          if (fail == 0) ExitCode.Success else ExitCode.TestError
-        }
-      }
-
-      // Get our generate options from whichever language we're generating
-      case Some(conf.generate) => {
-        val generateOpts = conf.generate.subcommand match {
-          case Some(conf.generate.c) => conf.generate.c
-
-          // Required to avoid "match may not be exhaustive", but should never happen
-          case _ => Assert.impossible()
-        }
-
-        // Read any config file and any tunables given as arguments
-        val optDafConfig = generateOpts.config.toOption.map{ DaffodilConfig.fromFile(_) }
-        val tunables = DaffodilTunables.configPlusMoreTunablesMap(generateOpts.tunables, optDafConfig)
-
-        // Create a CodeGenerator from the DFDL schema
-        val generator = createGeneratorFromSchema(generateOpts.schema(), generateOpts.rootNS.toOption,
-          tunables, generateOpts.language)
-
-        // Ask the CodeGenerator to generate source code from the DFDL schema
-        val outputDir = generateOpts.outdir.toOption.getOrElse(".")
-        val rc = generator match {
-          case Some(generator) => {
-            Timer.getResult("generating", generator.generateCode(outputDir))
-            displayDiagnostics(generator)
-            if (generator.isError) ExitCode.GenerateCodeError else ExitCode.Success
-          }
-          case None => ExitCode.GenerateCodeError
-        }
-        rc
-      }
-
-      case Some(conf.exi) => {
-        var rc = ExitCode.Success
-        val exiOpts = conf.exi
-        val channel = exiOpts.output.toOption match {
-          case Some("-") | None => Channels.newChannel(STDOUT)
-          case Some(file) => new FileOutputStream(file).getChannel()
-        }
-        val output = Channels.newOutputStream(channel)
-
-        val inputStream = exiOpts.infile.toOption match {
-          case Some("-") | None => STDIN
-          case Some(file) => {
-            val f = new File(file)
-            new FileInputStream(f)
-          }
-        }
-        val input = new InputSource(inputStream)
-
-        val exiFactory: Option[EXIFactory] = try {
-          Some(EXIInfosetHandler.createEXIFactory(exiOpts.schema.toOption))
-        } catch {
-          case e: EXIException => {
-            Logger.log.error(s"Error creating EXI grammar for the supplied schema: ${ Misc.getSomeMessage(e).get }")
-            rc = ExitCode.Failure
-            None
-          }
-        }
-
-        (exiOpts.decode.toOption.get, exiFactory.isDefined) match {
-          case (true, true) => { // Decoding
-            val exiSource = new EXISource(exiFactory.get)
-            exiSource.setInputSource(input)
-
-            val result = new StreamResult(output)
-            val tf = TransformerFactory.newInstance()
-            val transformer = tf.newTransformer
-            transformer.setErrorListener(new EXIErrorHandler)
-            try {
-              transformer.transform(exiSource, result)
-            } catch {
-              /* We catch a generic Exception here as Exificient will attempt
-               * to decode anything and will throw very generic errors, such as
-               * an IllegalArgumentException when it runs into a series of bytes
-               * that aren't a Unicode codepoint. This should be removed once
-               * https://github.com/EXIficient/exificient/issues/33 is fixed.*/
-              case e: Exception => {
-                Logger.log.error(s"Error decoding EXI input: ${ Misc.getSomeMessage(e).get }")
-                rc = ExitCode.Failure
-              }
-            }
-          }
-          case (false, true) => { // Encoding
-            val exiResult = new EXIResult(exiFactory.get)
-            exiResult.setOutputStream(output)
-
-            val reader = XMLReaderFactory.createXMLReader()
-            reader.setContentHandler(exiResult.getHandler)
-            reader.setErrorHandler(new EXIErrorHandler)
-            try {
-              reader.parse(input)
-            } catch {
-              case s: org.xml.sax.SAXException => {
-                Logger.log.error(s"Error parsing input XML: ${ Misc.getSomeMessage(s).get }")
-                rc = ExitCode.Failure
-              }
-            }
-          }
-          case (_, false) => // Hit an exception creating exiFactory, rc already set
-        }
-
-        inputStream.close
-        output.close
-        rc
-      }
-
-      // Required to avoid "match may not be exhaustive", but should never happen
-      case _ => Assert.impossible()
-    }
-
-    ret
-  }
-
-  def bugFound(e: Exception): Int = {
-    STDERR.println("""|
-                      |!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-                      |!!   An unexpected exception occurred. This is a bug!   !!
-                      |!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-                      |
-                      | Please report this bug and help us fix it:
-                      |
-                      |  https://daffodil.apache.org/community/#issue-tracker
-                      |
-                      | Please include the following exception, the command you
-                      | ran, and any input, schema, or tdml files used that led
-                      | to this bug.
-                      |
-                      |""".stripMargin)
-    e.printStackTrace
-    1
-  }
-
-  def nyiFound(e: NotYetImplementedException): Int = {
-    STDERR.println("""|
-                      |!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-                      |!!                 Not Yet Implemented                  !!
-                      |!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-                      |
-                      | You are using a feature that is not yet implemented:
-                      |
-                      | %s
-                      |
-                      | You can create a bug and track the progress of this
-                      | feature at:
-                      |
-                      |  https://issues.apache.org/jira/projects/DAFFODIL
-                      |
-                      |""".format(Misc.getSomeMessage(e)).stripMargin)
-    1
-  }
-
-  def oomError(e: OutOfMemoryError): Int = {
-    STDERR.println("""|
-                      |!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-                      |!!             Daffodil ran out of memory!              !!
-                      |!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-                      |
-                      | Try increasing the amount of memory by changing or
-                      | setting the DAFFODIL_JAVA_OPTS environment variable.
-                      | "DAFFODIL_JAVA_OPTS=-Xmx5G" for 5GB.
-                      |
-                      |""".stripMargin)
-    1
-  }
-
-  object ExitCode extends Enumeration {
-
-    val Success = Value(0)
-    val Failure = Value(1)
-
-    val FileNotFound = Value(2)
-    val OutOfMemory = Value(3)
-    val BugFound = Value(4)
-    val NotYetImplemented = Value(5)
-
-
-    val ParseError = Value(20)
-    val UnparseError = Value(21)
-    val GenerateCodeError = Value(23)
-    val TestError = Value(24)
-    val PerformanceTestError = Value(25)
-    val ConfigError = Value(26)
-
-    val LeftOverData = Value(31)
-    val InvalidParserException = Value(32)
-    val BadExternalVariable = Value(33)
-    val UserDefinedFunctionError = Value(34)
-    val UnableToCreateProcessor = Value(35)
-
-    val Usage = Value(64)
-
-  }
-
-
-  def run(arguments: Array[String]): ExitCode.Value = {
-    val ret = try {
-      runIgnoreExceptions(arguments)
-    } catch {
-      case s: scala.util.control.ControlThrowable => throw s
-      case e: java.io.FileNotFoundException => {
-        Logger.log.error(Misc.getSomeMessage(e).get)
-        ExitCode.FileNotFound
-      }
-      case e: ExternalVariableException => {
-        Logger.log.error(Misc.getSomeMessage(e).get)
-        ExitCode.BadExternalVariable
-      }
-      case e: BindingException => {
-        Logger.log.error(Misc.getSomeMessage(e).get)
-        ExitCode.BadExternalVariable
-      }
-      case e: NotYetImplementedException => {
-        nyiFound(e)
-        ExitCode.NotYetImplemented
-      }
-      case e: TDMLException => {
-        Logger.log.error(Misc.getSomeMessage(e).get)
-        ExitCode.TestError
-      }
-      case e: OutOfMemoryError => {
-        oomError(e)
-        ExitCode.OutOfMemory
-      }
-      case e: UserDefinedFunctionFatalErrorException => {
-        Logger.log.error(Misc.getSomeMessage(e).get)
-        e.cause.getStackTrace.take(10).foreach { ste =>
-          Logger.log.error(s"    at ${ste}")
-        }
-        ExitCode.UserDefinedFunctionError
-      }
-      case e: DebuggerExitException => {
-        ExitCode.Failure
-      }
-      case e: GenericScallopException => {
-        Logger.log.error(e.message)
-        ExitCode.Usage
-      }
-      case e: DaffodilConfigException => {
-        Logger.log.error(e.message)
-        ExitCode.ConfigError
-      }
-      case e: Exception => {
-        bugFound(e)
-        ExitCode.BugFound
-      }
-    }
-    ret
-  }
-
-  def main(arguments: Array[String]): Unit = {
-    val exitCode = run(arguments)
-    System.exit(exitCode.id)
-  }
-}
-
-class EXIErrorHandler extends org.xml.sax.ErrorHandler with javax.xml.transform.ErrorListener {
-
-  // SAX ErrorHandler methods
-  def error(e: SAXParseException): Unit = {
-    Logger.log.error(e.getMessage)
-  }
-
-  def fatalError(e: SAXParseException): Unit = {
-    Logger.log.error(e.getMessage)
-  }
-
-  def warning(e: SAXParseException): Unit = {
-    Logger.log.warn(e.getMessage)
-  }
-
-  // Transformer ErrorListener methods
-  def error(e: TransformerException): Unit = {
-    Logger.log.error(e.getMessage)
-  }
-  def fatalError(e: TransformerException): Unit = {
-    Logger.log.error(e.getMessage)
-  }
-  def warning(e: TransformerException): Unit = {
-    Logger.log.warn(e.getMessage)
-  }
-}
diff --git a/daffodil-cli/src/main/scala/org/apache/daffodil/cli/InfosetTypes.scala b/daffodil-cli/src/main/scala/org/apache/daffodil/cli/InfosetTypes.scala
new file mode 100644
index 000000000..c6063913c
--- /dev/null
+++ b/daffodil-cli/src/main/scala/org/apache/daffodil/cli/InfosetTypes.scala
@@ -0,0 +1,710 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+package org.apache.daffodil.cli
+
+import java.io.ByteArrayInputStream
+import java.io.InputStream
+import java.io.OutputStream
+import java.net.URI
+import java.nio.charset.StandardCharsets
+import javax.xml.parsers.DocumentBuilderFactory
+import javax.xml.transform.TransformerFactory
+import javax.xml.transform.dom.DOMSource
+import javax.xml.transform.stream.StreamResult
+
+import scala.collection.mutable.ArrayBuffer
+import scala.xml.SAXParser
+
+import com.siemens.ct.exi.core.EXIFactory
+import com.siemens.ct.exi.core.helpers.DefaultEXIFactory
+import com.siemens.ct.exi.grammars.GrammarFactory
+import com.siemens.ct.exi.main.api.sax.EXIResult
+import com.siemens.ct.exi.main.api.sax.EXISource
+
+import org.apache.commons.io.IOUtils
+
+import org.xml.sax.Attributes
+import org.xml.sax.ContentHandler
+import org.xml.sax.InputSource
+import org.xml.sax.XMLReader
+import org.xml.sax.Locator
+import org.xml.sax.helpers.AttributesImpl
+import org.xml.sax.helpers.DefaultHandler
+
+import org.apache.daffodil.runtime1.api.DFDL
+import org.apache.daffodil.runtime1.api.DFDL.DataProcessor
+import org.apache.daffodil.runtime1.api.DFDL.DaffodilUnparseErrorSAXException
+import org.apache.daffodil.runtime1.api.DFDL.ParseResult
+import org.apache.daffodil.runtime1.api.DFDL.UnparseResult
+import org.apache.daffodil.runtime1.infoset.InfosetInputter
+import org.apache.daffodil.runtime1.infoset.InfosetOutputter
+import org.apache.daffodil.runtime1.infoset.JDOMInfosetInputter
+import org.apache.daffodil.runtime1.infoset.JDOMInfosetOutputter
+import org.apache.daffodil.runtime1.infoset.JsonInfosetInputter
+import org.apache.daffodil.runtime1.infoset.JsonInfosetOutputter
+import org.apache.daffodil.runtime1.infoset.NullInfosetInputter
+import org.apache.daffodil.runtime1.infoset.NullInfosetOutputter
+import org.apache.daffodil.runtime1.infoset.ScalaXMLInfosetInputter
+import org.apache.daffodil.runtime1.infoset.ScalaXMLInfosetOutputter
+import org.apache.daffodil.runtime1.infoset.W3CDOMInfosetInputter
+import org.apache.daffodil.runtime1.infoset.W3CDOMInfosetOutputter
+import org.apache.daffodil.runtime1.infoset.XMLTextInfosetInputter
+import org.apache.daffodil.runtime1.infoset.XMLTextInfosetOutputter
+import org.apache.daffodil.io.InputSourceDataInputStream
+import org.apache.daffodil.runtime1.processors.DaffodilParseOutputStreamContentHandler
+import org.apache.daffodil.lib.xml.DFDLCatalogResolver
+import org.apache.daffodil.lib.xml.DaffodilSAXParserFactory
+import org.apache.daffodil.lib.xml.XMLUtils
+
+object InfosetType extends Enumeration {
+  type Type = Value
+
+  val EXI = Value("exi")
+  val EXISA = Value("exisa")
+  val JDOM = Value("jdom")
+  val JSON = Value("json")
+  val NULL = Value("null")
+  val SAX = Value("sax")
+  val SCALA_XML = Value("scala-xml")
+  val W3CDOM = Value("w3cdom")
+  val XML = Value("xml")
+
+  /**
+   * Get an InfosetHandler, with the goal of doing as much initialization/work
+   * as needed prior to calling the parse() or unparse() methods to improve
+   * accuracy of performance metrics
+   *
+   * @param infosetType the type of InfosetHandler to create
+   * @param dataProcessor the dataProcessor that the InfosetHandler should use
+   *   during parse/unparse operations
+   * @param schemaUri only used for EXISA, to support schema aware
+   *   parsing/unparsing
+   * @param forPerformance only used for SAX. If true, the
+   *   SAXInfosetHandler will drop all SAX events on parse, and will
+   *   pre-process the infoset into an array of SAX events and replay them on
+   *   unparse. If false, it directly parses and unparses to/from XML
+   *   text--this allows the caller to visualize the SAX as XML, similar
+   *   to how JDOM, SCALA_XML, etc can be serialized to strings
+   */
+  def getInfosetHandler(
+    infosetType: InfosetType.Type,
+    dataProcessor: DFDL.DataProcessor,
+    schemaUri: Option[URI],
+    forPerformance: Boolean): InfosetHandler = {
+
+    infosetType match {
+      case InfosetType.EXI => EXIInfosetHandler(dataProcessor)
+      case InfosetType.EXISA => EXIInfosetHandler(dataProcessor, schemaUri.get)
+      case InfosetType.JDOM => JDOMInfosetHandler(dataProcessor)
+      case InfosetType.JSON => JsonInfosetHandler(dataProcessor)
+      case InfosetType.NULL => NULLInfosetHandler(dataProcessor)
+      case InfosetType.SAX => SAXInfosetHandler(dataProcessor, forPerformance)
+      case InfosetType.SCALA_XML => ScalaXMLInfosetHandler(dataProcessor)
+      case InfosetType.W3CDOM => W3CDOMInfosetHandler(dataProcessor)
+      case InfosetType.XML => XMLTextInfosetHandler(dataProcessor)
+    }
+  }
+}
+
+sealed trait InfosetHandler {
+
+   /**
+    * Parse data from the InputSourceDataInputStream with the provided
+    * DataProcessor and return a new InfosetParseResult instance. Depending on
+    * the provided InfosetType, may or may not write an infoset instance to the
+    * OutputStream during parsing. This method will be called in a performance
+    * loop, so InfosetHandler constructors should do as much preprocessing and
+    * initialization as possible to save time in this method.
+    *
+    * Some InfosetHandlers will not write anything to the OutputStream here
+    * because they just create objects, call interface functions, or create
+    * nothing (e.g., JDOM, SCALA_XML, NULL). However, Daffodil's CLI wants all
+    * infoset types except NULL to output an infoset during parsing, so
+    * InfosetHandlers return an InfosetParseResult which can serialize the
+    * infoset to a string and write the string to the OutputStream. The CLI
+    * does not want to output the infoset in a performance loop, so we put such
+    * serialization and output code into InfosetParseResult, not
+    * InfosetHandler.
+    *
+    * @param input Data to be parsed into an infoset
+    * @param os OutputStream to write an infoset to
+    */
+  def parse(input: InputSourceDataInputStream, os: OutputStream): InfosetParseResult
+
+  /**
+   * Convert an Array of Bytes to whatever form the unparse() function expects
+   * to use, preferably as close to the native format for the infoset type as
+   * possible
+   *
+   * The return value of the Array[Byte] variant must be thread safe and
+   * immutable since it could potentially be shared among other threaded calls
+   * to unparse()
+   *
+   * This function is guaranteed to be called outside of a performance loop, so
+   * expensive operations to create the native infoset representation should
+   * take place here instead of the unparse() function.
+   */
+  def dataToInfoset(bytes: Array[Byte]): AnyRef
+
+  /**
+   * Convert an InputStream to whatever form the unparse() function expects to
+   * use, preferably as close to the native format for the infoset type as
+   * possible
+   *
+   * With this InputStream variant, we can assume the caller knows that the
+   * infoset represented by this InputStream will only be unparsed once and so
+   * it is acceptable if the result is mutable or non-thread safe. For
+   * InfosetHandlers that easily support InputStreams, it is recommended to
+   * simply return the same InputStream since it avoids reading the entire
+   * infoset into memory and making it possible to unparse large infosets.
+   * However, for InfosetHandlers that do not accept InputStreams,
+   * implementations must read in the entire InputStream and convert it to
+   * whatever they expect (e.g. Scala XML Node for "scala-xml"). Supporting
+   * large inputs with such infoset types is not possible.
+   *
+   * This function is guaranteed to be called outside of a performance loop, so
+   * expensive operations to create the native infoset representation should
+   * take place here instead of the unparse() function.
+   */
+  def dataToInfoset(stream: InputStream): AnyRef
+
+  /**
+   * Unparse an infoset to the given OutputStream. This method will be called
+   * in a performance loop, so InfosetHandler constructors and/or dataToInfoset()
+   * functions should perform as much preprocessing and initialization as possible
+   * to save time in this method.
+   *
+   * @param infoset Infoset representation returned by dataToInfoset() functions
+   * @param output Output channel to write the unparse data to
+   */
+  def unparse(infoset: AnyRef, output: DFDL.Output): UnparseResult
+
+  /**
+   * DataProcessor to be used for parse/unparse calls
+   */
+  protected def dataProcessor: DataProcessor
+
+  /**
+   * Helper function to parse data using the Daffodil InfosetOutputter API
+   */
+  final protected def parseWithInfosetOutputter(input: InputSourceDataInputStream, output: InfosetOutputter): ParseResult = {
+    output.setBlobAttributes(Main.blobDir, null, Main.blobSuffix)
+    val pr = dataProcessor.parse(input, output)
+    pr
+  }
+
+  /**
+   * Helper function to unparse data using the Daffodil InfosetInputter API
+   */
+  final protected def unparseWithInfosetInputter(input: InfosetInputter, output: DFDL.Output): UnparseResult = {
+    val ur = dataProcessor.unparse(input, output)
+    ur
+  }
+
+  /**
+   * Helper function to parse data using the Daffodil SAX API
+   */
+  final protected def parseWithSax(input: InputSourceDataInputStream, contentHandler: ContentHandler): ParseResult = {
+    val xmlReader = dataProcessor.newXMLReaderInstance
+    // SAX_NAMESPACE_PREFIXES_FEATURE is needed to preserve nil attributes with EXI
+    xmlReader.setFeature(XMLUtils.SAX_NAMESPACE_PREFIXES_FEATURE, true)
+    xmlReader.setProperty(XMLUtils.DAFFODIL_SAX_URN_BLOBDIRECTORY, Main.blobDir)
+    xmlReader.setProperty(XMLUtils.DAFFODIL_SAX_URN_BLOBSUFFIX, Main.blobSuffix)
+    xmlReader.setContentHandler(contentHandler)
+    xmlReader.parse(input)
+    val pr = xmlReader.getProperty(XMLUtils.DAFFODIL_SAX_URN_PARSERESULT).asInstanceOf[ParseResult]
+    pr
+  }
+
+  /**
+   * Helper function to unparse data using the Daffodil SAX API
+   */
+  final protected def unparseWithSax(xmlReader: XMLReader, input: InputStream, output: DFDL.Output): UnparseResult = {
+    val contentHandler = dataProcessor.newContentHandlerInstance(output)
+    xmlReader.setContentHandler(contentHandler)
+    xmlReader.setFeature(XMLUtils.SAX_NAMESPACES_FEATURE, true)
+    xmlReader.setFeature(XMLUtils.SAX_NAMESPACE_PREFIXES_FEATURE, true)
+    try {
+      xmlReader.parse(new InputSource(input))
+    } catch {
+      case _: DaffodilUnparseErrorSAXException => // do nothing, unparseResult has error info
+    }
+    val ur = contentHandler.getUnparseResult
+    ur
+  }
+
+}
+
+/**
+ * Wrapper around a ParseResult that allows for serializing the ParseResult to
+ * an XML string for easy visualization for caller.
+ *
+ * Some InfosetHandlers do not write to the parse() OutputStream by default
+ * because they just create Scala/Java objects or just call interface functions
+ * (e.g. SCALA_XML, JDOM). However, for usability, it can be useful to
+ * serialize such objects to a String and write to an OutputStream in some
+ * circumstances. We do not want to do that in a performance critical loop, so
+ * the InfosetHandler parse() function can return a custom InfosetParseResult
+ * with a write() implementation to serialize the infoset result to a string
+ * and write it to a given OutputStream. Note that this write() function may
+ * not ever be called and is guaranteed to occur outside of a performance loop.
+ */
+sealed class InfosetParseResult(val parseResult: ParseResult) {
+  def write(os: OutputStream): Unit = {}
+}
+
+
+/**
+ * InfosetType.XML
+ */
+case class XMLTextInfosetHandler(dataProcessor: DataProcessor)
+  extends InfosetHandler {
+
+  def parse(input: InputSourceDataInputStream, os: OutputStream): InfosetParseResult = {
+    val output = new XMLTextInfosetOutputter(os, pretty = true)
+    val pr = parseWithInfosetOutputter(input, output)
+    new InfosetParseResult(pr)
+  }
+
+  def unparse(data: AnyRef, output: DFDL.Output): UnparseResult = {
+    val is = data match {
+      case bytes: Array[Byte] => new ByteArrayInputStream(bytes)
+      case is: InputStream => is
+    }
+    val input = new XMLTextInfosetInputter(is)
+    val ur = unparseWithInfosetInputter(input, output)
+    ur
+  }
+
+  def dataToInfoset(bytes: Array[Byte]): AnyRef = bytes
+
+  def dataToInfoset(stream: InputStream): AnyRef = stream
+}
+
+/**
+ * InfosetType.JSON
+ */
+case class JsonInfosetHandler(dataProcessor: DataProcessor)
+  extends InfosetHandler {
+
+  def parse(input: InputSourceDataInputStream, os: OutputStream): InfosetParseResult = {
+    val output = new JsonInfosetOutputter(os, pretty = true)
+    val pr = parseWithInfosetOutputter(input, output)
+    new InfosetParseResult(pr)
+  }
+
+  def unparse(data: AnyRef, output: DFDL.Output): UnparseResult = {
+    val is = data match {
+      case bytes: Array[Byte] => new ByteArrayInputStream(bytes)
+      case is: InputStream => is
+    }
+    val input = new JsonInfosetInputter(is)
+    val ur = unparseWithInfosetInputter(input, output)
+    ur
+  }
+
+  def dataToInfoset(bytes: Array[Byte]): AnyRef = bytes
+
+  def dataToInfoset(stream: InputStream): AnyRef = stream
+}
+
+/**
+ * InfosetType.JDOM
+ */
+case class JDOMInfosetHandler(dataProcessor: DataProcessor)
+  extends InfosetHandler {
+
+  def parse(input: InputSourceDataInputStream, os: OutputStream): InfosetParseResult = {
+    val output = new JDOMInfosetOutputter()
+    val pr = parseWithInfosetOutputter(input, output)
+    new JDOMInfosetParseResult(pr, output)
+  }
+
+  def unparse(data: AnyRef, output: DFDL.Output): UnparseResult = {
+    val doc = data.asInstanceOf[org.jdom2.Document]
+    val input = new JDOMInfosetInputter(doc)
+    val ur = unparseWithInfosetInputter(input, output)
+    ur
+  }
+
+  def dataToInfoset(bytes: Array[Byte]): AnyRef = dataToInfoset(new ByteArrayInputStream(bytes))
+
+  def dataToInfoset(stream: InputStream): AnyRef = {
+    val builder = new org.jdom2.input.SAXBuilder() {
+      override protected def createParser(): XMLReader = {
+        val rdr = super.createParser()
+        XMLUtils.setSecureDefaults(rdr)
+        rdr
+      }
+    }
+    val doc = builder.build(stream)
+    doc
+  }
+}
+
+class JDOMInfosetParseResult(parseResult: ParseResult, output: JDOMInfosetOutputter)
+  extends InfosetParseResult(parseResult) {
+
+  override def write(os: OutputStream): Unit = {
+    new org.jdom2.output.XMLOutputter().output(output.getResult, os)
+  }
+}
+
+/**
+ * InfosetType.SCALA_XML
+ */
+case class ScalaXMLInfosetHandler(dataProcessor: DataProcessor)
+  extends InfosetHandler {
+
+  def parse(input: InputSourceDataInputStream, os: OutputStream): InfosetParseResult = {
+    val output = new ScalaXMLInfosetOutputter()
+    val pr = parseWithInfosetOutputter(input, output)
+    new ScalaXMLInfosetParseResult(pr, output)
+  }
+
+  def unparse(data: AnyRef, output: DFDL.Output): UnparseResult = {
+    val node = data.asInstanceOf[scala.xml.Node]
+    val input = new ScalaXMLInfosetInputter(node)
+    val ur = unparseWithInfosetInputter(input, output)
+    ur
+  }
+
+  def dataToInfoset(bytes: Array[Byte]): AnyRef = dataToInfoset(new ByteArrayInputStream(bytes))
+
+  def dataToInfoset(stream: InputStream): AnyRef = {
+    val parser: SAXParser = {
+      val f = DaffodilSAXParserFactory()
+      f.setNamespaceAware(false)
+      val p = f.newSAXParser()
+      p
+    }
+    val node = scala.xml.XML.withSAXParser(parser).load(stream)
+    node
+  }
+}
+
+class ScalaXMLInfosetParseResult(parseResult: ParseResult, output: ScalaXMLInfosetOutputter)
+  extends InfosetParseResult(parseResult) {
+
+  override def write(os: OutputStream): Unit = {
+    val writer = new java.io.OutputStreamWriter(os, StandardCharsets.UTF_8)
+    scala.xml.XML.write(writer, output.getResult, "UTF-8", true, null)
+    writer.flush()
+  }
+}
+
+/**
+ * InfosetType.W3CDOM
+ */
+case class W3CDOMInfosetHandler(dataProcessor: DataProcessor)
+  extends InfosetHandler {
+
+  def parse(input: InputSourceDataInputStream, os: OutputStream): InfosetParseResult = {
+    val output = new W3CDOMInfosetOutputter()
+    val pr = parseWithInfosetOutputter(input, output)
+    new W3CDOMInfosetParseResult(pr, output)
+  }
+
+  def unparse(data: AnyRef, output: DFDL.Output): UnparseResult = {
+    val doc = data.asInstanceOf[ThreadLocal[org.w3c.dom.Document]].get
+    val input = new W3CDOMInfosetInputter(doc)
+    val ur = unparseWithInfosetInputter(input, output)
+    ur
+  }
+
+  def dataToInfoset(bytes: Array[Byte]): AnyRef = {
+    // W3C Documents are not thread safe. So create a ThreadLocal so each
+    // thread gets its own DOM tree. This has the unfortunate downside that we
+    // don't actually convert the XML bytes to DOM until the first call to
+    // unparse(), and we'll parse it multiple times if there are multiple
+    // threads.
+    val doc = new ThreadLocal[org.w3c.dom.Document] {
+      override def initialValue = {
+        val dbf = DocumentBuilderFactory.newInstance()
+        dbf.setNamespaceAware(true)
+        dbf.setFeature(XMLUtils.XML_DISALLOW_DOCTYPE_FEATURE, true)
+        val db = dbf.newDocumentBuilder()
+        db.parse(new ByteArrayInputStream(bytes))
+      }
+    }
+    doc
+  }
+
+  def dataToInfoset(stream: InputStream): AnyRef = dataToInfoset(IOUtils.toByteArray(stream))
+}
+
+class W3CDOMInfosetParseResult(parseResult: ParseResult, output: W3CDOMInfosetOutputter)
+  extends InfosetParseResult(parseResult) {
+
+  override def write(os: OutputStream): Unit = {
+    val tf = TransformerFactory.newInstance()
+    val transformer = tf.newTransformer()
+    val result = new StreamResult(os)
+    val source = new DOMSource(output.getResult)
+    transformer.transform(source, result)
+  }
+}
+
+/**
+ * InfosetType.NULL
+ */
+case class NULLInfosetHandler(dataProcessor: DataProcessor)
+  extends InfosetHandler {
+
+  def parse(input: InputSourceDataInputStream, os: OutputStream): InfosetParseResult = {
+    val output = new NullInfosetOutputter()
+    val pr = parseWithInfosetOutputter(input, output)
+    new InfosetParseResult(pr)
+  }
+
+  def unparse(data: AnyRef, output: DFDL.Output): UnparseResult = {
+    val events = data.asInstanceOf[Array[NullInfosetInputter.Event]]
+    val input = new NullInfosetInputter(events)
+    val ur = unparseWithInfosetInputter(input, output)
+    ur
+  }
+
+  def dataToInfoset(bytes: Array[Byte]): AnyRef = dataToInfoset(new ByteArrayInputStream(bytes))
+
+  def dataToInfoset(stream: InputStream): AnyRef = {
+    val events = NullInfosetInputter.toEvents(stream)
+    events
+  }
+}
+
+/**
+ * InfosetType.SAX
+ */
+case class SAXInfosetHandler(dataProcessor: DataProcessor, forPerformance: Boolean)
+  extends InfosetHandler {
+
+  def parse(input: InputSourceDataInputStream, os: OutputStream): InfosetParseResult = {
+    val contentHandler =
+      if (forPerformance) {
+        new DefaultHandler() // ignores all SAX events
+      } else {
+        new DaffodilParseOutputStreamContentHandler(os, pretty=true)
+      }
+
+    val pr = parseWithSax(input, contentHandler)
+    new InfosetParseResult(pr)
+  }
+
+  def unparse(data: AnyRef, output: DFDL.Output): UnparseResult = {
+    val ur =
+      if (forPerformance) {
+        val xmlReader = new ReplayingXmlReader(data.asInstanceOf[Array[SaxEvent]])
+        unparseWithSax(xmlReader, null, output)
+      } else {
+        val input = data match {
+          case bytes: Array[Byte] => new ByteArrayInputStream(bytes)
+          case is: InputStream => is
+        }
+        val xmlReader = DaffodilSAXParserFactory().newSAXParser.getXMLReader
+        unparseWithSax(xmlReader, input, output)
+      }
+    ur
+  }
+
+  def dataToInfoset(bytes: Array[Byte]): AnyRef = {
+    if (forPerformance) {
+      dataToInfoset(new ByteArrayInputStream(bytes))
+    } else {
+      bytes
+    }
+  }
+
+  def dataToInfoset(stream: InputStream): AnyRef = {
+    if (forPerformance) {
+      val contentHandler = new SavingContentHandler
+      val xmlReader = DaffodilSAXParserFactory().newSAXParser.getXMLReader
+      xmlReader.setContentHandler(contentHandler)
+      xmlReader.setFeature(XMLUtils.SAX_NAMESPACES_FEATURE, true)
+      xmlReader.setFeature(XMLUtils.SAX_NAMESPACE_PREFIXES_FEATURE, true)
+      xmlReader.parse(new InputSource(stream))
+      contentHandler.events.toArray
+    } else {
+      stream
+    }
+  }
+
+  class ReplayingXmlReader(events: Array[SaxEvent]) extends XMLReader {
+    private var _contentHandler: ContentHandler = _
+
+    override def setContentHandler(contentHandler: ContentHandler): Unit = _contentHandler = contentHandler
+
+    override def parse(source: InputSource): Unit = {
+      for (event <- events) event.replay(_contentHandler)
+    }
+
+    // these functions will never be called by the Daffodil XMLReader used for
+    // unparsing with the SAX API, or if they are used it doesn't change how we
+    // replay the events. Just leave them unimplemented or no-ops
+    //$COVERAGE-OFF$
+    override def getContentHandler(): org.xml.sax.ContentHandler = ???
+    override def getDTDHandler(): org.xml.sax.DTDHandler = ???
+    override def getEntityResolver(): org.xml.sax.EntityResolver = ???
+    override def getErrorHandler(): org.xml.sax.ErrorHandler = ???
+    override def getFeature(name: String): Boolean = ???
+    override def getProperty(name: String): Object = ???
+    override def parse(systemId: String): Unit = ???
+    override def setDTDHandler(handler: org.xml.sax.DTDHandler): Unit = {}
+    override def setEntityResolver(resolver: org.xml.sax.EntityResolver): Unit = {}
+    override def setErrorHandler(handler: org.xml.sax.ErrorHandler): Unit = {}
+    override def setFeature(name: String, value: Boolean): Unit = {}
+    override def setProperty(name: String, value: Any): Unit = {}
+    //$COVERAGE-ON$
+  }
+
+  class SavingContentHandler extends ContentHandler {
+    val events = new ArrayBuffer[SaxEvent]()
+
+    override def characters(ch: Array[Char], start: Int, length: Int): Unit =
+      events += SaxEventCharacters(ch.clone(), start, length)
+
+    override def endDocument(): Unit =
+      events += SaxEventEndDocument()
+
+    override def endElement(uri: String, localName: String, qName: String): Unit =
+      events += SaxEventEndElement(uri, localName, qName)
+
+    override def endPrefixMapping(prefix: String): Unit =
+      events += SaxEventEndPrefixMapping(prefix)
+
+    override def ignorableWhitespace(ch: Array[Char], start: Int, length: Int): Unit =
+      events += SaxEventIgnorableWhitespace(ch.clone(), start, length)
+
+    override def processingInstruction(target: String, data: String): Unit =
+      events += SaxEventProcessingInstruction(target, data)
+
+    override def skippedEntity(name: String): Unit =
+      events += SaxEventSkippedEntity(name)
+
+    override def startDocument(): Unit =
+      events += SaxEventStartDocument()
+
+    override def startElement(uri: String, localName: String, qName: String, atts: Attributes): Unit =
+      events += SaxEventStartElement(uri, localName, qName, new AttributesImpl(atts))
+
+    override def startPrefixMapping(prefix: String, uri: String): Unit =
+      events += SaxEventStartPrefixMapping(prefix, uri)
+
+    override def setDocumentLocator(locator: Locator): Unit = {}
+  }
+
+  trait SaxEvent {
+    def replay(h: ContentHandler): Unit
+  }
+
+  case class SaxEventCharacters(ch: Array[Char], start: Int, length: Int) extends SaxEvent {
+    def replay(h: ContentHandler): Unit = h.characters(ch, start, length)
+  }
+
+  case class SaxEventEndDocument() extends SaxEvent {
+    def replay(h: ContentHandler): Unit = h.endDocument()
+  }
+
+  case class SaxEventEndElement(uri: String, localName: String, qName: String) extends SaxEvent {
+    def replay(h: ContentHandler): Unit = h.endElement(uri, localName, qName)
+  }
+
+  case class SaxEventEndPrefixMapping(prefix: String) extends SaxEvent {
+    def replay(h: ContentHandler): Unit = h.endPrefixMapping(prefix)
+  }
+
+  case class SaxEventIgnorableWhitespace(ch: Array[Char], start: Int, length: Int) extends SaxEvent {
+    def replay(h: ContentHandler): Unit = h.ignorableWhitespace(ch, start, length)
+  }
+
+  case class SaxEventProcessingInstruction(target: String, data: String) extends SaxEvent {
+    def replay(h: ContentHandler): Unit = h.processingInstruction(target, data)
+  }
+
+  case class SaxEventSkippedEntity(name: String) extends SaxEvent {
+    def replay(h: ContentHandler): Unit = h.skippedEntity(name)
+  }
+
+  case class SaxEventStartDocument() extends SaxEvent {
+    def replay(h: ContentHandler): Unit = h.startDocument()
+  }
+
+  case class SaxEventStartElement(uri: String, localName: String, qName: String, atts: Attributes) extends SaxEvent {
+    def replay(h: ContentHandler): Unit = h.startElement(uri, localName, qName, atts)
+  }
+
+  case class SaxEventStartPrefixMapping(prefix: String, uri: String) extends SaxEvent {
+    def replay(h: ContentHandler): Unit = h.startPrefixMapping(prefix, uri)
+  }
+}
+
+
+/**
+ * InfosetType.EXI and InfosetType.EXISA
+ */
+object EXIInfosetHandler {
+
+  /** non-schema aware EXI **/
+  def apply(dataProcessor: DataProcessor): InfosetHandler = {
+    val exiFactory = createEXIFactory(None)
+    EXIInfosetHandler(dataProcessor, exiFactory)
+  }
+
+  /** schema aware EXI **/
+  def apply(dataProcessor: DataProcessor, schemaUri: URI): InfosetHandler = {
+    val exiFactory = createEXIFactory(Some(schemaUri))
+    EXIInfosetHandler(dataProcessor, exiFactory)
+  }
+
+  def createEXIFactory(optSchema: Option[URI]): EXIFactory = {
+    val exiFactory = DefaultEXIFactory.newInstance
+    if (optSchema.isDefined) {
+      val grammarFactory = GrammarFactory.newInstance
+      val grammar = grammarFactory.createGrammars(optSchema.get.toString, DFDLCatalogResolver.get)
+      exiFactory.setGrammars(grammar)
+    }
+    exiFactory
+  }
+}
+
+case class EXIInfosetHandler(dataProcessor: DataProcessor, exiFactory: EXIFactory)
+  extends InfosetHandler {
+
+  def parse(input: InputSourceDataInputStream, os: OutputStream): InfosetParseResult = {
+    val exiResult = new EXIResult(exiFactory)
+    exiResult.setOutputStream(os)
+    val contentHandler = exiResult.getHandler
+
+    val pr = parseWithSax(input, contentHandler)
+    new InfosetParseResult(pr)
+  }
+
+  def unparse(data: AnyRef, output: DFDL.Output): UnparseResult = {
+    val input = data match {
+      case bytes: Array[Byte] => new ByteArrayInputStream(bytes)
+      case is: InputStream => is
+    }
+    val exiSource = new EXISource(exiFactory)
+    val xmlReader = exiSource.getXMLReader
+    val ur = unparseWithSax(xmlReader, input, output)
+    ur
+  }
+
+  def dataToInfoset(bytes: Array[Byte]): AnyRef = bytes
+
+  def dataToInfoset(stream: InputStream): AnyRef = stream
+}
diff --git a/daffodil-cli/src/main/scala/org/apache/daffodil/cli/Main.scala b/daffodil-cli/src/main/scala/org/apache/daffodil/cli/Main.scala
new file mode 100644
index 000000000..dfd09e8ae
--- /dev/null
+++ b/daffodil-cli/src/main/scala/org/apache/daffodil/cli/Main.scala
@@ -0,0 +1,1528 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+package org.apache.daffodil.cli
+
+import java.io.File
+import java.io.FileInputStream
+import java.io.FileOutputStream
+import java.io.InputStream
+import java.io.PrintStream
+import java.net.URI
+import java.nio.ByteBuffer
+import java.nio.channels.Channels
+import java.nio.file.Paths
+import java.util.Scanner
+import java.util.concurrent.Executors
+import javax.xml.transform.TransformerFactory
+import javax.xml.transform.TransformerException
+import javax.xml.transform.stream.StreamResult
+
+import scala.concurrent.Await
+import scala.concurrent.ExecutionContext
+import scala.concurrent.Future
+import scala.concurrent.duration.Duration
+import scala.util.matching.Regex
+
+import com.typesafe.config.ConfigFactory
+
+import org.apache.commons.io.output.NullOutputStream
+
+import org.apache.logging.log4j.Level
+import org.apache.logging.log4j.core.config.Configurator
+
+import org.rogach.scallop
+import org.rogach.scallop.ArgType
+import org.rogach.scallop.ScallopOption
+import org.rogach.scallop.ValueConverter
+import org.rogach.scallop.exceptions.GenericScallopException
+
+import org.xml.sax.InputSource
+import org.xml.sax.SAXParseException
+import org.xml.sax.helpers.XMLReaderFactory
+
+import com.siemens.ct.exi.core.EXIFactory
+import com.siemens.ct.exi.core.exceptions.EXIException
+import com.siemens.ct.exi.main.api.sax.EXIResult
+import com.siemens.ct.exi.main.api.sax.EXISource
+
+import org.apache.daffodil.runtime1.api.DFDL
+import org.apache.daffodil.lib.api.DaffodilConfig
+import org.apache.daffodil.lib.api.DaffodilConfigException
+import org.apache.daffodil.lib.api.DaffodilTunables
+import org.apache.daffodil.lib.api.TDMLImplementation
+import org.apache.daffodil.lib.api.URISchemaSource
+import org.apache.daffodil.lib.api.ValidationMode
+import org.apache.daffodil.lib.api.WithDiagnostics
+import org.apache.daffodil.core.compiler.Compiler
+import org.apache.daffodil.core.compiler.InvalidParserException
+import org.apache.daffodil.cli.debugger.CLIDebuggerRunner
+import org.apache.daffodil.runtime1.debugger.DebuggerExitException
+import org.apache.daffodil.runtime1.debugger.InteractiveDebugger
+import org.apache.daffodil.runtime1.debugger.TraceDebuggerRunner
+import org.apache.daffodil.core.dsom.ExpressionCompilers
+import org.apache.daffodil.lib.exceptions.Assert
+import org.apache.daffodil.lib.exceptions.NotYetImplementedException
+import org.apache.daffodil.lib.exceptions.UnsuppressableException
+import org.apache.daffodil.lib.externalvars.Binding
+import org.apache.daffodil.lib.externalvars.BindingException
+import org.apache.daffodil.runtime1.externalvars.ExternalVariablesLoader
+import org.apache.daffodil.io.DataDumper
+import org.apache.daffodil.io.FormatInfo
+import org.apache.daffodil.io.InputSourceDataInputStream
+import org.apache.daffodil.runtime1.processors.DataLoc
+import org.apache.daffodil.runtime1.processors.ExternalVariableException
+import org.apache.daffodil.lib.schema.annotation.props.gen.BitOrder
+import org.apache.daffodil.tdml.Runner
+import org.apache.daffodil.tdml.TDMLException
+import org.apache.daffodil.tdml.TDMLTestNotCompatibleException
+import org.apache.daffodil.runtime1.udf.UserDefinedFunctionFatalErrorException
+import org.apache.daffodil.lib.util.Logger
+import org.apache.daffodil.lib.util.Misc
+import org.apache.daffodil.lib.util.Timer
+import org.apache.daffodil.lib.validation.Validators
+import org.apache.daffodil.lib.xml.QName
+import org.apache.daffodil.lib.xml.RefQName
+
+
+class CLIConf(arguments: Array[String]) extends scallop.ScallopConf(arguments) {
+
+  /**
+   * This is used when the flag is optional and so is its
+   * argument.
+   *
+   * Let's use --debug [file] as an example.
+   *
+   * This optionalValueConverter first determines if the
+   * --debug flag was given.  If it wasn't, Right(None)
+   * is returned.
+   *
+   * If the flag was given we then need to determine
+   * if 'file' was also given.  If file was not given, Right(Some(None))
+   * is returned meaning that the --debug flag was there but 'file'
+   * was not.  If the file was given, Right(Some(Some(conv(file)))) is
+   * returned.  This means that the --debug flag was there as well as
+   * a filename for 'file'.
+   *
+   * conv(mode) simply performs the necessary conversion
+   * of the string to the type [A].
+   *
+   */
+  def optionalValueConverter[A](conv: String => A): scallop.ValueConverter[Option[A]] =
+    new scallop.ValueConverter[Option[A]] {
+
+      // From the Scallop wiki:
+      //
+      // parse is a method, that takes a list of arguments to all option invocations:
+      // for example, "-a 1 2 -a 3 4 5" would produce List(List(1,2),List(3,4,5)).
+      // parse returns Left with error message, if there was an error while parsing
+      // if no option was found, it returns Right(None)
+      // and if option was found, it returns Right(...)
+      def parse(s: List[(String, List[String])]): Either[String, Option[Option[A]]] = {
+        s match {
+          case Nil => Right(None) // flag was not present
+          case (_, Nil) :: Nil => Right(Some(None)) // flag was present but had no parameter
+          case (_, v :: Nil) :: Nil => { // flag was present with a parameter, convert the parameter
+            try {
+              Right(Some(Some(conv(v))))
+            } catch {
+              case s: scala.util.control.ControlThrowable => throw s
+              case u: UnsuppressableException => throw u
+              case e: Exception => {
+                Left(e.getMessage())
+              }
+            }
+          }
+          case _ => Left("you should provide no more than one argument for this option") // Error because we expect there to be at most one flag
+        }
+      }
+      val argType = scallop.ArgType.LIST
+      override def argFormat(name: String): String = "[" + name + "]"
+    }
+
+  implicit def validateConverter = singleArgConverter[ValidationMode.Type]((s: String) => {
+    import ValidatorPatterns._
+    s match {
+      case "on" => ValidationMode.Full
+      case "limited" => ValidationMode.Limited
+      case "off" => ValidationMode.Off
+      case DefaultArgPattern(name, arg) if Validators.isRegistered(name) =>
+        val config = if(arg.endsWith(".conf")) ConfigFactory.parseFile(new File(arg)) else ConfigFactory.parseString(s"$name=$arg")
+        ValidationMode.Custom(Validators.get(name).make(config))
+      case NoArgsPattern(name) if Validators.isRegistered(name) =>
+        ValidationMode.Custom(Validators.get(name).make(ConfigFactory.empty))
+      case _ => throw new Exception("Unrecognized ValidationMode %s.  Must be 'on', 'limited', 'off', or name of spi validator.".format(s))
+    }
+  })
+
+  implicit def infosetTypeConverter = singleArgConverter[InfosetType.Type]((s: String) => {
+    try {
+      InfosetType.withName(s.toLowerCase)
+    } catch {
+      case _: NoSuchElementException => throw new Exception("Unrecognized infoset type: %s.  Must be one of %s".format(s, InfosetType.values.mkString(", ")))
+    }
+  })
+
+  implicit def implementationConverter = singleArgConverter[TDMLImplementation]((s: String) => {
+    val optImplementation = TDMLImplementation.optionStringToEnum("implementation", s)
+    if (!optImplementation.isDefined) {
+      throw new Exception("Unrecognized TDML implementation '%s'.  Must be one of %s"
+        .format(s, TDMLImplementation.values.mkString(", ")))
+    }
+    optImplementation.get
+  })
+
+  def qnameConvert(s: String): RefQName = {
+    val eQN = QName.refQNameFromExtendedSyntax(s)
+    eQN.get
+  }
+
+  def singleArgConverter[A](conv: String => A) = new ValueConverter[A] {
+    def parse(s: List[(String, List[String])]) = {
+      s match {
+        case (_, i :: Nil) :: Nil =>
+          try {
+            Right(Some(conv(i)))
+          } catch {
+            case s: scala.util.control.ControlThrowable => throw s
+            case u: UnsuppressableException => throw u
+            case e: Throwable => Left(e.getMessage())
+          }
+        case Nil => Right(None)
+        case _ => Left("you should provide exactly one argument for this option")
+      }
+    }
+    val argType = ArgType.SINGLE
+  }
+
+  implicit def rootNSConverter = org.rogach.scallop.singleArgConverter[RefQName](qnameConvert _)
+
+  implicit def fileResourceURIConverter = singleArgConverter[URI]((s: String) => {
+    val file = new File(s)
+    val uri =
+      if (file.isFile()) {
+        Some(file.toURI)
+      } else {
+        Misc.getResourceRelativeOption(s, None)
+      }
+    uri.getOrElse(throw new Exception("Could not find file or resource %s" format s))
+  })
+
+  printedName = "Apache Daffodil"
+
+  val width = 87
+  helpWidth(width)
+
+  errorMessageHandler = { message =>
+    val msg =
+      if (message.indexOf("Wrong format for option 'schema'") >= 0) {
+        // the 'wrong format' error only occurs on --schema when options are
+        // provided after the trailing arg, so let's give a more helpful error
+        // message
+        "Options are not allowed after a trailing argument"
+      } else {
+        message
+      }
+    throw new GenericScallopException(msg)
+  }
+
+  banner("""|Usage: daffodil [GLOBAL_OPTS] <subcommand> [SUBCOMMAND_OPTS]
+            |
+            |Global Options:""".stripMargin)
+
+  footer("""|
+            |Run 'daffodil <subcommand> --help' for subcommand specific options""".stripMargin)
+
+  version({
+    val version = Misc.getDaffodilVersion
+    val strVers = "%s %s".format(printedName, version)
+    strVers
+  })
+
+  shortSubcommandsHelp()
+
+  // Global Options
+  val debug = opt[Option[String]](argName = "file", descr = "Enable the interactive debugger. Optionally, read initial debugger commands from [file] if provided.")(optionalValueConverter[String](a => a))
+  val trace = opt[Boolean](descr = "Run this program with verbose trace output")
+  val verbose = tally(descr = "Increment verbosity level, one level for each -v")
+  val version = opt[Boolean](descr = "Show Daffodil's version")
+
+  // Parse Subcommand Options
+  object parse extends scallop.Subcommand("parse") {
+    banner("""|Usage: daffodil parse (-s <schema> [-r <root>] | -P <parser>)
+              |                      [-c <file>] [-D<variable>=<value>...] [-I <infoset_type>]
+              |                      [-o <output>] [--stream] [-T<tunable>=<value>...] [-V <mode>]
+              |                      [infile]
+              |
+              |Parse a file, using either a DFDL schema or a saved parser
+              |
+              |Parse Options:""".stripMargin)
+
+    descr("Parse data to a DFDL infoset")
+    helpWidth(width)
+
+    val config = opt[File](short = 'c', argName = "file", descr = "XML file containing configuration items")
+    val vars = props[String](name = 'D', keyName = "variable", valueName = "value", descr = "Variables to be used when parsing. Can be prefixed with {namespace}.")
+    val infosetType = opt[InfosetType.Type](short = 'I', argName = "infoset_type", descr = "Infoset type to output. Type can be: " + InfosetType.values.mkString(", ") + ". Defaults to 'xml'.", default = Some(InfosetType.XML))
+    val output = opt[String](argName = "file", descr = "Output file to write infoset to. If not given or is -, infoset is written to stdout.")
+    val parser = opt[File](short = 'P', argName = "file", descr = "Previously saved parser to reuse")
+    val path = opt[String](argName = "path", descr = "Path from root element to node from which to start parsing", hidden = true)
+    val rootNS = opt[RefQName]("root", argName = "node", descr = "Root element to use. Can be prefixed with {namespace}. Must be a top-level element. Defaults to first top-level element of DFDL schema.")
+    val schema = opt[URI]("schema", argName = "file", descr = "DFDL schema to use to create parser")(fileResourceURIConverter)
+    val stream = toggle(noshort = true, default = Some(false), descrYes = "When left over data exists, parse again with remaining data, separating infosets by a NUL character", descrNo = "Stop after the first parse, throwing an error if left over data exists")
+    val tunables = props[String](name = 'T', keyName = "tunable", valueName = "value", descr = "Tunable configuration options to change Daffodil's behavior")
+    val validate: ScallopOption[ValidationMode.Type] = opt[ValidationMode.Type](short = 'V', default = Some(ValidationMode.Off), argName = "mode", descr = "Validation mode. Use 'on', 'limited', 'off', or a validator plugin name.")
+
+    val infile = trailArg[String](required = false, descr = "Input file to parse. If not specified, or a value of -, reads from stdin.")
+
+    requireOne(schema, parser) // must have one of --schema or --parser
+    conflicts(parser, List(rootNS)) // if --parser is provided, cannot also provide --root
+    validateFileIsFile(config) // --config must be a file that exists
+
+    validateOpt(debug, infile) {
+      case (Some(_), Some("-")) | (Some(_), None) => Left("Input must not be stdin during interactive debugging")
+      case _ => Right(Unit)
+    }
+
+    validateOpt(parser, validate) {
+      case (Some(_), Some(ValidationMode.Full)) => Left("The validation mode must be 'limited' or 'off' when using a saved parser.")
+      case _ => Right(Unit)
+    }
+
+    validateOpt(infosetType, stream, schema) {
+      case (Some(InfosetType.EXI), Some(true), _) => Left("Streaming mode is not currently supported with EXI infosets.")
+      case (Some(InfosetType.EXISA), Some(true), _) => Left("Streaming mode is not currently supported with EXI infosets.")
+      case (Some(InfosetType.EXISA), _, None) => Left("A schema must be specified to use schema-aware compression with EXI")
+      case _ => Right(Unit)
+    }
+  }
+
+  // Unparse Subcommand Options
+  object unparse extends scallop.Subcommand("unparse") {
+    banner("""|Usage: daffodil unparse (-s <schema> [-r <root>] | -P <parser>)
+              |                        [-c <file>] [-D<variable>=<value>...] [-I <infoset_type>]
+              |                        [-o <output>] [--stream] [-T<tunable>=<value>...] [-V <mode>]
+              |                        [infile]
+              |
+              |Unparse an infoset file, using either a DFDL schema or a saved parser
+              |
+              |Unparse Options:""".stripMargin)
+
+    descr("Unparse a DFDL infoset")
+    helpWidth(width)
+
+    val config = opt[File](short = 'c', argName = "file", descr = "XML file containing configuration items")
+    val vars = props[String](name = 'D', keyName = "variable", valueName = "value", descr = "Variables to be used when parsing. Can be prefixed with {namespace}.")
+    val infosetType = opt[InfosetType.Type](short = 'I', argName = "infoset_type", descr = "Infoset type to output. Type can be: " + InfosetType.values.mkString(", ") + ". Defaults to 'xml'.", default = Some(InfosetType.XML))
+    val output = opt[String](argName = "file", descr = "Output file to write data to. If not given or is -, data is written to stdout.")
+    val parser = opt[File](short = 'P', argName = "file", descr = "Previously saved parser to reuse")
+    val path = opt[String](argName = "path", descr = "Path from root element to node from which to start unparsing", hidden = true)
+    val rootNS = opt[RefQName]("root", argName = "node", descr = "Root element to use. Can be prefixed with {namespace}. Must be a top-level element. Defaults to first top-level element of DFDL schema.")
+    val schema = opt[URI]("schema", argName = "file", descr = "DFDL schema to use to create parser")(fileResourceURIConverter)
+    val stream = toggle(noshort = true, default = Some(false), descrYes = "Split the input data on the NUL character, and unparse each chuck separately", descrNo = "Treat the entire input data as one infoset")
+    val tunables = props[String](name = 'T', keyName = "tunable", valueName = "value", descr = "Tunable configuration options to change Daffodil's behavior")
+    val validate: ScallopOption[ValidationMode.Type] = opt[ValidationMode.Type](short = 'V', default = Some(ValidationMode.Off), argName = "mode", descr = "Validation mode. Use 'on', 'limited', 'off', or a validator plugin name.")
+
+    val infile = trailArg[String](required = false, descr = "Input file to unparse. If not specified, or a value of -, reads from stdin.")
+
+    requireOne(schema, parser) // must have one of --schema or --parser
+    conflicts(parser, List(rootNS)) // if --parser is provided, cannot also provide --root
+    validateFileIsFile(config) // --config must be a file that exists
+
+    validateOpt(debug, infile) {
+      case (Some(_), Some("-")) | (Some(_), None) => Left("Input must not be stdin during interactive debugging")
+      case _ => Right(Unit)
+    }
+
+    validateOpt(infosetType, stream, schema) {
+      case (Some(InfosetType.EXI), Some(true), _) => Left("Streaming mode is not currently supported with EXI infosets.")
+      case (Some(InfosetType.EXISA), Some(true), _) => Left("Streaming mode is not currently supported with EXI infosets.")
+      case (Some(InfosetType.EXISA), _, None) => Left("A schema must be specified to use schema-aware compression with EXI")
+      case _ => Right(Unit)
+    }
+  }
+
+  // Save Parser Subcommand Options
+  object save extends scallop.Subcommand("save-parser") {
+    banner("""|Usage: daffodil save-parser -s <schema> [-r <root>]
+              |                            [-c <file>] [-D<variable>=<value>...] [-T<tunable>=<value>...]
+              |                            [outfile]
+              |
+              |Create and save a parser using a DFDL schema
+              |
+              |Save Parser Options:""".stripMargin)
+
+    descr("Save a Daffodil parser for reuse")
+    helpWidth(width)
+
+    val config = opt[File](short = 'c', argName = "file", descr = "XML file containing configuration items")
+    val vars = props[String](name = 'D', keyName = "variable", valueName = "value", descr = "Variables to be used when parsing. Can be prefixed with {namespace}.")
+    val path = opt[String](argName = "path", descr = "Path from root element to node from which to start parsing", hidden = true)
+    val rootNS = opt[RefQName]("root", argName = "node", descr = "Root element to use. Can be prefixed with {namespace}. Must be a top-level element. Defaults to first top-level element of DFDL schema.")
+    val schema = opt[URI]("schema", required = true, argName = "file", descr = "DFDL schema to use to create parser")(fileResourceURIConverter)
+    val tunables = props[String](name = 'T', keyName = "tunable", valueName = "value", descr = "Tunable configuration options to change Daffodil's behavior")
+
+    val outfile = trailArg[String](required = false, descr = "Output file to save parser to. If not specified, or a value of -, saves to stdout.")
+
+    requireOne(schema) // --schema must be provided
+    validateFileIsFile(config) // --config must be a file that exists
+  }
+
+  // Test Subcommand Options
+  object test extends scallop.Subcommand("test") {
+    banner("""|Usage: daffodil test [-I <implementation>] [-l] [-r] [-i] <tdmlfile> [testnames...]
+              |
+              |List or execute tests in a TDML file
+              |
+              |Test Options:""".stripMargin)
+
+    descr("List or execute TDML tests")
+    helpWidth(width)
+
+    val implementation = opt[TDMLImplementation](short = 'I', argName = "implementation",
+      descr = "Implementation to run TDML tests. Choose one of %s. Defaults to %s."
+        .format(TDMLImplementation.values.mkString(", "), TDMLImplementation.Daffodil.toString),
+      default = None)
+    val info = tally(descr = "Increment test result information output level, one level for each -i")
+    val list = opt[Boolean](descr = "Show names and descriptions instead of running test cases")
+    val regex = opt[Boolean](descr = "Treat <testnames...> as regular expressions")
+    val tdmlfile = trailArg[String](required = true, descr = "Test Data Markup Language (TDML) file")
+    val testnames = trailArg[List[String]](required = false, descr = "Name(s) of test cases in tdmlfile. If not given, all tests in tdmlfile are run.")
+  }
+
+  // Performance Subcommand Options
+  object performance extends scallop.Subcommand("performance") {
+    banner("""|Usage: daffodil performance (-s <schema> [-r <root>] | -P <parser>)
+              |                            [-c <file>] [-D<variable>=<value>...] [-I <infoset_type>]
+              |                            [-N <number>] [-t <threads>] [-T<tunable>=<value>...]
+              |                            [-u] [-V <mode>]
+              |                            <infile>
+              |
+              |Run a performance test, using either a DFDL schema or a saved parser
+              |
+              |Performance Options:""".stripMargin)
+
+    descr("Run performance test")
+    helpWidth(width)
+
+    val config = opt[File](short = 'c', argName = "file", descr = "XML file containing configuration items")
+    val vars = props[String](name = 'D', keyName = "variable", valueName = "value", descr = "Variables to be used when parsing. Can be prefixed with {namespace}.")
+    val infosetType = opt[InfosetType.Type](short = 'I', argName = "infoset_type", descr = "Infoset type to output. Type can be: " + InfosetType.values.mkString(", ") + ". Defaults to 'xml'.", default = Some(InfosetType.XML))
+    val number = opt[Int](short = 'N', argName = "number", default = Some(1), descr = "Total number of files to process. Defaults to 1.")
+    val parser = opt[File](short = 'P', argName = "file", descr = "Previously saved parser to reuse")
+    val path = opt[String](argName = "path", descr = "Path from root element to node from which to start parsing or unparsing", hidden = true)
+    val rootNS = opt[RefQName]("root", argName = "node", descr = "Root element to use. Can be prefixed with {namespace}. Must be a top-level element. Defaults to first top-level element of DFDL schema.")
+    val schema = opt[URI]("schema", argName = "file", descr = "DFDL schema to use to create parser")(fileResourceURIConverter)
+    val threads = opt[Int](short = 't', argName = "threads", default = Some(1), descr = "Number of threads to use. Defaults to 1.")
+    val tunables = props[String](name = 'T', keyName = "tunable", valueName = "value", descr = "Tunable configuration options to change Daffodil's behavior")
+    val unparse = opt[Boolean](default = Some(false), descr = "Perform unparse instead of parse for performance test")
+    val validate: ScallopOption[ValidationMode.Type] = opt[ValidationMode.Type](short = 'V', default = Some(ValidationMode.Off), argName = "mode", descr = "Validation mode. Use 'on', 'limited', 'off', or a validator plugin name.")
+
+    val infile = trailArg[String](required = true, descr = "Input file or directory containing input files to parse or unparse")
+
+    requireOne(schema, parser) // must have one of --schema or --parser
+    conflicts(parser, List(rootNS)) // if --parser is provided, cannot also provide --root
+    validateFileIsFile(config) // --config must be a file that exists
+
+    validateOpt(infosetType, schema) {
+      case (Some(InfosetType.EXISA), None) => Left("A schema must be specified to use schema-aware compression with EXI")
+      case _ => Right(Unit)
+    }
+  }
+
+  // Generate Subcommand Options
+  object generate extends scallop.Subcommand("generate") {
+    descr("Generate <language> code from a DFDL schema")
+
+    banner("""|Usage: daffodil [GLOBAL_OPTS] generate <language> [SUBCOMMAND_OPTS]
+              |""".stripMargin)
+    shortSubcommandsHelp()
+    footer("""|
+              |Run 'daffodil generate <language> --help' for subcommand specific options""".stripMargin)
+
+    // Takes language by name so we can pass it to scallop.Subcommand and interpolate it into
+    // strings without getting a runtime java.lang.ClassCastException on Scala 2.12 (class
+    // scala.collection.mutable.WrappedArray$ofRef cannot be cast to class java.lang.String)
+    // @param languageArg Typed on command line & passed to Compiler.forLanguage
+    // @param languageName Formal descriptive name of language for help/usage messages
+    class LanguageConf(languageArg: => String, val languageName: String)
+      extends scallop.Subcommand(languageArg) {
+
+      val language = languageArg
+      banner(s"""|Usage: daffodil generate $language -s <schema> [-r <root>]
+                 |                              [-c <file>] [-T<tunable>=<value>...]
+                 |                              [outdir]
+                 |
+                 |Generate $languageName code from a DFDL schema to parse or unparse data
+                 |
+                 |Generate Options:""".stripMargin)
+      descr(s"Generate $languageName code from a DFDL schema")
+      helpWidth(width)
+
+      val config = opt[File](short = 'c', argName = "file", descr = "XML file containing configuration items")
+      val rootNS = opt[RefQName]("root", argName = "node", descr = "Root element to use. Can be prefixed with {namespace}. Must be a top-level element. Defaults to first top-level element of DFDL schema.")
+      val schema = opt[URI]("schema", required = true, argName = "file", descr = "DFDL schema to use to create parser")(fileResourceURIConverter)
+      val tunables = props[String](name = 'T', keyName = "tunable", valueName = "value", descr = "Tunable configuration options to change Daffodil's behavior")
+
+      val outdir = trailArg[String](required = false, descr = s"Output directory in which to create '$language' subdirectory. If not specified, uses current directory.")
+
+      requireOne(schema) // --schema must be provided
+      validateFileIsFile(config) // --config must be a file that exists
+    }
+
+    object c extends LanguageConf("c", "C")
+
+    addSubcommand(c)
+
+    requireSubcommand()
+  }
+
+  // Encode or decode EXI Subcommand Options
+  object exi extends scallop.Subcommand("exi") {
+    banner("""|Usage: daffodil exi [-d] [-s <schema>] [-o <output>] [infile]
+              |
+              |Encode/decode an XML file with EXI. If a schema is specified, it will use schema aware encoding/decoding.
+              |
+              |EncodeEXI Options:""".stripMargin)
+
+    descr("Encode an XML file with EXI")
+    helpWidth(width)
+
+    val output = opt[String](argName = "file", descr = "Output file to write the encoded/decoded file to. If not given or is -, data is written to stdout.")
+    val schema = opt[URI]("schema", argName = "file", descr = "DFDL schema to use for schema aware encoding/decoding.")(fileResourceURIConverter)
+    val decode = opt[Boolean](default = Some(false), descr = "Decode input file from EXI to XML.")
+    val infile = trailArg[String](required = false, descr = "Input XML file to encode. If not specified, or a value of -, reads from stdin.")
+  }
+
+  addSubcommand(parse)
+  addSubcommand(unparse)
+  addSubcommand(save)
+  addSubcommand(test)
+  addSubcommand(performance)
+  addSubcommand(generate)
+  addSubcommand(exi)
+
+  mutuallyExclusive(trace, debug) // cannot provide both --trace and --debug
+  requireSubcommand()
+
+  verify()
+}
+
+object ValidatorPatterns {
+  val NoArgsPattern: Regex = "(.+?)".r.anchored
+  val DefaultArgPattern: Regex = "(.+?)=(.+)".r.anchored
+}
+
+object Main {
+
+  var STDIN: InputStream = System.in
+  var STDOUT: PrintStream = System.out
+  var STDERR: PrintStream = System.err
+
+  /**
+   * Allows changing where the input/output of the CLI goes to. This defaults
+   * to the normal System.in/out/err, but can be changed to support easier
+   * testing. This is not thread safe, but this Main object is not really
+   * thread safe anyways if it is configured to read/write from stdin/stout.
+   */
+  def setInputOutput(in: InputStream, out: PrintStream, err: PrintStream): Unit = {
+    STDIN = in
+    STDOUT = out
+    STDERR = err
+  }
+
+  val traceCommands = Seq(
+    "display info parser",
+    "display info data",
+    "display info infoset",
+    "display info diff",
+    "trace")
+
+  /* indents a multi-line string */
+  def indent(str: String, pad: Int): String = {
+    val lines = str.split("\n")
+    val prefix = " " * pad
+    val indented = lines.map(prefix + _)
+    indented.mkString("\n")
+  }
+
+
+  /**
+   * Overrides bindings specified via the configuration file with those
+   * specified via the -D command.
+   *
+   * @param bindings A sequence of Bindings (external variables)
+   * @param bindingsToOverride The sequence of Bindings that could be overridden.
+   */
+  def overrideBindings(bindings: Seq[Binding], bindingsToOverride: Seq[Binding]) = {
+    val inBoth = bindings.intersect(bindingsToOverride).distinct
+    val bindingsMinusBoth = bindings.diff(inBoth)
+    val bindingsToOverrideMinusBoth = bindingsToOverride.diff(inBoth)
+    val bindingsWithCorrectValues = bindings.filter(b => inBoth.exists(p => b.hashCode == p.hashCode))
+
+    val bindingsMinusUpdates = bindingsMinusBoth.union(bindingsToOverrideMinusBoth)
+    val bindingsWithUpdates = bindingsMinusUpdates.union(bindingsWithCorrectValues)
+
+    bindingsWithUpdates
+  }
+
+  def displayDiagnostics(pr: WithDiagnostics): Unit = {
+    pr.getDiagnostics.foreach { d =>
+      if (d.isError) {
+        Logger.log.error(d.getMessage())
+      } else {
+        Logger.log.warn(d.getMessage())
+      }
+    }
+  }
+
+  /**
+   * Retrieves all external variables specified via the command line interface.
+   *
+   * @param vars The individual variables input via the command line using the -D command.
+   * @param optDafConfig DaffodilConfig object from config file (if any)
+   */
+  def combineExternalVariables(vars: Map[String, String], optDafConfig: Option[DaffodilConfig]): Seq[Binding] = {
+    val configFileVars: Seq[Binding] = optDafConfig.map{ _.externalVariableBindings }.getOrElse(Seq())
+
+    val individualVars = ExternalVariablesLoader.mapToBindings(vars)
+
+    val bindings = overrideBindings(individualVars, configFileVars)
+    bindings
+  }
+
+  def createProcessorFromParser(savedParser: File, path: Option[String], mode: ValidationMode.Type) = {
+    try {
+      val compiler = Compiler()
+      val processor = Timer.getResult("reloading", compiler.reload(savedParser))
+      // note that we do not display the diagnostics that were saved as part of the
+      // saved processor. Those are from compile time, are all warnings, and
+      // are just noise in a production system where we're reloading a parser.
+      if (!processor.isError) {
+        Some(processor.withValidationMode(mode))
+      } else {
+        None
+      }
+    } catch {
+      case e: InvalidParserException => {
+        Logger.log.error(e.getMessage())
+        None
+      }
+    }
+  }
+
+  def withDebugOrTrace(proc: DFDL.DataProcessor, conf: CLIConf): DFDL.DataProcessor = {
+    (conf.trace() || conf.debug.isDefined) match {
+      case true => {
+        val runner =
+          if (conf.trace()) {
+            new TraceDebuggerRunner(STDOUT)
+          } else {
+            if (System.console == null) {
+              Logger.log.warn(s"Using --debug on a non-interactive console may result in display issues")
+            }
+            conf.debug() match {
+              case Some(f) => new CLIDebuggerRunner(new File(f), STDIN, STDOUT)
+              case None => new CLIDebuggerRunner(STDIN, STDOUT)
+            }
+          }
+        val id = new InteractiveDebugger(runner, ExpressionCompilers)
+        proc.withDebugger(id).withDebugging(true)
+      }
+      case false => proc
+    }
+  }
+
+  def createProcessorFromSchema(schema: URI, rootNS: Option[RefQName], path: Option[String],
+    tunablesMap: Map[String, String],
+    mode: ValidationMode.Type): Option[DFDL.DataProcessor] = {
+    val compiler = {
+      val c = Compiler().withTunables(tunablesMap)
+      rootNS match {
+        case None => c
+        case Some(RefQName(_, root, ns)) => c.withDistinguishedRootNode(root, ns.toStringOrNullIfNoNS)
+      }
+    }
+
+    // Wrap timing around the whole of compilation
+    //
+    // compilation extends from the call to compile
+    // to also include the call to pf.onPath. (which is the last phase
+    // of compilation, where it asks for the parser)
+    //
+    val schemaSource = URISchemaSource(schema)
+    val res = Timer.getResult("compiling", {
+      val processorFactory = compiler.compileSource(schemaSource)
+      if (!processorFactory.isError) {
+        val processor = processorFactory.onPath(path.getOrElse("/")).withValidationMode(mode)
+        displayDiagnostics(processor)
+        if (processor.isError) {
+          None
+        } else {
+          Some(processor)
+        }
+      } else {
+        displayDiagnostics(processorFactory)
+        None
+      }
+    })
+    res
+  }
+
+  def createGeneratorFromSchema(schema: URI, rootNS: Option[RefQName], tunables: Map[String, String],
+                                language: String): Option[DFDL.CodeGenerator] = {
+    val compiler = {
+      val c = Compiler().withTunables(tunables)
+      rootNS match {
+        case None => c
+        case Some(RefQName(_, root, ns)) => c.withDistinguishedRootNode(root, ns.toStringOrNullIfNoNS)
+      }
+    }
+
+    val schemaSource = URISchemaSource(schema)
+    val cg = Timer.getResult("compiling", {
+      val processorFactory = compiler.compileSource(schemaSource)
+      if (!processorFactory.isError) {
+        val generator = processorFactory.forLanguage(language)
+        displayDiagnostics(generator)
+        Some(generator)
+      } else {
+        displayDiagnostics(processorFactory)
+        None
+      }
+    })
+    cg
+  }
+
+  // write blobs to $PWD/daffodil-blobs/*.bin
+  val blobDir = Paths.get(System.getProperty("user.dir"), "daffodil-blobs")
+  val blobSuffix = ".bin"
+
+
+  def setLogLevel(verbose: Int): Unit = {
+    val verboseLevel = verbose match {
+      case 0 => Level.WARN
+      case 1 => Level.INFO
+      case 2 => Level.DEBUG
+      case _ => Level.TRACE
+    }
+    Configurator.setLevel("org.apache.daffodil", verboseLevel)
+  }
+
+  def runIgnoreExceptions(arguments: Array[String]): ExitCode.Value = {
+    val conf = new CLIConf(arguments)
+
+    setLogLevel(conf.verbose())
+
+    val ret: ExitCode.Value = conf.subcommand match {
+
+      case Some(conf.parse) => {
+        val parseOpts = conf.parse
+
+        val validate = parseOpts.validate.toOption.get
+
+        val optDafConfig = parseOpts.config.toOption.map{ DaffodilConfig.fromFile(_) }
+
+        val processor: Option[DFDL.DataProcessor] = {
+          if (parseOpts.parser.isDefined) {
+            createProcessorFromParser(parseOpts.parser(), parseOpts.path.toOption, validate)
+          } else {
+            val tunables = DaffodilTunables.configPlusMoreTunablesMap(parseOpts.tunables, optDafConfig)
+            createProcessorFromSchema(parseOpts.schema(), parseOpts.rootNS.toOption, parseOpts.path.toOption, tunables, validate)
+          }
+        }.map{ _.withExternalVariables(combineExternalVariables(parseOpts.vars, optDafConfig)) }
+         .map{ _.withValidationMode(validate) }
+         .map{ withDebugOrTrace(_, conf) }
+
+        val rc = processor match {
+          case None => ExitCode.UnableToCreateProcessor
+          case Some(processor) => {
+            Assert.invariant(!processor.isError)
+            val input = parseOpts.infile.toOption match {
+              case Some("-") | None => STDIN
+              case Some(file) => {
+                val f = new File(file)
+                new FileInputStream(f)
+              }
+            }
+            val inStream = InputSourceDataInputStream(input)
+
+            val output = parseOpts.output.toOption match {
+              case Some("-") | None => STDOUT
+              case Some(file) => new FileOutputStream(file)
+            }
+
+            val infosetType = parseOpts.infosetType.toOption.get
+            val infosetHandler = InfosetType.getInfosetHandler(
+              parseOpts.infosetType.toOption.get,
+              processor,
+              parseOpts.schema.toOption,
+              forPerformance = false)
+
+            var lastParseBitPosition = 0L
+            var keepParsing = true
+            var exitCode = ExitCode.Success
+
+            while (keepParsing) {
+              val infosetResult = Timer.getResult("parsing", infosetHandler.parse(inStream, output))
+              val parseResult = infosetResult.parseResult
+
+              val finfo = parseResult.resultState.asInstanceOf[FormatInfo]
+              val loc = parseResult.resultState.currentLocation.asInstanceOf[DataLoc]
+              displayDiagnostics(parseResult)
+
+              if (parseResult.isProcessingError || parseResult.isValidationError) {
+                keepParsing = false
+                exitCode = ExitCode.ParseError
+              } else {
+                // Success. Some InfosetHandlers do not write the result to the output
+                // stream when parsing (e.g. they just create Scala objects). Since we are
+                // parsing, ask the InfosetHandler to serialize the result if it has an
+                // implementation so that the user can see an XML represenation regardless
+                // of the infoset type.
+                infosetResult.write(output)
+                output.flush()
+
+                if (!inStream.hasData()) {
+                  // not even 1 more bit is available.
+                  // do not try to keep parsing, nothing left to parse
+                  keepParsing = false
+                } else {
+                  // There is more data available.
+                  if (parseOpts.stream.toOption.get) {
+                    // Streaming mode
+                    if (lastParseBitPosition == loc.bitPos0b) {
+                      // this parse consumed no data, that means this would get
+                      // stuck in an infinite loop if we kept trying to stream,
+                      // so we need to quit
+                      val remainingBits =
+                        if (loc.bitLimit0b.isDefined) {
+                          (loc.bitLimit0b.get - loc.bitPos0b).toString
+                        } else {
+                          "at least " + (inStream.inputSource.bytesAvailable * 8)
+                        }
+                      Logger.log.error(s"Left over data after consuming 0 bits while streaming. Stopped after consuming ${loc.bitPos0b} bit(s) with ${remainingBits} bit(s) remaining.")
+                      keepParsing = false
+                      exitCode = ExitCode.LeftOverData
+                    } else {
+                      // last parse did consume data, and we know there is more
+                      // data to come, so try to parse again.
+                      lastParseBitPosition = loc.bitPos0b
+                      keepParsing = true
+                      output.write(0) // NUL-byte separates streams
+                    }
+                  } else {
+                    // not streaming mode, and there is more data available,
+                    // so show left over data warning
+                    val Dump = new DataDumper
+                    val bitsAlreadyConsumed = loc.bitPos0b % 8
+                    val firstByteString = if (bitsAlreadyConsumed != 0) {
+                      val bitsToDisplay = 8 - bitsAlreadyConsumed
+                      val pbp = inStream.inputSource.position + 1
+                      val firstByteBitArray = inStream.getByteArray(bitsToDisplay, finfo)
+                      val fbs = firstByteBitArray(0).toBinaryString.takeRight(8).reverse.padTo(8, '0').reverse
+                      val bits = if (finfo.bitOrder == BitOrder.MostSignificantBitFirst) {
+                        "x" * bitsAlreadyConsumed + fbs.dropRight(bitsAlreadyConsumed)
+                      } else {
+                        fbs.takeRight(bitsToDisplay) + "x" * bitsAlreadyConsumed
+                      }
+                      val dumpString = f"\nLeft over data starts with partial byte. Left over data (Binary) at byte $pbp is: (0b$bits)"
+                      dumpString
+                    } else ""
+                    val curBytePosition1b = inStream.inputSource.position + 1
+                    val bytesAvailable = inStream.inputSource.bytesAvailable
+                    val bytesLimit = math.min(8, bytesAvailable).toInt
+                    val destArray = new Array[Byte](bytesLimit)
+                    val destArrayFilled = inStream.inputSource.get(destArray, 0, bytesLimit)
+                    val dumpString = if (destArrayFilled) Dump.dump(Dump.TextOnly(Some("utf-8")), 0, destArray.length * 8, ByteBuffer.wrap(destArray), includeHeadingLine = false).mkString("\n") else ""
+                    val dataText = if (destArrayFilled) s"\nLeft over data (UTF-8) starting at byte ${curBytePosition1b} is: (${dumpString}...)" else ""
+                    val dataHex = if (destArrayFilled) s"\nLeft over data (Hex) starting at byte ${curBytePosition1b} is: (0x${destArray.map { a => f"$a%02x" }.mkString}...)" else ""
+                    val remainingBits =
+                      if (loc.bitLimit0b.isDefined) {
+                        (loc.bitLimit0b.get - loc.bitPos0b).toString
+                      } else {
+                        "at least " + (bytesAvailable * 8)
+                      }
+                    val leftOverDataMessage = s"Left over data. Consumed ${loc.bitPos0b} bit(s) with ${remainingBits} bit(s) remaining." + firstByteString + dataHex + dataText
+                    Logger.log.error(leftOverDataMessage)
+                    keepParsing = false
+                    exitCode = ExitCode.LeftOverData
+                  }
+                }
+              }
+            }
+          exitCode
+          }
+        }
+        rc
+      }
+
+      case Some(conf.performance) => {
+        val performanceOpts = conf.performance
+
+        val validate = performanceOpts.validate.toOption.get
+
+        val optDafConfig = performanceOpts.config.toOption.map{ DaffodilConfig.fromFile(_) }
+
+        val processor = {
+          if (performanceOpts.parser.isDefined) {
+            createProcessorFromParser(performanceOpts.parser(), performanceOpts.path.toOption, validate)
+          } else {
+            val tunables = DaffodilTunables.configPlusMoreTunablesMap(performanceOpts.tunables, optDafConfig)
+            createProcessorFromSchema(performanceOpts.schema(), performanceOpts.rootNS.toOption, performanceOpts.path.toOption, tunables, validate)
+          }
+        }.map{ _.withExternalVariables(combineExternalVariables(performanceOpts.vars, optDafConfig)) }
+         .map{ _.withValidationMode(validate) }
+
+        val rc: ExitCode.Value = processor match {
+          case None => ExitCode.UnableToCreateProcessor
+          case Some(processor) => {
+            val infile = new java.io.File(performanceOpts.infile())
+
+            val files = {
+              if (infile.isDirectory()) {
+                infile.listFiles.filter(!_.isDirectory)
+              } else {
+                Array(infile)
+              }
+            }
+
+            val infosetType = performanceOpts.infosetType.toOption.get
+            val infosetHandler = InfosetType.getInfosetHandler(
+              infosetType,
+              processor,
+              performanceOpts.schema.toOption,
+              forPerformance = true)
+
+            val dataSeq: Seq[Either[AnyRef, Array[Byte]]] = files.map { filePath =>
+              // For performance testing, we want everything in memory so as to
+              // remove I/O from consideration. Additionally, for both parse
+              // and unparse we need immutable inputs since we could parse the
+              // same input data multiple times in different performance runs.
+              // So read the file data into an Array[Byte], and use that for
+              // everything.
+              val input = (new FileInputStream(filePath))
+              val dataSize = filePath.length()
+              val bytes = new Array[Byte](dataSize.toInt)
+              input.read(bytes)
+              val data = performanceOpts.unparse() match {
+                case true => Left(infosetHandler.dataToInfoset(bytes))
+                case false => Right(bytes)
+              }
+              data
+            }
+
+            val inputs = (0 until performanceOpts.number()).map { n =>
+              val index = n % dataSeq.length
+              dataSeq(index)
+            }
+            val inputsWithIndex = inputs.zipWithIndex
+
+            implicit val executionContext = new ExecutionContext {
+              val threadPool = Executors.newFixedThreadPool(performanceOpts.threads())
+
+              def execute(runnable: Runnable): Unit = {
+                threadPool.submit(runnable)
+              }
+
+              def reportFailure(t: Throwable): Unit = {
+                //do nothing
+              }
+            }
+
+            val nullChannelForUnparse = Channels.newChannel(NullOutputStream.NULL_OUTPUT_STREAM)
+            val nullOutputStreamForParse = NullOutputStream.NULL_OUTPUT_STREAM
+
+            val NSConvert = 1000000000.0
+            val (totalTime, results) = Timer.getTimeResult({
+              val tasks = inputsWithIndex.map {
+                case (inData, n) =>
+                  val task: Future[(Int, Long, Boolean)] = Future {
+                    val (time, result) = inData match {
+                      case Left(anyRef) => Timer.getTimeResult({
+                        val unparseResult = infosetHandler.unparse(anyRef, nullChannelForUnparse)
+                        unparseResult
+                      })
+                      case Right(bytes) => Timer.getTimeResult({
+                        val input = InputSourceDataInputStream(bytes)
+                        val infosetResult = infosetHandler.parse(input, nullOutputStreamForParse)
+                        val parseResult = infosetResult.parseResult
+                        parseResult
+                      })
+                    }
+
+                    (n, time, result.isError)
+                  }
+                  task
+              }
+              val results = tasks.map { Await.result(_, Duration.Inf) }
+              results
+            })
+
+            val rates = results.map { results =>
+              val (runNum: Int, nsTime: Long, error: Boolean) = results
+              val rate = 1 / (nsTime / NSConvert)
+              val status = if (error) "fail" else "pass"
+              Logger.log.info(s"run: ${runNum}, seconds: ${nsTime / NSConvert}, rate: ${rate}, status: ${status}")
+              rate
+            }
+
+            val numFailures = results.map { _._3 }.filter { e => e }.length
+            if (numFailures > 0) {
+              Logger.log.error(s"${numFailures} failures found\n")
+            }
+
+            val sec = totalTime / NSConvert
+            val action = performanceOpts.unparse() match {
+              case true => "unparse"
+              case false => "parse"
+            }
+            STDOUT.println(s"total $action time (sec): $sec")
+            STDOUT.println(s"min rate (files/sec): ${rates.min.toFloat}")
+            STDOUT.println(s"max rate (files/sec): ${rates.max.toFloat}")
+            STDOUT.println(s"avg rate (files/sec): ${(performanceOpts.number() / sec).toFloat}")
+
+            if (numFailures == 0) ExitCode.Success else ExitCode.PerformanceTestError
+          }
+
+        }
+        rc
+      }
+
+      case Some(conf.unparse) => {
+        val unparseOpts = conf.unparse
+
+        val validate = unparseOpts.validate.toOption.get
+
+        val optDafConfig = unparseOpts.config.toOption.map{ DaffodilConfig.fromFile(_) }
+
+        val processor = {
+          if (unparseOpts.parser.isDefined) {
+            createProcessorFromParser(unparseOpts.parser(), unparseOpts.path.toOption, validate)
+          } else {
+            val tunables = DaffodilTunables.configPlusMoreTunablesMap(unparseOpts.tunables, optDafConfig)
+            createProcessorFromSchema(unparseOpts.schema(), unparseOpts.rootNS.toOption, unparseOpts.path.toOption, tunables, validate)
+          }
+        }.map{ _.withExternalVariables(combineExternalVariables(unparseOpts.vars, optDafConfig)) }
+         .map{ _.withValidationMode(validate) }
+         .map{ withDebugOrTrace(_, conf) }
+
+        val output = unparseOpts.output.toOption match {
+          case Some("-") | None => STDOUT
+          case Some(file) => new FileOutputStream(file)
+        }
+
+        val outChannel = Channels.newChannel(output)
+        //
+        // We are not loading a schema here, we're loading the infoset to unparse.
+        //
+        val is = unparseOpts.infile.toOption match {
+          case Some("-") | None => STDIN
+          case Some(fileName) => new FileInputStream(fileName)
+        }
+
+        val rc = processor match {
+          case None => ExitCode.UnableToCreateProcessor
+          case Some(processor) => {
+            Assert.invariant(processor.isError == false)
+
+            val maybeScanner =
+              if (unparseOpts.stream.toOption.get) {
+                val scnr = new Scanner(is)
+                scnr.useDelimiter("\u0000")
+                Some(scnr)
+              } else {
+                None
+              }
+
+            var keepUnparsing = maybeScanner.isEmpty || maybeScanner.get.hasNext
+            var exitCode = ExitCode.Success
+
+            val infosetType = unparseOpts.infosetType.toOption.get
+            val infosetHandler = InfosetType.getInfosetHandler(
+              unparseOpts.infosetType.toOption.get,
+              processor,
+              unparseOpts.schema.toOption,
+              forPerformance = false)
+
+            while (keepUnparsing) {
+
+              val inputterData =
+                if (maybeScanner.isDefined) {
+                  // The scanner reads the entire infoset up unto the delimiter
+                  // into memory. No way around that with the --stream option.
+                  val bytes = maybeScanner.get.next().getBytes()
+                  infosetHandler.dataToInfoset(bytes)
+                } else {
+                  // We are not using the --stream option and won't need to
+                  // unparse the infoset more than once. So pass the
+                  // InputStream into dataToInfoset. For some cases, such as
+                  // "xml" or "json", we can create an InfosetInputter directly
+                  // on this stream so that we can avoid reading the entire
+                  // InputStream into memory
+                  infosetHandler.dataToInfoset(is)
+                }
+              val unparseResult = Timer.getResult("unparsing", infosetHandler.unparse(inputterData, outChannel))
+
+              displayDiagnostics(unparseResult)
+
+              if (unparseResult.isError) {
+                keepUnparsing = false
+                exitCode = ExitCode.UnparseError
+              } else {
+                keepUnparsing = maybeScanner.isDefined && maybeScanner.get.hasNext
+              }
+            }
+            exitCode
+          }
+        }
+
+        is.close()
+        outChannel.close()
+
+        rc
+      }
+
+      case Some(conf.save) => {
+        val saveOpts = conf.save
+
+        val validate = ValidationMode.Off
+        val optDafConfig = saveOpts.config.toOption.map{ DaffodilConfig.fromFile(_) }
+
+        val tunables = DaffodilTunables.configPlusMoreTunablesMap(saveOpts.tunables, optDafConfig)
+        val tunablesObj = DaffodilTunables(tunables)
+
+        val processor = createProcessorFromSchema(saveOpts.schema(), saveOpts.rootNS.toOption, saveOpts.path.toOption, tunables, validate)
+
+        val output = saveOpts.outfile.toOption match {
+          case Some("-") | None => Channels.newChannel(STDOUT)
+          case Some(file) => new FileOutputStream(file).getChannel()
+        }
+
+        val rc = processor match {
+          case Some(processor) => {
+            Assert.invariant(processor.isError == false)
+            Timer.getResult("saving", processor.save(output))
+            ExitCode.Success
+          }
+          case None => ExitCode.UnableToCreateProcessor
+        }
+        rc
+      }
+
+      case Some(conf.test) => {
+        val testOpts = conf.test
+
+        val tdmlFile = testOpts.tdmlfile()
+        val optTDMLImplementation = testOpts.implementation.toOption
+        val tdmlRunner = Runner(tdmlFile, optTDMLImplementation)
+
+        val tests = {
+          if (testOpts.testnames.isDefined) {
+            testOpts.testnames().flatMap(testName => {
+              if (testOpts.regex()) {
+                val regex = testName.r
+                val matches = tdmlRunner.testCases.filter(testCase => regex.pattern.matcher(testCase.tcName).matches)
+                matches.map(testCase => (testCase.tcName, Some(testCase)))
+              } else {
+                List((testName, tdmlRunner.testCases.find(_.tcName == testName)))
+              }
+            })
+          } else {
+            tdmlRunner.testCases.map(test => (test.tcName, Some(test)))
+          }
+        }.distinct.sortBy(_._1)
+
+        tdmlRunner.reset
+
+        if (testOpts.list()) {
+          if (testOpts.info() > 0) {
+            // determine the max lengths of the various pieces of a test
+            val headers = List("Name", "Model", "Root", "Description")
+            val maxCols = tests.foldLeft(headers.map(_.length)) {
+              (maxVals, testPair) =>
+                {
+                  testPair match {
+                    case (name, None) => List(
+                      maxVals(0).max(name.length),
+                      maxVals(1),
+                      maxVals(2),
+                      maxVals(3))
+                    case (name, Some(test)) => List(
+                      maxVals(0).max(name.length),
+                      maxVals(1).max(test.model.length),
+                      maxVals(2).max(test.rootName.length),
+                      maxVals(3).max(test.description.length))
+                  }
+                }
+            }
+            val formatStr = maxCols.map(max => "%" + -max + "s").mkString("  ")
+            STDOUT.println(formatStr.format(headers: _*))
+            tests.foreach { testPair =>
+              testPair match {
+                case (name, Some(test)) => STDOUT.println(formatStr.format(name, test.model, test.rootName, test.description))
+                case (name, None) => STDOUT.println(formatStr.format(name, "[Not Found]", "", ""))
+              }
+            }
+          } else {
+            tests.foreach { testPair =>
+              testPair match {
+                case (name, Some(test)) => STDOUT.println(name)
+                case (name, None) => STDOUT.println("%s  [Not Found]".format(name))
+              }
+            }
+          }
+          ExitCode.Success
+        } else {
+          var pass = 0
+          var fail = 0
+          var notfound = 0
+          tests.foreach { testPair =>
+            testPair match {
+              case (name, Some(test)) => {
+                try {
+                  test.run()
+                  STDOUT.println("[Pass] %s".format(name))
+                  pass += 1
+                } catch {
+                  case s: scala.util.control.ControlThrowable => throw s
+                  case u: UnsuppressableException => throw u
+                  case e: TDMLTestNotCompatibleException => {
+                    STDOUT.println("[Skipped] %s (not compatible with implementation: %s)"
+                      .format(name, e.implementation.getOrElse("<none>")))
+                  }
+                  case e: Throwable => {
+                    e.getCause match {
+                      case e: TDMLTestNotCompatibleException => {
+                        // if JUnit is on the classpath (e.g. possible in integration tests), then
+                        // the TDML runner throws an AssumptionViolatedException where the cause is
+                        // a TDMLTestNotCompatibleException. In that case we should output the test
+                        // skipped message. The way we match here avoids having the CLI to require
+                        // JUnit as a dependency
+                        STDOUT.println("[Skipped] %s (not compatible with implementation: %s)"
+                          .format(name, e.implementation.getOrElse("<none>")))
+                      }
+                      case _ => {
+                        STDOUT.println("[Fail] %s".format(name))
+                        fail += 1
+                        if (testOpts.info() > 0) {
+                          STDOUT.println("  Failure Information:")
+                          STDOUT.println(indent(e.getMessage(), 4))
+                        }
+                        if (testOpts.info() > 1) {
+                          STDOUT.println("  Backtrace:")
+                          e.getStackTrace.foreach { st => STDOUT.println(indent(st.toString, 4)) }
+                        }
+                      }
+                    }
+                  }
+                }
+              }
+
+              case (name, None) => {
+                STDOUT.println("[Not Found] %s".format(name))
+                notfound += 1
+              }
+            }
+          }
+          STDOUT.println("")
+          STDOUT.println("Total: %d, Pass: %d, Fail: %d, Not Found: %s".format(pass + fail + notfound, pass, fail, notfound))
+
+          if (fail == 0) ExitCode.Success else ExitCode.TestError
+        }
+      }
+
+      // Get our generate options from whichever language we're generating
+      case Some(conf.generate) => {
+        val generateOpts = conf.generate.subcommand match {
+          case Some(conf.generate.c) => conf.generate.c
+
+          // Required to avoid "match may not be exhaustive", but should never happen
+          case _ => Assert.impossible()
+        }
+
+        // Read any config file and any tunables given as arguments
+        val optDafConfig = generateOpts.config.toOption.map{ DaffodilConfig.fromFile(_) }
+        val tunables = DaffodilTunables.configPlusMoreTunablesMap(generateOpts.tunables, optDafConfig)
+
+        // Create a CodeGenerator from the DFDL schema
+        val generator = createGeneratorFromSchema(generateOpts.schema(), generateOpts.rootNS.toOption,
+          tunables, generateOpts.language)
+
+        // Ask the CodeGenerator to generate source code from the DFDL schema
+        val outputDir = generateOpts.outdir.toOption.getOrElse(".")
+        val rc = generator match {
+          case Some(generator) => {
+            Timer.getResult("generating", generator.generateCode(outputDir))
+            displayDiagnostics(generator)
+            if (generator.isError) ExitCode.GenerateCodeError else ExitCode.Success
+          }
+          case None => ExitCode.GenerateCodeError
+        }
+        rc
+      }
+
+      case Some(conf.exi) => {
+        var rc = ExitCode.Success
+        val exiOpts = conf.exi
+        val channel = exiOpts.output.toOption match {
+          case Some("-") | None => Channels.newChannel(STDOUT)
+          case Some(file) => new FileOutputStream(file).getChannel()
+        }
+        val output = Channels.newOutputStream(channel)
+
+        val inputStream = exiOpts.infile.toOption match {
+          case Some("-") | None => STDIN
+          case Some(file) => {
+            val f = new File(file)
+            new FileInputStream(f)
+          }
+        }
+        val input = new InputSource(inputStream)
+
+        val exiFactory: Option[EXIFactory] = try {
+          Some(EXIInfosetHandler.createEXIFactory(exiOpts.schema.toOption))
+        } catch {
+          case e: EXIException => {
+            Logger.log.error(s"Error creating EXI grammar for the supplied schema: ${ Misc.getSomeMessage(e).get }")
+            rc = ExitCode.Failure
+            None
+          }
+        }
+
+        (exiOpts.decode.toOption.get, exiFactory.isDefined) match {
+          case (true, true) => { // Decoding
+            val exiSource = new EXISource(exiFactory.get)
+            exiSource.setInputSource(input)
+
+            val result = new StreamResult(output)
+            val tf = TransformerFactory.newInstance()
+            val transformer = tf.newTransformer
+            transformer.setErrorListener(new EXIErrorHandler)
+            try {
+              transformer.transform(exiSource, result)
+            } catch {
+              /* We catch a generic Exception here as Exificient will attempt
+               * to decode anything and will throw very generic errors, such as
+               * an IllegalArgumentException when it runs into a series of bytes
+               * that aren't a Unicode codepoint. This should be removed once
+               * https://github.com/EXIficient/exificient/issues/33 is fixed.*/
+              case e: Exception => {
+                Logger.log.error(s"Error decoding EXI input: ${ Misc.getSomeMessage(e).get }")
+                rc = ExitCode.Failure
+              }
+            }
+          }
+          case (false, true) => { // Encoding
+            val exiResult = new EXIResult(exiFactory.get)
+            exiResult.setOutputStream(output)
+
+            val reader = XMLReaderFactory.createXMLReader()
+            reader.setContentHandler(exiResult.getHandler)
+            reader.setErrorHandler(new EXIErrorHandler)
+            try {
+              reader.parse(input)
+            } catch {
+              case s: org.xml.sax.SAXException => {
+                Logger.log.error(s"Error parsing input XML: ${ Misc.getSomeMessage(s).get }")
+                rc = ExitCode.Failure
+              }
+            }
+          }
+          case (_, false) => // Hit an exception creating exiFactory, rc already set
+        }
+
+        inputStream.close
+        output.close
+        rc
+      }
+
+      // Required to avoid "match may not be exhaustive", but should never happen
+      case _ => Assert.impossible()
+    }
+
+    ret
+  }
+
+  def bugFound(e: Exception): Int = {
+    STDERR.println("""|
+                      |!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+                      |!!   An unexpected exception occurred. This is a bug!   !!
+                      |!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+                      |
+                      | Please report this bug and help us fix it:
+                      |
+                      |  https://daffodil.apache.org/community/#issue-tracker
+                      |
+                      | Please include the following exception, the command you
+                      | ran, and any input, schema, or tdml files used that led
+                      | to this bug.
+                      |
+                      |""".stripMargin)
+    e.printStackTrace
+    1
+  }
+
+  def nyiFound(e: NotYetImplementedException): Int = {
+    STDERR.println("""|
+                      |!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+                      |!!                 Not Yet Implemented                  !!
+                      |!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+                      |
+                      | You are using a feature that is not yet implemented:
+                      |
+                      | %s
+                      |
+                      | You can create a bug and track the progress of this
+                      | feature at:
+                      |
+                      |  https://issues.apache.org/jira/projects/DAFFODIL
+                      |
+                      |""".format(Misc.getSomeMessage(e)).stripMargin)
+    1
+  }
+
+  def oomError(e: OutOfMemoryError): Int = {
+    STDERR.println("""|
+                      |!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+                      |!!             Daffodil ran out of memory!              !!
+                      |!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+                      |
+                      | Try increasing the amount of memory by changing or
+                      | setting the DAFFODIL_JAVA_OPTS environment variable.
+                      | "DAFFODIL_JAVA_OPTS=-Xmx5G" for 5GB.
+                      |
+                      |""".stripMargin)
+    1
+  }
+
+  object ExitCode extends Enumeration {
+
+    val Success = Value(0)
+    val Failure = Value(1)
+
+    val FileNotFound = Value(2)
+    val OutOfMemory = Value(3)
+    val BugFound = Value(4)
+    val NotYetImplemented = Value(5)
+
+
+    val ParseError = Value(20)
+    val UnparseError = Value(21)
+    val GenerateCodeError = Value(23)
+    val TestError = Value(24)
+    val PerformanceTestError = Value(25)
+    val ConfigError = Value(26)
+
+    val LeftOverData = Value(31)
+    val InvalidParserException = Value(32)
+    val BadExternalVariable = Value(33)
+    val UserDefinedFunctionError = Value(34)
+    val UnableToCreateProcessor = Value(35)
+
+    val Usage = Value(64)
+
+  }
+
+
+  def run(arguments: Array[String]): ExitCode.Value = {
+    val ret = try {
+      runIgnoreExceptions(arguments)
+    } catch {
+      case s: scala.util.control.ControlThrowable => throw s
+      case e: java.io.FileNotFoundException => {
+        Logger.log.error(Misc.getSomeMessage(e).get)
+        ExitCode.FileNotFound
+      }
+      case e: ExternalVariableException => {
+        Logger.log.error(Misc.getSomeMessage(e).get)
+        ExitCode.BadExternalVariable
+      }
+      case e: BindingException => {
+        Logger.log.error(Misc.getSomeMessage(e).get)
+        ExitCode.BadExternalVariable
+      }
+      case e: NotYetImplementedException => {
+        nyiFound(e)
+        ExitCode.NotYetImplemented
+      }
+      case e: TDMLException => {
+        Logger.log.error(Misc.getSomeMessage(e).get)
+        ExitCode.TestError
+      }
+      case e: OutOfMemoryError => {
+        oomError(e)
+        ExitCode.OutOfMemory
+      }
+      case e: UserDefinedFunctionFatalErrorException => {
+        Logger.log.error(Misc.getSomeMessage(e).get)
+        e.cause.getStackTrace.take(10).foreach { ste =>
+          Logger.log.error(s"    at ${ste}")
+        }
+        ExitCode.UserDefinedFunctionError
+      }
+      case e: DebuggerExitException => {
+        ExitCode.Failure
+      }
+      case e: GenericScallopException => {
+        Logger.log.error(e.message)
+        ExitCode.Usage
+      }
+      case e: DaffodilConfigException => {
+        Logger.log.error(e.message)
+        ExitCode.ConfigError
+      }
+      case e: Exception => {
+        bugFound(e)
+        ExitCode.BugFound
+      }
+    }
+    ret
+  }
+
+  def main(arguments: Array[String]): Unit = {
+    val exitCode = run(arguments)
+    System.exit(exitCode.id)
+  }
+}
+
+class EXIErrorHandler extends org.xml.sax.ErrorHandler with javax.xml.transform.ErrorListener {
+
+  // SAX ErrorHandler methods
+  def error(e: SAXParseException): Unit = {
+    Logger.log.error(e.getMessage)
+  }
+
+  def fatalError(e: SAXParseException): Unit = {
+    Logger.log.error(e.getMessage)
+  }
+
+  def warning(e: SAXParseException): Unit = {
+    Logger.log.warn(e.getMessage)
+  }
+
+  // Transformer ErrorListener methods
+  def error(e: TransformerException): Unit = {
+    Logger.log.error(e.getMessage)
+  }
+  def fatalError(e: TransformerException): Unit = {
+    Logger.log.error(e.getMessage)
+  }
+  def warning(e: TransformerException): Unit = {
+    Logger.log.warn(e.getMessage)
+  }
+}
diff --git a/daffodil-cli/src/main/scala/org/apache/daffodil/cli/debugger/CLIDebuggerRunner.scala b/daffodil-cli/src/main/scala/org/apache/daffodil/cli/debugger/CLIDebuggerRunner.scala
new file mode 100644
index 000000000..2d9d6fb3b
--- /dev/null
+++ b/daffodil-cli/src/main/scala/org/apache/daffodil/cli/debugger/CLIDebuggerRunner.scala
@@ -0,0 +1,165 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+package org.apache.daffodil.cli.debugger
+
+import org.apache.daffodil.runtime1.debugger._
+
+import java.io.File
+import java.io.InputStream
+import java.io.PrintStream
+
+import scala.collection.JavaConverters._
+import scala.io.Source
+
+import org.jline.reader.Candidate
+import org.jline.reader.Completer
+import org.jline.reader.EndOfFileException
+import org.jline.reader.LineReader
+import org.jline.reader.LineReaderBuilder
+import org.jline.reader.ParsedLine
+import org.jline.reader.UserInterruptException
+import org.jline.terminal.TerminalBuilder
+import org.jline.terminal.impl.DumbTerminal
+
+class CLIDebuggerRunner(cmdsIter: Iterator[String], in: InputStream, out: PrintStream) extends InteractiveDebuggerRunner {
+  private val prompt = "(debug) "
+
+  def this(in: InputStream = System.in, out: PrintStream = System.out) {
+    this(Iterator.empty, in, out)
+  }
+
+  def this(file: File, in: InputStream, out: PrintStream) {
+    this(Source.fromFile(file).getLines, in, out)
+  }
+
+  def this(seq: Seq[String], in: InputStream, out: PrintStream) {
+    this(seq.iterator, in, out)
+  }
+
+  var reader: Option[LineReader] = None
+
+  def init(id: InteractiveDebugger): Unit = {
+    // if the in/out parameters aren't the normal stdin/stdout, it's likely
+    // either some sort of integration test or something where a DumbTerminal
+    // is needed. Otherwise, use the TerminalBuilder which detects OS
+    // capabilities and picks the best terminal
+    val terminal =
+      if ((in ne System.in) || (out ne System.out)) {
+        new DumbTerminal(in, out)
+      } else {
+        TerminalBuilder.builder().build()
+      }
+    val completer = new CLIDebuggerCompleter(id)
+    val r = LineReaderBuilder.builder()
+      .terminal(terminal)
+      .completer(completer)
+      .build()
+    reader = Some(r)
+  }
+
+  def fini(): Unit = {
+    reader.map { _.getTerminal.close }
+    reader = None
+  }
+
+  def getCommand: String = {
+    val cmd = if (cmdsIter.hasNext) {
+      val line = cmdsIter.next
+      if (line.length > 0) {
+        reader.get.getHistory.add(line)
+      }
+      out.println("%s%s".format(prompt, line))
+      line
+    } else {
+      val line = try {
+        reader.get.readLine(prompt)
+      } catch {
+        case _: UserInterruptException => "quit" // Ctrl-C
+        case _: EndOfFileException => "quit" // Ctrl-D
+      }
+      line
+    }
+    cmd.trim
+  }
+
+  def lineOutput(line: String): Unit = {
+    out.println("  " + line)
+  }
+}
+
+class CLIDebuggerCompleter(id: InteractiveDebugger) extends Completer {
+
+  def complete(reader: LineReader, line: ParsedLine, candidates: java.util.List[Candidate]): Unit = {
+    // JLine3 completely parses the line, taking care of delmiters/quotes/etc.,
+    // and stores it in the ParsedLine, with delimeted fields split up in to the
+    // line.words array. The last item in this array is the thing we are
+    // trying to autocomplete. Everything preceeding that last word (i.e.
+    // line.words.init) are the subcommands, which determines what the possible
+    // candidates are of that last word.
+    val cmds = line.words.asScala.init
+
+    // iterate over the list of commands to find the last subcommand which is
+    // used to determine what possible candidates there are
+    val optCmd = cmds.foldLeft(Some(id.DebugCommandBase): Option[id.DebugCommand]) { case (optCurCmd, nextCmdName) =>
+      optCurCmd match {
+        case Some(id.DebugCommandBase.Info) => {
+          // We found the info command, even if there are more command names
+          // after that, we are going to ignore them and just keep the Info
+          // command. This lets use provide and autocomplete multiple info
+          // commands at once and complete only the last one, e.g. "info foo
+          // bar baz"
+          optCurCmd
+        }
+        case Some(cmd) => {
+          // We have the name for the next command, try to find the
+          // associated subcommand of the current DebugCommand. If we don't
+          // find one, it just means they user typed something that's not a
+          // valid command and we have no command to use for completing. Note
+          // that by comparing using == with the RHS being a String, we match
+          // against both short and long debug command names
+          val nextCmd = cmd.subcommands.find { _ == nextCmdName }
+          nextCmd
+        }
+        case None => {
+          // We previously failed to find a next command, likely because one
+          // of the subcommands was misspelled. That means we'll have no idea
+          // how to complete the last word, so we'll have no candidates
+          None
+        }
+      }
+    }
+
+    optCmd match {
+      case Some(cmd) => {
+        // We found a command used for autocompleting. All subcommands of the
+        // last found command are potential completion candidates. If there
+        // are no subcommands, then there are no candidates. Of these
+        // canidates, JLine will filter out any candidates that do not match
+        // the last word so we don't have to do that. It may also uses these
+        // candidates if it looks like there is a type in the word being
+        // completed, so it's a bit smarter than just doing .startsWith. We
+        // let Jline figure that out.
+        cmd.subcommands.foreach { sub => candidates.add(new Candidate(sub.name)) }
+      }
+      case None => {
+        // We found words that weren't actually subcommands, so we don't know
+        // how to complete this last thing. We have no candidates
+      }
+    }
+  }
+}
diff --git a/daffodil-cli/src/main/scala/org/apache/daffodil/debugger/CLIDebuggerRunner.scala b/daffodil-cli/src/main/scala/org/apache/daffodil/debugger/CLIDebuggerRunner.scala
deleted file mode 100644
index cea2cad23..000000000
--- a/daffodil-cli/src/main/scala/org/apache/daffodil/debugger/CLIDebuggerRunner.scala
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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.
- */
-
-package org.apache.daffodil.debugger
-
-import java.io.File
-import java.io.InputStream
-import java.io.PrintStream
-
-import scala.collection.JavaConverters._
-import scala.io.Source
-
-import org.jline.reader.Candidate
-import org.jline.reader.Completer
-import org.jline.reader.EndOfFileException
-import org.jline.reader.LineReader
-import org.jline.reader.LineReaderBuilder
-import org.jline.reader.ParsedLine
-import org.jline.reader.UserInterruptException
-import org.jline.terminal.TerminalBuilder
-import org.jline.terminal.impl.DumbTerminal
-
-class CLIDebuggerRunner(cmdsIter: Iterator[String], in: InputStream, out: PrintStream) extends InteractiveDebuggerRunner {
-  private val prompt = "(debug) "
-
-  def this(in: InputStream = System.in, out: PrintStream = System.out) {
-    this(Iterator.empty, in, out)
-  }
-
-  def this(file: File, in: InputStream, out: PrintStream) {
-    this(Source.fromFile(file).getLines, in, out)
-  }
-
-  def this(seq: Seq[String], in: InputStream, out: PrintStream) {
-    this(seq.iterator, in, out)
-  }
-
-  var reader: Option[LineReader] = None
-
-  def init(id: InteractiveDebugger): Unit = {
-    // if the in/out parameters aren't the normal stdin/stdout, it's likely
-    // either some sort of integration test or something where a DumbTerminal
-    // is needed. Otherwise, use the TerminalBuilder which detects OS
-    // capabilities and picks the best terminal
-    val terminal =
-      if ((in ne System.in) || (out ne System.out)) {
-        new DumbTerminal(in, out)
-      } else {
-        TerminalBuilder.builder().build()
-      }
-    val completer = new CLIDebuggerCompleter(id)
-    val r = LineReaderBuilder.builder()
-      .terminal(terminal)
-      .completer(completer)
-      .build()
-    reader = Some(r)
-  }
-
-  def fini(): Unit = {
-    reader.map { _.getTerminal.close }
-    reader = None
-  }
-
-  def getCommand: String = {
-    val cmd = if (cmdsIter.hasNext) {
-      val line = cmdsIter.next
-      if (line.length > 0) {
-        reader.get.getHistory.add(line)
-      }
-      out.println("%s%s".format(prompt, line))
-      line
-    } else {
-      val line = try {
-        reader.get.readLine(prompt)
-      } catch {
-        case _: UserInterruptException => "quit" // Ctrl-C
-        case _: EndOfFileException => "quit" // Ctrl-D
-      }
-      line
-    }
-    cmd.trim
-  }
-
-  def lineOutput(line: String): Unit = {
-    out.println("  " + line)
-  }
-}
-
-class CLIDebuggerCompleter(id: InteractiveDebugger) extends Completer {
-
-  def complete(reader: LineReader, line: ParsedLine, candidates: java.util.List[Candidate]): Unit = {
-    // JLine3 completely parses the line, taking care of delmiters/quotes/etc.,
-    // and stores it in the ParsedLine, with delimeted fields split up in to the
-    // line.words array. The last item in this array is the thing we are
-    // trying to autocomplete. Everything preceeding that last word (i.e.
-    // line.words.init) are the subcommands, which determines what the possible
-    // candidates are of that last word.
-    val cmds = line.words.asScala.init
-
-    // iterate over the list of commands to find the last subcommand which is
-    // used to determine what possible candidates there are
-    val optCmd = cmds.foldLeft(Some(id.DebugCommandBase): Option[id.DebugCommand]) { case (optCurCmd, nextCmdName) =>
-      optCurCmd match {
-        case Some(id.DebugCommandBase.Info) => {
-          // We found the info command, even if there are more command names
-          // after that, we are going to ignore them and just keep the Info
-          // command. This lets use provide and autocomplete multiple info
-          // commands at once and complete only the last one, e.g. "info foo
-          // bar baz"
-          optCurCmd
-        }
-        case Some(cmd) => {
-          // We have the name for the next command, try to find the
-          // associated subcommand of the current DebugCommand. If we don't
-          // find one, it just means they user typed something that's not a
-          // valid command and we have no command to use for completing. Note
-          // that by comparing using == with the RHS being a String, we match
-          // against both short and long debug command names
-          val nextCmd = cmd.subcommands.find { _ == nextCmdName }
-          nextCmd
-        }
-        case None => {
-          // We previously failed to find a next command, likely because one
-          // of the subcommands was misspelled. That means we'll have no idea
-          // how to complete the last word, so we'll have no candidates
-          None
-        }
-      }
-    }
-
-    optCmd match {
-      case Some(cmd) => {
-        // We found a command used for autocompleting. All subcommands of the
-        // last found command are potential completion candidates. If there
-        // are no subcommands, then there are no candidates. Of these
-        // canidates, JLine will filter out any candidates that do not match
-        // the last word so we don't have to do that. It may also uses these
-        // candidates if it looks like there is a type in the word being
-        // completed, so it's a bit smarter than just doing .startsWith. We
-        // let Jline figure that out.
-        cmd.subcommands.foreach { sub => candidates.add(new Candidate(sub.name)) }
-      }
-      case None => {
-        // We found words that weren't actually subcommands, so we don't know
-        // how to complete this last thing. We have no candidates
-      }
-    }
-  }
-}
diff --git a/daffodil-cli/src/templates/bash-template b/daffodil-cli/src/templates/bash-template
index 726df4ecb..fbee60c13 100755
--- a/daffodil-cli/src/templates/bash-template
+++ b/daffodil-cli/src/templates/bash-template
@@ -48,7 +48,7 @@ realpath() {
   echo "$REALPATH"
 }
 
-MAINCLASS=org.apache.daffodil.Main
+MAINCLASS=org.apache.daffodil.cli.Main
 SCRIPT=$(realpath "$0")			# Full path to script, needed for symlinks
 BINDIR=$(dirname "${SCRIPT}")		# Directory script is run in
 LIBDIR="$BINDIR/../lib"
diff --git a/daffodil-cli/src/templates/bat-template b/daffodil-cli/src/templates/bat-template
index 872d28a85..05f121510 100755
--- a/daffodil-cli/src/templates/bat-template
+++ b/daffodil-cli/src/templates/bat-template
@@ -37,7 +37,7 @@ REM defaults for Daffodil will be defined.
 
 setlocal
 
-set MAINCLASS=org.apache.daffodil.Main
+set MAINCLASS=org.apache.daffodil.cli.Main
 set BINDIR=%~dp0
 set LIBDIR=%BINDIR%..\lib
 set CONFDIR=%BINDIR%..\conf
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/ABC_IBM_invalid.dfdl.xsd b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/ABC_IBM_invalid.dfdl.xsd
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/ABC_IBM_invalid.dfdl.xsd
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/ABC_IBM_invalid.dfdl.xsd
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/bits_parsing.dfdl.xsd b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/bits_parsing.dfdl.xsd
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/bits_parsing.dfdl.xsd
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/bits_parsing.dfdl.xsd
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/blob_backtracking.dfdl.xsd b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/blob_backtracking.dfdl.xsd
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/blob_backtracking.dfdl.xsd
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/blob_backtracking.dfdl.xsd
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/charClassEntities.dfdl.xsd b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/charClassEntities.dfdl.xsd
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/charClassEntities.dfdl.xsd
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/charClassEntities.dfdl.xsd
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/cli_schema.dfdl.xsd b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/cli_schema.dfdl.xsd
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/cli_schema.dfdl.xsd
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/cli_schema.dfdl.xsd
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/cli_schema_02.dfdl.xsd b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/cli_schema_02.dfdl.xsd
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/cli_schema_02.dfdl.xsd
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/cli_schema_02.dfdl.xsd
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/cli_schema_03.dfdl.xsd b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/cli_schema_03.dfdl.xsd
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/cli_schema_03.dfdl.xsd
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/cli_schema_03.dfdl.xsd
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/cli_schema_04.dfdl.xsd b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/cli_schema_04.dfdl.xsd
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/cli_schema_04.dfdl.xsd
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/cli_schema_04.dfdl.xsd
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/complex_types.dfdl.xsd b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/complex_types.dfdl.xsd
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/complex_types.dfdl.xsd
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/complex_types.dfdl.xsd
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/global_element.dfdl.xsd b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/global_element.dfdl.xsd
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/global_element.dfdl.xsd
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/global_element.dfdl.xsd
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/global_element_import.dfdl.xsd b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/global_element_import.dfdl.xsd
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/global_element_import.dfdl.xsd
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/global_element_import.dfdl.xsd
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/gen_blob.py b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/gen_blob.py
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/gen_blob.py
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/gen_blob.py
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/hextest.txt b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/hextest.txt
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/hextest.txt
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/hextest.txt
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input1.txt b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input1.txt
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input1.txt
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input1.txt
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input1.txt.xml b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input1.txt.xml
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input1.txt.xml
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input1.txt.xml
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input10.txt b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input10.txt
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input10.txt
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input10.txt
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input11.txt b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input11.txt
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input11.txt
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input11.txt
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input12.txt b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input12.txt
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input12.txt
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input12.txt
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input13.txt b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input13.txt
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input13.txt
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input13.txt
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input14.exi b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input14.exi
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input14.exi
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input14.exi
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input14.exisa b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input14.exisa
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input14.exisa
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input14.exisa
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input14.txt b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input14.txt
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input14.txt
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input14.txt
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input15.txt b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input15.txt
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input15.txt
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input15.txt
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input16.txt b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input16.txt
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input16.txt
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input16.txt
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input18.exi b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input18.exi
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input18.exi
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input18.exi
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input18.exisa b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input18.exisa
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input18.exisa
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input18.exisa
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input18.json b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input18.json
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input18.json
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input18.json
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input18.txt b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input18.txt
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input18.txt
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input18.txt
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input19.txt b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input19.txt
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input19.txt
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input19.txt
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input2.txt b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input2.txt
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input2.txt
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input2.txt
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input3.txt b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input3.txt
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input3.txt
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input3.txt
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input4.txt b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input4.txt
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input4.txt
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input4.txt
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input5.txt b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input5.txt
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input5.txt
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input5.txt
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input6.txt b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input6.txt
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input6.txt
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input6.txt
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input7.txt b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input7.txt
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input7.txt
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input7.txt
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input8.txt b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input8.txt
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input8.txt
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input8.txt
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input9.txt b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input9.txt
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input9.txt
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input9.txt
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input9.txt.xml b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input9.txt.xml
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/input9.txt.xml
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input9.txt.xml
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/inputBig1M.txt b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/inputBig1M.txt
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/inputBig1M.txt
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/inputBig1M.txt
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/prefix.txt b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/prefix.txt
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/prefix.txt
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/prefix.txt
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/test_DFDL-714.txt b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/test_DFDL-714.txt
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/test_DFDL-714.txt
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/test_DFDL-714.txt
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/uuid.txt b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/uuid.txt
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/input/uuid.txt
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/uuid.txt
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/large_blob.dfdl.xsd b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/large_blob.dfdl.xsd
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/large_blob.dfdl.xsd
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/large_blob.dfdl.xsd
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/prefixed_length.dfdl.xsd b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/prefixed_length.dfdl.xsd
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/prefixed_length.dfdl.xsd
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/prefixed_length.dfdl.xsd
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/single.dfdl.xsd b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/single.dfdl.xsd
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/single.dfdl.xsd
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/single.dfdl.xsd
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/single_conf_bad.txt b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/single_conf_bad.txt
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/single_conf_bad.txt
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/single_conf_bad.txt
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/suppressWarnTest.dfdl.xsd b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/suppressWarnTest.dfdl.xsd
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/suppressWarnTest.dfdl.xsd
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/suppressWarnTest.dfdl.xsd
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/testNonCompatibleImplementation.tdml b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/testNonCompatibleImplementation.tdml
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/testNonCompatibleImplementation.tdml
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/testNonCompatibleImplementation.tdml
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/trace_input.dfdl.xsd b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/trace_input.dfdl.xsd
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/trace_input.dfdl.xsd
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/trace_input.dfdl.xsd
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/unqualified_path_step.dfdl.xsd b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/unqualified_path_step.dfdl.xsd
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/unqualified_path_step.dfdl.xsd
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/unqualified_path_step.dfdl.xsd
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/xcatalog_import_failure.dfdl.xsd b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/xcatalog_import_failure.dfdl.xsd
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/xcatalog_import_failure.dfdl.xsd
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/xcatalog_import_failure.dfdl.xsd
diff --git a/daffodil-cli/src/test/resources/org/apache/daffodil/CLI/xcatalog_invalid.xml b/daffodil-cli/src/test/resources/org/apache/daffodil/cli/xcatalog_invalid.xml
similarity index 100%
rename from daffodil-cli/src/test/resources/org/apache/daffodil/CLI/xcatalog_invalid.xml
rename to daffodil-cli/src/test/resources/org/apache/daffodil/cli/xcatalog_invalid.xml
diff --git a/daffodil-cli/src/test/scala/org/apache/daffodil/CLI/Util.scala b/daffodil-cli/src/test/scala/org/apache/daffodil/CLI/Util.scala
deleted file mode 100644
index 060207f6d..000000000
--- a/daffodil-cli/src/test/scala/org/apache/daffodil/CLI/Util.scala
+++ /dev/null
@@ -1,629 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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.
- */
-
-package org.apache.daffodil.CLI
-
-import java.io.File
-import java.io.InputStream
-import java.io.OutputStream
-import java.io.PipedInputStream
-import java.io.PipedOutputStream
-import java.io.PrintStream
-import java.lang.ProcessBuilder
-import java.math.BigInteger
-import java.nio.file.Files
-import java.nio.file.Path
-import java.nio.file.Paths
-import java.security.MessageDigest
-import java.util.concurrent.TimeUnit
-
-import scala.collection.JavaConverters._
-import scala.collection.mutable
-
-import com.fasterxml.jackson.core.io.JsonStringEncoder
-
-import net.sf.expectit.Expect
-import net.sf.expectit.ExpectBuilder
-import net.sf.expectit.Result
-import net.sf.expectit.filter.Filters.replaceInString
-import net.sf.expectit.matcher.Matcher
-import net.sf.expectit.matcher.Matchers.contains
-
-import org.apache.commons.io.FileUtils
-
-import org.apache.logging.log4j.Level
-import org.apache.logging.log4j.core.appender.OutputStreamAppender
-import org.apache.logging.log4j.core.config.AbstractConfiguration
-import org.apache.logging.log4j.core.config.ConfigurationSource
-import org.apache.logging.log4j.core.config.Configurator
-import org.apache.logging.log4j.core.layout.PatternLayout
-
-import org.junit.Assert.assertEquals
-
-import org.apache.daffodil.Main
-import org.apache.daffodil.Main.ExitCode
-
-object Util {
-
-  private val isWindows = System.getProperty("os.name").toLowerCase().startsWith("windows")
-
-  private val daffodilRoot = sys.env.getOrElse("DAFFODIL_HOME", ".")
-
-  private val daffodilBinPath = {
-    val ext = if (isWindows) ".bat" else ""
-    Paths.get(daffodilRoot, s"daffodil-cli/target/universal/stage/bin/daffodil$ext")
-  }
-
-  /**
-   * Convert the daffodilRoot + parameter to a java Path. The string
-   * parameter should contain unix path sparators and it will be interpreted
-   * correctly regardless of operating system. When converted to a string to
-   * send to the CLI, it will use the correct line separator for the
-   * operating system
-   */
-  def path(string: String): Path = {
-    Paths.get(daffodilRoot, string)
-  }
-
-  def devNull(): String = if (isWindows) "NUL" else "/dev/null"
-
-  def md5sum(path: Path): String = {
-    val md = MessageDigest.getInstance("MD5")
-    val buffer = new Array[Byte](8192)
-    val stream = Files.newInputStream(path)
-    var read = 0
-    while ({read = stream.read(buffer); read} > 0) {
-      md.update(buffer, 0, read)
-    }
-    val md5sum = md.digest()
-    val bigInt = new BigInteger(1, md5sum)
-    bigInt.toString(16)
-  }
-
-  /**
-   * Create a temporary file in /tmp/daffodil/, call a user provided function
-   * passing in the Path to that new file, and delete the file when the
-   * function returns.
-   */
-  def withTempFile(f: (Path) => Unit) : Unit = withTempFile(null, f)
-
-  /**
-   * Create a temporary file in /tmp/daffodil/ with a givin suffix, call a user
-   * provided function passing in the Path to that new file, and delete the
-   * file when the function returns.
-   */
-  def withTempFile(suffix: String, f: (Path) => Unit): Unit = {
-    val tempRoot = Paths.get(System.getProperty("java.io.tmpdir"), "daffodil")
-    Files.createDirectories(tempRoot)
-    val tempFile = Files.createTempFile(tempRoot, "daffodil-", suffix)
-    try {
-      f(tempFile)
-    } finally {
-      tempFile.toFile.delete()
-    }
-  }
-
-  /**
-   * Create a temporary directory in /tmp/daffodil/, call a user provided
-   * function passing in the Path to that new directory, and delete the
-   * directory and all of its contents when the function returns
-   */
-  def withTempDir(f: (Path) => Unit): Unit = {
-    val tempRoot = Paths.get(System.getProperty("java.io.tmpdir"), "daffodil")
-    Files.createDirectories(tempRoot)
-    val tempDir = Files.createTempDirectory(tempRoot, "daffodil-")
-    try {
-      f(tempDir)
-    } finally {
-      FileUtils.deleteDirectory(tempDir.toFile)
-    }
-  }
-
-  /**
-   * Set a system property using a provided key, value tuple, call a user
-   * provided function, and reset or clear the property when the function
-   * returns.
-   */
-  def withSysProp(keyVal: (String, String))(f: => Unit): Unit = {
-    val key = keyVal._1
-    val newVal = keyVal._2
-    val oldVal = System.setProperty(key, newVal)
-    try {
-      f
-    } finally {
-      if (oldVal == null) {
-        System.clearProperty(key)
-      } else {
-        System.setProperty(key, oldVal)
-      }
-    }
-  }
-
-  /**
-   * Run a CLI test.
-   *
-   * Runs CLI logic using the provided arguments and classpath, creates a
-   * CLITester so that the user can send input and validate output, and
-   * verifies the expected exit code.
-   *
-   * For performance reasons, this defaults to running the CLI in a new thread
-   * unless the classpaths parameter is nonempty or the fork parameter is set to
-   * true. Otherwise a new process is spawned.
-   *
-   * @param args arguments to pass to the CLI. This should not include the
-   *   daffodil binary
-   * @param classpaths sequence of paths to add to the classpath. If non-empty,
-   *   runs the CLI in a new process instead of a thread and will likely decrease
-   *   performance
-   * @param fork if true, forces the the CLI to run in a new process
-   * @param timeout how long to wait, in seconds, for the testFunc to finish after
-   *   the CLI has completed. Test testFunc is interrupted if this timeout is reached
-   * @param debug if true, prints arguments and classpath information to
-   *   stdout. Also echos all CLITester input and output to stdout.
-   * @param testFunc function to call to send input to the CLI and validate
-   *   output from CLI stdout/stderr
-   * @param expectedExitCode the expected exit code of the CLI
-   *
-   * @throws AssertionError if the actual exit code does not match the expected exit code
-   * @throws ExpectIOException if a CLITester expect validation operation fails
-   */
-  def runCLI
-    (args: Array[String], classpaths: Seq[Path] = Seq(), fork: Boolean = false, timeout: Int = 10, debug: Boolean = false)
-    (testFunc: (CLITester) => Unit)
-    (expectedExitCode: ExitCode.Value): Unit = {
-
-    val (toIn, fromOut, fromErr, cliThreadOrProc: Either[CLIThread, Process]) =
-      if (classpaths.nonEmpty || fork) {
-        // spawn a new process to run Daffodil, needed if a custom classpath is
-        // defined or if the caller explicitly wants to fork
-        val processBuilder = new ProcessBuilder()
-
-        if (classpaths.nonEmpty) {
-          val classpath = classpaths.mkString(File.pathSeparator)
-          if (debug) System.out.println(s"DAFFODIL_CLASSPATH=$classpath")
-          processBuilder.environment().put("DAFFODIL_CLASSPATH", classpath)
-        }
-
-        val cmd = daffodilBinPath.toString +: args
-        if (debug) System.out.println(cmd.mkString(" "))
-        processBuilder.command(cmd.toList.asJava)
-
-        val process = processBuilder.start()
-
-        val toIn = process.getOutputStream()
-        val fromOut = process.getInputStream()
-        val fromErr = process.getErrorStream()
-        (toIn, fromOut, fromErr, Right(process))
-      } else {
-        // create a new thread for the CLI test to run, using piped
-        // input/output streams to connect the thread and the CLITester
-        val in = new PipedInputStream()
-        val toIn = new PipedOutputStream(in)
-
-        val out = new PipedOutputStream()
-        val fromOut = new PipedInputStream(out)
-
-        val err = new PipedOutputStream()
-        val fromErr = new PipedInputStream(err)
-
-        if (debug) System.out.println("daffodil " + args.mkString(" "))
-
-        val thread = new CLIThread(args, in, out, err)
-        thread.start()
-        (toIn, fromOut, fromErr, Left(thread))
-      }
-
-    // Create an ExpectIt object that reads/writes the streams created above.
-    val eb = new ExpectBuilder()
-    eb.withOutput(toIn)
-    eb.withInputs(fromOut, fromErr)
-    eb.withInputFilters(replaceInString("\r\n", "\n"))
-    // Disable timeouts on expect calls. We do this because often times the CLI can
-    // take a while to start up (e.g. spawning a thread, schema compilation) which
-    // can lead to timeouts and false negatives. Instead, we will spawn the expect
-    // body in a separate TestThread. Only once the CLI finishes do we start a
-    // timeout countdown and require the test body logic to complete in a specified
-    // number of seconds. If if doesn't finish, then we interrupt the test thread and
-    // capture any errors. So although expect will never timeout itself, we may time
-    // it out after the CLI is complete.
-    eb.withInfiniteTimeout()
-    eb.withExceptionOnFailure()
-    if (debug) {
-      eb.withEchoOutput(System.out)
-      eb.withEchoInput(System.out)
-    }
-    val expect = eb.build()
-    val tester = new CLITester(expect, toIn)
-
-    // run the test function in a new thread. We do this so that we can configure
-    // ExpectIt to have an infinite timeout. This way we can trigger a timeout only
-    // after the CLI has exited, at which point we give the test thread timeout
-    // seconds to finish its tests and interrupt it if it still hasn't finished. This
-    // way if the CLI is slow to start up, ExpectIt won't create a timeout exception
-    // and fail the test. We at least let the CLI finish before doing that
-    val testThread = new TestThread(testFunc, tester, cliThreadOrProc)
-    testThread.start()
-
-    // wait for the CLI process/thread to finish with no timeout--we really don't
-    // know how long the CLI is going to take, but we assume the CLI will eventually
-    // finish. If it doesn't, we'll see the hang and investigate. Note that if there
-    // is a hang here, it is most likely a bad test. The most common cause is the CLI
-    // is waiting for something on stdin (e.g. EOF, debugger command), but the test
-    // body is not providing it. For CLI tests taking significantly longer than
-    // expected (6x the timeout, which is 1 minute for the default timeout), we can
-    // assume it is hung and output a message with helpful reference to possible
-    // causes. We still don't kill it though, since we might just be very resources
-    // starved and it might finish eventually.
-    val cliWarningTimeoutMS = 6 * timeout * 1000
-    var isAlive = true
-    while (isAlive) {
-      isAlive = cliThreadOrProc match {
-        case Left(cliThread) => {
-          cliThread.join(cliWarningTimeoutMS)
-          cliThread.isAlive
-        }
-        case Right(cliProcess) => {
-          cliProcess.waitFor(cliWarningTimeoutMS, TimeUnit.MILLISECONDS)
-          cliProcess.isAlive
-        }
-      }
-      if (isAlive) {
-        System.err.println("CLI test may be hanging, see DAFFODIL-2751 for possible causes")
-      }
-    }
-
-    // now that the CLI has finished, we give the test thread timeout seconds to
-    // finish verifying the output. Hopefully it was doing all the checks in parallel
-    // with the CLI thread so it should be mostly complete and finish in time.
-    testThread.join(timeout * 1000)
-
-    // If the test thread still isn't done, assume it failed and is stuck expecting
-    // something that isn't ever coming from the CLI. Interrupt the thread to end it
-    // and join to wait for it to cleanup and die. Although this join is technically
-    // infinite, as long as the expect library or test body doesn't explicitly
-    // capture and ignore the interrupt and then block on something, this should not
-    // hang
-    testThread.interrupt()
-    testThread.join()
-
-    // the test thread is finally done, clean up our in/out/err buffers
-    expect.close()
-    toIn.close()
-    fromOut.close()
-    fromErr.close()
-
-    // if the test thread didn't end cleanly then it must have thrown an exception
-    // (e.g. assertion failed, interrupted exception). Just rethrow that exception
-    // and cause the test to fail
-    testThread.optException.map { e => throw e }
-
-    // if the test thread didn't throw an exception then that means all of its tests
-    // passed. We just need to verify the CLI exit code
-    val actualExitCode = cliThreadOrProc match {
-      case Left(cliThread) => cliThread.exitCode
-      case Right(cliProcess) => ExitCode(cliProcess.exitValue)
-    }
-    assertEquals("Incorrect exit code,", expectedExitCode, actualExitCode)
-  }
-
-  /**
-   * Run CLITester function body in a new thread
-   *
-   * If an exception was thrown during evaluation it is stored in the optException
-   * variable. This thread will not exit until the cli thread/process has completed
-   */
-  private class TestThread(
-    testFunc: (CLITester) => Unit,
-    tester: CLITester,
-    cli: Either[Thread, Process])
-    extends Thread {
-
-    var optException: Option[Throwable] = None
-
-    private val exceptionHandler = new Thread.UncaughtExceptionHandler {
-      def uncaughtException(t: Thread, e: Throwable): Unit = {
-        optException = Some(e)
-      }
-    }
-
-    override def run(): Unit = {
-      // if this thread throws an exception (e.g. an expect function fails, this
-      // thread is interrupted), we just handle it and store it in optException above
-      this.setUncaughtExceptionHandler(exceptionHandler)
-
-      // now run the test
-      testFunc(tester)
-
-      // it is possible we finished the test before the CLI actually finished.
-      // Unfortunately, we can't let this thread die until the CLI is complete
-      // because ExpectIt has some magic that will close the pipes we set up and
-      // cause the CLI to get a pipe broken exception instead of finishing. So we
-      // just wait for the CLI to finish. Once the CLI is done this thread can
-      // cleanly die and the main thread can figure out what happened.
-      cli match {
-        case Left(thread) => thread.join()
-        case Right(process) => process.waitFor()
-      }
-    }
-  }
-
-  /**
-   * A class to run the CLI in a thread instead of a new process, given the
-   * arguments to use (excluding the daffodil binary) and streams to use for
-   * stdin/out/err.
-   */
-  private class CLIThread(args: Array[String], in: InputStream, out: OutputStream, err: OutputStream) extends Thread {
-    var exitCode: ExitCode.Value = _
-
-    override def run(): Unit = {
-      val psOut = new PrintStream(out)
-      val psErr = new PrintStream(err)
-
-      // configure the CLI and log4j to use our custom streams, nothing should
-      // actually use stdin/stdout/stderr
-      Main.setInputOutput(in, psOut, psErr)
-      configureLog4j(psErr)
-
-      try {
-        exitCode = Main.run(args)
-      } catch {
-        case t: Throwable => {
-          // Main.run should never throw an exception so if it did it means the CLI
-          // hit a bug. So just print the exception and set the exit code to
-          // BugFound. No tests should expect the BugFound exit code and should fail.
-          t.printStackTrace()
-          exitCode = ExitCode.BugFound
-        }
-      }
-    }
-
-    /**
-     * By default log4j outputs to stderr. This changes that so it writes to a
-     * provided PrintStream which is connected to the CLITester, allowing tests
-     * to expect content written by log4j. This also defines the same pattern
-     * used by the CLI from the daffodil-cli/src/conf/log4j2.xml config
-     * file--we duplicate it here because the normal log4j config file targets
-     * stderr while we need to target the provided PrintStream (which can only
-     * be defined programatically). It was also found to be too difficult to
-     * load the config file and mutate the log4j configuration to write to this
-     * PrintStream instead of stderr.
-     */
-    private def configureLog4j(ps: PrintStream): Unit = {
-      val config = new AbstractConfiguration(null, ConfigurationSource.NULL_SOURCE) {
-        override def doConfigure(): Unit = {
-          val appenderName = "DaffodilCli"
-
-          val layout = PatternLayout.newBuilder()
-            .withPattern("[%p{lowerCase=true}] %m%n")
-            .withConfiguration(this)
-            .build()
-
-          val appenderBuilder: OutputStreamAppender.Builder[_] = OutputStreamAppender.newBuilder()
-          appenderBuilder.setName(appenderName)
-          appenderBuilder.setLayout(layout)
-          appenderBuilder.setTarget(ps)
-          appenderBuilder.setConfiguration(this)
-          val appender = appenderBuilder.build()
-
-          val rootLogger = getRootLogger()
-          rootLogger.setLevel(Level.WARN);
-          rootLogger.addAppender(appender, null, null)
-        }
-      }
-      Configurator.reconfigure(config)
-    }
-  }
-
-  /**
-   * Wrapper around Expect to make integration tests less verbose. It also
-   * supports closing the mimicked stdin input stream (via the closeInput()
-   * function or the inputDone parameter to the send*() functions), which is
-   * sometimes needed since Daffodil may need to receive an EOF before it can
-   * finish parsing.
-   */
-  private class CLITester(expect: Expect, toIn: OutputStream) {
-
-    /**
-     * Close stdin, triggering an EOF.
-     */
-    def closeInput(): Unit = { toIn.close() }
-
-    /**
-     * Write a string to stdin. This does not incluede trailing newline. If
-     * inputDone is true, close stdin afterwards.
-     */
-    def send(string: String, inputDone: Boolean = false): Unit = {
-      expect.send(string)
-      if (inputDone) closeInput()
-    }
-
-    /**
-     * Write a string to stdin with a trailing newline. If inputDone is true,
-     * close stdin afterwards.
-     */
-    def sendLine(string: String, inputDone: Boolean = false): Unit = {
-      expect.sendLine(string)
-      if (inputDone) closeInput()
-    }
-
-    /**
-     * Write an entire byte array to stdin. If inputDone is true, close stdin
-     * afterwards.
-     */
-    def sendBytes(bytes: Array[Byte], inputDone: Boolean = false): Unit = {
-      expect.sendBytes(bytes)
-      if (inputDone) closeInput()
-    }
-
-    /**
-     * Write a file to stdin. If inputDone is true, close stdin afterwards.
-     */
-    def sendFile(path: Path, inputDone: Boolean = false): Unit = {
-      val chunkSize = 8192
-      val buffer = new Array[Byte](chunkSize)
-      val stream = Files.newInputStream(path)
-      var read = 0
-      while ({read = stream.read(buffer); read} > 0) {
-        if (read == chunkSize) {
-          expect.sendBytes(buffer)
-        } else {
-          // The expect.sendBytes function does not have parameters to send a
-          // subset of an array, it just sends the whole array. So we need to
-          // trim it down to the actual read size and send that
-          val smaller = new Array[Byte](read)
-          buffer.copyToArray(smaller, 0, read)
-          expect.sendBytes(smaller)
-        }
-      }
-      if (inputDone) closeInput()
-    }
-
-    def expect(matcher: Matcher[_]): Result = expect.expect(matcher)
-    def expect(string: String): Result = expect.expect(contains(string))
-
-    def expectErr(matcher: Matcher[_]): Result = expect.expectIn(1, matcher)
-    def expectErr(string: String): Result = expect.expectIn(1, contains(string))
-  }
-
-  /**
-   * Escapes a string that is expected to be a json string
-   */
-  def jsonEscape(string: String): String = {
-    val chars = JsonStringEncoder.getInstance().quoteAsString(string)
-    new String(chars)
-  }
-
-  /**
-   * This "args" string interpoloator makes it easy to create an Array[String]
-   * used for CLI arguments. Only spaces in the "format" string are split on.
-   * Spaces in an expressions in the format string are not split. For example
-   *
-   *   args"parse -s $schema $input".split(" ")
-   *
-   * Becomes someething like this:
-   *
-   *   Array("parse", "-s", "path/to/schema.dfdl.xsd", "path/to/input.bin")
-   *
-   * An alternative approach one might choose by using existing interpolators
-   * is something like this:
-   *
-   *   s"parse -s $schema $input".split(" ")
-   *
-   * This issue with this approach is that if the $schema or $input variables
-   * evaluate to something with string (which is not uncommon on some windows
-   * systems), then we end up splitting those files paths into separate
-   * arguments. This args interpolator ensures we don't split spaces that come
-   * from expressions.
-   *
-   * Note that quotes cannot be used to prevent splitting. For example, this
-   *
-   *   args"quotes do 'not prevent' splitting"
-   *
-   * Results in the following:
-   *
-   *   Array("quotes", "do", "'not", "prevent'", "splitting")
-   *
-   * To prevent splitting on a particular space, then expressions can be used,
-   * for example:
-   *
-   *   args"this ${"is split"} correctly"
-   *
-   * Which results in the following:
-   *
-   *   Array("this", "is split", "correctly")
-   *
-   * Note that this also handles concatenating expression correctly, for
-   * example:
-   *
-   *   args"some --arg=$root/$value"
-   *
-   * Results in
-   *
-   *   Array("some", "--arg=the/result/of/root/and/value")
-   *
-   */
-  implicit class ArgsHelper(val sc: StringContext) extends AnyVal {
-    def args(exprs: Any*): Array[String] = {
-      val strings = sc.parts.iterator
-      val expressions = exprs.iterator
-      val buf = mutable.ArrayBuffer[String]()
-
-      // regex to split on spaces, but using positive lookahead and lookbehind
-      // so the spaces end up in the array instead of being discarded. For
-      // example, using this regular expression to split this string:
-      //
-      //   "parse --schema foo.xsd input"
-      //
-      // Results in the following:
-      //
-      //   Array("parse", " ", "--schema", " ", "foo.xsd", " ", "input")
-      //
-      // This is necessary so that after we build the buf array, the spaces
-      // provide extra information about how to concatenate exprs and
-      // StringContexts
-      val regex = "((?= )|(?<= ))".r
-
-      buf.appendAll(regex.split(strings.next()))
-      while (strings.hasNext) {
-        buf.append(expressions.next().toString)
-        buf.appendAll(regex.split(strings.next()))
-      }
-
-      // At this point, we have an array that is mixed with arguments and
-      // single spaces, for example, this string
-      //
-      //   args"parse --schema=$bar/$baz input"
-      //
-      // Now looks like this:
-      //
-      //   Array("parse", " ", "--schema=", "bar/result", "/", "baz/result", " ", "input")
-      //
-      // We want to concatent any array elements that aren't separated by a
-      // space element and drop all the spaces. The following does this by
-      // accumulating args until we hit a space and adds that to the
-      // final array
-      val (res, lastAccumulated) = buf.foldLeft((mutable.ArrayBuffer[String](), "")) { case ((resArray, accumulated), curVal) =>
-        (accumulated, curVal) match {
-          case ("", " ") => {
-            // curVal is a space, but we have accumulated nothing. This means
-            // there were multiple spaces in a row. Just ignore this curVal
-            (resArray, "")
-          }
-          case (_, " ") => {
-            // curVal is a space and we have accumulated an argument. Append
-            // what we have accumulated to the array and reset the accumulated
-            // string--we will start accumulating for the next arg
-            (resArray :+ accumulated, "")
-          }
-          case _ => {
-            // curVal is not a space. Do not modify the array, but concatenate
-            // the curVal to what we've accumulated so far
-            (resArray, accumulated + curVal)
-          }
-        }
-      }
-      // if non-empty, the last thing we accumulated must be appended to the array
-      val args =
-        if (lastAccumulated != "") {
-          res :+ lastAccumulated
-        } else {
-          res
-        }
-      args.toArray
-    }
-  }
-}
diff --git a/daffodil-cli/src/test/scala/org/apache/daffodil/cli/cliTest/TestBlob.scala b/daffodil-cli/src/test/scala/org/apache/daffodil/cli/cliTest/TestBlob.scala
new file mode 100644
index 000000000..9efd5b324
--- /dev/null
+++ b/daffodil-cli/src/test/scala/org/apache/daffodil/cli/cliTest/TestBlob.scala
@@ -0,0 +1,178 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+package org.apache.daffodil.cliTest
+
+import java.net.URI
+import java.nio.file.Files.exists
+import java.nio.file.Path
+import java.nio.file.Paths
+import org.apache.commons.io.FileUtils
+import org.junit.Test
+import org.junit.Assume.assumeTrue
+import org.junit.Assert.assertEquals
+
+import scala.io.Source
+import org.apache.daffodil.cli.cliTest.Util._
+import org.apache.daffodil.cli.Main.ExitCode
+
+class TestBlob {
+
+  /***
+   * ---- Blob Generation Instructions ----
+   *
+   * These large file tests are commented out so that they are not triggered on
+   * automatic regression tests on the build servers.  In order to run them you
+   * will need to generate the test file(s) using the gen_blob.py script. It can
+   * be found in and should be run from the directory:
+   *
+   * daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/
+   *
+   * Please note that the md5sum that is printed out from the script is the hash
+   * of just the blob portion of the file that is generated, it does not include
+   * the first 8 bytes of the file, which is the length of the blob.
+   *
+   * Make sure that the generated file is located in the following directory:
+   *
+   * daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/
+   *
+   * The exact command used to generate the blob for each test will be listed in
+   * a comment above that test
+   ***/
+
+  private def findInfosetBlob(path: Path): Path = {
+    val contents = Source.fromFile(path.toFile).mkString
+    val blob = contents.substring(contents.indexOf("file://")).takeWhile(_ != '<')
+    Paths.get(new URI(blob))
+  }
+
+  /**
+   * The CLI puts blobs in user.dir / "daffodil-blobs", which cannot be
+   * changed. This should wrap CLI runs so that the blob dir is deleted at the
+   * end
+   */
+  private def withBlobDir(f: => Unit): Unit = {
+    try {
+      f
+    } finally {
+      val blobDir = Paths.get(System.getProperty("user.dir"), "daffodil-blobs")
+      FileUtils.deleteDirectory(blobDir.toFile)
+    }
+  }
+
+  /***
+   * Command to generate blob file:
+   *
+   * python gen_blob.py -s 1 -o 1MB.bin
+   *
+   ***/
+  @Test def test_1MB_blob(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/large_blob.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/1MB.bin")
+
+    assumeTrue("large test input file must be manually generated", exists(input))
+
+    withTempFile { infoset =>
+      withTempFile { unparse =>
+        withBlobDir {
+          runCLI(args"parse -s $schema -o $infoset $input") { cli =>
+          } (ExitCode.Success)
+
+          runCLI(args"unparse -s $schema -o $unparse $infoset") { cli =>
+          } (ExitCode.Success)
+
+          val blob = findInfosetBlob(infoset)
+          assertEquals("bc8f9d01382bf12248747cd6faecbc59", md5sum(blob))
+          assertEquals("72d1f935d7fff766d011757ae03d5b1d", md5sum(unparse))
+        }
+      }
+    }
+  }
+
+  /***
+   * Command to generate blob file:
+   *
+   * python gen_blob.py -s 2049 -o 2049MB.bin
+   *
+   ***/
+  @Test def test_2GB_blob(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/large_blob.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/2049MB.bin")
+
+    assumeTrue("large test input file must be manually generated", exists(input))
+
+    withTempFile { infoset =>
+      withTempFile { unparse =>
+        withBlobDir {
+          runCLI(args"parse -s $schema -o $infoset $input", timeout = 120) { cli =>
+          } (ExitCode.Success)
+
+          runCLI(args"unparse -s $schema -o $unparse $infoset", timeout = 120) { cli =>
+          } (ExitCode.Success)
+
+          val blob = findInfosetBlob(infoset)
+          assertEquals("c5675d3317725595d128af56a624c49f", md5sum(blob))
+          assertEquals("2435c33e55aae043fc9b28f38f5cc2e9", md5sum(unparse))
+        }
+      }
+    }
+  }
+
+  /***
+   * Please note that this uses the same file as test_2GB_blob.
+   *
+   * Command to generate blob file:
+   *
+   * python gen_blob.py -s 2049 -o 2049MB.bin
+   *
+   ***/
+  @Test def test_blob_backtracking(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/blob_backtracking.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/2049MB.bin")
+
+    assumeTrue("large test input file must be manually generated", exists(input))
+
+    withBlobDir {
+      runCLI(args"parse -s $schema $input", timeout = 120) { cli =>
+        cli.expectErr("Attempted to backtrack too far")
+      } (ExitCode.ParseError)
+    }
+  }
+
+  /***
+   * Please note that this uses the same file as test_2GB_blob.
+   *
+   * Command to generate blob file:
+   *
+   * python gen_blob.py -s 2049 -o 2049MB.bin
+   *
+   ***/
+  @Test def test_blob_backtracking_streaming_fail(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/blob_backtracking.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/2049MB.bin")
+
+    assumeTrue("large test input file must be manually generated", exists(input))
+
+    withBlobDir {
+      runCLI(args"parse -s $schema", timeout = 120) { cli =>
+        cli.sendFile(input, inputDone = true)
+        cli.expectErr("Attempted to backtrack too far")
+      } (ExitCode.ParseError)
+    }
+  }
+
+}
diff --git a/daffodil-cli/src/test/scala/org/apache/daffodil/cli/cliTest/TestCLIDebugger.scala b/daffodil-cli/src/test/scala/org/apache/daffodil/cli/cliTest/TestCLIDebugger.scala
new file mode 100644
index 000000000..7c1325df1
--- /dev/null
+++ b/daffodil-cli/src/test/scala/org/apache/daffodil/cli/cliTest/TestCLIDebugger.scala
@@ -0,0 +1,1125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+package org.apache.daffodil.cliTest
+
+import org.junit.Test
+
+import java.nio.file.Files
+import java.nio.charset.StandardCharsets.UTF_8
+import net.sf.expectit.matcher.Matchers.regexp
+import org.apache.daffodil.cli.cliTest.Util._
+import org.apache.daffodil.cli.Main.ExitCode
+
+class TestCLIdebugger {
+
+  @Test def test_3385_CLI_Debugger_invalidExpressions(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input1.txt")
+
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
+
+      cli.sendLine("eval (/invalid)")
+      cli.expect("error: expression evaluation failed: Schema Definition Error:")
+      cli.expect("(debug)")
+
+      cli.sendLine("eval (func())")
+      cli.expect("error: expression evaluation failed: Schema Definition Error: Unsupported function:")
+      cli.expect("(debug)")
+
+      cli.sendLine("eval (/invalid!)")
+      cli.expect("error: expression evaluation failed: Schema Definition Error:")
+      cli.expect("(debug)")
+
+      cli.sendLine("eval (!)")
+      cli.expect("error: expression evaluation failed: Schema Definition Error:")
+      cli.expect("(debug)")
+
+      cli.sendLine("eval (././.\\/)")
+      cli.expect("error: expression evaluation failed: Schema Definition Error:")
+      cli.expect("(debug)")
+
+      cli.sendLine("quit")
+    } (ExitCode.Failure)
+  }
+
+  @Test def test_1591_CLI_Debugger_invalidCommandError(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input1.txt")
+
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
+      cli.sendLine("garbage")
+      cli.expect("error: undefined command: garbage")
+      cli.sendLine("quit")
+    } (ExitCode.Failure)
+  }
+
+  @Test def test_1335_CLI_Debugger_dataAndWrapLength(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input2.txt")
+
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("debug")
+
+      cli.sendLine("info data")
+      cli.expect("0~,~1~,~2~,~3~,~4~,~5~,~6~")
+
+      cli.sendLine("set dataLength -938")
+      cli.sendLine("info data")
+      cli.expect("0~,~1~,~2~,~3~,~4~,~5~,~6~")
+
+      cli.sendLine("continue")
+
+    } (ExitCode.Success)
+  }
+
+  @Test def test_982_CLI_Debugger_simpleDebugger(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input1.txt")
+
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
+      cli.sendLine("continue")
+
+    } (ExitCode.Success)
+  }
+
+  @Test def test_1326_CLI_Debugger_displaysTesting(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input1.txt")
+
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
+
+      cli.sendLine("display eval (.)")
+      cli.sendLine("step")
+      cli.expect("matrix")
+
+      cli.sendLine("info displays")
+      cli.expect("1: eval (.)")
+
+      cli.sendLine("disable display 1")
+      cli.sendLine("info displays")
+      cli.expect("1*: eval (.)")
+      cli.sendLine("step")
+      cli.sendLine("enable display 1")
+
+      cli.sendLine("step")
+      cli.expect("</tns:cell>")
+
+      cli.sendLine("delete display 1")
+      cli.sendLine("step")
+
+      cli.sendLine("enable display 1")
+      cli.expect("error: 1 is not a valid display id")
+
+      cli.sendLine("continue")
+      cli.expect("matrix")
+
+    } (ExitCode.Success)
+  }
+
+  @Test def test_1339_CLI_Debugger_removeHidden(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/cli_schema.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input6.txt")
+
+    runCLI(args"-d parse -s $schema -r e $input") { cli =>
+      cli.expect("(debug)")
+      cli.sendLine("set removeHidden false")
+      cli.sendLine("display info infoset")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      // intentionally look for a newline to make sure normally hidden elements
+      // are output with a trailing newline when the debugger displays them
+      cli.expect("<sneaky></sneaky>\n")
+      cli.sendLine("break g")
+      cli.sendLine("continue")
+      cli.expect("<sneaky>5</sneaky>\n")
+      cli.sendLine("quit")
+    } (ExitCode.Failure)
+  }
+
+  @Test def test_3268_CLI_Debugger_removeHidden2(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/cli_schema.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input6.txt")
+
+    runCLI(args"-d parse -s $schema -r e $input") { cli =>
+      cli.expect("(debug)")
+      cli.sendLine("set removeHidden false")
+      cli.sendLine("display info infoset")
+      cli.sendLine("break g")
+      cli.sendLine("continue")
+      cli.expect("<sneaky>5</sneaky>")
+      cli.sendLine("continue")
+      val result = cli.expect("</ex:e>").getBefore();
+      assert(!result.contains("sneaky"))
+
+    } (ExitCode.Success)
+  }
+
+  @Test def test_1331_CLI_Debugger_breakpointTesting4(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input3.txt")
+
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
+
+      cli.sendLine("break cell")
+      cli.sendLine("break cell")
+
+      cli.sendLine("condition 1 dfdl:occursIndex() mod 2 eq 1")
+      cli.sendLine("condition 2 dfdl:occursIndex() mod 2 eq 0")
+
+      cli.sendLine("info breakpoints")
+      cli.expect("2: cell   { dfdl:occursIndex() mod 2 eq 0 }")
+
+      cli.sendLine("display info occursIndex")
+
+      cli.sendLine("continue")
+      cli.expect("occursIndex: 1")
+
+      cli.sendLine("continue")
+      cli.expect("occursIndex: 2")
+
+      cli.sendLine("continue")
+      cli.expect("occursIndex: 3")
+
+      cli.sendLine("disable breakpoint 2")
+
+      cli.sendLine("continue")
+      cli.expect("occursIndex: 5")
+
+      cli.sendLine("continue")
+      cli.expect("occursIndex: 7")
+
+      cli.sendLine("enable breakpoint 2")
+
+      cli.sendLine("continue")
+      cli.expect("occursIndex: 8")
+
+      cli.sendLine("disable breakpoint 1")
+      cli.sendLine("disable breakpoint 2")
+
+      cli.sendLine("continue")
+      cli.expect("<tns:cell>3</tns:cell>")
+
+    } (ExitCode.Success)
+  }
+
+  @Test def test_1463_CLI_Debugger_breakOnValueOfElement(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input3.txt")
+
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
+
+      cli.sendLine("set breakOnlyOnCreation false")
+      cli.expect("(debug)")
+
+      cli.sendLine("display info infoset")
+      cli.expect("(debug)")
+
+      cli.sendLine("break cell")
+      cli.expect("1: cell")
+      cli.sendLine("condition 1 xsd:string(.) eq '3'")
+      cli.expect("1: cell   { xsd:string(.) eq '3' }")
+
+      cli.sendLine("info breakpoints")
+      cli.expect("breakpoints:")
+      cli.expect("1: cell   { xsd:string(.) eq '3' }")
+
+      cli.sendLine("continue")
+      cli.expect("<tns:cell>3</tns:cell>")
+      cli.expect("</tns:row>")
+      cli.expect("</tns:matrix>")
+      cli.sendLine("continue")
+      cli.expect("<tns:cell>3</tns:cell>")
+      cli.expect("</tns:row>")
+      cli.expect("</tns:matrix>")
+      cli.sendLine("continue")
+      cli.expect("<tns:cell>3</tns:cell>")
+      cli.expect("</tns:row>")
+      cli.expect("</tns:matrix>")
+
+      cli.sendLine("continue")
+      cli.expect("<tns:cell>3</tns:cell>")
+      cli.expect("<tns:cell>3</tns:cell>")
+      cli.expect("</tns:row>")
+      cli.expect("</tns:matrix>")
+      cli.sendLine("continue")
+      cli.expect("<tns:cell>3</tns:cell>")
+      cli.expect("<tns:cell>3</tns:cell>")
+      cli.expect("</tns:row>")
+      cli.expect("</tns:matrix>")
+
+      cli.sendLine("quit")
+    } (ExitCode.Failure)
+  }
+
+  @Test def test_1338_CLI_Debugger_pointsOfUncertaintyInfo(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/cli_schema.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input5.txt")
+
+    runCLI(args"-d parse -s $schema -r Item2 $input") { cli =>
+      cli.expect("(debug)")
+
+      cli.sendLine("display info pointsOfUncertainty")
+
+      cli.sendLine("step")
+      cli.expect("pointsOfUncertainty:")
+      cli.expect("(none)")
+
+      cli.sendLine("step")
+      cli.expect("pointsOfUncertainty:")
+      cli.expect("bitPos: 0, context: choice[1]")
+
+      cli.sendLine("step")
+      cli.expect("pointsOfUncertainty:")
+      cli.expect("(none)")
+
+      cli.sendLine("quit")
+    } (ExitCode.Failure)
+  }
+
+  @Test def test_1328_CLI_Debugger_breakpointTesting(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input1.txt")
+
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
+
+      cli.sendLine("display info infoset")
+      cli.sendLine("break cell")
+
+      cli.sendLine("continue")
+      cli.expect("</tns:cell>")
+
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.expect("<tns:cell>0</tns:cell>")
+
+      cli.sendLine("continue")
+      cli.expect("</tns:cell>")
+
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.expect("<tns:cell>1</tns:cell>")
+
+      cli.sendLine("delete breakpoint 1")
+      cli.sendLine("continue")
+
+    } (ExitCode.Success)
+  }
+
+  @Test def test_1329_CLI_Debugger_breakpointTesting2(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input2.txt")
+
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
+
+      cli.sendLine("display info infoset")
+      cli.sendLine("break cell")
+      cli.sendLine("condition 1 dfdl:occursIndex() eq 3")
+
+      cli.sendLine("info breakpoints")
+      cli.expect("1: cell   { dfdl:occursIndex() eq 3 }")
+
+      cli.sendLine("continue")
+      cli.expect("</tns:cell>")
+
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.expect("<tns:cell>2</tns:cell>")
+
+      cli.sendLine("continue")
+      cli.expect("<tns:cell>6</tns:cell>")
+
+    } (ExitCode.Success)
+  }
+
+  @Test def test_CLI_Debugger_SDE_message(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input2.txt")
+
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
+
+      cli.sendLine("display info infoset")
+      cli.sendLine("break cell")
+      cli.sendLine("condition 1 fn:count(../cell) eq 3") // ../cell is wrong. Needs to be ../tns:cell
+
+      cli.sendLine("continue")
+      cli.expect("Schema Definition Error")
+      cli.expect("{}cell")
+      cli.expect("tns:cell")
+
+    } (ExitCode.Success)
+  }
+
+  @Test def test_1330_CLI_Debugger_breakpointTesting3(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input2.txt")
+
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
+
+      cli.sendLine("display info occursIndex")
+      cli.expect("(debug)")
+      cli.sendLine("break cell")
+      cli.expect("(debug)")
+      cli.sendLine("info breakpoints")
+      cli.expect("1: cell")
+
+      cli.sendLine("continue")
+      cli.expect("occursIndex: 1")
+
+      cli.sendLine("continue")
+      cli.expect("occursIndex: 2")
+
+      cli.sendLine("disable breakpoint 1")
+      cli.sendLine("info breakpoints")
+      cli.expect("1*: cell")
+
+      cli.sendLine("info data")
+      cli.expect("0~,~1~,~2~,~3~,~4~,~5~,~6~")
+
+      cli.sendLine("continue")
+      cli.expect("<tns:cell>6</tns:cell>")
+
+    } (ExitCode.Success)
+  }
+
+  @Test def test_1333_CLI_Debugger_settingInfosetLines(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input3.txt")
+
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
+
+      cli.sendLine("display info infoset")
+      cli.sendLine("set infosetLines 1")
+
+      cli.sendLine("break cell")
+      cli.sendLine("continue")
+      cli.expect("...")
+      cli.expect("</tns:matrix>")
+
+      cli.sendLine("set infosetLines 4")
+      cli.sendLine("continue")
+      cli.expect("...")
+      cli.expect("<tns:cell>3</tns:cell>")
+      cli.expect("</tns:matrix>")
+
+      cli.sendLine("set infosetLines 10")
+      cli.sendLine("continue")
+      cli.expect("<tns:matrix")
+
+      cli.sendLine("set infosetLines -900")
+      cli.sendLine("continue")
+      cli.expect("<tns:matrix")
+      cli.expect("</tns:matrix>")
+
+      cli.sendLine("disable breakpoint 1")
+      cli.sendLine("continue")
+
+    } (ExitCode.Success)
+  }
+
+  @Test def test_1334_CLI_Debugger_infoBitPosition(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input1.txt")
+
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
+
+      cli.sendLine("display info bitPosition")
+      cli.sendLine("display info data")
+      cli.sendLine("break cell")
+
+      cli.sendLine("continue")
+      cli.expect("bitPosition: 0")
+
+      cli.sendLine("continue")
+      cli.expect("bitPosition: 16")
+
+      cli.sendLine("continue")
+      cli.expect("bitPosition: 32")
+
+      cli.sendLine("continue")
+
+    } (ExitCode.Success)
+  }
+
+  @Test def test_1337_CLI_Debugger_childIndex(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input4.txt")
+
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
+
+      cli.sendLine("break cell")
+      cli.sendLine("display info childIndex")
+      cli.sendLine("display info infoset")
+
+      cli.sendLine("continue")
+      cli.expect("childIndex: 1")
+
+      cli.sendLine("continue")
+      cli.expect("childIndex: 2")
+
+      cli.sendLine("continue")
+      cli.expect("childIndex: 4")
+
+      cli.sendLine("disable breakpoint 1")
+      cli.sendLine("continue")
+
+    } (ExitCode.Success)
+  }
+
+  @Test def test_1340_CLI_Debugger_infoPath(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input1.txt")
+
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
+
+      cli.sendLine("break cell")
+      cli.sendLine("display info path")
+
+      cli.sendLine("continue")
+      cli.expect("matrixType::sequence[1]::row::LocalComplexTypeDef::sequence[1]::cell")
+
+      cli.sendLine("delete breakpoint 1")
+      cli.expect("debug")
+      cli.sendLine("continue")
+
+      cli.expect("""<tns:matrix xmlns:tns="http://www.example.org/example1/">""")
+      cli.expect("<tns:cell>2</tns:cell>")
+
+    } (ExitCode.Success)
+  }
+
+  @Test def test_1382_CLI_Debugger_dataAndWrapLength2(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input2.txt")
+
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
+
+      cli.sendLine("break cell")
+      cli.sendLine("continue")
+      cli.sendLine("info data")
+      cli.expect("0~,~1~,~2~,~3~,~4~,~5~,~6~")
+
+      cli.sendLine("set dataLength -938")
+      cli.sendLine("info data")
+      cli.expect("0~,~1~,~2~,~3~,~4~,~5~,~6~")
+
+      cli.sendLine("disable breakpoint 1")
+      cli.sendLine("continue")
+
+    } (ExitCode.Success)
+  }
+
+  @Test def test_1863_CLI_Debugger_groupIndex01(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/cli_schema_03.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input9.txt")
+
+    runCLI(args"-d parse -r list -s $schema $input") { cli =>
+      cli.expect("(debug)")
+
+      cli.sendLine("display info groupIndex")
+      cli.sendLine("break price")
+      cli.expect("1: price")
+      cli.sendLine("break comment")
+      cli.expect("2: comment")
+
+      cli.sendLine("continue")
+      cli.expect("groupIndex: 2")
+      cli.sendLine("continue")
+      cli.expect("groupIndex: 4")
+      cli.sendLine("continue")
+      cli.expect("groupIndex: 2")
+      cli.sendLine("continue")
+      cli.expect("groupIndex: 4")
+      cli.sendLine("continue")
+      cli.expect("<ex:price>89.99</ex:price>")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_1029_CLI_Debugger_validation1(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/cli_schema_03.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input9.txt")
+
+    runCLI(args"-d parse -r list -s $schema $input") { cli =>
+      cli.expect("(debug)")
+
+      cli.sendLine("display info dne1")
+      cli.expect("error: undefined info command: dne1")
+      cli.sendLine("display info bitLimit dne2")
+      cli.expect("error: bitLimit command requires zero arguments")
+      cli.sendLine("display break")
+      cli.expect("error: undefined command: break")
+      cli.sendLine("quit")
+    } (ExitCode.Failure)
+  }
+
+  @Test def test_3258_CLI_Debugger_infodata(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input2.txt")
+
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
+
+      cli.sendLine("display info data")
+      cli.sendLine("step")
+      cli.expect("│") //  (0 to 0)
+      cli.expect("0~,~1~,~2~,~3~,~4~,~5~,~6~")
+
+      cli.sendLine("break cell")
+      cli.sendLine("condition 1 dfdl:occursIndex() eq 5")
+      cli.sendLine("continue")
+
+      cli.expect("""                                  │                                    │""")
+      cli.expect("""    87654321  0011 2233 4455 6677 8899 aabb ccdd eeff  0~1~2~3~4~5~6~7~8~9~a~b~c~d~e~f~""")
+      cli.expect("""    00000000: 302c 312c 322c 332c 342c 352c 36         0~,~1~,~2~,~3~,~4~,~5~,~6~      """)
+      cli.sendLine("continue")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_3264_CLI_Debugger_undefined_command(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input2.txt")
+
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
+
+      cli.sendLine("display data")
+      cli.expect("error: undefined command: data")
+
+      cli.sendLine("set breakonfailure true")
+      cli.expect("error: undefined command: breakonfailure")
+
+      cli.sendLine("continue")
+
+    } (ExitCode.Success)
+  }
+
+  @Test def test_CLI_Debugger_delimiterStack(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input2.txt")
+
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
+
+      cli.sendLine("break row")
+      cli.expect("(debug)")
+
+      cli.sendLine("continue")
+      cli.expect("(debug)")
+
+      cli.sendLine("info delimiterStack")
+      cli.expect("local:  %NL; (separator)")
+      cli.expect("(debug)")
+
+      cli.sendLine("break cell")
+      cli.expect("(debug)")
+
+      cli.sendLine("continue")
+      cli.expect("(debug)")
+
+      cli.sendLine("info delimiterStack")
+      cli.expect("remote: %NL; (separator)")
+      cli.expect("local:  , (separator)")
+
+      cli.sendLine("quit")
+    } (ExitCode.Failure)
+  }
+
+  @Test def test_CLI_Debugger_utf16_encoding(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/utf16schema.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/hextest.txt")
+
+    runCLI(args"-d parse -s $schema -r e2 $input") { cli =>
+      cli.expect("(debug)")
+
+      cli.sendLine("info data")
+      cli.expect("\u240A")
+
+      cli.sendLine("quit")
+    } (ExitCode.Failure)
+  }
+
+  @Test def test_1337_CLI_Debugger_info_infoset(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input1.txt")
+
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
+
+      cli.sendLine("info infoset")
+      cli.expect("No Infoset")
+
+      cli.sendLine("step")
+      cli.sendLine("info infoset")
+      cli.expect("matrix")
+
+      cli.sendLine("quit")
+    } (ExitCode.Failure)
+  }
+
+  @Test def test_CLI_Debugger_InfoHidden_1(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section14/sequence_groups/SequencesWithHiddenRefs.dfdl.xsd")
+
+    withTempFile { input =>
+      Files.write(input, "2~3".getBytes(UTF_8))
+
+      runCLI(args"-d parse -s $schema -r e5 $input") { cli =>
+        cli.expect("(debug)")
+
+        cli.sendLine("break f")
+        cli.sendLine("display info hidden")
+
+        cli.sendLine("continue")
+        cli.expect("hidden: false")
+
+        cli.sendLine("continue")
+        cli.expect("hidden: false")
+
+        cli.sendLine("continue")
+        cli.expect("hidden: true")
+
+        cli.sendLine("continue")
+        cli.expect("hidden: true")
+
+        cli.sendLine("continue")
+        cli.expect("<f xmlns=\"\">2</f>")
+
+      } (ExitCode.Success)
+    }
+  }
+
+  @Test def test_CLI_Debugger_InfoHidden_2(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section14/sequence_groups/SequencesWithHiddenRefs.dfdl.xsd")
+
+    withTempFile { input =>
+      Files.write(input, "2~3".getBytes(UTF_8))
+
+      runCLI(args"-d parse -s $schema -r e4 $input") { cli =>
+        cli.expect("(debug)")
+
+        cli.sendLine("break f")
+        cli.sendLine("display info hidden")
+
+        cli.sendLine("continue")
+        cli.expect("hidden: true")
+
+        cli.sendLine("continue")
+        cli.expect("hidden: true")
+
+        cli.sendLine("continue")
+        cli.expect("hidden: false")
+
+        cli.sendLine("continue")
+        cli.expect("hidden: false")
+
+        cli.sendLine("continue")
+        cli.expect("<f xmlns=\"\">3</f>")
+
+      } (ExitCode.Success)
+    }
+  }
+
+  @Test def test_CLI_Debugger_InfoHidden_3(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section15/choice_groups/ChoicesInHiddenContexts.dfdl.xsd")
+
+    withTempFile { input =>
+      Files.write(input, "2,3".getBytes(UTF_8))
+
+      runCLI(args"-d parse -s $schema -r e8 $input") { cli =>
+        cli.expect("(debug)")
+
+        cli.sendLine("break a")
+        cli.sendLine("break h")
+        cli.sendLine("break g")
+        cli.sendLine("break e")
+        cli.sendLine("break f")
+        cli.sendLine("display info hidden")
+
+        cli.sendLine("continue")
+        cli.expect("hidden: false")
+
+        cli.sendLine("continue")
+        cli.expect("hidden: false")
+
+        cli.sendLine("continue")
+        cli.expect("hidden: false")
+
+        cli.sendLine("continue")
+        cli.expect("hidden: true")
+
+        cli.sendLine("continue")
+        cli.expect("hidden: true")
+
+        cli.sendLine("continue")
+        cli.expect("<a>2</a>")
+        cli.expect("<g></g>")
+      } (ExitCode.Success)
+    }
+  }
+
+  @Test def test_CLI_Debugger_InfoHidden_4(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section15/choice_groups/ChoicesInHiddenContexts.dfdl.xsd")
+
+    withTempFile { input =>
+      Files.write(input, "[6~]9".getBytes(UTF_8))
+
+      runCLI(args"-d parse -s $schema -r e9 $input") { cli =>
+        cli.expect("(debug)")
+
+        cli.sendLine("break e")
+        cli.sendLine("break f")
+        cli.sendLine("break g")
+        cli.sendLine("break h")
+        cli.sendLine("break i")
+        cli.sendLine("display info path hidden")
+
+        cli.sendLine("continue")
+        cli.expect(":f")
+        cli.expect("hidden: true")
+
+        cli.sendLine("continue")
+        cli.expect(":i")
+        cli.expect("hidden: true")
+
+        cli.sendLine("continue")
+        cli.expect(":h")
+        cli.expect("hidden: true")
+
+        cli.sendLine("continue")
+        cli.expect(":e")
+        cli.expect("hidden: true")
+
+        cli.sendLine("continue")
+        cli.expect(":f")
+        cli.expect("hidden: true")
+
+        cli.sendLine("continue")
+        cli.expect(":f")
+        cli.expect("hidden: false")
+
+        cli.sendLine("continue")
+        cli.expect(":g")
+        cli.expect("hidden: false")
+
+        cli.sendLine("continue")
+        cli.expect(":i")
+        cli.expect("hidden: false")
+
+        cli.sendLine("continue")
+        cli.expect(":h")
+        cli.expect("hidden: false")
+
+        cli.sendLine("continue")
+        cli.expect(":e")
+        cli.expect("hidden: true")
+
+        cli.sendLine("continue")
+        cli.expect(":f")
+        cli.expect("hidden: true")
+
+        cli.sendLine("continue")
+        cli.expect("<h></h>")
+      } (ExitCode.Success)
+    }
+  }
+
+  @Test def test_3585_CLI_Debugger_simpleDebugger_unparse(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input12.txt")
+
+    runCLI(args"-d unparse -s $schema -r e1 $input") { cli =>
+      cli.expect("(debug)")
+      cli.sendLine("break e1")
+      cli.expect("1: e1")
+      cli.sendLine("continue")
+      cli.expect("Hello  breakpoint 1: e1")
+      cli.sendLine("info data")
+      cli.expect("4865 6c6c 6f                             Hello")
+      cli.sendLine("quit")
+    } (ExitCode.Failure)
+  }
+
+  @Test def test_3585_CLI_Debugger_prefixLength(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/prefixed_length.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/prefix.txt")
+
+    runCLI(args"-d parse -s $schema $input") { cli =>
+      cli.expect("(debug)")
+      cli.sendLine("display info infoset")
+      cli.expect("(debug)")
+      cli.sendLine("display eval .")
+      cli.sendLine("step")
+      cli.expect("<field></field>")
+      cli.sendLine("step")
+      cli.expect("<field (prefixLength)></field (prefixLength)>")
+      cli.sendLine("step")
+      cli.expect("<field (prefixLength)>4</field (prefixLength)>")
+      cli.sendLine("step")
+      cli.expect("<field>abcd</field>")
+      cli.sendLine("complete")
+      cli.expect("<field>abcd</field>")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_CLI_Debugger_info_variables(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input1.txt")
+
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
+      cli.sendLine("info variables byteOrder")
+      cli.expect("byteOrder: bigEndian (default)")
+      cli.sendLine("quit")
+    } (ExitCode.Failure)
+  }
+
+  @Test def test_CLI_Debugger_info_data_text(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input1.txt")
+
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
+      cli.sendLine("display info data text")
+      cli.expect("(debug)")
+      cli.sendLine("step")
+      cli.expect("0~,~1~,~2~")
+      cli.sendLine("quit")
+    } (ExitCode.Failure)
+  }
+
+  @Test def test_CLI_Debugger_info_data_binary(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input1.txt")
+
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
+      cli.sendLine("display info data binary")
+      cli.expect("(debug)")
+      cli.sendLine("step")
+      cli.expect("302c 312c 32")
+      cli.sendLine("quit")
+    } (ExitCode.Failure)
+  }
+
+  @Test def test_CLI_Debugger_info_diff_01(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section07/variables/variables_01.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input1.txt")
+
+    runCLI(args"-d parse -s $schema -r c $input") { cli =>
+      cli.expect("(debug)")
+      cli.sendLine("display info diff")
+      cli.expect("(debug)")
+      cli.sendLine("step")
+      cli.expect("(no differences)")
+      cli.sendLine("step")
+      cli.expect("(no differences)")
+      cli.sendLine("step")
+      cli.expect("variable: tns:v_with_default: 42 (default) -> 42 (read)")
+      cli.sendLine("step")
+      cli.expect("variable: tns:v_no_default: (undefined) -> 42 (set)")
+      cli.sendLine("step")
+      cli.expect("childIndex: 1 -> 2")
+      cli.sendLine("step")
+      cli.expect("variable: tns:v_no_default: 42 (set) -> 42 (read)")
+      cli.sendLine("step")
+      cli.expect("<d>42</d>")
+      cli.expect("<e>42</e>")
+    } (ExitCode.LeftOverData)
+  }
+
+  @Test def test_CLI_Debugger_info_diff_02(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input1.txt")
+
+    runCLI(args"-d parse -s $schema -r matrix $input") { cli =>
+      cli.expect("(debug)")
+      cli.sendLine("display info diff")
+      cli.expect("(debug)")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.expect("bitPosition: 0 -> 8")
+      cli.expect("foundDelimiter: (no value) -> ,")
+      cli.expect("foundField: (no value) -> 0")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.expect("bitPosition: 8 -> 16")
+      cli.expect("childIndex: 1 -> 2")
+      cli.expect("foundDelimiter: , -> (no value)")
+      cli.expect("foundField: 0 -> (no value)")
+      cli.expect("groupIndex: 1 -> 2")
+      cli.expect("occursIndex: 1 -> 2")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.expect("bitPosition: 16 -> 24")
+      cli.expect("foundDelimiter: (no value) -> ,")
+      cli.expect("foundField: (no value) -> 1")
+      cli.sendLine("quit")
+    } (ExitCode.Failure)
+  }
+
+  @Test def test_CLI_Debugger_info_diff_03(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/cli_schema.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input6.txt")
+
+    runCLI(args"-d parse -s $schema -r e $input") { cli =>
+      cli.expect("(debug)")
+      cli.sendLine("display info diff")
+      cli.expect("(debug)")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.expect("hidden: false -> true")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.expect("hidden: true -> false")
+      cli.sendLine("quit")
+    } (ExitCode.Failure)
+  }
+
+  @Test def test_CLI_Debugger_info_diff_04(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input1.txt.xml")
+
+    runCLI(args"-d unparse -s $schema -r matrix -o $devNull $input") { cli =>
+      cli.expect("(debug)")
+      cli.sendLine("display info diff")
+      cli.expect("(debug)")
+      cli.sendLine("set diffExcludes childIndex")
+      cli.expect("(debug)")
+      cli.sendLine("step")
+      cli.expect("bitPosition: 0 -> 8")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.expect(regexp("\\+ Suppressable.* for cell"))
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.expect(regexp("RegionSplit.* for cell"))
+      cli.sendLine("info suspensions")
+      cli.expect(regexp("Suppressable.* for cell"))
+      cli.expect(regexp("RegionSplit.* for cell"))
+      cli.sendLine("quit")
+    } (ExitCode.Failure)
+  }
+
+  @Test def test_CLI_Debugger_info_diff_05(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/cli_schema_03.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input9.txt.xml")
+
+    runCLI(args"-d unparse -r list -s $schema -o $devNull $input") { cli =>
+      cli.expect("(debug)")
+      cli.sendLine("set diffExcludes doesNotExist1 bitLimit doesNotExist2")
+      cli.expect("unknown or undiffable info commands: doesNotExist1, doesNotExist2")
+
+      cli.sendLine("display info diff")
+      cli.sendLine("break Item")
+      cli.sendLine("continue")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.expect(regexp("\\+ SuppressableSeparator.* ex:Item"))
+      cli.sendLine("continue")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.expect(regexp("\\+ RegionSplit.* ex:Item"))
+      cli.sendLine("step")
+      cli.sendLine("step")
+      cli.expect(regexp("\\- RegionSplit.* ex:Item"))
+      cli.sendLine("info suspensions")
+      cli.expect(regexp("SuppressableSeparator.* ex:Item"))
+      cli.sendLine("step")
+      cli.sendLine("step")
+
+    } (ExitCode.Success)
+  }
+
+  @Test def test_CLI_Debugger_parse_unparser_not_available(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input3.txt")
+
+    runCLI(args"-d parse -s $schema $input") { cli =>
+      cli.expect("(debug)")
+
+      cli.sendLine("info parser")
+      cli.expect("parser: <Element name='matrix'><DelimiterStackParser>...</DelimiterStackParser></Element>")
+
+      cli.sendLine("info unparser")
+      cli.expect("unparser: not available")
+
+      cli.sendLine("continue")
+
+    } (ExitCode.Success)
+  }
+
+  @Test def test_CLI_Debugger_unparse_parser_not_available(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input1.txt.xml")
+
+    runCLI(args"-d unparse -s $schema $input") { cli =>
+      cli.expect("(debug)")
+
+      cli.sendLine("info unparser")
+      cli.expect("unparser: <ConvertTextNumberUnparser/>")
+
+      cli.sendLine("info parser")
+      cli.expect("parser: not available")
+
+      cli.sendLine("continue")
+
+    } (ExitCode.Success)
+  }
+
+}
diff --git a/daffodil-cli/src/test/scala/org/apache/daffodil/cli/cliTest/TestCLIGenerateC.scala b/daffodil-cli/src/test/scala/org/apache/daffodil/cli/cliTest/TestCLIGenerateC.scala
new file mode 100644
index 000000000..67333b15e
--- /dev/null
+++ b/daffodil-cli/src/test/scala/org/apache/daffodil/cli/cliTest/TestCLIGenerateC.scala
@@ -0,0 +1,134 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+package org.apache.daffodil.cliTest
+
+import java.nio.file.Files.exists
+import org.junit.Test
+import org.junit.Assert.assertTrue
+import org.apache.daffodil.cli.cliTest.Util._
+import org.apache.daffodil.cli.Main.ExitCode
+
+/**
+ * Checks that we can run the "daffodil generate c" subcommand with
+ * various options and get expected outputs.
+ */
+class TestCLIGenerateC {
+
+  @Test def test_CLI_Generate_schema(): Unit = {
+    val schema = path("daffodil-runtime2/src/test/resources/org/apache/daffodil/runtime2/ex_nums.dfdl.xsd")
+
+    withTempDir { tempDir =>
+      runCLI(args"generate c -s $schema $tempDir") { cli =>
+      } (ExitCode.Success)
+      assertTrue(exists(tempDir.resolve("c/libruntime/generated_code.c")))
+    }
+  }
+
+  @Test def test_CLI_Generate_noC_error(): Unit = {
+    val schema = path("daffodil-runtime2/src/test/resources/org/apache/daffodil/runtime2/ex_nums.dfdl.xsd")
+
+    withTempDir { tempDir =>
+      runCLI(args"generate -s $schema $tempDir") { cli =>
+        cli.expectErr("Unknown option 's'")
+      } (ExitCode.Usage)
+    }
+  }
+
+  @Test def test_CLI_Generate_otherThanC_error(): Unit = {
+    val schema = path("daffodil-runtime2/src/test/resources/org/apache/daffodil/runtime2/ex_nums.dfdl.xsd")
+
+    withTempDir { tempDir =>
+      runCLI(args"generate vhld -s $schema $tempDir") { cli =>
+        cli.expectErr("Unknown option 's'")
+      } (ExitCode.Usage)
+    }
+  }
+
+  @Test def test_CLI_Generate_noSchema_error(): Unit = {
+    val schema = path("daffodil-runtime2/src/test/resources/org/apache/daffodil/runtime2/ex_nums.dfdl.xsd")
+
+    withTempDir { tempDir =>
+      runCLI(args"generate c $tempDir") { cli =>
+        cli.expectErr("Required option 'schema' not found")
+      } (ExitCode.Usage)
+    }
+  }
+
+  @Test def test_CLI_Generate_twoSchema_error(): Unit = {
+    val schema = path("daffodil-runtime2/src/test/resources/org/apache/daffodil/runtime2/ex_nums.dfdl.xsd")
+
+    withTempDir { tempDir =>
+      runCLI(args"generate c -s $schema -s $schema $tempDir") { cli =>
+        cli.expectErr("you should provide exactly one argument")
+      } (ExitCode.Usage)
+    }
+  }
+
+  @Test def test_CLI_Generate_verbose(): Unit = {
+    val schema = path("daffodil-runtime2/src/test/resources/org/apache/daffodil/runtime2/ex_nums.dfdl.xsd")
+
+    withTempDir { tempDir =>
+      runCLI(args"-v generate c -s $schema $tempDir") { cli =>
+        cli.expectErr("[info] Time (compiling)")
+        cli.expectErr("[info] Time (generating)")
+      } (ExitCode.Success)
+      assertTrue(exists(tempDir.resolve("c/libruntime/generated_code.c")))
+    }
+  }
+
+  @Test def test_CLI_Generate_root(): Unit = {
+    val schema = path("daffodil-runtime2/src/test/resources/org/apache/daffodil/runtime2/ex_nums.dfdl.xsd")
+
+    withTempDir { tempDir =>
+      runCLI(args"generate c -s $schema -r {http://example.com}ex_nums $tempDir") { cli =>
+      } (ExitCode.Success)
+      assertTrue(exists(tempDir.resolve("c/libruntime/generated_code.c")))
+    }
+  }
+
+  @Test def test_CLI_Generate_root_error(): Unit = {
+    val schema = path("daffodil-runtime2/src/test/resources/org/apache/daffodil/runtime2/ex_nums.dfdl.xsd")
+    
+    withTempDir { tempDir =>
+      runCLI(args"generate c -s $schema -r {ex}ex_nums $tempDir") { cli =>
+        cli.expectErr("Schema Definition Error")
+        cli.expectErr("No global element found for {ex}ex_nums")
+      } (ExitCode.GenerateCodeError)
+    }
+  }
+
+  @Test def test_CLI_Generate_namespaceNoRoot_error(): Unit = {
+    val schema = path("daffodil-runtime2/src/test/resources/org/apache/daffodil/runtime2/ex_nums.dfdl.xsd")
+
+    withTempDir { tempDir =>
+      runCLI(args"generate c -s $schema -r {http://example.com} $tempDir") { cli =>
+      cli.expectErr("Invalid syntax for extended QName")
+      } (ExitCode.Usage)
+    }
+  }
+
+  @Test def test_CLI_Generate_tunable(): Unit = {
+    val schema = path("daffodil-runtime2/src/test/resources/org/apache/daffodil/runtime2/ex_nums.dfdl.xsd")
+
+    withTempDir { tempDir =>
+      runCLI(args"generate c -s $schema -T parseUnparsePolicy=parseOnly $tempDir") { cli =>
+      } (ExitCode.Success)
+      assertTrue(exists(tempDir.resolve("c/libruntime/generated_code.c")))
+    }
+  }
+}
diff --git a/daffodil-cli/src/test/scala/org/apache/daffodil/cli/cliTest/TestCLIParsing.scala b/daffodil-cli/src/test/scala/org/apache/daffodil/cli/cliTest/TestCLIParsing.scala
new file mode 100644
index 000000000..ce79937ad
--- /dev/null
+++ b/daffodil-cli/src/test/scala/org/apache/daffodil/cli/cliTest/TestCLIParsing.scala
@@ -0,0 +1,719 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+package org.apache.daffodil.cliTest
+
+import java.nio.charset.StandardCharsets.UTF_8
+import org.apache.commons.io.FileUtils
+import org.junit.Assert._
+import org.junit.Test
+import org.apache.daffodil.cli.cliTest.Util._
+import org.apache.daffodil.cli.Main.ExitCode
+
+class TestCLIparsing {
+
+  @Test def test_3677_CLI_Parsing_elementFormDefault_qualified(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/elementFormDefaultQualified.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r s1") { cli =>
+      cli.sendLine("strng", inputDone = true)
+      cli.expect("<tns:e1>strng</tns:e1>")
+    } (ExitCode.LeftOverData)
+  }
+
+  @Test def test_3678_CLI_Parsing_elementFormDefault_unqualified(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/elementFormDefaultUnqualified.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r s1") { cli =>
+      cli.sendLine("strng", inputDone = true)
+      cli.expect("<e1>strng</e1>")
+    } (ExitCode.LeftOverData)
+  }
+
+  @Test def test_2358_CLI_Parsing_SimpleParse_stdOut_extVars(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/external_variables.dfdl.xsd")
+    val config = path("daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/daffodil_config_cli_test.xml")
+
+    runCLI(args"parse -s $schema -r row -D{http://example.com}var1=99 -c $config") { cli =>
+      cli.sendLine("0", inputDone = true)
+      cli.expect("<tns:row xmlns:tns=\"http://example.com\">")
+      cli.expect("<cell>99</cell>")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_3507_CLI_Parsing_SimpleParse_SaveParser_extVars(): Unit = {
+    withTempFile { parser =>
+      val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/external_variables.dfdl.xsd")
+      val config = path("daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/daffodil_config_cli_test.xml")
+
+      runCLI(args"-v save-parser -s $schema -r row -c $config $parser") { cli =>
+        cli.expectErr("[info] Time (saving)")
+      } (ExitCode.Success)
+
+      runCLI(args"parse -P $parser -D{http://example.com}var1=99") { cli =>
+        cli.sendLine("0", inputDone = true)
+        cli.expect("<tns:row xmlns:tns=\"http://example.com\">")
+        cli.expect("<cell>99</cell>")
+      } (ExitCode.Success)
+
+      runCLI(args"parse -P $parser -D{http://example.com}var1=55") { cli =>
+        cli.sendLine("0", inputDone = true)
+        cli.expect("<tns:row xmlns:tns=\"http://example.com\">")
+        cli.expect("<cell>55</cell>")
+      } (ExitCode.Success)
+    }
+  }
+
+  @Test def test_2360_CLI_Parsing_SimpleParse_stdOut_extVars2(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/external_variables.dfdl.xsd")
+    val config = path("daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/daffodil_config_cli_test.xml")
+
+    runCLI(args"parse -s $schema -r row2 -c $config") { cli =>
+      cli.sendLine("0,1,2", inputDone = true)
+      cli.expect("<cell>-9</cell>")
+      cli.expect("<cell>-2</cell>")
+      cli.expect("<cell>-8</cell>")
+    } (ExitCode.LeftOverData)
+  }
+
+  @Test def test_3506_CLI_Parsing_SimpleParse_extVars2(): Unit = {
+    withTempFile { parser =>
+      val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/external_variables.dfdl.xsd")
+
+      runCLI(args"-v save-parser -s $schema -r row $parser") { cli =>
+        cli.expectErr("[info] Time (saving)")
+      } (ExitCode.Success)
+
+      runCLI(args"parse --parser $parser") { cli =>
+        cli.sendLine("0", inputDone = true)
+        cli.expect("<tns:row xmlns:tns=\"http://example.com\">")
+        cli.expect("<cell>-1</cell>")
+      } (ExitCode.Success)
+
+      runCLI(args"parse --parser $parser -D{http://example.com}var1=55") { cli =>
+        cli.sendLine("0", inputDone = true)
+        cli.expect("<tns:row xmlns:tns=\"http://example.com\">")
+        cli.expect("<cell>55</cell>")
+      } (ExitCode.Success)
+    }
+  }
+
+  @Test def test_CLI_Parsing_SimpleParse_extVars_error(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/external_variables.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r row2 -DdoesNotExist=1") { cli =>
+      cli.sendLine("0,1,2", inputDone = true)
+      cli.expectErr("definition not found")
+      cli.expectErr("doesNotExist")
+    } (ExitCode.BadExternalVariable)
+  }
+
+  @Test def test_3227_CLI_Parsing_SimpleParse_DFDL1197_fix(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section12/delimiter_properties/testOptionalInfix.dfdl.xsd")
+
+    runCLI(args"-vv parse -s $schema") { cli =>
+      cli.sendLine("1/3", inputDone = true)
+      cli.expectErr("<Sequence><Separator/><RepMinMax name='s1'>")
+    } (ExitCode.LeftOverData)
+  }
+
+  @Test def test_1593_CLI_Parsing_MultifileSchema_noGlobalElem(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/namespaces/multi_base_21.dfdl.xsd")
+
+    runCLI(args"parse -s $schema") { cli =>
+      cli.send("does not matter", inputDone = true)
+      cli.expectErr("No global elements")
+      cli.expectErr("multi_base_21.dfdl.xsd")
+    } (ExitCode.UnableToCreateProcessor)
+  }
+
+  //  See comment in DAFFODIL-952
+  //
+  //  Also note that this test is important in showing the expected existence
+  //  and ordering of XML namespace prefix mappings. Daffodil ensures
+  //  consistent and repeatable output of namespace prefix mappings, but normal
+  //  TDML tests do not verify this part of expected infosets. This is one test
+  //  verifies the expected output. If this test fails, it likely means we've
+  //  broken our attempts to create consistent prefix mappings.
+  @Test def test_1585_CLI_Parsing_MultifileSchema_methodImportSameDir(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/namespaces/multi_base_14.dfdl.xsd")
+
+    runCLI(args"parse -s $schema") { cli =>
+      cli.send("test", inputDone = true)
+      cli.expect("""<base14:rabbitHole xmlns:a14="http://a14.com" xmlns:b14="http://b14.com" xmlns:base14="http://baseSchema.com">""")
+      cli.expect("<a14:nestSequence>")
+      cli.expect("<b14:nest>test</b14:nest>")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_1586_CLI_Parsing_MultifileSchema_methodIncludeSameDir(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/namespaces/multi_base_15.dfdl.xsd")
+
+    runCLI(args"parse -s $schema") { cli =>
+      cli.send("test", inputDone = true)
+      cli.expect("<rabbitHole>")
+      cli.expect("<nest>test</nest>")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_1587_CLI_Parsing_MultifileSchema_methodImportSameDir2(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/namespaces/multi_base_16.dfdl.xsd")
+
+    runCLI(args"parse -s $schema") { cli =>
+      cli.send("test", inputDone = true)
+      cli.expect("<rabbitHole>")
+      cli.expect("<nest>test</nest>")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_1317_IBMCompatibility_ABC_test_ibm_abc_cli(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/namespaces/ABC_IBM.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r ABC") { cli =>
+      cli.sendLine("abcabcabc", inputDone = true)
+      cli.expect("<Container>")
+      cli.expect("<c>c</c>")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_977_CLI_Parsing_SimpleParse_stdOut(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r matrix") { cli =>
+      cli.sendLine("0,1,2", inputDone = true)
+      cli.expect("<tns:cell>2</tns:cell>")
+    } (ExitCode.LeftOverData)
+  }
+
+  @Test def test_978_CLI_Parsing_SimpleParse_outFile(): Unit = {
+    withTempFile { output =>
+      val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+
+      runCLI(args"parse -s $schema -r matrix -o $output") { cli =>
+        cli.sendLine("0,1,2", inputDone = true)
+      } (ExitCode.LeftOverData)
+
+      val res = FileUtils.readFileToString(output.toFile, UTF_8)
+      assertTrue(res.contains("<tns:cell>2</tns:cell>"))
+    }
+  }
+
+  @Test def test_979_CLI_Parsing_SimpleParse_inFile(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input1.txt")
+
+    runCLI(args"parse -s $schema -r matrix $input") { cli =>
+      cli.expect("<tns:cell>2</tns:cell>")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_980_CLI_Parsing_SimpleParse_stOutDash(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input1.txt")
+
+    runCLI(args"parse -s $schema -r matrix -o - $input") { cli =>
+      cli.expect("<tns:cell>2</tns:cell>")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_981_CLI_Parsing_SimpleParse_stdInDash(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r matrix -") { cli =>
+      cli.sendLine("0,1,2,3", inputDone = true)
+      cli.expect("<tns:cell>3</tns:cell>")
+    } (ExitCode.LeftOverData)
+  }
+
+  @Test def test_983_CLI_Parsing_SimpleParse_verboseMode(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+
+    runCLI(args"-v parse -s $schema -r matrix -") { cli =>
+      cli.sendLine("0,1", inputDone = true)
+      cli.expectErr("[info]")
+    } (ExitCode.LeftOverData)
+
+    runCLI(args"-vv parse -s $schema -r matrix -") { cli =>
+      cli.sendLine("0,1", inputDone = true)
+      cli.expectErr("[debug]")
+    } (ExitCode.LeftOverData)
+  }
+
+  @Test def test_984_CLI_Parsing_negativeTest(): Unit = {
+    runCLI(args"parse") { cli =>
+      cli.sendLine("0,1,2,3", inputDone = true)
+      cli.expectErr("There should be exactly one of the following options: schema, parser")
+    } (ExitCode.Usage)
+  }
+
+  @Test def test_985_CLI_Parsing_SimpleParse_defaultRoot(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+
+    runCLI(args"parse -s $schema") { cli =>
+      cli.sendLine("0,1,2,3", inputDone = true)
+      cli.expect("<tns:cell>3</tns:cell>")
+    } (ExitCode.LeftOverData)
+  }
+
+  @Test def test_988_CLI_Parsing_SimpleParse_specifiedRoot(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r hcp2") { cli =>
+      cli.sendLine("12", inputDone = true)
+      cli.expect("<tns:hcp2")
+      cli.expect("12")
+      cli.expect("</tns:hcp2>")
+    } (ExitCode.LeftOverData)
+  }
+
+  @Test def test_996_CLI_Parsing_negativeTest04(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r unknown") { cli =>
+      cli.sendLine("12", inputDone = true)
+      cli.expectErr("No root element found for unknown in any available namespace")
+    } (ExitCode.UnableToCreateProcessor)
+  }
+
+  @Test def test_997_CLI_Parsing_multSchemas(): Unit = {
+    val schema1 = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val schema2 = path("daffodil-test/src/test/resources/org/apache/daffodil/section07/defineFormat/defineFormat.dfdl.xsd")
+
+    runCLI(args"parse -s $schema1 -s $schema2 -r hcp2") { cli =>
+      cli.sendLine("12", inputDone = true)
+      cli.expectErr("Bad arguments for option 'schema'")
+    } (ExitCode.Usage)
+  }
+
+  @Test def test_3661_CLI_Parsing_badSchemaPath(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/doesnotexist.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r root") { cli =>
+      cli.sendLine("12", inputDone = true)
+      cli.expectErr("Bad arguments for option 'schema'")
+      cli.expectErr("Could not find file or resource")
+    } (ExitCode.Usage)
+  }
+
+  @Test def test_1002_CLI_Parsing_negativeTest03(): Unit = {
+
+    runCLI(args"parse -P parserThatDoesNotExist") { cli =>
+      cli.sendLine("0,1,2", inputDone = true)
+      cli.expectErr("parserThatDoesNotExist")
+    } (ExitCode.FileNotFound)
+  }
+
+  @Test def test_1003_CLI_Parsing_SimpleParse_emptyNamespace(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section07/defineFormat/defineFormat.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input7.txt")
+
+    runCLI(args"parse -s $schema -r {}address $input") { cli =>
+      cli.expect("<address>")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_1004_CLI_Parsing_SimpleParse_namespaceUsed(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input8.txt")
+
+    runCLI(args"parse -s $schema -r {target}matrix $input") { cli =>
+      cli.expect("""<tns:matrix xmlns:tns="target">""")
+      cli.expect("<cell>14</cell>")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_2615_CLI_Parsing_SimpleParse_namespaceUsedLongOpt(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input8.txt")
+
+    runCLI(args"parse -s $schema --root {target}matrix $input") { cli =>
+      cli.expect("""<tns:matrix xmlns:tns="target">""")
+      cli.expect("<cell>14</cell>")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_1005_CLI_Parsing_SimpleParse_rootPath(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r hcp2 -p /") { cli =>
+      cli.send("12", inputDone = true)
+      cli.expect("<tns:hcp2 xmlns:tns=\"http://www.example.org/example1/\">")
+      cli.expect("12")
+      cli.expect("</tns:hcp2>")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_1015_CLI_Parsing_SimpleParse_defaultRootMultSchema(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section07/defineFormat/defineFormat.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input7.txt")
+
+    runCLI(args"parse -s $schema $input") { cli =>
+      cli.expect("<address>")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_XXX_CLI_Parsing_SimpleSchema_basicTest_validationOn(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r matrix --validate on") { cli =>
+      cli.sendLine("0,1,2", inputDone = true)
+      cli.expect("<tns:cell>2</tns:cell>")
+    } (ExitCode.LeftOverData)
+  }
+
+  @Test def test_XXX_CLI_Parsing_SimpleSchema_basicTest_validation_missing_mode(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r matrix --validate") { cli =>
+      cli.sendLine("0,1,2", inputDone = true)
+      cli.expectErr("Bad arguments")
+      cli.expectErr("validate")
+      cli.expectErr("exactly one argument")
+    } (ExitCode.Usage)
+  }
+
+  @Test def test_XXX_CLI_Parsing_SimpleSchema_basicTest_validationLimited(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r matrix --validate limited") { cli =>
+      cli.sendLine("0,1,2", inputDone = true)
+      cli.expect("<tns:cell>2</tns:cell>")
+    } (ExitCode.LeftOverData)
+  }
+
+  @Test def test_XXX_CLI_Parsing_SimpleSchema_basicTest_validationOff(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r matrix --validate off") { cli =>
+      cli.sendLine("0,1,2", inputDone = true)
+      cli.expect("<tns:cell>2</tns:cell>")
+    } (ExitCode.LeftOverData)
+  }
+
+  @Test def test_XXX_CLI_Parsing_SimpleSchema_basicTest_validationFooBar(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+
+    runCLI(args"parse --validate FooBar -s $schema -r matrix") { cli =>
+      cli.sendLine("0,1,2", inputDone = true)
+      cli.expectErr("FooBar")
+    } (ExitCode.Usage)
+  }
+
+  @Test def test_1319_CLI_Parsing_invalidElementSDE(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/ABC_IBM_invalid.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r ABC") { cli =>
+      cli.sendLine("ababababbaacccccb", inputDone = true)
+      cli.expectErr("'fixed' is not a valid")
+    } (ExitCode.UnableToCreateProcessor)
+  }
+
+  @Test def test_1346_CLI_Parsing_SimpleParse_defaultRootMultSchemaMultiple(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section07/defineFormat/defineFormat.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input7.txt")
+    for (x <- 1 to 10) {
+      runCLI(args"parse -s $schema $input") { cli =>
+        cli.expect("<address>")
+      } (ExitCode.Success)
+    }
+  }
+
+  @Test def test_1386_CLI_Parsing_negativeTest05(): Unit = {
+    runCLI(args"") { cli =>
+      cli.sendLine("12", inputDone = true)
+      cli.expectErr("Subcommand required")
+    } (ExitCode.Usage)
+  }
+
+  @Test def test_1971_CLI_Parsing_traceMode01(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/namespaces/multi_base_15.dfdl.xsd")
+
+    runCLI(args"-t parse -s $schema") { cli =>
+      cli.sendLine("test", inputDone = true)
+      cli.expect("parser: <Element name='rabbitHole'>")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_1973_CLI_Parsing_traceMode03(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+
+    runCLI(args"-t parse -s $schema") { cli =>
+      cli.sendLine("0,1,2,3,,,,", inputDone = true)
+      cli.expectErr("Left over data. Consumed 56 bit(s) with at least")
+      cli.expectErr("Left over data (Hex) starting at byte 8 is: (")
+      cli.expectErr("Left over data (UTF-8) starting at byte 8 is: (")
+    } (ExitCode.LeftOverData)
+  }
+
+  @Test def test_1941_CLI_Parsing_SimpleParse_leftOverData(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r matrix") { cli =>
+      cli.sendLine("1,2,3,4,,,", inputDone = true)
+      cli.expectErr("Left over data. Consumed 56 bit(s) with at least")
+      cli.expectErr("Left over data (Hex) starting at byte 8 is: (")
+      cli.expectErr("Left over data (UTF-8) starting at byte 8 is: (")
+    } (ExitCode.LeftOverData)
+  }
+
+  @Test def test_CLI_Parsing_BitParse_LSBPartialByte_leftOverData(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/bits_parsing.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r lsbPartialByte") { cli =>
+      cli.send("stri", inputDone = true)
+      cli.expectErr("Left over data. Consumed 10 bit(s) with at least 16 bit(s) remaining."
+        + "\nLeft over data starts with partial byte. Left over data (Binary) at byte 2 is: (0b011101xx)"
+        + "\nLeft over data (Hex) starting at byte 3 is: (0x7269...)"
+        + "\nLeft over data (UTF-8) starting at byte 3 is: (ri...)")
+    } (ExitCode.LeftOverData)
+  }
+
+  @Test def test_CLI_Parsing_BitParse_MSBPartialByte_leftOverData(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/bits_parsing.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r msbPartialByte") { cli =>
+      cli.send("stri", inputDone = true)
+      cli.expectErr("Left over data. Consumed 10 bit(s) with at least 16 bit(s) remaining."
+        + "\nLeft over data starts with partial byte. Left over data (Binary) at byte 2 is: (0bxx110100)"
+        + "\nLeft over data (Hex) starting at byte 3 is: (0x7269...)"
+        + "\nLeft over data (UTF-8) starting at byte 3 is: (ri...)")
+    } (ExitCode.LeftOverData)
+  }
+
+  @Test def test_CLI_Parsing_BitParse_MSBFullByte_leftOverData(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/bits_parsing.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r msbFullByte") { cli =>
+      cli.send("stri", inputDone = true)
+      cli.expectErr("Left over data. Consumed 16 bit(s) with at least 16 bit(s) remaining."
+        + "\nLeft over data (Hex) starting at byte 3 is: (0x7269...)"
+        + "\nLeft over data (UTF-8) starting at byte 3 is: (ri...)")
+    } (ExitCode.LeftOverData)
+  }
+
+  @Test def test_DFDL_714(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/global_element.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/test_DFDL-714.txt")
+
+    runCLI(args"parse -s $schema $input") { cli =>
+      cli.expect("<tns:elem xmlns:tns=\"http://baseSchema.com\">")
+      cli.expect("<content")
+      cli.expect("Hello World")
+      cli.expect("</tns:elem>")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_DFDL_1203_schema_from_jar(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/global_element.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/test_DFDL-714.txt")
+
+    runCLI(args"parse -s $schema $input") { cli =>
+      cli.expect("<tns:elem xmlns:tns=\"http://baseSchema.com\">")
+      cli.expect("<content")
+      cli.expect("Hello World")
+      cli.expect("</tns:elem>")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_3606_CLI_Parsing_SimpleParse_largeInfoset(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r matrix") { cli =>
+      val longInput = "0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1,24,5,64,0,1 [...]
+      cli.sendLine(longInput, inputDone = true)
+      val result = cli.expect("<tns:row").getBefore()
+      if (result.contains("""<tns:matrix xmlns:tns="http://www.example.org/example1/"><tns:matrix xmlns:tns="http://www.example.org/example1/">""")) {
+        throw new Exception("Error - Root has been duplicated")
+      }
+    } (ExitCode.LeftOverData)
+  }
+
+  @Test def test_CLI_Parsing_built_in_formats(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/cli_schema_04.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input6.txt")
+
+    runCLI(args"parse -s $schema -r e $input") { cli =>
+      cli.expectErr("Schema Definition Warning")
+      cli.expectErr("edu/illinois/ncsa/daffodil/xsd/built-in-formats.xsd")
+      cli.expectErr("org/apache/daffodil/xsd/DFDLGeneralFormat.dfdl.xsd")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_CLI_Parsing_JavaDefaults(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+
+    withSysProp("javax.xml.parsers.SAXParserFactory" -> "com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl") {
+      withSysProp("javax.xml.xml.validation.SchemaFactory" -> "com/sun/org/apache/xerces/internal/jaxp/validation/XMLSchemaFactory") {
+        runCLI(args"parse -s $schema -r matrix") { cli =>
+          cli.sendLine("0,1,2", inputDone = true)
+          cli.expect("<tns:cell>2</tns:cell>")
+        } (ExitCode.LeftOverData)
+      }
+    }
+  }
+
+  @Test def test_XXX_CLI_Parsing_Stream_01(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/cli_schema_02.dfdl.xsd")
+
+    runCLI(args"parse --stream -s $schema") { cli =>
+      cli.send("123", inputDone = true)
+      cli.expect("<a>1</a>")
+      cli.expect("<a>2</a>")
+      cli.expect("<a>3</a>")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_XXX_CLI_Parsing_Stream_02(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/cli_schema_02.dfdl.xsd")
+
+    runCLI(args"parse --stream -s $schema") { cli =>
+      cli.send("123ab", inputDone = true)
+      cli.expect("<a>1</a>")
+      cli.expect("<a>2</a>")
+      cli.expect("<a>3</a>")
+      cli.expectErr("Left over data after consuming 0 bits while streaming.")
+      cli.expectErr("Stopped after consuming 24 bit(s) with at least 16 bit(s) remaining.")
+    } (ExitCode.LeftOverData)
+  }
+
+  @Test def test_XXX_CLI_Parsing_Stream_03(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/cli_schema_02.dfdl.xsd")
+
+    runCLI(args"--trace parse --stream -s $schema") { cli =>
+      cli.send("123", inputDone = true)
+      cli.expect("<a>1</a>")
+      cli.expect("bitPosition: 8")
+      cli.expect("<a>2</a>")
+      cli.expect("bitPosition: 16")
+      cli.expect("<a>3</a>")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_CLI_Parsing_XCatalog_Resolution_Failure(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/xcatalog_import_failure.dfdl.xsd")
+    val xcatalog = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/xcatalog_invalid.xml")
+
+    withSysProp("xml.catalog.files" -> xcatalog.toAbsolutePath.toString) {
+      runCLI(args"parse -s $schema") { cli =>
+        cli.send("X", inputDone = true)
+        cli.expectErr("Schema Definition Error")
+        cli.expectErr("non_existent_file.xml")
+      } (ExitCode.UnableToCreateProcessor)
+    }
+  }
+
+  @Test def test_XXX_CLI_Parsing_SimpleParse_w3cdom(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
+
+    runCLI(args"parse -I w3cdom -s $schema -r e1") { cli =>
+      cli.send("Hello", inputDone = true)
+      cli.expect("""<tns:e1 xmlns:tns="http://example.com">Hello</tns:e1>""")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_XXX_CLI_Parsing_SimpleParse_jdom(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
+
+    runCLI(args"parse -I jdom -s $schema -r e1") { cli =>
+      cli.send("Hello", inputDone = true)
+      cli.expect("""<tns:e1 xmlns:tns="http://example.com">Hello</tns:e1>""")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_XXX_CLI_Parsing_SimpleParse_scala_xml(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
+
+    runCLI(args"parse -I scala-xml -s $schema -r e1") { cli =>
+      cli.send("Hello", inputDone = true)
+      cli.expect("""<tns:e1 xmlns:tns="http://example.com">Hello</tns:e1>""")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_XXX_CLI_Parsing_SimpleParse_json(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
+
+    runCLI(args"parse -I json -s $schema -r e1") { cli =>
+      cli.send("Hello", inputDone = true)
+      cli.expect(""""e1": "Hello"""")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_XXX_CLI_Parsing_SimpleParse_null(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
+    withTempFile { output =>
+      // should create no output to the file.
+      runCLI(args"parse -I null -s $schema -r e1 -o $output") { cli =>
+        cli.send("Hello", inputDone = true)
+      }(ExitCode.Success)
+
+      val res = FileUtils.readFileToString(output.toFile, UTF_8)
+      assertTrue(res.contains(""))
+    }
+  }
+
+  @Test def test_XXX_CLI_Parsing_SimpleParse_sax(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
+
+    runCLI(args"parse -I sax -s $schema -r e1") { cli =>
+      cli.send("Hello", inputDone = true)
+      cli.expect("""<tns:e1 xmlns:tns="http://example.com">Hello</tns:e1>""")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_XXX_CLI_Parsing_SimpleParse_exi(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
+
+    runCLI(args"parse -I exi -s $schema -r e1") { cli =>
+      cli.send("Hello", inputDone = true)
+    } (ExitCode.Success)
+  }
+
+  @Test def test_XXX_CLI_Parsing_SimpleParse_exisa(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
+
+    runCLI(args"parse -I exisa -s $schema -r e1") { cli =>
+      cli.send("Hello", inputDone = true)
+    } (ExitCode.Success)
+  }
+
+  @Test def test_CLI_Error_Return_Codes(): Unit = {
+    val schema = path("this.does.not.exist")
+
+    runCLI(args"parse -I scala-xml -s $schema -r e1") { cli =>
+      cli.expectErr("this.does.not.exist")
+    } (ExitCode.Usage)
+  }
+
+  @Test def test_2575_DFDLX_Trace_output(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/trace_input.dfdl.xsd")
+
+    runCLI(args"-v parse -r output -s $schema") { cli =>
+      cli.send("0", inputDone = true)
+      cli.expectErr("dfdlx:trace")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_Invalid_Configuration_File(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/single.dfdl.xsd")
+    val config = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/single_conf_bad.txt")
+
+    runCLI(args"parse -s $schema -c $config") { cli =>
+      cli.sendLine("0", inputDone = true)
+      cli.expectErr("Unable to load configuration")
+    } (ExitCode.ConfigError)
+  }
+}
diff --git a/daffodil-cli/src/test/scala/org/apache/daffodil/cli/cliTest/TestCLIPerformance.scala b/daffodil-cli/src/test/scala/org/apache/daffodil/cli/cliTest/TestCLIPerformance.scala
new file mode 100644
index 000000000..0447b6b4b
--- /dev/null
+++ b/daffodil-cli/src/test/scala/org/apache/daffodil/cli/cliTest/TestCLIPerformance.scala
@@ -0,0 +1,177 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+package org.apache.daffodil.cliTest
+
+import org.junit.Test
+import org.apache.daffodil.cli.cliTest.Util._
+import org.apache.daffodil.cli.Main.ExitCode
+
+class TestCLIPerformance {
+
+  @Test def test_3393_CLI_Performance_2_Threads_2_Times(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input1.txt")
+
+    runCLI(args"performance -N 2 -t 2 -s $schema -r matrix $input") { cli =>
+      cli.expect("total parse time (sec):")
+      cli.expect("avg rate (files/sec):")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_XXX_CLI_Performance_2_Threads_2_Times_sax(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input1.txt")
+
+    runCLI(args"performance -I sax -N 2 -t 2 -s $schema -r matrix $input") { cli =>
+      cli.expect("total parse time (sec):")
+      cli.expect("avg rate (files/sec):")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_XXX_CLI_Performance_2_Threads_2_Times_exi(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input1.txt")
+
+    runCLI(args"performance -I exi -N 2 -t 2 -s $schema -r matrix $input") { cli =>
+      cli.expect("total parse time (sec):")
+      cli.expect("avg rate (files/sec):")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_XXX_CLI_Performance_2_Threads_2_Times_exisa(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input1.txt")
+
+    runCLI(args"performance -I exisa -N 2 -t 2 -s $schema -r matrix $input") { cli =>
+      cli.expect("total parse time (sec):")
+      cli.expect("avg rate (files/sec):")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_3394_CLI_Performance_3_Threads_20_Times(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input1.txt")
+
+    runCLI(args"performance -N 20 -t 3 -s $schema -r matrix $input") { cli =>
+      cli.expect("total parse time (sec):")
+      cli.expect("avg rate (files/sec):")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_3395_CLI_Performance_5_Threads_50_Times(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/cli_schema.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input5.txt")
+
+    runCLI(args"performance -N 50 -t 5 -s $schema -r Item2 $input") { cli =>
+      cli.expect("total parse time (sec):")
+      cli.expect("avg rate (files/sec):")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_3396_CLI_Performance_2_Threads_2_Times_Negative(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input5.txt")
+
+    runCLI(args"performance -N 2 -t 2 -s $schema $input") { cli =>
+      cli.expect("total parse time (sec):")
+      cli.expect("avg rate (files/sec):")
+      cli.expectErr("error")
+    } (ExitCode.PerformanceTestError)
+  }
+
+  @Test def test_3641_CLI_Performance_Unparse_2_Threads_2_Times(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input14.txt")
+
+    runCLI(args"performance --unparse -N 2 -t 2 -s $schema -r e3 $input") { cli =>
+      cli.expect("total unparse time (sec):")
+      cli.expect("avg rate (files/sec):")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_XXX_CLI_Performance_Unparse_2_Threads_2_Times_sax(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input14.txt")
+
+    runCLI(args"performance --unparse -I sax -N 2 -t 2 -s $schema -r e3 $input") { cli =>
+      cli.expect("total unparse time (sec):")
+      cli.expect("avg rate (files/sec):")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_XXX_CLI_Performance_Unparse_2_Threads_2_Times_exi(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input14.exi")
+
+    runCLI(args"performance --unparse -I exi -N 2 -t 2 -s $schema -r e3 $input") { cli =>
+      cli.expect("total unparse time (sec):")
+      cli.expect("avg rate (files/sec):")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_XXX_CLI_Performance_Unparse_2_Threads_2_Times_exisa(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input14.exisa")
+
+    runCLI(args"performance --unparse -I exisa -N 2 -t 2 -s $schema -r e3 $input") { cli =>
+      cli.expect("total unparse time (sec):")
+      cli.expect("avg rate (files/sec):")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_XXX_CLI_Performance_Unparse_2_Threads_2_Times_null(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input14.txt")
+
+    runCLI(args"performance --unparse -I null -N 2 -t 2 -s $schema -r e3 $input") { cli =>
+      cli.expect("total unparse time (sec):")
+      cli.expect("avg rate (files/sec):")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_3643_CLI_Performance_Unparse_3_Threads_20_Times(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input14.txt")
+
+    runCLI(args"performance --unparse -N 20 -t 3 -s $schema -r e3 $input") { cli =>
+      cli.expect("total unparse time (sec):")
+      cli.expect("avg rate (files/sec):")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_3644_CLI_Performance_Unparse_5_Threads_50_Times(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input14.txt")
+
+    runCLI(args"performance --unparse -N 50 -t 5 -s $schema -r e3 $input") { cli =>
+      cli.expect("total unparse time (sec):")
+      cli.expect("avg rate (files/sec):")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_3642_CLI_Performance_Unparse_2_Threads_2_Times_Negative(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input16.txt")
+
+    runCLI(args"performance --unparse -N 2 -t 2 -s $schema $input") { cli =>
+      cli.expect("total unparse time (sec):")
+      cli.expect("avg rate (files/sec):")
+      cli.expectErr("error")
+    } (ExitCode.PerformanceTestError)
+  }
+}
diff --git a/daffodil-cli/src/test/scala/org/apache/daffodil/cli/cliTest/TestCLISaveParser.scala b/daffodil-cli/src/test/scala/org/apache/daffodil/cli/cliTest/TestCLISaveParser.scala
new file mode 100644
index 000000000..619004fb8
--- /dev/null
+++ b/daffodil-cli/src/test/scala/org/apache/daffodil/cli/cliTest/TestCLISaveParser.scala
@@ -0,0 +1,269 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+package org.apache.daffodil.cliTest
+
+import org.junit.Test
+import org.apache.daffodil.cli.cliTest.Util._
+import org.apache.daffodil.cli.Main.ExitCode
+
+class TestCLISaveParser {
+
+  @Test def test_3017_CLI_Saving_SaveParser_simple(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+
+    withTempFile { parser =>
+      runCLI(args"save-parser -s $schema -r matrix $parser") { cli =>
+      } (ExitCode.Success)
+
+      runCLI(args"parse --parser $parser") { cli =>
+        cli.sendLine("0,1,2", inputDone = true)
+        cli.expect("<tns:cell>2</tns:cell>")
+      } (ExitCode.LeftOverData)
+    }
+  }
+
+  @Test def test_3018_CLI_Saving_SaveParser_stdout(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/external_variables.dfdl.xsd")
+
+    runCLI(args"-v save-parser -s $schema") { cli =>
+      cli.expectErr("[info] Time")
+      cli.expect("DAFFODIL") // check for magic number
+    } (ExitCode.Success)
+  }
+
+  @Test def test_3019_CLI_Saving_SaveParser_withConfig(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/external_variables.dfdl.xsd")
+    val config = path ("daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/daffodil_config_cli_test.xml")
+
+    withTempFile { parser =>
+      runCLI(args"save-parser -s $schema -r row2 -c $config $parser") { cli =>
+      } (ExitCode.Success)
+
+      runCLI(args"parse --parser $parser -c $config") { cli =>
+        cli.sendLine("0,1,2", inputDone = true)
+        cli.expect("<cell>-9</cell>")
+        cli.expect("<cell>-2</cell>")
+        cli.expect("<cell>-8</cell>")
+      } (ExitCode.LeftOverData)
+    }
+  }
+
+  @Test def test_3020_CLI_Saving_SaveParser_namespaceUsed(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input8.txt")
+
+    withTempFile { parser =>
+      runCLI(args"save-parser -s $schema -r {target}matrix $parser") { cli =>
+      } (ExitCode.Success)
+
+      runCLI(args"parse --parser $parser $input") { cli =>
+        cli.expect("<cell>14</cell>")
+      } (ExitCode.Success)
+    }
+  }
+
+  @Test def test_3021_CLI_Saving_SaveParser_path(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+
+    withTempFile { parser =>
+      runCLI(args"save-parser -s $schema -r matrix -p / $parser") { cli =>
+      } (ExitCode.Success)
+
+      runCLI(args"parse --parser $parser") { cli =>
+        cli.sendLine("0,1,2", inputDone = true)
+        cli.expect("<tns:cell>2</tns:cell>")
+      } (ExitCode.LeftOverData)
+    }
+  }
+
+  @Test def test_3022_CLI_Saving_SaveParser_MultSchema(): Unit = {
+    val schema1 = path("daffodil-test/src/test/resources/org/apache/daffodil/section07/defineFormat/defineFormat.dfdl.xsd")
+    val schema2 = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/charClassEntities.dfdl.xsd")
+
+    withTempFile { parser =>
+      runCLI(args"save-parser -s $schema1 -s $schema2 $parser") { cli =>
+        cli.expectErr("Bad arguments for option 'schema'")
+      } (ExitCode.Usage)
+    }
+  }
+
+  @Test def test_3023_CLI_Saving_SaveParser_verboseMode(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+
+    withTempFile { parser =>
+      runCLI(args"-v save-parser -s $schema -r matrix $parser") { cli =>
+        cli.expectErr("[info]")
+      } (ExitCode.Success)
+
+      runCLI(args"-vv save-parser -s $schema -r matrix $parser") { cli =>
+        cli.expectErr("[debug]")
+      } (ExitCode.Success)
+    }
+  }
+
+  @Test def test_3038_CLI_Saving_SaveParser_namespaceNoRoot(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+
+    withTempFile { parser =>
+      runCLI(args"save-parser -s $schema -r {http://www.example.org/example1/} $parser") { cli =>
+        cli.expectErr("Bad arguments for option 'root'")
+        cli.expectErr("{http://www.example.org/example1/}")
+      } (ExitCode.Usage)
+    }
+  }
+
+  @Test def test_3039_CLI_Saving_SaveParser_emptyNamespace(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+
+    withTempFile { parser =>
+      runCLI(args"save-parser -s $schema -r {}matrix -p / $parser") { cli =>
+      } (ExitCode.Success)
+
+      runCLI(args"parse --parser $parser") { cli =>
+        cli.sendLine("0,1,2", inputDone = true)
+        cli.expect("<tns:cell>2</tns:cell>")
+      } (ExitCode.LeftOverData)
+    }
+  }
+
+  @Test def test_DFDL_1205_CLI_FullValidation_SavedParser_Incompatible(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input8.txt")
+
+    withTempFile { parser =>
+      runCLI(args"save-parser -s $schema -r {target}matrix $parser") { cli =>
+      } (ExitCode.Success)
+
+      runCLI(args"parse --parser $parser --validate on $input") { cli =>
+        cli.expectErr("[error]")
+        cli.expectErr("The validation mode must be 'limited' or 'off' when using a saved parser.")
+      } (ExitCode.Usage)
+    }
+  }
+
+  /**
+   * Note that in Daffodil 2.6.0 behavior of external variables changed. They are not saved as part of binary
+   * compiling. They are a runtime-thing only.
+   */
+  @Test def test_3508_CLI_Saving_SaveParser_extVars(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/external_variables.dfdl.xsd")
+
+    withTempFile { parser =>
+      runCLI(args"save-parser -s $schema -r row2 $parser") { cli =>
+      } (ExitCode.Success)
+
+      runCLI(args"parse --parser $parser -D{http://example.com}var1=25 {http://example.com}var3=7") { cli =>
+        cli.sendLine("0", inputDone = true)
+        cli.expect("<tns:row2 xmlns:tns=\"http://example.com\">")
+        cli.expect("<cell>25</cell>")
+        cli.expect("<cell>7</cell>")
+      } (ExitCode.LeftOverData)
+    }
+  }
+
+  @Test def test_3063_CLI_Saving_SaveParser_validate(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/cli_schema.dfdl.xsd")
+
+    withTempFile { parser =>
+      runCLI(args"save-parser --validate on -s $schema -r validation_check $parser") { cli =>
+        cli.expectErr("Unknown option 'validate'")
+      } (ExitCode.Usage)
+
+      runCLI(args"save-parser -s $schema -r validation_check $parser") { cli =>
+      } (ExitCode.Success)
+
+      runCLI(args"parse --validate limited -P $parser") { cli =>
+        cli.send("test", inputDone = true)
+        cli.expectErr("[error] Validation Error")
+        cli.expectErr("ex:validation_check failed")
+        cli.expectErr("[0-8]+")
+      } (ExitCode.ParseError)
+
+      runCLI(args"parse --validate on -P $parser") { cli =>
+        cli.send("test", inputDone = true)
+        cli.expectErr("validation mode must be 'limited' or 'off' when using a saved parser.")
+      } (ExitCode.Usage)
+    }
+  }
+
+  // DAFFODIL-1141
+  /*@Test*/ def test_3036_CLI_Saving_SaveParser_debug(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+
+    withTempFile { parser =>
+      runCLI(args"-d save-parser -s $schema -r matrix $parser") { cli =>
+        cli.expectErr("Some error about -d not being valid with save-parser")
+      } (ExitCode.Usage)
+    }
+  }
+
+  // DAFFODIL-1141
+  /*@Test*/ def test_3037_CLI_Saving_SaveParser_trace(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+
+    withTempFile { parser =>
+      runCLI(args"-t save-parser -s $schema -r matrix $parser") { cli =>
+        cli.expectErr("Some error about -t not being valid with save-parser")
+      } (ExitCode.Usage)
+    }
+  }
+
+  @Test def test_3572_CLI_Saving_SaveParser_unparse(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input1.txt.xml")
+
+    withTempFile { parser =>
+      runCLI(args"-t save-parser -s $schema -r matrix $parser") { cli =>
+      } (ExitCode.Success)
+
+      runCLI(args"unparse --parser $parser $input") { cli =>
+        cli.expect("0,1,2")
+      } (ExitCode.Success)
+    }
+  }
+
+  @Test def test_3573_CLI_Saving_SaveParser_unparse2(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input12.txt")
+
+    withTempFile { parser =>
+      runCLI(args"save-parser -s $schema -r e1 $parser") { cli =>
+      } (ExitCode.Success)
+
+      runCLI(args"unparse --parser $parser $input") { cli =>
+        cli.expect("Hello")
+      } (ExitCode.Success)
+    }
+  }
+
+  @Test def test_3941_CLI_Saving_SaveParser_tunables(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input12.txt")
+
+    withTempFile { parser =>
+      runCLI(args"save-parser -s $schema -r e1 -T parseUnparsePolicy=parseOnly $parser") { cli =>
+      } (ExitCode.Success)
+
+      runCLI(args"unparse --parser $parser $input") { cli =>
+        cli.expectErr("[error]")
+        cli.expectErr("Runtime Schema Definition Error: This schema was compiled without unparse support.")
+      } (ExitCode.UnparseError)
+    }
+  }
+
+}
diff --git a/daffodil-cli/src/test/scala/org/apache/daffodil/cli/cliTest/TestCLITunables.scala b/daffodil-cli/src/test/scala/org/apache/daffodil/cli/cliTest/TestCLITunables.scala
new file mode 100644
index 000000000..45a641391
--- /dev/null
+++ b/daffodil-cli/src/test/scala/org/apache/daffodil/cli/cliTest/TestCLITunables.scala
@@ -0,0 +1,199 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+package org.apache.daffodil.cliTest
+
+import org.junit.Test
+import org.apache.daffodil.cli.cliTest.Util._
+import org.apache.daffodil.cli.Main.ExitCode
+
+class TestCLITunables {
+
+  @Test def test_CLI_Parsing_unqualifiedPathStepPolicy_noNamespace_test_01(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/unqualified_path_step.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r test_01 -TunqualifiedPathStepPolicy=noNamespace") { cli =>
+      cli.send("12", inputDone = true)
+      cli.expect("""<c xmlns="">2</c>""")
+      cli.expect("""<s xmlns="">1</s>""")
+      cli.expect("</test_01>")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_CLI_Parsing_unqualifiedPathStepPolicy_noNamespace_test_02(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/unqualified_path_step.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r test_02 -TunqualifiedPathStepPolicy=noNamespace") { cli =>
+      cli.send("12", inputDone = true)
+      cli.expectErr("Schema Definition Error")
+    } (ExitCode.UnableToCreateProcessor)
+  }
+
+  @Test def test_CLI_Parsing_unqualifiedPathStepPolicy_noNamespace_test_03(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/unqualified_path_step.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r test_03 -TunqualifiedPathStepPolicy=noNamespace") { cli =>
+      cli.send("12", inputDone = true)
+      cli.expectErr("Schema Definition Error")
+    } (ExitCode.UnableToCreateProcessor)
+  }
+
+  @Test def test_CLI_Parsing_unqualifiedPathStepPolicy_noNamespace_test_04(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/unqualified_path_step.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r test_04 -TunqualifiedPathStepPolicy=noNamespace") { cli =>
+      cli.send("12", inputDone = true)
+      cli.expect("""<c xmlns="">2</c>""")
+      cli.expect("""<s xmlns="">2</s>""")
+      cli.expect("</test_04>")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_CLI_Parsing_unqualifiedPathStepPolicy_defaultNamespace_test_01(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/unqualified_path_step.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r test_01 -TunqualifiedPathStepPolicy=defaultNamespace") { cli =>
+      cli.send("12", inputDone = true)
+      cli.expect("""<c xmlns="">2</c>""")
+      cli.expect("""<s xmlns="">1</s>""")
+      cli.expect("</test_01>")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_CLI_Parsing_unqualifiedPathStepPolicy_defaultNamespace_test_02(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/unqualified_path_step.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r test_02 -TunqualifiedPathStepPolicy=defaultNamespace") { cli =>
+      cli.send("12", inputDone = true)
+      cli.expect("""<c xmlns="">2</c>""")
+      cli.expect("""<s xmlns="">1</s>""")
+      cli.expect("</test_02>")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_CLI_Parsing_unqualifiedPathStepPolicy_defaultNamespace_test_03(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/unqualified_path_step.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r test_03 -TunqualifiedPathStepPolicy=defaultNamespace") { cli =>
+      cli.send("12", inputDone = true)
+      cli.expectErr("Schema Definition Error")
+    } (ExitCode.UnableToCreateProcessor)
+  }
+
+  @Test def test_CLI_Parsing_unqualifiedPathStepPolicy_defaultNamespace_test_04(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/unqualified_path_step.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r test_04 -TunqualifiedPathStepPolicy=defaultNamespace") { cli =>
+      cli.send("12", inputDone = true)
+      cli.expectErr("Schema Definition Error")
+    } (ExitCode.UnableToCreateProcessor)
+  }
+
+  @Test def test_CLI_Parsing_unqualifiedPathStepPolicy_preferDefaultNamespace_test_01(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/unqualified_path_step.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r test_01 -TunqualifiedPathStepPolicy=preferDefaultNamespace") { cli =>
+      cli.send("12", inputDone = true)
+      cli.expect("""<c xmlns="">2</c>""")
+      cli.expect("""<s xmlns="">1</s>""")
+      cli.expect("</test_01>")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_CLI_Parsing_unqualifiedPathStepPolicy_preferDefaultNamespace_test_02(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/unqualified_path_step.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r test_02 -TunqualifiedPathStepPolicy=preferDefaultNamespace") { cli =>
+      cli.send("12", inputDone = true)
+      cli.expect("""<c xmlns="">2</c>""")
+      cli.expect("""<s xmlns="">1</s>""")
+      cli.expect("</test_02>")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_CLI_Parsing_unqualifiedPathStepPolicy_preferDefaultNamespace_test_03(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/unqualified_path_step.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r test_03 -TunqualifiedPathStepPolicy=preferDefaultNamespace") { cli =>
+      cli.send("12", inputDone = true)
+      cli.expectErr("Schema Definition Error")
+    } (ExitCode.UnableToCreateProcessor)
+  }
+
+  @Test def test_CLI_Parsing_unqualifiedPathStepPolicy_preferDefaultNamespace_test_04(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/unqualified_path_step.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -r test_04 -TunqualifiedPathStepPolicy=preferDefaultNamespace") { cli =>
+      cli.send("12", inputDone = true)
+      cli.expect("""<c xmlns="">2</c>""")
+      cli.expect("""<s xmlns="">2</s>""")
+      cli.expect("</test_04>")
+    } (ExitCode.Success)
+  }
+
+  /**
+   * Suppresses SDW messages.
+   */
+  @Test def test_CLI_Parsing_SuppressSDEWarnings1(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/suppressWarnTest.dfdl.xsd")
+
+    runCLI(args"parse -s $schema -TsuppressSchemaDefinitionWarnings=all") { cli =>
+      cli.send("a,b", inputDone = true)
+      cli.expect("<s1>a</s1>")
+      cli.expect("<s2>b</s2>")
+    } (ExitCode.Success)
+  }
+
+  /**
+   * Will display SDW warnings. Does not set the tunable that suppresses them.
+   */
+  @Test def test_CLI_Parsing_SuppressSDEWarnings2(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/suppressWarnTest.dfdl.xsd")
+
+    runCLI(args"parse -s $schema") { cli =>
+      cli.send("a,b", inputDone = true)
+      cli.expect("<s1>a</s1>")
+      cli.expect("<s2>b</s2>")
+      cli.expectErr("Schema Definition Warning")
+      cli.expectErr("dfdl:lengthKind")
+      cli.expectErr("delimited")
+      cli.expectErr("dfdl:length")
+    } (ExitCode.Success)
+  }
+
+  /**
+   * Saving the processor should compile and issue SDWs which we should see in
+   * the expected output. When reloading, we should NOT see a SDW because that
+   * isn't displayed on a reload of a compiled processor
+   */
+  @Test def test_CLI_Parsing_ReloadingDoesNotRepeatWarnings(): Unit = {
+    val schema = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/suppressWarnTest.dfdl.xsd")
+
+    withTempFile { parser =>
+      runCLI(args"save-parser -s $schema $parser") { cli =>
+        cli.expectErr("Schema Definition Warning")
+      } (ExitCode.Success)
+
+      runCLI(args"parse -P $parser") { cli =>
+        cli.send("a,b", inputDone = true)
+        cli.expect("<s1>a</s1>")
+        cli.expect("<s2>b</s2>")
+      } (ExitCode.Success)
+    }
+  }
+
+}
diff --git a/daffodil-cli/src/test/scala/org/apache/daffodil/cli/cliTest/TestCLIUnparsing.scala b/daffodil-cli/src/test/scala/org/apache/daffodil/cli/cliTest/TestCLIUnparsing.scala
new file mode 100644
index 000000000..812cc8a6b
--- /dev/null
+++ b/daffodil-cli/src/test/scala/org/apache/daffodil/cli/cliTest/TestCLIUnparsing.scala
@@ -0,0 +1,341 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+package org.apache.daffodil.cliTest
+
+import java.nio.charset.StandardCharsets.UTF_8
+import org.apache.commons.io.FileUtils
+import org.junit.Assert._
+import org.junit.Test
+import org.apache.daffodil.cli.cliTest.Util._
+import org.apache.daffodil.cli.Main.ExitCode
+
+class TestCLIunparsing {
+
+  @Test def test_3525_CLI_Unparsing_SimpleUnparse_inFile(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input12.txt")
+
+    runCLI(args"unparse -s $schema --root e1 $input") { cli =>
+      cli.expect("Hello")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_3526_CLI_Unparsing_SimpleUnparse_inFile2(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input13.txt")
+
+    runCLI(args"unparse -s $schema --root e3 $input") { cli =>
+      cli.expect("[1,2]")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_3527_CLI_Unparsing_SimpleUnparse_stdin(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
+    val input = path("daffodil-cli/src/test/resources/org/apache/daffodil/cli/input/input14.txt")
+
+    runCLI(args"unparse -s $schema --root e3") { cli =>
+      cli.sendFile(input, inputDone = true)
+      cli.expect("[1,2]")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_3528_CLI_Unparsing_SimpleUnparse_stdin2(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
+
+    runCLI(args"unparse -s $schema --root e1") { cli =>
+      cli.send("<tns:e1 xmlns:tns='http://example.com'>Hello</tns:e1>", inputDone = true)
+      cli.expect("Hello")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_3529_CLI_Unparsing_SimpleUnparse_stdin3(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section00/general/generalSchema.dfdl.xsd")
+
+    runCLI(args"unparse -s $schema --root e1 -") { cli =>
+      cli.send("<tns:e1 xmlns:tns='http://example.com'>Hello</tns:e1>", inputDone = true)
+      cli.expect("Hello")
+    } (ExitCode.Success)
+  }
+
+  @Test def test_3584_CLI_Unparsing_SimpleUnparse_stdin4(): Unit = {
+    val schema = path("daffodil-test/src/test/resources/org/apache/daffodil/section06/entities/charClassEntities.dfdl.xsd")
+
+    runCLI(args"unparse -s $schema --root file") { cli =>
+      val input = "<tns:file xmlns:tns='http://www.example.org/example1/'><tns:header><tns:title>1</tns:title><tns:title>2</tns:title><tns:title>3</tns:title></tns:header><tns:record><tns:item>4</tns:item><tns:item>5</tns:item><tns:item>6</tns:item></tns:record></tns:file>"
+      cli.send(input, inputDone = true)
+      cli.expect("1,2,3")
... 247299 lines suppressed ...