alecAbstraction Layer for Escape Codes | 
          
| git clone git://git.dimitrijedobrota.com/alec.git | 
| Log | Files | Refs | README | LICENSE | HACKING | CONTRIBUTING | CODE_OF_CONDUCT | BUILDING | 
| commit | 6afe33840c7ae7d9a1c1a6fc265d9b962fafb7e7 | 
| parent | 3bfef0fc60051e24602ae12693f297df63a7ce84 | 
| author | Dimitrije Dobrota < mail@dimitrijedobrota.com > | 
| date | Thu, 30 Jan 2025 15:17:17 +0100 | 
Modernize the codebase using clang-tidy
| M | .clang-format | | | +++++++++++++++++++++++++++++++++++ ----------------------------------------------- | 
| A | .clang-tidy | | | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | 
| A | .codespellrc | | | ++++++ | 
| M | .gitignore | | | +++++++++++ -- | 
| A | BUILDING.md | | | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | 
| M | CMakeLists.txt | | | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ----------- | 
| A | CMakePresets.json | | | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | 
| A | CODE_OF_CONDUCT.md | | | +++++ | 
| A | CONTRIBUTING.md | | | +++++++++++++++++++ | 
| A | HACKING.md | | | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | 
| M | README.md | | | ++++++++++ ----------- | 
| A | cmake/coverage.cmake | | | +++++++++++++++++++++++++++++++++ | 
| A | cmake/dev-mode.cmake | | | +++++++++++ | 
| A | cmake/folders.cmake | | | +++++++++++++++++++++ | 
| A | cmake/install-config.cmake | | | + | 
| A | cmake/install-rules.cmake | | | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | 
| A | cmake/lint-targets.cmake | | | +++++++++++++++++++++++++++++++++ | 
| A | cmake/lint.cmake | | | +++++++++++++++++++++++++++++++++++++++++++++++++++ | 
| A | cmake/prelude.cmake | | | ++++++++++ | 
| A | cmake/project-is-top-level.cmake | | | ++++++ | 
| A | cmake/spell-targets.cmake | | | ++++++++++++++++++++++ | 
| A | cmake/spell.cmake | | | +++++++++++++++++++++++++++++ | 
| A | cmake/variables.cmake | | | ++++++++++++++++++++++++++++ | 
| D | demo/CMakeLists.txt | | | ----------------- | 
| D | demo/demo.cpp | | | ---------------------------- | 
| D | demo/demo_runtime.cpp | | | ---------------------------- | 
| A | example/CMakeLists.txt | | | ++++++++++++++++++++++++++ | 
| A | example/alec_compile.cpp | | | +++++++++++++++++++++++++++++ | 
| A | example/alec_runtime.cpp | | | ++++++++++++++++++++++++++++ | 
| A | source/alec.rules.hpp | | | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | 
| A | source/generator.c | | | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | 
| A | source/generator.h | | | ++++++++++++++++++++++++++++++++++++++++++++++++++++++ | 
| A | source/lexer.l | | | ++++++++++++++++++++++++++++++++++++++++ | 
| A | source/parser.y | | | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | 
| D | src/CMakeLists.txt | | | ---------------------------------------------------- | 
| D | src/alec.rules.hpp | | | --------------------------------------------------------------------------------- | 
| D | src/generator.c | | | --------------------------------------------------------------------------------- | 
| D | src/generator.h | | | ------------------------------------------------------ | 
| D | src/lexer.l | | | ---------------------------------------- | 
| D | src/parser.y | | | ------------------------------------------------------------------------ | 
40 files changed, 1965 insertions(+), 1066 deletions(-)
diff --git a/ .clang-format b/ .clang-format
@@ -1,216 +1,178 @@
---
          Language:        Cpp
          # BasedOnStyle:  Microsoft
          Language: Cpp
          # BasedOnStyle: Chromium
          AccessModifierOffset: -2
          AlignAfterOpenBracket: Align
          AlignArrayOfStructures: None
          AlignConsecutiveAssignments:
            Enabled:         false
            AcrossEmptyLines: false
            AcrossComments:  false
            AlignCompound:   false
            PadOperators:    true
          AlignConsecutiveBitFields:
            Enabled:         false
            AcrossEmptyLines: false
            AcrossComments:  false
            AlignCompound:   false
            PadOperators:    false
          AlignConsecutiveDeclarations:
            Enabled:         false
            AcrossEmptyLines: false
            AcrossComments:  false
            AlignCompound:   false
            PadOperators:    false
          AlignConsecutiveMacros:
            Enabled:         false
            AcrossEmptyLines: false
            AcrossComments:  false
            AlignCompound:   false
            PadOperators:    false
          AlignEscapedNewlines: Right
          AlignOperands:   Align
          AlignTrailingComments: true
          AlignConsecutiveMacros: false
          AlignConsecutiveAssignments: false
          AlignConsecutiveBitFields: false
          AlignConsecutiveDeclarations: false
          AlignEscapedNewlines: DontAlign
          AlignOperands: DontAlign
          AlignTrailingComments: false
          AllowAllArgumentsOnNextLine: true
          AllowAllConstructorInitializersOnNextLine: false
          AllowAllParametersOfDeclarationOnNextLine: true
          AllowShortEnumsOnASingleLine: false
          AllowShortBlocksOnASingleLine: false
          AllowShortCaseLabelsOnASingleLine: true
          AllowShortFunctionsOnASingleLine: true
          AllowShortBlocksOnASingleLine: Empty
          AllowShortCaseLabelsOnASingleLine: false
          AllowShortFunctionsOnASingleLine: Inline
          AllowShortLambdasOnASingleLine: All
          AllowShortIfStatementsOnASingleLine: Always
          AllowShortIfStatementsOnASingleLine: Never
          AllowShortLoopsOnASingleLine: false
          AlwaysBreakAfterDefinitionReturnType: None
          AlwaysBreakAfterReturnType: None
          AlwaysBreakBeforeMultilineStrings: false
          AlwaysBreakTemplateDeclarations: MultiLine
          AttributeMacros:
            - __capability
          BinPackArguments: true
          BinPackParameters: true
          AlwaysBreakBeforeMultilineStrings: true
          AlwaysBreakTemplateDeclarations: Yes
          BinPackArguments: false
          BinPackParameters: false
          BraceWrapping:
            AfterCaseLabel:  false
            AfterClass:      false
            AfterControlStatement: false
            AfterEnum:       false
            AfterFunction:   false
            AfterNamespace:  false
            AfterCaseLabel: false
            AfterClass: true
            AfterControlStatement: MultiLine
            AfterEnum: true
            AfterFunction: true
            AfterNamespace: true
            AfterObjCDeclaration: false
            AfterStruct:     false
            AfterUnion:      false
            AfterExternBlock: false
            BeforeCatch:     false
            BeforeElse:      false
            BeforeLambdaBody: false
            BeforeWhile:     false
            IndentBraces:    false
            AfterStruct: true
            AfterUnion: true
            AfterExternBlock: true
            BeforeCatch: false
            BeforeElse: false
            BeforeLambdaBody: true
            BeforeWhile: false
            IndentBraces: false
            SplitEmptyFunction: true
            SplitEmptyRecord: true
            SplitEmptyNamespace: true
          BreakBeforeBinaryOperators: None
          BreakBeforeConceptDeclarations: Always
          BreakBeforeBinaryOperators: NonAssignment
          BreakBeforeBraces: Custom
          BreakBeforeInheritanceComma: false
          BreakInheritanceList: BeforeColon
          # BreakBeforeInheritanceComma: true
          BreakInheritanceList: BeforeComma
          BreakBeforeTernaryOperators: true
          BreakConstructorInitializersBeforeComma: false
          BreakConstructorInitializers: BeforeColon
          BreakAfterJavaFieldAnnotations: false
          BreakConstructorInitializersBeforeComma: true
          BreakConstructorInitializers: BeforeComma
          BreakAfterJavaFieldAnnotations: true
          BreakStringLiterals: true
          ColumnLimit:     110
          CommentPragmas:  '^ IWYU pragma:'
          QualifierAlignment: Leave
          ColumnLimit: 80
          CommentPragmas: '^ IWYU pragma:'
          CompactNamespaces: false
          ConstructorInitializerAllOnOneLineOrOnePerLine: false
          ConstructorInitializerIndentWidth: 4
          ContinuationIndentWidth: 4
          Cpp11BracedListStyle: true
          DeriveLineEnding: true
          DeriveLineEnding: false
          DerivePointerAlignment: false
          DisableFormat:   false
          EmptyLineAfterAccessModifier: Never
          EmptyLineBeforeAccessModifier: LogicalBlock
          DisableFormat: false
          ExperimentalAutoDetectBinPacking: false
          PackConstructorInitializers: BinPack
          BasedOnStyle:    ''
          ConstructorInitializerAllOnOneLineOrOnePerLine: false
          AllowAllConstructorInitializersOnNextLine: true
          FixNamespaceComments: true
          ForEachMacros:
            - foreach
            - Q_FOREACH
            - BOOST_FOREACH
          IfMacros:
            - KJ_IF_MAYBE
          IncludeBlocks:   Preserve
          IncludeBlocks: Regroup
          IncludeCategories:
            - Regex:           '^"(llvm|llvm-c|clang|clang-c)/'
              Priority:        2
              SortPriority:    0
              CaseSensitive:   false
            - Regex:           '^(<|"(gtest|gmock|isl|json)/)'
              Priority:        3
              SortPriority:    0
              CaseSensitive:   false
            - Regex:           '.*'
              Priority:        1
              SortPriority:    0
              CaseSensitive:   false
          IncludeIsMainRegex: '(Test)?$'
            # Standard library headers come before anything else
            - Regex: '^<[a-z_]+>'
              Priority: -1
            - Regex: '^<.+\.h(pp)?>'
              Priority: 1
            - Regex: '^<.*'
              Priority: 2
            - Regex: '.*'
              Priority: 3
          IncludeIsMainRegex: ''
          IncludeIsMainSourceRegex: ''
          IndentAccessModifiers: false
          IndentCaseLabels: false
          IndentCaseLabels: true
          IndentCaseBlocks: false
          IndentGotoLabels: true
          IndentPPDirectives: None
          IndentExternBlock: AfterExternBlock
          IndentRequiresClause: true
          IndentWidth:     4
          IndentPPDirectives: AfterHash
          IndentExternBlock: NoIndent
          IndentWidth: 2
          IndentWrappedFunctionNames: false
          InsertBraces:    false
          InsertTrailingCommas: None
          JavaScriptQuotes: Leave
          InsertTrailingCommas: Wrapped
          JavaScriptQuotes: Double
          JavaScriptWrapImports: true
          KeepEmptyLinesAtTheStartOfBlocks: true
          LambdaBodyIndentation: Signature
          KeepEmptyLinesAtTheStartOfBlocks: false
          MacroBlockBegin: ''
          MacroBlockEnd:   ''
          MacroBlockEnd: ''
          MaxEmptyLinesToKeep: 1
          NamespaceIndentation: None
          ObjCBinPackProtocolList: Auto
          ObjCBinPackProtocolList: Never
          ObjCBlockIndentWidth: 2
          ObjCBreakBeforeNestedBlockParam: true
          ObjCSpaceAfterProperty: false
          ObjCSpaceBeforeProtocolList: true
          PenaltyBreakAssignment: 2
          PenaltyBreakBeforeFirstCallParameter: 19
          PenaltyBreakBeforeFirstCallParameter: 1
          PenaltyBreakComment: 300
          PenaltyBreakFirstLessLess: 120
          PenaltyBreakOpenParenthesis: 0
          PenaltyBreakString: 1000
          PenaltyBreakTemplateDeclaration: 10
          PenaltyExcessCharacter: 1000000
          PenaltyReturnTypeOnItsOwnLine: 1000
          PenaltyIndentedWhitespace: 0
          PointerAlignment: Right
          PPIndentWidth:   -1
          ReferenceAlignment: Pointer
          ReflowComments:  true
          RemoveBracesLLVM: false
          RequiresClausePosition: OwnLine
          SeparateDefinitionBlocks: Leave
          ShortNamespaceLines: 1
          SortIncludes:    CaseSensitive
          SortJavaStaticImport: Before
          PenaltyReturnTypeOnItsOwnLine: 200
          PointerAlignment: Left
          RawStringFormats:
            - Language: Cpp
              Delimiters:
                - cc
                - CC
                - cpp
                - Cpp
                - CPP
                - 'c++'
                - 'C++'
              CanonicalDelimiter: ''
              BasedOnStyle: google
            - Language: TextProto
              Delimiters:
                - pb
                - PB
                - proto
                - PROTO
              EnclosingFunctions:
                - EqualsProto
                - EquivToProto
                - PARSE_PARTIAL_TEXT_PROTO
                - PARSE_TEST_PROTO
                - PARSE_TEXT_PROTO
                - ParseTextOrDie
                - ParseTextProtoOrDie
                - ParseTestProto
                - ParsePartialTestProto
              CanonicalDelimiter: ''
              BasedOnStyle: google
          ReflowComments: true
          SortIncludes: true
          SortUsingDeclarations: true
          SpaceAfterCStyleCast: false
          SpaceAfterLogicalNot: false
          SpaceAfterTemplateKeyword: true
          SpaceAfterTemplateKeyword: false
          SpaceBeforeAssignmentOperators: true
          SpaceBeforeCaseColon: false
          SpaceBeforeCpp11BracedList: false
          SpaceBeforeCpp11BracedList: true
          SpaceBeforeCtorInitializerColon: true
          SpaceBeforeInheritanceColon: true
          SpaceBeforeParens: ControlStatements
          SpaceBeforeParensOptions:
            AfterControlStatements: true
            AfterForeachMacros: true
            AfterFunctionDefinitionName: false
            AfterFunctionDeclarationName: false
            AfterIfMacros:   true
            AfterOverloadedOperator: false
            AfterRequiresInClause: false
            AfterRequiresInExpression: false
            BeforeNonEmptyParentheses: false
          SpaceAroundPointerQualifiers: Default
          SpaceBeforeParens: ControlStatementsExceptForEachMacros
          SpaceBeforeRangeBasedForLoopColon: true
          SpaceInEmptyBlock: false
          SpaceInEmptyParentheses: false
          SpacesBeforeTrailingComments: 1
          SpacesInAngles:  Never
          SpacesBeforeTrailingComments: 2
          SpacesInAngles: false
          SpacesInConditionalStatement: false
          SpacesInContainerLiterals: true
          SpacesInContainerLiterals: false
          SpacesInCStyleCastParentheses: false
          SpacesInLineCommentPrefix:
            Minimum:         1
            Maximum:         -1
          SpacesInParentheses: false
          SpacesInSquareBrackets: false
          SpaceBeforeSquareBrackets: false
          BitFieldColonSpacing: Both
          Standard:        Latest
          StatementAttributeLikeMacros:
            - Q_EMIT
          Standard: Auto
          StatementMacros:
            - Q_UNUSED
            - QT_REQUIRE_VERSION
          TabWidth:        4
          UseCRLF:         false
          UseTab:          Never
          TabWidth: 8
          UseCRLF: false
          UseTab: Never
          WhitespaceSensitiveMacros:
            - STRINGIZE
            - PP_STRINGIZE
            - BOOST_PP_STRINGIZE
            - NS_SWIFT_NAME
            - CF_SWIFT_NAME
          ...
          diff --git a/ .clang-tidy b/ .clang-tidy
