├── .codecov.yml ├── .cproject ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .gitmodules ├── .project ├── .settings ├── org.eclipse.cdt.codan.core.prefs ├── org.eclipse.cdt.core.prefs ├── org.eclipse.cdt.ui.prefs └── org.eclipse.ltk.core.refactoring.prefs ├── CMakeLists.txt ├── LICENSE ├── README.md ├── api └── ccs │ ├── ccs.h │ ├── context.h │ ├── domain.h │ ├── rule_builder.h │ └── types.h ├── misc ├── CCS.xml ├── README.md ├── ccs.vim └── grammar.txt ├── src ├── CMakeLists.txt ├── context.cpp ├── dag │ ├── dag_builder.h │ ├── key.cpp │ ├── key.h │ ├── node.h │ ├── property.cpp │ ├── property.h │ ├── specificity.h │ ├── tally.cpp │ └── tally.h ├── domain.cpp ├── graphviz.cpp ├── graphviz.h ├── parser │ ├── ast.cpp │ ├── ast.h │ ├── build_context.cpp │ ├── build_context.h │ ├── loader.h │ ├── parser.cpp │ └── parser.h ├── rule_builder.cpp ├── search_state.cpp └── search_state.h ├── test ├── CMakeLists.txt ├── acceptance_tests.cpp ├── ccs_test.cpp ├── context_test.cpp └── parser │ └── parser_test.cpp └── tests.txt /.codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "external/**/*" 3 | # seems like you shouldn't need both of these, but you do... 4 | - "test/*" 5 | - "test/**/*" 6 | -------------------------------------------------------------------------------- /.cproject: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build: 12 | name: ${{ matrix.name }} 13 | runs-on: ${{ matrix.os }} 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | include: 18 | - name: Linux GCC 10 Debug/Coverage 19 | os: ubuntu-latest 20 | env: BUILD_TYPE=Debug EXTRA_CMAKE=-DCOVERAGE=On GCOV=gcov-10 CC=gcc-10 CXX=g++-10 21 | - name: Linux GCC 10 Release 22 | os: ubuntu-latest 23 | env: BUILD_TYPE=Release LTO=On CC=gcc-10 CXX=g++-10 24 | - name: Linux GCC 9 Release 25 | os: ubuntu-latest 26 | env: BUILD_TYPE=Release LTO=On CC=gcc-9 CXX=g++-9 27 | - name: Linux Clang 14 Release 28 | os: ubuntu-latest 29 | env: BUILD_TYPE=Release LTO=On CC=clang-14 CXX=clang++-14 30 | - name: Linux Clang 13 Release 31 | os: ubuntu-latest 32 | env: BUILD_TYPE=Release LTO=On CC=clang-13 CXX=clang++-13 33 | - name: Linux Clang 12 Release 34 | os: ubuntu-latest 35 | env: BUILD_TYPE=Release LTO=On CC=clang-12 CXX=clang++-12 36 | - name: OSX GCC 11 Release 37 | os: macos-latest 38 | env: BUILD_TYPE=Release CC=gcc-11 CXX=g++-11 39 | - name: OSX Clang 12 Release 40 | os: macos-latest 41 | env: BUILD_TYPE=Release 42 | steps: 43 | - uses: actions/checkout@v2 44 | with: 45 | submodules: true 46 | - name: CMake Build 47 | run: | 48 | eval ${{ matrix.env }} 49 | export CC CXX 50 | mkdir out 51 | cd out 52 | cmake -Wno-dev -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${EXTRA_CMAKE} .. 53 | cmake --build . -- -j2 54 | ctest --output-on-failure -D ExperimentalBuild -j2 55 | ctest --output-on-failure -D ExperimentalTest -j2 56 | [[ -z "${GCOV}" ]] || find . -type f -name '*.gcno' -exec ${GCOV} -pabcfu '{}' + 57 | - name: Codecov Upload 58 | if: contains(matrix.name, 'Coverage') 59 | uses: codecov/codecov-action@v2 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /out 3 | /.vscode 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "external/googletest"] 2 | path = external/googletest 3 | url = https://github.com/google/googletest 4 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | ccs-cpp 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.cdt.managedbuilder.core.genmakebuilder 10 | 11 | 12 | ?name? 13 | 14 | 15 | 16 | org.eclipse.cdt.make.core.append_environment 17 | true 18 | 19 | 20 | org.eclipse.cdt.make.core.autoBuildTarget 21 | all 22 | 23 | 24 | org.eclipse.cdt.make.core.buildArguments 25 | -j 26 | 27 | 28 | org.eclipse.cdt.make.core.buildCommand 29 | make 30 | 31 | 32 | org.eclipse.cdt.make.core.cleanBuildTarget 33 | clean 34 | 35 | 36 | org.eclipse.cdt.make.core.contents 37 | org.eclipse.cdt.make.core.activeConfigSettings 38 | 39 | 40 | org.eclipse.cdt.make.core.enableAutoBuild 41 | true 42 | 43 | 44 | org.eclipse.cdt.make.core.enableCleanBuild 45 | true 46 | 47 | 48 | org.eclipse.cdt.make.core.enableFullBuild 49 | true 50 | 51 | 52 | org.eclipse.cdt.make.core.fullBuildTarget 53 | all 54 | 55 | 56 | org.eclipse.cdt.make.core.stopOnError 57 | true 58 | 59 | 60 | org.eclipse.cdt.make.core.useDefaultBuildCmd 61 | true 62 | 63 | 64 | 65 | 66 | org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder 67 | full,incremental, 68 | 69 | 70 | 71 | 72 | 73 | org.eclipse.cdt.core.cnature 74 | org.eclipse.cdt.core.ccnature 75 | org.eclipse.cdt.managedbuilder.core.managedBuildNature 76 | org.eclipse.cdt.managedbuilder.core.ScannerConfigNature 77 | 78 | 79 | 80 | 1331129850464 81 | 82 | 10 83 | 84 | org.eclipse.ui.ide.multiFilter 85 | 1.0-projectRelativePath-matches-false-false-dist 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /.settings/org.eclipse.cdt.codan.core.prefs: -------------------------------------------------------------------------------- 1 | #Fri Aug 26 13:14:01 CDT 2011 2 | eclipse.preferences.version=1 3 | org.eclipse.cdt.codan.checkers.errnoreturn=Warning 4 | org.eclipse.cdt.codan.checkers.errnoreturn.params={implicit\=>false} 5 | org.eclipse.cdt.codan.checkers.errreturnvalue=Error 6 | org.eclipse.cdt.codan.checkers.errreturnvalue.params={} 7 | org.eclipse.cdt.codan.checkers.noreturn=Error 8 | org.eclipse.cdt.codan.checkers.noreturn.params={implicit\=>false} 9 | org.eclipse.cdt.codan.internal.checkers.AbstractClassCreation=Error 10 | org.eclipse.cdt.codan.internal.checkers.AbstractClassCreation.params={launchModes\=>{RUN_ON_FULL_BUILD\=>false,RUN_ON_INC_BUILD\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} 11 | org.eclipse.cdt.codan.internal.checkers.AmbiguousProblem=Error 12 | org.eclipse.cdt.codan.internal.checkers.AmbiguousProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>false,RUN_ON_INC_BUILD\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} 13 | org.eclipse.cdt.codan.internal.checkers.AssignmentInConditionProblem=Warning 14 | org.eclipse.cdt.codan.internal.checkers.AssignmentInConditionProblem.params={} 15 | org.eclipse.cdt.codan.internal.checkers.AssignmentToItselfProblem=Error 16 | org.eclipse.cdt.codan.internal.checkers.AssignmentToItselfProblem.params={} 17 | org.eclipse.cdt.codan.internal.checkers.CaseBreakProblem=Warning 18 | org.eclipse.cdt.codan.internal.checkers.CaseBreakProblem.params={no_break_comment\=>"no break",last_case_param\=>true,empty_case_param\=>false} 19 | org.eclipse.cdt.codan.internal.checkers.CatchByReference=Warning 20 | org.eclipse.cdt.codan.internal.checkers.CatchByReference.params={unknown\=>false,exceptions\=>()} 21 | org.eclipse.cdt.codan.internal.checkers.CircularReferenceProblem=Error 22 | org.eclipse.cdt.codan.internal.checkers.CircularReferenceProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>false,RUN_ON_INC_BUILD\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} 23 | org.eclipse.cdt.codan.internal.checkers.FieldResolutionProblem=Error 24 | org.eclipse.cdt.codan.internal.checkers.FieldResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>false,RUN_ON_INC_BUILD\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} 25 | org.eclipse.cdt.codan.internal.checkers.FunctionResolutionProblem=Error 26 | org.eclipse.cdt.codan.internal.checkers.FunctionResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>false,RUN_ON_INC_BUILD\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} 27 | org.eclipse.cdt.codan.internal.checkers.InvalidArguments=Error 28 | org.eclipse.cdt.codan.internal.checkers.InvalidArguments.params={launchModes\=>{RUN_ON_FULL_BUILD\=>false,RUN_ON_INC_BUILD\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} 29 | org.eclipse.cdt.codan.internal.checkers.InvalidTemplateArgumentsProblem=Error 30 | org.eclipse.cdt.codan.internal.checkers.InvalidTemplateArgumentsProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>false,RUN_ON_INC_BUILD\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} 31 | org.eclipse.cdt.codan.internal.checkers.LabelStatementNotFoundProblem=Error 32 | org.eclipse.cdt.codan.internal.checkers.LabelStatementNotFoundProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>false,RUN_ON_INC_BUILD\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} 33 | org.eclipse.cdt.codan.internal.checkers.MemberDeclarationNotFoundProblem=Error 34 | org.eclipse.cdt.codan.internal.checkers.MemberDeclarationNotFoundProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>false,RUN_ON_INC_BUILD\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} 35 | org.eclipse.cdt.codan.internal.checkers.MethodResolutionProblem=Error 36 | org.eclipse.cdt.codan.internal.checkers.MethodResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>false,RUN_ON_INC_BUILD\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} 37 | org.eclipse.cdt.codan.internal.checkers.NamingConventionFunctionChecker=-Info 38 | org.eclipse.cdt.codan.internal.checkers.NamingConventionFunctionChecker.params={pattern\=>"^[a-z]",macro\=>true,exceptions\=>()} 39 | org.eclipse.cdt.codan.internal.checkers.NonVirtualDestructorProblem=Warning 40 | org.eclipse.cdt.codan.internal.checkers.NonVirtualDestructorProblem.params={} 41 | org.eclipse.cdt.codan.internal.checkers.OverloadProblem=Error 42 | org.eclipse.cdt.codan.internal.checkers.OverloadProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>false,RUN_ON_INC_BUILD\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} 43 | org.eclipse.cdt.codan.internal.checkers.RedeclarationProblem=Error 44 | org.eclipse.cdt.codan.internal.checkers.RedeclarationProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>false,RUN_ON_INC_BUILD\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} 45 | org.eclipse.cdt.codan.internal.checkers.RedefinitionProblem=Error 46 | org.eclipse.cdt.codan.internal.checkers.RedefinitionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>false,RUN_ON_INC_BUILD\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} 47 | org.eclipse.cdt.codan.internal.checkers.ReturnStyleProblem=-Warning 48 | org.eclipse.cdt.codan.internal.checkers.ReturnStyleProblem.params={} 49 | org.eclipse.cdt.codan.internal.checkers.ScanfFormatStringSecurityProblem=-Warning 50 | org.eclipse.cdt.codan.internal.checkers.ScanfFormatStringSecurityProblem.params={} 51 | org.eclipse.cdt.codan.internal.checkers.StatementHasNoEffectProblem=Warning 52 | org.eclipse.cdt.codan.internal.checkers.StatementHasNoEffectProblem.params={macro\=>true,exceptions\=>()} 53 | org.eclipse.cdt.codan.internal.checkers.SuggestedParenthesisProblem=Warning 54 | org.eclipse.cdt.codan.internal.checkers.SuggestedParenthesisProblem.params={paramNot\=>false} 55 | org.eclipse.cdt.codan.internal.checkers.SuspiciousSemicolonProblem=Warning 56 | org.eclipse.cdt.codan.internal.checkers.SuspiciousSemicolonProblem.params={else\=>false,afterelse\=>false} 57 | org.eclipse.cdt.codan.internal.checkers.TypeResolutionProblem=Error 58 | org.eclipse.cdt.codan.internal.checkers.TypeResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>false,RUN_ON_INC_BUILD\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} 59 | org.eclipse.cdt.codan.internal.checkers.UnusedFunctionDeclarationProblem=Warning 60 | org.eclipse.cdt.codan.internal.checkers.UnusedFunctionDeclarationProblem.params={} 61 | org.eclipse.cdt.codan.internal.checkers.UnusedStaticFunctionProblem=Warning 62 | org.eclipse.cdt.codan.internal.checkers.UnusedStaticFunctionProblem.params={} 63 | org.eclipse.cdt.codan.internal.checkers.UnusedVariableDeclarationProblem=Warning 64 | org.eclipse.cdt.codan.internal.checkers.UnusedVariableDeclarationProblem.params={exceptions\=>("@(\#)","$Id")} 65 | org.eclipse.cdt.codan.internal.checkers.VariableResolutionProblem=Error 66 | org.eclipse.cdt.codan.internal.checkers.VariableResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>false,RUN_ON_INC_BUILD\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} 67 | -------------------------------------------------------------------------------- /.settings/org.eclipse.cdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.cdt.core.formatter.alignment_for_arguments_in_method_invocation=16 3 | org.eclipse.cdt.core.formatter.alignment_for_assignment=16 4 | org.eclipse.cdt.core.formatter.alignment_for_base_clause_in_type_declaration=80 5 | org.eclipse.cdt.core.formatter.alignment_for_binary_expression=16 6 | org.eclipse.cdt.core.formatter.alignment_for_compact_if=16 7 | org.eclipse.cdt.core.formatter.alignment_for_conditional_expression=34 8 | org.eclipse.cdt.core.formatter.alignment_for_conditional_expression_chain=18 9 | org.eclipse.cdt.core.formatter.alignment_for_constructor_initializer_list=0 10 | org.eclipse.cdt.core.formatter.alignment_for_declarator_list=16 11 | org.eclipse.cdt.core.formatter.alignment_for_enumerator_list=48 12 | org.eclipse.cdt.core.formatter.alignment_for_expression_list=0 13 | org.eclipse.cdt.core.formatter.alignment_for_expressions_in_array_initializer=16 14 | org.eclipse.cdt.core.formatter.alignment_for_member_access=0 15 | org.eclipse.cdt.core.formatter.alignment_for_overloaded_left_shift_chain=16 16 | org.eclipse.cdt.core.formatter.alignment_for_parameters_in_method_declaration=16 17 | org.eclipse.cdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 18 | org.eclipse.cdt.core.formatter.brace_position_for_array_initializer=end_of_line 19 | org.eclipse.cdt.core.formatter.brace_position_for_block=end_of_line 20 | org.eclipse.cdt.core.formatter.brace_position_for_block_in_case=end_of_line 21 | org.eclipse.cdt.core.formatter.brace_position_for_method_declaration=end_of_line 22 | org.eclipse.cdt.core.formatter.brace_position_for_namespace_declaration=end_of_line 23 | org.eclipse.cdt.core.formatter.brace_position_for_switch=end_of_line 24 | org.eclipse.cdt.core.formatter.brace_position_for_type_declaration=end_of_line 25 | org.eclipse.cdt.core.formatter.comment.min_distance_between_code_and_line_comment=1 26 | org.eclipse.cdt.core.formatter.comment.never_indent_line_comments_on_first_column=true 27 | org.eclipse.cdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=true 28 | org.eclipse.cdt.core.formatter.compact_else_if=true 29 | org.eclipse.cdt.core.formatter.continuation_indentation=2 30 | org.eclipse.cdt.core.formatter.continuation_indentation_for_array_initializer=2 31 | org.eclipse.cdt.core.formatter.format_guardian_clause_on_one_line=false 32 | org.eclipse.cdt.core.formatter.indent_access_specifier_compare_to_type_header=false 33 | org.eclipse.cdt.core.formatter.indent_access_specifier_extra_spaces=0 34 | org.eclipse.cdt.core.formatter.indent_body_declarations_compare_to_access_specifier=true 35 | org.eclipse.cdt.core.formatter.indent_body_declarations_compare_to_namespace_header=false 36 | org.eclipse.cdt.core.formatter.indent_breaks_compare_to_cases=true 37 | org.eclipse.cdt.core.formatter.indent_declaration_compare_to_template_header=false 38 | org.eclipse.cdt.core.formatter.indent_empty_lines=false 39 | org.eclipse.cdt.core.formatter.indent_statements_compare_to_block=true 40 | org.eclipse.cdt.core.formatter.indent_statements_compare_to_body=true 41 | org.eclipse.cdt.core.formatter.indent_switchstatements_compare_to_cases=true 42 | org.eclipse.cdt.core.formatter.indent_switchstatements_compare_to_switch=false 43 | org.eclipse.cdt.core.formatter.indentation.size=2 44 | org.eclipse.cdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert 45 | org.eclipse.cdt.core.formatter.insert_new_line_after_template_declaration=do not insert 46 | org.eclipse.cdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert 47 | org.eclipse.cdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert 48 | org.eclipse.cdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert 49 | org.eclipse.cdt.core.formatter.insert_new_line_before_colon_in_constructor_initializer_list=do not insert 50 | org.eclipse.cdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert 51 | org.eclipse.cdt.core.formatter.insert_new_line_before_identifier_in_function_declaration=do not insert 52 | org.eclipse.cdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert 53 | org.eclipse.cdt.core.formatter.insert_new_line_in_empty_block=insert 54 | org.eclipse.cdt.core.formatter.insert_space_after_assignment_operator=insert 55 | org.eclipse.cdt.core.formatter.insert_space_after_binary_operator=insert 56 | org.eclipse.cdt.core.formatter.insert_space_after_closing_angle_bracket_in_template_arguments=insert 57 | org.eclipse.cdt.core.formatter.insert_space_after_closing_angle_bracket_in_template_parameters=insert 58 | org.eclipse.cdt.core.formatter.insert_space_after_closing_brace_in_block=insert 59 | org.eclipse.cdt.core.formatter.insert_space_after_closing_paren_in_cast=insert 60 | org.eclipse.cdt.core.formatter.insert_space_after_colon_in_base_clause=insert 61 | org.eclipse.cdt.core.formatter.insert_space_after_colon_in_case=insert 62 | org.eclipse.cdt.core.formatter.insert_space_after_colon_in_conditional=insert 63 | org.eclipse.cdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert 64 | org.eclipse.cdt.core.formatter.insert_space_after_comma_in_array_initializer=insert 65 | org.eclipse.cdt.core.formatter.insert_space_after_comma_in_base_types=insert 66 | org.eclipse.cdt.core.formatter.insert_space_after_comma_in_declarator_list=insert 67 | org.eclipse.cdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert 68 | org.eclipse.cdt.core.formatter.insert_space_after_comma_in_expression_list=insert 69 | org.eclipse.cdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert 70 | org.eclipse.cdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert 71 | org.eclipse.cdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert 72 | org.eclipse.cdt.core.formatter.insert_space_after_comma_in_template_arguments=insert 73 | org.eclipse.cdt.core.formatter.insert_space_after_comma_in_template_parameters=insert 74 | org.eclipse.cdt.core.formatter.insert_space_after_opening_angle_bracket_in_template_arguments=do not insert 75 | org.eclipse.cdt.core.formatter.insert_space_after_opening_angle_bracket_in_template_parameters=do not insert 76 | org.eclipse.cdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert 77 | org.eclipse.cdt.core.formatter.insert_space_after_opening_bracket=do not insert 78 | org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert 79 | org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert 80 | org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_exception_specification=do not insert 81 | org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert 82 | org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert 83 | org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert 84 | org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert 85 | org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert 86 | org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert 87 | org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert 88 | org.eclipse.cdt.core.formatter.insert_space_after_postfix_operator=do not insert 89 | org.eclipse.cdt.core.formatter.insert_space_after_prefix_operator=do not insert 90 | org.eclipse.cdt.core.formatter.insert_space_after_question_in_conditional=insert 91 | org.eclipse.cdt.core.formatter.insert_space_after_semicolon_in_for=insert 92 | org.eclipse.cdt.core.formatter.insert_space_after_unary_operator=do not insert 93 | org.eclipse.cdt.core.formatter.insert_space_before_assignment_operator=insert 94 | org.eclipse.cdt.core.formatter.insert_space_before_binary_operator=insert 95 | org.eclipse.cdt.core.formatter.insert_space_before_closing_angle_bracket_in_template_arguments=do not insert 96 | org.eclipse.cdt.core.formatter.insert_space_before_closing_angle_bracket_in_template_parameters=do not insert 97 | org.eclipse.cdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert 98 | org.eclipse.cdt.core.formatter.insert_space_before_closing_bracket=do not insert 99 | org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert 100 | org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert 101 | org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_exception_specification=do not insert 102 | org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert 103 | org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert 104 | org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert 105 | org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert 106 | org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert 107 | org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert 108 | org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert 109 | org.eclipse.cdt.core.formatter.insert_space_before_colon_in_base_clause=do not insert 110 | org.eclipse.cdt.core.formatter.insert_space_before_colon_in_case=do not insert 111 | org.eclipse.cdt.core.formatter.insert_space_before_colon_in_conditional=insert 112 | org.eclipse.cdt.core.formatter.insert_space_before_colon_in_default=do not insert 113 | org.eclipse.cdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert 114 | org.eclipse.cdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert 115 | org.eclipse.cdt.core.formatter.insert_space_before_comma_in_base_types=do not insert 116 | org.eclipse.cdt.core.formatter.insert_space_before_comma_in_declarator_list=do not insert 117 | org.eclipse.cdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert 118 | org.eclipse.cdt.core.formatter.insert_space_before_comma_in_expression_list=do not insert 119 | org.eclipse.cdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert 120 | org.eclipse.cdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert 121 | org.eclipse.cdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert 122 | org.eclipse.cdt.core.formatter.insert_space_before_comma_in_template_arguments=do not insert 123 | org.eclipse.cdt.core.formatter.insert_space_before_comma_in_template_parameters=do not insert 124 | org.eclipse.cdt.core.formatter.insert_space_before_opening_angle_bracket_in_template_arguments=do not insert 125 | org.eclipse.cdt.core.formatter.insert_space_before_opening_angle_bracket_in_template_parameters=do not insert 126 | org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert 127 | org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_block=insert 128 | org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert 129 | org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_namespace_declaration=insert 130 | org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_switch=insert 131 | org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert 132 | org.eclipse.cdt.core.formatter.insert_space_before_opening_bracket=do not insert 133 | org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_catch=insert 134 | org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_exception_specification=insert 135 | org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_for=insert 136 | org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_if=insert 137 | org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert 138 | org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert 139 | org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert 140 | org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_switch=insert 141 | org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_while=insert 142 | org.eclipse.cdt.core.formatter.insert_space_before_postfix_operator=do not insert 143 | org.eclipse.cdt.core.formatter.insert_space_before_prefix_operator=do not insert 144 | org.eclipse.cdt.core.formatter.insert_space_before_question_in_conditional=insert 145 | org.eclipse.cdt.core.formatter.insert_space_before_semicolon=do not insert 146 | org.eclipse.cdt.core.formatter.insert_space_before_semicolon_in_for=do not insert 147 | org.eclipse.cdt.core.formatter.insert_space_before_unary_operator=do not insert 148 | org.eclipse.cdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert 149 | org.eclipse.cdt.core.formatter.insert_space_between_empty_brackets=do not insert 150 | org.eclipse.cdt.core.formatter.insert_space_between_empty_parens_in_exception_specification=do not insert 151 | org.eclipse.cdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert 152 | org.eclipse.cdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert 153 | org.eclipse.cdt.core.formatter.join_wrapped_lines=true 154 | org.eclipse.cdt.core.formatter.keep_else_statement_on_same_line=false 155 | org.eclipse.cdt.core.formatter.keep_empty_array_initializer_on_one_line=false 156 | org.eclipse.cdt.core.formatter.keep_imple_if_on_one_line=false 157 | org.eclipse.cdt.core.formatter.keep_then_statement_on_same_line=false 158 | org.eclipse.cdt.core.formatter.lineSplit=80 159 | org.eclipse.cdt.core.formatter.number_of_empty_lines_to_preserve=1 160 | org.eclipse.cdt.core.formatter.put_empty_statement_on_new_line=true 161 | org.eclipse.cdt.core.formatter.tabulation.char=space 162 | org.eclipse.cdt.core.formatter.tabulation.size=2 163 | org.eclipse.cdt.core.formatter.use_tabs_only_for_leading_indentations=false 164 | -------------------------------------------------------------------------------- /.settings/org.eclipse.cdt.ui.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | formatter_profile=_two spaces 3 | formatter_settings_version=1 4 | -------------------------------------------------------------------------------- /.settings/org.eclipse.ltk.core.refactoring.prefs: -------------------------------------------------------------------------------- 1 | #Wed Mar 07 08:16:33 CST 2012 2 | eclipse.preferences.version=1 3 | org.eclipse.ltk.core.refactoring.enable.project.refactoring.history=false 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.9.1) 2 | 3 | project(Ccs VERSION 0.9.20 DESCRIPTION "CCS configuration library") 4 | 5 | option(COVERAGE "Build with code coverage enabled" OFF) 6 | 7 | message(STATUS "${PROJECT_NAME} ${PROJECT_VERSION}") 8 | message(STATUS "Coverage: ${COVERAGE}") 9 | 10 | 11 | set(CMAKE_CXX_STANDARD 14) 12 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 13 | set(CMAKE_CXX_EXTENSIONS OFF) 14 | 15 | # All the sanitizer flags in one place 16 | if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") 17 | set(SANITIZE_FLAGS "${SANITIZE_FLAGS} -fsanitize=address") 18 | set(SANITIZE_FLAGS "${SANITIZE_FLAGS} -fsanitize=undefined") 19 | else () 20 | set(SANITIZE_FLAGS "${SANITIZE_FLAGS} -static-libasan") 21 | set(SANITIZE_FLAGS "${SANITIZE_FLAGS} -static-libubsan") 22 | set(SANITIZE_FLAGS "${SANITIZE_FLAGS} -fsanitize=address") 23 | set(SANITIZE_FLAGS "${SANITIZE_FLAGS} -fsanitize=signed-integer-overflow") 24 | set(SANITIZE_FLAGS "${SANITIZE_FLAGS} -fsanitize=bounds-strict -fsanitize=undefined") 25 | endif () 26 | 27 | # Common flags shared across all builds 28 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Werror -g") 29 | if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") 30 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-mismatched-tags") 31 | endif() 32 | 33 | # Debug flags 34 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${SANITIZE_FLAGS}") 35 | 36 | include(CTest) 37 | include(GNUInstallDirs) 38 | 39 | # Enable libtooling support 40 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 41 | 42 | # External libraries/dependencies 43 | add_subdirectory(external/googletest EXCLUDE_FROM_ALL) 44 | 45 | include_directories(api src) 46 | link_directories(${PROJECT_BINARY_DIR}/lib) 47 | 48 | add_subdirectory(src) 49 | enable_testing() 50 | add_subdirectory(test) 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2011, 2018 Matt Hellige 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build status](https://github.com/hellige/ccs-cpp/actions/workflows/ci.yml/badge.svg)](https://github.com/hellige/ccs-cpp/actions?workflow=CI) 2 | [![Code coverage](https://codecov.io/gh/hellige/ccs-cpp/branch/master/graph/badge.svg)](https://codecov.io/gh/hellige/ccs-cpp) 3 | [![GitHub release](https://img.shields.io/github/v/release/hellige/ccs-cpp?include_prereleases&sort=semver)](https://github.com/hellige/ccs-cpp/releases) 4 | [![GitHub license](https://img.shields.io/github/license/hellige/ccs-cpp)](https://github.com/hellige/ccs-cpp/blob/master/LICENSE) 5 | 6 | 7 | CCS for C++ 8 | =========== 9 | 10 | This is the C++ implementation of [CCS][1]. 11 | 12 | CCS is a language for config files, and libraries to read those files and 13 | configure applications. The documentation is currently quite poor, but the 14 | [Java][1] and C++ implementations are mature and have been used in production 15 | for many years. 16 | 17 | There's a presentation about the language [here][2], but it's a little sketchy 18 | without someone talking along with it. 19 | 20 | [1]: http://github.com/hellige/ccs 21 | [2]: http://hellige.github.io/ccs 22 | 23 | 24 | Building 25 | -------- 26 | 27 | CCS is built with CMake. You can set the usual options to control your 28 | compiler, build type, etc., but the crash course is: 29 | 30 | $ git submodule update --init 31 | $ mkdir build 32 | $ cd build 33 | $ cmake .. 34 | $ make 35 | 36 | You can run unit tests with pretty output using: 37 | 38 | $ make unittest 39 | 40 | CMake can install everything for you, but in any case the client-facing 41 | headers are in the `api` directory. 42 | 43 | 44 | Syntax quick reference 45 | ---------------------- 46 | 47 | #### @context (a.b c, d) 48 | 49 | Sets "context" for the rest of the ruleset. Must appear prior to any other 50 | rules. Equivalent to: 51 | 52 | a.b c, d { /* rest of file */ } 53 | 54 | #### name = 123 55 | 56 | Property definition. Property _name_ will have the specified value in the 57 | current context. The syntax of values is conventional: _true_, _false_, 58 | 64-bit signed integers (may be specified in hex as _0x1a2bc_), doubles, 59 | and strings. 60 | 61 | #### 'name with spaces' = 'value' 62 | 63 | _name_ may be enclosed in quotes and written as a string literal if it 64 | contains spaces or other non-identifier characters. 65 | 66 | #### @override name = 123 67 | 68 | Overriding property definition. For a given property, any definition marked 69 | _@override_ will take precedence over any normal definition, regardless of 70 | the relative specificities of the two definitions. If more than one 71 | _@override_ definition applies in a particular context, the most specific wins. 72 | 73 | #### 'VAR: ${VAR}'
"literal: \${VAR}" 74 | 75 | Strings may be enclosed in single or double quotes. Environment variables may 76 | be interpolated with ```${VAR}```. Special characters may be escaped with a 77 | backslash. Recognized escape sequences include: ```\t \n \r \' \" \\ \$```. 78 | A string may be broken across multiple lines by ending a line with a single 79 | backslash. 80 | 81 | #### 'double: ", single: \''
"double: \", single: '" 82 | 83 | The non-delimiting quote character need not be escaped. 84 | 85 | #### @import "..."
a b.c d : @import "..." 86 | 87 | Import a ruleset into the current ruleset. The entire imported ruleset will be 88 | nested in the current context, so for example the second example above further 89 | constrains all imported settings to the context ```a b.c d```. 90 | 91 | #### @constrain a/b.c/d.e.f 92 | 93 | Further constrain the context. This is equivalent to further constraining the 94 | context using the ```context.constrain(...)``` API call. 95 | 96 | Of course, this is most useful when applied only in a particular selected 97 | context, in which case it allows for some additional reuse and 98 | modularity within CCS rulesets (activating additional sets of rules in 99 | particular cases, for example). 100 | 101 | #### _selector_ : @import "..."
_selector_ : @constrain _..._
_selector_ : _name_ = _value_
_selector_ { _rules_ } 102 | 103 | Constrain rules to apply only in the selected context. _rule_ is any of: 104 | an import, a property setting, a constraint, or a selector and further nested 105 | rules. Selector syntax is documented below... 106 | 107 | #### a.b.c 108 | 109 | Matches a constraint of type ```a``` with values ```b``` and ```c```. Note 110 | that this matches only a single constraint with _both_ values. Multiple 111 | constraints of the same type are allowed (thus preserving monotonicity), so 112 | ```a.b.c``` is not equivalent to ```a.b a.c``` in every case. 113 | 114 | ### a/c 115 | 116 | _Rarely needed._ 117 | 118 | Matches a simultaneous occurence of constraints of type ```a``` and type 119 | ```c```. Again, this is not generally equivalent to ```a c```, as it only 120 | matches when the two constraints are applied in the same single step. 121 | 122 | #### a.b c.d
a.b, c.d
a.b > c.d 123 | 124 | Conjunction, disjunction, and descendant selection, respectively. The first 125 | form matches in any context containing _both_ ```a.b``` and ```c.d```. 126 | The second matches in any context containing _either one_. 127 | 128 | The third form matches in any context containing ```c.d```, _itself_ in a 129 | context containing ```a.b```. This form is infrequently used in CCS (although it 130 | is of course the default operator in CSS). 131 | 132 | Precedence of operators is as follows, from highest to lowest: 133 | 134 | - ```>``` (descendant) 135 | - juxtaposition (conjunction) 136 | - ```,``` (disjunction) 137 | 138 | Parentheses may be used to enforce grouping. So, for example, ```a, c > d e``` 139 | is equivalent to ```a, ((c > d) e)```. Since ```>``` is infrequently used, 140 | the rules are generally simple and intuitive. 141 | 142 | 143 | Note that rules may be separated by semicolons for clarity, but this is always 144 | completely optional and will never affect the way the ruleset is parsed. 145 | 146 | 147 | TODO 148 | ---- 149 | 150 | * Aggregate values: lists, maps. 151 | * For aggregate values, allow modification of inherited value as well as 152 | replacement?? 153 | -------------------------------------------------------------------------------- /api/ccs/ccs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* Single all-in header, includes the entire CCS API. */ 4 | 5 | #include "ccs/context.h" 6 | #include "ccs/domain.h" 7 | #include "ccs/types.h" 8 | -------------------------------------------------------------------------------- /api/ccs/context.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "ccs/types.h" 11 | 12 | namespace ccs { 13 | 14 | class CcsTracer; 15 | class CcsProperty; 16 | class Key; 17 | class Node; 18 | class SearchState; 19 | 20 | class CcsContext { 21 | std::shared_ptr searchState; 22 | 23 | friend class CcsDomain; 24 | CcsContext(std::shared_ptr root); 25 | CcsContext(const CcsContext &parent, const Key &key); 26 | CcsContext(const CcsContext &parent, const std::string &name); 27 | CcsContext(const CcsContext &parent, const std::string &name, 28 | const std::vector &values); 29 | 30 | public: 31 | CcsContext(const CcsContext &) = default; 32 | CcsContext &operator=(const CcsContext &) = default; 33 | ~CcsContext() = default; 34 | 35 | class Builder; 36 | 37 | void logRuleDag(std::ostream &os) const; 38 | 39 | Builder builder() const; 40 | 41 | CcsContext constrain(const std::string &name) const 42 | { return CcsContext(*this, name); } 43 | 44 | CcsContext constrain(const std::string &name, 45 | const std::vector &values) const 46 | { return CcsContext(*this, name, values); } 47 | 48 | const CcsProperty &getProperty(const std::string &propertyName) const; 49 | 50 | const std::string &getString(const std::string &propertyName) const; 51 | const std::string &getString(const std::string &propertyName, 52 | const std::string &defaultVal) const; 53 | bool getInto(std::string &dest, const std::string &propertyName) const; 54 | 55 | int getInt(const std::string &propertyName) const; 56 | int getInt(const std::string &propertyName, int defaultVal) const; 57 | bool getInto(int &dest, const std::string &propertyName) const; 58 | 59 | double getDouble(const std::string &propertyName) const; 60 | double getDouble(const std::string &propertyName, double defaultVal) const; 61 | bool getInto(double &dest, const std::string &propertyName) const; 62 | 63 | bool getBool(const std::string &propertyName) const; 64 | bool getBool(const std::string &propertyName, bool defaultVal) const; 65 | bool getInto(bool &dest, const std::string &propertyName) const; 66 | 67 | // these depend on operator>> for T 68 | template 69 | T get(const std::string &propertyName) const; 70 | template 71 | T get(const std::string &propertyName, const T &defaultVal) const; 72 | template 73 | bool getInto(T &dest, const std::string &propertyName) const; 74 | 75 | friend std::ostream &operator<<(std::ostream &, const CcsContext); 76 | 77 | template 78 | static bool coerceString(const std::string &s, T &dest); 79 | 80 | private: 81 | static bool checkEmpty(std::istream &stream); 82 | }; 83 | 84 | 85 | struct no_such_property : public virtual std::exception { 86 | std::string msg; 87 | CcsContext context; 88 | no_such_property(const std::string &name, CcsContext context); 89 | virtual ~no_such_property() throw() {} 90 | virtual const char *what() const throw() 91 | { return msg.c_str(); } 92 | }; 93 | 94 | struct bad_coercion : public virtual std::exception { 95 | std::string msg; 96 | bad_coercion(const std::string &name, const std::string &value) : 97 | msg(std::string("property cannot be coerced to requested type: ") + name 98 | + " with value " + value) {} 99 | virtual ~bad_coercion() throw() {} 100 | virtual const char *what() const throw() 101 | { return msg.c_str(); } 102 | }; 103 | 104 | 105 | template 106 | bool CcsContext::coerceString(const std::string &s, T &dest) { 107 | std::istringstream ist(s); 108 | ist >> dest; 109 | return checkEmpty(ist); 110 | } 111 | 112 | template<> 113 | inline bool CcsContext::coerceString(const std::string &s, 114 | std::string &dest) { 115 | dest = s; 116 | return true; 117 | } 118 | 119 | template<> 120 | inline bool CcsContext::coerceString(const std::string &s, 121 | bool &dest) { 122 | // convert from a string to a bool: value must be exactly "true" or "false", 123 | // for maximum consistency with ccs boolean literals. 124 | if (s == std::string("true")) { 125 | dest = true; 126 | return true; 127 | } 128 | if (s == std::string("false")) { 129 | dest = false; 130 | return true; 131 | } 132 | return false; 133 | } 134 | 135 | template 136 | T CcsContext::get(const std::string &propertyName) const { 137 | auto &val = getString(propertyName); 138 | T t; 139 | if (!coerceString(val, t)) throw bad_coercion(propertyName, val); 140 | return t; 141 | } 142 | 143 | template 144 | T CcsContext::get(const std::string &propertyName, const T &defaultVal) const { 145 | std::string str; 146 | if (!getInto(str, propertyName)) return defaultVal; 147 | T t; 148 | if (!coerceString(str, t)) return defaultVal; 149 | return t; 150 | } 151 | 152 | template 153 | bool CcsContext::getInto(T &dest, const std::string &propertyName) const { 154 | std::string str; 155 | if (!getInto(str, propertyName)) return false; 156 | return coerceString(str, dest); 157 | } 158 | 159 | 160 | class CcsContext::Builder { 161 | class Impl; 162 | std::unique_ptr impl; 163 | 164 | public: 165 | explicit Builder(const CcsContext &context); 166 | Builder(const Builder &); 167 | Builder &operator=(const Builder &); 168 | ~Builder(); 169 | 170 | CcsContext build() const; 171 | 172 | Builder &add(const std::string &name) 173 | { return add(name, {}); } 174 | Builder &add(const std::string &name, 175 | const std::vector &values); 176 | }; 177 | 178 | } 179 | -------------------------------------------------------------------------------- /api/ccs/domain.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "ccs/context.h" 9 | #include "ccs/rule_builder.h" 10 | 11 | namespace ccs { 12 | 13 | class DagBuilder; 14 | 15 | class CcsLogger { 16 | public: 17 | virtual ~CcsLogger() {} 18 | virtual void info(const std::string &msg) = 0; 19 | virtual void warn(const std::string &msg) = 0; 20 | virtual void error(const std::string &msg) = 0; 21 | 22 | static std::shared_ptr makeStdErrLogger(); 23 | }; 24 | 25 | class CcsTracer { 26 | public: 27 | virtual ~CcsTracer() {} 28 | virtual void onPropertyFound( 29 | const CcsContext &ccsContext, 30 | const std::string &propertyName, 31 | const CcsProperty &prop) = 0; 32 | virtual void onPropertyNotFound( 33 | const CcsContext &ccsContext, 34 | const std::string &propertyName) = 0; 35 | virtual void onConflict( 36 | const CcsContext &ccsContext, 37 | const std::string &propertyName, 38 | const std::vector values) = 0; 39 | virtual void onParseError(const std::string &msg) = 0; 40 | 41 | static std::shared_ptr makeLoggingTracer( 42 | std::shared_ptr logger, bool logAccesses = false); 43 | }; 44 | 45 | class ImportResolver { 46 | public: 47 | static ImportResolver &None; 48 | virtual ~ImportResolver() {} 49 | virtual bool resolve(const std::string &location, 50 | std::function load) = 0; 51 | }; 52 | 53 | class CcsDomain { 54 | std::unique_ptr dag; 55 | 56 | public: 57 | explicit CcsDomain(bool logAccesses = false); 58 | explicit CcsDomain(std::shared_ptr log, bool logAccesses = false); 59 | explicit CcsDomain(std::shared_ptr tracer); 60 | ~CcsDomain(); 61 | CcsDomain(const CcsDomain &) = delete; 62 | CcsDomain &operator=(const CcsDomain &) = delete; 63 | 64 | CcsDomain &loadCcsStream(std::istream &stream, const std::string &fileName, 65 | ImportResolver &importResolver); 66 | RuleBuilder ruleBuilder(); 67 | 68 | void logRuleDag(std::ostream &os) const; 69 | 70 | CcsContext build(); 71 | }; 72 | 73 | } 74 | -------------------------------------------------------------------------------- /api/ccs/rule_builder.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace ccs { 8 | 9 | class DagBuilder; 10 | 11 | class RuleBuilder { 12 | friend class CcsDomain; 13 | class Impl; 14 | class Root; 15 | class Child; 16 | std::shared_ptr impl; 17 | 18 | RuleBuilder(DagBuilder &dag); 19 | RuleBuilder(const std::shared_ptr &impl) : impl(impl) {} 20 | 21 | public: 22 | RuleBuilder pop(); 23 | RuleBuilder set(const std::string &name, const std::string &value); 24 | 25 | RuleBuilder select(const std::string &name); 26 | RuleBuilder select(const std::string &name, 27 | const std::vector &values); 28 | }; 29 | 30 | } 31 | -------------------------------------------------------------------------------- /api/ccs/types.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace ccs { 7 | 8 | struct Origin { 9 | std::string fileName; 10 | unsigned line; 11 | 12 | Origin() : fileName(""), line(0) {} 13 | Origin(const std::string &fileName, unsigned line) : 14 | fileName(fileName), line(line) {} 15 | }; 16 | 17 | static inline std::ostream &operator<<(std::ostream &str, const Origin &origin) { 18 | str << origin.fileName << ':' << origin.line; 19 | return str; 20 | } 21 | 22 | struct CcsProperty { 23 | virtual ~CcsProperty() {} 24 | virtual bool exists() const = 0; 25 | virtual Origin origin() const = 0; 26 | virtual const std::string &strValue() const = 0; 27 | virtual int intValue() const = 0; 28 | virtual double doubleValue() const = 0; 29 | virtual bool boolValue() const = 0; 30 | }; 31 | 32 | } 33 | -------------------------------------------------------------------------------- /misc/CCS.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /misc/README.md: -------------------------------------------------------------------------------- 1 | ## Misc files 2 | 3 | Syntax highlighting for vim is in `ccs.vim`. 4 | Syntax highlighting for JetBrains products (e.g. CLion) is in CCS.xml - copy to the `filetypes` subdirectory of your 5 | `~/.CLion/config`. 6 | -------------------------------------------------------------------------------- /misc/ccs.vim: -------------------------------------------------------------------------------- 1 | " Vim syntax file 2 | " Language: CCS 3 | " Maintainer: Matt Hellige , Matt Godbolt 4 | " Last change: 2015 February 9th 5 | 6 | if exists("b:current_syntax") 7 | finish 8 | endif 9 | 10 | syn clear 11 | syn case match 12 | 13 | syn match ccsError '@\w*' 14 | syn match ccsKeywords '@import\>' skipwhite nextgroup=ccsString 15 | syn match ccsKeywords '@constrain\>' skipwhite 16 | syn match ccsKeywords '@context\>' skipwhite nextgroup=ccsParens 17 | syn match ccsModifiers '@override\>' skipwhite 18 | " needs to be high up as it is spectacularly oversensitive 19 | syn match ccsConstraint '\<\w\+\>' 20 | 21 | syn region ccsBlock start="{" end="}" fold transparent 22 | syn match ccsComment "//.*$" 23 | syn region ccsComment start="/\*" end="\*/" contains=ccsComment 24 | syn region ccsParens start="(" end=")" transparent 25 | 26 | syn region ccsString start='"' end='"' contained contains=ccsInterpolant,ccsEscape 27 | syn region ccsString start='\'' end='\'' contained contains=ccsInterpolant,ccsEscape 28 | syn region ccsInterpolant start='${' end='}' contained 29 | syn match ccsEscape '\\[tnr'"\$]' contained 30 | 31 | syn region ccsConstraintString start='"' end='"' 32 | syn region ccsConstraintString start='\'' end='\'' 33 | 34 | " Integer with - + or nothing in front 35 | syn match ccsNumber '\d\+' contained 36 | syn match ccsNumber '[-+]\d\+' contained 37 | 38 | " Floating point number with decimal no E or e (+,-) 39 | syn match ccsNumber '\d\+\.\d*' contained 40 | syn match ccsNumber '[-+]\d\+\.\d*' contained 41 | 42 | " Floating point like number with E and no decimal point (+,-) 43 | syn match ccsNumber '[-+]\=\d[[:digit:]]*[eE][\-+]\=\d\+' contained 44 | syn match ccsNumber '\d[[:digit:]]*[eE][\-+]\=\d\+' contained 45 | 46 | " Floating point like number with E and decimal point (+,-) 47 | syn match ccsNumber '[-+]\=\d[[:digit:]]*\.\d*[eE][\-+]\=\d\+' contained 48 | syn match ccsNumber '\d[[:digit:]]*\.\d*[eE][\-+]\=\d\+' contained 49 | 50 | syn keyword ccsBoolean true false skipwhite contained 51 | 52 | syn match ccsIdentifier '\<\w\+\>\(\s*=\)\@=' 53 | syn match ccsDefId '\<\w\+\>' contained 54 | 55 | syn match ccsOperator '[.,>:|{}*()]' 56 | 57 | syn match ccsOperator '=' skipwhite nextgroup=ccsNumber,ccsBoolean,ccsString,ccsDefId 58 | 59 | let b:current_syntax = "ccs" 60 | hi def link ccsError Error 61 | hi def link ccsKeywords Statement 62 | hi def link ccsModifiers Type 63 | hi def link ccsConstraint Type 64 | hi def link ccsConstraintString Type 65 | hi def link ccsComment Comment 66 | hi def link ccsString Constant 67 | hi def link ccsNumber Constant 68 | hi def link ccsBoolean Constant 69 | hi def link ccsDefId Constant 70 | hi def link ccsIdentifier Identifier 71 | hi def link ccsOperator Operator 72 | hi def link ccsInterpolant PreProc 73 | -------------------------------------------------------------------------------- /misc/grammar.txt: -------------------------------------------------------------------------------- 1 | terminals are in CAPS or 'single-quotes' below. 2 | 3 | ruleset ::= ('@context' '(' selector ')') ';'?)? rules 4 | | rules 5 | rules ::= rule rules 6 | 7 | // when parsing rule, the ambiguity is between ident as start of prule and 8 | // ident as start of selector... '=' can be used to disambiguate. 9 | rule ::= prule ';'? 10 | | selector ':' prule ';'? 11 | | selector '{' rules '}' 12 | prule ::= '@import' STRING 13 | | '@constrain' singlestep 14 | | '@override'? ident '=' val 15 | 16 | // stratified precedence... 17 | selector ::= sum '>'? 18 | sum ::= product (',' product)* 19 | // term* starts with ident or '(', which is enough to disambiguate... 20 | product ::= term term* 21 | // here we have to distinguish another step from a trailing '>'. again, 22 | // peeking for ident or '(' does the trick. 23 | term ::= step ('>' step)* 24 | step ::= singlestep 25 | | '(' sum ')' 26 | singlestep ::= ident ('.' ident)* ('/' singlestep)? 27 | 28 | ident ::= ID | STRING 29 | val ::= INT | DOUBLE | BOOL | STRING 30 | 31 | 32 | // without precedence... 33 | selector ::= step '>'? 34 | step ::= step op step 35 | | singlestep step 36 | | singlestep 37 | | '(' step ')' 38 | op ::= ',' | '>' 39 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | option(LTO "Use link-time optimization" OFF) 2 | 3 | if (LTO) 4 | message("Enabling link-time optimization") 5 | include(CheckIPOSupported) 6 | check_ipo_supported() 7 | set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) 8 | endif () 9 | 10 | if (COVERAGE) 11 | add_compile_options(--coverage -O0) 12 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage") 13 | endif () 14 | 15 | set(CCS_SOURCE_FILES 16 | context.cpp 17 | dag/key.cpp 18 | dag/property.cpp 19 | dag/tally.cpp 20 | domain.cpp 21 | graphviz.cpp 22 | parser/ast.cpp 23 | parser/build_context.cpp 24 | parser/parser.cpp 25 | rule_builder.cpp 26 | search_state.cpp) 27 | 28 | add_library(ccs_obj OBJECT ${CCS_SOURCE_FILES}) 29 | target_include_directories(ccs_obj PRIVATE .) 30 | target_include_directories(ccs_obj PUBLIC 31 | $ 32 | $ 33 | ) 34 | set_property(TARGET ccs_obj PROPERTY POSITION_INDEPENDENT_CODE TRUE) 35 | 36 | add_library(ccs STATIC $) 37 | add_library(Ccs::ccs ALIAS ccs) 38 | target_include_directories(ccs PUBLIC 39 | $ 40 | $ 41 | ) 42 | 43 | add_library(ccs_so SHARED $) 44 | add_library(Ccs::ccs_so ALIAS ccs_so) 45 | target_include_directories(ccs_so PUBLIC 46 | $ 47 | $ 48 | ) 49 | set_target_properties(ccs_so PROPERTIES OUTPUT_NAME ccs VERSION ${PROJECT_VERSION}) 50 | 51 | install(TARGETS ccs ccs_so EXPORT ${PROJECT_NAME}Config 52 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 53 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 54 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 55 | ) 56 | 57 | install(DIRECTORY ../api/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) 58 | 59 | include(CMakePackageConfigHelpers) 60 | write_basic_package_version_file( 61 | ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake 62 | VERSION ${PROJECT_VERSION} 63 | COMPATIBILITY SameMajorVersion 64 | ) 65 | 66 | install(FILES 67 | ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake 68 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}/ 69 | ) 70 | 71 | export( 72 | TARGETS ccs ccs_so 73 | NAMESPACE ${PROJECT_NAME}:: 74 | FILE ${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake 75 | ) 76 | 77 | install( 78 | EXPORT ${PROJECT_NAME}Config 79 | FILE ${PROJECT_NAME}Config.cmake 80 | NAMESPACE ${PROJECT_NAME}:: 81 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}/ 82 | ) 83 | -------------------------------------------------------------------------------- /src/context.cpp: -------------------------------------------------------------------------------- 1 | #include "ccs/context.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "search_state.h" 9 | #include "ccs/types.h" 10 | 11 | namespace ccs { 12 | 13 | struct MissingProp : public CcsProperty { 14 | virtual bool exists() const { return false; } 15 | virtual Origin origin() const 16 | { throw std::runtime_error("called origin() on MissingProp"); } 17 | virtual const std::string &strValue() const 18 | { throw std::runtime_error("called strValue() on MissingProp"); } 19 | virtual int intValue() const 20 | { throw std::runtime_error("called intValue() on MissingProp"); } 21 | virtual double doubleValue() const 22 | { throw std::runtime_error("called doubleValue() on MissingProp"); } 23 | virtual bool boolValue() const 24 | { throw std::runtime_error("called boolValue() on MissingProp"); } 25 | }; 26 | 27 | namespace { MissingProp Missing; } 28 | 29 | CcsContext::CcsContext(std::shared_ptr root) 30 | : searchState(new SearchState(root)) {} 31 | 32 | CcsContext::CcsContext(const CcsContext &parent, const Key &key) 33 | : searchState(SearchState::newChild(parent.searchState, key)) {} 34 | 35 | CcsContext::CcsContext(const CcsContext &parent, const std::string &name) 36 | : searchState(SearchState::newChild(parent.searchState, Key(name, {}))) {} 37 | 38 | CcsContext::CcsContext(const CcsContext &parent, const std::string &name, 39 | const std::vector &values) 40 | : searchState(SearchState::newChild(parent.searchState, Key(name, values))) {} 41 | 42 | void CcsContext::logRuleDag(std::ostream &os) const { 43 | searchState->logRuleDag(os); 44 | } 45 | 46 | CcsContext::Builder CcsContext::builder() const { return Builder(*this); } 47 | 48 | const CcsProperty &CcsContext::getProperty(const std::string &propertyName) 49 | const { 50 | const CcsProperty *prop = searchState->findProperty(*this, propertyName); 51 | if (prop) return *prop; 52 | return Missing; 53 | } 54 | 55 | const std::string &CcsContext::getString(const std::string &propertyName) 56 | const { 57 | const CcsProperty &prop(getProperty(propertyName)); 58 | if (!prop.exists()) throw no_such_property(propertyName, *this); 59 | return prop.strValue(); 60 | } 61 | 62 | const std::string &CcsContext::getString(const std::string &propertyName, 63 | const std::string &defaultVal) const { 64 | const CcsProperty *prop(searchState->findProperty(*this, propertyName)); 65 | if (!prop) return defaultVal; 66 | return prop->strValue(); 67 | } 68 | 69 | bool CcsContext::getInto(std::string &dest, const std::string &propertyName) 70 | const { 71 | const CcsProperty &prop(getProperty(propertyName)); 72 | if (!prop.exists()) return false; 73 | dest = prop.strValue(); 74 | return true; 75 | } 76 | 77 | int CcsContext::getInt(const std::string &propertyName) const { 78 | const CcsProperty &prop(getProperty(propertyName)); 79 | if (!prop.exists()) throw no_such_property(propertyName, *this); 80 | return prop.intValue(); 81 | } 82 | 83 | int CcsContext::getInt(const std::string &propertyName, int defaultVal) const { 84 | const CcsProperty *prop(searchState->findProperty(*this, propertyName)); 85 | if (!prop) return defaultVal; 86 | return prop->intValue(); 87 | } 88 | 89 | bool CcsContext::getInto(int &dest, const std::string &propertyName) 90 | const { 91 | const CcsProperty &prop(getProperty(propertyName)); 92 | if (!prop.exists()) return false; 93 | dest = prop.intValue(); 94 | return true; 95 | } 96 | 97 | double CcsContext::getDouble(const std::string &propertyName) const { 98 | const CcsProperty &prop(getProperty(propertyName)); 99 | if (!prop.exists()) throw no_such_property(propertyName, *this); 100 | return prop.doubleValue(); 101 | } 102 | 103 | double CcsContext::getDouble(const std::string &propertyName, 104 | double defaultVal) const { 105 | const CcsProperty *prop(searchState->findProperty(*this, propertyName)); 106 | if (!prop) return defaultVal; 107 | return prop->doubleValue(); 108 | } 109 | 110 | bool CcsContext::getInto(double &dest, const std::string &propertyName) 111 | const { 112 | const CcsProperty &prop(getProperty(propertyName)); 113 | if (!prop.exists()) return false; 114 | dest = prop.doubleValue(); 115 | return true; 116 | } 117 | 118 | bool CcsContext::getBool(const std::string &propertyName) const { 119 | const CcsProperty &prop(getProperty(propertyName)); 120 | if (!prop.exists()) throw no_such_property(propertyName, *this); 121 | return prop.boolValue(); 122 | } 123 | 124 | bool CcsContext::getBool(const std::string &propertyName, bool defaultVal) 125 | const { 126 | const CcsProperty *prop(searchState->findProperty(*this, propertyName)); 127 | if (!prop) return defaultVal; 128 | return prop->boolValue(); 129 | } 130 | 131 | bool CcsContext::getInto(bool &dest, const std::string &propertyName) 132 | const { 133 | const CcsProperty &prop(getProperty(propertyName)); 134 | if (!prop.exists()) return false; 135 | dest = prop.boolValue(); 136 | return true; 137 | } 138 | 139 | std::ostream &operator<<(std::ostream &str, const CcsContext ctx) { 140 | return str << *ctx.searchState; 141 | } 142 | 143 | bool CcsContext::checkEmpty(std::istream &stream) { 144 | if (stream.fail()) return false; 145 | stream.peek(); 146 | if (!stream.eof()) return false; 147 | return true; 148 | } 149 | 150 | struct CcsContext::Builder::Impl { 151 | CcsContext context; 152 | Key key; 153 | Impl(const CcsContext &context) : context(context) {} 154 | }; 155 | 156 | CcsContext::Builder::Builder(const CcsContext &context) : 157 | impl(new Impl(context)) {} 158 | CcsContext::Builder::Builder(const Builder &that) : 159 | impl(new Impl(*that.impl)) {} 160 | CcsContext::Builder &CcsContext::Builder::operator=( 161 | const CcsContext::Builder &that) { 162 | impl.reset(new Impl(*that.impl)); 163 | return *this; 164 | } 165 | 166 | CcsContext::Builder::~Builder() {} 167 | 168 | CcsContext CcsContext::Builder::build() const 169 | { return CcsContext(impl->context, impl->key); } 170 | 171 | CcsContext::Builder &CcsContext::Builder::add(const std::string &name, 172 | const std::vector &values) { 173 | impl->key.addName(name); 174 | for (auto it = values.cbegin(); it != values.cend(); ++it) 175 | impl->key.addValue(name, *it); 176 | return *this; 177 | } 178 | 179 | no_such_property::no_such_property(const std::string &name, CcsContext context) 180 | : context(context) { 181 | std::ostringstream str; 182 | str << "no such property: '" << name << "' (in context: " << context << ")"; 183 | msg = str.str(); 184 | } 185 | 186 | } 187 | -------------------------------------------------------------------------------- /src/dag/dag_builder.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "dag/node.h" 6 | #include "parser/build_context.h" 7 | 8 | namespace ccs { 9 | 10 | class DagBuilder { 11 | int nextProperty_; 12 | std::shared_ptr root_; 13 | std::shared_ptr buildContext_; 14 | 15 | public: 16 | DagBuilder(std::shared_ptr tracer) : 17 | nextProperty_(0), 18 | root_(new Node(std::move(tracer))), 19 | buildContext_(BuildContext::descendant(*this, *root_)) {} 20 | 21 | std::shared_ptr root() { return root_; } 22 | BuildContext::P buildContext() { return buildContext_; } 23 | int nextProperty() { return nextProperty_++; } 24 | }; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/dag/key.cpp: -------------------------------------------------------------------------------- 1 | #include "dag/key.h" 2 | 3 | namespace ccs { 4 | 5 | std::ostream &operator<<(std::ostream &out, const Key &key) { 6 | bool first = true; 7 | for (auto it = key.values_.cbegin(); it != key.values_.cend(); ++it) { 8 | if (!first) out << '/'; 9 | out << it->first; 10 | for (auto it2 = it->second.cbegin(); it2 != it->second.cend(); ++it2) { 11 | // MMH the java version checks to see if *it2 is actually an ident, and 12 | // if not, quotes/escapes it. a nice touch, but maybe more trouble than 13 | // it's worth. 14 | //if (identRegex.matcher(*it2).matches()) 15 | out << '.' << *it2; 16 | //else 17 | // out << ".'" << v.replace("'", "\\'") << '\''; 18 | } 19 | first = false; 20 | } 21 | return out; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/dag/key.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "dag/specificity.h" 11 | 12 | namespace ccs { 13 | 14 | class Key { 15 | std::map> values_; 16 | Specificity specificity_; 17 | 18 | public: 19 | Key() {} 20 | 21 | Key(const std::string &name, const std::vector &values) { 22 | addName(name); 23 | for (auto it = values.begin(); it != values.end(); ++it) 24 | addValue(name, *it); 25 | } 26 | 27 | Key(const Key &) = default; 28 | Key &operator=(const Key &) = default; 29 | ~Key() = default; 30 | 31 | const Specificity &specificity() const { return specificity_; } 32 | 33 | bool empty() const { return values_.empty(); } 34 | 35 | bool operator<(const Key &that) const 36 | { return values_ < that.values_; } 37 | 38 | bool addName(const std::string &name) { 39 | if (values_.find(name) == values_.end()) { 40 | values_[name]; 41 | specificity_.names++; 42 | return true; 43 | } 44 | return false; 45 | } 46 | 47 | bool addValue(const std::string &name, const std::string &value) { 48 | bool changed = false; 49 | if (values_.find(name) == values_.end()) { 50 | changed = true; 51 | specificity_.names++; 52 | } 53 | if (values_[name].insert(value).second) { 54 | changed = true; 55 | specificity_.values++; 56 | } 57 | return changed; 58 | } 59 | 60 | bool addAll(const Key &key) { 61 | bool changed = false; 62 | for (auto it = key.values_.cbegin(); it != key.values_.cend(); ++it) { 63 | changed |= addName(it->first); 64 | for (auto it2 = it->second.cbegin(); it2 != it->second.cend(); ++it2) 65 | changed |= addValue(it->first, *it2); 66 | } 67 | return changed; 68 | } 69 | 70 | /* 71 | * treating this as a pattern, see whether it matches the given specific 72 | * key. this is asymmetric because the given key can have unmatched (extra) 73 | * names/values, but the current object must fully match the key. wildcards 74 | * also match on the current object, but not on the given key. 75 | * returns true if this object, as a pattern, matches the given key. 76 | */ 77 | bool matches(Key k) const { 78 | for (auto it = values_.cbegin(); it != values_.cend(); ++it) { 79 | auto valSet = k.values_.find(it->first); 80 | if (valSet == k.values_.cend()) return false; 81 | if (!std::includes(valSet->second.cbegin(), valSet->second.cend(), 82 | it->second.cbegin(), it->second.cend())) 83 | return false; 84 | } 85 | return true; 86 | } 87 | 88 | friend std::ostream &operator<<(std::ostream &, const Key &); 89 | }; 90 | 91 | std::ostream &operator<<(std::ostream &out, const Key &key); 92 | 93 | } 94 | -------------------------------------------------------------------------------- /src/dag/node.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "search_state.h" 9 | #include "ccs/types.h" 10 | #include "dag/key.h" 11 | #include "dag/property.h" 12 | #include "dag/tally.h" 13 | 14 | namespace ccs { 15 | 16 | class Dumper; 17 | 18 | template 19 | struct identity { typedef T type; }; 20 | 21 | class Node { 22 | friend class Dumper; 23 | std::shared_ptr tracer_; // to pin tracer, only non-null in root 24 | std::map> children; 25 | std::multimap props; 26 | std::set> andTallies_; 27 | std::set> orTallies_; 28 | Key constraints; 29 | 30 | public: 31 | Node() {} 32 | Node(std::shared_ptr tracer) : tracer_(std::move(tracer)) {} 33 | Node(const Node &) = delete; 34 | Node &operator=(const Node &) = delete; 35 | 36 | CcsTracer &tracer() const { return *tracer_; } 37 | 38 | const std::map> &allChildren() const 39 | { return children; } 40 | 41 | template 42 | const std::set> &tallies() const { 43 | return tallies(identity()); 44 | } 45 | 46 | const std::set> &tallies(identity) const 47 | { return andTallies_; } 48 | const std::set> &tallies(identity) const 49 | { return orTallies_; } 50 | 51 | const std::multimap &properties() const 52 | { return props; } 53 | const Key &allConstraints() const { return constraints; } 54 | 55 | void addTally(std::shared_ptr tally) { andTallies_.insert(tally); } 56 | void addTally(std::shared_ptr tally) { orTallies_.insert(tally); } 57 | 58 | Node &addChild(const Key &key) { 59 | return *(*children.insert(std::make_pair(key, std::make_shared())) 60 | .first).second; 61 | } 62 | 63 | void getChildren(const Key &key, const Specificity &spec, 64 | SearchState &searchState) const { 65 | for (auto it = children.cbegin(); it != children.cend(); ++it) { 66 | if (it->first.matches(key)) 67 | it->second->activate(spec + it->first.specificity(), searchState); 68 | } 69 | } 70 | 71 | std::vector getProperty(const std::string &name) const { 72 | std::pair::const_iterator, 73 | std::multimap::const_iterator> range( 74 | props.equal_range(name)); 75 | std::vector result; 76 | for (; range.first != range.second; ++range.first) 77 | result.push_back(&range.first->second); 78 | return result; 79 | } 80 | 81 | void activate(const Specificity &spec, SearchState &searchState) const { 82 | searchState.constrain(constraints); 83 | if (searchState.add(spec, this)) { 84 | for (auto it = props.begin(); it != props.end(); ++it) 85 | searchState.cacheProperty(it->first, spec, &it->second); 86 | for (auto it = andTallies_.begin(); it != andTallies_.end(); ++it) 87 | (*it)->activate(*this, spec, searchState); 88 | for (auto it = orTallies_.begin(); it != orTallies_.end(); ++it) 89 | (*it)->activate(*this, spec, searchState); 90 | } 91 | } 92 | 93 | void addConstraint(const Key &key) { 94 | constraints.addAll(key); 95 | } 96 | 97 | void addProperty(const std::string &name, const Property &value) { 98 | props.insert(std::pair(name, value)); 99 | } 100 | }; 101 | 102 | 103 | } 104 | -------------------------------------------------------------------------------- /src/dag/property.cpp: -------------------------------------------------------------------------------- 1 | #include "dag/property.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "ccs/context.h" 7 | 8 | namespace ccs { 9 | 10 | template 11 | S Value::accept(V &&visitor) const { 12 | switch (which_) { 13 | case String: return visitor(rawStringVal_); 14 | case Int: return visitor(rawPrimVal_.intVal); 15 | case Double: return visitor(rawPrimVal_.doubleVal); 16 | case Bool: return visitor(rawPrimVal_.boolVal); 17 | } 18 | std::ostringstream msg; 19 | msg << "Bad enum value " << which_; 20 | throw std::runtime_error(msg.str()); 21 | }; 22 | 23 | namespace { 24 | 25 | template 26 | struct Caster { 27 | const Value &val; 28 | Caster(const Value &val) : val(val) {} 29 | template 30 | S operator()(const T &v) const { 31 | (void)v; 32 | S s; 33 | if (!CcsContext::coerceString(val.asString(), s)) 34 | throw bad_coercion(val.name(), val.asString()); 35 | return s; 36 | } 37 | S operator()(const S &v) const { return v; } 38 | }; 39 | 40 | struct ToString { 41 | std::string operator()(bool v) const { return v ? "true" : "false"; } 42 | std::string operator()(const StringVal &v) const { return v.str(); } 43 | template 44 | std::string operator()(const T &v) const { 45 | std::ostringstream str; 46 | str << v; 47 | return str.str(); 48 | } 49 | }; 50 | 51 | } 52 | 53 | void StringElem::interpolateInto(std::ostream &os) const { 54 | if (isInterpolant) { 55 | const char *val = getenv(value.c_str()); 56 | if (val) os << val; 57 | return; 58 | } 59 | 60 | os << value; 61 | } 62 | 63 | std::string StringVal::str() const { 64 | std::ostringstream str; 65 | for (auto it = elements_.begin(); it != elements_.end(); ++it) 66 | it->interpolateInto(str); 67 | return str.str(); 68 | } 69 | 70 | int Value::asInt() const 71 | { return accept(Caster(*this)); } 72 | double Value::asDouble() const 73 | { return accept(Caster(*this)); } 74 | bool Value::asBool() const 75 | { return accept(Caster(*this)); } 76 | void Value::str() 77 | { strVal_ = accept(ToString()); } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/dag/property.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "ccs/types.h" 8 | 9 | namespace ccs { 10 | 11 | struct StringElem { 12 | std::string value; 13 | bool isInterpolant; 14 | 15 | explicit StringElem(std::string value, bool isInterpolant = false) : 16 | value(std::move(value)), isInterpolant(isInterpolant) {} 17 | 18 | void interpolateInto(std::ostream &os) const; 19 | }; 20 | 21 | struct StringVal { 22 | std::vector elements_; 23 | 24 | StringVal() {} 25 | explicit StringVal(const std::string &str) { 26 | elements_.emplace_back(str); 27 | } 28 | std::string str() const; 29 | 30 | bool interpolation() const { 31 | if (elements_.size() > 1) return true; 32 | if (elements_.front().isInterpolant) return true; 33 | return false; 34 | } 35 | }; 36 | 37 | class Value { 38 | enum Which { String, Int, Double, Bool }; 39 | Which which_; 40 | StringVal rawStringVal_; 41 | union { 42 | int64_t intVal; 43 | double doubleVal; 44 | bool boolVal; 45 | } rawPrimVal_; 46 | std::string strVal_; 47 | std::string name_; 48 | 49 | public: 50 | Value() : which_(String), rawPrimVal_{0} {} 51 | void setString(const StringVal &val) 52 | { rawStringVal_ = val; which_ = String; str(); } 53 | void setInt(int64_t val) 54 | { rawPrimVal_.intVal = val; which_ = Int; str(); } 55 | void setBool(bool val) 56 | { rawPrimVal_.boolVal = val; which_ = Bool; str(); } 57 | void setDouble(double val) 58 | { rawPrimVal_.doubleVal = val; which_ = Double; str(); } 59 | void setName(const std::string &name) { name_ = name; } 60 | 61 | const std::string &name() const { return name_; } 62 | const std::string &asString() const { return strVal_; } 63 | int asInt() const; 64 | double asDouble() const; 65 | bool asBool() const; 66 | 67 | private: 68 | void str(); 69 | template 70 | S accept(V &&visitor) const; 71 | }; 72 | 73 | class Property : public CcsProperty { 74 | const Value value_; 75 | Origin origin_; 76 | unsigned propertyNumber_; 77 | bool override_; 78 | 79 | public: 80 | Property(const Value &value, const Origin &origin, 81 | unsigned propertyNumber, bool override) : 82 | value_(value), origin_(origin), propertyNumber_(propertyNumber), 83 | override_(override) {} 84 | 85 | virtual bool exists() const { return true; } 86 | virtual Origin origin() const { return origin_; } 87 | virtual const std::string &strValue() const { return value_.asString(); } 88 | virtual int intValue() const { return value_.asInt(); } 89 | virtual double doubleValue() const { return value_.asDouble(); } 90 | virtual bool boolValue() const { return value_.asBool(); } 91 | bool override() const { return override_; } 92 | unsigned propertyNumber() const { return propertyNumber_; } 93 | }; 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/dag/specificity.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace ccs { 4 | 5 | struct Specificity { 6 | unsigned names; 7 | unsigned values; 8 | 9 | Specificity() : names(0), values(0) {} 10 | Specificity(unsigned names, unsigned values) : names(names), values(values) {} 11 | 12 | bool operator<(const Specificity &s) const { 13 | if (values < s.values) return true; 14 | if (values == s.values && names < s.names) return true; 15 | return false; 16 | } 17 | 18 | Specificity operator+(const Specificity &that) const { 19 | return Specificity(names + that.names, values + that.values); 20 | } 21 | }; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/dag/tally.cpp: -------------------------------------------------------------------------------- 1 | #include "dag/tally.h" 2 | 3 | #include "dag/node.h" 4 | 5 | namespace ccs { 6 | 7 | TallyState *TallyState::clone() const { 8 | TallyState *next = new TallyState(tally); 9 | next->firstMatched = firstMatched; 10 | next->firstMatch = firstMatch; 11 | next->secondMatched = secondMatched; 12 | next->secondMatch = secondMatch; 13 | return next; 14 | } 15 | 16 | TallyState *TallyState::activate(const Node &leg, 17 | const Specificity &spec) const { 18 | // NB reference equality in the below... 19 | TallyState *next = clone(); 20 | if (&tally.firstLeg_ == &leg) { 21 | next->firstMatched = true; 22 | if (next->firstMatch < spec) next->firstMatch = spec; 23 | } 24 | if (&tally.secondLeg_ == &leg) { 25 | next->secondMatched = true; 26 | if (next->secondMatch < spec) next->secondMatch = spec; 27 | } 28 | return next; 29 | } 30 | 31 | Tally::Tally(Node &firstLeg, Node &secondLeg) : 32 | node_(new Node()), 33 | firstLeg_(firstLeg), 34 | secondLeg_(secondLeg) {} 35 | 36 | Tally::~Tally() {} 37 | 38 | void AndTally::activate(const Node &leg, const Specificity &spec, 39 | SearchState &searchState) const { 40 | const TallyState *state = searchState.getTallyState(this)->activate(leg, spec); 41 | searchState.setTallyState(this, state); 42 | // seems like this could lead to spurious warnings, but see comment below... 43 | if (state->fullyMatched()) 44 | node_->activate(state->specificity(), searchState); 45 | } 46 | 47 | void OrTally::activate(const Node &, const Specificity &spec, 48 | SearchState &searchState) const { 49 | // no state for or-joins, just re-activate node with the current specificity 50 | // it seems that this may allow spurious warnings, if multiple legs of the 51 | // disjunction match with same specificity. but this is detected in 52 | // SearchState, where we keep a *set* of nodes for each specificity, rather 53 | // than, for example, a *list*. 54 | node_->activate(spec, searchState); 55 | } 56 | 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/dag/tally.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "dag/specificity.h" 6 | 7 | namespace ccs { 8 | 9 | class AndTally; 10 | class Node; 11 | class SearchState; 12 | 13 | class TallyState { 14 | friend class AndTally; 15 | const AndTally &tally; 16 | bool firstMatched; 17 | bool secondMatched; 18 | Specificity firstMatch; 19 | Specificity secondMatch; 20 | 21 | TallyState *activate(const Node &leg, const Specificity &spec) const; 22 | TallyState *clone() const; 23 | 24 | bool fullyMatched() const { return firstMatched && secondMatched; } 25 | Specificity specificity() const { return firstMatch + secondMatch; } 26 | 27 | public: 28 | explicit TallyState(const AndTally &tally) : 29 | tally(tally), firstMatched(false), secondMatched(false) {} 30 | TallyState(const TallyState &) = delete; 31 | TallyState &operator=(const TallyState &) = delete; 32 | }; 33 | 34 | class Tally { 35 | protected: 36 | std::unique_ptr node_; 37 | Node &firstLeg_; 38 | Node &secondLeg_; 39 | 40 | public: 41 | Tally(Node &firstLeg, Node &secondLeg); 42 | virtual ~Tally(); 43 | 44 | Tally(const Tally &) = delete; 45 | Tally &operator=(const Tally &) = delete; 46 | 47 | const Node &node() const { return *node_; } 48 | Node &node() { return *node_; } 49 | 50 | virtual void activate(const Node &leg, const Specificity &spec, 51 | SearchState &searchState) const = 0; 52 | }; 53 | 54 | class OrTally : public Tally { 55 | public: 56 | OrTally(Node &firstLeg, Node &secondLeg) : Tally(firstLeg, secondLeg) {} 57 | virtual void activate(const Node &leg, const Specificity &spec, 58 | SearchState &searchState) const; 59 | }; 60 | 61 | class AndTally : public Tally { 62 | friend class TallyState; 63 | TallyState emptyState_; 64 | 65 | public: 66 | AndTally(Node &firstLeg, Node &secondLeg) : 67 | Tally(firstLeg, secondLeg), emptyState_(*this) {} 68 | virtual void activate(const Node &leg, const Specificity &spec, 69 | SearchState &searchState) const; 70 | const TallyState *emptyState() const { return &emptyState_; } 71 | }; 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/domain.cpp: -------------------------------------------------------------------------------- 1 | #include "ccs/domain.h" 2 | 3 | #include 4 | 5 | #include "graphviz.h" 6 | #include "ccs/context.h" 7 | #include "dag/dag_builder.h" 8 | #include "parser/loader.h" 9 | 10 | namespace ccs { 11 | 12 | namespace { 13 | 14 | void origins(std::ostream &str, const std::vector &values) { 15 | bool first = true; 16 | for (auto it = values.cbegin(); it != values.cend(); ++it) { 17 | if (!first) str << ", "; 18 | str << (*it)->origin(); 19 | first = false; 20 | } 21 | } 22 | 23 | class LoggingTracer : public CcsTracer { 24 | std::shared_ptr logger; 25 | bool logAccesses; 26 | 27 | public: 28 | LoggingTracer(std::shared_ptr logger, bool logAccesses) : 29 | logger(std::move(logger)), logAccesses(logAccesses) {} 30 | 31 | virtual void onPropertyFound( 32 | const CcsContext &ccsContext, 33 | const std::string &propertyName, 34 | const CcsProperty &prop) { 35 | if (logAccesses) { 36 | std::ostringstream msg; 37 | msg << "Found property: " << propertyName 38 | << " = " << prop.strValue() << "\n"; 39 | msg << " at " << prop.origin() << " in context: [" << ccsContext << "]"; 40 | logger->info(msg.str()); 41 | } 42 | } 43 | 44 | virtual void onPropertyNotFound( 45 | const CcsContext &ccsContext, 46 | const std::string &propertyName) { 47 | if (logAccesses) { 48 | std::ostringstream msg; 49 | msg << "Property not found: " << propertyName << "\n"; 50 | msg << " in context: [" << ccsContext << "]"; 51 | logger->info(msg.str()); 52 | } 53 | } 54 | 55 | virtual void onConflict( 56 | const CcsContext &ccsContext, 57 | const std::string &propertyName, 58 | const std::vector values) { 59 | std::ostringstream msg; 60 | msg << "Conflict detected for property '" << propertyName 61 | << "' in context [" << ccsContext << "]. " 62 | << "(Conflicting settings at: ["; 63 | origins(msg, values); 64 | msg << "].) Using most recent value."; 65 | logger->warn(msg.str()); 66 | } 67 | 68 | virtual void onParseError(const std::string &msg) { 69 | logger->error(msg); 70 | } 71 | }; 72 | 73 | struct StdErrLogger : public CcsLogger { 74 | virtual void info(const std::string &msg) 75 | { std::cerr << "INFO: " << msg << std::endl; } 76 | virtual void warn(const std::string &msg) 77 | { std::cerr << "WARN: " << msg << std::endl; } 78 | virtual void error(const std::string &msg) 79 | { std::cerr << "ERROR: " << msg << std::endl; } 80 | }; 81 | 82 | struct NoImportResolver : public ImportResolver { 83 | virtual bool resolve(const std::string &, 84 | std::function) { 85 | return false; 86 | } 87 | }; 88 | 89 | NoImportResolver NoImportResolver; 90 | 91 | } 92 | 93 | std::shared_ptr CcsLogger::makeStdErrLogger() { 94 | return std::make_shared(); 95 | } 96 | 97 | std::shared_ptr CcsTracer::makeLoggingTracer( 98 | std::shared_ptr logger, bool logAccesses) { 99 | return std::make_shared(std::move(logger), logAccesses); 100 | } 101 | 102 | ImportResolver &ImportResolver::None = NoImportResolver; 103 | 104 | CcsDomain::CcsDomain(std::shared_ptr tracer) : 105 | dag(new DagBuilder(std::move(tracer))) {} 106 | 107 | CcsDomain::CcsDomain(bool logAccesses) : 108 | dag(new DagBuilder(CcsTracer::makeLoggingTracer( 109 | CcsLogger::makeStdErrLogger(), logAccesses))) {} 110 | 111 | CcsDomain::CcsDomain(std::shared_ptr log, bool logAccesses) : 112 | dag(new DagBuilder(CcsTracer::makeLoggingTracer( 113 | std::move(log), logAccesses))) {} 114 | 115 | CcsDomain::~CcsDomain() {} 116 | 117 | CcsDomain &CcsDomain::loadCcsStream(std::istream &stream, 118 | const std::string &fileName, ImportResolver &importResolver) { 119 | Loader loader(dag->root()->tracer()); 120 | loader.loadCcsStream(stream, fileName, *dag, importResolver); 121 | return *this; 122 | } 123 | 124 | RuleBuilder CcsDomain::ruleBuilder() { 125 | return RuleBuilder(*dag); 126 | } 127 | 128 | CcsContext CcsDomain::build() { 129 | return CcsContext(dag->root()); 130 | } 131 | 132 | void CcsDomain::logRuleDag(std::ostream &os) const { 133 | os << Dumper(*dag->root()); 134 | } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /src/graphviz.cpp: -------------------------------------------------------------------------------- 1 | #include "graphviz.h" 2 | 3 | #include "dag/node.h" 4 | 5 | #include 6 | #include 7 | 8 | namespace ccs { 9 | 10 | typedef Dumper::Style Style; 11 | 12 | namespace { 13 | 14 | struct Streamer { 15 | const Style &style; 16 | bool root; 17 | Streamer(const Style &style, bool root=false) : style(style), root(root) {} 18 | friend std::ostream &operator<<(std::ostream &os, const Streamer &ss) { 19 | if (!ss.style.empty()) { 20 | bool first = true; 21 | if (!ss.root) os << '['; 22 | for (auto it = ss.style.cbegin(); it != ss.style.cend(); ++it) { 23 | if (!first && !ss.root) os << ", "; 24 | first = false; 25 | os << it->first; 26 | os << "=\""; 27 | for (auto cit = it->second.cbegin(); cit != it->second.cend(); ++cit) { 28 | switch (*cit) { 29 | default: os << *cit; break; 30 | case '"': os << "\\\""; break; 31 | case '\n': os << "\\n"; break; 32 | case '\t': os << "\\t"; break; 33 | } 34 | } 35 | os << '"'; 36 | if (ss.root) os << ";"; 37 | } 38 | if (!ss.root) os << ']'; 39 | } 40 | return os; 41 | } 42 | }; 43 | 44 | template 45 | struct NodeName { 46 | const T &t; 47 | NodeName(const T &t) : t(t) {} 48 | friend std::ostream &operator<<(std::ostream &os, const NodeName &nn) { 49 | return os << '"' << &nn.t << '"'; 50 | } 51 | 52 | }; 53 | 54 | template 55 | NodeName nn(const T &t) { return NodeName(t); } 56 | 57 | void visit(const Dumper &dumper, std::ostream &os, 58 | std::unordered_set &visited, const Node &node) { 59 | if (visited.count(&node)) return; 60 | visited.insert(&node); 61 | os << nn(node) << ' ' << Streamer(dumper.nodeStyle(node)) << ";\n"; 62 | const auto &cs = node.allChildren(); 63 | for (auto it = cs.cbegin(); it != cs.cend(); ++it) { 64 | const auto &child = *it->second; 65 | os << nn(node) << "->" << nn(child) << ' ' 66 | << Streamer(dumper.edgeStyle(it->first)) << ";\n"; 67 | visit(dumper, os, visited, child); 68 | } 69 | 70 | const auto &ts = node.tallies(); 71 | for (auto it = ts.cbegin(); it != ts.cend(); ++it) { 72 | const auto &tally = **it; 73 | os << nn(node) << "->" << nn(tally) << ' ' 74 | << Streamer(dumper.tallyEdgeStyle()) << ";\n"; 75 | if (!visited.count(&tally.node())) { 76 | os << nn(tally) << ' ' << Streamer(dumper.tallyStyle(tally)) << ";\n"; 77 | os << nn(tally) << "->" << nn(tally.node()) << ' ' 78 | << Streamer(dumper.tallyEdgeStyle()) << ";\n"; 79 | visit(dumper, os, visited, tally.node()); 80 | } 81 | } 82 | const auto &ts2 = node.tallies(); 83 | for (auto it = ts2.cbegin(); it != ts2.cend(); ++it) { 84 | const auto &tally = **it; 85 | os << nn(node) << "->" << nn(tally) << ' ' 86 | << Streamer(dumper.tallyEdgeStyle()) << ";\n"; 87 | if (!visited.count(&tally.node())) { 88 | os << nn(tally) << ' ' << Streamer(dumper.tallyStyle(tally)) << ";\n"; 89 | os << nn(tally) << "->" << nn(tally.node()) << ' ' 90 | << Streamer(dumper.tallyEdgeStyle()) << ";\n"; 91 | visit(dumper, os, visited, tally.node()); 92 | } 93 | } 94 | } 95 | 96 | } 97 | 98 | Style Dumper::graphStyle() const { 99 | return Style(); 100 | } 101 | 102 | Style Dumper::tallyStyle(const Tally &tally) const { 103 | Style style; 104 | style["nodesep"] = "2.0"; 105 | style["color"] = "blue"; 106 | style["label"] = ""; 107 | 108 | if (dynamic_cast(&tally)) { 109 | style["shape"] = "triangle"; 110 | } else { 111 | style["shape"] = "invtriangle"; 112 | } 113 | 114 | return style; 115 | } 116 | 117 | Style Dumper::nodeStyle(const Node &node) const { 118 | Style style; 119 | style["shape"] = "box"; 120 | style["style"] = "rounded"; 121 | style["nodesep"] = "2.0"; 122 | 123 | std::ostringstream str; 124 | const auto &props = node.properties(); 125 | for (auto it = props.cbegin(); it != props.cend(); ++it) { 126 | if (it->second.override()) str << "@override "; 127 | str << it->first << " = " << it->second.strValue() << "\n"; 128 | } 129 | if (!node.allConstraints().empty()) 130 | str << "@constrain " << node.allConstraints(); 131 | style["label"] = str.str(); 132 | style["fontsize"] = "9"; 133 | return style; 134 | } 135 | 136 | Style Dumper::tallyEdgeStyle() const { 137 | Style style; 138 | style["style"] = "dashed"; 139 | return style; 140 | } 141 | 142 | Style Dumper::edgeStyle(const Key &key) const { 143 | Style style; 144 | std::ostringstream str; 145 | str << key; 146 | style["label"] = str.str(); 147 | style["fontsize"] = "9"; 148 | return style; 149 | } 150 | 151 | std::ostream &operator<<(std::ostream &os, const Dumper &dagDumper) { 152 | std::unordered_set visited; 153 | 154 | os << "digraph {"; 155 | os << "edge [arrowhead = none]"; 156 | os << Streamer(dagDumper.graphStyle(), true); 157 | 158 | visit(dagDumper, os, visited, dagDumper.node_); 159 | 160 | os << "}"; 161 | 162 | return os; 163 | } 164 | 165 | } 166 | -------------------------------------------------------------------------------- /src/graphviz.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace ccs { 8 | 9 | class Key; 10 | class Node; 11 | class Tally; 12 | 13 | class Dumper { 14 | const Node &node_; 15 | 16 | public: 17 | explicit Dumper(const Node &node) : node_(node) {} 18 | virtual ~Dumper() {} 19 | 20 | typedef std::map Style; 21 | Style graphStyle() const; 22 | Style nodeStyle(const Node &node) const; 23 | Style edgeStyle(const Key &key) const; 24 | Style tallyStyle(const Tally &tally) const; 25 | Style tallyEdgeStyle() const; 26 | 27 | friend std::ostream &operator<<(std::ostream &os, const Dumper &dumper); 28 | }; 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/parser/ast.cpp: -------------------------------------------------------------------------------- 1 | #include "parser/ast.h" 2 | 3 | #include "ccs/domain.h" 4 | #include "dag/node.h" 5 | #include "parser/build_context.h" 6 | #include "parser/loader.h" 7 | 8 | namespace ccs { namespace ast { 9 | 10 | void Import::addTo(BuildContext::P buildContext, BuildContext::P baseContext) const 11 | { ast.addTo(buildContext, baseContext); } 12 | void PropDef::addTo(BuildContext::P buildContext, BuildContext::P) const 13 | { buildContext->addProperty(*this); } 14 | void Constraint::addTo(BuildContext::P buildContext, BuildContext::P) const 15 | { buildContext->node().addConstraint(key_); } 16 | 17 | bool PropDef::resolveImports(ImportResolver &, Loader &, 18 | std::vector &) 19 | { return true; } 20 | bool Constraint::resolveImports(ImportResolver &, Loader &, 21 | std::vector &) 22 | { return true; } 23 | 24 | bool Import::resolveImports(ImportResolver &importResolver, Loader &loader, 25 | std::vector &inProgress) { 26 | bool result = false; 27 | if (std::find(inProgress.begin(), inProgress.end(), location) != 28 | inProgress.end()) { 29 | std::ostringstream msg; 30 | msg << "Circular import detected involving '" << location << "'"; 31 | loader.tracer().onParseError(msg.str()); 32 | } else { 33 | inProgress.push_back(location); 34 | result = importResolver.resolve(location, 35 | [&](std::istream &stream) { 36 | return loader.parseCcsStream(stream, location, 37 | importResolver, inProgress, ast); 38 | }); 39 | inProgress.pop_back(); 40 | if (!result) { 41 | std::ostringstream msg; 42 | msg << "Failed to resolve '" << location 43 | << "'! (User-provided resolver returned false.)"; 44 | loader.tracer().onParseError(msg.str()); 45 | } 46 | } 47 | return result; 48 | } 49 | 50 | 51 | void Nested::addTo(BuildContext::P buildContext, BuildContext::P baseContext) const { 52 | BuildContext::P bc = buildContext; 53 | if (selector_) 54 | bc = selector_->traverse(buildContext, baseContext); 55 | for (auto it = rules_.begin(); it != rules_.end(); ++it) 56 | (*it)->addTo(bc, baseContext); 57 | } 58 | 59 | bool Nested::resolveImports(ImportResolver &importResolver, Loader &loader, 60 | std::vector &inProgress) { 61 | for (auto it = rules_.begin(); it != rules_.end(); ++it) 62 | if (!(*it)->resolveImports(importResolver, loader, inProgress)) 63 | return false; 64 | return true; 65 | } 66 | 67 | 68 | struct Wrap : public SelectorLeaf { 69 | std::vector branches; 70 | SelectorLeaf::P right; 71 | 72 | Wrap(SelectorBranch::P branch, SelectorLeaf::P leaf) : right(leaf) 73 | { branches.push_back(branch); } 74 | 75 | // left == this, always. guess we could just use enable_shared_from_this, 76 | // but that's just as ugly... spirit just doesn't like this way of doing 77 | // things... it works fine, though, and i don't want to invest a lot of 78 | // time in switching to some kind of value-types-in-a-variant design just 79 | // to play nicer with spirit. it's also worth something to stay roughly 80 | // in sync with the java version. anyway, maybe reconsider down the road. 81 | virtual P descendant(P left, P right) 82 | { push(SelectorBranch::descendant(this->right), right); return left; } 83 | virtual P conjunction(P left, P right) 84 | { push(SelectorBranch::conjunction(this->right), right); return left; } 85 | virtual P disjunction(P left, P right) 86 | { push(SelectorBranch::disjunction(this->right), right); return left; } 87 | 88 | void push(SelectorBranch::P newBranch, SelectorLeaf::P newRight) { 89 | branches.push_back(newBranch); 90 | right = newRight; 91 | } 92 | 93 | virtual Node &traverse(BuildContext::P context) { 94 | BuildContext::P tmp = context; 95 | for (auto it = branches.begin(); it != branches.end(); ++it) 96 | tmp = (*it)->traverse(tmp, context); 97 | return tmp->traverse(*right); 98 | } 99 | }; 100 | 101 | struct Step : public SelectorLeaf { 102 | Key key_; 103 | 104 | Step(const Key &key) : key_(key) {} 105 | 106 | virtual P descendant(P left, P right) 107 | { return std::make_shared(SelectorBranch::descendant(left), right); } 108 | virtual P conjunction(P left, P right) 109 | { return std::make_shared(SelectorBranch::conjunction(left), right); } 110 | virtual P disjunction(P left, P right) 111 | { return std::make_shared(SelectorBranch::disjunction(left), right); } 112 | 113 | virtual Node &traverse(BuildContext::P context) 114 | { return context->node().addChild(key_); } 115 | }; 116 | 117 | SelectorLeaf::P SelectorLeaf::step(const Key &key) { 118 | return std::make_shared(key); 119 | } 120 | 121 | class BranchImpl : public SelectorBranch { 122 | typedef std::function Traverse; 124 | SelectorLeaf::P first_; 125 | Traverse traverse_; 126 | 127 | public: 128 | BranchImpl(SelectorLeaf::P first, Traverse traverse) : 129 | first_(first), traverse_(traverse) {} 130 | 131 | virtual BuildContext::P traverse(BuildContext::P context, 132 | BuildContext::P baseContext) { 133 | return traverse_(*first_, context, baseContext); 134 | } 135 | }; 136 | 137 | SelectorBranch::P SelectorBranch::descendant(SelectorLeaf::P first) { 138 | return std::make_shared(first, [](SelectorLeaf &first, 139 | BuildContext::P context, BuildContext::P baseContext) { 140 | (void)baseContext; 141 | return context->descendant(context->traverse(first)); 142 | }); 143 | } 144 | 145 | SelectorBranch::P SelectorBranch::conjunction(SelectorLeaf::P first) { 146 | return std::make_shared(first, [](SelectorLeaf &first, 147 | BuildContext::P context, BuildContext::P baseContext) { 148 | return context->conjunction(context->traverse(first), baseContext); 149 | }); 150 | } 151 | 152 | SelectorBranch::P SelectorBranch::disjunction(SelectorLeaf::P first) { 153 | return std::make_shared(first, [](SelectorLeaf &first, 154 | BuildContext::P context, BuildContext::P baseContext) { 155 | return context->disjunction(context->traverse(first), baseContext); 156 | }); 157 | } 158 | 159 | }} 160 | -------------------------------------------------------------------------------- /src/parser/ast.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "ccs/types.h" 8 | #include "dag/key.h" 9 | #include "dag/property.h" 10 | 11 | namespace ccs { 12 | 13 | class Loader; 14 | class ImportResolver; 15 | class Node; 16 | class BuildContext; 17 | 18 | namespace ast { 19 | 20 | struct AstRule { 21 | virtual ~AstRule() {} 22 | virtual void addTo(std::shared_ptr buildContext, 23 | std::shared_ptr baseContext) const = 0; 24 | virtual bool resolveImports(ImportResolver &importResolver, Loader &loader, 25 | std::vector &inProgress) = 0; 26 | 27 | }; 28 | 29 | struct PropDef : AstRule { 30 | std::string name_; 31 | Value value_; 32 | Origin origin_; 33 | bool override_; 34 | 35 | PropDef() : override_(false) {} 36 | void addTo(std::shared_ptr buildContext, 37 | std::shared_ptr baseContext) const override; 38 | bool resolveImports(ImportResolver &importResolver, Loader &loader, 39 | std::vector &inProgress) override; 40 | }; 41 | 42 | struct Constraint : AstRule { 43 | Key key_; 44 | explicit Constraint(const Key &key) : key_(key) {} 45 | void addTo(std::shared_ptr buildContext, 46 | std::shared_ptr baseContext) const override; 47 | bool resolveImports(ImportResolver &importResolver, Loader &loader, 48 | std::vector &inProgress) override; 49 | }; 50 | 51 | struct SelectorLeaf { 52 | typedef std::shared_ptr P; 53 | virtual ~SelectorLeaf() {}; 54 | virtual Node &traverse(std::shared_ptr context) = 0; 55 | 56 | static P step(const Key &key); 57 | 58 | virtual P descendant(P left, P right) = 0; 59 | virtual P conjunction(P left, P right) = 0; 60 | virtual P disjunction(P left, P right) = 0; 61 | }; 62 | 63 | struct SelectorBranch { 64 | typedef std::shared_ptr P; 65 | virtual ~SelectorBranch() {} 66 | virtual std::shared_ptr traverse( 67 | std::shared_ptr context, 68 | std::shared_ptr baseContext) = 0; 69 | 70 | static P descendant(SelectorLeaf::P first); 71 | static P conjunction(SelectorLeaf::P first); 72 | static P disjunction(SelectorLeaf::P first); 73 | }; 74 | 75 | struct Nested : AstRule { 76 | std::shared_ptr selector_; 77 | std::vector> rules_; 78 | 79 | void addRule(std::unique_ptr rule) { 80 | rules_.push_back(std::move(rule)); 81 | } 82 | void addTo(std::shared_ptr buildContext, 83 | std::shared_ptr baseContext) const override; 84 | bool resolveImports(ImportResolver &importResolver, Loader &loader, 85 | std::vector &inProgress) override; 86 | }; 87 | 88 | struct Import : AstRule { 89 | std::string location; 90 | Nested ast; 91 | 92 | explicit Import(const std::string &location) : location(location) {} 93 | void addTo(std::shared_ptr buildContext, 94 | std::shared_ptr baseContext) const override; 95 | bool resolveImports(ImportResolver &importResolver, Loader &loader, 96 | std::vector &inProgress) override; 97 | }; 98 | 99 | }} 100 | -------------------------------------------------------------------------------- /src/parser/build_context.cpp: -------------------------------------------------------------------------------- 1 | #include "parser/build_context.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "dag/dag_builder.h" 7 | #include "dag/node.h" 8 | #include "dag/tally.h" 9 | #include "parser/ast.h" 10 | 11 | namespace ccs { 12 | 13 | class Descendant : public BuildContext { 14 | Node &node_; 15 | 16 | public: 17 | Descendant(DagBuilder &dag, Node &node) : 18 | BuildContext(dag), 19 | node_(node) {} 20 | 21 | virtual Node &node() { return node_; } 22 | // this copy-into-new-shared-ptr thing is ugly, but enable_shared_from_this 23 | // is just as bad, or worse... 24 | virtual Node &traverse(ast::SelectorLeaf &selector) 25 | { return selector.traverse(std::make_shared(*this)); } 26 | }; 27 | 28 | template 29 | class TallyBuildContext : public BuildContext { 30 | Node &firstNode_; 31 | BuildContext::P baseContext_; 32 | 33 | public: 34 | TallyBuildContext(DagBuilder &dag, Node &node, BuildContext::P baseContext) : 35 | BuildContext(dag), 36 | firstNode_(node), 37 | baseContext_(baseContext) {} 38 | 39 | virtual Node &node() { return firstNode_; } 40 | 41 | virtual Node &traverse(ast::SelectorLeaf &selector) { 42 | Node &secondNode = selector.traverse(baseContext_); 43 | 44 | // we've arrived at the same node by two different paths. no tally is 45 | // actually needed here... 46 | if (&firstNode_ == &secondNode) return firstNode_; 47 | 48 | std::set> tallies; 49 | 50 | std::set_intersection( 51 | firstNode_.tallies().begin(), firstNode_.tallies().end(), 52 | secondNode.tallies().begin(), secondNode.tallies().end(), 53 | std::inserter(tallies, tallies.end())); 54 | 55 | // result will be either empty or have exactly one entry. 56 | 57 | if (tallies.empty()) { 58 | std::shared_ptr tally = std::make_shared(firstNode_, 59 | secondNode); 60 | firstNode_.addTally(tally); 61 | secondNode.addTally(tally); 62 | return tally->node(); 63 | } else { 64 | return (*tallies.begin())->node(); 65 | } 66 | } 67 | }; 68 | 69 | BuildContext::P BuildContext::descendant(DagBuilder &dag, Node &node) 70 | { return std::make_shared(dag, node); } 71 | BuildContext::P BuildContext::descendant(Node &node) 72 | { return std::make_shared(dag_, node); } 73 | BuildContext::P BuildContext::conjunction(Node &node, 74 | BuildContext::P baseContext) 75 | { return std::make_shared>(dag_, node, 76 | baseContext); } 77 | BuildContext::P BuildContext::disjunction(Node &node, 78 | BuildContext::P baseContext) 79 | { return std::make_shared>(dag_, node, 80 | baseContext); } 81 | 82 | void BuildContext::addProperty(const ast::PropDef &propDef) { 83 | Value value(propDef.value_); 84 | value.setName(propDef.name_); 85 | node().addProperty(propDef.name_, Property(value, 86 | propDef.origin_, dag_.nextProperty(), propDef.override_)); 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /src/parser/build_context.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "ccs/types.h" 6 | 7 | namespace ccs { 8 | 9 | class DagBuilder; 10 | class Node; 11 | namespace ast { class PropDef; } 12 | namespace ast { class SelectorLeaf; } 13 | 14 | class BuildContext { 15 | protected: 16 | DagBuilder &dag_; 17 | 18 | BuildContext(DagBuilder &dag) : dag_(dag) {} 19 | 20 | public: 21 | typedef std::shared_ptr P; 22 | virtual ~BuildContext() {} 23 | 24 | virtual Node &node() = 0; 25 | virtual Node &traverse(ast::SelectorLeaf &selector) = 0; 26 | 27 | static BuildContext::P descendant(DagBuilder &dag, Node &root); 28 | BuildContext::P descendant(Node &node); 29 | BuildContext::P conjunction(Node &node, BuildContext::P baseContext); 30 | BuildContext::P disjunction(Node &node, BuildContext::P baseContext); 31 | void addProperty(const ast::PropDef &propDef); 32 | }; 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/parser/loader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "parser/ast.h" 7 | #include "parser/parser.h" 8 | #include "dag/dag_builder.h" 9 | 10 | namespace ccs { 11 | 12 | class Loader { 13 | CcsTracer &trace; 14 | 15 | public: 16 | Loader(CcsTracer &trace) : trace(trace) {} 17 | 18 | CcsTracer &tracer() { return trace; } 19 | 20 | void loadCcsStream(std::istream &stream, const std::string &fileName, 21 | DagBuilder &dag, ImportResolver &importResolver) { 22 | ast::Nested ast; 23 | std::vector inProgress; 24 | if (parseCcsStream(stream, fileName, importResolver, inProgress, ast)) { 25 | // everything parsed, no errors. now it's safe to modify the dag... 26 | ast.addTo(dag.buildContext(), dag.buildContext()); 27 | } 28 | // otherwise, errors already reported, don't modify the dag... 29 | } 30 | 31 | bool parseCcsStream(std::istream &stream, const std::string &fileName, 32 | ImportResolver &importResolver, std::vector &inProgress, 33 | ast::Nested &ast) { 34 | Parser parser(trace); 35 | if (!parser.parseCcsStream(fileName, stream, ast)) return false; 36 | if (!ast.resolveImports(importResolver, *this, inProgress)) return false; 37 | return true; 38 | } 39 | }; 40 | 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/parser/parser.cpp: -------------------------------------------------------------------------------- 1 | #include "parser/parser.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define THROW(where, stuff) \ 9 | do { \ 10 | std::ostringstream _message; \ 11 | _message << stuff; \ 12 | throw parse_error(_message.str(), where); \ 13 | } while (0) 14 | 15 | namespace ccs { 16 | 17 | namespace { 18 | 19 | struct Location { 20 | uint32_t line; 21 | uint32_t column; 22 | 23 | Location(uint32_t line, uint32_t column) 24 | : line(line), column(column) {} 25 | }; 26 | 27 | struct parse_error { 28 | std::string what; 29 | Location where; 30 | 31 | parse_error(const std::string &what, Location where) 32 | : what(what), where(where) {} 33 | }; 34 | 35 | // TODO collapse IDENT and STRING? 36 | 37 | // TODO this should be cleaned up, all these stupid "values" is dumb and 38 | // confusing... 39 | struct Token { 40 | enum Type { 41 | EOS, 42 | LPAREN, RPAREN, LBRACE, RBRACE, SEMI, COLON, COMMA, DOT, GT, EQ, SLASH, 43 | CONSTRAIN, CONTEXT, IMPORT, OVERRIDE, 44 | INT, DOUBLE, IDENT, NUMID, STRING 45 | }; 46 | 47 | Type type; 48 | Location location; 49 | std::string value; 50 | StringVal stringValue; 51 | int64_t intValue; // only valid in case of ints... 52 | double doubleValue; // only valid in case of numbers... 53 | 54 | Token() : type(EOS), location(0, 0), intValue(0), doubleValue(0) {} 55 | Token(Type type, Location loc) 56 | : type(type), location(loc), intValue(0), doubleValue(0) {} 57 | Token(Type type, char first, Location loc) 58 | : type(type), location(loc), intValue(0), doubleValue(0) { 59 | value += first; 60 | } 61 | 62 | void append(char c) { value += c; } 63 | }; 64 | 65 | std::ostream &operator<<(std::ostream &os, Token::Type type) { 66 | switch (type) { 67 | case Token::EOS: os << "end-of-input"; break; 68 | case Token::LPAREN: os << "'('"; break; 69 | case Token::RPAREN: os << "')"; break; 70 | case Token::LBRACE: os << "'{'"; break; 71 | case Token::RBRACE: os << "'}'"; break; 72 | case Token::SEMI: os << "';'"; break; 73 | case Token::COLON: os << "':'"; break; 74 | case Token::COMMA: os << "','"; break; 75 | case Token::DOT: os << "'.'"; break; 76 | case Token::GT: os << "'>'"; break; 77 | case Token::EQ: os << "'='"; break; 78 | case Token::SLASH: os << "'/'"; break; 79 | case Token::CONSTRAIN: os << "'@constrain'"; break; 80 | case Token::CONTEXT: os << "'@context'"; break; 81 | case Token::IMPORT: os << "'@import'"; break; 82 | case Token::OVERRIDE: os << "'@override'"; break; 83 | case Token::INT: os << "integer"; break; 84 | case Token::DOUBLE: os << "double"; break; 85 | case Token::IDENT: os << "identifier"; break; 86 | case Token::NUMID: os << "numeric/identifier"; break; 87 | case Token::STRING: os << "string literal"; break; 88 | } 89 | return os; 90 | } 91 | 92 | class Buf { 93 | std::istream &stream_; 94 | uint32_t line_; 95 | uint32_t column_; 96 | 97 | public: 98 | explicit Buf(std::istream &stream) : stream_(stream), line_(1), column_(0) { 99 | stream_.unsetf(std::ios::skipws); 100 | } 101 | 102 | Buf(const Buf &) = delete; 103 | const Buf &operator=(const Buf &) = delete; 104 | 105 | int get() { 106 | auto c = stream_.get(); 107 | // this way of tracking location gives funny results when get() returns 108 | // a newline, but we don't actually care about that anyway... 109 | column_++; 110 | if (c == '\n') { 111 | line_++; 112 | column_ = 0; 113 | } 114 | return c; 115 | } 116 | 117 | int peek() { 118 | return stream_.peek(); 119 | } 120 | 121 | Location location() { return Location(line_, column_); } 122 | Location peekLocation() { return Location(line_, column_+1); } 123 | }; 124 | 125 | class Lexer { 126 | Buf stream_; 127 | Token next_; 128 | 129 | public: 130 | Lexer(std::istream &stream) : stream_(stream), next_(nextToken()) {} 131 | 132 | const Token &peek() const { return next_; } 133 | 134 | Token consume() { 135 | Token tmp = next_; 136 | next_ = nextToken(); 137 | return tmp; 138 | } 139 | 140 | private: 141 | Token nextToken() { 142 | int c = stream_.get(); 143 | 144 | while (std::isspace(c) || comment(c)) c = stream_.get(); 145 | 146 | auto where = stream_.location(); 147 | 148 | switch (c) { 149 | case EOF: return Token(Token::EOS, where); 150 | case '(': return Token(Token::LPAREN, where); 151 | case ')': return Token(Token::RPAREN, where); 152 | case '{': return Token(Token::LBRACE, where); 153 | case '}': return Token(Token::RBRACE, where); 154 | case ';': return Token(Token::SEMI, where); 155 | case ':': return Token(Token::COLON, where); 156 | case ',': return Token(Token::COMMA, where); 157 | case '.': return Token(Token::DOT, where); 158 | case '>': return Token(Token::GT, where); 159 | case '=': return Token(Token::EQ, where); 160 | case '/': return Token(Token::SLASH, where); 161 | case '@': { 162 | Token tok = ident(c, where); 163 | if (tok.value == "@constrain") tok.type = Token::CONSTRAIN; 164 | else if (tok.value == "@context") tok.type = Token::CONTEXT; 165 | else if (tok.value == "@import") tok.type = Token::IMPORT; 166 | else if (tok.value == "@override") tok.type = Token::OVERRIDE; 167 | else THROW(where, "Unrecognized @-command: " << tok.value); 168 | return tok; 169 | } 170 | case '\'': return string(c, where); 171 | case '"': return string(c, where); 172 | } 173 | 174 | if (numIdInitChar(c)) return numId(c, where); 175 | if (identInitChar(c)) return ident(c, where); 176 | 177 | THROW(where, "Unexpected character: '" << (char)c << "' (0x" << std::hex 178 | << c << ")"); 179 | } 180 | 181 | bool comment(int c) { 182 | if (c != '/') return false; 183 | if (stream_.peek() == '/') { 184 | stream_.get(); 185 | for (int tmp = stream_.get(); tmp != '\n' && tmp != EOF; 186 | tmp = stream_.get()); 187 | return true; 188 | } else if (stream_.peek() == '*') { 189 | stream_.get(); 190 | multilineComment(); 191 | return true; 192 | } 193 | return false; 194 | } 195 | 196 | void multilineComment() { 197 | while (true) { 198 | int c = stream_.get(); 199 | if (c == EOF) 200 | THROW(stream_.location(), "Unterminated multi-line comment"); 201 | if (c == '*' && stream_.peek() == '/') { 202 | stream_.get(); 203 | return; 204 | } 205 | if (c == '/' && stream_.peek() == '*') { 206 | stream_.get(); 207 | multilineComment(); 208 | } 209 | } 210 | } 211 | 212 | bool identInitChar(int c) { 213 | if (c == '$') return true; 214 | if (c == '_') return true; 215 | if ('A' <= c && c <= 'Z') return true; 216 | if ('a' <= c && c <= 'z') return true; 217 | return false; 218 | } 219 | 220 | bool identChar(int c) { 221 | if (identInitChar(c)) return true; 222 | if ('0' <= c && c <= '9') return true; 223 | return false; 224 | } 225 | 226 | bool interpolantChar(int c) { 227 | if (c == '_') return true; 228 | if ('0' <= c && c <= '9') return true; 229 | if ('A' <= c && c <= 'Z') return true; 230 | if ('a' <= c && c <= 'z') return true; 231 | return false; 232 | } 233 | 234 | Token string(char first, Location where) { 235 | StringVal result; 236 | std::string current; 237 | while (stream_.peek() != first) { 238 | switch (stream_.peek()) { 239 | case EOF: 240 | THROW(stream_.peekLocation(), "Unterminated string literal"); 241 | case '$': { 242 | stream_.get(); 243 | if (stream_.peek() != '{') 244 | THROW(stream_.peekLocation(), "Expected '{'"); 245 | stream_.get(); 246 | if (!current.empty()) result.elements_.emplace_back(current); 247 | current = ""; 248 | std::string interpolant; 249 | while (stream_.peek() != '}') { 250 | if (!interpolantChar(stream_.peek())) 251 | THROW(stream_.peekLocation(), 252 | "Character not allowed in string interpolant: '" << 253 | (char)stream_.peek() << "' (0x" << std::hex << stream_.peek() 254 | << ")"); 255 | interpolant += stream_.get(); 256 | } 257 | stream_.get(); 258 | result.elements_.emplace_back(interpolant, true); 259 | break; 260 | } 261 | case '\\': { 262 | stream_.get(); 263 | auto escape = stream_.get(); 264 | switch (escape) { 265 | case EOF: THROW(stream_.location(), "Unterminated string literal"); 266 | case '$': current += '$'; break; 267 | case '\'': current += '\''; break; 268 | case '"': current += '"'; break; 269 | case '\\': current += '\\'; break; 270 | case 't': current += '\t'; break; 271 | case 'n': current += '\n'; break; 272 | case 'r': current += '\r'; break; 273 | case '\n': break; // escaped newline: ignore 274 | default: THROW(stream_.location(), "Unrecognized escape sequence: '\\" 275 | << (char)escape << "' (0x" << std::hex << escape << ")"); 276 | } 277 | break; 278 | } 279 | default: 280 | current += (char)stream_.get(); 281 | break; 282 | } 283 | } 284 | stream_.get(); 285 | if (!current.empty()) result.elements_.emplace_back(current); 286 | auto tok = Token(Token::STRING, where); 287 | tok.stringValue = result; 288 | return tok; 289 | } 290 | 291 | bool numIdInitChar(int c) { 292 | if ('0' <= c && c <= '9') return true; 293 | if (c == '-' || c == '+') return true; 294 | return false; 295 | } 296 | 297 | bool numIdChar(int c) { 298 | if (numIdInitChar(c)) return true; 299 | if (identChar(c)) return true; 300 | if (c == '.') return true; 301 | return false; 302 | } 303 | 304 | Token numId(char first, Location where) { 305 | static std::regex intRe("(\\+|-)?[0-9]+"); 306 | static std::regex doubleRe("[-+]?[0-9]+\\.?[0-9]*([eE][-+]?[0-9]+)?"); 307 | 308 | if (first == '0' and stream_.peek() == 'x') { 309 | stream_.get(); 310 | return hexLiteral(where); 311 | } 312 | 313 | Token token(Token::NUMID, first, where); 314 | while (numIdChar(stream_.peek())) token.append(stream_.get()); 315 | 316 | if (std::regex_match(token.value, intRe)) { 317 | token.type = Token::INT; 318 | char *end; 319 | token.intValue = ::strtoll(token.value.c_str(), &end, 10); 320 | if (end == token.value.c_str() + token.value.length()) { 321 | return token; 322 | } else { 323 | THROW(where, "Internal error: " 324 | << "integer regex matched, but strtoll() failed! '" 325 | << token.value << "'"); 326 | } 327 | } else if (std::regex_match(token.value, doubleRe)) { 328 | token.type = Token::DOUBLE; 329 | char *end; 330 | token.doubleValue = ::strtod(token.value.c_str(), &end); 331 | if (end == token.value.c_str() + token.value.length()) { 332 | return token; 333 | } else { 334 | THROW(where, "Internal error: " 335 | << "double regex matched, but strtod() failed! '" 336 | << token.value << "'"); 337 | } 338 | } 339 | 340 | return token; // it's a generic NUMID 341 | } 342 | 343 | int hexChar(int c) { 344 | if ('0' <= c && c <= '9') return c - '0'; 345 | if ('a' <= c && c <= 'f') return 10 + c - 'a'; 346 | if ('A' <= c && c <= 'F') return 10 + c - 'A'; 347 | return -1; 348 | } 349 | 350 | Token hexLiteral(Location where) { 351 | Token token(Token::INT, where); 352 | for (int n = hexChar(stream_.peek()); n != -1; 353 | n = hexChar(stream_.peek())) { 354 | token.intValue = token.intValue * 16 + n; 355 | token.append(stream_.get()); 356 | } 357 | token.doubleValue = token.intValue; 358 | return token; 359 | } 360 | 361 | Token ident(char first, Location where) { 362 | Token token(Token::IDENT, first, where); 363 | while (identChar(stream_.peek())) token.append(stream_.get()); 364 | return token; 365 | } 366 | }; 367 | 368 | } 369 | 370 | class ParserImpl { 371 | std::string fileName_; 372 | Lexer lex_; 373 | Token cur_; 374 | Token last_; 375 | 376 | public: 377 | ParserImpl(const std::string &fileName, std::istream &stream) 378 | : fileName_(fileName), lex_(stream) {} 379 | 380 | bool parseRuleset(ast::Nested &ast) { 381 | advance(); 382 | if (advanceIf(Token::CONTEXT)) ast.selector_ = parseContext(); 383 | while (cur_.type != Token::EOS) parseRule(ast); 384 | return true; 385 | } 386 | 387 | private: 388 | void advance() { last_ = cur_; cur_ = lex_.consume(); } 389 | 390 | bool advanceIf(Token::Type type) { 391 | if (cur_.type == type) { 392 | advance(); 393 | return true; 394 | } 395 | return false; 396 | } 397 | 398 | void expect(Token::Type type) { 399 | if (!advanceIf(type)) 400 | THROW(cur_.location, "Expected " << type << ", found " << cur_.type); 401 | } 402 | 403 | ast::SelectorBranch::P parseContext() { 404 | expect(Token::LPAREN); 405 | auto result = parseSelector(); 406 | expect(Token::RPAREN); 407 | advanceIf(Token::SEMI); 408 | return result; 409 | } 410 | 411 | void parseRule(ast::Nested &ast) { 412 | // the only ambiguity is between ident as start of a property setting 413 | // and ident as start of a selector, i.e.: 414 | // foo = bar 415 | // foo : bar = 'baz' 416 | // we can use the presence of '=' to disambiguate. parsePrimRule() performs 417 | // this lookahead without consuming the additional token. 418 | if (parsePrimRule(ast)) { 419 | advanceIf(Token::SEMI); 420 | return; 421 | } 422 | 423 | auto nested = std::make_unique(); 424 | nested->selector_ = parseSelector(); 425 | 426 | if (advanceIf(Token::COLON)) { 427 | if (!parsePrimRule(*nested)) 428 | THROW(cur_.location, 429 | "Expected @import, @constrain, or property setting"); 430 | advanceIf(Token::SEMI); 431 | } else if (advanceIf(Token::LBRACE)) { 432 | while (!advanceIf(Token::RBRACE)) parseRule(*nested); 433 | } else { 434 | THROW(cur_.location, "Expected ':' or '{' following selector"); 435 | } 436 | 437 | ast.addRule(std::move(nested)); 438 | } 439 | 440 | bool parsePrimRule(ast::Nested &ast) { 441 | switch (cur_.type) { 442 | case Token::IMPORT: 443 | advance(); 444 | expect(Token::STRING); 445 | if (last_.stringValue.interpolation()) 446 | THROW(last_.location, "Interpolation not allowed in import statements"); 447 | ast.addRule(std::make_unique(last_.stringValue.str())); 448 | return true; 449 | case Token::CONSTRAIN: 450 | advance(); 451 | ast.addRule(std::make_unique(parseSingleStep())); 452 | return true; 453 | case Token::OVERRIDE: 454 | advance(); 455 | parseProperty(ast, true); 456 | return true; 457 | case Token::IDENT: 458 | case Token::STRING: 459 | if (lex_.peek().type == Token::EQ) { 460 | parseProperty(ast, false); 461 | return true; 462 | } 463 | break; 464 | default: break; 465 | } 466 | return false; 467 | } 468 | 469 | void parseProperty(ast::Nested &ast, bool override) { 470 | auto prop = std::make_unique(); 471 | prop->name_ = parseIdent("property name"); 472 | prop->override_ = override; 473 | expect(Token::EQ); 474 | 475 | // we set the origin from the location of the equals sign. it's a bit 476 | // arbitrary, but it seems as good as anything. 477 | prop->origin_ = Origin(fileName_, last_.location.line); 478 | 479 | switch (cur_.type) { 480 | case Token::INT: 481 | prop->value_.setInt(cur_.intValue); break; 482 | case Token::DOUBLE: 483 | prop->value_.setDouble(cur_.doubleValue); break; 484 | case Token::STRING: 485 | prop->value_.setString(cur_.stringValue); break; 486 | case Token::NUMID: 487 | prop->value_.setString(StringVal(cur_.value)); break; 488 | case Token::IDENT: 489 | if (cur_.value == "true") prop->value_.setBool(true); 490 | else if (cur_.value == "false") prop->value_.setBool(false); 491 | else prop->value_.setString(StringVal(cur_.value)); 492 | break; 493 | default: 494 | THROW(cur_.location, cur_.type 495 | << " cannot occur here. Expected property value " 496 | << "(number, identifier, string, or boolean)"); 497 | } 498 | ast.addRule(std::move(prop)); 499 | advance(); 500 | return; 501 | } 502 | 503 | ast::SelectorBranch::P parseSelector() { 504 | auto leaf = parseSum(); 505 | if (advanceIf(Token::GT)) { 506 | return ast::SelectorBranch::descendant(leaf); 507 | } else { 508 | return ast::SelectorBranch::conjunction(leaf); 509 | } 510 | } 511 | 512 | ast::SelectorLeaf::P parseSum() { 513 | auto left = parseProduct(); 514 | while (advanceIf(Token::COMMA)) 515 | left = left->disjunction(left, parseProduct()); 516 | return left; 517 | } 518 | 519 | bool couldStartStep(const Token &token) { 520 | switch(token.type) { 521 | case Token::IDENT: 522 | case Token::STRING: 523 | case Token::LPAREN: 524 | return true; 525 | default: 526 | return false; 527 | } 528 | } 529 | 530 | ast::SelectorLeaf::P parseProduct() { 531 | auto left = parseTerm(); 532 | // term starts with ident or '(', which is enough to disambiguate... 533 | while (couldStartStep(cur_)) 534 | left = left->conjunction(left, parseTerm()); 535 | return left; 536 | } 537 | 538 | ast::SelectorLeaf::P parseTerm() { 539 | auto left = parseStep(); 540 | while (cur_.type == Token::GT) { 541 | // here we have to distinguish another step from a trailing '>'. again, 542 | // peeking for ident or '(' does the trick. 543 | if (!couldStartStep(lex_.peek())) return left; 544 | advance(); 545 | left = left->descendant(left, parseStep()); 546 | } 547 | return left; 548 | } 549 | 550 | ast::SelectorLeaf::P parseStep() { 551 | if (advanceIf(Token::LPAREN)) { 552 | auto result = parseSum(); 553 | expect(Token::RPAREN); 554 | return result; 555 | } else { 556 | return ast::SelectorLeaf::step(parseSingleStep()); 557 | } 558 | } 559 | 560 | Key parseSingleStep() { 561 | Key key; 562 | do { 563 | auto name = parseIdent("selector name"); 564 | key.addName(name); 565 | while (advanceIf(Token::DOT)) 566 | key.addValue(name, parseIdent("selector value")); 567 | } while (advanceIf(Token::SLASH)); 568 | return key; 569 | } 570 | 571 | std::string parseIdent(const char *what) { 572 | if (advanceIf(Token::IDENT)) return last_.value; 573 | if (advanceIf(Token::STRING)) { 574 | if (last_.stringValue.interpolation()) 575 | THROW(last_.location, "Interpolation not allowed in " << what); 576 | return last_.stringValue.str(); 577 | } 578 | THROW(cur_.location, cur_.type << " cannot occur here. Expected " << what); 579 | } 580 | }; 581 | 582 | Parser::Parser(CcsTracer &tracer) : tracer(tracer) {} 583 | Parser::~Parser() {} 584 | 585 | bool Parser::parseCcsStream(const std::string &fileName, std::istream &stream, 586 | ast::Nested &ast) { 587 | try { 588 | ParserImpl p(fileName, stream); 589 | if (p.parseRuleset(ast)) 590 | return true; 591 | std::ostringstream msg; 592 | msg << "Unknown error parsing " << fileName + "!"; 593 | tracer.onParseError(msg.str()); 594 | } catch (const parse_error &e) { 595 | std::ostringstream msg; 596 | msg << "Parse error at file " << fileName << ':' << e.where.line << ':' 597 | << e.where.column << ": " << e.what; 598 | tracer.onParseError(msg.str()); 599 | } 600 | return false; 601 | } 602 | 603 | } 604 | -------------------------------------------------------------------------------- /src/parser/parser.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "ccs/domain.h" 7 | #include "parser/ast.h" 8 | 9 | namespace ccs { 10 | 11 | class Node; 12 | 13 | class Parser { 14 | CcsTracer &tracer; 15 | 16 | public: 17 | Parser(CcsTracer &tracer); 18 | ~Parser(); 19 | 20 | bool parseCcsStream(const std::string &fileName, std::istream &stream, 21 | ast::Nested &ast); 22 | }; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/rule_builder.cpp: -------------------------------------------------------------------------------- 1 | #include "ccs/rule_builder.h" 2 | 3 | #include "dag/dag_builder.h" 4 | #include "parser/ast.h" 5 | 6 | namespace ccs { 7 | 8 | struct RuleBuilder::Impl : std::enable_shared_from_this { 9 | std::unique_ptr ast; 10 | 11 | Impl() : ast(new ast::Nested()) {} 12 | 13 | virtual ~Impl() {} 14 | 15 | void set(const std::string &name, const std::string &value) { 16 | auto def = std::make_unique(); 17 | def->name_ = name; 18 | def->value_.setString(StringVal(value)); 19 | ast->addRule(std::move(def)); 20 | } 21 | 22 | void add(std::unique_ptr child) 23 | { ast->addRule(std::move(child)); } 24 | std::shared_ptr select(const std::string &name, 25 | const std::vector &values); 26 | virtual std::shared_ptr &pop() = 0; 27 | }; 28 | 29 | struct RuleBuilder::Root : RuleBuilder::Impl { 30 | DagBuilder &dag; 31 | 32 | Root(DagBuilder &dag) : dag(dag) {} 33 | ~Root() { ast->addTo(dag.buildContext(), dag.buildContext()); } 34 | std::shared_ptr &pop() { throw std::runtime_error("unmatched pop()!"); } 35 | }; 36 | 37 | 38 | struct RuleBuilder::Child : RuleBuilder::Impl { 39 | std::shared_ptr parent; 40 | 41 | Child(const std::shared_ptr &parent, const std::string &name, 42 | const std::vector &values) : parent(parent) { 43 | Key key(name, values); 44 | ast->selector_ = ast::SelectorBranch::conjunction( 45 | ast::SelectorLeaf::step(key)); 46 | } 47 | 48 | ~Child() { parent->add(std::move(ast)); } 49 | std::shared_ptr &pop() { return parent; } 50 | }; 51 | 52 | std::shared_ptr RuleBuilder::Impl::select( 53 | const std::string &name, const std::vector &values) { 54 | return std::shared_ptr(new Child(shared_from_this(), name, values)); 55 | } 56 | 57 | RuleBuilder::RuleBuilder(DagBuilder &dag) : impl(new Root(dag)) {} 58 | 59 | RuleBuilder RuleBuilder::pop() { 60 | return RuleBuilder(impl->pop()); 61 | } 62 | 63 | RuleBuilder RuleBuilder::set(const std::string &name, 64 | const std::string &value) { 65 | impl->set(name, value); 66 | return *this; 67 | } 68 | 69 | RuleBuilder RuleBuilder::select(const std::string &name) { 70 | return select(name, std::vector()); 71 | } 72 | 73 | RuleBuilder RuleBuilder::select(const std::string &name, 74 | const std::vector &values) { 75 | return RuleBuilder(impl->select(name, values)); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/search_state.cpp: -------------------------------------------------------------------------------- 1 | #include "search_state.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "ccs/domain.h" 8 | #include "dag/key.h" 9 | #include "dag/node.h" 10 | #include "dag/specificity.h" 11 | #include "dag/tally.h" 12 | 13 | namespace ccs { 14 | 15 | SearchState::SearchState(const std::shared_ptr &parent, 16 | const Key &key) : 17 | parent(parent), 18 | tracer(parent->tracer), 19 | key(key), 20 | constraintsChanged(false) {} 21 | 22 | SearchState::SearchState(std::shared_ptr &root) : 23 | root(root), tracer(root->tracer()) { 24 | constraintsChanged = false; 25 | root->activate(Specificity(), *this); 26 | while (constraintsChanged) { 27 | constraintsChanged = false; 28 | root->getChildren(key, Specificity(), *this); 29 | } 30 | } 31 | 32 | SearchState::~SearchState() { 33 | for (auto it = tallyMap.begin(); it != tallyMap.end(); ++it) 34 | delete it->second; 35 | } 36 | 37 | std::shared_ptr SearchState::newChild( 38 | const std::shared_ptr &parent, const Key &key) { 39 | std::shared_ptr searchState(new SearchState(parent, key)); 40 | 41 | bool constraintsChanged; 42 | do { 43 | constraintsChanged = false; 44 | const SearchState *p = parent.get(); 45 | while (p) { 46 | constraintsChanged |= searchState->extendWith(*p); 47 | p = p->parent.get(); 48 | } 49 | } while (constraintsChanged); 50 | 51 | return searchState; 52 | } 53 | 54 | void SearchState::logRuleDag(std::ostream &os) const { 55 | if (parent) 56 | parent->logRuleDag(os); 57 | else 58 | os << Dumper(*root); 59 | } 60 | 61 | bool SearchState::extendWith(const SearchState &priorState) { 62 | constraintsChanged = false; 63 | for (auto it = priorState.nodes.cbegin(); it != priorState.nodes.cend(); ++it) 64 | it->first->getChildren(key, it->second, *this); 65 | return constraintsChanged; 66 | } 67 | 68 | const CcsProperty *SearchState::findProperty(const CcsContext &context, 69 | const std::string &propertyName) const { 70 | const CcsProperty *prop = doSearch(context, propertyName); 71 | if (prop) { 72 | tracer.onPropertyFound(context, propertyName, *prop); 73 | } else { 74 | tracer.onPropertyNotFound(context, propertyName); 75 | } 76 | return prop; 77 | } 78 | 79 | const CcsProperty *SearchState::doSearch(const CcsContext &context, 80 | const std::string &propertyName) const { 81 | auto it = properties.find(propertyName); 82 | if (it == properties.end()) { 83 | if (parent) return parent->doSearch(context, propertyName); 84 | return nullptr; 85 | } 86 | 87 | if (it->second.values.size() == 1) 88 | return *it->second.values.begin(); 89 | 90 | // it->second.values.size() > 1 91 | std::vector values(it->second.values.begin(), 92 | it->second.values.end()); 93 | std::sort(values.begin(), values.end(), 94 | [](const Property *l, const Property *r) { 95 | return l->propertyNumber() < r->propertyNumber(); 96 | }); 97 | 98 | std::vector baseValues(values.begin(), values.end()); 99 | tracer.onConflict(context, propertyName, baseValues); 100 | return values.back(); 101 | } 102 | 103 | const TallyState *SearchState::getTallyState(const AndTally *tally) const { 104 | auto it = tallyMap.find(tally); 105 | if (it != tallyMap.end()) return it->second; 106 | if (parent) return parent->getTallyState(tally); 107 | return tally->emptyState(); 108 | } 109 | 110 | void SearchState::setTallyState(const AndTally *tally, 111 | const TallyState *state) { 112 | const TallyState *&loc = tallyMap[tally]; 113 | if (loc) delete loc; 114 | loc = state; 115 | } 116 | 117 | void SearchState::append(std::ostream &out, bool isPrefix) const { 118 | if (parent) { 119 | parent->append(out, isPrefix || !key.empty()); 120 | } else if (key.empty() && !isPrefix) { 121 | out << ""; 122 | return; 123 | } 124 | 125 | if (!key.empty()) { 126 | out << key; 127 | if (isPrefix) out << " > "; 128 | } 129 | } 130 | 131 | std::ostream &operator<<(std::ostream &out, const SearchState &state) { 132 | state.append(out, false); 133 | return out; 134 | } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /src/search_state.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "ccs/domain.h" 9 | #include "dag/key.h" 10 | #include "dag/property.h" 11 | #include "dag/specificity.h" 12 | #include "graphviz.h" 13 | 14 | namespace ccs { 15 | 16 | class AndTally; 17 | class CcsProperty; 18 | class Node; 19 | class TallyState; 20 | 21 | struct PropertySetting { 22 | Specificity spec; 23 | bool override; 24 | std::set values; 25 | 26 | PropertySetting(Specificity spec, const Property *value) 27 | : spec(spec), 28 | override(value->override()) { 29 | values.insert(value); 30 | } 31 | 32 | bool better(const PropertySetting &that) const { 33 | if (override && !that.override) return true; 34 | if (!override && that.override) return false; 35 | return that.spec < spec; 36 | } 37 | }; 38 | 39 | class SearchState { 40 | // we need to be sure to retain a reference to the root of the dag. the 41 | // simplest way is to just make everything in 'nodes' shared_ptrs, but that 42 | // seems awfully heavy-handed. instead we'll just retain a direct reference 43 | // to the root in the root search state. the parent links are shared, so 44 | // this is sufficient. 45 | std::shared_ptr root; 46 | std::shared_ptr parent; 47 | std::map nodes; 48 | // the TallyStates here should rightly be unique_ptrs, but gcc 4.5 can't 49 | // support that in a map. bummer. 50 | std::map tallyMap; 51 | // cache of properties newly set in this context 52 | std::map properties; 53 | CcsTracer &tracer; 54 | Key key; 55 | bool constraintsChanged; 56 | 57 | SearchState(const std::shared_ptr &parent, const Key &key); 58 | 59 | public: 60 | SearchState(std::shared_ptr &root); 61 | SearchState(const SearchState &) = delete; 62 | SearchState &operator=(const SearchState &) = delete; 63 | ~SearchState(); 64 | 65 | static std::shared_ptr newChild( 66 | const std::shared_ptr &parent, const Key &key); 67 | 68 | void logRuleDag(std::ostream &os) const; 69 | 70 | bool extendWith(const SearchState &priorState); 71 | 72 | const CcsProperty *findProperty(const CcsContext &context, 73 | const std::string &propertyName) const; 74 | 75 | bool add(Specificity spec, const Node *node) { 76 | auto pr = nodes.insert(std::make_pair(node, spec)); 77 | 78 | if (pr.second) return true; 79 | 80 | auto &it = pr.first; 81 | if (it->second < spec) { 82 | it->second = spec; 83 | return true; 84 | } 85 | 86 | return false; 87 | } 88 | 89 | void constrain(const Key &constraints) 90 | { constraintsChanged |= key.addAll(constraints); } 91 | 92 | void cacheProperty(const std::string &propertyName, 93 | Specificity spec, const Property *property) { 94 | auto it = properties.find(propertyName); 95 | 96 | PropertySetting newSetting(spec, property); 97 | 98 | if (it == properties.end()) { 99 | // we don't have a local setting for this yet. 100 | auto parentProperty = parent ? parent->checkCache(propertyName) : nullptr; 101 | if (parentProperty) { 102 | if (parentProperty->better(newSetting)) 103 | // parent copy found, parent property better, leave local cache empty. 104 | return; 105 | 106 | // copy parent property into local cache. this is done solely to 107 | // support conflict detection. 108 | it = properties.insert(std::make_pair(propertyName, *parentProperty)) 109 | .first; 110 | } 111 | } 112 | 113 | if (it == properties.end()) { 114 | properties.insert(std::make_pair(propertyName, newSetting)); 115 | } else if (newSetting.better(it->second)) { 116 | // new property better than local cache. replace. 117 | it->second = newSetting; 118 | } else if (it->second.better(newSetting)) { 119 | // ignore 120 | } else { 121 | // new property has same specificity/override as existing... append. 122 | it->second.values.insert(property); 123 | } 124 | } 125 | 126 | const PropertySetting *checkCache(const std::string &propertyName) const { 127 | auto it = properties.find(propertyName); 128 | if (it != properties.end()) return &it->second; 129 | if (!parent) return nullptr; 130 | return parent->checkCache(propertyName); 131 | } 132 | 133 | const TallyState *getTallyState(const AndTally *tally) const; 134 | void setTallyState(const AndTally *tally, const TallyState *state); 135 | 136 | private: 137 | const CcsProperty *doSearch(const CcsContext &context, 138 | const std::string &propertyName) const; 139 | 140 | friend std::ostream &operator<<(std::ostream &, const SearchState &); 141 | void append(std::ostream &out, bool isPrefix) const; 142 | }; 143 | 144 | } 145 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if (COVERAGE) 2 | add_compile_options(--coverage -O0) 3 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage") 4 | endif () 5 | 6 | add_executable(Test 7 | ./acceptance_tests.cpp 8 | ./ccs_test.cpp 9 | ./context_test.cpp 10 | ./parser/parser_test.cpp) 11 | target_link_libraries(Test ccs gtest_main) 12 | add_test(NAME Tests 13 | COMMAND Test 14 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) 15 | file(COPY ../tests.txt DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) 16 | 17 | add_custom_target(unittest Test 18 | COMMENT "Running unittests\n\n" 19 | VERBATIM 20 | ) 21 | -------------------------------------------------------------------------------- /test/acceptance_tests.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "ccs/ccs.h" 9 | 10 | using ccs::ImportResolver; 11 | using ccs::CcsDomain; 12 | using ccs::CcsContext; 13 | using std::string; 14 | 15 | namespace { 16 | 17 | struct Assertion { 18 | typedef std::pair> NameVals; 19 | typedef std::vector> C; 20 | C constraints; 21 | string property; 22 | string value; 23 | }; 24 | 25 | struct CcsTestCase { 26 | string name; 27 | string ccs; 28 | std::vector assertions; 29 | }; 30 | 31 | std::ostream &operator<<(std::ostream &os, const CcsTestCase &test) { 32 | return os << test.name; 33 | } 34 | 35 | void expect(std::istream &reader, const std::string &expect) { 36 | std::string line; 37 | ASSERT_TRUE(std::getline(reader, line)); 38 | ASSERT_EQ(expect, line); 39 | } 40 | 41 | template 42 | void doUntil(std::istream &reader, const std::string &delim, F &&f) { 43 | std::string line; 44 | while (std::getline(reader, line) && line != delim) 45 | f(line); 46 | } 47 | 48 | std::string readUntil(std::istream &reader, const std::string &delim) { 49 | std::ostringstream result; 50 | doUntil(reader, delim, [&](auto &&str) { result << str << "\n"; }); 51 | return result.str(); 52 | } 53 | 54 | std::string trim(const std::string &str) { 55 | size_t first = str.find_first_not_of(' '); 56 | if (string::npos == first) return str; 57 | size_t last = str.find_last_not_of(' '); 58 | return str.substr(first, (last - first + 1)); 59 | } 60 | 61 | Assertion::NameVals parseElem(std::string elem) { 62 | Assertion::NameVals nameVals; 63 | size_t dot; 64 | auto seenName = false; 65 | do { 66 | dot = elem.find('.'); 67 | auto word = elem.substr(0, dot); 68 | elem = elem.substr(dot+1); 69 | if (seenName) { 70 | nameVals.second.push_back(trim(word)); 71 | } else { 72 | nameVals.first = trim(word); 73 | seenName = true; 74 | } 75 | } while (dot != string::npos); 76 | if (!seenName) 77 | throw std::runtime_error("Malformed assertion"); 78 | return nameVals; 79 | } 80 | 81 | Assertion::C::value_type parseStep(std::string step) { 82 | Assertion::C::value_type result; 83 | size_t slash; 84 | do { 85 | slash = step.find('/'); 86 | result.push_back(parseElem(step.substr(0, slash))); 87 | step = step.substr(slash+1); 88 | } while (slash != string::npos); 89 | return result; 90 | } 91 | 92 | Assertion parseAssertion(const std::string &line) { 93 | Assertion result; 94 | auto colon = line.find(':'); 95 | if (colon != string::npos) { 96 | auto ctx = line.substr(0, colon); 97 | size_t space; 98 | do { 99 | space = ctx.find(' '); 100 | result.constraints.push_back( 101 | parseStep(ctx.substr(0, space))); 102 | ctx = ctx.substr(space+1); 103 | } while (space != string::npos); 104 | } 105 | 106 | auto rest = line.substr(colon+1); 107 | auto eq = rest.find('='); 108 | if (eq == string::npos) 109 | throw std::runtime_error("Malformed assertion"); 110 | 111 | result.property = trim(rest.substr(0, eq)); 112 | result.value = trim(rest.substr(eq+1)); 113 | 114 | return result; 115 | } 116 | 117 | bool parseTestCase(std::istream &reader, 118 | std::vector &cases) { 119 | std::string name; 120 | if (!std::getline(reader, name)) return false; 121 | 122 | expect(reader, "---"); 123 | auto ccs = readUntil(reader, "---"); 124 | std::vector assertions; 125 | doUntil(reader, "===", [&](auto &&str) { 126 | assertions.emplace_back(parseAssertion(str)); 127 | }); 128 | expect(reader, ""); 129 | cases.push_back(CcsTestCase {name, ccs, assertions}); 130 | return true; 131 | } 132 | 133 | class AcceptanceTests : public ::testing::TestWithParam { 134 | public: 135 | static std::vector loadValues() { 136 | std::ifstream cases; 137 | cases.open("tests.txt", std::ifstream::in); 138 | return parseStream(cases); 139 | } 140 | 141 | static std::vector parseStream(std::istream &stream) { 142 | stream.unsetf(std::ios::skipws); 143 | 144 | std::vector result; 145 | while (!stream.eof() && parseTestCase(stream, result)); 146 | return result; 147 | 148 | // std::cout << "Parsing failed, stopped at: \"" << rest << "\"\n"; 149 | // throw std::runtime_error("Couldn't parse test cases."); 150 | } 151 | }; 152 | 153 | TEST_P(AcceptanceTests, Load) { 154 | const CcsTestCase &test = GetParam(); 155 | std::cout << "Running test: " << test.name << std::endl; 156 | CcsDomain ccs; 157 | std::istringstream input(test.ccs); 158 | ccs.loadCcsStream(input, "", ImportResolver::None); 159 | CcsContext root = ccs.build(); 160 | 161 | //ccs.logRuleDag(std::cout); 162 | //std::cout << std::endl; 163 | 164 | for (auto it = test.assertions.cbegin(); it != test.assertions.cend(); ++it) { 165 | CcsContext ctx = root; 166 | auto &cs = it->constraints; 167 | for (auto it2 = cs.cbegin(); it2 != cs.cend(); ++it2) { 168 | CcsContext::Builder b = ctx.builder(); 169 | for (auto it3 = it2->cbegin(); it3 != it2->cend(); ++it3) 170 | b.add(it3->first, it3->second); 171 | ctx = b.build(); 172 | } 173 | ASSERT_NO_THROW(EXPECT_EQ(it->value, ctx.getString(it->property))); 174 | } 175 | } 176 | 177 | } 178 | 179 | INSTANTIATE_TEST_SUITE_P(Run, 180 | AcceptanceTests, 181 | ::testing::ValuesIn(AcceptanceTests::loadValues())); 182 | -------------------------------------------------------------------------------- /test/ccs_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include "ccs/ccs.h" 7 | 8 | using namespace ccs; 9 | 10 | namespace { 11 | 12 | std::vector v(const std::string &s) { 13 | return std::vector{s}; 14 | } 15 | 16 | } 17 | 18 | 19 | TEST(CcsTest, Load) { 20 | CcsDomain ccs; 21 | std::istringstream input("foo.bar > baz.quux: frob = 'nitz'"); 22 | ccs.loadCcsStream(input, "", ImportResolver::None); 23 | CcsContext root = ccs.build(); 24 | CcsContext ctx = root.constrain("foo", v("bar")).constrain("baz", v("quux")); 25 | ASSERT_NO_THROW(EXPECT_EQ("nitz", ctx.getString("frob"))); 26 | } 27 | 28 | TEST(CcsTest, Memory) { 29 | CcsDomain *ccs = new CcsDomain(); 30 | std::istringstream input("foo.bar > baz.quux: frob = 'nitz'"); 31 | ccs->loadCcsStream(input, "", ImportResolver::None); 32 | CcsContext root = ccs->build(); 33 | delete ccs; 34 | CcsContext ctx = root.constrain("foo", v("bar")).constrain("baz", v("quux")); 35 | ASSERT_NO_THROW(EXPECT_EQ("nitz", ctx.getString("frob"))); 36 | } 37 | 38 | namespace { 39 | 40 | struct StringImportResolver : ccs::ImportResolver { 41 | std::string ccs; 42 | 43 | explicit StringImportResolver(const std::string &ccs) : ccs(ccs) {} 44 | 45 | virtual bool resolve(const std::string &, 46 | std::function load) { 47 | std::istringstream stream(ccs); 48 | return load(stream); 49 | } 50 | }; 51 | 52 | } 53 | 54 | TEST(CcsTest, Import) { 55 | CcsDomain ccs; 56 | std::istringstream input("@import 'foo'"); 57 | StringImportResolver ir("baz.bar: frob = 'nitz'"); 58 | ccs.loadCcsStream(input, "", ir); 59 | CcsContext ctx = ccs.build().constrain("baz", v("bar")); 60 | ASSERT_NO_THROW(EXPECT_EQ("nitz", ctx.getString("frob"))); 61 | } 62 | 63 | TEST(CcsTest, Types) { 64 | CcsDomain ccs; 65 | std::istringstream input("a = 3; a2 = 0xff; b = true; c = 1.23; d = 'ok'"); 66 | ccs.loadCcsStream(input, "", ImportResolver::None); 67 | CcsContext ctx = ccs.build(); 68 | 69 | ASSERT_NO_THROW(EXPECT_EQ(3, ctx.getInt("a"))); 70 | ASSERT_NO_THROW(EXPECT_EQ(3.0, ctx.getDouble("a"))); 71 | ASSERT_THROW(ctx.getBool("a"), bad_coercion); 72 | ASSERT_NO_THROW(EXPECT_EQ("3", ctx.getString("a"))); 73 | 74 | ASSERT_NO_THROW(EXPECT_EQ(255, ctx.getInt("a2"))); 75 | ASSERT_NO_THROW(EXPECT_EQ(255.0, ctx.getDouble("a2"))); 76 | ASSERT_THROW(ctx.getBool("a2"), bad_coercion); 77 | ASSERT_NO_THROW(EXPECT_EQ("255", ctx.getString("a2"))); 78 | 79 | ASSERT_THROW(ctx.getInt("b"), bad_coercion); 80 | ASSERT_THROW(ctx.getDouble("b"), bad_coercion); 81 | ASSERT_NO_THROW(EXPECT_EQ(true, ctx.getBool("b"))); 82 | ASSERT_NO_THROW(EXPECT_EQ("true", ctx.getString("b"))); 83 | 84 | ASSERT_THROW(ctx.getInt("c"), bad_coercion); 85 | ASSERT_NO_THROW(EXPECT_EQ(1.23, ctx.getDouble("c"))); 86 | ASSERT_THROW(ctx.getBool("c"), bad_coercion); 87 | ASSERT_NO_THROW(EXPECT_EQ("1.23", ctx.getString("c"))); 88 | 89 | ASSERT_THROW(ctx.getInt("d"), bad_coercion); 90 | ASSERT_THROW(ctx.getDouble("d"), bad_coercion); 91 | ASSERT_THROW(ctx.getBool("d"), bad_coercion); 92 | ASSERT_NO_THROW(EXPECT_EQ("ok", ctx.getString("d"))); 93 | } 94 | 95 | namespace { 96 | 97 | struct Foo { 98 | int x; 99 | }; 100 | 101 | std::istream &operator>>(std::istream &str, Foo &foo) { 102 | str >> foo.x; 103 | return str; 104 | } 105 | 106 | } 107 | 108 | TEST(CcsTest, TemplateGetters) { 109 | CcsDomain ccs; 110 | std::istringstream input("a = 3; b = 0x10"); 111 | ccs.loadCcsStream(input, "", ImportResolver::None); 112 | CcsContext ctx = ccs.build(); 113 | Foo foo = ctx.get("a"); 114 | EXPECT_EQ(3, foo.x); 115 | ASSERT_TRUE(ctx.getInto(foo, "b")); 116 | EXPECT_EQ(16, foo.x); 117 | } 118 | 119 | TEST(CcsTest, TemplatedBool) { 120 | CcsDomain ccs; 121 | std::istringstream input( 122 | "a = true; b = 'true'; c = false; d = 'false'; e = '1'"); 123 | ccs.loadCcsStream(input, "", ImportResolver::None); 124 | CcsContext ctx = ccs.build(); 125 | EXPECT_TRUE(ctx.get("a")); 126 | EXPECT_TRUE(ctx.get("b")); 127 | EXPECT_FALSE(ctx.get("c")); 128 | EXPECT_FALSE(ctx.get("d")); 129 | EXPECT_ANY_THROW(ctx.get("e")); 130 | EXPECT_TRUE(ctx.get("unset", true)); 131 | EXPECT_FALSE(ctx.get("unset", false)); 132 | } 133 | 134 | TEST(CcsTest, SameStep) { 135 | CcsDomain ccs; 136 | std::istringstream input("a.b.c d.e: test = 'nope'; a.b.c/d.e: test = 'yep'"); 137 | ccs.loadCcsStream(input, "", ImportResolver::None); 138 | CcsContext root = ccs.build(); 139 | auto ctx = root.builder() 140 | .add("a", std::vector{"b", "c"}) 141 | .add("d", std::vector{"e"}) 142 | .build(); 143 | EXPECT_EQ("yep", ctx.getString("test")); 144 | ctx = root 145 | .constrain("a", std::vector{"b", "c"}) 146 | .constrain("d", std::vector{"e"}); 147 | EXPECT_EQ("nope", ctx.getString("test")); 148 | } 149 | 150 | TEST(CcsTest, Interpolation) { 151 | CcsDomain ccs; 152 | setenv("VAR1", "bar", true); 153 | std::istringstream input("a = 'foo${VAR1} baz'; b = 'foo\\${VAR1} baz'"); 154 | ccs.loadCcsStream(input, "", ImportResolver::None); 155 | CcsContext ctx = ccs.build(); 156 | EXPECT_EQ("foobar baz", ctx.getString("a")); 157 | EXPECT_EQ("foo${VAR1} baz", ctx.getString("b")); 158 | setenv("VAR1", "quux", true); 159 | EXPECT_EQ("foobar baz", ctx.getString("a")); 160 | unsetenv("VAR1"); 161 | } 162 | 163 | TEST(CcsTest, InterpolationIsDoneAtLoadTime) { 164 | CcsDomain ccs; 165 | std::istringstream input("a = 'foo${VAR1} baz'"); 166 | ccs.loadCcsStream(input, "", ImportResolver::None); 167 | CcsContext ctx = ccs.build(); 168 | EXPECT_EQ("foo baz", ctx.getString("a")); 169 | setenv("VAR1", "bar", true); 170 | EXPECT_EQ("foo baz", ctx.getString("a")); 171 | unsetenv("VAR1"); 172 | } 173 | 174 | TEST(CcsTest, Escapes) { 175 | CcsDomain ccs; 176 | std::istringstream input( 177 | "a = '\\\"\\t\\n\\'s'; b = '\\\\'; c = 'Hi \\\nthere'"); 178 | ccs.loadCcsStream(input, "", ImportResolver::None); 179 | CcsContext ctx = ccs.build(); 180 | EXPECT_EQ("\"\t\n's", ctx.getString("a")); 181 | EXPECT_EQ("\\", ctx.getString("b")); 182 | EXPECT_EQ("Hi there", ctx.getString("c")); 183 | } 184 | 185 | TEST(CcsTest, DomainBuilder) { 186 | CcsDomain ccs; 187 | ccs.ruleBuilder() 188 | .set("a", "base") 189 | .select("a", v("b")).set("a", "123").pop() 190 | .select("c").set("b", "true").pop() 191 | .select("c", v("d")).set("b", "false").pop(); 192 | CcsContext ctx = ccs.build(); 193 | EXPECT_EQ("base", ctx.getString("a")); 194 | EXPECT_EQ("123", ctx.constrain("a", v("b")).getString("a")); 195 | EXPECT_EQ("base", ctx.constrain("c").getString("a")); 196 | } 197 | 198 | TEST(CcsTest, DomainBuilderNestedSelect) { 199 | CcsDomain ccs; 200 | ccs.ruleBuilder() 201 | .set("a", "base") 202 | .select("a", v("b")) 203 | .select("c", v("d")) 204 | .set("a", "123"); 205 | CcsContext ctx = ccs.build(); 206 | EXPECT_EQ("base", ctx.getString("a")); 207 | EXPECT_EQ("base", ctx.constrain("a", v("b")).getString("a")); 208 | EXPECT_EQ("base", ctx.constrain("c", v("d")).getString("a")); 209 | EXPECT_EQ("123", ctx.constrain("a", v("b")).constrain("c", v("d")) 210 | .getString("a")); 211 | } 212 | 213 | TEST(CcsTest, Coercion) { 214 | CcsDomain ccs; 215 | ccs.ruleBuilder() 216 | .set("int", "123") 217 | .set("double", "123.0") 218 | .set("bool", "true"); 219 | CcsContext ctx = ccs.build(); 220 | EXPECT_EQ(123, ctx.getInt("int")); 221 | EXPECT_EQ(123.0, ctx.getDouble("int")); 222 | EXPECT_EQ(123.0, ctx.getDouble("double")); 223 | EXPECT_EQ(true, ctx.getBool("bool")); 224 | } 225 | 226 | TEST(CcsTest, CoercionInterpolation) { 227 | setenv("F1", "23", true); 228 | setenv("F2", ".0", true); 229 | CcsDomain ccs; 230 | std::istringstream input( 231 | "int1 = '${F1}'; int2 = '1${F1}4'; double1 = '${F2}'; double2 = '1${F2}1'"); 232 | ccs.loadCcsStream(input, "", ImportResolver::None); 233 | CcsContext ctx = ccs.build(); 234 | EXPECT_EQ(23, ctx.getInt("int1")); 235 | EXPECT_EQ(1234, ctx.getInt("int2")); 236 | EXPECT_EQ(0, ctx.getDouble("double1")); 237 | EXPECT_EQ(1.01, ctx.getDouble("double2")); 238 | } 239 | 240 | TEST(CcsTest, DefaultVals) { 241 | CcsDomain ccs; 242 | CcsContext ctx = ccs.build(); 243 | EXPECT_TRUE(ctx.getBool("nope", true)); 244 | EXPECT_FALSE(ctx.getBool("nope", false)); 245 | EXPECT_EQ(1, ctx.getInt("nope", 1)); 246 | EXPECT_EQ("asdf", ctx.getString("nope", "asdf")); 247 | EXPECT_EQ(1.234, ctx.getDouble("nope", 1.234)); 248 | } 249 | 250 | namespace { 251 | 252 | struct FailingLogger : ccs::CcsLogger { 253 | virtual void info(const std::string &msg) { FAIL() << "info: " << msg; } 254 | virtual void warn(const std::string &msg) { FAIL() << "warn: " << msg; } 255 | virtual void error(const std::string &msg) { FAIL() << "error: " << msg; } 256 | }; 257 | 258 | } 259 | 260 | TEST(CcsTest, FalseConflict) { 261 | CcsDomain ccs(std::make_shared()); 262 | std::istringstream input("a b : foo = 'bar'"); 263 | ccs.loadCcsStream(input, "", ImportResolver::None); 264 | CcsContext ctx = ccs.build(); 265 | 266 | EXPECT_EQ("bar", ctx.constrain("a").constrain("b").getString("foo")); 267 | EXPECT_EQ("bar", ctx.constrain("a").constrain("b").constrain("a") 268 | .getString("foo")); 269 | } 270 | 271 | TEST(CcsTest, Graphviz) { 272 | CcsDomain ccs(std::make_shared()); 273 | ccs.ruleBuilder() 274 | .select("a").select("b").set("p1", "1").pop() 275 | .select("b").set("p2", "2"); 276 | 277 | std::ostringstream out; 278 | ccs.logRuleDag(out); 279 | 280 | // TODO is there anything useful that can be done here? the graph 281 | // itself is probably dependent on the hash order of pointers and thus 282 | // nondeterministic... here's something dumb w can verify... 283 | EXPECT_NE(std::string::npos, out.str().find("p1 = 1")); 284 | EXPECT_NE(std::string::npos, out.str().find("p2 = 2")); 285 | } 286 | -------------------------------------------------------------------------------- /test/context_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | #include "ccs/ccs.h" 8 | 9 | using namespace ccs; 10 | 11 | TEST(ContextTest, StringFormat) { 12 | CcsDomain ccs; 13 | std::istringstream input("b c : @constrain d.e"); 14 | ccs.loadCcsStream(input, "", ImportResolver::None); 15 | CcsContext root = ccs.build(); 16 | CcsContext ctx = root.constrain("c").constrain("b"); 17 | 18 | std::ostringstream os; 19 | os << ctx; 20 | EXPECT_EQ("c > b/d.e", os.str()); 21 | 22 | os.str(""); 23 | os << root; 24 | EXPECT_EQ("", os.str()); 25 | } 26 | 27 | TEST(ContextTest, StringFormatWithRootConstraint) { 28 | CcsDomain ccs; 29 | std::istringstream input("@constrain a.b; b c : @constrain d.e"); 30 | ccs.loadCcsStream(input, "", ImportResolver::None); 31 | CcsContext root = ccs.build(); 32 | CcsContext ctx = root.constrain("c").constrain("b"); 33 | std::ostringstream os; 34 | os << ctx; 35 | EXPECT_EQ("a.b > c > b/d.e", os.str()); 36 | } 37 | 38 | TEST(ContextTest, StringFormatWithEmptyContext) { 39 | CcsDomain ccs; 40 | std::istringstream input("b c : @constrain d.e"); 41 | ccs.loadCcsStream(input, "", ImportResolver::None); 42 | CcsContext root = ccs.build(); 43 | CcsContext ctx = root.constrain("c").builder().build().constrain("b"); 44 | std::ostringstream os; 45 | os << ctx; 46 | EXPECT_EQ("c > b/d.e", os.str()); 47 | } 48 | -------------------------------------------------------------------------------- /test/parser/parser_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "parser/ast.h" 6 | #include "parser/parser.h" 7 | 8 | using namespace ccs; 9 | 10 | namespace { 11 | 12 | struct P { 13 | std::shared_ptr trace; 14 | Parser parser; 15 | P() : 16 | trace(CcsTracer::makeLoggingTracer(CcsLogger::makeStdErrLogger())), 17 | parser(*trace) {} 18 | bool parse(const std::string &input) { 19 | std::istringstream str(input); 20 | ast::Nested ast; 21 | return parser.parseCcsStream("", str, ast); 22 | } 23 | 24 | std::pair parseAndReturnValue(const std::string &input) { 25 | std::istringstream str(input); 26 | ast::Nested ast; 27 | if (!parser.parseCcsStream("", str, ast)) return {false, Value()}; 28 | if (ast.rules_.size() != 1) return {false, Value()}; 29 | ast::PropDef *propDef = dynamic_cast(ast.rules_[0].get()); 30 | if (!propDef) return {false, Value()}; 31 | return {true, propDef->value_}; 32 | } 33 | 34 | bool parseDouble(const std::string &input, double &out) { 35 | auto pr = parseAndReturnValue(input); 36 | if (!pr.first) return false; 37 | try { 38 | out = pr.second.asDouble(); 39 | } catch (ccs::bad_coercion &) { 40 | return false; 41 | } 42 | return true; 43 | } 44 | 45 | bool parseInt(const std::string &input, int64_t &out) { 46 | auto pr = parseAndReturnValue(input); 47 | if (!pr.first) return false; 48 | try { 49 | out = pr.second.asInt(); 50 | } catch (ccs::bad_coercion &) { 51 | return false; 52 | } 53 | return true; 54 | } 55 | 56 | }; 57 | 58 | } 59 | 60 | TEST(ParserTest, BasicPhrases) { 61 | P parser; 62 | EXPECT_TRUE(parser.parse("")); 63 | EXPECT_TRUE(parser.parse("\n")); 64 | EXPECT_TRUE(parser.parse("@import 'file'")); 65 | EXPECT_TRUE(parser.parse("@context (foo.bar > baz)")); 66 | EXPECT_TRUE(parser.parse("@context (foo x.bar > baz >)")); 67 | EXPECT_FALSE(parser.parse("@context (foo x.bar > # baz >)")); 68 | EXPECT_TRUE(parser.parse("prop = 'val'")); 69 | EXPECT_TRUE(parser.parse("elem.id {}")); 70 | EXPECT_TRUE(parser.parse("elem.id {prop = 'val'}")); 71 | EXPECT_FALSE(parser.parse("elem.id {prop = @override 'hi'}")); 72 | EXPECT_TRUE(parser.parse("a.class.class blah > elem.id {prop=3}")); 73 | EXPECT_TRUE(parser.parse("a.class.class blah > elem.id {prop=2.3}")); 74 | EXPECT_TRUE(parser.parse("a.class.class blah > elem.id {prop=\"val\"}")); 75 | EXPECT_FALSE(parser.parse("a.class.class blah : elem.id { prop=\"val\" }")); 76 | EXPECT_FALSE(parser.parse("a.class.class blah elem.id prop=\"val\" }")); 77 | EXPECT_TRUE(parser.parse("a.class.class blah > elem.id {prop=0xAB12}")); 78 | EXPECT_TRUE(parser.parse("a.class.class blah > elem. id {prop=2.3}")); 79 | EXPECT_TRUE(parser.parse("a.class. class > elem.id {prop=\"val\"}")); 80 | EXPECT_FALSE(parser.parse("blah")); 81 | EXPECT_FALSE(parser.parse("@import 'file'; @context (foo)")); 82 | EXPECT_FALSE(parser.parse("@yuno?")); 83 | EXPECT_TRUE(parser.parse("@import 'file' ; @constrain foo")); 84 | EXPECT_TRUE(parser.parse("a.class { @import 'file' }")); 85 | EXPECT_FALSE(parser.parse("a.class { @context (foo) }")); 86 | EXPECT_TRUE(parser.parse("elem.id { prop = 'val'; prop2 = 31337 }")); 87 | EXPECT_TRUE(parser.parse("prop.'val'/a.foo/p.'hmm' { p = 1; }")); 88 | EXPECT_TRUE(parser.parse("a b > c d {p=1}")); 89 | EXPECT_TRUE(parser.parse("(a > b) (c > d) {p=1}")); 90 | EXPECT_TRUE(parser.parse("a > b > c {p=1}")); 91 | EXPECT_TRUE(parser.parse("a > (b c) > d {p=1}")); 92 | EXPECT_TRUE(parser.parse("a.\"foo\" 'bar' {'test' = 1}")); 93 | } 94 | 95 | TEST(ParserTest, Comments) { 96 | P parser; 97 | EXPECT_TRUE(parser.parse("// single line comment\n")); 98 | EXPECT_TRUE(parser.parse("// single line comment nonl")); 99 | EXPECT_TRUE(parser.parse("/* multi-line comment */")); 100 | EXPECT_TRUE(parser.parse("prop = /* comment */ 'val'")); 101 | EXPECT_TRUE(parser.parse("prop = /*/ comment */ 'val'")); 102 | EXPECT_TRUE(parser.parse("prop = /**/ 'val'")); 103 | EXPECT_TRUE(parser.parse("prop = /* comment /*nest*/ more */ 'val'")); 104 | EXPECT_TRUE(parser.parse("elem.id /* comment */ {prop = 'val'}")); 105 | EXPECT_FALSE(parser.parse("elem.id /* comment {prop = 'val'}")); 106 | EXPECT_TRUE(parser.parse("// comment\nelem { prop = 'val' prop = 'val' }")); 107 | } 108 | 109 | TEST(ParserTest, UglyAbutments) { 110 | P parser; 111 | EXPECT_FALSE(parser.parse("foo {p = 1x = 2}")); 112 | EXPECT_TRUE(parser.parse("foo {p = 1x p2 = 2}")); 113 | EXPECT_TRUE(parser.parse("foo {p = 'x'x = 2}")); 114 | EXPECT_FALSE(parser.parse("value=12asdf.foo {}")); 115 | EXPECT_TRUE(parser.parse("value=12asdf.foo nextsel {}")); 116 | EXPECT_TRUE(parser.parse("foo {p = 1 x = 2}")); 117 | EXPECT_TRUE(parser.parse("foo{p=1;x=2}")); 118 | EXPECT_FALSE(parser.parse("foo{@overridep=1}")); 119 | EXPECT_TRUE(parser.parse("foo{@override /*hi*/ p=1}")); 120 | EXPECT_TRUE(parser.parse("@import'asdf'")); 121 | EXPECT_FALSE(parser.parse("@constrainasdf")); 122 | EXPECT_TRUE(parser.parse( 123 | "@import 'asdf' \n ; \n @constrain asdf \n ; @import 'foo' ")); 124 | EXPECT_TRUE(parser.parse("@import /*hi*/ 'asdf'")); 125 | EXPECT_TRUE(parser.parse("env.foo/* some comment */{ }")); 126 | } 127 | 128 | TEST(ParserTest, SelectorSections) { 129 | P parser; 130 | EXPECT_TRUE(parser.parse("foo > { bar {}}")); 131 | EXPECT_TRUE(parser.parse("foo > { bar > baz {}}")); 132 | EXPECT_TRUE(parser.parse("bar > baz {}")); 133 | EXPECT_TRUE(parser.parse("bar baz {}")); 134 | } 135 | 136 | TEST(ParserTest, Constraints) { 137 | P parser; 138 | EXPECT_TRUE(parser.parse("a.b: @constrain a.c")); 139 | } 140 | 141 | TEST(ParserTest, StringsWithInterpolation) { 142 | P parser; 143 | EXPECT_TRUE(parser.parse("a = 'hi'")); 144 | EXPECT_FALSE(parser.parse("a = 'hi")); 145 | EXPECT_FALSE(parser.parse("a = 'hi\\")); 146 | EXPECT_FALSE(parser.parse("a = 'hi\\4 there'")); 147 | EXPECT_TRUE(parser.parse("a = 'h${there}i'")); 148 | EXPECT_FALSE(parser.parse("a = 'h$there}i'")); 149 | EXPECT_FALSE(parser.parse("a = 'h${t-here}i'")); 150 | } 151 | 152 | TEST(ParserTest, ParsesIntegers) { 153 | P parser; 154 | int64_t v64 = 0; 155 | ASSERT_TRUE(parser.parseInt("value = 100", v64)); 156 | EXPECT_EQ(100, v64); 157 | ASSERT_TRUE(parser.parseInt("value = 0", v64)); 158 | EXPECT_EQ(0, v64); 159 | ASSERT_TRUE(parser.parseInt("value = -0", v64)); 160 | EXPECT_EQ(0, v64); 161 | ASSERT_TRUE(parser.parseInt("value = -100", v64)); 162 | EXPECT_EQ(-100, v64); 163 | ASSERT_TRUE(parser.parseInt("value = 0x1a", v64)); 164 | EXPECT_EQ(26, v64); 165 | ASSERT_TRUE(parser.parseInt("value = 0x1F", v64)); 166 | EXPECT_EQ(31, v64); 167 | ASSERT_TRUE(parser.parseInt("value = 0x0", v64)); 168 | EXPECT_EQ(0, v64); 169 | ASSERT_FALSE(parser.parseInt("value = 100.123", v64)); 170 | ASSERT_TRUE(parser.parseInt("value = '100'", v64)); 171 | EXPECT_EQ(100, v64); 172 | } 173 | 174 | TEST(ParserTest, ParsesDoubles) { 175 | P parser; 176 | double vDouble = 0.0; 177 | ASSERT_TRUE(parser.parseDouble("value = 100.", vDouble)); 178 | EXPECT_DOUBLE_EQ(100., vDouble); 179 | ASSERT_TRUE(parser.parseDouble("value = 100.0000", vDouble)); 180 | EXPECT_DOUBLE_EQ(100., vDouble); 181 | ASSERT_TRUE(parser.parseDouble("value = 0.0000", vDouble)); 182 | EXPECT_DOUBLE_EQ(0., vDouble); 183 | ASSERT_TRUE(parser.parseDouble("value = -0.0000", vDouble)); 184 | EXPECT_DOUBLE_EQ(0., vDouble); 185 | ASSERT_TRUE(parser.parseDouble("value = 1.0e-2", vDouble)); 186 | EXPECT_DOUBLE_EQ(0.01, vDouble); 187 | ASSERT_TRUE(parser.parseDouble("value = 1.0E-2", vDouble)); 188 | EXPECT_DOUBLE_EQ(0.01, vDouble); 189 | ASSERT_TRUE(parser.parseDouble("value = 1e-2", vDouble)); 190 | EXPECT_DOUBLE_EQ(0.01, vDouble); 191 | ASSERT_TRUE(parser.parseDouble("value = 1E-2", vDouble)); 192 | EXPECT_DOUBLE_EQ(0.01, vDouble); 193 | ASSERT_TRUE(parser.parseDouble("value = 100", vDouble)); 194 | EXPECT_DOUBLE_EQ(100, vDouble); 195 | ASSERT_TRUE(parser.parseDouble("value = '100.0'", vDouble)); 196 | EXPECT_DOUBLE_EQ(100, vDouble); 197 | } 198 | -------------------------------------------------------------------------------- /tests.txt: -------------------------------------------------------------------------------- 1 | Basic test 2 | --- 3 | foo.bar > baz.quux: frob = 'nitz' 4 | --- 5 | foo.bar baz.quux: frob = nitz 6 | === 7 | 8 | Basic conjunction 9 | --- 10 | frob = 'nope' 11 | a b: frob = 'nitz' 12 | --- 13 | a: frob = nope 14 | b: frob = nope 15 | a b: frob = nitz 16 | b a: frob = nitz 17 | === 18 | 19 | Best before closest 20 | --- 21 | first.id: test = "correct" 22 | first second: test = "incorrect" 23 | --- 24 | first.id second: test = correct 25 | === 26 | 27 | ID match has greater specificity 28 | --- 29 | second.id: test = "correct" 30 | first second: test = "incorrect" 31 | --- 32 | first second.id: test = correct 33 | === 34 | 35 | Tied specificities 36 | --- 37 | second.class2: test = "foo" 38 | second.class1: test = "correct" 39 | --- 40 | first second.class1.class2: test = correct 41 | === 42 | 43 | Complex tie 44 | --- 45 | bar.class2 : test1 = "incorrect" 46 | bar.class1 : test1 = "correct" 47 | bar.class1 foo : test2 = "incorrect 1" 48 | bar.class2 foo : test2 = "correct" 49 | bar foo : test2 = "incorrect 2" 50 | --- 51 | bar.class1.class2: test1 = correct 52 | bar.class1.class2 foo: test2 = correct 53 | === 54 | 55 | Conjunction specificities 56 | --- 57 | (a > b.b, a > b > c) c { test = 'correct' } 58 | c.c { test = 'incorrect' } 59 | --- 60 | a b.b c.c: test = correct 61 | === 62 | 63 | Disjunction specificities 64 | --- 65 | a > b, a > b > y.d { test = 'correct1' } 66 | x.c { test = 'correct2' } 67 | a f.f > h b, g > h { test = 'correct3' } 68 | h.k { test = 'incorrect' } 69 | --- 70 | a b: test = correct1 71 | a b x.c: test = correct2 72 | a b x.c y.d: test = correct1 73 | a b x.c y.d f.f g h.k: test = correct3 74 | === 75 | 76 | Conjunction 77 | --- 78 | a b { test = 'correct1' } 79 | a c c.class1 { test = 'correct2' } 80 | a b > { d { test = 'correct3' } } 81 | --- 82 | a b: test = correct1 83 | a b c.class1: test = correct2 84 | a b d: test = correct3 85 | === 86 | 87 | Disjunction 88 | --- 89 | a > c, c.b { test = 'correct1' } 90 | a > c > c { test = 'correct2' } 91 | --- 92 | a b c: test = correct1 93 | a b c c: test = correct2 94 | a b c c c.b: test = correct1 95 | === 96 | 97 | Context 98 | --- 99 | @context (a >) 100 | test = 'correct1' 101 | b > a: test = 'correct2' 102 | --- 103 | b a: test = correct1 104 | b a b a: test = correct2 105 | === 106 | 107 | Trailing combinator 108 | --- 109 | a: test = "top" 110 | a { b >: test = "bottom" } 111 | 112 | test2 = "top" 113 | a > { b { c > { d { e: test2 = "bottom" }}}} 114 | --- 115 | b a: test = bottom 116 | b a e c b d: test2 = bottom 117 | b a e c d: test2 = top 118 | === 119 | 120 | Order-dependent disjunction 121 | --- 122 | a, b, d { x = 1 } 123 | b { x = 2 } 124 | c { x = 3 } 125 | 126 | (d, e) > f { y = 1 } 127 | d > f { y = 2 } 128 | --- 129 | b: x = 2 130 | a c d: x = 3 131 | d f: y = 2 132 | === 133 | 134 | Override 135 | --- 136 | a.b.c d : test = 'incorrect' 137 | a : @override test = 'correct' 138 | --- 139 | a.b.c d: test = correct 140 | === 141 | 142 | Same step 143 | --- 144 | a.b.c d.e: test = "nope" 145 | a.b.c/d.e: test = "yep" 146 | --- 147 | a.b.c d.e: test = nope 148 | a.b.c/d.e: test = yep 149 | === 150 | 151 | Constraints in CCS 152 | --- 153 | a.b: @constrain a.c 154 | a.b.c: @constrain d.e 155 | a.b.c/d.e: test = "correct" 156 | --- 157 | a.b: test = correct 158 | === 159 | 160 | Constraints at top level 161 | --- 162 | @constrain a 163 | test = "wrong" 164 | a: test = "right" 165 | --- 166 | test = right 167 | === 168 | 169 | Chained constraints at top level 170 | --- 171 | @constrain a 172 | test = "wrong" 173 | a: @constrain b.c 174 | b.c: test = "right" 175 | --- 176 | test = right 177 | === 178 | 179 | Valueless constraint 180 | --- 181 | a.b: @constrain c 182 | c: test = "correct" 183 | --- 184 | a.b: test = correct 185 | === 186 | 187 | Specific vs lowest 188 | --- 189 | a.b c: test = "correct" 190 | d: test = "incorrect" 191 | --- 192 | a.b c d: test = correct 193 | === 194 | 195 | Specific vs lowest 2 196 | --- 197 | a.b: test = "correct" 198 | c: test = "incorrect" 199 | --- 200 | a.b c d: test = correct 201 | === 202 | 203 | Multiple chained constraints and conjunction 204 | --- 205 | test = "wrong" 206 | a: @constrain b 207 | a b: @constrain c 208 | a b c: test = "right" 209 | --- 210 | a: test = right 211 | === 212 | 213 | Bogus conflicts 214 | --- 215 | a: @constrain b 216 | a: @constrain c 217 | a: test2 = "right" 218 | --- 219 | a: test2 = right 220 | === 221 | 222 | Multiple chained constraints at root 223 | --- 224 | @constrain a 225 | a: @constrain b 226 | a b: @constrain c 227 | a b c: test = "right" 228 | --- 229 | test = right 230 | === 231 | 232 | Test case from hippo... 233 | --- 234 | expect = true 235 | env.a env.b : foo = 123 236 | env.lab, env.dev { 237 | expect = false 238 | env.a, env.b, env.c : expect = true 239 | } 240 | --- 241 | env.a env.dev: expect = true 242 | env.dev: expect = false 243 | env.prod: expect = true 244 | === 245 | 246 | Identical conj and disj 247 | --- 248 | a b : foo = and 249 | a, b : foo = or 250 | --- 251 | a: foo = or 252 | b: foo = or 253 | a b: foo = and 254 | === 255 | 256 | Duplicated constraint builds incorrect dag 257 | --- 258 | b a {} 259 | a a : foo = here 260 | --- 261 | a: foo = here 262 | === 263 | 264 | --------------------------------------------------------------------------------