From 100e2585b9c9b5fd98d5cd5c75bfe81895b1d40b Mon Sep 17 00:00:00 2001 From: ChrisRackauckas Date: Tue, 9 Sep 2025 06:10:59 -0400 Subject: [PATCH 1/2] Add support for experiment annotations within equation blocks (#38) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add BaseModelicaAnnotation AST node for annotations within models - Modify equation section parsing to allow annotations alongside equations - Update BaseModelicaComposition to handle annotations in equation lists - Add annotation evaluation that returns nothing (annotations are metadata) - Filter out nothing values from equation evaluation - Fixes #38: annotation(experiment(...)) now parses correctly in equation blocks The issue was that annotations like experiment were appearing inside equation blocks, but the parser only allowed pure equations there. Now annotations are parsed as BaseModelicaAnnotation nodes and treated as metadata. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/evaluator.jl | 8 +++++++- src/parser.jl | 11 ++++++++--- test/runtests.jl | 14 ++++++++++++++ test/testfiles/Experiment.mo | 8 ++++++++ 4 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 test/testfiles/Experiment.mo diff --git a/src/evaluator.jl b/src/evaluator.jl index db6eb3c..f92f008 100644 --- a/src/evaluator.jl +++ b/src/evaluator.jl @@ -49,6 +49,12 @@ function eval_AST(eq::BaseModelicaAnyEquation) return equation end +function eval_AST(annotation::BaseModelicaAnnotation) + # Annotations are metadata and don't produce equations + # For now, return nothing (they are parsed but ignored during evaluation) + return nothing +end + function eval_AST(eq::BaseModelicaSimpleEquation) lhs = eval_AST(eq.lhs) rhs = eval_AST(eq.rhs) @@ -102,7 +108,7 @@ function eval_AST(model::BaseModelicaModel) end end - eqs = [eval_AST(eq) for eq in equations] + eqs = filter(x -> x !== nothing, [eval_AST(eq) for eq in equations]) #vars_and_pars = merge(Dict(vars .=> vars), Dict(pars .=> pars)) #println(vars_and_pars) diff --git a/src/parser.jl b/src/parser.jl index 398afb6..7d63a2a 100644 --- a/src/parser.jl +++ b/src/parser.jl @@ -20,6 +20,7 @@ BaseModelicaForEquation(index, equations) BaseModelicaIfEquation(ifs, thens) BaseModelicaAnyEquation(equation, description) + BaseModelicaAnnotation(annotation_content) BaseModelicaForIndex(ident, expression) BaseModelicaComposition(components, equations, initial_equations) BaseModelicaLongClass(name, description, composition) @@ -301,6 +302,10 @@ function BaseModelicaComposition(input_list) push!(initial_equations, input) elseif input isa BaseModelicaAnyEquation push!(equations, input) + elseif input isa BaseModelicaAnnotation + # For now, treat annotations as part of equations + # (they appear in equation sections) + push!(equations, input) end end BaseModelicaComposition(components, equations, initial_equations) @@ -437,7 +442,7 @@ spc = Drop(Star(Space())) subscript = (E":" > BMColon) | expression array_subscripts.matcher = E"[" + subscript + Star(E"," + subscript) + E"]" |> BaseModelicaArraySubscripts - annotation_comment = E"annotation" + class_modification + annotation_comment = E"annotation" + class_modification > BaseModelicaAnnotation comment.matcher = (string_comment + annotation_comment[0:1]) |> (x -> length(x) == 1 ? x[1] : BaseModelicaString(join([string(elem) for elem in x], " "))) enumeration_literal = IDENT + comment @@ -463,9 +468,9 @@ spc = Drop(Star(Space())) 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)) | + Star((spc + e"equation" + spc + Star(spc + (equation | annotation_comment) + E";" + spc)) | (e"initial equation" + spc + - Star(spc + initial_equation + E";" + spc)) | + Star(spc + (initial_equation | annotation_comment) + 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] |> diff --git a/test/runtests.jl b/test/runtests.jl index f88bbcd..ff5b29c 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -24,6 +24,11 @@ if GROUP == "All" || GROUP == "Core" unary_minus_test = only(PC.parse_one("-5", BM.arithmetic_expression)) @test unary_minus_test isa BM.BaseModelicaUnaryMinus @test BM.eval_AST(unary_minus_test) == -5.0 + + # Test annotation parsing (issue #38) + annotation_test = only(PC.parse_one("annotation(experiment(StartTime = 0, StopTime = 2.0))", BM.annotation_comment)) + @test annotation_test isa BM.BaseModelicaAnnotation + @test BM.eval_AST(annotation_test) === nothing newton_path = joinpath( dirname(dirname(pathof(BM))), "test", "testfiles", "NewtonCoolingBase.mo") @@ -41,6 +46,15 @@ if GROUP == "All" || GROUP == "Core" negative_system = BM.baseModelica_to_ModelingToolkit(negative_package) @test negative_system isa ODESystem @test parse_basemodelica("testfiles/NegativeVariable.mo") isa ODESystem + + # Test experiment annotation parsing (issue #38) + experiment_path = joinpath( + dirname(dirname(pathof(BM))), "test", "testfiles", "Experiment.mo") + experiment_package = BM.parse_file(experiment_path) + @test experiment_package isa BM.BaseModelicaPackage + experiment_system = BM.baseModelica_to_ModelingToolkit(experiment_package) + @test experiment_system isa ODESystem + @test parse_basemodelica("testfiles/Experiment.mo") isa ODESystem end end end diff --git a/test/testfiles/Experiment.mo b/test/testfiles/Experiment.mo new file mode 100644 index 0000000..1ed8bda --- /dev/null +++ b/test/testfiles/Experiment.mo @@ -0,0 +1,8 @@ +package 'Experiment' + model 'Experiment' + Real 'x'; + equation + der('x') = 'x'; + annotation(experiment(StartTime = 0, StopTime = 2.0, Tolerance = 1e-06, Interval = 0.004)); + end 'Experiment'; +end 'Experiment'; \ No newline at end of file From 2c091488fd2ef44ba666f47f093374aba3d32ccb Mon Sep 17 00:00:00 2001 From: jClugstor Date: Sat, 27 Sep 2025 14:44:44 -0400 Subject: [PATCH 2/2] use correct symbol for parsing to Annotation --- src/parser.jl | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/parser.jl b/src/parser.jl index 7d63a2a..ba3ebb1 100644 --- a/src/parser.jl +++ b/src/parser.jl @@ -303,9 +303,6 @@ function BaseModelicaComposition(input_list) elseif input isa BaseModelicaAnyEquation push!(equations, input) elseif input isa BaseModelicaAnnotation - # For now, treat annotations as part of equations - # (they appear in equation sections) - push!(equations, input) end end BaseModelicaComposition(components, equations, initial_equations) @@ -442,7 +439,7 @@ spc = Drop(Star(Space())) subscript = (E":" > BMColon) | expression array_subscripts.matcher = E"[" + subscript + Star(E"," + subscript) + E"]" |> BaseModelicaArraySubscripts - annotation_comment = E"annotation" + class_modification > BaseModelicaAnnotation + annotation_comment = E"annotation" + class_modification |> BaseModelicaAnnotation comment.matcher = (string_comment + annotation_comment[0:1]) |> (x -> length(x) == 1 ? x[1] : BaseModelicaString(join([string(elem) for elem in x], " "))) enumeration_literal = IDENT + comment