@@ -0,0 +1,155 @@
---
          # Enable ALL the things! Except not really
          # misc-non-private-member-variables-in-classes: the options don't do anything
          # modernize-use-nodiscard: too aggressive, attribute is situationally useful
          Checks: "*,\
            -google-readability-todo,\
            -altera-*,\
            -fuchsia-*,\
            fuchsia-multiple-inheritance,\
            -llvm-header-guard,\
            -llvm-include-order,\
            -llvmlibc-*,\
            -modernize-use-nodiscard,\
            -misc-non-private-member-variables-in-classes"
          WarningsAsErrors: ''
          CheckOptions:
            - key: 'bugprone-argument-comment.StrictMode'
              value: 'true'
          # Prefer using enum classes with 2 values for parameters instead of bools
            - key: 'bugprone-argument-comment.CommentBoolLiterals'
              value: 'true'
            - key: 'bugprone-misplaced-widening-cast.CheckImplicitCasts'
              value: 'true'
            - key: 'bugprone-sizeof-expression.WarnOnSizeOfIntegerExpression'
              value: 'true'
            - key: 'bugprone-suspicious-string-compare.WarnOnLogicalNotComparison'
              value: 'true'
            - key: 'readability-simplify-boolean-expr.ChainedConditionalReturn'
              value: 'true'
            - key: 'readability-simplify-boolean-expr.ChainedConditionalAssignment'
              value: 'true'
            - key: 'readability-uniqueptr-delete-release.PreferResetCall'
              value: 'true'
            - key: 'cppcoreguidelines-init-variables.MathHeader'
              value: '<cmath>'
            - key: 'cppcoreguidelines-narrowing-conversions.PedanticMode'
              value: 'true'
            - key: 'readability-else-after-return.WarnOnUnfixable'
              value: 'true'
            - key: 'readability-else-after-return.WarnOnConditionVariables'
              value: 'true'
            - key: 'readability-inconsistent-declaration-parameter-name.Strict'
              value: 'true'
            - key: 'readability-qualified-auto.AddConstToQualified'
              value: 'true'
            - key: 'readability-redundant-access-specifiers.CheckFirstDeclaration'
              value: 'true'
          # These seem to be the most common identifier styles
            - key: 'readability-identifier-naming.AbstractClassCase'
              value: 'lower_case'
            - key: 'readability-identifier-naming.ClassCase'
              value: 'lower_case'
            - key: 'readability-identifier-naming.ClassConstantCase'
              value: 'lower_case'
            - key: 'readability-identifier-naming.ClassMemberCase'
              value: 'lower_case'
            - key: 'readability-identifier-naming.ClassMethodCase'
              value: 'lower_case'
            - key: 'readability-identifier-naming.ConstantCase'
              value: 'lower_case'
            - key: 'readability-identifier-naming.ConstantMemberCase'
              value: 'lower_case'
            - key: 'readability-identifier-naming.ConstantParameterCase'
              value: 'lower_case'
            - key: 'readability-identifier-naming.ConstantPointerParameterCase'
              value: 'lower_case'
            - key: 'readability-identifier-naming.ConstexprFunctionCase'
              value: 'lower_case'
            - key: 'readability-identifier-naming.ConstexprMethodCase'
              value: 'lower_case'
            - key: 'readability-identifier-naming.ConstexprVariableCase'
              value: 'lower_case'
            - key: 'readability-identifier-naming.EnumCase'
              value: 'CamelCase'
            - key: 'readability-identifier-naming.EnumConstantCase'
              value: 'UPPER_CASE'
            - key: 'readability-identifier-naming.FunctionCase'
              value: 'lower_case'
            - key: 'readability-identifier-naming.GlobalConstantCase'
              value: 'lower_case'
            - key: 'readability-identifier-naming.GlobalConstantPointerCase'
              value: 'lower_case'
            - key: 'readability-identifier-naming.GlobalFunctionCase'
              value: 'lower_case'
            - key: 'readability-identifier-naming.GlobalPointerCase'
              value: 'lower_case'
            - key: 'readability-identifier-naming.GlobalVariableCase'
              value: 'lower_case'
            - key: 'readability-identifier-naming.InlineNamespaceCase'
              value: 'lower_case'
            - key: 'readability-identifier-naming.LocalConstantCase'
              value: 'lower_case'
            - key: 'readability-identifier-naming.LocalConstantPointerCase'
              value: 'lower_case'
            - key: 'readability-identifier-naming.LocalPointerCase'
              value: 'lower_case'
            - key: 'readability-identifier-naming.LocalVariableCase'
              value: 'lower_case'
            - key: 'readability-identifier-naming.MacroDefinitionCase'
              value: 'UPPER_CASE'
            - key: 'readability-identifier-naming.MemberCase'
              value: 'lower_case'
            - key: 'readability-identifier-naming.MethodCase'
              value: 'lower_case'
            - key: 'readability-identifier-naming.NamespaceCase'
              value: 'lower_case'
            - key: 'readability-identifier-naming.ParameterCase'
              value: 'lower_case'
            - key: 'readability-identifier-naming.ParameterPackCase'
              value: 'lower_case'
            - key: 'readability-identifier-naming.PointerParameterCase'
              value: 'lower_case'
            - key: 'readability-identifier-naming.PrivateMemberCase'
              value: 'lower_case'
            - key: 'readability-identifier-naming.PrivateMemberPrefix'
              value: 'm_'
            - key: 'readability-identifier-naming.PrivateMethodCase'
              value: 'lower_case'
            - key: 'readability-identifier-naming.ProtectedMemberCase'
              value: 'lower_case'
            - key: 'readability-identifier-naming.ProtectedMemberPrefix'
              value: 'm_'
            - key: 'readability-identifier-naming.ProtectedMethodCase'
              value: 'lower_case'
            - key: 'readability-identifier-naming.PublicMemberCase'
              value: 'lower_case'
            - key: 'readability-identifier-naming.PublicMethodCase'
              value: 'lower_case'
            - key: 'readability-identifier-naming.ScopedEnumConstantCase'
              value: 'UPPER_CASE'
            - key: 'readability-identifier-naming.StaticConstantCase'
              value: 'lower_case'
            - key: 'readability-identifier-naming.StaticVariableCase'
              value: 'lower_case'
            - key: 'readability-identifier-naming.StructCase'
              value: 'lower_case'
            - key: 'readability-identifier-naming.TemplateParameterCase'
              value: 'CamelCase'
            - key: 'readability-identifier-naming.TemplateTemplateParameterCase'
              value: 'CamelCase'
            - key: 'readability-identifier-naming.TypeAliasCase'
              value: 'lower_case'
            - key: 'readability-identifier-naming.TypedefCase'
              value: 'lower_case'
            - key: 'readability-identifier-naming.TypeTemplateParameterCase'
              value: 'CamelCase'
            - key: 'readability-identifier-naming.UnionCase'
              value: 'lower_case'
            - key: 'readability-identifier-naming.ValueTemplateParameterCase'
              value: 'CamelCase'
            - key: 'readability-identifier-naming.VariableCase'
              value: 'lower_case'
            - key: 'readability-identifier-naming.VirtualMethodCase'
              value: 'lower_case'
          ...
        
        diff --git a/ .codespellrc b/ .codespellrc
@@ -0,0 +1,6 @@
[codespell]
          builtin = clear,rare,en-GB_to_en-US,names,informal,code
          check-filenames =
          check-hidden =
          skip = */.git,*/build,*/prefix
          quiet-level = 2
        
        diff --git a/ .gitignore b/ .gitignore
@@ -1,2 +1,11 @@
build
          .cache
          **/.DS_Store
          .idea/
          .vs/
          .vscode/
          build/
          cmake-build-*/
          prefix/
          .clangd
          CMakeLists.txt.user
          CMakeUserPresets.json
          compile_commands.json
        
        diff --git a/ BUILDING.md b/ BUILDING.md
@@ -0,0 +1,89 @@
# Building with CMake
          ## Build
          This project doesn't require any special command-line flags to build to keep
          things simple.
          Here are the steps for building in release mode with a single-configuration
          generator, like the Unix Makefiles one:
          ```sh
          cmake -S . -B build -D CMAKE_BUILD_TYPE=Release
          cmake --build build
          ```
          Here are the steps for building in release mode with a multi-configuration
          generator, like the Visual Studio ones:
          ```sh
          cmake -S . -B build
          cmake --build build --config Release
          ```
          ### Building with MSVC
          Note that MSVC by default is not standards compliant and you need to pass some
          flags to make it behave properly. See the `flags-msvc` preset in the
          [CMakePresets.json](CMakePresets.json) file for the flags and with what
          variable to provide them to CMake during configuration.
          ### Building on Apple Silicon
          CMake supports building on Apple Silicon properly since 3.20.1. Make sure you
          have the [latest version][1] installed.
          ## Install
          This project doesn't require any special command-line flags to install to keep
          things simple. As a prerequisite, the project has to be built with the above
          commands already.
          The below commands require at least CMake 3.15 to run, because that is the
          version in which [Install a Project][2] was added.
          Here is the command for installing the release mode artifacts with a
          single-configuration generator, like the Unix Makefiles one:
          ```sh
          cmake --install build
          ```
          Here is the command for installing the release mode artifacts with a
          multi-configuration generator, like the Visual Studio ones:
          ```sh
          cmake --install build --config Release
          ```
          ### CMake package
          This project exports a CMake package to be used with the [`find_package`][3]
          command of CMake:
          * Package name: `alec`
          * Target name: `alec::alec`
          Example usage:
          ```cmake
          find_package(alec REQUIRED)
          # Declare the imported target as a build requirement using PRIVATE, where
          # project_target is a target created in the consuming project
          target_link_libraries(
              project_target PRIVATE
              alec::alec
          )
          ```
          ### Note to packagers
          The `CMAKE_INSTALL_INCLUDEDIR` is set to a path other than just `include` if
          the project is configured as a top level project to avoid indirectly including
          other libraries when installed to a common prefix. Please review the
          [install-rules.cmake](cmake/install-rules.cmake) file for the full set of
          install rules.
          [1]: https://cmake.org/download/
          [2]: https://cmake.org/cmake/help/latest/manual/cmake.1.html#install-a-project
          [3]: https://cmake.org/cmake/help/latest/command/find_package.html
        
        diff --git a/ CMakeLists.txt b/ CMakeLists.txt
