OpenModelica / modelica-language-server

A VS Code language server extension for Modelica.
Other
5 stars 2 forks source link

How to handle multiple Modelica files #7

Closed AnHeuermann closed 5 months ago

AnHeuermann commented 9 months ago

Currently the language server will create a tree for a single opened Modelica file. At the moment there is no way to query information from other trees of other files.

Many Modelica packages are not stored in a single file but a folder structure with many smaller Modelica files. See for example the MSL.

There are multiple ways a language server could handle packages:

  1. Generate one giant tree with tree-sitter for one package. Tree-Sitter should be good enough to only update the changed part of a library and don't need to rebuild all of the tree, so performance-wise this should be okay. But the size could become very large.
  2. Have a separate tree for each file

How to auto-explore packages to build up a tree / multiple trees?

  1. Add sub-trees for needed sub-packages on demand.
  2. Explore all of the package at once.

How to query information from other packages? How should the language server know what packages are available and where to find the Modelica files?

AnHeuermann commented 8 months ago

Single Tree vs. Multiple Trees

Usually a tree only contains the line numbers and not the file itself. And passing multiple files to the parser will result in the generation of multiple trees:

tree-sitter parse "/home/USERNAME/.openmodelica/libraries/Modelica 4.0.0+maint.om/package.mo" "/home/USERNAME/.openmodelica/libraries/Modelica 4.0.0+maint.om/Icons.mo"
(stored_definitions [0, 0] - [9040, 0]
  [...]
(stored_definitions [0, 0] - [822, 0]
  [...]

The Modelica Standard Library might be a bit too large to begin with. So let's create a smaller Modelica library:

MyLibrary
├── Examples
│   ├── M.mo
│   ├── package.mo
│   └── package.order
├── package.mo
└── package.order

In the end it all boils down on how to handle within:

within MyLibrary.Examples;

model M "MWE Modelica Model"
  Real x(start = 1.0, fixed = true);
equation
  der(x) = -0.5*x;
end M;

Trees

(stored_definitions [0, 0] - [2, 0]
  storedDefinitions: (stored_definition [0, 0] - [1, 14]
    classDefinition: (class_definition [0, 0] - [1, 13]
      classPrefixes: (class_prefixes [0, 0] - [0, 7])
      classSpecifier: (long_class_specifier [0, 8] - [1, 13]
        identifier: (IDENT [0, 8] - [0, 17])
        descriptionString: (description_string [0, 18] - [0, 39]
          value: (STRING [0, 18] - [0, 39]))
        endIdentifier: (IDENT [1, 4] - [1, 13])))))
(stored_definitions [0, 0] - [4, 0]
  (within_clause [0, 0] - [0, 17]
    name: (name [0, 7] - [0, 16]
      identifier: (IDENT [0, 7] - [0, 16])))
  storedDefinitions: (stored_definition [2, 0] - [3, 13]
    classDefinition: (class_definition [2, 0] - [3, 12]
      classPrefixes: (class_prefixes [2, 0] - [2, 7])
      classSpecifier: (long_class_specifier [2, 8] - [3, 12]
        identifier: (IDENT [2, 8] - [2, 16])
        endIdentifier: (IDENT [3, 4] - [3, 12])))))
(stored_definitions [0, 0] - [7, 0]
  (within_clause [0, 0] - [0, 26]
    name: (name [0, 7] - [0, 25]
      qualifier: (name [0, 7] - [0, 16]
        identifier: (IDENT [0, 7] - [0, 16]))
      identifier: (IDENT [0, 17] - [0, 25])))
  storedDefinitions: (stored_definition [2, 0] - [6, 6]
    classDefinition: (class_definition [2, 0] - [6, 5]
      classPrefixes: (class_prefixes [2, 0] - [2, 5])
      classSpecifier: (long_class_specifier [2, 6] - [6, 5]
        identifier: (IDENT [2, 6] - [2, 7])
        descriptionString: (description_string [2, 8] - [2, 28]
          value: (STRING [2, 8] - [2, 28]))
        (element_list [3, 2] - [3, 36]
          element: (named_element [3, 2] - [3, 35]
            componentClause: (component_clause [3, 2] - [3, 35]
              typeSpecifier: (type_specifier [3, 2] - [3, 6]
                name: (name [3, 2] - [3, 6]
                  identifier: (IDENT [3, 2] - [3, 6])))
              componentDeclarations: (component_list [3, 7] - [3, 35]
                componentDeclaration: (component_declaration [3, 7] - [3, 35]
                  declaration: (declaration [3, 7] - [3, 35]
                    identifier: (IDENT [3, 7] - [3, 8])
                    modification: (modification [3, 8] - [3, 35]
                      classModification: (class_modification [3, 8] - [3, 35]
                        arguments: (argument_list [3, 9] - [3, 34]
                          argument: (element_modification [3, 9] - [3, 20]
                            name: (name [3, 9] - [3, 14]
                              identifier: (IDENT [3, 9] - [3, 14]))
                            modification: (modification [3, 15] - [3, 20]
                              expression: (expression [3, 17] - [3, 20]
                                (simple_expression [3, 17] - [3, 20]
                                  (primary_expression [3, 17] - [3, 20]
                                    (literal_expression [3, 17] - [3, 20]
                                      (unsigned_real_literal_expression [3, 17] - [3, 20]
                                        (UNSIGNED_REAL [3, 17] - [3, 20]))))))))
                          argument: (element_modification [3, 22] - [3, 34]
                            name: (name [3, 22] - [3, 27]
                              identifier: (IDENT [3, 22] - [3, 27]))
                            modification: (modification [3, 28] - [3, 34]
                              expression: (expression [3, 30] - [3, 34]
                                (simple_expression [3, 30] - [3, 34]
                                  (primary_expression [3, 30] - [3, 34]
                                    (literal_expression [3, 30] - [3, 34]
                                      (logical_literal_expression [3, 30] - [3, 34]))))))))))))))))
        (equation_section [4, 0] - [5, 18]
          equations: (equation_list [5, 2] - [5, 18]
            equation: (simple_equation [5, 2] - [5, 17]
              expression1: (simple_expression [5, 2] - [5, 8]
                (primary_expression [5, 2] - [5, 8]
                  (function_application [5, 2] - [5, 8]
                    arguments: (function_call_args [5, 5] - [5, 8]
                      arguments: (function_arguments [5, 6] - [5, 7]
                        argument: (expression [5, 6] - [5, 7]
                          (simple_expression [5, 6] - [5, 7]
                            (primary_expression [5, 6] - [5, 7]
                              (component_reference [5, 6] - [5, 7]
                                identifier: (IDENT [5, 6] - [5, 7]))))))))))
              expression2: (expression [5, 11] - [5, 17]
                (simple_expression [5, 11] - [5, 17]
                  (binary_expression [5, 11] - [5, 17]
                    operand1: (simple_expression [5, 11] - [5, 15]
                      (unary_expression [5, 11] - [5, 15]
                        operand: (simple_expression [5, 12] - [5, 15]
                          (primary_expression [5, 12] - [5, 15]
                            (literal_expression [5, 12] - [5, 15]
                              (unsigned_real_literal_expression [5, 12] - [5, 15]
                                (UNSIGNED_REAL [5, 12] - [5, 15])))))))
                    operand2: (simple_expression [5, 16] - [5, 17]
                      (primary_expression [5, 16] - [5, 17]
                        (component_reference [5, 16] - [5, 17]
                          identifier: (IDENT [5, 16] - [5, 17]))))))))))
        endIdentifier: (IDENT [6, 4] - [6, 5])))))

Conclusion

Having multiple trees shouldn't be much of an issue. If we need some information for a different file we can see via within and the package.order files what's in scope and look in our list of stored trees to find the specific information. To check if all trees are still up to date we would only need to check the last modification date of the file. If something changes there is no need to update all nodes of all trees.