diff --git a/.github/workflows/Tests.yml b/.github/workflows/Tests.yml index a625cd9..d892d89 100644 --- a/.github/workflows/Tests.yml +++ b/.github/workflows/Tests.yml @@ -21,5 +21,16 @@ concurrency: jobs: tests: name: "Tests" + strategy: + fail-fast: false + matrix: + group: + - Core + - Quality + version: + - '1' uses: "SciML/.github/.github/workflows/tests.yml@v1" + with: + group: "${{ matrix.group }}" secrets: "inherit" + diff --git a/Project.toml b/Project.toml index a112350..83a1cb9 100644 --- a/Project.toml +++ b/Project.toml @@ -1,19 +1,21 @@ name = "BaseModelica" uuid = "a17d5099-185d-4ff5-b5d3-51aa4569e56d" authors = ["jClugstor and contributors"] -version = "1.0.0" +version = "1.1.0" [deps] +MLStyle = "d8e11817-5142-5d16-987a-aa16d5891078" ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78" ParserCombinator = "fae87a5f-d1ad-5cf0-8f61-c941e1580b46" [compat] Aqua = "0.8" -ModelingToolkit = "8.75, 9" +MLStyle = "0.4.17" +ModelingToolkit = "9" +ParserCombinator = "2" SafeTestsets = "0.1" Test = "1.10" julia = "1.10" -ParserCombinator = "2" [extras] Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" diff --git a/README.md b/README.md index b0eadc0..2350f78 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,9 @@ [![ColPrac: Contributor's Guide on Collaborative Practices for Community Packages](https://img.shields.io/badge/ColPrac-Contributor%27s%20Guide-blueviolet)](https://github.com/SciML/ColPrac) [![SciML Code Style](https://img.shields.io/static/v1?label=code%20style&message=SciML&color=9558b2&labelColor=389826)](https://github.com/SciML/SciMLStyle) - A parser for the [Base Modelica](https://github.com/modelica/ModelicaSpecification/tree/MCP/0031/RationaleMCP/0031) format. Contains utilities to parse Base Modelica model files in to Julia objects, and to convert Base Modelica models to [ModelingToolkit](https://docs.sciml.ai/ModelingToolkit/stable/) models. -So far only very simple Base Modelica models are supported. Only models with real parameters, real variables, and equations consisting of simple arithmetic equations and first order derivatives are supported. Support for the rest of the BaseModelica specification is planned to be added in the future. +So far only very simple Base Modelica models are supported. Only models with real parameters, real variables, and equations consisting of simple arithmetic equations and first order derivatives are supported. Support for the rest of the BaseModelica specification is planned to be added in the future. ## Installation @@ -25,6 +24,7 @@ Pkg.add("BaseModelica"); ``` # Example + A Base Modelica model is in the file `ExampleFirstOrder.mo`. Inside of the file is a Base Modelica model specifying a simple first order linear differential equation: ``` diff --git a/src/BaseModelica.jl b/src/BaseModelica.jl index fd32553..65ab881 100644 --- a/src/BaseModelica.jl +++ b/src/BaseModelica.jl @@ -2,60 +2,11 @@ module BaseModelica using ModelingToolkit using ParserCombinator - -""" -Holds the name of the package, the models in the package, and eventually BaseModelica records. -""" -struct BaseModelicaPackage - name::Any - model::Any -end - -""" -Represents a BaseModelica model. -""" -struct BaseModelicaModel - name::Any - description::Any - parameters::Any - variables::Any - equations::Any - initialequations::Any -end - -struct BaseModelicaParameter - type::Any - name::Any - value::Any - description::Any -end - -struct BaseModelicaVariable - type::Any - name::Any - input_or_output::Any - description::Any -end - -struct BaseModelicaEquation - lhs::Any - rhs::Any - description::Any -end - -struct BaseModelicaInitialEquation - lhs::Any - rhs::Any - description::Any -end - -# needed to parse derivatives in equations correctly -@variables t -der = Differential(t) +using MLStyle #Includes include("parser.jl") -include("conversion.jl") +include("evaluator.jl") """ parse_basemodelica(filename::String)::ODESystem @@ -69,7 +20,7 @@ parse_basemodelica("testfiles/NewtonCoolingBase.mo") """ function parse_basemodelica(filename::String) package = parse_file(filename) - baseModelica_to_ModelingToolkit(package.model) + baseModelica_to_ModelingToolkit(package) end export parse_basemodelica diff --git a/src/conversion.jl b/src/conversion.jl deleted file mode 100644 index faeac58..0000000 --- a/src/conversion.jl +++ /dev/null @@ -1,49 +0,0 @@ -function baseModelica_to_ModelingToolkit(model::BaseModelicaModel) - param_names = [Symbol(param.name) for param in model.parameters] - param_descs = [param.description for param in model.parameters] - parameters = [only(@parameters $name [description = "$desc"]) - for (name, desc) in zip(param_names, param_descs)] - - var_names = [Symbol(var.name) for var in model.variables] - var_descs = [var.description for var in model.variables] - variables = [only(@variables $name [description = "$desc"]) - for (name, desc) in zip(var_names, var_descs)] - var_funcs = [only(@variables $name(t) [description = "$desc"]) - for (name, desc) in zip(var_names, var_descs)] - - equ_lhses = [replace(equ.lhs, "\'" => "") for equ in model.equations] - equ_rhses = [replace(equ.rhs, "\'" => "") for equ in model.equations] - - init_lhses = [replace(equ.lhs, "\'" => "") for equ in model.initialequations] - init_rhses = [replace(equ.rhs, "\'" => "") for equ in model.initialequations] - - param_defaults = parameters .=> - [param.value == "" ? nothing : parse(Float64, param.value) - for param in model.parameters] - - init_subst_dict = Dict(vcat(param_defaults, variables .=> var_funcs)) - - init_lhs_exprs = [substitute(Symbolics.parse_expr_to_symbolic(Meta.parse(lhs), Main), - init_subst_dict) for lhs in init_lhses] - init_rhs_exprs = [substitute(Symbolics.parse_expr_to_symbolic(Meta.parse(lhs), Main), - init_subst_dict) for lhs in init_rhses] - - inits = init_lhs_exprs .=> substitute(init_rhs_exprs, init_subst_dict) - - defaults_and_inits = Dict(vcat(inits, param_defaults)) - - subst_dict = Dict(vcat(parameters .=> parameters, variables .=> var_funcs)) - - lhs_exprs = [substitute( - Symbolics.parse_expr_to_symbolic(Meta.parse(lhs), BaseModelica), - subst_dict) for lhs in equ_lhses] - rhs_exprs = [substitute( - Symbolics.parse_expr_to_symbolic(Meta.parse(rhs), BaseModelica), - subst_dict) for rhs in equ_rhses] - - eqs = [lhs ~ rhs for (lhs, rhs) in zip(lhs_exprs, rhs_exprs)] - - model_name = Symbol(model.name) - sys = structural_simplify(ODESystem( - eqs, t, var_funcs, parameters; defaults = defaults_and_inits, name = model_name)) -end diff --git a/src/evaluator.jl b/src/evaluator.jl new file mode 100644 index 0000000..23bbfc8 --- /dev/null +++ b/src/evaluator.jl @@ -0,0 +1,154 @@ +# function to convert "AST" to ModelingToolkit +using ModelingToolkit: t_nounits as t, D_nounits as D + +@data BaseModelicaRuntimeTypes begin + RTRecord() + RTClass() + RTFunction() + #built in classes + RTReal() + RTInteger() + RTBoolean() + RTString() +end + +function eval_AST(expr::BaseModelicaExpr) + let f = eval_AST + @match expr begin + BaseModelicaNumber(val) => val + BaseModelicaFactor(base, exp) => (f(base))^f(exp) + BaseModelicaSum(left, right) => (f(left)) + (f(right)) + BaseModelicaMinus(left, right) => f(left) - f(right) + BaseModelicaProd(left, right) => f(left) * f(right) + BaseModelicaDivide(left, right) => f(left) / f(right) + BaseModelicaNot(relation) => !(f(relation)) + BaseModelicaAnd(left, right) => f(left) && f(right) + BaseModelicaOr(left, right) => f(left) || f(right) + BaseModelicaLEQ(left, right) => f(left) <= f(right) + BaseModelicaGEQ(left, right) => f(left) >= f(right) + BaseModelicaLessThan(left, right) => f(left) < f(right) + BaseModelicaGreaterThan(left, right) => f(left) > f(right) + BaseModelicaEQ(left, right) => f(left) == f(right) + BaseModelicaNEQ(left, right) => f(left) != f(right) + _ => nothing + end + end +end + +include("maps.jl") + +function eval_AST(eq::BaseModelicaInitialEquation) + inner_eq = eq.equation.equation + Dict(eval_AST(inner_eq.lhs) => eval_AST(inner_eq.rhs)) +end + +function eval_AST(eq::BaseModelicaAnyEquation) + equation = eval_AST(eq.equation) + description = eq.description + return equation +end + +function eval_AST(eq::BaseModelicaSimpleEquation) + lhs = eval_AST(eq.lhs) + rhs = eval_AST(eq.rhs) + lhs ~ rhs +end + +function eval_AST(component::BaseModelicaComponentClause) + #this mutates a dict + #place holder to get simple equations working + #also needs to account for "modifications" + #also doesn't handle constants yet + list = component.component_list + type_prefix = component.type_prefix.dpc + declaration = list[1].declaration + name = Symbol(declaration.ident[1].name) + if type_prefix == "parameter" + variable_map[name] = only(@parameters($name)) + parameter_val_map[variable_map[name]] = declaration.modification[1].expr[1].val + return variable_map[name] + elseif isnothing(type_prefix) + variable_map[name] = only(@variables($name(t))) + end +end + +function eval_AST(model::BaseModelicaModel) + class_specifier = model.long_class_specifier + model_name = class_specifier.name + description = class_specifier.description + + composition = class_specifier.composition + + components = composition.components + equations = composition.equations + initial_equations = composition.initial_equations + + #vars = [eval_AST(comp) for comp in components if comp.type_prefix.dpc != "parameter"] + #pars = [eval_AST(comp) for comp in components if comp.type_prefix.dpc == "parameter"] + + # this loop populates the variable_map + vars = Num[] + pars = Num[] + for comp in components + name = Symbol(comp.component_list[1].declaration.ident[1].name) + + eval_AST(comp) + + if comp.type_prefix.dpc == "parameter" || comp.type_prefix.dpc == "constant" + push!(pars, variable_map[name]) + else + push!(vars, variable_map[name]) + end + end + + eqs = [eval_AST(eq) for eq in equations] + + #vars_and_pars = merge(Dict(vars .=> vars), Dict(pars .=> pars)) + #println(vars_and_pars) + #eqs = [substitute(x,vars_and_pars) for x in eqs] + + init_eqs = [eval_AST(eq) for eq in initial_equations] + init_eqs_dict = Dict() + + # quick and dumb kind of + for dictionary in init_eqs + for (key, value) in dictionary + init_eqs_dict[key] = value + end + end + for (key, value) in init_eqs_dict + init_eqs_dict[key] = substitute(value, parameter_val_map) + end + + #vars,pars,eqs, init_eqs_dict + + defaults = merge(init_eqs_dict, parameter_val_map) + @named model = ODESystem(eqs, t, vars, pars; defaults) + structural_simplify(model) +end + +function eval_AST(package::BaseModelicaPackage) + model = package.model + eval_AST(model) +end + +function eval_AST(function_args::BaseModelicaFunctionArgs) + args = function_args.args + eval_AST.([args...]) +end + +function eval_AST(function_call::BaseModelicaFunctionCall) + function_name = Symbol(function_call.func_name) + args = eval_AST(function_call.args) + function_map[function_name](args) +end + +function eval_AST(comp_reference::BaseModelicaComponentReference) + #will need to eventually account for array references and dot references... + #for now only does direct references to variables + return variable_map[Symbol(comp_reference.ref_list[1].name)] +end + +function baseModelica_to_ModelingToolkit(package::BaseModelicaPackage) + eval_AST(package) +end diff --git a/src/maps.jl b/src/maps.jl new file mode 100644 index 0000000..1d5d5d1 --- /dev/null +++ b/src/maps.jl @@ -0,0 +1,14 @@ +# holds functions predefined or defined in the Base Modelica Package +function_map = Dict( + :der => x -> D(x...), + :abs => x -> Base.abs(x...), + :sin => x -> Base.sin(x...) +) + +# holds variables, populated by evaluating component_clause +variable_map = Dict() + +# holds parameter values, default values +parameter_val_map = Dict() + +initial_value_map = Dict() diff --git a/src/parser.jl b/src/parser.jl index 1d3b23e..ab8416d 100644 --- a/src/parser.jl +++ b/src/parser.jl @@ -1,310 +1,611 @@ -list2string(x) = isempty(x) ? x : reduce(*,x) -spc = Drop(Star(Space())) +@data BaseModelicaASTNode begin + BaseModelicaType(name, fields) + BaseModelicaPackage(name, class_defs, model) + BaseModelicaModel(long_class_specifier) + BaseModelicaConstant(type, name, value, description, modification) + BaseModelicaParameter(type, name, value, description, modification) + BaseModelicaVariable(type, name, input_or_output, description, modification) + BaseModelicaSimpleEquation(lhs, rhs) + BaseModelicaInitialEquation(equation) # Just holds a BaseModelicaAnyEquation and denotes that it's an initial equation + BaseModelicaArray(type, length) + BaseModelicaString(string) + BaseModelicaTypeSpecifier(type) + BaseModelicaTypePrefix(dpc, io) + BaseModelicaDeclaration(ident, array_subs, modification) + BaseModelicaComponentDeclaration(declaration, comment) + BaseModelicaComponentClause(type_prefix, type_specifier, component_list) + BaseModelicaComponentReference(ref_list) + BaseModelicaParameterEquation(component_reference, expression, comment) + BaseModelicaWhenEquation(whens, thens) + BaseModelicaForEquation(index, equations) + BaseModelicaIfEquation(ifs, thens) + BaseModelicaAnyEquation(equation, description) + BaseModelicaForIndex(ident, expression) + BaseModelicaComposition(components, equations, initial_equations) + BaseModelicaLongClass(name, description, composition) + BaseModelicaModification(expr) + #Class types + BaseModelicaClassDefinition(class_type, class) +end + +@data BaseModelicaExpr<:BaseModelicaASTNode begin + # these are basically just tokens... + BMAdd() + BMElementWiseAdd() + BMSubtract() + BMElementWiseSubtract() + BMMult() + BMElementWiseMult() + BMDivide() + BMElementWiseDivide() + BMColon() + BMIf() + BMFor() + BMWhen() + BMThen() + BMLoop() + BMequation() + + # relational tokens + BMLessThan() + BMGreaterThan() + BMLEQ() + BMGEQ() + BMEQ() + BMNEQ() + + BMAND() + BMOR() + BMNOT() + + # nodes in the AST + BaseModelicaNumber(val) + BaseModelicaIdentifier(name) + BaseModelicaSum(left, right) + BaseModelicaMinus(left, right) + BaseModelicaProd(left, right) + BaseModelicaFactor(base, exp) + BaseModelicaElementWiseFactor(base, exp) + BaseModelicaElementWiseProd(left, right) + BaseModelicaElementWiseSum(left, right) + BaseModelicaElementWiseMinus(top, bottom) + BaseModelicaDivide(top, bottom) + BaseModelicaElementWiseDivide(left, right) + BaseModelicaParens(BaseModelicaExpr) + BaseModelicaFunctionArgs(args) + BaseModelicaFunctionCall(func_name, args) + BaseModelicaRange(start, step, stop) + BaseModelicaArraySubscripts(subscripts) + + # relational nodes + BaseModelicaNot(relation) + BaseModelicaAnd(left, right) + BaseModelicaOr(left, right) + BaseModelicaLessThan(left, right) + BaseModelicaGreaterThan(left, right) + BaseModelicaLEQ(left, right) + BaseModelicaGEQ(left, right) + BaseModelicaEQ(left, right) + BaseModelicaNEQ(left, right) + BaseModelicaIfExpression(conditions, expressions) + BaseModelicaArrayReference(term, indexes) +end + +#constructors +function create_factor(input_list) + elementwise_index = findfirst(x -> x == ".^", input_list) + power_index = findfirst(x -> x == "^", input_list) + + if !isnothing(elementwise_index) + base = only(input_list[begin:(elementwise_index - 1)]) + exp = only(input_list[(elementwise_index + 1):end]) + return BaseModelicaElementWiseFactor(base, exp) + elseif !isnothing(power_index) + base = only(input_list[begin:(power_index - 1)]) + exp = only(input_list[(power_index + 1):end]) + return BaseModelicaFactor(base, exp) + elseif isnothing(elementwise_index) && isnothing(power_index) + base = isempty(input_list) ? nothing : only(input_list) + return base # return input if no exponent operator + end +end + +function create_term(input_list) + left_el = input_list[1] + for (i, element) in enumerate(input_list) + left_el = @match element begin + ::BMMult => BaseModelicaProd(left_el, input_list[i + 1]) #make a product between the previous item and next item + ::BMElementWiseMult => BaseModelicaElementWiseProd(left_el, input_list[i + 1]) + ::BMDivide => BaseModelicaDivide(left_el, input_list[i + 1]) + ::BMElementWiseDivide => BaseModelicaElementWiseDivide( + left_el, input_list[i + 1]) + _ => left_el + end + end + left_el +end + +function create_arithmetic_expression(input_list) + left_el = input_list[1] + for (i, element) in enumerate(input_list) + left_el = @match element begin + ::BMAdd => BaseModelicaSum(left_el, input_list[i + 1]) #make a sum between the previous item and next item + ::BMElementWiseAdd => BaseModelicaElementWiseSum(left_el, input_list[i + 1]) + ::BMSubtract => BaseModelicaMinus(left_el, input_list[i + 1]) + ::BMElementWiseSubtract => BaseModelicaElementWiseMinus( + left_el, input_list[i + 1]) + _ => left_el + end + end + left_el +end -function create_component(prefix, type, components) - #only do parameters and Reals for now - #eventually will need to do arbitrary base modelica types - comp = components[1] #for now only supports one parameter/variable per statement, no "component-list"s - if isempty(prefix) - type = type - name = comp[1] - length(comp) == 2 ? description = comp[2] : description = nothing - return BaseModelicaVariable(type,name,nothing,description) - elseif prefix[1] == "parameter" # only do parameters and Reals for now - type = type - name = comp[1] - length(comp) > 1 ? value = comp[2] : value = nothing - length(comp) == 3 ? description = comp[3] : description = nothing - return BaseModelicaParameter(type,name,value,description) - elseif prefix[1] == "input" || prefix[1] == "output" - type = type - name = comp[1] - length(comp) == 2 ? description = comp[2] : description = nothing - return BaseModelicaVariable(type,name,prefix[1],description) +function create_relation(input_list) + not_flag = input_list[1] isa BMNOT + left_el = input_list[2] + for (i, element) in enumerate(input_list) + left_el = @match element begin + ::BMLessThan => BaseModelicaLessThan(left_el, input_list[i + 1]) + ::BMGreaterThan => BaseModelicaGreaterThan(left_el, input_list[i + 1]) + ::BMLEQ => BaseModelicaLEQ(left_el, input_list[i + 1]) + ::BMGEQ => BaseModelicaGEQ(left_el, input_list[i + 1]) + ::BMEQ => BaseModelicaEQ(left_el, input_list[i + 1]) + ::BMNEQ => BaseModelicaNEQ(left_el, input_list[i + 1]) + _ => left_el + end + end + if not_flag + return BaseModelicaNot(left_el) + else + return left_el end end -function create_equation(equation_list) - #so far only handles normal equations, no if, whens, or anything like that - #println(equation_list) - eq = equation_list[1] - equal_index = findfirst(x -> x == "=", eq) - if !isnothing(equal_index) - lhs = only(eq[begin:(equal_index-1)]) - rhs = only(eq[(equal_index+1):end]) # - else - lhs = eq # hack because equations don't need to be equations in base modelica for some reason - rhs = "" +function create_logical_term(input_list) + left_el = input_list[1] + for (i, element) in enumerate(input_list) + left_el = @match element begin + ::BMAND => BaseModelicaAnd(left_el, input_list[i + 1]) #make a sum between the previous item and next item + _ => left_el + end end - !isempty(equation_list[2]) ? description = only(equation_list[2]) : description = "" - BaseModelicaEquation(lhs,rhs,description) + left_el end -function create_initial_equation(equation) - #so far only handles normal equations, no if, whens, or anything like that - eq = equation[1] - lhs = eq.lhs - rhs = eq.rhs - BaseModelicaInitialEquation(lhs,rhs,nothing) +function create_logical_expression(input_list) + left_el = input_list[1] + for (i, element) in enumerate(input_list) + left_el = @match element begin + ::BMOR => BaseModelicaOr(left_el, input_list[i + 1]) #make a sum between the previous item and next item + _ => left_el + end + end + left_el end -function construct_package(input) - name = input[1] - input[4] isa String ? description = input[4] : description = nothing - variables = [] - parameters = [] +function create_simple_expression(input_list) + @match input_list begin + [start, ":", stop] => BaseModelicaRange(start, BaseModelicaNumber(1), stop) + [start, ":", step, ":", stop] => BaseModelicaRange(start, step, stop) + _ => only(input_list) + end +end + +function BaseModelicaIfExpression(input_list) + condition_list = [] + expression_list = [] + + for (i, el) in enumerate(input_list) + @match el begin + "if" => push!(condition_list, input_list[i + 1]) + "then" => push!(expression_list, input_list[i + 1]) + "elseif" => push!(condition_list, input_list[i + 1]) + "else" => push!(expression_list, input_list[i + 1]) + _ => nothing + end + end + BaseModelicaIfExpression(condition_list, expression_list) +end + +function create_component_clause(input_list) + #empty +end + +function BaseModelicaArraySubscripts(input::Vector{Any}) + BaseModelicaArraySubscripts(Tuple(input)) +end + +function BaseModelicaFunctionCall(input) + BaseModelicaFunctionCall(input[1], input[2:end]) +end + +function BaseModelicaTypePrefix(input_list) + dpc = nothing + io = nothing + for input in input_list + if input == "parameter" || input == "discrete" || input == "constant" + dpc = input + elseif input == ("input") || input == ("output") + io = input + end + end + BaseModelicaTypePrefix(dpc, io) +end + +function BaseModelicaSimpleEquation(input_list) + @match input_list begin + [lhs] => BaseModelicaSimpleEquation(lhs, nothing) + [lhs, rhs] => BaseModelicaSimpleEquation(lhs, rhs) + _ => nothing + end +end + +function BaseModelicaString(input_list::Array{<:Any}) + if length(input_list) == 1 + return BaseModelicaString(input_list[1]) + end + bmstring = foldl(*, input_list, init = "") + return BaseModelicaString(bmstring) +end + +function BaseModelicaWhenEquation(input_list) + whens = [] + thens = [] + for (i, element) in enumerate(input_list) + @match element begin + ::BMWhen => push!(whens, input_list[i + 1]) + ::BMThen => push!(thens, input_list[i + 1]) + _ => nothing + end + end + BaseModelicaWhenEquation(whens, thens) +end + +function BaseModelicaIfEquation(input_list) + ifs = [] + thens = [] + for (i, element) in enumerate(input_list) + @match element begin + ::BMIf => push!(ifs, input_list[i + 1]) + ::BMThen => push!(thens, input_list[i + 1]) + _ => nothing + end + end + BaseModelicaIfEquation(ifs, thens) +end + +function BaseModelicaForEquation(input_list) + index = input_list[1] + equations = input_list[2:end] + BaseModelicaForEquation(index, equations) +end + +function BaseModelicaComposition(input_list) equations = [] initial_equations = [] - for thing in input - typeof(thing) == BaseModelicaVariable ? push!(variables, thing) : - typeof(thing) == BaseModelicaParameter ? push!(parameters, thing) : - typeof(thing) == BaseModelicaEquation ? push!(equations, thing) : - typeof(thing) == BaseModelicaInitialEquation ? push!(initial_equations,thing) : - nothing + components = [] + + for input in input_list + if input isa BaseModelicaComponentClause + push!(components, input) + elseif input isa BaseModelicaInitialEquation + push!(initial_equations, input) + elseif input isa BaseModelicaAnyEquation + push!(equations, input) + end + end + BaseModelicaComposition(components, equations, initial_equations) +end + +function BaseModelicaPackage(input_list) + name = input_list[1] + model = nothing + class_defs = [] + for input in input_list[2:end] + if input isa BaseModelicaClassDefinition + push!(class_defs, input) + elseif input isa BaseModelicaModel + model = input + end end - - model = BaseModelicaModel(name,description,parameters,variables,equations,initial_equations) - BaseModelicaPackage(name, model) + BaseModelicaPackage(name, class_defs, model) end +function component_reference_or_function_call(input_list) + if length(input_list) >= 2 && input_list[2] isa BaseModelicaFunctionArgs + return BaseModelicaFunctionCall(input_list[1], input_list[2]) + else + return input_list[1] + end +end +list2string(x) = isempty(x) ? x : reduce(*, x) +spc = Drop(Star(Space())) # Base Modelica grammar @with_names begin -NL = p"\r\n" | p"\n" | p"\r"; -WS = p" " | p"\t" | NL; -LINE_COMMENT = p"//[^\r\n]*" + NL; -ML_COMMENT = p"/[*]([^*]|([*][^/]))*[*]/"; - -#lexical units, not keywords -NONDIGIT = p"_|[a-z]|[A-Z]"; -DIGIT = p"[0-9]"; -UNSIGNED_INTEGER = Plus(DIGIT); -Q_CHAR = NONDIGIT | DIGIT | p"[-!#$%&()*>+,./:;<>=?>@\[\]{}|~ ^]"; -S_ESCAPE = p"\\['\"?\\abfnrtv]"; -S_CHAR = NL | p"[^\r\n\\\"]"; -Q_IDENT = (E"'" + (Q_CHAR | S_ESCAPE ) + Star(Q_CHAR | S_ESCAPE | E"\"" ) + E"'") |> list2string; -IDENT = (((NONDIGIT + Star( DIGIT | NONDIGIT )) |> list2string) | Q_IDENT); -STRING = E"\"" + Star( S_CHAR | S_ESCAPE ) + E"\"" |> list2string; -EXPONENT = ( e"e" | e"E" ) + ( e"+" | e"-" )[0:1] + DIGIT[1:end]; -UNSIGNED_NUMBER = DIGIT[1:end] + ( e"." + Star(DIGIT) )[0:1] + EXPONENT[0:1] |> list2string; - -#component clauses -name = (IDENT + Star(e"." + IDENT)) |> list2string; -type_specifier = E"."[0:1] + name; -type_prefix = (( e"discrete" | e"parameter" | e"constant" )[0:1] + spc + ( e"input" | e"output" )[0:1]); -array_subscripts = Delayed() -modification = Delayed() -declaration = IDENT + array_subscripts[0:1] + modification[0:1]; -comment = Delayed() -component_declaration = declaration + spc + comment; -global_constant = e"constant" + type_specifier + array_subscripts[0:1] + declaration + comment; -component_list = (component_declaration & spc & Star(E"," + component_declaration)); -component_reference = E"."[0:1] + IDENT + array_subscripts[0:1] + Star(E"." + IDENT + array_subscripts[0:1]); -component_clause = type_prefix[1:1,:&] + spc + type_specifier + spc + component_list[1:1,:&] > create_component; -#equations - -#modification -string_comment = (STRING + Star(E"+" + STRING))[0:1]; -element_modification = name + modification[0:1] + string_comment; -element_modification_or_replaceable = element_modification; -decoration = E"@" + UNSIGNED_INTEGER; -argument = decoration[0:1] + element_modification_or_replaceable; -argument_list = argument + Star(E"," + argument); -class_modification = E"(" + argument_list[0:1] + E")"; -expression = Delayed() -modification.matcher = (class_modification + (spc + E"=" + spc + expression)[0:1]) | (spc + E"=" + spc + expression) | (E":=" + spc + expression); - -#expressions -relational_operator = e"<" | e"<=" | e">" | e">=" | e"==" | e"<>"; -add_operator = e"+" | e"-" | e".+" | e".-"; -mul_operator = e"*" | e"/" | e".*" | e"./"; - -for_index = IDENT + E"in" + expression; - -named_arguments = Delayed() -function_partial_application = E"function" + type_specifier + e"(" + named_arguments + e")"; -function_argument = function_partial_application | expression; -function_arguments_non_first = Delayed() -function_arguments_non_first.matcher = (function_argument + (E"," + function_arguments_non_first)[0:1]) | named_arguments; -named_argument = IDENT + E"=" + function_argument; -named_arguments.matcher = named_argument + Star(E"," + named_argument); -function_partial_applications = E"function" + type_specifier + E"(" + named_arguments[0:1] + E")"; -function_arguments = (expression + ((E"," + function_arguments_non_first) | (E"for" + for_index))[0:1]) | - (function_partial_application + (E"," + function_arguments_non_first)[0:1]) | - named_arguments; -function_call_args = e"(" + function_arguments[0:1] + e")"; -output_expression_list = Delayed() -expression_list = Delayed() -array_arguments = expression + (Star(E"," + expression) | E"for" + for_index); -primary = UNSIGNED_NUMBER | STRING | e"false" | e"true" | - ((e"der" | e"initial" | e"pure") + function_call_args) | - (component_reference + function_call_args[0:1]) | - (e"(" + spc + output_expression_list + spc + e")" + array_subscripts[0:1]) | - (e"[" + spc + expression_list + spc +Star(E";" + spc + expression_list) + spc + e"]") | - (e"{" + spc + array_arguments + spc + e"}") | - E"end"; -factor = primary + spc + ((E"^" | E".^") + spc + primary)[0:1]; -term = factor + spc + Star(mul_operator + spc + factor); -arithmetic_expression = add_operator[0:1] + spc + term + spc + Star(add_operator + spc + term); - -subscript = E":" | expression; -array_subscripts.matcher = E"[" + subscript + Star(E"," + subscript); -annotation_comment = E"annotation" + class_modification; -comment.matcher = string_comment + annotation_comment[0:1]; - -enumeration_literal = IDENT + comment; -enum_list = enumeration_literal + Star(E"," + enumeration_literal); - -guess_value = E"guess" + E"(" + component_reference + E")" ; -prioritize_expression = Delayed() -parameter_equation = E"parameter equation" + guess_value + E"=" + (expression | prioritize_expression) + comment; - -normal_element = component_clause; - - -generic_element = normal_element | parameter_equation; - -language_specification = STRING; - -external_function_call = (component_reference + E"=")[0:1] + IDENT + E"(" + expression_list[0:1] + E")"; - -equation = Delayed() -initial_equation = Delayed() -statement = Delayed() -base_partition = Delayed() -composition = Star(decoration[0:1] + generic_element + E";" + spc) + - Star((spc + e"equation" + spc + Star(spc + equation + E";" + spc)) | - (e"initial equation" + spc + Star(spc + initial_equation + E";" + spc)) | - (e"initial"[0:1] + e"algorithm" + Star(statement + E";"))) + (decoration[0:1] + E"external" + language_specification[0:1] + external_function_call[0:1] + annotation_comment[0:1] + E";")[0:1] + - Star(base_partition) + (annotation_comment + E";")[0:1]; - - -base_prefix = e"input" | e"output" -long_class_specifier = IDENT + spc + string_comment + spc + composition + spc + e"end" + spc + IDENT; -short_class_specifier = IDENT + E"=" + (base_prefix[0:1] + type_specifier + class_modification[0:1]) | - (e"enumeration" + E"(" + (enum_list[0:1] | E":" ) + E")") + comment; -class_prefixes = e"type" | e"record" | ((e"pure constant")[0:1] | (e"impure")[0:1]) + e"function"; -der_class_specifier = IDENT + E"=" + E" "[0:1] + E"der" + E" " + E"(" + type_specifier + E"," + IDENT + Star(E"," + IDENT) + E")" + comment; -class_specifier = long_class_specifier | short_class_specifier | der_class_specifier; -class_definition = class_prefixes + class_specifier; - -clock_clause = decoration[0:1] + E"Clock" + IDENT + E"=" + expression + comment; -sub_partition = E"subpartition" + E"(" + argument_list + E")" + string_comment + (annotation_comment + E";")[0:1] + (Star(E"equation" + ((equation + E";"))) | E"algorithm" + Star(statement + E";")); -base_partition.matcher = E"partition" + string_comment + (annotation_comment + E";")[0:1] + (clock_clause + E";") + sub_partition; - - - -#equations - -relation = arithmetic_expression + spc + (relational_operator + arithmetic_expression)[0:1] |> list2string; -logical_factor = E"not"[0:1] + spc + relation; -logical_term = logical_factor + spc + Star(E"and" + spc + logical_factor); -logical_expression = logical_term + spc + Star(E"or" + spc + logical_term); -simple_expression = logical_expression + spc + (E":" + spc +logical_expression + spc + (E":" + spc + logical_expression)[0:1])[0:1]; - -priority = expression; - - -prioritize_equation = E"prioritize" + E"(" + component_reference + E"," + priority + E")"; -prioritize_expression.matcher = E"prioritize" + E"(" + expression + E"," + priority + E")"; - - -initial_equation.matcher = (equation | prioritize_equation) |> create_initial_equation; - -output_expression_list.matcher = expression[0:1] + Star(E"," + expression[0:1]); -expression_list.matcher = expression + Star(E"," + expression); - -if_expression = Delayed() -expression_no_decoration = simple_expression | if_expression; -if_expression.matcher = - E"if" + expression_no_decoration + E"then" + expression_no_decoration + - Star(E"elseif" + expression_no_decoration + E"then" + expression_no_decoration) + - E"else" + expression_no_decoration; - -expression.matcher = expression_no_decoration + decoration[0:1]; - -if_equation = - E"if" + expression + E"then" + NL + - Star(equation + E";") + - Star(E"elseif" + expression + E"then" + NL + - Star(equation + E";") - ) + - (E"else" + NL + - Star(equation + E";") - )[0:1] + NL + - E"end if"; - -for_index = IDENT + E"in" + expression; - -for_equation = - E"for" + for_index + E"loop" + NL + - Star(equation + E";") + NL + - E"end for"; - -for_statement = - E"for" + for_index + E"loop" + NL + - Star(statement + E";") + NL + - E"end for"; - -while_statement = - E"while" + expression + E"loop" + - Star(statement + E";") + NL + - E"end while"; - -when_equation = - E"when" + expression + E"then" + NL + - Star(statement + E";") + NL + - E"end when"; - -when_statement = - E"when" + expression + E"then" + NL + - Star(statement + E";") + NL + - Star(E"elsewhen" + expression + E"then" + NL + - Star(statement + E";")) + NL + - E"end when"; - -if_statement = - E"if" + expression + E"then" + NL + - Star(statement + E";") + NL + - Star(E"elseif" + expression + E"then" + NL + - Star(statement + E";")) + - (E"else" + NL + - Star(statement + E";"))[0:1] + NL + - E"end if"; - -statement.matcher = decoration[0:1] + (component_reference + (E":=" + expression | function_call_args) | - E"(" + output_expression_list + E")" + E":=" + component_reference + function_call_args | - E"break" | - E"return" | - if_statement | - for_statement | - while_statement | - when_statement) + comment; - -equation.matcher = ((decoration[0:1] + (simple_expression + decoration[0:1] + (spc + e"=" + spc + expression)[0:1]) | - if_equation | - for_equation | - when_equation) & comment) |> create_equation; - -base_modelica = - (spc + E"package" + spc + IDENT + spc + - Star((decoration[0:1] + spc + class_definition + spc + E";") | - (decoration[0:1] + global_constant + E";")) + - spc + decoration[0:1] + spc + e"model" + spc + long_class_specifier + E";" + - spc + (annotation_comment + E";")[0:1] + spc + - e"end" + spc + IDENT + spc + E";" + spc) |> construct_package + NL = p"\r\n" | p"\n" | p"\r" + WS = p" " | p"\t" | NL + LINE_COMMENT = p"//[^\r\n]*" + NL + ML_COMMENT = p"/[*]([^*]|([*][^/]))*[*]/" + + #lexical units, not keywords + NONDIGIT = p"_|[a-z]|[A-Z]" + DIGIT = p"[0-9]" + UNSIGNED_INTEGER = Plus(DIGIT) + Q_CHAR = NONDIGIT | DIGIT | p"[-!#$%&()*>+,./:;<>=?>@\[\]{}|~ ^]" + S_ESCAPE = p"\\['\"?\\abfnrtv]" + S_CHAR = NL | p"[^\r\n\\\"]" + Q_IDENT = (E"'" + (Q_CHAR | S_ESCAPE) + Star(Q_CHAR | S_ESCAPE | E"\"") + E"'") |> + list2string + IDENT = (((NONDIGIT + Star(DIGIT | NONDIGIT)) |> list2string) | Q_IDENT) > + BaseModelicaIdentifier + STRING = E"\"" + Star(S_CHAR | S_ESCAPE) + E"\"" |> list2string + EXPONENT = (e"e" | e"E") + (e"+" | e"-")[0:1] + DIGIT[1:end] + UNSIGNED_NUMBER = (DIGIT[1:end] + (e"." + Star(DIGIT))[0:1] + EXPONENT[0:1] |> + list2string) |> (x -> BaseModelicaNumber(parse(Float64, only(x)))) + + #component clauses + name = Not(Lookahead(e"end")) + Not(Lookahead(E"equation")) + + Not(Lookahead(E"initial equation")) + (IDENT + Star(e"." + IDENT)) |> list2string # Not(Lookahead(foo)) tells it that names can't be foo + type_specifier = E"."[0:1] + name > BaseModelicaTypeSpecifier + type_prefix = ((e"discrete" | e"parameter" | e"constant")[0:1] + spc + + (e"input" | e"output")[0:1]) |> BaseModelicaTypePrefix + array_subscripts = Delayed() + modification = Delayed() + declaration = IDENT & array_subscripts[0:1] & modification[0:1] > + BaseModelicaDeclaration + comment = Delayed() + component_declaration = declaration + spc + comment > BaseModelicaComponentDeclaration + global_constant = e"constant" + type_specifier + array_subscripts[0:1] + declaration + + comment + component_list = (component_declaration + spc + + Star(E"," + spc + component_declaration)) + component_reference = E"."[0:1] + IDENT + array_subscripts[0:1] + + Star(E"." + IDENT + array_subscripts[0:1]) |> + BaseModelicaComponentReference + component_clause = type_prefix + spc + type_specifier + spc + component_list[1:1, :&] > + BaseModelicaComponentClause + #equations + + #modification + string_comment = (STRING + spc + Star(spc + E"+" + spc + STRING))[0:1] |> + BaseModelicaString + element_modification = name + modification[0:1] + string_comment + element_modification_or_replaceable = element_modification + decoration = E"@" + UNSIGNED_INTEGER + argument = decoration[0:1] + element_modification_or_replaceable + argument_list = argument + Star(E"," + argument) + class_modification = E"(" + argument_list[0:1] + E")" + expression = Delayed() + modification.matcher = (class_modification + (spc + E"=" + spc + expression)[0:1]) | + (spc + E"=" + spc + expression) | (E":=" + spc + expression) |> + BaseModelicaModification + + #expressions + relational_operator = (E"<" > BMLessThan) | (E"<=" > BMLEQ) | (E">" > BMGreaterThan) | + (E">=" > BMGEQ) | (E"==" > BMEQ) | (E"<>" > BMNEQ) + add_operator = (E"+" > BMAdd) | (E"-" > BMSubtract) | (E".+" > BMElementWiseAdd) | + (E".-" > BMElementWiseSubtract) + mul_operator = (E"*" > BMMult) | (E"/" > BMDivide) | (E".*" > BMElementWiseMult) | + (E"./" > BMElementWiseDivide) + named_arguments = Delayed() + function_partial_application = E"function" + type_specifier + e"(" + named_arguments + + e")" + function_argument = function_partial_application | expression + function_arguments_non_first = Delayed() + function_arguments_non_first.matcher = (function_argument + + (E"," + function_arguments_non_first)[0:1]) | + named_arguments + named_argument = IDENT + E"=" + function_argument + named_arguments.matcher = named_argument + Star(E"," + named_argument) + function_partial_applications = E"function" + type_specifier + E"(" + + named_arguments[0:1] + E")" + for_index = Delayed() + function_arguments = (expression + + ((E"," + function_arguments_non_first) | (E"for" + for_index))[0:1]) | + (function_partial_application + + (E"," + function_arguments_non_first)[0:1]) | + named_arguments |> BaseModelicaFunctionArgs + function_call_args = E"(" + function_arguments[0:1] + E")" + output_expression_list = Delayed() + expression_list = Delayed() + array_arguments = expression + (Star(E"," + expression) | E"for" + for_index) + primary = UNSIGNED_NUMBER | STRING | e"false" | e"true" | + ((e"der" | e"initial" | e"pure") + function_call_args |> + component_reference_or_function_call) | + ((component_reference + function_call_args[0:1]) |> + component_reference_or_function_call) | + (E"(" + spc + output_expression_list + spc + E")" + array_subscripts[0:1]) | + (e"[" + spc + expression_list + spc + Star(E";" + spc + expression_list) + + spc + e"]") | + (e"{" + spc + array_arguments + spc + e"}") | + E"end" + factor = primary + spc + ((e"^" | e".^") + spc + primary)[0:1] |> create_factor + term = factor + spc + Star(mul_operator + spc + factor) |> create_term + arithmetic_expression = add_operator[0:1] + spc + term + spc + + Star(add_operator + spc + term) |> create_arithmetic_expression + + subscript = (E":" > BMColon) | expression + array_subscripts.matcher = E"[" + subscript + Star(E"," + subscript) + E"]" |> + BaseModelicaArraySubscripts + annotation_comment = E"annotation" + class_modification + comment.matcher = string_comment + annotation_comment[0:1] + + enumeration_literal = IDENT + comment + enum_list = enumeration_literal + Star(E"," + enumeration_literal) + + guess_value = E"guess" + E"(" + component_reference + E")" + prioritize_expression = Delayed() + parameter_equation = E"parameter equation" + spc + guess_value + spc + E"=" + spc + + (expression | prioritize_expression) + comment > + BaseModelicaParameterEquation + + normal_element = component_clause + + generic_element = normal_element | parameter_equation + + language_specification = STRING + + external_function_call = (component_reference + E"=")[0:1] + IDENT + E"(" + + expression_list[0:1] + E")" + + equation = Delayed() + initial_equation = Delayed() + statement = Delayed() + base_partition = Delayed() + composition = Star(decoration[0:1] + generic_element + E";" + spc) + spc + + Star((spc + e"equation" + spc + Star(spc + equation + E";" + spc)) | + (e"initial equation" + spc + + Star(spc + initial_equation + E";" + spc)) | + (e"initial"[0:1] + e"algorithm" + Star(statement + E";"))) + + (decoration[0:1] + E"external" + language_specification[0:1] + external_function_call[0:1] + annotation_comment[0:1] + E";")[0:1] + + Star(base_partition) + (annotation_comment + E";")[0:1] |> + BaseModelicaComposition + + base_prefix = e"input" | e"output" + long_class_specifier = IDENT + spc + string_comment + spc + composition + spc + E"end" + + spc + Drop(IDENT) > BaseModelicaLongClass + short_class_specifier = IDENT + spc + E"=" + spc + + (base_prefix[0:1] + type_specifier + class_modification[0:1]) + (e"enumeration" + E"(" + (enum_list[0:1] | E":") + E")") + comment + class_prefixes = e"type" | e"record" | + ((e"pure constant")[0:1] | ((e"impure")[0:1]) + e"function") + der_class_specifier = IDENT + E"=" + E" "[0:1] + E"der" + E" " + E"(" + type_specifier + + E"," + IDENT + Star(E"," + IDENT) + E")" + comment + class_specifier = long_class_specifier | short_class_specifier | der_class_specifier + class_definition = class_prefixes + spc + class_specifier > BaseModelicaClassDefinition + + clock_clause = decoration[0:1] + E"Clock" + IDENT + E"=" + expression + comment + sub_partition = E"subpartition" + E"(" + argument_list + E")" + string_comment + + (annotation_comment + E";")[0:1] + + (Star(E"equation" + ((equation + E";"))) | E"algorithm" + + Star(statement + E";")) + base_partition.matcher = E"partition" + string_comment + + (annotation_comment + E";")[0:1] + (clock_clause + E";") + + sub_partition + + #equations + + relation = arithmetic_expression + spc + + (relational_operator + arithmetic_expression)[0:1] + logical_factor = (e"not"[0:1] |> (x -> !isempty(x) ? BMNOT() : nothing)) + spc + + relation |> create_relation + logical_term = logical_factor + spc + Star((E"and" > BMAND) + spc + logical_factor) |> + create_logical_term + logical_expression = logical_term + spc + Star((E"or" > BMOR) + spc + logical_term) |> + create_logical_expression + + # can be expression or a range, the : are for ranges + simple_expression = logical_expression + spc + + (e":" + spc + logical_expression + spc + (e":" + spc + logical_expression)[0:1])[0:1] |> + create_simple_expression + + priority = expression + + prioritize_equation = E"prioritize" + E"(" + component_reference + E"," + priority + + E")" + prioritize_expression.matcher = E"prioritize" + E"(" + expression + E"," + priority + + E")" + + initial_equation.matcher = (equation | prioritize_equation) > + BaseModelicaInitialEquation + + output_expression_list.matcher = expression[0:1] + Star(E"," + expression[0:1]) + expression_list.matcher = expression + Star(E"," + expression) + + if_expression = Delayed() + expression_no_decoration = simple_expression | if_expression + if_expression.matcher = e"if" + expression_no_decoration + e"then" + + expression_no_decoration + + Star(e"elseif" + expression_no_decoration + e"then" + + expression_no_decoration) + + e"else" + expression_no_decoration |> BaseModelicaIfExpression + + expression.matcher = expression_no_decoration + decoration[0:1] + + if_equation = (E"if" > BMIf) + expression + (E"then" > BMThen) + spc + + Star(equation + E";") + spc + + Star((E"elseif" > BMIf) + spc + expression + (E"then" > BMThen) + spc + + Star(spc + equation + E";") + ) + spc + + ((E"else" > BMIf) + spc + + Star(spc + equation + E";") + )[0:1] + spc + + E"end if" |> BaseModelicaIfEquation + + for_index.matcher = IDENT + spc + E"in" + spc + expression > BaseModelicaForIndex + + for_equation = E"for" + spc + for_index + spc + E"loop" + spc + + Star(spc + equation + E";") + spc + + E"end for" |> BaseModelicaForEquation + + for_statement = E"for" + for_index + E"loop" + NL + + Star(statement + E";") + NL + + E"end for" + + while_statement = E"while" + expression + E"loop" + + Star(statement + E";") + NL + + E"end while" + + when_equation = (E"when" > BMWhen) + spc + expression + spc + (E"then" > BMThen) + spc + + Star(equation + E";" + spc) + spc + + Star((E"elsewhen" > BMWhen) + spc + expression + spc + + (E"then" > BMThen) + spc + + Star(equation + E";")) + spc + + E"end when" |> BaseModelicaWhenEquation + + when_statement = E"when" + expression + E"then" + spc + + Star(statement + E";") + spc + + Star(E"elsewhen" + expression + E"then" + NL + + Star(statement + E";")) + NL + + E"end when" + + if_statement = E"if" + expression + E"then" + NL + + Star(statement + E";") + NL + + Star(E"elseif" + expression + E"then" + NL + + Star(statement + E";")) + + (E"else" + NL + + Star(statement + E";"))[0:1] + NL + + E"end if" + + statement.matcher = decoration[0:1] + + (component_reference + (E":=" + expression | function_call_args) | + E"(" + output_expression_list + E")" + E":=" + + component_reference + function_call_args | + E"break" | + E"return" | + if_statement | + for_statement | + while_statement | + when_statement) + comment + + equation.matcher = decoration[0:1] + + (when_equation | + if_equation | + for_equation | + (simple_expression + decoration[0:1] + + (spc + E"=" + spc + expression)[0:1]) |> + BaseModelicaSimpleEquation) + comment > BaseModelicaAnyEquation + + base_modelica = (spc + E"package" + spc + IDENT + spc + + Star((decoration[0:1] + spc + class_definition + spc + E";") | + (decoration[0:1] + global_constant + E";")) + + spc + decoration[0:1] + spc + + ((E"model" + spc + long_class_specifier + E";") > BaseModelicaModel) + + spc + (annotation_comment + E";")[0:1] + spc + + E"end" + spc + Drop(IDENT) + spc + E";" + spc) |> BaseModelicaPackage end; """ Parses a String in to a BaseModelicaPackage. """ function parse_str(data) - only(parse_one(data,base_modelica)) + only(parse_one(data, base_modelica)) end """ Takes a path to a file and parses the contents in to a BaseModelicaPackage """ function parse_file(file) - parse_str(read(file,String)) -end \ No newline at end of file + parse_str(read(file, String)) +end diff --git a/src/scratch.jl b/src/scratch.jl new file mode 100644 index 0000000..586a111 --- /dev/null +++ b/src/scratch.jl @@ -0,0 +1,277 @@ +parse_one("'x'.^2", factor, debug = true) +parse_one("('stone'+'rock').^(5 + 5)", factor) +parse_one("2", UNSIGNED_NUMBER, debug = true) + +parse_one("'x'", IDENT)[1].name +parse_one("4*2*4 .*2", term) + +parse_one("4.9", factor) +parse_one("'x'", factor) +parse_one("'juice'^'eeep'", factor) +parse_one("8*9*3*3", term) +parse_one("5*'x'+8*(3 + 'y')", arithmetic_expression) +parse_one("(3 * 5)", primary) + +parse_one("x*(z + y)", arithmetic_expression) +parse_one("'x'*('z' + 'y')", expression) + +parse_one("20.89", UNSIGNED_NUMBER) + +eval_AST(only(parse_one("1^5", arithmetic_expression))) + +parse_one("5^5", arithmetic_expression)[1] + +parse_one("4.0", arithmetic_expression)[1] + +eval_AST(only(parse_one("5 + 6*(45 + 9^2)^2", arithmetic_expression))) + +eval_AST(only(parse_one("5 + 6*(32 + 100)", arithmetic_expression))) +eval_AST(only(parse_one("5 + 6*(45 + 9^2)^2", arithmetic_expression))) + +parse_one("5 > 6", relation) +parse_one("4 < 5", relation) +parse_one("4 <> 5", relation) +parse_one("3 >= 3 ", relation) +parse_one("3 <= 5", relation) +parse_one("3 == 3", relation) + +parse_one("(4 + 5) < (6 + 6)", logical_factor, debug = true) + +parse_one("5 < 5", logical_factor) +parse_one("(5 + 9) > (7-2)", logical_factor) +parse_one("not (5*9) < (4+3)", logical_factor) + +parse_one("4 and 9", logical_term) +parse_one("4 or 9", logical_expression) +parse_one("(4 > 9) and (4 < 6)", logical_expression) +parse_one("4 == 5", simple_expression) + +parse_one("3:4:5", simple_expression) + +parse_one("if 4>5 then 8 elseif 4<7 then 5 else 6", if_expression, debug = true) + +parse_one("""'R' +Real 'x'; +end 'R'""", long_class_specifier, debug = true) + +parse_one("parameter input Real 'x' \"goop\";", component_clause) + +parse_one("'x'[4,4,:]", declaration) + +parse_one("\"string\"", string_comment) +parse_one("[3,4,6,:]", array_subscripts, debug = true) + +parse_one("'z'[4,5,6] \"z3\"", component_declaration) + +parse_one("'z', 'x'", component_list) + +parse_one("parameter Real 'x','y','z'", component_clause) + +parse_one("parameter Real 'x' = 43 \"the worst parameter\"", component_clause) +parse_one("parameter Real 'x'[3]", component_clause) + +parse_one("'x'[1].'y'[4]", component_reference) + +parse_one("parameter equation guess('x') = 4", parameter_equation, debug = true) + +parse_one("1 + 4*3 = 100/2", equation) +parse_one("'y' = 5", equation, debug = true) + +parse_one("""when 'x' == 5 then +'y' = 5; +elsewhen 'y' == 20 then +'x' = 30; +end when""", when_equation, debug = true) + +parse_one("parameter", type_prefix) + +parse_one("""if 'x' == 5 then +'y' = 4; +elseif 'x' == 21 then +'y' = 6; +end if""", if_equation, debug = true) + +parse_one("\"string\" + \"otherstring\"", comment, debug = true) + +parse_one("x in 1:3", for_index) + +parse_one("""for x in 1:3 loop + 'y' = 21; + 'x' = 31 + x; +end for""", for_equation) + +x = only(parse_one("""parameter Real 'wagon' = 1000 \"Mass\"; + Real 'other_wagon' \"other wagon\"; +equation + 'wagon' = 5; + 'y' = 6; +initial equation + 'x' = 'wagon'; +""", composition, debug = true)) + +parse_one("""equation + 'wagon' = 5; +""", composition) + +parse_one("'x'", name) + +parse_one("""package 'Train' +model 'Train' +parameter Real 'wagon' = 1000 \"Mass\"; +Real 'other_wagon' \"other wagon\"; +equation +'wagon' = 5; +'y' = 6; +initial equation +'x' = 'wagon'; +end 'Train'; +end 'Train';""", base_modelica) + +parse_one("'x' = 21", equation) + +eval_AST(only(parse_one("(4 + 5) < (6 + 6)", logical_factor, debug = true))) + +parse_one("5 < 5", logical_factor) +eval_AST(only(parse_one("(5 + 9) > (7-2)", logical_factor))) +eval_AST(only(parse_one("not (5*1) > (4+3) and 5 > 3", logical_term))) +eval_AST(only(parse_one("4 == 4", logical_factor))) +eval_AST(only(parse_one("4 <> 4", logical_factor))) +eval_AST(only(parse_one("4 == 6", logical_factor))) +eval_AST(only(parse_one("4 <> 6", logical_factor))) + +parse_one("""record 'R' + Real 'x'; +end 'R'; +""", class_definition) + +parse_one("record", class_prefixes) + +parse_one("""record 'R' "a record that is a record" + Real 'x'; +end 'R'; +""", class_definition) + +x = parse_one("""package 'Train' +record 'R' + Real 'x'; +end 'R'; +model 'Train' +parameter Real 'wagon' = 1000 \"Mass\"; +Real 'other_wagon' \"other wagon\"; +equation +'wagon' = 5; +'y' = 6; +initial equation +'x' = 'wagon'; +end 'Train'; +end 'Train';""", base_modelica)[1] + +parse_one("""'Train' +parameter Real 'wagon' = 1000 \"Mass\"; +Real 'other_wagon' \"other wagon\"; +equation +'wagon' = 5; +'y' = 6; +end 'Train' +""", long_class_specifier) + +y = eval_AST(only(parse_one("'x' = 21 + 'y' * 'z'", equation))) + +@variables x y + +eval_AST(only(parse_one("x + y*z", arithmetic_expression))) + +eval(eval_AST(only(parse_one("1 + 1", arithmetic_expression)))) + +parse_one("parameter Real 'x' = 43 \"the worst parameter\"", component_clause) +parse_one("Real 'x'", component_clause) + +eval_AST(only(parse_one("""package 'Train' +model 'Train' +parameter Real 'wagon' = 1000 \"Mass\"; +Real 'other_wagon' \"other wagon\"; +equation +'wagon' = 5; +'y' = 6; +initial equation +'x' = 'wagon'; +end 'Train'; +end 'Train';""", base_modelica))) + +only(parse_one("""package 'Train' +model 'Train' "poop" +parameter Real 'wagon' = 1000 \"Mass\"; +Real 'other_wagon' \"other wagon\"; +initial equation +'x' = 'wagon'; +equation +'wagon' = 5; +'y' = 6; +end 'Train'; +end 'Train';""", base_modelica)) + +parse_one("'y' = 6", initial_equation) + +parse_one("'m' * 'c_p' * der('T') = 'h' * 'A' * ('T_inf' - 'T')", equation) +parse_one("sin(x)", primary, debug = true) +parse_one("der(x)", primary) + +parse_one("x,y,z", function_arguments) +parse_one("x", simple_expression) + +parse_one("f(x,y,z)", primary) + +parse_one("4^4", factor) + +eval_AST(only(parse_one("'m' * 'c_p' * der('T') = 'h' * 'A' * ('T_inf' - 'T')", equation))) + +eval_AST(only(parse_one("sin(x)", primary))) + +only(parse_one("sin(x)", primary)).args + +eval_AST(only(parse_one( + "parameter Real 'T_inf' = 25.0 \"Ambient temperature\";", component_clause))) + +eval_AST(only(parse_one("Real x;", component_clause))) + +eval_AST(parse_one("x", component_reference)[1]) + +eval_AST(only(parse_one("Real 'm' = 25.0;", component_clause))) +eval_AST(only(parse_one("Real c_p;", component_clause))) +eval_AST(only(parse_one("Real T;", component_clause))) +eval_AST(only(parse_one("Real h;", component_clause))) +eval_AST(only(parse_one("Real A;", component_clause))) +eval_AST(only(parse_one("Real T_inf;", component_clause))) + +eval_AST(parse_one("'m' * 'c_p' * der('T') = 'h' * 'A' * ('T_inf' - 'T')", equation)[1]) + +eval_AST(only(parse_one("der('T')", primary))) + +model = parse_one( + """package 'NewtonCoolingWithDefaults' +model 'NewtonCoolingWithDefaults' "Cooling example with default parameter values" + parameter Real 'T_inf' = 25.0 "Ambient temperature"; + parameter Real 'T0' = 90.0 "Initial temperature"; + parameter Real 'h' = 0.7 "Convective cooling coefficient"; + parameter Real 'A' = 1.0 "Surface area"; + parameter Real 'm' = 0.1 "Mass of thermal capacitance"; + parameter Real 'c_p' = 1.2 "Specific heat"; + Real 'T' "Temperature"; +initial equation + 'T' = 'T0' "Specify initial value for T"; +equation + 'm' * 'c_p' * der('T') = 'h' * 'A' * ('T_inf' - 'T') "Newton's law of cooling"; +end 'NewtonCoolingWithDefaults'; +end 'NewtonCoolingWithDefaults';""", base_modelica)[1].model + +x = eval_AST(model) + +parse_one("'m' = 25.0;", declaration) +parse_one("'m' = 25.0;", component_declaration) + +parse_one("= x", modification) + +x = only(parse_one("Real 'm' = 25.0;", component_clause)) +x.component_list[1].declaration.modification[1].expr[1].val + +x = only(parse_one("Real 'm';", component_clause)) diff --git a/src/utils.jl b/src/utils.jl deleted file mode 100644 index c1bedcc..0000000 --- a/src/utils.jl +++ /dev/null @@ -1,7 +0,0 @@ -function display_machine(m::Automa.Machine) - open("/tmp/machine.dot", "w") do io - println(io, Automa.machine2dot(m)) - end - run(pipeline(`dot -Tsvg /tmp/machine.dot`, stdout = "/tmp/machine.svg")) - run(`firefox /tmp/machine.svg`) -end diff --git a/test/runtests.jl b/test/runtests.jl index e48fd5a..4c44d6d 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,64 +1,32 @@ using Test, SafeTestsets -@testset "BaseModelica" begin - @safetestset "Quality Assurance" include("qa.jl") - @safetestset "Parsing and Conversion Tests" begin - using BaseModelica - using ModelingToolkit - BM = BaseModelica - PC = BM.ParserCombinator +const GROUP = get(ENV, "GROUP", "All") - @test PC.parse_one("3+ 5 -3/ 4*5+787 -10", BM.arithmetic_expression) |> length == 13 - @test only(PC.parse_one("output Real 'juice' \"juicy\";",BM.component_clause)) isa BM.BaseModelicaVariable - @test only(PC.parse_one("Real 'juice' \"the real juice\";",BM.component_clause)) isa BM.BaseModelicaVariable - @test only(PC.parse_one("parameter Real 'juice' = 45",BM.component_clause)) isa BM.BaseModelicaParameter - @test only(PC.parse_one("parameter Real 'juice' = 45 \"juice 45\"",BM.component_clause)) isa BM.BaseModelicaParameter - @test only(PC.parse_one("'locomotive' + 'creepers' = 'jeepers' \"holy cheepers\";",BM.equation)) isa BM.BaseModelicaEquation - @test only(PC.parse_one("der('T') = 'x'",BM.equation)) isa BM.BaseModelicaEquation - @test only(PC.parse_one("otherfun('x') = 'func_arg'", BM.equation)) isa BM.BaseModelicaEquation - @test PC.parse_one(""" - Real 'juice.juice' \"juicy\"; - Real 'fruit' \"fruity\"; - output Real 'output_fruit'; - parameter Real 'doop' = 60; - equation - ('juice'+'fruit')*'blade' = 'puree'; - 'juicy' * 'fruit' = 'juicyfruit'; - """, BM.composition) |> length == 7 - @test PC.parse_one("""JuiceModel - Real 'juice.juice' \"juicy\"; - Real 'fruit' \"fruity\"; - output Real 'output_fruit'; - parameter Real 'doop' = 60; - equation - ('juice'+'fruit')*'blade' = 'puree'; - 'juicy' * 'fruit' = 'juicyfruit'; - end JuiceModel; - """, BM.long_class_specifier) |> length == 10 +if GROUP == "All" || GROUP == "Quality" + @testset "Quality Assurance" begin + @safetestset "Quality Assurance" include("qa.jl") + end +end + +if GROUP == "All" || GROUP == "Core" + @testset "BaseModelica" begin + @safetestset "Parsing and Conversion Tests" begin + using BaseModelica + using ModelingToolkit + BM = BaseModelica + PC = BM.ParserCombinator - @test only(PC.parse_one(""" - package JuiceModel - model JuiceModel - Real 'juice.juice' \"juicy\"; - Real 'fruit' \"fruity\"; - Real 'T'; - output Real 'output_fruit'; - parameter Real 'soap' = 59; - equation - ('juice'+'fruit' + 100.0)*'blade' = 'puree' "smoothie?"; - 'juicy' * 'fruit' = 'juicyfruit'; - initial equation - 'juicy' = 1000; - 'fruit' = 2000; - end JuiceModel; - end JuiceModel;""",BM.base_modelica)) isa BM.BaseModelicaPackage + arith_test = only(PC.parse_one("5 + 6*(45 + 9^2)^2", BM.arithmetic_expression)) + @test arith_test isa BM.BaseModelicaSum + @test BM.eval_AST(arith_test) == 95261.0 - newton_path = joinpath( - pathof(BM), "test", "testfiles", "NewtonCoolingBase.mo") - newton_cooling = BM.parse_file("testfiles/NewtonCoolingBase.mo") - @test newton_cooling isa BM.BaseModelicaPackage - newton_system = BM.baseModelica_to_ModelingToolkit(newton_cooling.model) - @test newton_system isa ODESystem - @test parse_basemodelica("testfiles/NewtonCoolingBase.mo") isa ODESystem + newton_path = joinpath( + dirname(dirname(pathof(BM))), "test", "testfiles", "NewtonCoolingBase.mo") + newton_cooling = BM.parse_file(newton_path) + @test newton_cooling isa BM.BaseModelicaPackage + newton_system = BM.baseModelica_to_ModelingToolkit(newton_cooling) + @test newton_system isa ODESystem + @test parse_basemodelica("testfiles/NewtonCoolingBase.mo") isa ODESystem + end end end