@@ -1,22 +1,104 @@
cmake_minimum_required(VERSION 3.25.2)
          set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
          cmake_minimum_required(VERSION 3.14)
          include(cmake/prelude.cmake)
          project(
              Alec
          	VERSION 1.0.8
              alec
              VERSION 0.1.9
              DESCRIPTION "Abstraction Layer for Escape Codes"
          	HOMEPAGE_URL https://git.dimitrijedobrota.com/alec.git
              HOMEPAGE_URL "git://git.dimitrijedobrota.com/alec.git"
              LANGUAGES C CXX
          )
          enable_testing()
          set(CMAKE_C_STANDARD 11)
          set(CMAKE_C_STANDARD_REQUIRED ON)
          set(CMAKE_C_EXTENSIONS YES)
          include(cmake/project-is-top-level.cmake)
          include(cmake/variables.cmake)
          # ---- Build library ----
          set(PARSER_DIR "${CMAKE_CURRENT_BINARY_DIR}/source")
          file(MAKE_DIRECTORY ${PARSER_DIR})
          find_package(FLEX)
          find_package(BISON)
          set(LEXER_OUT "${PARSER_DIR}/lexer.c")
          set(PARSER_OUT "${PARSER_DIR}/parser.c")
          if(CMAKE_BUILD_TYPE STREQUAL "Debug")
          	set(FLAGS "--debug")
          endif()
          FLEX_TARGET(LEXER source/lexer.l "${LEXER_OUT}" DEFINES_FILE "${PARSER_DIR}/scanner.h" COMPILE_FLAGS "${FLAGS}")
          BISON_TARGET(PARSER source/parser.y "${PARSER_OUT}" DEFINES_FILE "${PARSER_DIR}/parser.h" COMPILE_FLAGS "${FLAGS}")
          ADD_FLEX_BISON_DEPENDENCY(LEXER PARSER)
          add_executable(generator "${LEXER_OUT}" "${PARSER_OUT}" source/generator.c)
          target_include_directories(generator PRIVATE source ${PARSER_DIR})
          set(GENERATE_OUT_BIN "${PROJECT_BINARY_DIR}/bin")
          set(GENERATE_OUT_INCLUDE "${PROJECT_BINARY_DIR}/include/alec")
          file(MAKE_DIRECTORY ${GENERATE_OUT_BIN} ${GENERATE_OUT_INCLUDE})
          set_target_properties(generator PROPERTIES
              VERSION ${PROJECT_VERSION}
              SOVERSION ${PROJECT_VERSION_MAJOR}
              RUNTIME_OUTPUT_DIRECTORY "${GENERATE_OUT_BIN}"
          )
          set(RULES_NAME "alec.rules.hpp")
          set(RULES_FILE "${GENERATE_OUT_INCLUDE}/${RULES_NAME}")
          configure_file(source/alec.rules.hpp ${RULES_FILE} COPYONLY)
          add_custom_command(
              OUTPUT ${GENERATE_OUT_INCLUDE}/alec.hpp
              COMMAND generator ${RULES_FILE} > ${GENERATE_OUT_INCLUDE}/alec.hpp
              DEPENDS generator source/${RULES_NAME}
              COMMENT "Generating include file"
          )
          # ---- Declare library ----
          add_library(alec_alec INTERFACE ${GENERATE_OUT_INCLUDE}/alec.hpp)
          add_library(alec::alec ALIAS alec_alec)
          set_property(
              TARGET alec_alec PROPERTY
              EXPORT_NAME alec
          )
          target_include_directories(
              alec_alec ${warning_guard}
              INTERFACE
              "\$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/include>"
          )
          target_compile_features(alec_alec INTERFACE cxx_std_20)
          # ---- Install rules ----
          if(NOT CMAKE_SKIP_INSTALL_RULES)
            include(cmake/install-rules.cmake)
          endif()
          # ---- Examples ----
          if(PROJECT_IS_TOP_LEVEL)
            option(BUILD_EXAMPLES "Build examples tree." "${alec_DEVELOPER_MODE}")
            if(BUILD_EXAMPLES)
              add_subdirectory(example)
            endif()
          endif()
          # ---- Developer mode ----
          set(CMAKE_CXX_STANDARD 23)
          set(CMAKE_CXX_STANDARD_REQUIRED ON)
          set(CMAKE_CXX_EXTENSIONS OFF)
          if(NOT alec_DEVELOPER_MODE)
            return()
          elseif(NOT PROJECT_IS_TOP_LEVEL)
            message(
                AUTHOR_WARNING
                "Developer mode is intended for developers of alec"
            )
          endif()
          add_subdirectory(src)
          add_subdirectory(demo)
          include(cmake/dev-mode.cmake)
        
        diff --git a/ CMakePresets.json b/ CMakePresets.json
@@ -0,0 +1,159 @@
{
            "version": 2,
            "cmakeMinimumRequired": {
              "major": 3,
              "minor": 14,
              "patch": 0
            },
            "configurePresets": [
              {
                "name": "cmake-pedantic",
                "hidden": true,
                "warnings": {
                  "dev": true,
                  "deprecated": true,
                  "uninitialized": true,
                  "unusedCli": true,
                  "systemVars": false
                },
                "errors": {
                  "dev": true,
                  "deprecated": true
                }
              },
              {
                "name": "dev-mode",
                "hidden": true,
                "inherits": "cmake-pedantic",
                "cacheVariables": {
                  "alec_DEVELOPER_MODE": "ON"
                }
              },
              {
                "name": "cppcheck",
                "hidden": true,
                "cacheVariables": {
                  "CMAKE_CXX_CPPCHECK": "cppcheck;--inline-suppr"
                }
              },
              {
                "name": "clang-tidy",
                "hidden": true,
                "cacheVariables": {
                  "CMAKE_CXX_CLANG_TIDY": "clang-tidy;--header-filter=^${sourceDir}/"
                }
              },
              {
                "name": "ci-std",
                "description": "This preset makes sure the project actually builds with at least the specified standard",
                "hidden": true,
                "cacheVariables": {
                  "CMAKE_CXX_EXTENSIONS": "OFF",
                  "CMAKE_CXX_STANDARD": "20",
                  "CMAKE_CXX_STANDARD_REQUIRED": "ON"
                }
              },
              {
                "name": "flags-gcc-clang",
                "description": "These flags are supported by both GCC and Clang",
                "hidden": true,
                "cacheVariables": {
                  "CMAKE_CXX_FLAGS": "-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3 -D_GLIBCXX_ASSERTIONS=1 -fstack-protector-strong -fcf-protection=full -fstack-clash-protection -Wall -Wextra -Wpedantic -Wconversion -Wsign-conversion -Wcast-qual -Wformat=2 -Wundef -Werror=float-equal -Wshadow -Wcast-align -Wunused -Wnull-dereference -Wdouble-promotion -Wimplicit-fallthrough -Wextra-semi -Woverloaded-virtual -Wnon-virtual-dtor -Wold-style-cast",
                  "CMAKE_EXE_LINKER_FLAGS": "-Wl,--allow-shlib-undefined,--as-needed,-z,noexecstack,-z,relro,-z,now,-z,nodlopen",
                  "CMAKE_SHARED_LINKER_FLAGS": "-Wl,--allow-shlib-undefined,--as-needed,-z,noexecstack,-z,relro,-z,now,-z,nodlopen"
                }
              },
              {
                "name": "flags-appleclang",
                "hidden": true,
                "cacheVariables": {
                  "CMAKE_CXX_FLAGS": "-fstack-protector-strong -Wall -Wextra -Wpedantic -Wconversion -Wsign-conversion -Wcast-qual -Wformat=2 -Wundef -Werror=float-equal -Wshadow -Wcast-align -Wunused -Wnull-dereference -Wdouble-promotion -Wimplicit-fallthrough -Wextra-semi -Woverloaded-virtual -Wnon-virtual-dtor -Wold-style-cast"
                }
              },
              {
                "name": "flags-msvc",
                "description": "Note that all the flags after /W4 are required for MSVC to conform to the language standard",
                "hidden": true,
                "cacheVariables": {
                  "CMAKE_CXX_FLAGS": "/sdl /guard:cf /utf-8 /diagnostics:caret /w14165 /w44242 /w44254 /w44263 /w34265 /w34287 /w44296 /w44365 /w44388 /w44464 /w14545 /w14546 /w14547 /w14549 /w14555 /w34619 /w34640 /w24826 /w14905 /w14906 /w14928 /w45038 /W4 /permissive- /volatile:iso /Zc:inline /Zc:preprocessor /Zc:enumTypes /Zc:lambda /Zc:__cplusplus /Zc:externConstexpr /Zc:throwingNew /EHsc",
                  "CMAKE_EXE_LINKER_FLAGS": "/machine:x64 /guard:cf"
                }
              },
              {
                "name": "ci-linux",
                "inherits": ["flags-gcc-clang", "ci-std"],
                "generator": "Unix Makefiles",
                "hidden": true,
                "cacheVariables": {
                  "CMAKE_BUILD_TYPE": "Release"
                }
              },
              {
                "name": "ci-darwin",
                "inherits": ["flags-appleclang", "ci-std"],
                "generator": "Xcode",
                "hidden": true
              },
              {
                "name": "ci-win64",
                "inherits": ["flags-msvc", "ci-std"],
                "generator": "Visual Studio 17 2022",
                "architecture": "x64",
                "hidden": true
              },
              {
                "name": "coverage-linux",
                "binaryDir": "${sourceDir}/build/coverage",
                "inherits": "ci-linux",
                "hidden": true,
                "cacheVariables": {
                  "ENABLE_COVERAGE": "ON",
                  "CMAKE_BUILD_TYPE": "Coverage",
                  "CMAKE_CXX_FLAGS_COVERAGE": "-Og -g --coverage -fkeep-inline-functions -fkeep-static-functions",
                  "CMAKE_EXE_LINKER_FLAGS_COVERAGE": "--coverage",
                  "CMAKE_SHARED_LINKER_FLAGS_COVERAGE": "--coverage"
                }
              },
              {
                "name": "ci-coverage",
                "inherits": ["coverage-linux", "dev-mode"],
                "cacheVariables": {
                  "COVERAGE_HTML_COMMAND": ""
                }
              },
              {
                "name": "ci-sanitize",
                "binaryDir": "${sourceDir}/build/sanitize",
                "inherits": ["ci-linux", "dev-mode"],
                "cacheVariables": {
                  "CMAKE_BUILD_TYPE": "Sanitize",
                  "CMAKE_CXX_FLAGS_SANITIZE": "-U_FORTIFY_SOURCE -O2 -g -fsanitize=address,undefined -fno-omit-frame-pointer -fno-common"
                }
              },
              {
                "name": "ci-build",
                "binaryDir": "${sourceDir}/build",
                "hidden": true
              },
              {
                "name": "ci-multi-config",
                "description": "Speed up multi-config generators by generating only one configuration instead of the defaults",
                "hidden": true,
                "cacheVariables": {
                  "CMAKE_CONFIGURATION_TYPES": "Release"
                }
              },
              {
                "name": "ci-macos",
                "inherits": ["ci-build", "ci-darwin", "dev-mode", "ci-multi-config"]
              },
              {
                "name": "ci-ubuntu",
                "inherits": ["ci-build", "ci-linux", "clang-tidy", "cppcheck", "dev-mode"]
              },
              {
                "name": "ci-windows",
                "inherits": ["ci-build", "ci-win64", "dev-mode", "ci-multi-config"]
              }
            ]
          }
        
        diff --git a/ CODE_OF_CONDUCT.md b/ CODE_OF_CONDUCT.md
@@ -0,0 +1,5 @@
# Code of Conduct
          * You will be judged by your contributions first, and your sense of humor
            second.
          * Nobody owes you anything.
        
        diff --git a/ CONTRIBUTING.md b/ CONTRIBUTING.md
@@ -0,0 +1,19 @@
# Contributing
          <!--
              Short overview, rules, general guidelines, notes about pull requests and
              style should go here.
          -->
          ## Code of Conduct
          Please see the [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md) document.
          ## Getting started
          Helpful notes for developers can be found in the [`HACKING.md`](HACKING.md)
          document.
          In addition to he above, if you use the presets file as instructed, then you
          should NOT check it into source control, just as the CMake documentation
          suggests.
        
        diff --git a/ HACKING.md b/ HACKING.md
@@ -0,0 +1,145 @@
# Hacking
          Here is some wisdom to help you build and test this project as a developer and
          potential contributor.
          If you plan to contribute, please read the [CONTRIBUTING](CONTRIBUTING.md)
          guide.
          ## Developer mode
          Build system targets that are only useful for developers of this project are
          hidden if the `alec_DEVELOPER_MODE` option is disabled. Enabling this
          option makes tests and other developer targets and options available. Not
          enabling this option means that you are a consumer of this project and thus you
          have no need for these targets and options.
          Developer mode is always set to on in CI workflows.
          ### Presets
          This project makes use of [presets][1] to simplify the process of configuring
          the project. As a developer, you are recommended to always have the [latest
          CMake version][2] installed to make use of the latest Quality-of-Life
          additions.
          You have a few options to pass `alec_DEVELOPER_MODE` to the configure
          command, but this project prefers to use presets.
          As a developer, you should create a `CMakeUserPresets.json` file at the root of
          the project:
          ```json
          {
            "version": 2,
            "cmakeMinimumRequired": {
              "major": 3,
              "minor": 14,
              "patch": 0
            },
            "configurePresets": [
              {
                "name": "dev",
                "binaryDir": "${sourceDir}/build/dev",
                "inherits": ["dev-mode", "ci-<os>"],
                "cacheVariables": {
                  "CMAKE_BUILD_TYPE": "Debug"
                }
              }
            ],
            "buildPresets": [
              {
                "name": "dev",
                "configurePreset": "dev",
                "configuration": "Debug"
              }
            ],
            "testPresets": [
              {
                "name": "dev",
                "configurePreset": "dev",
                "configuration": "Debug",
                "output": {
                  "outputOnFailure": true
                }
              }
            ]
          }
          ```
          You should replace `<os>` in your newly created presets file with the name of
          the operating system you have, which may be `win64`, `linux` or `darwin`. You
          can see what these correspond to in the
          [`CMakePresets.json`](CMakePresets.json) file.
          `CMakeUserPresets.json` is also the perfect place in which you can put all
          sorts of things that you would otherwise want to pass to the configure command
          in the terminal.
          > **Note**
          > Some editors are pretty greedy with how they open projects with presets.
          > Some just randomly pick a preset and start configuring without your consent,
          > which can be confusing. Make sure that your editor configures when you
          > actually want it to, for example in CLion you have to make sure only the
          > `dev-dev preset` has `Enable profile` ticked in
          > `File > Settings... > Build, Execution, Deployment > CMake` and in Visual
          > Studio you have to set the option `Never run configure step automatically`
          > in `Tools > Options > CMake` **prior to opening the project**, after which
          > you can manually configure using `Project > Configure Cache`.
          ### Configure, build and test
          If you followed the above instructions, then you can configure, build and test
          the project respectively with the following commands from the project root on
          any operating system with any build system:
          ```sh
          cmake --preset=dev
          cmake --build --preset=dev
          ctest --preset=dev
          ```
          If you are using a compatible editor (e.g. VSCode) or IDE (e.g. CLion, VS), you
          will also be able to select the above created user presets for automatic
          integration.
          Please note that both the build and test commands accept a `-j` flag to specify
          the number of jobs to use, which should ideally be specified to the number of
          threads your CPU has. You may also want to add that to your preset using the
          `jobs` property, see the [presets documentation][1] for more details.
          ### Developer mode targets
          These are targets you may invoke using the build command from above, with an
          additional `-t <target>` flag:
          #### `coverage`
          Available if `ENABLE_COVERAGE` is enabled. This target processes the output of
          the previously run tests when built with coverage configuration. The commands
          this target runs can be found in the `COVERAGE_TRACE_COMMAND` and
          `COVERAGE_HTML_COMMAND` cache variables. The trace command produces an info
          file by default, which can be submitted to services with CI integration. The
          HTML command uses the trace command's output to generate an HTML document to
          `<binary-dir>/coverage_html` by default.
          #### `docs`
          Available if `BUILD_MCSS_DOCS` is enabled. Builds to documentation using
          Doxygen and m.css. The output will go to `<binary-dir>/docs` by default
          (customizable using `DOXYGEN_OUTPUT_DIRECTORY`).
          #### `format-check` and `format-fix`
          These targets run the clang-format tool on the codebase to check errors and to
          fix them respectively. Customization available using the `FORMAT_PATTERNS` and
          `FORMAT_COMMAND` cache variables.
          #### `spell-check` and `spell-fix`
          These targets run the codespell tool on the codebase to check errors and to fix
          them respectively. Customization available using the `SPELL_COMMAND` cache
          variable.
          [1]: https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html
          [2]: https://cmake.org/download/
        
        diff --git a/ README.md b/ README.md
          @@ -19,9 +19,7 @@ 
          Generator code shown here is written in flex and bison and can be easily
        
        
          adapted for other use cases.
          ## Getting Started
          ### Dependencies
          ## Dependencies
          * CMake 3.25.2 or latter
          * Compiler with C++20 support (tested on clang version 16.0.5)
        
        
          @@ -29,13 +27,9 @@ 
          adapted for other use cases.
        
        
          * Bison 3.8.2
          ### Installing
          ## Building and installing
          * Clone the repo
          * Make a build folder and cd into it
          * Run `cmake -DCMAKE_BUILD_TYPE=Release <path to cloned repo>`
          * Run `make`
          * If desired run `make install` in order to install the library
          See the [`BUILDING`](BUILDING.md) document.
          ### Usage
        
        
          @@ -200,13 +194,18 @@ 
          static inline bool limit_pos(int n) { return n >= 0; };
        
        
          ## Version History
          * 1.0
          * 0.1
              * Initial Release
          ## Contributing
          See the [`CONTRIBUTING`](CONTRIBUTING.md) document.
          ## License
          This project is licensed under the MIT License - see the LICENSE.md file for details
          This project is licensed under the MIT License - see the [`LICENSE`](LICENSE.md) file for details
          ## References
          diff --git a/ cmake/coverage.cmake b/ cmake/coverage.cmake
@@ -0,0 +1,33 @@
# ---- Variables ----
          # We use variables separate from what CTest uses, because those have
          # customization issues
          set(
              COVERAGE_TRACE_COMMAND
              lcov -c -q
              -o "${PROJECT_BINARY_DIR}/coverage.info"
              -d "${PROJECT_BINARY_DIR}"
              --include "${PROJECT_SOURCE_DIR}/*"
              CACHE STRING
              "; separated command to generate a trace for the 'coverage' target"
          )
          set(
              COVERAGE_HTML_COMMAND
              genhtml --legend -f -q
              "${PROJECT_BINARY_DIR}/coverage.info"
              -p "${PROJECT_SOURCE_DIR}"
              -o "${PROJECT_BINARY_DIR}/coverage_html"
              CACHE STRING
              "; separated command to generate an HTML report for the 'coverage' target"
          )
          # ---- Coverage target ----
          add_custom_target(
              coverage
              COMMAND ${COVERAGE_TRACE_COMMAND}
              COMMAND ${COVERAGE_HTML_COMMAND}
              COMMENT "Generating coverage report"
              VERBATIM
          )
        
        diff --git a/ cmake/dev-mode.cmake b/ cmake/dev-mode.cmake
@@ -0,0 +1,11 @@
include(cmake/folders.cmake)
          option(ENABLE_COVERAGE "Enable coverage support separate from CTest's" OFF)
          if(ENABLE_COVERAGE)
            include(cmake/coverage.cmake)
          endif()
          include(cmake/lint-targets.cmake)
          include(cmake/spell-targets.cmake)
          add_folders(Project)
        
        diff --git a/ cmake/folders.cmake b/ cmake/folders.cmake
@@ -0,0 +1,21 @@
set_property(GLOBAL PROPERTY USE_FOLDERS YES)
          # Call this function at the end of a directory scope to assign a folder to
          # targets created in that directory. Utility targets will be assigned to the
          # UtilityTargets folder, otherwise to the ${name}Targets folder. If a target
          # already has a folder assigned, then that target will be skipped.
          function(add_folders name)
            get_property(targets DIRECTORY PROPERTY BUILDSYSTEM_TARGETS)
            foreach(target IN LISTS targets)
              get_property(folder TARGET "${target}" PROPERTY FOLDER)
              if(DEFINED folder)
                continue()
              endif()
              set(folder Utility)
              get_property(type TARGET "${target}" PROPERTY TYPE)
              if(NOT type STREQUAL "UTILITY")
                set(folder "${name}")
              endif()
              set_property(TARGET "${target}" PROPERTY FOLDER "${folder}Targets")
            endforeach()
          endfunction()
        
        diff --git a/ cmake/install-config.cmake b/ cmake/install-config.cmake
@@ -0,0 +1,1 @@
include("${CMAKE_CURRENT_LIST_DIR}/alecTargets.cmake")
        
        diff --git a/ cmake/install-rules.cmake b/ cmake/install-rules.cmake
@@ -0,0 +1,66 @@
if(PROJECT_IS_TOP_LEVEL)
            set(
                CMAKE_INSTALL_INCLUDEDIR "include/alec-${PROJECT_VERSION}"
                CACHE STRING ""
            )
            set_property(CACHE CMAKE_INSTALL_INCLUDEDIR PROPERTY TYPE PATH)
          endif()
          # Project is configured with no languages, so tell GNUInstallDirs the lib dir
          set(CMAKE_INSTALL_LIBDIR lib CACHE PATH "")
          include(CMakePackageConfigHelpers)
          include(GNUInstallDirs)
          # find_package(<package>) call for consumers to find this project
          set(package alec)
          install(
              DIRECTORY include/
              DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
              COMPONENT alec_Development
          )
          install(
              TARGETS alec_alec
              EXPORT alecTargets
              INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
          )
          write_basic_package_version_file(
              "${package}ConfigVersion.cmake"
              COMPATIBILITY SameMajorVersion
              ARCH_INDEPENDENT
          )
          # Allow package maintainers to freely override the path for the configs
          set(
              alec_INSTALL_CMAKEDIR "${CMAKE_INSTALL_DATADIR}/${package}"
              CACHE STRING "CMake package config location relative to the install prefix"
          )
          set_property(CACHE alec_INSTALL_CMAKEDIR PROPERTY TYPE PATH)
          mark_as_advanced(alec_INSTALL_CMAKEDIR)
          install(
              FILES cmake/install-config.cmake
              DESTINATION "${alec_INSTALL_CMAKEDIR}"
              RENAME "${package}Config.cmake"
              COMPONENT alec_Development
          )
          install(
              FILES "${PROJECT_BINARY_DIR}/${package}ConfigVersion.cmake"
              DESTINATION "${alec_INSTALL_CMAKEDIR}"
              COMPONENT alec_Development
          )
          install(
              EXPORT alecTargets
              NAMESPACE alec::
              DESTINATION "${alec_INSTALL_CMAKEDIR}"
              COMPONENT alec_Development
          )
          if(PROJECT_IS_TOP_LEVEL)
            include(CPack)
          endif()
        
        diff --git a/ cmake/lint-targets.cmake b/ cmake/lint-targets.cmake
@@ -0,0 +1,33 @@
set(
              FORMAT_PATTERNS
              source/*.cpp source/*.hpp
              include/*.hpp
              test/*.cpp test/*.hpp
              CACHE STRING
              "; separated patterns relative to the project source dir to format"
          )
          set(FORMAT_COMMAND clang-format CACHE STRING "Formatter to use")
          add_custom_target(
              format-check
              COMMAND "${CMAKE_COMMAND}"
              -D "FORMAT_COMMAND=${FORMAT_COMMAND}"
              -D "PATTERNS=${FORMAT_PATTERNS}"
              -P "${PROJECT_SOURCE_DIR}/cmake/lint.cmake"
              WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
              COMMENT "Linting the code"
              VERBATIM
          )
          add_custom_target(
              format-fix
              COMMAND "${CMAKE_COMMAND}"
              -D "FORMAT_COMMAND=${FORMAT_COMMAND}"
              -D "PATTERNS=${FORMAT_PATTERNS}"
              -D FIX=YES
              -P "${PROJECT_SOURCE_DIR}/cmake/lint.cmake"
              WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
              COMMENT "Fixing the code"
              VERBATIM
          )
        
        diff --git a/ cmake/lint.cmake b/ cmake/lint.cmake
@@ -0,0 +1,51 @@
cmake_minimum_required(VERSION 3.14)
          macro(default name)
            if(NOT DEFINED "${name}")
              set("${name}" "${ARGN}")
            endif()
          endmacro()
          default(FORMAT_COMMAND clang-format)
          default(
              PATTERNS
              source/*.cpp source/*.hpp
              include/*.hpp
              test/*.cpp test/*.hpp
          )
          default(FIX NO)
          set(flag --output-replacements-xml)
          set(args OUTPUT_VARIABLE output)
          if(FIX)
            set(flag -i)
            set(args "")
          endif()
          file(GLOB_RECURSE files ${PATTERNS})
          set(badly_formatted "")
          set(output "")
          string(LENGTH "${CMAKE_SOURCE_DIR}/" path_prefix_length)
          foreach(file IN LISTS files)
            execute_process(
                COMMAND "${FORMAT_COMMAND}" --style=file "${flag}" "${file}"
                WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
                RESULT_VARIABLE result
                ${args}
            )
            if(NOT result EQUAL "0")
              message(FATAL_ERROR "'${file}': formatter returned with ${result}")
            endif()
            if(NOT FIX AND output MATCHES "\n<replacement offset")
              string(SUBSTRING "${file}" "${path_prefix_length}" -1 relative_file)
              list(APPEND badly_formatted "${relative_file}")
            endif()
            set(output "")
          endforeach()
          if(NOT badly_formatted STREQUAL "")
            list(JOIN badly_formatted "\n" bad_list)
            message("The following files are badly formatted:\n\n${bad_list}\n")
            message(FATAL_ERROR "Run again with FIX=YES to fix these files.")
          endif()
        
        diff --git a/ cmake/prelude.cmake b/ cmake/prelude.cmake
@@ -0,0 +1,10 @@
# ---- In-source guard ----
          if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR)
            message(
                FATAL_ERROR
                "In-source builds are not supported. "
                "Please read the BUILDING document before trying to build this project. "
                "You may need to delete 'CMakeCache.txt' and 'CMakeFiles/' first."
            )
          endif()
        
        diff --git a/ cmake/project-is-top-level.cmake b/ cmake/project-is-top-level.cmake
@@ -0,0 +1,6 @@
# This variable is set by project() in CMake 3.21+
          string(
              COMPARE EQUAL
              "${CMAKE_SOURCE_DIR}" "${PROJECT_SOURCE_DIR}"
              PROJECT_IS_TOP_LEVEL
          )
        
        diff --git a/ cmake/spell-targets.cmake b/ cmake/spell-targets.cmake
@@ -0,0 +1,22 @@
set(SPELL_COMMAND codespell CACHE STRING "Spell checker to use")
          add_custom_target(
              spell-check
              COMMAND "${CMAKE_COMMAND}"
              -D "SPELL_COMMAND=${SPELL_COMMAND}"
              -P "${PROJECT_SOURCE_DIR}/cmake/spell.cmake"
              WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
              COMMENT "Checking spelling"
              VERBATIM
          )
          add_custom_target(
              spell-fix
              COMMAND "${CMAKE_COMMAND}"
              -D "SPELL_COMMAND=${SPELL_COMMAND}"
              -D FIX=YES
              -P "${PROJECT_SOURCE_DIR}/cmake/spell.cmake"
              WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
              COMMENT "Fixing spelling errors"
              VERBATIM
          )
        
        diff --git a/ cmake/spell.cmake b/ cmake/spell.cmake
@@ -0,0 +1,29 @@
cmake_minimum_required(VERSION 3.14)
          macro(default name)
            if(NOT DEFINED "${name}")
              set("${name}" "${ARGN}")
            endif()
          endmacro()
          default(SPELL_COMMAND codespell)
          default(FIX NO)
          set(flag "")
          if(FIX)
            set(flag -w)
          endif()
          execute_process(
              COMMAND "${SPELL_COMMAND}" ${flag}
              WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
              RESULT_VARIABLE result
          )
          if(result EQUAL "65")
            message(FATAL_ERROR "Run again with FIX=YES to fix these errors.")
          elseif(result EQUAL "64")
            message(FATAL_ERROR "Spell checker printed the usage info. Bad arguments?")
          elseif(NOT result EQUAL "0")
            message(FATAL_ERROR "Spell checker returned with ${result}")
          endif()
        
        diff --git a/ cmake/variables.cmake b/ cmake/variables.cmake
@@ -0,0 +1,28 @@
# ---- Developer mode ----
          # Developer mode enables targets and code paths in the CMake scripts that are
          # only relevant for the developer(s) of alec
          # Targets necessary to build the project must be provided unconditionally, so
          # consumers can trivially build and package the project
          if(PROJECT_IS_TOP_LEVEL)
            option(alec_DEVELOPER_MODE "Enable developer mode" OFF)
          endif()
          # ---- Warning guard ----
          # target_include_directories with the SYSTEM modifier will request the compiler
          # to omit warnings from the provided paths, if the compiler supports that
          # This is to provide a user experience similar to find_package when
          # add_subdirectory or FetchContent is used to consume this project
          set(warning_guard "")
          if(NOT PROJECT_IS_TOP_LEVEL)
            option(
                alec_INCLUDES_WITH_SYSTEM
                "Use SYSTEM modifier for alec's includes, disabling warnings"
                ON
            )
            mark_as_advanced(alec_INCLUDES_WITH_SYSTEM)
            if(alec_INCLUDES_WITH_SYSTEM)
              set(warning_guard SYSTEM)
            endif()
          endif()
        
        diff --git a/ demo/CMakeLists.txt b/ demo/CMakeLists.txt
@@ -1,17 +0,0 @@
add_executable(demo demo.cpp)
          target_link_libraries(demo alec)
          set_target_properties(demo PROPERTIES
              VERSION ${PROJECT_VERSION}
              SOVERSION ${PROJECT_VERSION_MAJOR}
              RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
          )
          add_executable(demo_runtime demo_runtime.cpp)
          target_link_libraries(demo_runtime alec)
          set_target_properties(demo_runtime PROPERTIES
              VERSION ${PROJECT_VERSION}
              SOVERSION ${PROJECT_VERSION_MAJOR}
              RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
          )
        
        diff --git a/ demo/demo.cpp b/ demo/demo.cpp
@@ -1,28 +0,0 @@
#include "alec.hpp"
          #include <iostream>
          using namespace alec;
          using enum Color;
          using enum Decor;
          int main(void) {
              std::cout << abuf_enable_v << cursor_hide_v;
              std::cout << cursor_position_v<1, 1> << foreground_v<91> << "HELLO!\n";
              std::cout << cursor_down_v<3>;
              std::cout << foreground_v<30> << background_v<196, 53, 64> << "WORLD!\n";
              std::cout << background_v<DEFAULT> << "testing 1...\n" << foreground_v<DEFAULT>;
              std::cout << decor_set_v<INVERSE> << "testing 2...\n" << decor_reset_v<INVERSE>;
              std::cout << cursor_up_v<5> << "Hello there!" << cursor_save_v;
              std::cout << cursor_down_v<10> << "General Kenobi!";
              std::cout << cursor_position_v<10, 40> << "no pain no gain" << cursor_restore_v << cursor_show_v;
              getchar();
              std::cout << abuf_disable_v;
              return 0;
          }
        
        diff --git a/ demo/demo_runtime.cpp b/ demo/demo_runtime.cpp
@@ -1,28 +0,0 @@
#include "alec.hpp"
          #include <iostream>
          using namespace alec;
          using enum Color;
          using enum Decor;
          int main(void) {
              std::cout << abuf_enable() << cursor_hide();
              std::cout << cursor_position(1, 1) << foreground(91) << "HELLO!\n";
              std::cout << cursor_down(3);
              std::cout << foreground(30) << background(96, 53, 64) << "WORLD!\n";
              std::cout << background(DEFAULT) << "testing 1...\n" << foreground(DEFAULT);
              std::cout << decor_set(INVERSE) << "testing 2...\n" << decor_reset(INVERSE);
              std::cout << cursor_up(5) << "Hello there!" << cursor_save();
              std::cout << cursor_down(10) << "General Kenobi!";
              std::cout << cursor_position(10, 40) << "no pain no gain" << cursor_restore() << cursor_show();
              getchar();
              std::cout << abuf_disable();
              return 0;
          }
        
        diff --git a/ example/CMakeLists.txt b/ example/CMakeLists.txt
@@ -0,0 +1,26 @@
cmake_minimum_required(VERSION 3.14)
          project(alecExamples CXX)
          include(../cmake/project-is-top-level.cmake)
          include(../cmake/folders.cmake)
          if(PROJECT_IS_TOP_LEVEL)
            find_package(alec REQUIRED)
          endif()
          add_custom_target(run-examples)
          function(add_example NAME)
            add_executable("${NAME}" "${NAME}.cpp")
            target_link_libraries("${NAME}" PRIVATE alec::alec)
            target_compile_features("${NAME}" PRIVATE cxx_std_20)
            add_custom_target("run_${NAME}" COMMAND "${NAME}" VERBATIM)
            add_dependencies("run_${NAME}" "${NAME}")
            add_dependencies(run-examples "run_${NAME}")
          endfunction()
          add_example(alec_compile)
          add_example(alec_runtime)
          add_folders(Example)
        
        diff --git a/ example/alec_compile.cpp b/ example/alec_compile.cpp
@@ -0,0 +1,29 @@
#include "alec/alec.hpp"
          #include <iostream>
          using namespace alec;
          using enum Color;
          using enum Decor;
          int main(void) {
              std::cout << abuf_enable_v << cursor_hide_v;
              std::cout << cursor_position_v<1, 1> << foreground_v<91> << "HELLO!\n";
              std::cout << cursor_down_v<3>;
              std::cout << foreground_v<30> << background_v<196, 53, 64> << "WORLD!\n";
              std::cout << background_v<DEFAULT> << "testing 1...\n" << foreground_v<DEFAULT>;
              std::cout << decor_set_v<INVERSE> << "testing 2...\n" << decor_reset_v<INVERSE>;
              std::cout << cursor_up_v<5> << "Hello there!" << cursor_save_v;
              std::cout << cursor_down_v<10> << "General Kenobi!";
              std::cout << cursor_position_v<10, 40> << "no pain no gain" << cursor_restore_v << cursor_show_v;
              getchar();
              std::cout << abuf_disable_v;
              return 0;
          }
        
        diff --git a/ example/alec_runtime.cpp b/ example/alec_runtime.cpp
@@ -0,0 +1,28 @@
#include "alec/alec.hpp"
          #include <iostream>
          using namespace alec;
          using enum Color;
          using enum Decor;
          int main(void) {
              std::cout << abuf_enable() << cursor_hide();
              std::cout << cursor_position(1, 1) << foreground(91) << "HELLO!\n";
              std::cout << cursor_down(3);
              std::cout << foreground(30) << background(96, 53, 64) << "WORLD!\n";
              std::cout << background(DEFAULT) << "testing 1...\n" << foreground(DEFAULT);
              std::cout << decor_set(INVERSE) << "testing 2...\n" << decor_reset(INVERSE);
              std::cout << cursor_up(5) << "Hello there!" << cursor_save();
              std::cout << cursor_down(10) << "General Kenobi!";
              std::cout << cursor_position(10, 40) << "no pain no gain" << cursor_restore() << cursor_show();
              getchar();
              std::cout << abuf_disable();
              return 0;
          }
        
        diff --git a/ source/alec.rules.hpp b/ source/alec.rules.hpp
@@ -0,0 +1,337 @@
#ifndef ALEC_ALEC_H
          #define ALEC_ALEC_H
          #include <algorithm>
          #include <array>
          #include <assert.h>
          #include <cstdint>
          #include <string>
          #include <type_traits>
          namespace alec {
          enum Ctrl {
              BELL = 0x07,
              BS = 0x08,
              HT = 0x09,
              LF = 0x0A,
              VT = 0x0B,
              FF = 0x0C,
              CR = 0x0D,
              ESC = 0x1B,
              DEL = 0x7F,
          };
          enum class Color {
              BLACK = 0,
              RED = 1,
              GREEN = 2,
              YELLOW = 3,
              BLUE = 4,
              MAGENTA = 5,
              CYAN = 6,
              WHITE = 7,
              DEFAULT = 9,
          };
          enum class Decor {
              RESET = 0,
              BOLD = 1,
              DIM = 2,
              ITALIC = 3,
              UNDERLINE = 4,
              BLINK = 5,
              INVERSE = 7,
              HIDE = 8,
              STRIKE = 9,
          };
          enum class Motion {
              END = 0,
              BEGIN = 1,
              WHOLE = 2,
          };
          namespace details {
          template<std::size_t N>
          struct string_literal
          {
            constexpr string_literal(const char (&str)[N]) : m_value(std::to_array(str)) {}
            constexpr std::size_t size() const { return N; }
            constexpr const char* data() const { return m_value.data(); }
            std::array<char, N> m_value;
          };
          namespace helper {
              template <std::size_t N> static constexpr std::size_t size(string_literal<N> val) { return N; }
              static constexpr std::size_t size(char val) { return 1; }
              static constexpr std::size_t size(int val) {
                  std::size_t len = 1;
                  while (val /= 10) len++;
                  return len;
              }
              template <std::size_t N> static constexpr char *append(char *ptr, string_literal<N> val) {
                  std::copy_n(val.data(), N, ptr);
                  return ptr + N;
              }
              static constexpr char *append(char *ptr, char val) {
                  *ptr++ = val;
                  return ptr;
              }
              static constexpr char *append(char *ptr, int val) {
                  char *tmp = ptr += size(val);
                  do {
                      *--tmp = '0' + (val % 10);
                  } while (val /= 10);
                  return ptr;
              }
              static const std::string make(auto... args) {
                  std::string res((helper::size(args) + ... + 2), 0);
                  res[0] = Ctrl::ESC, res[1] = '[';
                  auto ptr = res.data() + 2;
                  ((ptr = helper::append(ptr, args)), ...);
                  return res;
              }
              template <auto... Args> struct escape_t {
                  static constexpr const auto value = []() {
                      std::array<char, (helper::size(Args) + ... + 3)> arr = {Ctrl::ESC, '[', 0};
                      auto ptr = arr.data() + 2;
                      ((ptr = helper::append(ptr, Args)), ...);
                      return arr;
                  }();
                  static constexpr auto data = value.data();
              };
          };
          template <auto... Args> static constexpr auto escape = helper::escape_t<Args...>().data;
          template <details::string_literal... Strs> static constexpr auto escape_literal = escape<Strs...>;
          } // namespace details
          // Tamplate parameter constraints
          template <int n>
          concept limit_256_v = n >= 0 && n < 256;
          template <int n>
          concept limit_pos_v = n >= 0;
          static inline bool limit_pos(int n) { return n >= 0; };
          static inline bool limit_256(int n) { return n >= 0 && n < 256; };
          %%
          // Move cursor up/down/frwd/back
              cursor_up
              int n
              limit_pos
              n, 'A'
              cursor_down
              int n
              limit_pos
              n, 'B'
              cursor_frwd
              int n
              limit_pos
              n, 'C'
              cursor_back
              int n
              limit_pos
              n, 'D'
          // Move cursor to the next/prev line
              cursor_line_next
              int n
              limit_pos
              n, 'E'
              cursor_line_prev
              int n
              limit_pos
              n, 'F'
          // Set cursor to specific column
              cursor_column
              int n
              limit_pos
              n, 'G'
          // Erase functions
              erase_display
              Motion m
              |
              (int)m, 'J'
              erase_line
              Motion m
              |
              (int)m, 'K'
          // Scroll up/down
              scroll_up
              int n
              limit_pos
              n, 'S'
              scroll_down
              int n
              limit_pos
              n, 'T'
          // Set cursor to a specific position
              cursor_position
              int n, int m
              limit_pos
              n, ';', m, 'H'
          // color
          // palet colors
              foreground
              Color color
              |
              (int)color + 30, 'm'
              background
              Color color
              |
              (int)color + 40, 'm'
          // 256-color palette
              foreground
              int idx
              limit_256
              38, ';', 5, ';', idx, 'm'
              background
              int idx
              limit_256
              48, ';', 5, ';', idx, 'm'
          // RGB colors
              foreground
              int R, int G, int B
              limit_256
              38, ';', 2, ';', R, ';', G, ';', B, 'm'
              background
              int R, int G, int B
              limit_256
              48, ';', 2, ';', R, ';', G, ';', B, 'm'
          // Set/reset text decorators
              decor_set
              Decor decor
              |
              (int)decor, 'm'
              decor_reset
              Decor decor
              |
              (int)decor + 20, 'm'
          // Save/restore cursor position;
              cursor_save
              |
              |
              's'
              cursor_restore
              |
              |
              'u'
          // Set screen modes
              screen_mode_set
              int n
              limit_pos
              '=', n, 'h'
              screen_mode_reset
              int n
              limit_pos
              '=', n, 'l'
          // Private screen modes supported by most terminals
          // Save/restore screen
              screen_save
              |
              |
              "?47h"
              screen_restore
              |
              |
              "?47l"
          // Show/hide cursor
              cursor_show
              |
              |
              "?25h"
              cursor_hide
              |
              |
              "?25l"
          // Enable/disable alternate buffer
              abuf_enable
              |
              |
              "?1049h"
              abuf_disable
              |
              |
              "?1049l"
          // Enable/disable bracketed paste mode
              paste_enable
              |
              |
              "?2004h"
              paste_disable
              |
              |
              "?2004l"
          %%
          // Keyboard string TODO
          } // namespace ALEC
          #endif
        
        diff --git a/ source/generator.c b/ source/generator.c
@@ -0,0 +1,265 @@
#include "generator.h"
          #include "parser.h"
          #define MALLOC(x)                                                                                            \
              do {                                                                                                     \
                  x = malloc(sizeof(*x));                                                                              \
                  if (!x) yyerror("out of space"), exit(1);                                                            \
              } while (0);
          #ifndef YYDEBUG
          int yydebug = 1;
          #endif
          list_t records = {0};
          list_t epilogue = {0};
          list_t prologue = {0};
          int main(const int argc, char *argv[]) {
              if (argc < 2) {
                  yyparse();
                  return 0;
              }
              for (int i = 1; i < argc; i++) {
                  FILE *f = fopen(argv[i], "r");
                  if (!f) {
                      perror(argv[1]);
                      return -1;
                  }
                  yyrestart(f);
                  yyparse();
                  fclose(f);
              }
              // print prologue section
              for (node_t *p = prologue.head; p; p = p->next) {
                  printf("%s", p->data);
              }
              list_t dupes = {0};
              record_dupes(&dupes, &records);
              printf("\n/* Template compile-time variables */\n\n");
              record_print_dupes(&dupes);
              for (node_t *p = records.head; p; p = p->next) {
                  const record_t *r = (const record_t *)p->data;
                  record_print_template(r, list_find(&dupes, r->name, scmp));
              }
              printf("\n/* Run-time functions */\n\n");
              for (node_t *p = records.head; p; p = p->next) {
                  const record_t *r = (const record_t *)p->data;
                  record_print_function(r);
              }
              // print epilogue section
              for (node_t *p = epilogue.head; p; p = p->next) {
                  printf("%s", p->data);
              }
              list_free(&dupes, 0);
              list_free(&prologue, free);
              list_free(&records, record_free);
              list_free(&epilogue, free);
              yylex_destroy();
          }
          node_t *node_new(char *data) {
              node_t *n;
              MALLOC(n);
              *n = (node_t){
                  .data = data,
                  .next = NULL,
              };
              return n;
          }
          list_t *list_new(char *data) {
              list_t *l;
              MALLOC(l);
              *l = (list_t){0};
              if (data) list_append(l, node_new(data));
              return l;
          }
          void list_free(list_t *l, void (*free_data)(void *)) {
              if (!l) return;
              node_t *c = l->head, *t;
              while (c) {
                  t = c;
                  c = c->next;
                  if (free_data) free_data((void *)t->data);
                  free(t);
              }
          }
          void list_append(list_t *l, node_t *n) {
              if (!l->head) l->head = l->tail = n;
              else
                  l->tail = l->tail->next = n;
          }
          record_t *record_new(char *name, list_t *args, list_t *rules, list_t *recipe) {
              record_t *rec;
              MALLOC(rec);
              *rec = (record_t){
                  .name = name,
                  .args = args,
                  .rules = rules,
                  .recipe = recipe,
              };
              return rec;
          }
          void record_free(void *rp) {
              record_t *r = (record_t *)rp;
              if (r->args) list_free(r->args, free), free(r->args);
              if (r->rules) list_free(r->rules, free), free(r->rules);
              if (r->recipe) list_free(r->recipe, free), free(r->recipe);
              free(r->name);
              free(r);
          }
          int list_find(list_t *l, void *data, cmp_f cmp) {
              node_t *c = l->head;
              while (c) {
                  if (!cmp(c->data, data)) return 1;
                  c = c->next;
              }
              return 0;
          }
          void record_print_dupes(const list_t *l) {
              node_t *c = l->head;
              while (c) {
                  printf("template <auto... val> static const char *%s_v;\n", c->data);
                  c = c->next;
              }
              printf("\n");
          }
          void record_print_function(const record_t *r) {
              list_t dummy = {0};
              if (!r->recipe) { // comment
                  printf("%s\n\n", r->name);
                  return;
              }
              printf("static constexpr auto %s(", r->name);
              if (r->args) {
                  for (node_t *p = r->args->head; p; p = p->next) {
                      list_append(&dummy, node_new(strchr(p->data, ' ') + 1));
                      printf("%s", p->data);
                      if (p->next) printf(", ");
                  }
              }
              printf(") {\n");
              if (r->rules) {
                  for (node_t *p = dummy.head; p; p = p->next) {
                      printf("\tassert(");
                      for (node_t *q = r->rules->head; q; q = q->next) {
                          printf("%s(%s)", q->data, p->data);
                          if (q->next) printf(" && ");
                      }
                      printf(");\n");
                  }
              }
              if (r->args) {
                  printf("\treturn details::helper::make(");
                  for (node_t *p = r->recipe->head; p; p = p->next) {
                      printf("%s", p->data);
                      if (p->next) printf(", ");
                  }
                  printf(")");
              } else {
                  printf("\treturn %s_v", r->name);
              }
              printf(";\n}\n\n");
              list_free(&dummy, NULL);
          }
          void record_print_template(const record_t *r, int dup) {
              list_t dummy = {0};
              if (!r->recipe) { // comment
                  printf("%s\n\n", r->name);
                  return;
              }
              if (r->args) {
                  printf("template <");
                  for (node_t *p = r->args->head; p; p = p->next) {
                      list_append(&dummy, node_new(strchr(p->data, ' ') + 1));
                      printf("%s", p->data);
                      if (p->next) printf(", ");
                  }
                  printf(">\n");
              }
              if (r->rules) {
                  printf("\trequires ");
                  for (node_t *p = dummy.head; p; p = p->next) {
                      for (node_t *q = r->rules->head; q; q = q->next) {
                          printf("%s_v<%s>", q->data, p->data);
                          if (q->next) printf(" && ");
                      }
                      if (p->next) printf(" && ");
                  }
                  printf("\n");
              }
              printf("static constexpr auto %s_v", r->name);
              if (dup) {
                  printf("<");
                  for (node_t *p = dummy.head; p; p = p->next) {
                      printf("%s", p->data);
                      if (p->next) printf(", ");
                  }
                  printf(">");
              }
              if (r->recipe->head && r->recipe->head->data[0] == '"') {
                  printf("\n\t = details::escape_literal<%s>;", r->recipe->head->data);
              } else {
                  printf("\n\t = details::escape<");
                  for (node_t *p = r->recipe->head; p; p = p->next) {
                      printf("%s", p->data);
                      if (p->next) printf(", ");
                  }
                  printf(">;");
              }
              printf("\n\n");
              list_free(&dummy, NULL);
          }
          int scmp(const void *a, const void *b) { return strcmp((const char *)a, (const char *)b); }
          void record_dupes(list_t *d, list_t *l) {
              list_t s = {0};
              for (node_t *p = records.head; p; p = p->next) {
                  const record_t *r = (const record_t *)p->data;
                  if (!list_find(&s, r->name, scmp)) list_append(&s, node_new(r->name));
                  else if (!list_find(d, r->name, scmp))
                      list_append(d, node_new(r->name));
              }
              list_free(&s, NULL);
          }
        
        diff --git a/ source/generator.h b/ source/generator.h
@@ -0,0 +1,54 @@
#ifndef ALEC_PARSER_H
          #define ALEC_PARSER_H
          #include <stdio.h>
          #include <stdlib.h>
          #include <string.h>
          extern int yylineno;
          void yyerror(char *s, ...);
          typedef struct node node_t;
          typedef struct list list_t;
          typedef struct record record_t;
          struct node {
              char *data;
              node_t *next;
          };
          struct list {
              node_t *head;
              node_t *tail;
          };
          struct record {
              char *name;
              list_t *args;
              list_t *rules;
              list_t *recipe;
          };
          typedef void (*free_f)(void *);
          typedef int (*cmp_f)(const void *, const void *);
          int scmp(const void *a, const void *b);
          node_t *node_new(char *data);
          list_t *list_new(char *data);
          void list_free(list_t *l, free_f free_data);
          void list_append(list_t *l, node_t *n);
          int list_find(list_t *l, void *data, cmp_f cmp);
          struct record *record_new(char *name, list_t *args, list_t *rules, list_t *recipe);
          void record_free(void *rp);
          void record_dupes(list_t *d, list_t *l);
          void record_print_template(const struct record *r, int dup);
          void record_print_function(const struct record *r);
          void record_print_dupes(const list_t *l);
          #endif
        
        diff --git a/ source/lexer.l b/ source/lexer.l
@@ -0,0 +1,40 @@
%option noyywrap nodefault yylineno
          %{
              #include "generator.h"
              #include "parser.h"
              #include <ctype.h>
          %}
          LINE_END (\n|\r|\r\n)
          %x GEN
          %x LAST
          %%
          "%%"{LINE_END}     { BEGIN GEN; return SWITCH; }
          .*{LINE_END}       { yylval.n = strdup(yytext); return PROLOGUE; }
          <GEN>{LINE_END}                  { return EOL; }
          <GEN>^[\t ]*{LINE_END}           { return EOL; }
          <GEN>^[\t ]*\|*[\t ]*{LINE_END}  { return EMPTY; }
          <GEN>^[\t ]*"//".* { yylval.n = strdup(yytext); return COMMENT; }
          <GEN>, { return COMMA; }
          <GEN>[^,\n]*                  {
              char *p = yytext + strlen(yytext) - 1;
              while(isspace(*p)) *p-- = '\0';
          	while(*yytext && isspace(*yytext)) yytext++;
          	yylval.n = strdup(yytext); return LITERAL;
          }
          <GEN>"%%"{LINE_END}        { BEGIN LAST; return SWITCH; }
          <LAST>.*{LINE_END}       { yylval.n = strdup(yytext); return EPILOGUE; }
          %%
          diff --git a/ source/parser.y b/ source/parser.y
@@ -0,0 +1,72 @@
%code requires {
              #include "generator.h"
              #include <stdarg.h>
          }
          %define api.value.type union
          %token <char *> n LITERAL COMMENT PROLOGUE EPILOGUE
          %type <record_t *> record
          %type <list_t *> list items
          %type <char *> name
          %token EOL COMMA SWITCH EMPTY
          %code provides {
          	int yylex(void);
          	int yyparse(void);
          	void yyrestart(FILE *);
          	int yylex_destroy(void);
          	extern list_t records;
          	extern list_t epilogue;
          	extern list_t prologue;
          }
          %destructor { free($$); } <char *>
          %start document
          %%
          document: prologue grammar epilogue
          prologue: %empty
                | prologue PROLOGUE { list_append(&prologue, node_new($2)); }
                ;
          epilogue: SWITCH
               | epilogue EPILOGUE { list_append(&epilogue, node_new($2)); }
               ;
          grammar: SWITCH
             | grammar EOL
             | grammar record { list_append(&records, node_new((char *)$2)); }
             ;
          record: name list list list { $$ = record_new($1, $2, $3, $4); }
                | COMMENT             { $$ = record_new($1, NULL, NULL, NULL); }
                ;
          name: LITERAL EOL   { $$ = $1; }
              ;
          list: EMPTY       { $$ = NULL; }
              | items EOL   { $$ = $1; }
              ;
          items: LITERAL             { $$ = list_new($1); }
               | items COMMA LITERAL { list_append($1, node_new($3)); $$ = $1; }
               ;
          %%
          void yyerror(char *s, ...) {
            va_list ap;
            va_start(ap, s);
            fprintf(stderr, "%d: error: ", yylineno);
            vfprintf(stderr, s, ap);
            fprintf(stderr, "\n");
          }
          diff --git a/ src/CMakeLists.txt b/ src/CMakeLists.txt
@@ -1,52 +0,0 @@
set(PARSER_DIR "${CMAKE_CURRENT_BINARY_DIR}")
          find_package(FLEX)
          find_package(BISON)
          set(LEXER_OUT "${PARSER_DIR}/lexer.c")
          set(PARSER_OUT "${PARSER_DIR}/parser.c")
          if(CMAKE_BUILD_TYPE STREQUAL "Debug")
          	set(FLAGS "--debug")
          endif()
          FLEX_TARGET(LEXER lexer.l "${LEXER_OUT}" DEFINES_FILE "${PARSER_DIR}/scanner.h" COMPILE_FLAGS "${FLAGS}")
          BISON_TARGET(PARSER parser.y "${PARSER_OUT}" DEFINES_FILE "${PARSER_DIR}/parser.h" COMPILE_FLAGS "${FLAGS}")
          ADD_FLEX_BISON_DEPENDENCY(LEXER PARSER)
          add_executable(generator "${LEXER_OUT}" "${PARSER_OUT}" generator.c)
          target_include_directories(generator PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_BINARY_DIR}")
          set(GENERATE_OUT "${CMAKE_BINARY_DIR}/bin")
          set_target_properties(generator PROPERTIES
              VERSION ${PROJECT_VERSION}
              SOVERSION ${PROJECT_VERSION_MAJOR}
              RUNTIME_OUTPUT_DIRECTORY "${GENERATE_OUT}"
          )
          set(RULES_NAME "alec.rules.hpp")
          set(RULES_FILE "${CMAKE_BINARY_DIR}/${RULES_NAME}")
          configure_file(alec.rules.hpp ${RULES_FILE} COPYONLY)
          add_custom_command(
              OUTPUT ${GENERATE_OUT}/alec.hpp
              COMMAND generator ${RULES_FILE} > ${GENERATE_OUT}/alec.hpp
              DEPENDS generator ${RULES_NAME}
              COMMENT "Generating include file"
          )
          add_library(alec INTERFACE ${GENERATE_OUT}/alec.hpp)
          target_include_directories(alec INTERFACE ${GENERATE_OUT})
          set_target_properties(alec PROPERTIES
              VERSION ${PROJECT_VERSION}
              SOVERSION ${PROJECT_VERSION_MAJOR}
              PUBLIC_HEADER ${GENERATE_OUT}/alec.hpp
          )
          install(TARGETS alec
              LIBRARY DESTINATION lib
              PUBLIC_HEADER DESTINATION include
          )
        
        diff --git a/ src/alec.rules.hpp b/ src/alec.rules.hpp
@@ -1,337 +0,0 @@
#ifndef ALEC_ALEC_H
          #define ALEC_ALEC_H
          #include <algorithm>
          #include <array>
          #include <assert.h>
          #include <cstdint>
          #include <string>
          #include <type_traits>
          namespace alec {
          enum Ctrl {
              BELL = 0x07,
              BS = 0x08,
              HT = 0x09,
              LF = 0x0A,
              VT = 0x0B,
              FF = 0x0C,
              CR = 0x0D,
              ESC = 0x1B,
              DEL = 0x7F,
          };
          enum class Color {
              BLACK = 0,
              RED = 1,
              GREEN = 2,
              YELLOW = 3,
              BLUE = 4,
              MAGENTA = 5,
              CYAN = 6,
              WHITE = 7,
              DEFAULT = 9,
          };
          enum class Decor {
              RESET = 0,
              BOLD = 1,
              DIM = 2,
              ITALIC = 3,
              UNDERLINE = 4,
              BLINK = 5,
              INVERSE = 7,
              HIDE = 8,
              STRIKE = 9,
          };
          enum class Motion {
              END = 0,
              BEGIN = 1,
              WHOLE = 2,
          };
          namespace details {
          template<std::size_t N>
          struct string_literal
          {
            constexpr string_literal(const char (&str)[N]) : m_value(std::to_array(str)) {}
            constexpr std::size_t size() const { return N; }
            constexpr const char* data() const { return m_value.data(); }
            std::array<char, N> m_value;
          };
          namespace helper {
              template <std::size_t N> static constexpr std::size_t size(string_literal<N> val) { return N; }
              static constexpr std::size_t size(char val) { return 1; }
              static constexpr std::size_t size(int val) {
                  std::size_t len = 1;
                  while (val /= 10) len++;
                  return len;
              }
              template <std::size_t N> static constexpr char *append(char *ptr, string_literal<N> val) {
                  std::copy_n(val.data(), N, ptr);
                  return ptr + N;
              }
              static constexpr char *append(char *ptr, char val) {
                  *ptr++ = val;
                  return ptr;
              }
              static constexpr char *append(char *ptr, int val) {
                  char *tmp = ptr += size(val);
                  do {
                      *--tmp = '0' + (val % 10);
                  } while (val /= 10);
                  return ptr;
              }
              static const std::string make(auto... args) {
                  std::string res((helper::size(args) + ... + 2), 0);
                  res[0] = Ctrl::ESC, res[1] = '[';
                  auto ptr = res.data() + 2;
                  ((ptr = helper::append(ptr, args)), ...);
                  return res;
              }
              template <auto... Args> struct escape_t {
                  static constexpr const auto value = []() {
                      std::array<char, (helper::size(Args) + ... + 3)> arr = {Ctrl::ESC, '[', 0};
                      auto ptr = arr.data() + 2;
                      ((ptr = helper::append(ptr, Args)), ...);
                      return arr;
                  }();
                  static constexpr auto data = value.data();
              };
          };
          template <auto... Args> static constexpr auto escape = helper::escape_t<Args...>().data;
          template <details::string_literal... Strs> static constexpr auto escape_literal = escape<Strs...>;
          } // namespace details
          // Tamplate parameter constraints
          template <int n>
          concept limit_256_v = n >= 0 && n < 256;
          template <int n>
          concept limit_pos_v = n >= 0;
          static inline bool limit_pos(int n) { return n >= 0; };
          static inline bool limit_256(int n) { return n >= 0 && n < 256; };
          %%
          // Move cursor up/down/frwd/back
              cursor_up
              int n
              limit_pos
              n, 'A'
              cursor_down
              int n
              limit_pos
              n, 'B'
              cursor_frwd
              int n
              limit_pos
              n, 'C'
              cursor_back
              int n
              limit_pos
              n, 'D'
          // Move cursor to the next/prev line
              cursor_line_next
              int n
              limit_pos
              n, 'E'
              cursor_line_prev
              int n
              limit_pos
              n, 'F'
          // Set cursor to specific column
              cursor_column
              int n
              limit_pos
              n, 'G'
          // Erase functions
              erase_display
              Motion m
              |
              (int)m, 'J'
              erase_line
              Motion m
              |
              (int)m, 'K'
          // Scroll up/down
              scroll_up
              int n
              limit_pos
              n, 'S'
              scroll_down
              int n
              limit_pos
              n, 'T'
          // Set cursor to a specific position
              cursor_position
              int n, int m
              limit_pos
              n, ';', m, 'H'
          // color
          // palet colors
              foreground
              Color color
              |
              (int)color + 30, 'm'
              background
              Color color
              |
              (int)color + 40, 'm'
          // 256-color palette
              foreground
              int idx
              limit_256
              38, ';', 5, ';', idx, 'm'
              background
              int idx
              limit_256
              48, ';', 5, ';', idx, 'm'
          // RGB colors
              foreground
              int R, int G, int B
              limit_256
              38, ';', 2, ';', R, ';', G, ';', B, 'm'
              background
              int R, int G, int B
              limit_256
              48, ';', 2, ';', R, ';', G, ';', B, 'm'
          // Set/reset text decorators
              decor_set
              Decor decor
              |
              (int)decor, 'm'
              decor_reset
              Decor decor
              |
              (int)decor + 20, 'm'
          // Save/restore cursor position;
              cursor_save
              |
              |
              's'
              cursor_restore
              |
              |
              'u'
          // Set screen modes
              screen_mode_set
              int n
              limit_pos
              '=', n, 'h'
              screen_mode_reset
              int n
              limit_pos
              '=', n, 'l'
          // Private screen modes supported by most terminals
          // Save/restore screen
              screen_save
              |
              |
              "?47h"
              screen_restore
              |
              |
              "?47l"
          // Show/hide cursor
              cursor_show
              |
              |
              "?25h"
              cursor_hide
              |
              |
              "?25l"
          // Enable/disable alternate buffer
              abuf_enable
              |
              |
              "?1049h"
              abuf_disable
              |
              |
              "?1049l"
          // Enable/disable bracketed paste mode
              paste_enable
              |
              |
              "?2004h"
              paste_disable
              |
              |
              "?2004l"
          %%
          // Keyboard string TODO
          } // namespace ALEC
          #endif
        
        diff --git a/ src/generator.c b/ src/generator.c
@@ -1,265 +0,0 @@
#include "generator.h"
          #include "parser.h"
          #define MALLOC(x)                                                                                            \
              do {                                                                                                     \
                  x = malloc(sizeof(*x));                                                                              \
                  if (!x) yyerror("out of space"), exit(1);                                                            \
              } while (0);
          #ifndef YYDEBUG
          int yydebug = 1;
          #endif
          list_t records = {0};
          list_t epilogue = {0};
          list_t prologue = {0};
          int main(const int argc, char *argv[]) {
              if (argc < 2) {
                  yyparse();
                  return 0;
              }
              for (int i = 1; i < argc; i++) {
                  FILE *f = fopen(argv[i], "r");
                  if (!f) {
                      perror(argv[1]);
                      return -1;
                  }
                  yyrestart(f);
                  yyparse();
                  fclose(f);
              }
              // print prologue section
              for (node_t *p = prologue.head; p; p = p->next) {
                  printf("%s", p->data);
              }
              list_t dupes = {0};
              record_dupes(&dupes, &records);
              printf("\n/* Template compile-time variables */\n\n");
              record_print_dupes(&dupes);
              for (node_t *p = records.head; p; p = p->next) {
                  const record_t *r = (const record_t *)p->data;
                  record_print_template(r, list_find(&dupes, r->name, scmp));
              }
              printf("\n/* Run-time functions */\n\n");
              for (node_t *p = records.head; p; p = p->next) {
                  const record_t *r = (const record_t *)p->data;
                  record_print_function(r);
              }
              // print epilogue section
              for (node_t *p = epilogue.head; p; p = p->next) {
                  printf("%s", p->data);
              }
              list_free(&dupes, 0);
              list_free(&prologue, free);
              list_free(&records, record_free);
              list_free(&epilogue, free);
              yylex_destroy();
          }
          node_t *node_new(char *data) {
              node_t *n;
              MALLOC(n);
              *n = (node_t){
                  .data = data,
                  .next = NULL,
              };
              return n;
          }
          list_t *list_new(char *data) {
              list_t *l;
              MALLOC(l);
              *l = (list_t){0};
              if (data) list_append(l, node_new(data));
              return l;
          }
          void list_free(list_t *l, void (*free_data)(void *)) {
              if (!l) return;
              node_t *c = l->head, *t;
              while (c) {
                  t = c;
                  c = c->next;
                  if (free_data) free_data((void *)t->data);
                  free(t);
              }
          }
          void list_append(list_t *l, node_t *n) {
              if (!l->head) l->head = l->tail = n;
              else
                  l->tail = l->tail->next = n;
          }
          record_t *record_new(char *name, list_t *args, list_t *rules, list_t *recipe) {
              record_t *rec;
              MALLOC(rec);
              *rec = (record_t){
                  .name = name,
                  .args = args,
                  .rules = rules,
                  .recipe = recipe,
              };
              return rec;
          }
          void record_free(void *rp) {
              record_t *r = (record_t *)rp;
              if (r->args) list_free(r->args, free), free(r->args);
              if (r->rules) list_free(r->rules, free), free(r->rules);
              if (r->recipe) list_free(r->recipe, free), free(r->recipe);
              free(r->name);
              free(r);
          }
          int list_find(list_t *l, void *data, cmp_f cmp) {
              node_t *c = l->head;
              while (c) {
                  if (!cmp(c->data, data)) return 1;
                  c = c->next;
              }
              return 0;
          }
          void record_print_dupes(const list_t *l) {
              node_t *c = l->head;
              while (c) {
                  printf("template <auto... val> static const char *%s_v;\n", c->data);
                  c = c->next;
              }
              printf("\n");
          }
          void record_print_function(const record_t *r) {
              list_t dummy = {0};
              if (!r->recipe) { // comment
                  printf("%s\n\n", r->name);
                  return;
              }
              printf("static constexpr auto %s(", r->name);
              if (r->args) {
                  for (node_t *p = r->args->head; p; p = p->next) {
                      list_append(&dummy, node_new(strchr(p->data, ' ') + 1));
                      printf("%s", p->data);
                      if (p->next) printf(", ");
                  }
              }
              printf(") {\n");
              if (r->rules) {
                  for (node_t *p = dummy.head; p; p = p->next) {
                      printf("\tassert(");
                      for (node_t *q = r->rules->head; q; q = q->next) {
                          printf("%s(%s)", q->data, p->data);
                          if (q->next) printf(" && ");
                      }
                      printf(");\n");
                  }
              }
              if (r->args) {
                  printf("\treturn details::helper::make(");
                  for (node_t *p = r->recipe->head; p; p = p->next) {
                      printf("%s", p->data);
                      if (p->next) printf(", ");
                  }
                  printf(")");
              } else {
                  printf("\treturn %s_v", r->name);
              }
              printf(";\n}\n\n");
              list_free(&dummy, NULL);
          }
          void record_print_template(const record_t *r, int dup) {
              list_t dummy = {0};
              if (!r->recipe) { // comment
                  printf("%s\n\n", r->name);
                  return;
              }
              if (r->args) {
                  printf("template <");
                  for (node_t *p = r->args->head; p; p = p->next) {
                      list_append(&dummy, node_new(strchr(p->data, ' ') + 1));
                      printf("%s", p->data);
                      if (p->next) printf(", ");
                  }
                  printf(">\n");
              }
              if (r->rules) {
                  printf("\trequires ");
                  for (node_t *p = dummy.head; p; p = p->next) {
                      for (node_t *q = r->rules->head; q; q = q->next) {
                          printf("%s_v<%s>", q->data, p->data);
                          if (q->next) printf(" && ");
                      }
                      if (p->next) printf(" && ");
                  }
                  printf("\n");
              }
              printf("static constexpr auto %s_v", r->name);
              if (dup) {
                  printf("<");
                  for (node_t *p = dummy.head; p; p = p->next) {
                      printf("%s", p->data);
                      if (p->next) printf(", ");
                  }
                  printf(">");
              }
              if (r->recipe->head && r->recipe->head->data[0] == '"') {
                  printf("\n\t = details::escape_literal<%s>;", r->recipe->head->data);
              } else {
                  printf("\n\t = details::escape<");
                  for (node_t *p = r->recipe->head; p; p = p->next) {
                      printf("%s", p->data);
                      if (p->next) printf(", ");
                  }
                  printf(">;");
              }
              printf("\n\n");
              list_free(&dummy, NULL);
          }
          int scmp(const void *a, const void *b) { return strcmp((const char *)a, (const char *)b); }
          void record_dupes(list_t *d, list_t *l) {
              list_t s = {0};
              for (node_t *p = records.head; p; p = p->next) {
                  const record_t *r = (const record_t *)p->data;
                  if (!list_find(&s, r->name, scmp)) list_append(&s, node_new(r->name));
                  else if (!list_find(d, r->name, scmp))
                      list_append(d, node_new(r->name));
              }
              list_free(&s, NULL);
          }
        
        diff --git a/ src/generator.h b/ src/generator.h
@@ -1,54 +0,0 @@
#ifndef ALEC_PARSER_H
          #define ALEC_PARSER_H
          #include <stdio.h>
          #include <stdlib.h>
          #include <string.h>
          extern int yylineno;
          void yyerror(char *s, ...);
          typedef struct node node_t;
          typedef struct list list_t;
          typedef struct record record_t;
          struct node {
              char *data;
              node_t *next;
          };
          struct list {
              node_t *head;
              node_t *tail;
          };
          struct record {
              char *name;
              list_t *args;
              list_t *rules;
              list_t *recipe;
          };
          typedef void (*free_f)(void *);
          typedef int (*cmp_f)(const void *, const void *);
          int scmp(const void *a, const void *b);
          node_t *node_new(char *data);
          list_t *list_new(char *data);
          void list_free(list_t *l, free_f free_data);
          void list_append(list_t *l, node_t *n);
          int list_find(list_t *l, void *data, cmp_f cmp);
          struct record *record_new(char *name, list_t *args, list_t *rules, list_t *recipe);
          void record_free(void *rp);
          void record_dupes(list_t *d, list_t *l);
          void record_print_template(const struct record *r, int dup);
          void record_print_function(const struct record *r);
          void record_print_dupes(const list_t *l);
          #endif
        
        diff --git a/ src/lexer.l b/ src/lexer.l
@@ -1,40 +0,0 @@
%option noyywrap nodefault yylineno
          %{
              #include "generator.h"
              #include "parser.h"
              #include <ctype.h>
          %}
          LINE_END (\n|\r|\r\n)
          %x GEN
          %x LAST
          %%
          "%%"{LINE_END}     { BEGIN GEN; return SWITCH; }
          .*{LINE_END}       { yylval.n = strdup(yytext); return PROLOGUE; }
          <GEN>{LINE_END}                  { return EOL; }
          <GEN>^[\t ]*{LINE_END}           { return EOL; }
          <GEN>^[\t ]*\|*[\t ]*{LINE_END}  { return EMPTY; }
          <GEN>^[\t ]*"//".* { yylval.n = strdup(yytext); return COMMENT; }
          <GEN>, { return COMMA; }
          <GEN>[^,\n]*                  {
              char *p = yytext + strlen(yytext) - 1;
              while(isspace(*p)) *p-- = '\0';
          	while(*yytext && isspace(*yytext)) yytext++;
          	yylval.n = strdup(yytext); return LITERAL;
          }
          <GEN>"%%"{LINE_END}        { BEGIN LAST; return SWITCH; }
          <LAST>.*{LINE_END}       { yylval.n = strdup(yytext); return EPILOGUE; }
          %%
          diff --git a/ src/parser.y b/ src/parser.y
@@ -1,72 +0,0 @@
%code requires {
              #include "generator.h"
              #include <stdarg.h>
          }
          %define api.value.type union
          %token <char *> n LITERAL COMMENT PROLOGUE EPILOGUE
          %type <record_t *> record
          %type <list_t *> list items
          %type <char *> name
          %token EOL COMMA SWITCH EMPTY
          %code provides {
          	int yylex(void);
          	int yyparse(void);
          	void yyrestart(FILE *);
          	int yylex_destroy(void);
          	extern list_t records;
          	extern list_t epilogue;
          	extern list_t prologue;
          }
          %destructor { free($$); } <char *>
          %start document
          %%
          document: prologue grammar epilogue
          prologue: %empty
                | prologue PROLOGUE { list_append(&prologue, node_new($2)); }
                ;
          epilogue: SWITCH
               | epilogue EPILOGUE { list_append(&epilogue, node_new($2)); }
               ;
          grammar: SWITCH
             | grammar EOL
             | grammar record { list_append(&records, node_new((char *)$2)); }
             ;
          record: name list list list { $$ = record_new($1, $2, $3, $4); }
                | COMMENT             { $$ = record_new($1, NULL, NULL, NULL); }
                ;
          name: LITERAL EOL   { $$ = $1; }
              ;
          list: EMPTY       { $$ = NULL; }
              | items EOL   { $$ = $1; }
              ;
          items: LITERAL             { $$ = list_new($1); }
               | items COMMA LITERAL { list_append($1, node_new($3)); $$ = $1; }
               ;
          %%
          void yyerror(char *s, ...) {
            va_list ap;
            va_start(ap, s);
            fprintf(stderr, "%d: error: ", yylineno);
            vfprintf(stderr, s, ap);
            fprintf(stderr, "\n");
          }