From 9f0dc1cc0aa522b33c167f017ad26f36a2d9d71f Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 8 Sep 2026 02:33:30 +0000 Subject: [PATCH 01/11] feat(runtime): calc definitions, usages and calc parameters as function values A calc definition, a calc usage awaiting an input, or an `in calc` parameter read as a value is a function value: the lowered calc together with the scope, object and enclosing body frames it was read in, invoked through the calc invocation path. Calc-typed parameters accept one by position or name, `f(a)` invokes it, `SampledFunctions::Sample` samples a user calc, and a natively implemented library function is a value too. Function values cross the wire as `Value.function` (`calc_id`, optional `self_id`) under the `function_values` capability, exposed by the Go, Python, Node, Rust and Java clients. Native compilation refuses a calc that binds or applies a function value with a typed error. Co-Authored-By: jason.han --- api/proto/sysml.pb.go | 563 ++++++++------- api/proto/sysml.proto | 22 + changes/unreleased/function-values.added.md | 2 + client/opensysml/README.md | 11 +- client/opensysml/client.go | 27 +- client/opensysml/convert.go | 10 + client/opensysml/function_test.go | 202 ++++++ client/opensysml/types.go | 1 + client/opensysml/value.go | 22 +- clients/java/README.md | 2 +- .../org/openmbee/opensysml/Capabilities.java | 3 + .../java/org/openmbee/opensysml/Value.java | 30 + .../openmbee/opensysml/internal/Protos.java | 11 + .../openmbee/opensysml/proto/Function.java | 643 ++++++++++++++++++ .../opensysml/proto/FunctionOrBuilder.java | 45 ++ .../opensysml/proto/ServerInfoResponse.java | 65 ++ .../proto/ServerInfoResponseOrBuilder.java | 20 + .../org/openmbee/opensysml/proto/Sysml.java | 362 +++++----- .../org/openmbee/opensysml/proto/Value.java | 256 +++++++ .../opensysml/proto/ValueOrBuilder.java | 27 + .../opensysml/ApiIntegrationTest.java | 35 + .../opensysml/internal/ProtosTest.java | 34 + .../opensysml/conformance/Normalizer.java | 6 +- .../opensysml/conformance/Rendering.java | 5 + .../opensysml/conformance/NormalizerTest.java | 13 + clients/node/README.md | 9 +- clients/node/src/core/capabilities.ts | 2 + clients/node/src/core/index.ts | 2 + clients/node/src/core/values.ts | 34 + clients/node/src/generated/sysml_pb.ts | 111 ++- clients/node/test/client.test.ts | 32 + clients/node/test/values.test.ts | 25 + clients/python/opensysml/__init__.py | 4 +- clients/python/opensysml/capabilities.py | 7 + clients/python/opensysml/connection.py | 22 +- clients/python/opensysml/proto/sysml_pb2.py | 150 ++-- clients/python/opensysml/proto/sysml_pb2.pyi | 14 +- clients/python/opensysml/values.py | 53 +- clients/python/tests/test_function.py | 246 +++++++ clients/python/tests/test_wire_compat.py | 23 + clients/rust/README.md | 4 +- clients/rust/conformance/src/compare.rs | 18 +- clients/rust/conformance/src/normalize.rs | 28 +- .../rust/conformance/sysml.descriptor.binpb | Bin 65930 -> 67430 bytes clients/rust/opensysml/src/domain.rs | 72 ++ clients/rust/opensysml/src/lib.rs | 4 +- .../rust/opensysml/src/proto/sysml/sysml.rs | 28 +- clients/rust/opensysml/tests/client.rs | 48 +- cmd/conformance/normalize.go | 1 + cmd/conformance/normalize_test.go | 4 + cmd/conformance/pkgclient.go | 10 + conformance/README.md | 3 +- conformance/fixtures/function.sysml | 16 + conformance/scenarios/01-server-info.json | 1 + conformance/scenarios/04-evaluate.json | 71 ++ conformance/scenarios/10-evaluate-calc.json | 112 +++ docs/internals/architecture.md | 1 + docs/project/spec-compliance.md | 11 +- docs/reference/java-api.md | 1 + docs/reference/node-api.md | 1 + docs/reference/python-api.md | 1 + docs/reference/rust-api.md | 1 + docs/reference/service-transports.md | 6 +- docs/reference/wire-contract.md | 69 +- internal/core/codegen/compile.go | 3 + internal/core/parser/behavior.go | 1 + .../parse/action_calc_parameter.golden | 44 ++ .../parse/action_calc_parameter.sysml | 11 + internal/core/runtime/adopt.go | 41 ++ internal/core/runtime/adopt_test.go | 67 ++ internal/core/runtime/calc_usage.go | 11 +- internal/core/runtime/compile.go | 6 + internal/core/runtime/conformance_test.go | 10 + internal/core/runtime/describe.go | 2 + internal/core/runtime/errors.go | 4 + internal/core/runtime/eval.go | 164 ++++- internal/core/runtime/eval_no_value_test.go | 10 +- internal/core/runtime/function_value.go | 189 +++++ internal/core/runtime/invoke_calc.go | 47 +- internal/core/runtime/robustness_test.go | 142 ++++ .../runtime/testdata/conformance/README.md | 2 + ...ction_value_action_parameter.expected.json | 10 + .../function_value_action_parameter.sysml | 34 + .../function_value_body_closure.expected.json | 6 + .../function_value_body_closure.sysml | 23 + .../function_value_calc_usage.expected.json | 6 + .../function_value_calc_usage.sysml | 6 + .../function_value_chain_call.expected.json | 6 + .../function_value_chain_call.sysml | 9 + ...nction_value_feature_closure.expected.json | 6 + .../function_value_feature_closure.sysml | 11 + .../function_value_library.expected.json | 10 + .../conformance/function_value_library.sysml | 12 + .../function_value_named_args.expected.json | 6 + .../function_value_named_args.sysml | 10 + .../function_value_probe.expected.json | 7 + .../conformance/function_value_probe.sysml | 6 + .../function_value_probe.trace.golden | 20 + .../function_value_read.expected.json | 10 + .../conformance/function_value_read.sysml | 16 + .../function_value_sampled.expected.json | 6 + .../conformance/function_value_sampled.sysml | 9 + ...nction_value_sampled_closure.expected.json | 6 + .../function_value_sampled_closure.sysml | 13 + internal/core/runtime/trace.go | 2 + internal/core/runtime/value.go | 10 +- internal/core/runtime/value_equality.go | 11 + internal/core/runtime/value_kinds_test.go | 30 + internal/grpc/analysis.go | 2 +- internal/grpc/capability_response.go | 4 + internal/grpc/convert.go | 86 ++- internal/grpc/convert_function_test.go | 318 +++++++++ internal/grpc/service.go | 16 +- internal/grpc/verify.go | 2 +- internal/repl/compile_test.go | 2 + internal/repl/evalin_test.go | 12 +- internal/repl/meta.go | 10 + internal/repl/testdata/compile_calcs.sysml | 3 + 118 files changed, 4540 insertions(+), 632 deletions(-) create mode 100644 changes/unreleased/function-values.added.md create mode 100644 client/opensysml/function_test.go create mode 100644 clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/proto/Function.java create mode 100644 clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/proto/FunctionOrBuilder.java create mode 100644 clients/python/tests/test_function.py create mode 100644 conformance/fixtures/function.sysml create mode 100644 internal/core/parser/testdata/parse/action_calc_parameter.golden create mode 100644 internal/core/parser/testdata/parse/action_calc_parameter.sysml create mode 100644 internal/core/runtime/function_value.go create mode 100644 internal/core/runtime/testdata/conformance/function_value_action_parameter.expected.json create mode 100644 internal/core/runtime/testdata/conformance/function_value_action_parameter.sysml create mode 100644 internal/core/runtime/testdata/conformance/function_value_body_closure.expected.json create mode 100644 internal/core/runtime/testdata/conformance/function_value_body_closure.sysml create mode 100644 internal/core/runtime/testdata/conformance/function_value_calc_usage.expected.json create mode 100644 internal/core/runtime/testdata/conformance/function_value_calc_usage.sysml create mode 100644 internal/core/runtime/testdata/conformance/function_value_chain_call.expected.json create mode 100644 internal/core/runtime/testdata/conformance/function_value_chain_call.sysml create mode 100644 internal/core/runtime/testdata/conformance/function_value_feature_closure.expected.json create mode 100644 internal/core/runtime/testdata/conformance/function_value_feature_closure.sysml create mode 100644 internal/core/runtime/testdata/conformance/function_value_library.expected.json create mode 100644 internal/core/runtime/testdata/conformance/function_value_library.sysml create mode 100644 internal/core/runtime/testdata/conformance/function_value_named_args.expected.json create mode 100644 internal/core/runtime/testdata/conformance/function_value_named_args.sysml create mode 100644 internal/core/runtime/testdata/conformance/function_value_probe.expected.json create mode 100644 internal/core/runtime/testdata/conformance/function_value_probe.sysml create mode 100644 internal/core/runtime/testdata/conformance/function_value_probe.trace.golden create mode 100644 internal/core/runtime/testdata/conformance/function_value_read.expected.json create mode 100644 internal/core/runtime/testdata/conformance/function_value_read.sysml create mode 100644 internal/core/runtime/testdata/conformance/function_value_sampled.expected.json create mode 100644 internal/core/runtime/testdata/conformance/function_value_sampled.sysml create mode 100644 internal/core/runtime/testdata/conformance/function_value_sampled_closure.expected.json create mode 100644 internal/core/runtime/testdata/conformance/function_value_sampled_closure.sysml create mode 100644 internal/grpc/convert_function_test.go diff --git a/api/proto/sysml.pb.go b/api/proto/sysml.pb.go index ad45337ab..33aa3cf89 100644 --- a/api/proto/sysml.pb.go +++ b/api/proto/sysml.pb.go @@ -3757,6 +3757,7 @@ type Value struct { // *Value_Vector // *Value_VectorQuantity // *Value_MeasurementRef + // *Value_Function Kind isValue_Kind `protobuf_oneof:"kind"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -3934,6 +3935,15 @@ func (x *Value) GetMeasurementRef() *MeasurementRef { return nil } +func (x *Value) GetFunction() *Function { + if x != nil { + if x, ok := x.Kind.(*Value_Function); ok { + return x.Function + } + } + return nil +} + type isValue_Kind interface { isValue_Kind() } @@ -4000,6 +4010,10 @@ type Value_MeasurementRef struct { MeasurementRef *MeasurementRef `protobuf:"bytes,15,opt,name=measurement_ref,json=measurementRef,proto3,oneof"` // a unit by itself, no magnitude } +type Value_Function struct { + Function *Function `protobuf:"bytes,16,opt,name=function,proto3,oneof"` // a calc as a value, named by its declaration +} + func (*Value_IntValue) isValue_Kind() {} func (*Value_RealValue) isValue_Kind() {} @@ -4030,6 +4044,71 @@ func (*Value_VectorQuantity) isValue_Kind() {} func (*Value_MeasurementRef) isValue_Kind() {} +func (*Value_Function) isValue_Kind() {} + +// Function is a calc held as a value: a calc definition, or a calc usage with +// an input no read could supply, as `Sq` in `Fn(Sq, 3.0)` or the `f` of +// `in calc f {...}`. It crosses as the declaration it is a value of, which is +// its identity: two functions are the same exactly when calc_id and self_id +// are. A function closing over the bindings of the behavior body it is +// declared in has no wire form and crosses as the null arm. +type Function struct { + state protoimpl.MessageState `protogen:"open.v1"` + // FQN of the calc declaration ("Analysis::Sq"). Its identity. + CalcId string `protobuf:"bytes,1,opt,name=calc_id,json=calcId,proto3" json:"calc_id,omitempty"` + // ID of the object the calc's feature names resolve against, for a calc + // usage read off a part (`holder.scale`); 0 for a function closing over no + // object. Sent by the service; a client sending one must name an object of + // the runtime the value is read in, or the value is rejected. + SelfId int64 `protobuf:"varint,2,opt,name=self_id,json=selfId,proto3" json:"self_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Function) Reset() { + *x = Function{} + mi := &file_sysml_proto_msgTypes[47] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Function) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Function) ProtoMessage() {} + +func (x *Function) ProtoReflect() protoreflect.Message { + mi := &file_sysml_proto_msgTypes[47] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Function.ProtoReflect.Descriptor instead. +func (*Function) Descriptor() ([]byte, []int) { + return file_sysml_proto_rawDescGZIP(), []int{47} +} + +func (x *Function) GetCalcId() string { + if x != nil { + return x.CalcId + } + return "" +} + +func (x *Function) GetSelfId() int64 { + if x != nil { + return x.SelfId + } + return 0 +} + // Array is a Collections::Array: its elements flattened in row-major order // under its dimensions, compared by content rather than by the object read. type Array struct { @@ -4045,7 +4124,7 @@ type Array struct { func (x *Array) Reset() { *x = Array{} - mi := &file_sysml_proto_msgTypes[47] + mi := &file_sysml_proto_msgTypes[48] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4057,7 +4136,7 @@ func (x *Array) String() string { func (*Array) ProtoMessage() {} func (x *Array) ProtoReflect() protoreflect.Message { - mi := &file_sysml_proto_msgTypes[47] + mi := &file_sysml_proto_msgTypes[48] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4070,7 +4149,7 @@ func (x *Array) ProtoReflect() protoreflect.Message { // Deprecated: Use Array.ProtoReflect.Descriptor instead. func (*Array) Descriptor() ([]byte, []int) { - return file_sysml_proto_rawDescGZIP(), []int{47} + return file_sysml_proto_rawDescGZIP(), []int{48} } func (x *Array) GetDimensions() []int64 { @@ -4100,7 +4179,7 @@ type Vector struct { func (x *Vector) Reset() { *x = Vector{} - mi := &file_sysml_proto_msgTypes[48] + mi := &file_sysml_proto_msgTypes[49] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4112,7 +4191,7 @@ func (x *Vector) String() string { func (*Vector) ProtoMessage() {} func (x *Vector) ProtoReflect() protoreflect.Message { - mi := &file_sysml_proto_msgTypes[48] + mi := &file_sysml_proto_msgTypes[49] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4125,7 +4204,7 @@ func (x *Vector) ProtoReflect() protoreflect.Message { // Deprecated: Use Vector.ProtoReflect.Descriptor instead. func (*Vector) Descriptor() ([]byte, []int) { - return file_sysml_proto_rawDescGZIP(), []int{48} + return file_sysml_proto_rawDescGZIP(), []int{49} } func (x *Vector) GetComponents() []*Value { @@ -4148,7 +4227,7 @@ type VectorQuantity struct { func (x *VectorQuantity) Reset() { *x = VectorQuantity{} - mi := &file_sysml_proto_msgTypes[49] + mi := &file_sysml_proto_msgTypes[50] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4160,7 +4239,7 @@ func (x *VectorQuantity) String() string { func (*VectorQuantity) ProtoMessage() {} func (x *VectorQuantity) ProtoReflect() protoreflect.Message { - mi := &file_sysml_proto_msgTypes[49] + mi := &file_sysml_proto_msgTypes[50] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4173,7 +4252,7 @@ func (x *VectorQuantity) ProtoReflect() protoreflect.Message { // Deprecated: Use VectorQuantity.ProtoReflect.Descriptor instead. func (*VectorQuantity) Descriptor() ([]byte, []int) { - return file_sysml_proto_rawDescGZIP(), []int{49} + return file_sysml_proto_rawDescGZIP(), []int{50} } func (x *VectorQuantity) GetComponents() []*Quantity { @@ -4195,7 +4274,7 @@ type Complex struct { func (x *Complex) Reset() { *x = Complex{} - mi := &file_sysml_proto_msgTypes[50] + mi := &file_sysml_proto_msgTypes[51] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4207,7 +4286,7 @@ func (x *Complex) String() string { func (*Complex) ProtoMessage() {} func (x *Complex) ProtoReflect() protoreflect.Message { - mi := &file_sysml_proto_msgTypes[50] + mi := &file_sysml_proto_msgTypes[51] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4220,7 +4299,7 @@ func (x *Complex) ProtoReflect() protoreflect.Message { // Deprecated: Use Complex.ProtoReflect.Descriptor instead. func (*Complex) Descriptor() ([]byte, []int) { - return file_sysml_proto_rawDescGZIP(), []int{50} + return file_sysml_proto_rawDescGZIP(), []int{51} } func (x *Complex) GetReal() float64 { @@ -4254,7 +4333,7 @@ type EnumLiteral struct { func (x *EnumLiteral) Reset() { *x = EnumLiteral{} - mi := &file_sysml_proto_msgTypes[51] + mi := &file_sysml_proto_msgTypes[52] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4266,7 +4345,7 @@ func (x *EnumLiteral) String() string { func (*EnumLiteral) ProtoMessage() {} func (x *EnumLiteral) ProtoReflect() protoreflect.Message { - mi := &file_sysml_proto_msgTypes[51] + mi := &file_sysml_proto_msgTypes[52] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4279,7 +4358,7 @@ func (x *EnumLiteral) ProtoReflect() protoreflect.Message { // Deprecated: Use EnumLiteral.ProtoReflect.Descriptor instead. func (*EnumLiteral) Descriptor() ([]byte, []int) { - return file_sysml_proto_rawDescGZIP(), []int{51} + return file_sysml_proto_rawDescGZIP(), []int{52} } func (x *EnumLiteral) GetLiteralId() string { @@ -4312,7 +4391,7 @@ type ValueSequence struct { func (x *ValueSequence) Reset() { *x = ValueSequence{} - mi := &file_sysml_proto_msgTypes[52] + mi := &file_sysml_proto_msgTypes[53] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4324,7 +4403,7 @@ func (x *ValueSequence) String() string { func (*ValueSequence) ProtoMessage() {} func (x *ValueSequence) ProtoReflect() protoreflect.Message { - mi := &file_sysml_proto_msgTypes[52] + mi := &file_sysml_proto_msgTypes[53] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4337,7 +4416,7 @@ func (x *ValueSequence) ProtoReflect() protoreflect.Message { // Deprecated: Use ValueSequence.ProtoReflect.Descriptor instead. func (*ValueSequence) Descriptor() ([]byte, []int) { - return file_sysml_proto_rawDescGZIP(), []int{52} + return file_sysml_proto_rawDescGZIP(), []int{53} } func (x *ValueSequence) GetElements() []*Value { @@ -4371,7 +4450,7 @@ type Quantity struct { func (x *Quantity) Reset() { *x = Quantity{} - mi := &file_sysml_proto_msgTypes[53] + mi := &file_sysml_proto_msgTypes[54] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4383,7 +4462,7 @@ func (x *Quantity) String() string { func (*Quantity) ProtoMessage() {} func (x *Quantity) ProtoReflect() protoreflect.Message { - mi := &file_sysml_proto_msgTypes[53] + mi := &file_sysml_proto_msgTypes[54] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4396,7 +4475,7 @@ func (x *Quantity) ProtoReflect() protoreflect.Message { // Deprecated: Use Quantity.ProtoReflect.Descriptor instead. func (*Quantity) Descriptor() ([]byte, []int) { - return file_sysml_proto_rawDescGZIP(), []int{53} + return file_sysml_proto_rawDescGZIP(), []int{54} } func (x *Quantity) GetMagnitude() isQuantity_Magnitude { @@ -4482,7 +4561,7 @@ type MeasurementRef struct { func (x *MeasurementRef) Reset() { *x = MeasurementRef{} - mi := &file_sysml_proto_msgTypes[54] + mi := &file_sysml_proto_msgTypes[55] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4494,7 +4573,7 @@ func (x *MeasurementRef) String() string { func (*MeasurementRef) ProtoMessage() {} func (x *MeasurementRef) ProtoReflect() protoreflect.Message { - mi := &file_sysml_proto_msgTypes[54] + mi := &file_sysml_proto_msgTypes[55] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4507,7 +4586,7 @@ func (x *MeasurementRef) ProtoReflect() protoreflect.Message { // Deprecated: Use MeasurementRef.ProtoReflect.Descriptor instead. func (*MeasurementRef) Descriptor() ([]byte, []int) { - return file_sysml_proto_rawDescGZIP(), []int{54} + return file_sysml_proto_rawDescGZIP(), []int{55} } func (x *MeasurementRef) GetUnit() string { @@ -4546,7 +4625,7 @@ type UnitTerm struct { func (x *UnitTerm) Reset() { *x = UnitTerm{} - mi := &file_sysml_proto_msgTypes[55] + mi := &file_sysml_proto_msgTypes[56] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4558,7 +4637,7 @@ func (x *UnitTerm) String() string { func (*UnitTerm) ProtoMessage() {} func (x *UnitTerm) ProtoReflect() protoreflect.Message { - mi := &file_sysml_proto_msgTypes[55] + mi := &file_sysml_proto_msgTypes[56] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4571,7 +4650,7 @@ func (x *UnitTerm) ProtoReflect() protoreflect.Message { // Deprecated: Use UnitTerm.ProtoReflect.Descriptor instead. func (*UnitTerm) Descriptor() ([]byte, []int) { - return file_sysml_proto_rawDescGZIP(), []int{55} + return file_sysml_proto_rawDescGZIP(), []int{56} } func (x *UnitTerm) GetScaleNum() float64 { @@ -4607,7 +4686,7 @@ type UnitFactor struct { func (x *UnitFactor) Reset() { *x = UnitFactor{} - mi := &file_sysml_proto_msgTypes[56] + mi := &file_sysml_proto_msgTypes[57] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4619,7 +4698,7 @@ func (x *UnitFactor) String() string { func (*UnitFactor) ProtoMessage() {} func (x *UnitFactor) ProtoReflect() protoreflect.Message { - mi := &file_sysml_proto_msgTypes[56] + mi := &file_sysml_proto_msgTypes[57] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4632,7 +4711,7 @@ func (x *UnitFactor) ProtoReflect() protoreflect.Message { // Deprecated: Use UnitFactor.ProtoReflect.Descriptor instead. func (*UnitFactor) Descriptor() ([]byte, []int) { - return file_sysml_proto_rawDescGZIP(), []int{56} + return file_sysml_proto_rawDescGZIP(), []int{57} } func (x *UnitFactor) GetUnitId() string { @@ -4661,7 +4740,7 @@ type Diagnostic struct { func (x *Diagnostic) Reset() { *x = Diagnostic{} - mi := &file_sysml_proto_msgTypes[57] + mi := &file_sysml_proto_msgTypes[58] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4673,7 +4752,7 @@ func (x *Diagnostic) String() string { func (*Diagnostic) ProtoMessage() {} func (x *Diagnostic) ProtoReflect() protoreflect.Message { - mi := &file_sysml_proto_msgTypes[57] + mi := &file_sysml_proto_msgTypes[58] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4686,7 +4765,7 @@ func (x *Diagnostic) ProtoReflect() protoreflect.Message { // Deprecated: Use Diagnostic.ProtoReflect.Descriptor instead. func (*Diagnostic) Descriptor() ([]byte, []int) { - return file_sysml_proto_rawDescGZIP(), []int{57} + return file_sysml_proto_rawDescGZIP(), []int{58} } func (x *Diagnostic) GetSeverity() string { @@ -4724,7 +4803,7 @@ type Span struct { func (x *Span) Reset() { *x = Span{} - mi := &file_sysml_proto_msgTypes[58] + mi := &file_sysml_proto_msgTypes[59] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4736,7 +4815,7 @@ func (x *Span) String() string { func (*Span) ProtoMessage() {} func (x *Span) ProtoReflect() protoreflect.Message { - mi := &file_sysml_proto_msgTypes[58] + mi := &file_sysml_proto_msgTypes[59] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4749,7 +4828,7 @@ func (x *Span) ProtoReflect() protoreflect.Message { // Deprecated: Use Span.ProtoReflect.Descriptor instead. func (*Span) Descriptor() ([]byte, []int) { - return file_sysml_proto_rawDescGZIP(), []int{58} + return file_sysml_proto_rawDescGZIP(), []int{59} } func (x *Span) GetFile() string { @@ -4797,7 +4876,7 @@ type ServerInfoRequest struct { func (x *ServerInfoRequest) Reset() { *x = ServerInfoRequest{} - mi := &file_sysml_proto_msgTypes[59] + mi := &file_sysml_proto_msgTypes[60] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4809,7 +4888,7 @@ func (x *ServerInfoRequest) String() string { func (*ServerInfoRequest) ProtoMessage() {} func (x *ServerInfoRequest) ProtoReflect() protoreflect.Message { - mi := &file_sysml_proto_msgTypes[59] + mi := &file_sysml_proto_msgTypes[60] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4822,7 +4901,7 @@ func (x *ServerInfoRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ServerInfoRequest.ProtoReflect.Descriptor instead. func (*ServerInfoRequest) Descriptor() ([]byte, []int) { - return file_sysml_proto_rawDescGZIP(), []int{59} + return file_sysml_proto_rawDescGZIP(), []int{60} } // ServerInfoResponse describes the running service. @@ -4872,6 +4951,11 @@ type ServerInfoResponse struct { // refused with UNIMPLEMENTED rather than read as another // value. Separate from structured_values, which a client // built before this arm existed may already claim. + // "function_values" - a Value carries a calc held as a value as function, + // named by its declaration, rather than reporting it as an + // unsupported null, and one is accepted as an action input + // or calc argument; without it, one is refused with + // UNIMPLEMENTED rather than read as another value. // "apply_edits" - the ApplyEdits RPC edits a parsed model's own source, // preserving everything the edit did not touch. // "document_query" - the RunDocumentQuery RPC runs a named document query @@ -4885,7 +4969,7 @@ type ServerInfoResponse struct { func (x *ServerInfoResponse) Reset() { *x = ServerInfoResponse{} - mi := &file_sysml_proto_msgTypes[60] + mi := &file_sysml_proto_msgTypes[61] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4897,7 +4981,7 @@ func (x *ServerInfoResponse) String() string { func (*ServerInfoResponse) ProtoMessage() {} func (x *ServerInfoResponse) ProtoReflect() protoreflect.Message { - mi := &file_sysml_proto_msgTypes[60] + mi := &file_sysml_proto_msgTypes[61] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4910,7 +4994,7 @@ func (x *ServerInfoResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ServerInfoResponse.ProtoReflect.Descriptor instead. func (*ServerInfoResponse) Descriptor() ([]byte, []int) { - return file_sysml_proto_rawDescGZIP(), []int{60} + return file_sysml_proto_rawDescGZIP(), []int{61} } func (x *ServerInfoResponse) GetVersion() string { @@ -4940,7 +5024,7 @@ type QueryRequest struct { func (x *QueryRequest) Reset() { *x = QueryRequest{} - mi := &file_sysml_proto_msgTypes[61] + mi := &file_sysml_proto_msgTypes[62] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4952,7 +5036,7 @@ func (x *QueryRequest) String() string { func (*QueryRequest) ProtoMessage() {} func (x *QueryRequest) ProtoReflect() protoreflect.Message { - mi := &file_sysml_proto_msgTypes[61] + mi := &file_sysml_proto_msgTypes[62] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4965,7 +5049,7 @@ func (x *QueryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use QueryRequest.ProtoReflect.Descriptor instead. func (*QueryRequest) Descriptor() ([]byte, []int) { - return file_sysml_proto_rawDescGZIP(), []int{61} + return file_sysml_proto_rawDescGZIP(), []int{62} } func (x *QueryRequest) GetModelHash() string { @@ -5002,7 +5086,7 @@ type QueryResponse struct { func (x *QueryResponse) Reset() { *x = QueryResponse{} - mi := &file_sysml_proto_msgTypes[62] + mi := &file_sysml_proto_msgTypes[63] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5014,7 +5098,7 @@ func (x *QueryResponse) String() string { func (*QueryResponse) ProtoMessage() {} func (x *QueryResponse) ProtoReflect() protoreflect.Message { - mi := &file_sysml_proto_msgTypes[62] + mi := &file_sysml_proto_msgTypes[63] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5027,7 +5111,7 @@ func (x *QueryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use QueryResponse.ProtoReflect.Descriptor instead. func (*QueryResponse) Descriptor() ([]byte, []int) { - return file_sysml_proto_rawDescGZIP(), []int{62} + return file_sysml_proto_rawDescGZIP(), []int{63} } func (x *QueryResponse) GetElements() []*QueryResultElement { @@ -5057,7 +5141,7 @@ type Query struct { func (x *Query) Reset() { *x = Query{} - mi := &file_sysml_proto_msgTypes[63] + mi := &file_sysml_proto_msgTypes[64] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5069,7 +5153,7 @@ func (x *Query) String() string { func (*Query) ProtoMessage() {} func (x *Query) ProtoReflect() protoreflect.Message { - mi := &file_sysml_proto_msgTypes[63] + mi := &file_sysml_proto_msgTypes[64] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5082,7 +5166,7 @@ func (x *Query) ProtoReflect() protoreflect.Message { // Deprecated: Use Query.ProtoReflect.Descriptor instead. func (*Query) Descriptor() ([]byte, []int) { - return file_sysml_proto_rawDescGZIP(), []int{63} + return file_sysml_proto_rawDescGZIP(), []int{64} } func (x *Query) GetScope() []string { @@ -5121,7 +5205,7 @@ type Constraint struct { func (x *Constraint) Reset() { *x = Constraint{} - mi := &file_sysml_proto_msgTypes[64] + mi := &file_sysml_proto_msgTypes[65] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5133,7 +5217,7 @@ func (x *Constraint) String() string { func (*Constraint) ProtoMessage() {} func (x *Constraint) ProtoReflect() protoreflect.Message { - mi := &file_sysml_proto_msgTypes[64] + mi := &file_sysml_proto_msgTypes[65] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5146,7 +5230,7 @@ func (x *Constraint) ProtoReflect() protoreflect.Message { // Deprecated: Use Constraint.ProtoReflect.Descriptor instead. func (*Constraint) Descriptor() ([]byte, []int) { - return file_sysml_proto_rawDescGZIP(), []int{64} + return file_sysml_proto_rawDescGZIP(), []int{65} } func (x *Constraint) GetConstraint() isConstraint_Constraint { @@ -5209,7 +5293,7 @@ type PrimitiveConstraint struct { func (x *PrimitiveConstraint) Reset() { *x = PrimitiveConstraint{} - mi := &file_sysml_proto_msgTypes[65] + mi := &file_sysml_proto_msgTypes[66] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5221,7 +5305,7 @@ func (x *PrimitiveConstraint) String() string { func (*PrimitiveConstraint) ProtoMessage() {} func (x *PrimitiveConstraint) ProtoReflect() protoreflect.Message { - mi := &file_sysml_proto_msgTypes[65] + mi := &file_sysml_proto_msgTypes[66] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5234,7 +5318,7 @@ func (x *PrimitiveConstraint) ProtoReflect() protoreflect.Message { // Deprecated: Use PrimitiveConstraint.ProtoReflect.Descriptor instead. func (*PrimitiveConstraint) Descriptor() ([]byte, []int) { - return file_sysml_proto_rawDescGZIP(), []int{65} + return file_sysml_proto_rawDescGZIP(), []int{66} } func (x *PrimitiveConstraint) GetInverse() bool { @@ -5277,7 +5361,7 @@ type CompositeConstraint struct { func (x *CompositeConstraint) Reset() { *x = CompositeConstraint{} - mi := &file_sysml_proto_msgTypes[66] + mi := &file_sysml_proto_msgTypes[67] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5289,7 +5373,7 @@ func (x *CompositeConstraint) String() string { func (*CompositeConstraint) ProtoMessage() {} func (x *CompositeConstraint) ProtoReflect() protoreflect.Message { - mi := &file_sysml_proto_msgTypes[66] + mi := &file_sysml_proto_msgTypes[67] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5302,7 +5386,7 @@ func (x *CompositeConstraint) ProtoReflect() protoreflect.Message { // Deprecated: Use CompositeConstraint.ProtoReflect.Descriptor instead. func (*CompositeConstraint) Descriptor() ([]byte, []int) { - return file_sysml_proto_rawDescGZIP(), []int{66} + return file_sysml_proto_rawDescGZIP(), []int{67} } func (x *CompositeConstraint) GetOperator() CompositeOperator { @@ -5334,7 +5418,7 @@ type QueryResultElement struct { func (x *QueryResultElement) Reset() { *x = QueryResultElement{} - mi := &file_sysml_proto_msgTypes[67] + mi := &file_sysml_proto_msgTypes[68] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5346,7 +5430,7 @@ func (x *QueryResultElement) String() string { func (*QueryResultElement) ProtoMessage() {} func (x *QueryResultElement) ProtoReflect() protoreflect.Message { - mi := &file_sysml_proto_msgTypes[67] + mi := &file_sysml_proto_msgTypes[68] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5359,7 +5443,7 @@ func (x *QueryResultElement) ProtoReflect() protoreflect.Message { // Deprecated: Use QueryResultElement.ProtoReflect.Descriptor instead. func (*QueryResultElement) Descriptor() ([]byte, []int) { - return file_sysml_proto_rawDescGZIP(), []int{67} + return file_sysml_proto_rawDescGZIP(), []int{68} } func (x *QueryResultElement) GetId() string { @@ -5402,7 +5486,7 @@ type RunDocumentQueryRequest struct { func (x *RunDocumentQueryRequest) Reset() { *x = RunDocumentQueryRequest{} - mi := &file_sysml_proto_msgTypes[68] + mi := &file_sysml_proto_msgTypes[69] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5414,7 +5498,7 @@ func (x *RunDocumentQueryRequest) String() string { func (*RunDocumentQueryRequest) ProtoMessage() {} func (x *RunDocumentQueryRequest) ProtoReflect() protoreflect.Message { - mi := &file_sysml_proto_msgTypes[68] + mi := &file_sysml_proto_msgTypes[69] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5427,7 +5511,7 @@ func (x *RunDocumentQueryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RunDocumentQueryRequest.ProtoReflect.Descriptor instead. func (*RunDocumentQueryRequest) Descriptor() ([]byte, []int) { - return file_sysml_proto_rawDescGZIP(), []int{68} + return file_sysml_proto_rawDescGZIP(), []int{69} } func (x *RunDocumentQueryRequest) GetModelHash() string { @@ -5462,7 +5546,7 @@ type DocumentQueryBinding struct { func (x *DocumentQueryBinding) Reset() { *x = DocumentQueryBinding{} - mi := &file_sysml_proto_msgTypes[69] + mi := &file_sysml_proto_msgTypes[70] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5474,7 +5558,7 @@ func (x *DocumentQueryBinding) String() string { func (*DocumentQueryBinding) ProtoMessage() {} func (x *DocumentQueryBinding) ProtoReflect() protoreflect.Message { - mi := &file_sysml_proto_msgTypes[69] + mi := &file_sysml_proto_msgTypes[70] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5487,7 +5571,7 @@ func (x *DocumentQueryBinding) ProtoReflect() protoreflect.Message { // Deprecated: Use DocumentQueryBinding.ProtoReflect.Descriptor instead. func (*DocumentQueryBinding) Descriptor() ([]byte, []int) { - return file_sysml_proto_rawDescGZIP(), []int{69} + return file_sysml_proto_rawDescGZIP(), []int{70} } func (x *DocumentQueryBinding) GetParameter() string { @@ -5528,7 +5612,7 @@ type DocumentValue struct { func (x *DocumentValue) Reset() { *x = DocumentValue{} - mi := &file_sysml_proto_msgTypes[70] + mi := &file_sysml_proto_msgTypes[71] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5540,7 +5624,7 @@ func (x *DocumentValue) String() string { func (*DocumentValue) ProtoMessage() {} func (x *DocumentValue) ProtoReflect() protoreflect.Message { - mi := &file_sysml_proto_msgTypes[70] + mi := &file_sysml_proto_msgTypes[71] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5553,7 +5637,7 @@ func (x *DocumentValue) ProtoReflect() protoreflect.Message { // Deprecated: Use DocumentValue.ProtoReflect.Descriptor instead. func (*DocumentValue) Descriptor() ([]byte, []int) { - return file_sysml_proto_rawDescGZIP(), []int{70} + return file_sysml_proto_rawDescGZIP(), []int{71} } func (x *DocumentValue) GetKind() isDocumentValue_Kind { @@ -5689,7 +5773,7 @@ type DocumentQueryColumn struct { func (x *DocumentQueryColumn) Reset() { *x = DocumentQueryColumn{} - mi := &file_sysml_proto_msgTypes[71] + mi := &file_sysml_proto_msgTypes[72] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5701,7 +5785,7 @@ func (x *DocumentQueryColumn) String() string { func (*DocumentQueryColumn) ProtoMessage() {} func (x *DocumentQueryColumn) ProtoReflect() protoreflect.Message { - mi := &file_sysml_proto_msgTypes[71] + mi := &file_sysml_proto_msgTypes[72] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5714,7 +5798,7 @@ func (x *DocumentQueryColumn) ProtoReflect() protoreflect.Message { // Deprecated: Use DocumentQueryColumn.ProtoReflect.Descriptor instead. func (*DocumentQueryColumn) Descriptor() ([]byte, []int) { - return file_sysml_proto_rawDescGZIP(), []int{71} + return file_sysml_proto_rawDescGZIP(), []int{72} } func (x *DocumentQueryColumn) GetName() string { @@ -5734,7 +5818,7 @@ type DocumentQueryCell struct { func (x *DocumentQueryCell) Reset() { *x = DocumentQueryCell{} - mi := &file_sysml_proto_msgTypes[72] + mi := &file_sysml_proto_msgTypes[73] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5746,7 +5830,7 @@ func (x *DocumentQueryCell) String() string { func (*DocumentQueryCell) ProtoMessage() {} func (x *DocumentQueryCell) ProtoReflect() protoreflect.Message { - mi := &file_sysml_proto_msgTypes[72] + mi := &file_sysml_proto_msgTypes[73] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5759,7 +5843,7 @@ func (x *DocumentQueryCell) ProtoReflect() protoreflect.Message { // Deprecated: Use DocumentQueryCell.ProtoReflect.Descriptor instead. func (*DocumentQueryCell) Descriptor() ([]byte, []int) { - return file_sysml_proto_rawDescGZIP(), []int{72} + return file_sysml_proto_rawDescGZIP(), []int{73} } func (x *DocumentQueryCell) GetValues() []*DocumentValue { @@ -5782,7 +5866,7 @@ type DocumentQueryRow struct { func (x *DocumentQueryRow) Reset() { *x = DocumentQueryRow{} - mi := &file_sysml_proto_msgTypes[73] + mi := &file_sysml_proto_msgTypes[74] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5794,7 +5878,7 @@ func (x *DocumentQueryRow) String() string { func (*DocumentQueryRow) ProtoMessage() {} func (x *DocumentQueryRow) ProtoReflect() protoreflect.Message { - mi := &file_sysml_proto_msgTypes[73] + mi := &file_sysml_proto_msgTypes[74] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5807,7 +5891,7 @@ func (x *DocumentQueryRow) ProtoReflect() protoreflect.Message { // Deprecated: Use DocumentQueryRow.ProtoReflect.Descriptor instead. func (*DocumentQueryRow) Descriptor() ([]byte, []int) { - return file_sysml_proto_rawDescGZIP(), []int{73} + return file_sysml_proto_rawDescGZIP(), []int{74} } func (x *DocumentQueryRow) GetElement() *DocumentValue { @@ -5838,7 +5922,7 @@ type RunDocumentQueryResponse struct { func (x *RunDocumentQueryResponse) Reset() { *x = RunDocumentQueryResponse{} - mi := &file_sysml_proto_msgTypes[74] + mi := &file_sysml_proto_msgTypes[75] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5850,7 +5934,7 @@ func (x *RunDocumentQueryResponse) String() string { func (*RunDocumentQueryResponse) ProtoMessage() {} func (x *RunDocumentQueryResponse) ProtoReflect() protoreflect.Message { - mi := &file_sysml_proto_msgTypes[74] + mi := &file_sysml_proto_msgTypes[75] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5863,7 +5947,7 @@ func (x *RunDocumentQueryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RunDocumentQueryResponse.ProtoReflect.Descriptor instead. func (*RunDocumentQueryResponse) Descriptor() ([]byte, []int) { - return file_sysml_proto_rawDescGZIP(), []int{74} + return file_sysml_proto_rawDescGZIP(), []int{75} } func (x *RunDocumentQueryResponse) GetColumns() []*DocumentQueryColumn { @@ -5895,7 +5979,7 @@ type RenderDocumentRequest struct { func (x *RenderDocumentRequest) Reset() { *x = RenderDocumentRequest{} - mi := &file_sysml_proto_msgTypes[75] + mi := &file_sysml_proto_msgTypes[76] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5907,7 +5991,7 @@ func (x *RenderDocumentRequest) String() string { func (*RenderDocumentRequest) ProtoMessage() {} func (x *RenderDocumentRequest) ProtoReflect() protoreflect.Message { - mi := &file_sysml_proto_msgTypes[75] + mi := &file_sysml_proto_msgTypes[76] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5920,7 +6004,7 @@ func (x *RenderDocumentRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RenderDocumentRequest.ProtoReflect.Descriptor instead. func (*RenderDocumentRequest) Descriptor() ([]byte, []int) { - return file_sysml_proto_rawDescGZIP(), []int{75} + return file_sysml_proto_rawDescGZIP(), []int{76} } func (x *RenderDocumentRequest) GetModelHash() string { @@ -5948,7 +6032,7 @@ type RenderDocumentResponse struct { func (x *RenderDocumentResponse) Reset() { *x = RenderDocumentResponse{} - mi := &file_sysml_proto_msgTypes[76] + mi := &file_sysml_proto_msgTypes[77] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5960,7 +6044,7 @@ func (x *RenderDocumentResponse) String() string { func (*RenderDocumentResponse) ProtoMessage() {} func (x *RenderDocumentResponse) ProtoReflect() protoreflect.Message { - mi := &file_sysml_proto_msgTypes[76] + mi := &file_sysml_proto_msgTypes[77] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5973,7 +6057,7 @@ func (x *RenderDocumentResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RenderDocumentResponse.ProtoReflect.Descriptor instead. func (*RenderDocumentResponse) Descriptor() ([]byte, []int) { - return file_sysml_proto_rawDescGZIP(), []int{76} + return file_sysml_proto_rawDescGZIP(), []int{77} } func (x *RenderDocumentResponse) GetMarkdown() string { @@ -6270,7 +6354,7 @@ const file_sysml_proto_rawDesc = "" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x12\n" + "\x04type\x18\x02 \x01(\tR\x04type\x12\"\n" + "\x05value\x18\x03 \x01(\v2\f.sysml.ValueR\x05value\x12\x12\n" + - "\x04unit\x18\x04 \x01(\tR\x04unit\"\x81\x05\n" + + "\x04unit\x18\x04 \x01(\tR\x04unit\"\xb0\x05\n" + "\x05Value\x12\x1d\n" + "\tint_value\x18\x01 \x01(\x03H\x00R\bintValue\x12\x1f\n" + "\n" + @@ -6290,8 +6374,12 @@ const file_sysml_proto_rawDesc = "" + "\x05array\x18\f \x01(\v2\f.sysml.ArrayH\x00R\x05array\x12'\n" + "\x06vector\x18\r \x01(\v2\r.sysml.VectorH\x00R\x06vector\x12@\n" + "\x0fvector_quantity\x18\x0e \x01(\v2\x15.sysml.VectorQuantityH\x00R\x0evectorQuantity\x12@\n" + - "\x0fmeasurement_ref\x18\x0f \x01(\v2\x15.sysml.MeasurementRefH\x00R\x0emeasurementRefB\x06\n" + - "\x04kind\"Q\n" + + "\x0fmeasurement_ref\x18\x0f \x01(\v2\x15.sysml.MeasurementRefH\x00R\x0emeasurementRef\x12-\n" + + "\bfunction\x18\x10 \x01(\v2\x0f.sysml.FunctionH\x00R\bfunctionB\x06\n" + + "\x04kind\"<\n" + + "\bFunction\x12\x17\n" + + "\acalc_id\x18\x01 \x01(\tR\x06calcId\x12\x17\n" + + "\aself_id\x18\x02 \x01(\x03R\x06selfId\"Q\n" + "\x05Array\x12\x1e\n" + "\n" + "dimensions\x18\x01 \x03(\x03R\n" + @@ -6493,7 +6581,7 @@ func file_sysml_proto_rawDescGZIP() []byte { } var file_sysml_proto_enumTypes = make([]protoimpl.EnumInfo, 4) -var file_sysml_proto_msgTypes = make([]protoimpl.MessageInfo, 84) +var file_sysml_proto_msgTypes = make([]protoimpl.MessageInfo, 85) var file_sysml_proto_goTypes = []any{ (FailureReason)(0), // 0: sysml.FailureReason (EditFailure)(0), // 1: sysml.EditFailure @@ -6546,90 +6634,91 @@ var file_sysml_proto_goTypes = []any{ (*MultiplicityInfo)(nil), // 48: sysml.MultiplicityInfo (*AttributeInfo)(nil), // 49: sysml.AttributeInfo (*Value)(nil), // 50: sysml.Value - (*Array)(nil), // 51: sysml.Array - (*Vector)(nil), // 52: sysml.Vector - (*VectorQuantity)(nil), // 53: sysml.VectorQuantity - (*Complex)(nil), // 54: sysml.Complex - (*EnumLiteral)(nil), // 55: sysml.EnumLiteral - (*ValueSequence)(nil), // 56: sysml.ValueSequence - (*Quantity)(nil), // 57: sysml.Quantity - (*MeasurementRef)(nil), // 58: sysml.MeasurementRef - (*UnitTerm)(nil), // 59: sysml.UnitTerm - (*UnitFactor)(nil), // 60: sysml.UnitFactor - (*Diagnostic)(nil), // 61: sysml.Diagnostic - (*Span)(nil), // 62: sysml.Span - (*ServerInfoRequest)(nil), // 63: sysml.ServerInfoRequest - (*ServerInfoResponse)(nil), // 64: sysml.ServerInfoResponse - (*QueryRequest)(nil), // 65: sysml.QueryRequest - (*QueryResponse)(nil), // 66: sysml.QueryResponse - (*Query)(nil), // 67: sysml.Query - (*Constraint)(nil), // 68: sysml.Constraint - (*PrimitiveConstraint)(nil), // 69: sysml.PrimitiveConstraint - (*CompositeConstraint)(nil), // 70: sysml.CompositeConstraint - (*QueryResultElement)(nil), // 71: sysml.QueryResultElement - (*RunDocumentQueryRequest)(nil), // 72: sysml.RunDocumentQueryRequest - (*DocumentQueryBinding)(nil), // 73: sysml.DocumentQueryBinding - (*DocumentValue)(nil), // 74: sysml.DocumentValue - (*DocumentQueryColumn)(nil), // 75: sysml.DocumentQueryColumn - (*DocumentQueryCell)(nil), // 76: sysml.DocumentQueryCell - (*DocumentQueryRow)(nil), // 77: sysml.DocumentQueryRow - (*RunDocumentQueryResponse)(nil), // 78: sysml.RunDocumentQueryResponse - (*RenderDocumentRequest)(nil), // 79: sysml.RenderDocumentRequest - (*RenderDocumentResponse)(nil), // 80: sysml.RenderDocumentResponse - nil, // 81: sysml.RunAnalysisRequest.NamedArgumentsEntry - nil, // 82: sysml.Instance.FeatureValuesEntry - nil, // 83: sysml.ExecuteActionRequest.InputsEntry - nil, // 84: sysml.ExecuteActionResponse.OutputsEntry - nil, // 85: sysml.ExecuteStateResponse.FinalContextEntry - nil, // 86: sysml.SymbolInfo.MetadataEntry - nil, // 87: sysml.QueryResultElement.PropertiesEntry + (*Function)(nil), // 51: sysml.Function + (*Array)(nil), // 52: sysml.Array + (*Vector)(nil), // 53: sysml.Vector + (*VectorQuantity)(nil), // 54: sysml.VectorQuantity + (*Complex)(nil), // 55: sysml.Complex + (*EnumLiteral)(nil), // 56: sysml.EnumLiteral + (*ValueSequence)(nil), // 57: sysml.ValueSequence + (*Quantity)(nil), // 58: sysml.Quantity + (*MeasurementRef)(nil), // 59: sysml.MeasurementRef + (*UnitTerm)(nil), // 60: sysml.UnitTerm + (*UnitFactor)(nil), // 61: sysml.UnitFactor + (*Diagnostic)(nil), // 62: sysml.Diagnostic + (*Span)(nil), // 63: sysml.Span + (*ServerInfoRequest)(nil), // 64: sysml.ServerInfoRequest + (*ServerInfoResponse)(nil), // 65: sysml.ServerInfoResponse + (*QueryRequest)(nil), // 66: sysml.QueryRequest + (*QueryResponse)(nil), // 67: sysml.QueryResponse + (*Query)(nil), // 68: sysml.Query + (*Constraint)(nil), // 69: sysml.Constraint + (*PrimitiveConstraint)(nil), // 70: sysml.PrimitiveConstraint + (*CompositeConstraint)(nil), // 71: sysml.CompositeConstraint + (*QueryResultElement)(nil), // 72: sysml.QueryResultElement + (*RunDocumentQueryRequest)(nil), // 73: sysml.RunDocumentQueryRequest + (*DocumentQueryBinding)(nil), // 74: sysml.DocumentQueryBinding + (*DocumentValue)(nil), // 75: sysml.DocumentValue + (*DocumentQueryColumn)(nil), // 76: sysml.DocumentQueryColumn + (*DocumentQueryCell)(nil), // 77: sysml.DocumentQueryCell + (*DocumentQueryRow)(nil), // 78: sysml.DocumentQueryRow + (*RunDocumentQueryResponse)(nil), // 79: sysml.RunDocumentQueryResponse + (*RenderDocumentRequest)(nil), // 80: sysml.RenderDocumentRequest + (*RenderDocumentResponse)(nil), // 81: sysml.RenderDocumentResponse + nil, // 82: sysml.RunAnalysisRequest.NamedArgumentsEntry + nil, // 83: sysml.Instance.FeatureValuesEntry + nil, // 84: sysml.ExecuteActionRequest.InputsEntry + nil, // 85: sysml.ExecuteActionResponse.OutputsEntry + nil, // 86: sysml.ExecuteStateResponse.FinalContextEntry + nil, // 87: sysml.SymbolInfo.MetadataEntry + nil, // 88: sysml.QueryResultElement.PropertiesEntry } var file_sysml_proto_depIdxs = []int32{ 0, // 0: sysml.Verdict.failure_reason:type_name -> sysml.FailureReason 4, // 1: sysml.VerifyConstraintResponse.verdict:type_name -> sysml.Verdict 27, // 2: sysml.VerifyConstraintResponse.instances:type_name -> sysml.Instance - 61, // 3: sysml.VerifyConstraintResponse.diagnostics:type_name -> sysml.Diagnostic + 62, // 3: sysml.VerifyConstraintResponse.diagnostics:type_name -> sysml.Diagnostic 4, // 4: sysml.VerifyRequirementResponse.verdict:type_name -> sysml.Verdict 27, // 5: sysml.VerifyRequirementResponse.instances:type_name -> sysml.Instance - 61, // 6: sysml.VerifyRequirementResponse.diagnostics:type_name -> sysml.Diagnostic + 62, // 6: sysml.VerifyRequirementResponse.diagnostics:type_name -> sysml.Diagnostic 4, // 7: sysml.VerifySatisfactionResponse.verdicts:type_name -> sysml.Verdict 27, // 8: sysml.VerifySatisfactionResponse.instances:type_name -> sysml.Instance - 61, // 9: sysml.VerifySatisfactionResponse.diagnostics:type_name -> sysml.Diagnostic + 62, // 9: sysml.VerifySatisfactionResponse.diagnostics:type_name -> sysml.Diagnostic 0, // 10: sysml.VerifySatisfactionResponse.failure_reason:type_name -> sysml.FailureReason 50, // 11: sysml.EvaluateCalcRequest.arguments:type_name -> sysml.Value 50, // 12: sysml.EvaluateCalcResponse.result:type_name -> sysml.Value 13, // 13: sysml.EvaluateCalcResponse.outputs:type_name -> sysml.CalcOutput - 61, // 14: sysml.EvaluateCalcResponse.diagnostics:type_name -> sysml.Diagnostic + 62, // 14: sysml.EvaluateCalcResponse.diagnostics:type_name -> sysml.Diagnostic 0, // 15: sysml.EvaluateCalcResponse.failure_reason:type_name -> sysml.FailureReason 50, // 16: sysml.CalcOutput.value:type_name -> sysml.Value 50, // 17: sysml.RunAnalysisRequest.arguments:type_name -> sysml.Value - 81, // 18: sysml.RunAnalysisRequest.named_arguments:type_name -> sysml.RunAnalysisRequest.NamedArgumentsEntry + 82, // 18: sysml.RunAnalysisRequest.named_arguments:type_name -> sysml.RunAnalysisRequest.NamedArgumentsEntry 13, // 19: sysml.RunAnalysisResponse.outputs:type_name -> sysml.CalcOutput 4, // 20: sysml.RunAnalysisResponse.verdicts:type_name -> sysml.Verdict 27, // 21: sysml.RunAnalysisResponse.instances:type_name -> sysml.Instance - 61, // 22: sysml.RunAnalysisResponse.diagnostics:type_name -> sysml.Diagnostic + 62, // 22: sysml.RunAnalysisResponse.diagnostics:type_name -> sysml.Diagnostic 0, // 23: sysml.RunAnalysisResponse.failure_reason:type_name -> sysml.FailureReason 17, // 24: sysml.ParseSourcesRequest.documents:type_name -> sysml.SourceDocument 45, // 25: sysml.ParseSourcesResponse.roots:type_name -> sysml.SymbolInfo - 61, // 26: sysml.ParseSourcesResponse.diagnostics:type_name -> sysml.Diagnostic + 62, // 26: sysml.ParseSourcesResponse.diagnostics:type_name -> sysml.Diagnostic 45, // 27: sysml.ParseFileResponse.root:type_name -> sysml.SymbolInfo - 61, // 28: sysml.ParseFileResponse.diagnostics:type_name -> sysml.Diagnostic + 62, // 28: sysml.ParseFileResponse.diagnostics:type_name -> sysml.Diagnostic 45, // 29: sysml.SymbolResponse.symbol:type_name -> sysml.SymbolInfo - 61, // 30: sysml.DiagnosticsResponse.diagnostics:type_name -> sysml.Diagnostic + 62, // 30: sysml.DiagnosticsResponse.diagnostics:type_name -> sysml.Diagnostic 50, // 31: sysml.EvaluateResponse.result:type_name -> sysml.Value - 61, // 32: sysml.EvaluateResponse.diagnostics:type_name -> sysml.Diagnostic - 82, // 33: sysml.Instance.feature_values:type_name -> sysml.Instance.FeatureValuesEntry + 62, // 32: sysml.EvaluateResponse.diagnostics:type_name -> sysml.Diagnostic + 83, // 33: sysml.Instance.feature_values:type_name -> sysml.Instance.FeatureValuesEntry 50, // 34: sysml.FeatureValue.value:type_name -> sysml.Value 50, // 35: sysml.FeatureValue.values:type_name -> sysml.Value 27, // 36: sysml.InstantiateResponse.instance:type_name -> sysml.Instance - 61, // 37: sysml.InstantiateResponse.diagnostics:type_name -> sysml.Diagnostic + 62, // 37: sysml.InstantiateResponse.diagnostics:type_name -> sysml.Diagnostic 27, // 38: sysml.InstantiateResponse.instances:type_name -> sysml.Instance - 83, // 39: sysml.ExecuteActionRequest.inputs:type_name -> sysml.ExecuteActionRequest.InputsEntry - 84, // 40: sysml.ExecuteActionResponse.outputs:type_name -> sysml.ExecuteActionResponse.OutputsEntry - 61, // 41: sysml.ExecuteActionResponse.diagnostics:type_name -> sysml.Diagnostic - 85, // 42: sysml.ExecuteStateResponse.final_context:type_name -> sysml.ExecuteStateResponse.FinalContextEntry - 61, // 43: sysml.ExecuteStateResponse.diagnostics:type_name -> sysml.Diagnostic - 61, // 44: sysml.ConvertResponse.diagnostics:type_name -> sysml.Diagnostic + 84, // 39: sysml.ExecuteActionRequest.inputs:type_name -> sysml.ExecuteActionRequest.InputsEntry + 85, // 40: sysml.ExecuteActionResponse.outputs:type_name -> sysml.ExecuteActionResponse.OutputsEntry + 62, // 41: sysml.ExecuteActionResponse.diagnostics:type_name -> sysml.Diagnostic + 86, // 42: sysml.ExecuteStateResponse.final_context:type_name -> sysml.ExecuteStateResponse.FinalContextEntry + 62, // 43: sysml.ExecuteStateResponse.diagnostics:type_name -> sysml.Diagnostic + 62, // 44: sysml.ConvertResponse.diagnostics:type_name -> sysml.Diagnostic 38, // 45: sysml.ApplyEditsRequest.operations:type_name -> sysml.EditOperation 41, // 46: sysml.EditOperation.set_value:type_name -> sysml.SetValueEdit 42, // 47: sysml.EditOperation.rename:type_name -> sysml.RenameEdit @@ -6637,94 +6726,95 @@ var file_sysml_proto_depIdxs = []int32{ 40, // 49: sysml.EditOperation.delete:type_name -> sysml.DeleteEdit 44, // 50: sysml.ApplyEditsResponse.applied:type_name -> sysml.AppliedEdit 1, // 51: sysml.ApplyEditsResponse.failure:type_name -> sysml.EditFailure - 61, // 52: sysml.ApplyEditsResponse.diagnostics:type_name -> sysml.Diagnostic - 86, // 53: sysml.SymbolInfo.metadata:type_name -> sysml.SymbolInfo.MetadataEntry + 62, // 52: sysml.ApplyEditsResponse.diagnostics:type_name -> sysml.Diagnostic + 87, // 53: sysml.SymbolInfo.metadata:type_name -> sysml.SymbolInfo.MetadataEntry 49, // 54: sysml.SymbolInfo.attributes:type_name -> sysml.AttributeInfo 47, // 55: sysml.SymbolInfo.type_info:type_name -> sysml.TypeInfo 48, // 56: sysml.SymbolInfo.multiplicity:type_name -> sysml.MultiplicityInfo 46, // 57: sysml.SymbolInfo.specializations:type_name -> sysml.Specialization 50, // 58: sysml.AttributeInfo.value:type_name -> sysml.Value - 56, // 59: sysml.Value.sequence:type_name -> sysml.ValueSequence - 57, // 60: sysml.Value.quantity:type_name -> sysml.Quantity - 55, // 61: sysml.Value.enum_literal:type_name -> sysml.EnumLiteral - 54, // 62: sysml.Value.complex:type_name -> sysml.Complex - 51, // 63: sysml.Value.array:type_name -> sysml.Array - 52, // 64: sysml.Value.vector:type_name -> sysml.Vector - 53, // 65: sysml.Value.vector_quantity:type_name -> sysml.VectorQuantity - 58, // 66: sysml.Value.measurement_ref:type_name -> sysml.MeasurementRef - 50, // 67: sysml.Array.elements:type_name -> sysml.Value - 50, // 68: sysml.Vector.components:type_name -> sysml.Value - 57, // 69: sysml.VectorQuantity.components:type_name -> sysml.Quantity - 50, // 70: sysml.ValueSequence.elements:type_name -> sysml.Value - 59, // 71: sysml.Quantity.unit_term:type_name -> sysml.UnitTerm - 59, // 72: sysml.MeasurementRef.unit_term:type_name -> sysml.UnitTerm - 60, // 73: sysml.UnitTerm.factors:type_name -> sysml.UnitFactor - 62, // 74: sysml.Diagnostic.span:type_name -> sysml.Span - 67, // 75: sysml.QueryRequest.query:type_name -> sysml.Query - 71, // 76: sysml.QueryResponse.elements:type_name -> sysml.QueryResultElement - 68, // 77: sysml.Query.where:type_name -> sysml.Constraint - 69, // 78: sysml.Constraint.primitive:type_name -> sysml.PrimitiveConstraint - 70, // 79: sysml.Constraint.composite:type_name -> sysml.CompositeConstraint - 2, // 80: sysml.PrimitiveConstraint.operator:type_name -> sysml.PrimitiveOperator - 3, // 81: sysml.CompositeConstraint.operator:type_name -> sysml.CompositeOperator - 68, // 82: sysml.CompositeConstraint.constraint:type_name -> sysml.Constraint - 87, // 83: sysml.QueryResultElement.properties:type_name -> sysml.QueryResultElement.PropertiesEntry - 73, // 84: sysml.RunDocumentQueryRequest.bindings:type_name -> sysml.DocumentQueryBinding - 74, // 85: sysml.DocumentQueryBinding.values:type_name -> sysml.DocumentValue - 57, // 86: sysml.DocumentValue.quantity:type_name -> sysml.Quantity - 74, // 87: sysml.DocumentQueryCell.values:type_name -> sysml.DocumentValue - 74, // 88: sysml.DocumentQueryRow.element:type_name -> sysml.DocumentValue - 76, // 89: sysml.DocumentQueryRow.cells:type_name -> sysml.DocumentQueryCell - 75, // 90: sysml.RunDocumentQueryResponse.columns:type_name -> sysml.DocumentQueryColumn - 77, // 91: sysml.RunDocumentQueryResponse.rows:type_name -> sysml.DocumentQueryRow - 50, // 92: sysml.RunAnalysisRequest.NamedArgumentsEntry.value:type_name -> sysml.Value - 28, // 93: sysml.Instance.FeatureValuesEntry.value:type_name -> sysml.FeatureValue - 50, // 94: sysml.ExecuteActionRequest.InputsEntry.value:type_name -> sysml.Value - 50, // 95: sysml.ExecuteActionResponse.OutputsEntry.value:type_name -> sysml.Value - 50, // 96: sysml.ExecuteStateResponse.FinalContextEntry.value:type_name -> sysml.Value - 63, // 97: sysml.SysMLService.GetServerInfo:input_type -> sysml.ServerInfoRequest - 16, // 98: sysml.SysMLService.ParseFile:input_type -> sysml.ParseFileRequest - 18, // 99: sysml.SysMLService.ParseSources:input_type -> sysml.ParseSourcesRequest - 21, // 100: sysml.SysMLService.GetSymbol:input_type -> sysml.GetSymbolRequest - 23, // 101: sysml.SysMLService.GetDiagnostics:input_type -> sysml.DiagnosticsRequest - 25, // 102: sysml.SysMLService.Evaluate:input_type -> sysml.EvaluateRequest - 29, // 103: sysml.SysMLService.Instantiate:input_type -> sysml.InstantiateRequest - 31, // 104: sysml.SysMLService.ExecuteAction:input_type -> sysml.ExecuteActionRequest - 33, // 105: sysml.SysMLService.ExecuteState:input_type -> sysml.ExecuteStateRequest - 35, // 106: sysml.SysMLService.Convert:input_type -> sysml.ConvertRequest - 37, // 107: sysml.SysMLService.ApplyEdits:input_type -> sysml.ApplyEditsRequest - 5, // 108: sysml.SysMLService.VerifyConstraint:input_type -> sysml.VerifyConstraintRequest - 7, // 109: sysml.SysMLService.VerifyRequirement:input_type -> sysml.VerifyRequirementRequest - 9, // 110: sysml.SysMLService.VerifySatisfaction:input_type -> sysml.VerifySatisfactionRequest - 11, // 111: sysml.SysMLService.EvaluateCalc:input_type -> sysml.EvaluateCalcRequest - 14, // 112: sysml.SysMLService.RunAnalysis:input_type -> sysml.RunAnalysisRequest - 65, // 113: sysml.SysMLService.Query:input_type -> sysml.QueryRequest - 72, // 114: sysml.SysMLService.RunDocumentQuery:input_type -> sysml.RunDocumentQueryRequest - 79, // 115: sysml.SysMLService.RenderDocument:input_type -> sysml.RenderDocumentRequest - 64, // 116: sysml.SysMLService.GetServerInfo:output_type -> sysml.ServerInfoResponse - 20, // 117: sysml.SysMLService.ParseFile:output_type -> sysml.ParseFileResponse - 19, // 118: sysml.SysMLService.ParseSources:output_type -> sysml.ParseSourcesResponse - 22, // 119: sysml.SysMLService.GetSymbol:output_type -> sysml.SymbolResponse - 24, // 120: sysml.SysMLService.GetDiagnostics:output_type -> sysml.DiagnosticsResponse - 26, // 121: sysml.SysMLService.Evaluate:output_type -> sysml.EvaluateResponse - 30, // 122: sysml.SysMLService.Instantiate:output_type -> sysml.InstantiateResponse - 32, // 123: sysml.SysMLService.ExecuteAction:output_type -> sysml.ExecuteActionResponse - 34, // 124: sysml.SysMLService.ExecuteState:output_type -> sysml.ExecuteStateResponse - 36, // 125: sysml.SysMLService.Convert:output_type -> sysml.ConvertResponse - 43, // 126: sysml.SysMLService.ApplyEdits:output_type -> sysml.ApplyEditsResponse - 6, // 127: sysml.SysMLService.VerifyConstraint:output_type -> sysml.VerifyConstraintResponse - 8, // 128: sysml.SysMLService.VerifyRequirement:output_type -> sysml.VerifyRequirementResponse - 10, // 129: sysml.SysMLService.VerifySatisfaction:output_type -> sysml.VerifySatisfactionResponse - 12, // 130: sysml.SysMLService.EvaluateCalc:output_type -> sysml.EvaluateCalcResponse - 15, // 131: sysml.SysMLService.RunAnalysis:output_type -> sysml.RunAnalysisResponse - 66, // 132: sysml.SysMLService.Query:output_type -> sysml.QueryResponse - 78, // 133: sysml.SysMLService.RunDocumentQuery:output_type -> sysml.RunDocumentQueryResponse - 80, // 134: sysml.SysMLService.RenderDocument:output_type -> sysml.RenderDocumentResponse - 116, // [116:135] is the sub-list for method output_type - 97, // [97:116] is the sub-list for method input_type - 97, // [97:97] is the sub-list for extension type_name - 97, // [97:97] is the sub-list for extension extendee - 0, // [0:97] is the sub-list for field type_name + 57, // 59: sysml.Value.sequence:type_name -> sysml.ValueSequence + 58, // 60: sysml.Value.quantity:type_name -> sysml.Quantity + 56, // 61: sysml.Value.enum_literal:type_name -> sysml.EnumLiteral + 55, // 62: sysml.Value.complex:type_name -> sysml.Complex + 52, // 63: sysml.Value.array:type_name -> sysml.Array + 53, // 64: sysml.Value.vector:type_name -> sysml.Vector + 54, // 65: sysml.Value.vector_quantity:type_name -> sysml.VectorQuantity + 59, // 66: sysml.Value.measurement_ref:type_name -> sysml.MeasurementRef + 51, // 67: sysml.Value.function:type_name -> sysml.Function + 50, // 68: sysml.Array.elements:type_name -> sysml.Value + 50, // 69: sysml.Vector.components:type_name -> sysml.Value + 58, // 70: sysml.VectorQuantity.components:type_name -> sysml.Quantity + 50, // 71: sysml.ValueSequence.elements:type_name -> sysml.Value + 60, // 72: sysml.Quantity.unit_term:type_name -> sysml.UnitTerm + 60, // 73: sysml.MeasurementRef.unit_term:type_name -> sysml.UnitTerm + 61, // 74: sysml.UnitTerm.factors:type_name -> sysml.UnitFactor + 63, // 75: sysml.Diagnostic.span:type_name -> sysml.Span + 68, // 76: sysml.QueryRequest.query:type_name -> sysml.Query + 72, // 77: sysml.QueryResponse.elements:type_name -> sysml.QueryResultElement + 69, // 78: sysml.Query.where:type_name -> sysml.Constraint + 70, // 79: sysml.Constraint.primitive:type_name -> sysml.PrimitiveConstraint + 71, // 80: sysml.Constraint.composite:type_name -> sysml.CompositeConstraint + 2, // 81: sysml.PrimitiveConstraint.operator:type_name -> sysml.PrimitiveOperator + 3, // 82: sysml.CompositeConstraint.operator:type_name -> sysml.CompositeOperator + 69, // 83: sysml.CompositeConstraint.constraint:type_name -> sysml.Constraint + 88, // 84: sysml.QueryResultElement.properties:type_name -> sysml.QueryResultElement.PropertiesEntry + 74, // 85: sysml.RunDocumentQueryRequest.bindings:type_name -> sysml.DocumentQueryBinding + 75, // 86: sysml.DocumentQueryBinding.values:type_name -> sysml.DocumentValue + 58, // 87: sysml.DocumentValue.quantity:type_name -> sysml.Quantity + 75, // 88: sysml.DocumentQueryCell.values:type_name -> sysml.DocumentValue + 75, // 89: sysml.DocumentQueryRow.element:type_name -> sysml.DocumentValue + 77, // 90: sysml.DocumentQueryRow.cells:type_name -> sysml.DocumentQueryCell + 76, // 91: sysml.RunDocumentQueryResponse.columns:type_name -> sysml.DocumentQueryColumn + 78, // 92: sysml.RunDocumentQueryResponse.rows:type_name -> sysml.DocumentQueryRow + 50, // 93: sysml.RunAnalysisRequest.NamedArgumentsEntry.value:type_name -> sysml.Value + 28, // 94: sysml.Instance.FeatureValuesEntry.value:type_name -> sysml.FeatureValue + 50, // 95: sysml.ExecuteActionRequest.InputsEntry.value:type_name -> sysml.Value + 50, // 96: sysml.ExecuteActionResponse.OutputsEntry.value:type_name -> sysml.Value + 50, // 97: sysml.ExecuteStateResponse.FinalContextEntry.value:type_name -> sysml.Value + 64, // 98: sysml.SysMLService.GetServerInfo:input_type -> sysml.ServerInfoRequest + 16, // 99: sysml.SysMLService.ParseFile:input_type -> sysml.ParseFileRequest + 18, // 100: sysml.SysMLService.ParseSources:input_type -> sysml.ParseSourcesRequest + 21, // 101: sysml.SysMLService.GetSymbol:input_type -> sysml.GetSymbolRequest + 23, // 102: sysml.SysMLService.GetDiagnostics:input_type -> sysml.DiagnosticsRequest + 25, // 103: sysml.SysMLService.Evaluate:input_type -> sysml.EvaluateRequest + 29, // 104: sysml.SysMLService.Instantiate:input_type -> sysml.InstantiateRequest + 31, // 105: sysml.SysMLService.ExecuteAction:input_type -> sysml.ExecuteActionRequest + 33, // 106: sysml.SysMLService.ExecuteState:input_type -> sysml.ExecuteStateRequest + 35, // 107: sysml.SysMLService.Convert:input_type -> sysml.ConvertRequest + 37, // 108: sysml.SysMLService.ApplyEdits:input_type -> sysml.ApplyEditsRequest + 5, // 109: sysml.SysMLService.VerifyConstraint:input_type -> sysml.VerifyConstraintRequest + 7, // 110: sysml.SysMLService.VerifyRequirement:input_type -> sysml.VerifyRequirementRequest + 9, // 111: sysml.SysMLService.VerifySatisfaction:input_type -> sysml.VerifySatisfactionRequest + 11, // 112: sysml.SysMLService.EvaluateCalc:input_type -> sysml.EvaluateCalcRequest + 14, // 113: sysml.SysMLService.RunAnalysis:input_type -> sysml.RunAnalysisRequest + 66, // 114: sysml.SysMLService.Query:input_type -> sysml.QueryRequest + 73, // 115: sysml.SysMLService.RunDocumentQuery:input_type -> sysml.RunDocumentQueryRequest + 80, // 116: sysml.SysMLService.RenderDocument:input_type -> sysml.RenderDocumentRequest + 65, // 117: sysml.SysMLService.GetServerInfo:output_type -> sysml.ServerInfoResponse + 20, // 118: sysml.SysMLService.ParseFile:output_type -> sysml.ParseFileResponse + 19, // 119: sysml.SysMLService.ParseSources:output_type -> sysml.ParseSourcesResponse + 22, // 120: sysml.SysMLService.GetSymbol:output_type -> sysml.SymbolResponse + 24, // 121: sysml.SysMLService.GetDiagnostics:output_type -> sysml.DiagnosticsResponse + 26, // 122: sysml.SysMLService.Evaluate:output_type -> sysml.EvaluateResponse + 30, // 123: sysml.SysMLService.Instantiate:output_type -> sysml.InstantiateResponse + 32, // 124: sysml.SysMLService.ExecuteAction:output_type -> sysml.ExecuteActionResponse + 34, // 125: sysml.SysMLService.ExecuteState:output_type -> sysml.ExecuteStateResponse + 36, // 126: sysml.SysMLService.Convert:output_type -> sysml.ConvertResponse + 43, // 127: sysml.SysMLService.ApplyEdits:output_type -> sysml.ApplyEditsResponse + 6, // 128: sysml.SysMLService.VerifyConstraint:output_type -> sysml.VerifyConstraintResponse + 8, // 129: sysml.SysMLService.VerifyRequirement:output_type -> sysml.VerifyRequirementResponse + 10, // 130: sysml.SysMLService.VerifySatisfaction:output_type -> sysml.VerifySatisfactionResponse + 12, // 131: sysml.SysMLService.EvaluateCalc:output_type -> sysml.EvaluateCalcResponse + 15, // 132: sysml.SysMLService.RunAnalysis:output_type -> sysml.RunAnalysisResponse + 67, // 133: sysml.SysMLService.Query:output_type -> sysml.QueryResponse + 79, // 134: sysml.SysMLService.RunDocumentQuery:output_type -> sysml.RunDocumentQueryResponse + 81, // 135: sysml.SysMLService.RenderDocument:output_type -> sysml.RenderDocumentResponse + 117, // [117:136] is the sub-list for method output_type + 98, // [98:117] is the sub-list for method input_type + 98, // [98:98] is the sub-list for extension type_name + 98, // [98:98] is the sub-list for extension extendee + 0, // [0:98] is the sub-list for field type_name } func init() { file_sysml_proto_init() } @@ -6767,16 +6857,17 @@ func file_sysml_proto_init() { (*Value_Vector)(nil), (*Value_VectorQuantity)(nil), (*Value_MeasurementRef)(nil), + (*Value_Function)(nil), } - file_sysml_proto_msgTypes[53].OneofWrappers = []any{ + file_sysml_proto_msgTypes[54].OneofWrappers = []any{ (*Quantity_IntMagnitude)(nil), (*Quantity_RealMagnitude)(nil), } - file_sysml_proto_msgTypes[64].OneofWrappers = []any{ + file_sysml_proto_msgTypes[65].OneofWrappers = []any{ (*Constraint_Primitive)(nil), (*Constraint_Composite)(nil), } - file_sysml_proto_msgTypes[70].OneofWrappers = []any{ + file_sysml_proto_msgTypes[71].OneofWrappers = []any{ (*DocumentValue_ElementId)(nil), (*DocumentValue_StringValue)(nil), (*DocumentValue_IntValue)(nil), @@ -6791,7 +6882,7 @@ func file_sysml_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_sysml_proto_rawDesc), len(file_sysml_proto_rawDesc)), NumEnums: 4, - NumMessages: 84, + NumMessages: 85, NumExtensions: 0, NumServices: 1, }, diff --git a/api/proto/sysml.proto b/api/proto/sysml.proto index b14d152e1..d16e49426 100644 --- a/api/proto/sysml.proto +++ b/api/proto/sysml.proto @@ -659,9 +659,26 @@ message Value { Vector vector = 13; // numeric components, never a sequence VectorQuantity vector_quantity = 14; // components each with their unit MeasurementRef measurement_ref = 15; // a unit by itself, no magnitude + Function function = 16; // a calc as a value, named by its declaration } } +// Function is a calc held as a value: a calc definition, or a calc usage with +// an input no read could supply, as `Sq` in `Fn(Sq, 3.0)` or the `f` of +// `in calc f {...}`. It crosses as the declaration it is a value of, which is +// its identity: two functions are the same exactly when calc_id and self_id +// are. A function closing over the bindings of the behavior body it is +// declared in has no wire form and crosses as the null arm. +message Function { + // FQN of the calc declaration ("Analysis::Sq"). Its identity. + string calc_id = 1; + // ID of the object the calc's feature names resolve against, for a calc + // usage read off a part (`holder.scale`); 0 for a function closing over no + // object. Sent by the service; a client sending one must name an object of + // the runtime the value is read in, or the value is rejected. + int64 self_id = 2; +} + // Array is a Collections::Array: its elements flattened in row-major order // under its dimensions, compared by content rather than by the object read. message Array { @@ -833,6 +850,11 @@ message ServerInfoResponse { // refused with UNIMPLEMENTED rather than read as another // value. Separate from structured_values, which a client // built before this arm existed may already claim. + // "function_values" - a Value carries a calc held as a value as function, + // named by its declaration, rather than reporting it as an + // unsupported null, and one is accepted as an action input + // or calc argument; without it, one is refused with + // UNIMPLEMENTED rather than read as another value. // "apply_edits" - the ApplyEdits RPC edits a parsed model's own source, // preserving everything the edit did not touch. // "document_query" - the RunDocumentQuery RPC runs a named document query diff --git a/changes/unreleased/function-values.added.md b/changes/unreleased/function-values.added.md new file mode 100644 index 000000000..6f55ebe2b --- /dev/null +++ b/changes/unreleased/function-values.added.md @@ -0,0 +1,2 @@ +- **A calc is a value.** A calc definition, a calc usage awaiting an input, or an `in calc` parameter named where a value is expected is a function value: the calc together with the scope and object it was read in, invoked through a calc-typed parameter (`calc def Fn { in calc f { in v : Real; return : Real; } in a : Real; return : Real = f(a); }` makes `Fn(Sq, 3.0)` `9.0`), passed positionally or by name, read off a part, returned from a calc, compared and adopted. `SampledFunctions::Sample` now samples a user calc, and a library function the runtime implements (`RealFunctions::sqrt`) is a value too. Calling a non-function, an arity mismatch and an unbound calc parameter are typed errors; `in calc` parameters parse in action bodies as they do in calc bodies. +- **Function values cross the API.** `Value.function` carries the calc's qualified name and the id of the object it was read off, under the new `function_values` capability, which the Go, Python, Node, Rust and Java clients expose as a typed value and refuse to send to a service without the capability. A function closing over a behavior body's bindings crosses as an unsupported null, since no name reconstructs it. Native compilation refuses a calc that binds or applies a function value with a typed error. diff --git a/client/opensysml/README.md b/client/opensysml/README.md index bba48a4b2..18425cda5 100644 --- a/client/opensysml/README.md +++ b/client/opensysml/README.md @@ -194,13 +194,14 @@ answering implementation supports, and `ServerInfo.Has` checks one. A request that asks for an unavailable capability is refused with `CodeUnimplemented`; capabilities that describe response population instead omit the fields they name. Check the list first for an operation-specific error (the `Capability*` -constants name the known ones). Three capabilities are checked for you: a `Complex` +constants name the known ones). Four capabilities are checked for you: a `Complex` among `ExecuteAction` inputs or `EvaluateCalc`/`RunAnalysis` arguments needs `complex_values`, an `Array`, `Vector` or `VectorQuantity` needs -`structured_values`, and a `MeasurementRef` needs `measurement_refs` — each at -the top level or nested in a sequence or array; a service without them would read -the value as null, so the client refuses with `CodeUnimplemented` before sending -anything. +`structured_values`, a `MeasurementRef` needs `measurement_refs`, and a `Function` +(a calc held as a value, sent back to bind a calc-typed parameter) needs +`function_values` — each at the top level or nested in a sequence or array; a +service without them would read the value as null, so the client refuses with +`CodeUnimplemented` before sending anything. ## Stability diff --git a/client/opensysml/client.go b/client/opensysml/client.go index 9f5339c5d..e2227073f 100644 --- a/client/opensysml/client.go +++ b/client/opensysml/client.go @@ -62,8 +62,9 @@ type Client interface { // ExecuteAction executes the named action with the inputs given, bound by // parameter name, and reports the outputs it produced. A Complex input // requires the complex_values capability, an Array, Vector or - // VectorQuantity input the structured_values one and a MeasurementRef - // input the measurement_refs one, checked before anything is sent. + // VectorQuantity input the structured_values one, a MeasurementRef input + // the measurement_refs one and a Function input the function_values one, + // checked before anything is sent. ExecuteAction(ctx context.Context, model *Model, actionSymbolID string, inputs map[string]Value) (*ActionRun, error) // ExecuteState runs the named state machine, feeding it the events in @@ -85,9 +86,10 @@ type Client interface { // EvaluateCalc invokes the named calculation with positional arguments, or, // given none, evaluates a calc usage from its own members. Requires the - // verification capability, and the complex_values, structured_values or - // measurement_refs capability for a Complex, a structured or a - // MeasurementRef argument, checked before anything is sent. + // verification capability, and the complex_values, structured_values, + // measurement_refs or function_values capability for a Complex, a + // structured, a MeasurementRef or a Function argument, checked before + // anything is sent. EvaluateCalc(ctx context.Context, model *Model, symbolID string, arguments ...Value) (*Calculation, error) // RunAnalysis runs the named analysis case — a definition or a usage — and @@ -505,7 +507,8 @@ func (c *client) call(model *Model) (string, error) { // requireValueCapabilities refuses to send a value of a kind whose capability // the service lacks — a Complex without complex_values, an Array, Vector or // VectorQuantity without structured_values, a MeasurementRef without -// measurement_refs — which would read it as null rather than refuse it. +// measurement_refs, a Function without function_values — which would read it +// as null rather than refuse it. func (c *client) requireValueCapabilities(ctx context.Context, values ...Value) error { var needed []string if slices.ContainsFunc(values, carriesComplex) { @@ -517,6 +520,9 @@ func (c *client) requireValueCapabilities(ctx context.Context, values ...Value) if slices.ContainsFunc(values, carriesMeasurementRef) { needed = append(needed, CapabilityMeasurementRefs) } + if slices.ContainsFunc(values, carriesFunction) { + needed = append(needed, CapabilityFunctionValues) + } if len(needed) == 0 { return nil } @@ -586,6 +592,15 @@ func carriesMeasurementRef(value Value) bool { return slices.ContainsFunc(nestedValues(value), carriesMeasurementRef) } +// carriesFunction reports whether a value, or any value nested in it, is a +// Function. +func carriesFunction(value Value) bool { + if _, ok := value.(Function); ok { + return true + } + return slices.ContainsFunc(nestedValues(value), carriesFunction) +} + // nestedValues are the values a value holds: a sequence's elements, an array's. func nestedValues(value Value) []Value { switch v := value.(type) { diff --git a/client/opensysml/convert.go b/client/opensysml/convert.go index aaae9ea96..eee63be49 100644 --- a/client/opensysml/convert.go +++ b/client/opensysml/convert.go @@ -165,6 +165,11 @@ func valueFromProto(value *pb.Value) Value { return Null("unsupported: measurement reference without its reduction") } return MeasurementRef{Unit: ref.GetUnit(), Term: unitTermFromProto(ref.GetUnitTerm()), UnitID: ref.GetUnitId()} + case *pb.Value_Function: + if kind.Function.GetCalcId() == "" { + return Null("unsupported: function naming no calc") + } + return Function{CalcID: kind.Function.GetCalcId(), Self: InstanceID(kind.Function.GetSelfId())} default: // A newer service's arm parses as an unknown field: no kind at all. return Null("unsupported: a value arm this client does not know") @@ -245,6 +250,11 @@ func valueToProto(value Value) (*pb.Value, error) { UnitTerm: unitTermToProto(v.Term), UnitId: v.UnitID, }}}, nil + case Function: + if v.CalcID == "" { + return nil, &StatusError{Code: CodeInvalidArgument, Message: "a function names no calc"} + } + return &pb.Value{Kind: &pb.Value_Function{Function: &pb.Function{CalcId: v.CalcID, SelfId: int64(v.Self)}}}, nil case Unset: return nil, &StatusError{ Code: CodeInvalidArgument, diff --git a/client/opensysml/function_test.go b/client/opensysml/function_test.go new file mode 100644 index 000000000..d7196aeaa --- /dev/null +++ b/client/opensysml/function_test.go @@ -0,0 +1,202 @@ +package opensysml_test + +import ( + "context" + "errors" + "fmt" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/Open-MBEE/OpenSysML/api/proto/protoconnect" + "github.com/Open-MBEE/OpenSysML/client/opensysml" + sysmlgrpc "github.com/Open-MBEE/OpenSysML/internal/grpc" +) + +const functionSource = `package F { + private import ScalarValues::*; + calc def Sq { in v : Real; return : Real = v * v; } + calc def Cube { in v : Real; return : Real = v * v * v; } + calc def Apply { in calc f { in v : Real; return : Real; } in a : Real; return : Real = f(a); } + calc apply : Apply; + calc def Identity { in calc f { in v : Real; return : Real; } return r = f; } + attribute pick = Identity(Sq); + attribute nine = Apply(Sq, 3.0); + part def Scaler { + attribute k : Real = 2.0; + calc scale { in x : Real; return : Real = x * k; } + } + part holder : Scaler; + attribute scaler = holder.scale; + + action applyTwice { + in calc f { in v : Real; return : Real; } + in a : Real; + out y : Real; + first start; + action inner { assign y := f(f(a)); } + then done; + succession first start then inner; + } +}` + +// A calc read as a value arrives as a Function naming it over every transport, +// one read off an object naming that object too, and a Function sent back binds +// the calc-typed parameter the calc invokes. +func TestFunctionsCrossEveryTransport(t *testing.T) { + address := startService(t) + for name, client := range map[string]opensysml.Client{ + "in-process": newClient(t), + "connect-proto": dialClient(t, address), + "connect-json": dialClient(t, address, opensysml.WithJSONBody()), + } { + t.Run(name, func(t *testing.T) { + ctx := context.Background() + info, err := client.ServerInfo(ctx) + if err != nil { + t.Fatalf("ServerInfo: %v", err) + } + if !info.Has(opensysml.CapabilityFunctionValues) { + t.Errorf("capabilities %v do not name %s", info.Capabilities, opensysml.CapabilityFunctionValues) + } + model := parse(t, client, functionSource) + + pick, err := client.Evaluate(ctx, model, "F::pick") + if err != nil { + t.Fatalf("Evaluate(F::pick): %v", err) + } + if pick != (opensysml.Function{CalcID: "F::Sq"}) { + t.Errorf("F::pick = %#v, want the function F::Sq bound to no object", pick) + } + + scaler, err := client.Evaluate(ctx, model, "F::scaler") + if err != nil { + t.Fatalf("Evaluate(F::scaler): %v", err) + } + fn, ok := scaler.(opensysml.Function) + if !ok || fn.CalcID != "F::Scaler::scale" || fn.Self == 0 { + t.Errorf("F::scaler = %#v, want the function F::Scaler::scale bound to holder", scaler) + } + + nine, err := client.Evaluate(ctx, model, "F::nine") + if err != nil { + t.Fatalf("Evaluate(F::nine): %v", err) + } + if nine != opensysml.Real(9) { + t.Errorf("F::nine = %#v, want 9.0", nine) + } + + calc, err := client.EvaluateCalc(ctx, model, "F::apply", opensysml.Function{CalcID: "F::Cube"}, opensysml.Real(2)) + if err != nil { + t.Fatalf("EvaluateCalc(apply): %v", err) + } + if calc.Result != opensysml.Real(8) { + t.Errorf("apply(Cube, 2.0) = %#v, want 8.0", calc.Result) + } + + run, err := client.ExecuteAction(ctx, model, "F::applyTwice", map[string]opensysml.Value{ + "f": opensysml.Function{CalcID: "F::Sq"}, "a": opensysml.Real(3), + }) + if err != nil { + t.Fatalf("ExecuteAction: %v", err) + } + if run.Outputs["y"] != opensysml.Real(81) { + t.Errorf("y = %#v, want 81.0", run.Outputs["y"]) + } + + // A function naming no calc, or an object this call did not + // create, is refused in band rather than invoked. + for label, bad := range map[string]opensysml.Function{ + "an unknown calc": {CalcID: "F::Nothing"}, + "a non-calc": {CalcID: "F::holder"}, + "an unknown object": {CalcID: "F::Scaler::scale", Self: 99}, + } { + if _, err := client.EvaluateCalc(ctx, model, "F::apply", bad, opensysml.Real(2)); err == nil { + t.Errorf("EvaluateCalc with a function naming %s succeeded", label) + } + } + var status *opensysml.StatusError + _, err = client.EvaluateCalc(ctx, model, "F::apply", opensysml.Function{}, opensysml.Real(2)) + if !errors.As(err, &status) || status.Code != opensysml.CodeInvalidArgument { + t.Errorf("EvaluateCalc with an empty function: err = %v, want CodeInvalidArgument", err) + } + }) + } +} + +// A service without function_values would read a function input as null, so +// the client refuses to send one, however deeply nested; and what such a +// service reports for a calc read as a value is an unsupported null naming it. +func TestFunctionInputNeedsFunctionValues(t *testing.T) { + svc, err := sysmlgrpc.NewServiceWithUnavailableCapabilitiesForTesting(16, "test", []string{opensysml.CapabilityFunctionValues}) + if err != nil { + t.Fatalf("NewServiceWithUnavailableCapabilitiesForTesting: %v", err) + } + t.Cleanup(svc.Close) + mux := http.NewServeMux() + mux.Handle(protoconnect.NewSysMLServiceHandler(sysmlgrpc.NewConnectAdapter(svc))) + server := httptest.NewServer(mux) + t.Cleanup(server.Close) + + sq := opensysml.Function{CalcID: "F::Sq"} + for name, client := range map[string]opensysml.Client{ + "connect-proto": dialClient(t, server.URL), + "connect-json": dialClient(t, server.URL, opensysml.WithJSONBody()), + } { + t.Run(name, func(t *testing.T) { + ctx := context.Background() + model := parse(t, client, functionSource) + for label, input := range map[string]opensysml.Value{ + "function": sq, + "nested": opensysml.Sequence{opensysml.Int(1), opensysml.Sequence{sq}}, + "in an array": opensysml.Array{Dimensions: []int64{1}, Elements: []opensysml.Value{sq}}, + } { + _, err := client.ExecuteAction(ctx, model, "F::applyTwice", map[string]opensysml.Value{"f": input, "a": opensysml.Real(3)}) + wantFunctionValuesRefusal(t, "ExecuteAction "+label, err) + _, err = client.EvaluateCalc(ctx, model, "F::apply", input, opensysml.Real(2)) + wantFunctionValuesRefusal(t, "EvaluateCalc "+label, err) + } + + // Invocation inside the model needs no capability: only the + // value crossing the boundary does. + nine, err := client.Evaluate(ctx, model, "F::nine") + if err != nil { + t.Fatalf("Evaluate(F::nine): %v", err) + } + if nine != opensysml.Real(9) { + t.Errorf("F::nine without function_values = %#v, want 9.0", nine) + } + pick, err := client.Evaluate(ctx, model, "F::pick") + if err != nil { + t.Fatalf("Evaluate(F::pick): %v", err) + } + if pick != opensysml.Null("unsupported: function F::Sq") { + t.Errorf("F::pick without function_values = %#v, want an unsupported null naming F::Sq", pick) + } + }) + } +} + +func wantFunctionValuesRefusal(t *testing.T, op string, err error) { + t.Helper() + var status *opensysml.StatusError + if !errors.As(err, &status) || status.Code != opensysml.CodeUnimplemented || !strings.Contains(status.Message, opensysml.CapabilityFunctionValues) { + t.Errorf("%s: err = %v, want CodeUnimplemented naming %s", op, err, opensysml.CapabilityFunctionValues) + } +} + +func TestFunctionRendersAsItsCalc(t *testing.T) { + for _, testcase := range []struct { + value opensysml.Value + want string + }{ + {opensysml.Function{CalcID: "F::Sq"}, "F::Sq"}, + {opensysml.Function{CalcID: "F::Scaler::scale", Self: 1}, "F::Scaler::scale"}, + {opensysml.Sequence{opensysml.Function{CalcID: "F::Sq"}, opensysml.Function{CalcID: "F::Cube"}}, "[F::Sq F::Cube]"}, + } { + if got := fmt.Sprintf("%v", testcase.value); got != testcase.want { + t.Errorf("%#v renders as %q, want %q", testcase.value, got, testcase.want) + } + } +} diff --git a/client/opensysml/types.go b/client/opensysml/types.go index 97b4cb601..030e221ed 100644 --- a/client/opensysml/types.go +++ b/client/opensysml/types.go @@ -31,6 +31,7 @@ const ( CapabilityComplexValues = sysmlgrpc.CapabilityComplexValues CapabilityStructuredValues = sysmlgrpc.CapabilityStructuredValues CapabilityMeasurementRefs = sysmlgrpc.CapabilityMeasurementRefs + CapabilityFunctionValues = sysmlgrpc.CapabilityFunctionValues ) // ServerInfo describes the implementation answering a Client's calls. diff --git a/client/opensysml/value.go b/client/opensysml/value.go index 2071a0165..e127a84fe 100644 --- a/client/opensysml/value.go +++ b/client/opensysml/value.go @@ -9,8 +9,8 @@ import ( // Value is one evaluated SysML value. It is a sealed sum: the concrete types // are Int, Real, Complex, Bool, String, InstanceID, Sequence, Null, Unset, -// Quantity, EnumLiteral, Array, Vector, VectorQuantity and MeasurementRef, and -// a type switch over them is exhaustive. +// Quantity, EnumLiteral, Array, Vector, VectorQuantity, MeasurementRef and +// Function, and a type switch over them is exhaustive. type Value interface { isValue() } @@ -97,6 +97,18 @@ type MeasurementRef struct { UnitID string } +// Function is a calc held as a value: a calc definition, a calc usage or an +// `in calc` parameter read where a value is expected. It names the calc and, for +// one read off an object, that object; sent as an argument it is invoked through +// the calc-typed parameter it binds. Identity is the calc together with Self. +type Function struct { + // CalcID is the FQN of the calc declaration ("M::Sq"). + CalcID string + // Self is the object the calc computes over, an id of the answer that + // reported it; 0 for a calc bound to no object. + Self InstanceID +} + // EnumLiteral is one literal of an enumeration definition. A literal is its // own identity: two values are the same literal exactly when LiteralID is. type EnumLiteral struct { @@ -214,6 +226,11 @@ func (t UnitTerm) String() string { return strings.Join(parts, "·") } +// String names the calc the function is a value of. +func (f Function) String() string { + return f.CalcID +} + // String is the literal as a reader writes it, the Name the service reported. func (e EnumLiteral) String() string { return e.Name @@ -248,6 +265,7 @@ func (Array) isValue() { /* marker: closed Value set */ } func (Vector) isValue() { /* marker: closed Value set */ } func (VectorQuantity) isValue() { /* marker: closed Value set */ } func (MeasurementRef) isValue() { /* marker: closed Value set */ } +func (Function) isValue() { /* marker: closed Value set */ } func (Int) isNumber() { /* marker: closed Number set */ } func (Real) isNumber() { /* marker: closed Number set */ } diff --git a/clients/java/README.md b/clients/java/README.md index 5625d1b6f..cf3e63bf4 100644 --- a/clients/java/README.md +++ b/clients/java/README.md @@ -36,7 +36,7 @@ try (Connection connection = Connection.open()) { // starts a private sysml Every value the API answers with is immutable: `Value` is a sealed interface over records (`IntegerValue`, `RealValue`, `ComplexValue`, `QuantityValue`, `ArrayValue`, -`VectorValue`, `VectorQuantityValue`, `MeasurementRefValue`, `EnumerationValue`, +`VectorValue`, `VectorQuantityValue`, `MeasurementRefValue`, `FunctionValue`, `EnumerationValue`, `InstanceReference`, `Sequence`, `NullValue`, `UnsetValue`, …), and `Symbol`, `Diagnostic`, `Instance` and `Instantiation` are records with copied collections. No generated protobuf message or builder appears in the public API. diff --git a/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/Capabilities.java b/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/Capabilities.java index 88b6ea3dd..6b42150e5 100644 --- a/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/Capabilities.java +++ b/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/Capabilities.java @@ -44,6 +44,9 @@ public final class Capabilities { /** A bare measurement unit ({@code SI::m}, {@code m / s}) travels as itself rather than as an unsupported null. */ public static final String MEASUREMENT_REFS = "measurement_refs"; + /** A calc held as a value travels as the {@code function} naming its declaration rather than as an unsupported null. */ + public static final String FUNCTION_VALUES = "function_values"; + /** The {@code ApplyEdits} RPC edits a parsed model's own source. */ public static final String APPLY_EDITS = "apply_edits"; diff --git a/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/Value.java b/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/Value.java index 5273b4c34..f7ea2540d 100644 --- a/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/Value.java +++ b/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/Value.java @@ -134,6 +134,36 @@ record MeasurementRefValue(String unit, Quantity.UnitTerm reduction, OptionalIt is the declaration it is a value of, which is its identity: two functions are equal + * exactly when both components are. A function closing over the bindings of the behavior body + * it is declared in has no wire form; the service sends it as an unsupported {@link NullValue}, + * as does a service without the {@code function_values} capability for every function. + * + * @param calcId FQN of the calc declaration ({@code "Analysis::Sq"}), never empty + * @param selfId id of the object the calc's feature names resolve against, for a calc usage read + * off a part ({@code holder.scale}); absent for a function closing over no object + */ + record FunctionValue(String calcId, Optional selfId) implements Value { + /** + * Creates a function. + * + * @param calcId the calc declaration, never {@code null} or empty + * @param selfId the object read against, never {@code null} + * @throws IllegalArgumentException if {@code calcId} is empty + */ + public FunctionValue { + Objects.requireNonNull(calcId, "calcId"); + Objects.requireNonNull(selfId, "selfId"); + if (calcId.isEmpty()) { + throw new IllegalArgumentException("a function names no calc"); + } + } + } + /** * One literal of an enumeration definition. * diff --git a/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/internal/Protos.java b/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/internal/Protos.java index 14882906f..2f2af0f6f 100644 --- a/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/internal/Protos.java +++ b/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/internal/Protos.java @@ -58,6 +58,7 @@ public static Optional value(org.openmbee.opensysml.proto.Value value) { case VECTOR -> Optional.of(vector(value.getVector())); case VECTOR_QUANTITY -> Optional.of(vectorQuantity(value.getVectorQuantity())); case MEASUREMENT_REF -> Optional.of(measurementRef(value.getMeasurementRef())); + case FUNCTION -> Optional.of(function(value.getFunction())); case KIND_NOT_SET -> Optional.empty(); }; } @@ -121,6 +122,16 @@ private static Value measurementRef(org.openmbee.opensysml.proto.MeasurementRef ref.getUnit(), unitTerm(ref.getUnitTerm()), present(ref.getUnitId())); } + private static Value function(org.openmbee.opensysml.proto.Function function) { + if (function.getCalcId().isEmpty()) { + throw new TransportException( + "the service answered a malformed function: it names no calc", null); + } + return new Value.FunctionValue( + function.getCalcId(), + function.getSelfId() == 0 ? Optional.empty() : Optional.of(function.getSelfId())); + } + private static Value sequence(org.openmbee.opensysml.proto.Value value) { List elements = new ArrayList<>(); for (org.openmbee.opensysml.proto.Value element : value.getSequence().getElementsList()) { diff --git a/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/proto/Function.java b/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/proto/Function.java new file mode 100644 index 000000000..1e70e194c --- /dev/null +++ b/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/proto/Function.java @@ -0,0 +1,643 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// NO CHECKED-IN PROTOBUF GENCODE +// source: sysml.proto +// Protobuf Java Version: 4.33.1 + +package org.openmbee.opensysml.proto; + +/** + *
+ * Function is a calc held as a value: a calc definition, or a calc usage with
+ * an input no read could supply, as `Sq` in `Fn(Sq, 3.0)` or the `f` of
+ * `in calc f {...}`. It crosses as the declaration it is a value of, which is
+ * its identity: two functions are the same exactly when calc_id and self_id
+ * are. A function closing over the bindings of the behavior body it is
+ * declared in has no wire form and crosses as the null arm.
+ * 
+ * + * Protobuf type {@code sysml.Function} + */ +@com.google.protobuf.Generated +public final class Function extends + com.google.protobuf.GeneratedMessage implements + // @@protoc_insertion_point(message_implements:sysml.Function) + FunctionOrBuilder { +private static final long serialVersionUID = 0L; + static { + com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( + com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, + /* major= */ 4, + /* minor= */ 33, + /* patch= */ 1, + /* suffix= */ "", + "Function"); + } + // Use Function.newBuilder() to construct. + private Function(com.google.protobuf.GeneratedMessage.Builder builder) { + super(builder); + } + private Function() { + calcId_ = ""; + } + + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.openmbee.opensysml.proto.Sysml.internal_static_sysml_Function_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.openmbee.opensysml.proto.Sysml.internal_static_sysml_Function_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.openmbee.opensysml.proto.Function.class, org.openmbee.opensysml.proto.Function.Builder.class); + } + + public static final int CALC_ID_FIELD_NUMBER = 1; + @SuppressWarnings("serial") + private volatile java.lang.Object calcId_ = ""; + /** + *
+   * FQN of the calc declaration ("Analysis::Sq"). Its identity.
+   * 
+ * + * string calc_id = 1 [json_name = "calcId"]; + * @return The calcId. + */ + @java.lang.Override + public java.lang.String getCalcId() { + java.lang.Object ref = calcId_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + calcId_ = s; + return s; + } + } + /** + *
+   * FQN of the calc declaration ("Analysis::Sq"). Its identity.
+   * 
+ * + * string calc_id = 1 [json_name = "calcId"]; + * @return The bytes for calcId. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getCalcIdBytes() { + java.lang.Object ref = calcId_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + calcId_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int SELF_ID_FIELD_NUMBER = 2; + private long selfId_ = 0L; + /** + *
+   * ID of the object the calc's feature names resolve against, for a calc
+   * usage read off a part (`holder.scale`); 0 for a function closing over no
+   * object. Sent by the service; a client sending one must name an object of
+   * the runtime the value is read in, or the value is rejected.
+   * 
+ * + * int64 self_id = 2 [json_name = "selfId"]; + * @return The selfId. + */ + @java.lang.Override + public long getSelfId() { + return selfId_; + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(calcId_)) { + com.google.protobuf.GeneratedMessage.writeString(output, 1, calcId_); + } + if (selfId_ != 0L) { + output.writeInt64(2, selfId_); + } + getUnknownFields().writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(calcId_)) { + size += com.google.protobuf.GeneratedMessage.computeStringSize(1, calcId_); + } + if (selfId_ != 0L) { + size += com.google.protobuf.CodedOutputStream + .computeInt64Size(2, selfId_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof org.openmbee.opensysml.proto.Function)) { + return super.equals(obj); + } + org.openmbee.opensysml.proto.Function other = (org.openmbee.opensysml.proto.Function) obj; + + if (!getCalcId() + .equals(other.getCalcId())) return false; + if (getSelfId() + != other.getSelfId()) return false; + if (!getUnknownFields().equals(other.getUnknownFields())) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + CALC_ID_FIELD_NUMBER; + hash = (53 * hash) + getCalcId().hashCode(); + hash = (37 * hash) + SELF_ID_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getSelfId()); + hash = (29 * hash) + getUnknownFields().hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static org.openmbee.opensysml.proto.Function parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.openmbee.opensysml.proto.Function parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.openmbee.opensysml.proto.Function parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.openmbee.opensysml.proto.Function parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.openmbee.opensysml.proto.Function parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.openmbee.opensysml.proto.Function parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.openmbee.opensysml.proto.Function parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input); + } + public static org.openmbee.opensysml.proto.Function parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input, extensionRegistry); + } + + public static org.openmbee.opensysml.proto.Function parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseDelimitedWithIOException(PARSER, input); + } + + public static org.openmbee.opensysml.proto.Function parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static org.openmbee.opensysml.proto.Function parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input); + } + public static org.openmbee.opensysml.proto.Function parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(org.openmbee.opensysml.proto.Function prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + *
+   * Function is a calc held as a value: a calc definition, or a calc usage with
+   * an input no read could supply, as `Sq` in `Fn(Sq, 3.0)` or the `f` of
+   * `in calc f {...}`. It crosses as the declaration it is a value of, which is
+   * its identity: two functions are the same exactly when calc_id and self_id
+   * are. A function closing over the bindings of the behavior body it is
+   * declared in has no wire form and crosses as the null arm.
+   * 
+ * + * Protobuf type {@code sysml.Function} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder implements + // @@protoc_insertion_point(builder_implements:sysml.Function) + org.openmbee.opensysml.proto.FunctionOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.openmbee.opensysml.proto.Sysml.internal_static_sysml_Function_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.openmbee.opensysml.proto.Sysml.internal_static_sysml_Function_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.openmbee.opensysml.proto.Function.class, org.openmbee.opensysml.proto.Function.Builder.class); + } + + // Construct using org.openmbee.opensysml.proto.Function.newBuilder() + private Builder() { + + } + + private Builder( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + super(parent); + + } + @java.lang.Override + public Builder clear() { + super.clear(); + bitField0_ = 0; + calcId_ = ""; + selfId_ = 0L; + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.openmbee.opensysml.proto.Sysml.internal_static_sysml_Function_descriptor; + } + + @java.lang.Override + public org.openmbee.opensysml.proto.Function getDefaultInstanceForType() { + return org.openmbee.opensysml.proto.Function.getDefaultInstance(); + } + + @java.lang.Override + public org.openmbee.opensysml.proto.Function build() { + org.openmbee.opensysml.proto.Function result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public org.openmbee.opensysml.proto.Function buildPartial() { + org.openmbee.opensysml.proto.Function result = new org.openmbee.opensysml.proto.Function(this); + if (bitField0_ != 0) { buildPartial0(result); } + onBuilt(); + return result; + } + + private void buildPartial0(org.openmbee.opensysml.proto.Function result) { + int from_bitField0_ = bitField0_; + if (((from_bitField0_ & 0x00000001) != 0)) { + result.calcId_ = calcId_; + } + if (((from_bitField0_ & 0x00000002) != 0)) { + result.selfId_ = selfId_; + } + } + + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.openmbee.opensysml.proto.Function) { + return mergeFrom((org.openmbee.opensysml.proto.Function)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.openmbee.opensysml.proto.Function other) { + if (other == org.openmbee.opensysml.proto.Function.getDefaultInstance()) return this; + if (!other.getCalcId().isEmpty()) { + calcId_ = other.calcId_; + bitField0_ |= 0x00000001; + onChanged(); + } + if (other.getSelfId() != 0L) { + setSelfId(other.getSelfId()); + } + this.mergeUnknownFields(other.getUnknownFields()); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + calcId_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000001; + break; + } // case 10 + case 16: { + selfId_ = input.readInt64(); + bitField0_ |= 0x00000002; + break; + } // case 16 + default: { + if (!super.parseUnknownField(input, extensionRegistry, tag)) { + done = true; // was an endgroup tag + } + break; + } // default: + } // switch (tag) + } // while (!done) + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } finally { + onChanged(); + } // finally + return this; + } + private int bitField0_; + + private java.lang.Object calcId_ = ""; + /** + *
+     * FQN of the calc declaration ("Analysis::Sq"). Its identity.
+     * 
+ * + * string calc_id = 1 [json_name = "calcId"]; + * @return The calcId. + */ + public java.lang.String getCalcId() { + java.lang.Object ref = calcId_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + calcId_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + *
+     * FQN of the calc declaration ("Analysis::Sq"). Its identity.
+     * 
+ * + * string calc_id = 1 [json_name = "calcId"]; + * @return The bytes for calcId. + */ + public com.google.protobuf.ByteString + getCalcIdBytes() { + java.lang.Object ref = calcId_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + calcId_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + *
+     * FQN of the calc declaration ("Analysis::Sq"). Its identity.
+     * 
+ * + * string calc_id = 1 [json_name = "calcId"]; + * @param value The calcId to set. + * @return This builder for chaining. + */ + public Builder setCalcId( + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + calcId_ = value; + bitField0_ |= 0x00000001; + onChanged(); + return this; + } + /** + *
+     * FQN of the calc declaration ("Analysis::Sq"). Its identity.
+     * 
+ * + * string calc_id = 1 [json_name = "calcId"]; + * @return This builder for chaining. + */ + public Builder clearCalcId() { + calcId_ = getDefaultInstance().getCalcId(); + bitField0_ = (bitField0_ & ~0x00000001); + onChanged(); + return this; + } + /** + *
+     * FQN of the calc declaration ("Analysis::Sq"). Its identity.
+     * 
+ * + * string calc_id = 1 [json_name = "calcId"]; + * @param value The bytes for calcId to set. + * @return This builder for chaining. + */ + public Builder setCalcIdBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + checkByteStringIsUtf8(value); + calcId_ = value; + bitField0_ |= 0x00000001; + onChanged(); + return this; + } + + private long selfId_ ; + /** + *
+     * ID of the object the calc's feature names resolve against, for a calc
+     * usage read off a part (`holder.scale`); 0 for a function closing over no
+     * object. Sent by the service; a client sending one must name an object of
+     * the runtime the value is read in, or the value is rejected.
+     * 
+ * + * int64 self_id = 2 [json_name = "selfId"]; + * @return The selfId. + */ + @java.lang.Override + public long getSelfId() { + return selfId_; + } + /** + *
+     * ID of the object the calc's feature names resolve against, for a calc
+     * usage read off a part (`holder.scale`); 0 for a function closing over no
+     * object. Sent by the service; a client sending one must name an object of
+     * the runtime the value is read in, or the value is rejected.
+     * 
+ * + * int64 self_id = 2 [json_name = "selfId"]; + * @param value The selfId to set. + * @return This builder for chaining. + */ + public Builder setSelfId(long value) { + + selfId_ = value; + bitField0_ |= 0x00000002; + onChanged(); + return this; + } + /** + *
+     * ID of the object the calc's feature names resolve against, for a calc
+     * usage read off a part (`holder.scale`); 0 for a function closing over no
+     * object. Sent by the service; a client sending one must name an object of
+     * the runtime the value is read in, or the value is rejected.
+     * 
+ * + * int64 self_id = 2 [json_name = "selfId"]; + * @return This builder for chaining. + */ + public Builder clearSelfId() { + bitField0_ = (bitField0_ & ~0x00000002); + selfId_ = 0L; + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:sysml.Function) + } + + // @@protoc_insertion_point(class_scope:sysml.Function) + private static final org.openmbee.opensysml.proto.Function DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new org.openmbee.opensysml.proto.Function(); + } + + public static org.openmbee.opensysml.proto.Function getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public Function parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + Builder builder = newBuilder(); + try { + builder.mergeFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(builder.buildPartial()); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(builder.buildPartial()); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException(e) + .setUnfinishedMessage(builder.buildPartial()); + } + return builder.buildPartial(); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public org.openmbee.opensysml.proto.Function getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + +} + diff --git a/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/proto/FunctionOrBuilder.java b/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/proto/FunctionOrBuilder.java new file mode 100644 index 000000000..026edcc62 --- /dev/null +++ b/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/proto/FunctionOrBuilder.java @@ -0,0 +1,45 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// NO CHECKED-IN PROTOBUF GENCODE +// source: sysml.proto +// Protobuf Java Version: 4.33.1 + +package org.openmbee.opensysml.proto; + +@com.google.protobuf.Generated +public interface FunctionOrBuilder extends + // @@protoc_insertion_point(interface_extends:sysml.Function) + com.google.protobuf.MessageOrBuilder { + + /** + *
+   * FQN of the calc declaration ("Analysis::Sq"). Its identity.
+   * 
+ * + * string calc_id = 1 [json_name = "calcId"]; + * @return The calcId. + */ + java.lang.String getCalcId(); + /** + *
+   * FQN of the calc declaration ("Analysis::Sq"). Its identity.
+   * 
+ * + * string calc_id = 1 [json_name = "calcId"]; + * @return The bytes for calcId. + */ + com.google.protobuf.ByteString + getCalcIdBytes(); + + /** + *
+   * ID of the object the calc's feature names resolve against, for a calc
+   * usage read off a part (`holder.scale`); 0 for a function closing over no
+   * object. Sent by the service; a client sending one must name an object of
+   * the runtime the value is read in, or the value is rejected.
+   * 
+ * + * int64 self_id = 2 [json_name = "selfId"]; + * @return The selfId. + */ + long getSelfId(); +} diff --git a/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/proto/ServerInfoResponse.java b/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/proto/ServerInfoResponse.java index 00ea3cac5..fdc8af8f6 100644 --- a/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/proto/ServerInfoResponse.java +++ b/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/proto/ServerInfoResponse.java @@ -146,6 +146,11 @@ public java.lang.String getVersion() { * refused with UNIMPLEMENTED rather than read as another * value. Separate from structured_values, which a client * built before this arm existed may already claim. + * "function_values" - a Value carries a calc held as a value as function, + * named by its declaration, rather than reporting it as an + * unsupported null, and one is accepted as an action input + * or calc argument; without it, one is refused with + * UNIMPLEMENTED rather than read as another value. * "apply_edits" - the ApplyEdits RPC edits a parsed model's own source, * preserving everything the edit did not touch. * "document_query" - the RunDocumentQuery RPC runs a named document query @@ -202,6 +207,11 @@ public java.lang.String getVersion() { * refused with UNIMPLEMENTED rather than read as another * value. Separate from structured_values, which a client * built before this arm existed may already claim. + * "function_values" - a Value carries a calc held as a value as function, + * named by its declaration, rather than reporting it as an + * unsupported null, and one is accepted as an action input + * or calc argument; without it, one is refused with + * UNIMPLEMENTED rather than read as another value. * "apply_edits" - the ApplyEdits RPC edits a parsed model's own source, * preserving everything the edit did not touch. * "document_query" - the RunDocumentQuery RPC runs a named document query @@ -257,6 +267,11 @@ public int getCapabilitiesCount() { * refused with UNIMPLEMENTED rather than read as another * value. Separate from structured_values, which a client * built before this arm existed may already claim. + * "function_values" - a Value carries a calc held as a value as function, + * named by its declaration, rather than reporting it as an + * unsupported null, and one is accepted as an action input + * or calc argument; without it, one is refused with + * UNIMPLEMENTED rather than read as another value. * "apply_edits" - the ApplyEdits RPC edits a parsed model's own source, * preserving everything the edit did not touch. * "document_query" - the RunDocumentQuery RPC runs a named document query @@ -313,6 +328,11 @@ public java.lang.String getCapabilities(int index) { * refused with UNIMPLEMENTED rather than read as another * value. Separate from structured_values, which a client * built before this arm existed may already claim. + * "function_values" - a Value carries a calc held as a value as function, + * named by its declaration, rather than reporting it as an + * unsupported null, and one is accepted as an action input + * or calc argument; without it, one is refused with + * UNIMPLEMENTED rather than read as another value. * "apply_edits" - the ApplyEdits RPC edits a parsed model's own source, * preserving everything the edit did not touch. * "document_query" - the RunDocumentQuery RPC runs a named document query @@ -818,6 +838,11 @@ private void ensureCapabilitiesIsMutable() { * refused with UNIMPLEMENTED rather than read as another * value. Separate from structured_values, which a client * built before this arm existed may already claim. + * "function_values" - a Value carries a calc held as a value as function, + * named by its declaration, rather than reporting it as an + * unsupported null, and one is accepted as an action input + * or calc argument; without it, one is refused with + * UNIMPLEMENTED rather than read as another value. * "apply_edits" - the ApplyEdits RPC edits a parsed model's own source, * preserving everything the edit did not touch. * "document_query" - the RunDocumentQuery RPC runs a named document query @@ -875,6 +900,11 @@ private void ensureCapabilitiesIsMutable() { * refused with UNIMPLEMENTED rather than read as another * value. Separate from structured_values, which a client * built before this arm existed may already claim. + * "function_values" - a Value carries a calc held as a value as function, + * named by its declaration, rather than reporting it as an + * unsupported null, and one is accepted as an action input + * or calc argument; without it, one is refused with + * UNIMPLEMENTED rather than read as another value. * "apply_edits" - the ApplyEdits RPC edits a parsed model's own source, * preserving everything the edit did not touch. * "document_query" - the RunDocumentQuery RPC runs a named document query @@ -930,6 +960,11 @@ public int getCapabilitiesCount() { * refused with UNIMPLEMENTED rather than read as another * value. Separate from structured_values, which a client * built before this arm existed may already claim. + * "function_values" - a Value carries a calc held as a value as function, + * named by its declaration, rather than reporting it as an + * unsupported null, and one is accepted as an action input + * or calc argument; without it, one is refused with + * UNIMPLEMENTED rather than read as another value. * "apply_edits" - the ApplyEdits RPC edits a parsed model's own source, * preserving everything the edit did not touch. * "document_query" - the RunDocumentQuery RPC runs a named document query @@ -986,6 +1021,11 @@ public java.lang.String getCapabilities(int index) { * refused with UNIMPLEMENTED rather than read as another * value. Separate from structured_values, which a client * built before this arm existed may already claim. + * "function_values" - a Value carries a calc held as a value as function, + * named by its declaration, rather than reporting it as an + * unsupported null, and one is accepted as an action input + * or calc argument; without it, one is refused with + * UNIMPLEMENTED rather than read as another value. * "apply_edits" - the ApplyEdits RPC edits a parsed model's own source, * preserving everything the edit did not touch. * "document_query" - the RunDocumentQuery RPC runs a named document query @@ -1043,6 +1083,11 @@ public java.lang.String getCapabilities(int index) { * refused with UNIMPLEMENTED rather than read as another * value. Separate from structured_values, which a client * built before this arm existed may already claim. + * "function_values" - a Value carries a calc held as a value as function, + * named by its declaration, rather than reporting it as an + * unsupported null, and one is accepted as an action input + * or calc argument; without it, one is refused with + * UNIMPLEMENTED rather than read as another value. * "apply_edits" - the ApplyEdits RPC edits a parsed model's own source, * preserving everything the edit did not touch. * "document_query" - the RunDocumentQuery RPC runs a named document query @@ -1106,6 +1151,11 @@ public Builder setCapabilities( * refused with UNIMPLEMENTED rather than read as another * value. Separate from structured_values, which a client * built before this arm existed may already claim. + * "function_values" - a Value carries a calc held as a value as function, + * named by its declaration, rather than reporting it as an + * unsupported null, and one is accepted as an action input + * or calc argument; without it, one is refused with + * UNIMPLEMENTED rather than read as another value. * "apply_edits" - the ApplyEdits RPC edits a parsed model's own source, * preserving everything the edit did not touch. * "document_query" - the RunDocumentQuery RPC runs a named document query @@ -1168,6 +1218,11 @@ public Builder addCapabilities( * refused with UNIMPLEMENTED rather than read as another * value. Separate from structured_values, which a client * built before this arm existed may already claim. + * "function_values" - a Value carries a calc held as a value as function, + * named by its declaration, rather than reporting it as an + * unsupported null, and one is accepted as an action input + * or calc argument; without it, one is refused with + * UNIMPLEMENTED rather than read as another value. * "apply_edits" - the ApplyEdits RPC edits a parsed model's own source, * preserving everything the edit did not touch. * "document_query" - the RunDocumentQuery RPC runs a named document query @@ -1230,6 +1285,11 @@ public Builder addAllCapabilities( * refused with UNIMPLEMENTED rather than read as another * value. Separate from structured_values, which a client * built before this arm existed may already claim. + * "function_values" - a Value carries a calc held as a value as function, + * named by its declaration, rather than reporting it as an + * unsupported null, and one is accepted as an action input + * or calc argument; without it, one is refused with + * UNIMPLEMENTED rather than read as another value. * "apply_edits" - the ApplyEdits RPC edits a parsed model's own source, * preserving everything the edit did not touch. * "document_query" - the RunDocumentQuery RPC runs a named document query @@ -1289,6 +1349,11 @@ public Builder clearCapabilities() { * refused with UNIMPLEMENTED rather than read as another * value. Separate from structured_values, which a client * built before this arm existed may already claim. + * "function_values" - a Value carries a calc held as a value as function, + * named by its declaration, rather than reporting it as an + * unsupported null, and one is accepted as an action input + * or calc argument; without it, one is refused with + * UNIMPLEMENTED rather than read as another value. * "apply_edits" - the ApplyEdits RPC edits a parsed model's own source, * preserving everything the edit did not touch. * "document_query" - the RunDocumentQuery RPC runs a named document query diff --git a/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/proto/ServerInfoResponseOrBuilder.java b/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/proto/ServerInfoResponseOrBuilder.java index 6dfb2ca32..6bdb5f845 100644 --- a/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/proto/ServerInfoResponseOrBuilder.java +++ b/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/proto/ServerInfoResponseOrBuilder.java @@ -75,6 +75,11 @@ public interface ServerInfoResponseOrBuilder extends * refused with UNIMPLEMENTED rather than read as another * value. Separate from structured_values, which a client * built before this arm existed may already claim. + * "function_values" - a Value carries a calc held as a value as function, + * named by its declaration, rather than reporting it as an + * unsupported null, and one is accepted as an action input + * or calc argument; without it, one is refused with + * UNIMPLEMENTED rather than read as another value. * "apply_edits" - the ApplyEdits RPC edits a parsed model's own source, * preserving everything the edit did not touch. * "document_query" - the RunDocumentQuery RPC runs a named document query @@ -129,6 +134,11 @@ public interface ServerInfoResponseOrBuilder extends * refused with UNIMPLEMENTED rather than read as another * value. Separate from structured_values, which a client * built before this arm existed may already claim. + * "function_values" - a Value carries a calc held as a value as function, + * named by its declaration, rather than reporting it as an + * unsupported null, and one is accepted as an action input + * or calc argument; without it, one is refused with + * UNIMPLEMENTED rather than read as another value. * "apply_edits" - the ApplyEdits RPC edits a parsed model's own source, * preserving everything the edit did not touch. * "document_query" - the RunDocumentQuery RPC runs a named document query @@ -182,6 +192,11 @@ public interface ServerInfoResponseOrBuilder extends * refused with UNIMPLEMENTED rather than read as another * value. Separate from structured_values, which a client * built before this arm existed may already claim. + * "function_values" - a Value carries a calc held as a value as function, + * named by its declaration, rather than reporting it as an + * unsupported null, and one is accepted as an action input + * or calc argument; without it, one is refused with + * UNIMPLEMENTED rather than read as another value. * "apply_edits" - the ApplyEdits RPC edits a parsed model's own source, * preserving everything the edit did not touch. * "document_query" - the RunDocumentQuery RPC runs a named document query @@ -236,6 +251,11 @@ public interface ServerInfoResponseOrBuilder extends * refused with UNIMPLEMENTED rather than read as another * value. Separate from structured_values, which a client * built before this arm existed may already claim. + * "function_values" - a Value carries a calc held as a value as function, + * named by its declaration, rather than reporting it as an + * unsupported null, and one is accepted as an action input + * or calc argument; without it, one is refused with + * UNIMPLEMENTED rather than read as another value. * "apply_edits" - the ApplyEdits RPC edits a parsed model's own source, * preserving everything the edit did not touch. * "document_query" - the RunDocumentQuery RPC runs a named document query diff --git a/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/proto/Sysml.java b/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/proto/Sysml.java index 68c1a59d8..2024867c4 100644 --- a/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/proto/Sysml.java +++ b/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/proto/Sysml.java @@ -291,6 +291,11 @@ public static void registerAllExtensions( static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_sysml_Value_fieldAccessorTable; + static final com.google.protobuf.Descriptors.Descriptor + internal_static_sysml_Function_descriptor; + static final + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_sysml_Function_fieldAccessorTable; static final com.google.protobuf.Descriptors.Descriptor internal_static_sysml_Array_descriptor; static final @@ -658,7 +663,7 @@ public static void registerAllExtensions( "\030\001 \001(\tR\005lower\022\024\n\005upper\030\002 \001(\tR\005upper\"o\n\rA" + "ttributeInfo\022\022\n\004name\030\001 \001(\tR\004name\022\022\n\004type" + "\030\002 \001(\tR\004type\022\"\n\005value\030\003 \001(\0132\014.sysml.Valu" + - "eR\005value\022\022\n\004unit\030\004 \001(\tR\004unit\"\201\005\n\005Value\022\035" + + "eR\005value\022\022\n\004unit\030\004 \001(\tR\004unit\"\260\005\n\005Value\022\035" + "\n\tint_value\030\001 \001(\003H\000R\010intValue\022\037\n\nreal_va" + "lue\030\002 \001(\001H\000R\trealValue\022\037\n\nbool_value\030\003 \001" + "(\010H\000R\tboolValue\022#\n\014string_value\030\004 \001(\tH\000R" + @@ -674,148 +679,151 @@ public static void registerAllExtensions( "orH\000R\006vector\022@\n\017vector_quantity\030\016 \001(\0132\025." + "sysml.VectorQuantityH\000R\016vectorQuantity\022@" + "\n\017measurement_ref\030\017 \001(\0132\025.sysml.Measurem" + - "entRefH\000R\016measurementRefB\006\n\004kind\"Q\n\005Arra" + - "y\022\036\n\ndimensions\030\001 \003(\003R\ndimensions\022(\n\010ele" + - "ments\030\002 \003(\0132\014.sysml.ValueR\010elements\"6\n\006V" + - "ector\022,\n\ncomponents\030\001 \003(\0132\014.sysml.ValueR" + - "\ncomponents\"A\n\016VectorQuantity\022/\n\ncompone" + - "nts\030\001 \003(\0132\017.sysml.QuantityR\ncomponents\";" + - "\n\007Complex\022\022\n\004real\030\001 \001(\001R\004real\022\034\n\timagina" + - "ry\030\002 \001(\001R\timaginary\"g\n\013EnumLiteral\022\035\n\nli" + - "teral_id\030\001 \001(\tR\tliteralId\022%\n\016enumeration" + - "_id\030\002 \001(\tR\renumerationId\022\022\n\004name\030\003 \001(\tR\004" + - "name\"9\n\rValueSequence\022(\n\010elements\030\001 \003(\0132" + - "\014.sysml.ValueR\010elements\"\251\001\n\010Quantity\022%\n\r" + - "int_magnitude\030\001 \001(\003H\000R\014intMagnitude\022\'\n\016r" + - "eal_magnitude\030\002 \001(\001H\000R\rrealMagnitude\022\022\n\004" + - "unit\030\003 \001(\tR\004unit\022,\n\tunit_term\030\004 \001(\0132\017.sy" + - "sml.UnitTermR\010unitTermB\013\n\tmagnitude\"k\n\016M" + - "easurementRef\022\022\n\004unit\030\001 \001(\tR\004unit\022,\n\tuni" + - "t_term\030\002 \001(\0132\017.sysml.UnitTermR\010unitTerm\022" + - "\027\n\007unit_id\030\003 \001(\tR\006unitId\"q\n\010UnitTerm\022\033\n\t" + - "scale_num\030\001 \001(\001R\010scaleNum\022\033\n\tscale_den\030\002" + - " \001(\001R\010scaleDen\022+\n\007factors\030\003 \003(\0132\021.sysml." + - "UnitFactorR\007factors\"A\n\nUnitFactor\022\027\n\007uni" + - "t_id\030\001 \001(\tR\006unitId\022\032\n\010exponent\030\002 \001(\001R\010ex" + - "ponent\"c\n\nDiagnostic\022\032\n\010severity\030\001 \001(\tR\010" + - "severity\022\030\n\007message\030\002 \001(\tR\007message\022\037\n\004sp" + - "an\030\003 \001(\0132\013.sysml.SpanR\004span\"\212\001\n\004Span\022\022\n\004" + - "file\030\001 \001(\tR\004file\022\035\n\nstart_line\030\002 \001(\005R\tst" + - "artLine\022\033\n\tstart_col\030\003 \001(\005R\010startCol\022\031\n\010" + - "end_line\030\004 \001(\005R\007endLine\022\027\n\007end_col\030\005 \001(\005" + - "R\006endCol\"\023\n\021ServerInfoRequest\"R\n\022ServerI" + - "nfoResponse\022\030\n\007version\030\001 \001(\tR\007version\022\"\n" + - "\014capabilities\030\002 \003(\tR\014capabilities\"p\n\014Que" + - "ryRequest\022\035\n\nmodel_hash\030\001 \001(\tR\tmodelHash" + - "\022\"\n\005query\030\002 \001(\0132\014.sysml.QueryR\005query\022\035\n\n" + - "oslc_query\030\003 \001(\tR\toslcQuery\"F\n\rQueryResp" + - "onse\0225\n\010elements\030\001 \003(\0132\031.sysml.QueryResu" + - "ltElementR\010elements\"^\n\005Query\022\024\n\005scope\030\001 " + - "\003(\tR\005scope\022\026\n\006select\030\002 \003(\tR\006select\022\'\n\005wh" + - "ere\030\003 \001(\0132\021.sysml.ConstraintR\005where\"\222\001\n\n" + - "Constraint\022:\n\tprimitive\030\001 \001(\0132\032.sysml.Pr" + - "imitiveConstraintH\000R\tprimitive\022:\n\tcompos" + - "ite\030\002 \001(\0132\032.sysml.CompositeConstraintH\000R" + - "\tcompositeB\014\n\nconstraint\"\227\001\n\023PrimitiveCo" + - "nstraint\022\030\n\007inverse\030\001 \001(\010R\007inverse\022\032\n\010pr" + - "operty\030\002 \001(\tR\010property\0224\n\010operator\030\003 \001(\016" + - "2\030.sysml.PrimitiveOperatorR\010operator\022\024\n\005" + - "value\030\004 \003(\tR\005value\"~\n\023CompositeConstrain" + - "t\0224\n\010operator\030\001 \001(\0162\030.sysml.CompositeOpe" + - "ratorR\010operator\0221\n\nconstraint\030\002 \003(\0132\021.sy" + - "sml.ConstraintR\nconstraint\"\302\001\n\022QueryResu" + - "ltElement\022\016\n\002id\030\001 \001(\tR\002id\022\022\n\004type\030\002 \001(\tR" + - "\004type\022I\n\nproperties\030\003 \003(\0132).sysml.QueryR" + - "esultElement.PropertiesEntryR\nproperties" + - "\032=\n\017PropertiesEntry\022\020\n\003key\030\001 \001(\tR\003key\022\024\n" + - "\005value\030\002 \001(\tR\005value:\0028\001\"\214\001\n\027RunDocumentQ" + - "ueryRequest\022\035\n\nmodel_hash\030\001 \001(\tR\tmodelHa" + - "sh\022\031\n\010query_id\030\002 \001(\tR\007queryId\0227\n\010binding" + - "s\030\003 \003(\0132\033.sysml.DocumentQueryBindingR\010bi" + - "ndings\"b\n\024DocumentQueryBinding\022\034\n\tparame" + - "ter\030\001 \001(\tR\tparameter\022,\n\006values\030\002 \003(\0132\024.s" + - "ysml.DocumentValueR\006values\"\256\002\n\rDocumentV" + - "alue\022\037\n\nelement_id\030\001 \001(\tH\000R\telementId\022#\n" + - "\014string_value\030\002 \001(\tH\000R\013stringValue\022\035\n\tin" + - "t_value\030\003 \001(\003H\000R\010intValue\022\037\n\nreal_value\030" + - "\004 \001(\001H\000R\trealValue\022\037\n\nbool_value\030\005 \001(\010H\000" + - "R\tboolValue\022\034\n\010infinity\030\006 \001(\010H\000R\010infinit" + - "y\022-\n\010quantity\030\010 \001(\0132\017.sysml.QuantityH\000R\010" + - "quantity\022!\n\014element_type\030\007 \001(\tR\013elementT" + - "ypeB\006\n\004kind\")\n\023DocumentQueryColumn\022\022\n\004na" + - "me\030\001 \001(\tR\004name\"A\n\021DocumentQueryCell\022,\n\006v" + - "alues\030\001 \003(\0132\024.sysml.DocumentValueR\006value" + - "s\"r\n\020DocumentQueryRow\022.\n\007element\030\001 \001(\0132\024" + - ".sysml.DocumentValueR\007element\022.\n\005cells\030\002" + - " \003(\0132\030.sysml.DocumentQueryCellR\005cells\"}\n" + - "\030RunDocumentQueryResponse\0224\n\007columns\030\001 \003" + - "(\0132\032.sysml.DocumentQueryColumnR\007columns\022" + - "+\n\004rows\030\002 \003(\0132\027.sysml.DocumentQueryRowR\004" + - "rows\"W\n\025RenderDocumentRequest\022\035\n\nmodel_h" + - "ash\030\001 \001(\tR\tmodelHash\022\037\n\013document_id\030\002 \001(" + - "\tR\ndocumentId\"4\n\026RenderDocumentResponse\022" + - "\032\n\010markdown\030\001 \001(\tR\010markdown*\223\001\n\rFailureR" + - "eason\022\036\n\032FAILURE_REASON_UNSPECIFIED\020\000\022\035\n" + - "\031FAILURE_REASON_EVALUATION\020\001\022\035\n\031FAILURE_" + - "REASON_WRONG_KIND\020\002\022$\n FAILURE_REASON_AM" + - "BIGUOUS_SUBJECT\020\003*\235\004\n\013EditFailure\022\034\n\030EDI" + - "T_FAILURE_UNSPECIFIED\020\000\022\036\n\032EDIT_FAILURE_" + - "NO_OPERATIONS\020\001\022\037\n\033EDIT_FAILURE_UNKNOWN_" + - "TARGET\020\002\022!\n\035EDIT_FAILURE_AMBIGUOUS_TARGE" + - "T\020\003\022\033\n\027EDIT_FAILURE_NOT_VALUED\020\004\022\036\n\032EDIT" + - "_FAILURE_INVALID_VALUE\020\005\022\035\n\031EDIT_FAILURE" + - "_INVALID_NAME\020\006\022\032\n\026EDIT_FAILURE_NOT_NAME" + - "D\020\007\022\"\n\036EDIT_FAILURE_RENAME_REFERENCED\020\010\022" + - "\"\n\036EDIT_FAILURE_OVERLAPPING_EDITS\020\t\022\037\n\033E" + - "DIT_FAILURE_RESULT_INVALID\020\n\022\036\n\032EDIT_FAI" + - "LURE_OWNER_UNKNOWN\020\013\022$\n EDIT_FAILURE_OWN" + - "ER_NOT_NAMESPACE\020\014\022\035\n\031EDIT_FAILURE_ILLEG" + - "AL_KIND\020\r\022\"\n\036EDIT_FAILURE_MEMBER_NAME_TA" + - "KEN\020\016\022\"\n\036EDIT_FAILURE_DELETE_REFERENCED\020" + - "\017*\222\001\n\021PrimitiveOperator\022\"\n\036PRIMITIVE_OPE" + - "RATOR_UNSPECIFIED\020\000\022\034\n\030PRIMITIVE_OPERATO" + - "R_EQUAL\020\001\022\036\n\032PRIMITIVE_OPERATOR_GREATER\020" + - "\002\022\033\n\027PRIMITIVE_OPERATOR_LESS\020\003*n\n\021Compos" + - "iteOperator\022\"\n\036COMPOSITE_OPERATOR_UNSPEC" + - "IFIED\020\000\022\032\n\026COMPOSITE_OPERATOR_AND\020\001\022\031\n\025C" + - "OMPOSITE_OPERATOR_OR\020\0022\347\n\n\014SysMLService\022" + - "D\n\rGetServerInfo\022\030.sysml.ServerInfoReque" + - "st\032\031.sysml.ServerInfoResponse\022>\n\tParseFi" + - "le\022\027.sysml.ParseFileRequest\032\030.sysml.Pars" + - "eFileResponse\022G\n\014ParseSources\022\032.sysml.Pa" + - "rseSourcesRequest\032\033.sysml.ParseSourcesRe" + - "sponse\022;\n\tGetSymbol\022\027.sysml.GetSymbolReq" + - "uest\032\025.sysml.SymbolResponse\022G\n\016GetDiagno" + - "stics\022\031.sysml.DiagnosticsRequest\032\032.sysml" + - ".DiagnosticsResponse\022;\n\010Evaluate\022\026.sysml" + - ".EvaluateRequest\032\027.sysml.EvaluateRespons" + - "e\022D\n\013Instantiate\022\031.sysml.InstantiateRequ" + - "est\032\032.sysml.InstantiateResponse\022J\n\rExecu" + - "teAction\022\033.sysml.ExecuteActionRequest\032\034." + - "sysml.ExecuteActionResponse\022G\n\014ExecuteSt" + - "ate\022\032.sysml.ExecuteStateRequest\032\033.sysml." + - "ExecuteStateResponse\0228\n\007Convert\022\025.sysml." + - "ConvertRequest\032\026.sysml.ConvertResponse\022A" + - "\n\nApplyEdits\022\030.sysml.ApplyEditsRequest\032\031" + - ".sysml.ApplyEditsResponse\022S\n\020VerifyConst" + - "raint\022\036.sysml.VerifyConstraintRequest\032\037." + - "sysml.VerifyConstraintResponse\022V\n\021Verify" + - "Requirement\022\037.sysml.VerifyRequirementReq" + - "uest\032 .sysml.VerifyRequirementResponse\022Y" + - "\n\022VerifySatisfaction\022 .sysml.VerifySatis" + - "factionRequest\032!.sysml.VerifySatisfactio" + - "nResponse\022G\n\014EvaluateCalc\022\032.sysml.Evalua" + - "teCalcRequest\032\033.sysml.EvaluateCalcRespon" + - "se\022D\n\013RunAnalysis\022\031.sysml.RunAnalysisReq" + - "uest\032\032.sysml.RunAnalysisResponse\0222\n\005Quer" + - "y\022\023.sysml.QueryRequest\032\024.sysml.QueryResp" + - "onse\022S\n\020RunDocumentQuery\022\036.sysml.RunDocu" + - "mentQueryRequest\032\037.sysml.RunDocumentQuer" + - "yResponse\022M\n\016RenderDocument\022\034.sysml.Rend" + - "erDocumentRequest\032\035.sysml.RenderDocument" + - "ResponseBJ\n\034org.openmbee.opensysml.proto" + - "P\001Z(github.com/Open-MBEE/OpenSysML/api/p" + - "rotob\006proto3" + "entRefH\000R\016measurementRef\022-\n\010function\030\020 \001" + + "(\0132\017.sysml.FunctionH\000R\010functionB\006\n\004kind\"" + + "<\n\010Function\022\027\n\007calc_id\030\001 \001(\tR\006calcId\022\027\n\007" + + "self_id\030\002 \001(\003R\006selfId\"Q\n\005Array\022\036\n\ndimens" + + "ions\030\001 \003(\003R\ndimensions\022(\n\010elements\030\002 \003(\013" + + "2\014.sysml.ValueR\010elements\"6\n\006Vector\022,\n\nco" + + "mponents\030\001 \003(\0132\014.sysml.ValueR\ncomponents" + + "\"A\n\016VectorQuantity\022/\n\ncomponents\030\001 \003(\0132\017" + + ".sysml.QuantityR\ncomponents\";\n\007Complex\022\022" + + "\n\004real\030\001 \001(\001R\004real\022\034\n\timaginary\030\002 \001(\001R\ti" + + "maginary\"g\n\013EnumLiteral\022\035\n\nliteral_id\030\001 " + + "\001(\tR\tliteralId\022%\n\016enumeration_id\030\002 \001(\tR\r" + + "enumerationId\022\022\n\004name\030\003 \001(\tR\004name\"9\n\rVal" + + "ueSequence\022(\n\010elements\030\001 \003(\0132\014.sysml.Val" + + "ueR\010elements\"\251\001\n\010Quantity\022%\n\rint_magnitu" + + "de\030\001 \001(\003H\000R\014intMagnitude\022\'\n\016real_magnitu" + + "de\030\002 \001(\001H\000R\rrealMagnitude\022\022\n\004unit\030\003 \001(\tR" + + "\004unit\022,\n\tunit_term\030\004 \001(\0132\017.sysml.UnitTer" + + "mR\010unitTermB\013\n\tmagnitude\"k\n\016MeasurementR" + + "ef\022\022\n\004unit\030\001 \001(\tR\004unit\022,\n\tunit_term\030\002 \001(" + + "\0132\017.sysml.UnitTermR\010unitTerm\022\027\n\007unit_id\030" + + "\003 \001(\tR\006unitId\"q\n\010UnitTerm\022\033\n\tscale_num\030\001" + + " \001(\001R\010scaleNum\022\033\n\tscale_den\030\002 \001(\001R\010scale" + + "Den\022+\n\007factors\030\003 \003(\0132\021.sysml.UnitFactorR" + + "\007factors\"A\n\nUnitFactor\022\027\n\007unit_id\030\001 \001(\tR" + + "\006unitId\022\032\n\010exponent\030\002 \001(\001R\010exponent\"c\n\nD" + + "iagnostic\022\032\n\010severity\030\001 \001(\tR\010severity\022\030\n" + + "\007message\030\002 \001(\tR\007message\022\037\n\004span\030\003 \001(\0132\013." + + "sysml.SpanR\004span\"\212\001\n\004Span\022\022\n\004file\030\001 \001(\tR" + + "\004file\022\035\n\nstart_line\030\002 \001(\005R\tstartLine\022\033\n\t" + + "start_col\030\003 \001(\005R\010startCol\022\031\n\010end_line\030\004 " + + "\001(\005R\007endLine\022\027\n\007end_col\030\005 \001(\005R\006endCol\"\023\n" + + "\021ServerInfoRequest\"R\n\022ServerInfoResponse" + + "\022\030\n\007version\030\001 \001(\tR\007version\022\"\n\014capabiliti" + + "es\030\002 \003(\tR\014capabilities\"p\n\014QueryRequest\022\035" + + "\n\nmodel_hash\030\001 \001(\tR\tmodelHash\022\"\n\005query\030\002" + + " \001(\0132\014.sysml.QueryR\005query\022\035\n\noslc_query\030" + + "\003 \001(\tR\toslcQuery\"F\n\rQueryResponse\0225\n\010ele" + + "ments\030\001 \003(\0132\031.sysml.QueryResultElementR\010" + + "elements\"^\n\005Query\022\024\n\005scope\030\001 \003(\tR\005scope\022" + + "\026\n\006select\030\002 \003(\tR\006select\022\'\n\005where\030\003 \001(\0132\021" + + ".sysml.ConstraintR\005where\"\222\001\n\nConstraint\022" + + ":\n\tprimitive\030\001 \001(\0132\032.sysml.PrimitiveCons" + + "traintH\000R\tprimitive\022:\n\tcomposite\030\002 \001(\0132\032" + + ".sysml.CompositeConstraintH\000R\tcompositeB" + + "\014\n\nconstraint\"\227\001\n\023PrimitiveConstraint\022\030\n" + + "\007inverse\030\001 \001(\010R\007inverse\022\032\n\010property\030\002 \001(" + + "\tR\010property\0224\n\010operator\030\003 \001(\0162\030.sysml.Pr" + + "imitiveOperatorR\010operator\022\024\n\005value\030\004 \003(\t" + + "R\005value\"~\n\023CompositeConstraint\0224\n\010operat" + + "or\030\001 \001(\0162\030.sysml.CompositeOperatorR\010oper" + + "ator\0221\n\nconstraint\030\002 \003(\0132\021.sysml.Constra" + + "intR\nconstraint\"\302\001\n\022QueryResultElement\022\016" + + "\n\002id\030\001 \001(\tR\002id\022\022\n\004type\030\002 \001(\tR\004type\022I\n\npr" + + "operties\030\003 \003(\0132).sysml.QueryResultElemen" + + "t.PropertiesEntryR\nproperties\032=\n\017Propert" + + "iesEntry\022\020\n\003key\030\001 \001(\tR\003key\022\024\n\005value\030\002 \001(" + + "\tR\005value:\0028\001\"\214\001\n\027RunDocumentQueryRequest" + + "\022\035\n\nmodel_hash\030\001 \001(\tR\tmodelHash\022\031\n\010query" + + "_id\030\002 \001(\tR\007queryId\0227\n\010bindings\030\003 \003(\0132\033.s" + + "ysml.DocumentQueryBindingR\010bindings\"b\n\024D" + + "ocumentQueryBinding\022\034\n\tparameter\030\001 \001(\tR\t" + + "parameter\022,\n\006values\030\002 \003(\0132\024.sysml.Docume" + + "ntValueR\006values\"\256\002\n\rDocumentValue\022\037\n\nele" + + "ment_id\030\001 \001(\tH\000R\telementId\022#\n\014string_val" + + "ue\030\002 \001(\tH\000R\013stringValue\022\035\n\tint_value\030\003 \001" + + "(\003H\000R\010intValue\022\037\n\nreal_value\030\004 \001(\001H\000R\tre" + + "alValue\022\037\n\nbool_value\030\005 \001(\010H\000R\tboolValue" + + "\022\034\n\010infinity\030\006 \001(\010H\000R\010infinity\022-\n\010quanti" + + "ty\030\010 \001(\0132\017.sysml.QuantityH\000R\010quantity\022!\n" + + "\014element_type\030\007 \001(\tR\013elementTypeB\006\n\004kind" + + "\")\n\023DocumentQueryColumn\022\022\n\004name\030\001 \001(\tR\004n" + + "ame\"A\n\021DocumentQueryCell\022,\n\006values\030\001 \003(\013" + + "2\024.sysml.DocumentValueR\006values\"r\n\020Docume" + + "ntQueryRow\022.\n\007element\030\001 \001(\0132\024.sysml.Docu" + + "mentValueR\007element\022.\n\005cells\030\002 \003(\0132\030.sysm" + + "l.DocumentQueryCellR\005cells\"}\n\030RunDocumen" + + "tQueryResponse\0224\n\007columns\030\001 \003(\0132\032.sysml." + + "DocumentQueryColumnR\007columns\022+\n\004rows\030\002 \003" + + "(\0132\027.sysml.DocumentQueryRowR\004rows\"W\n\025Ren" + + "derDocumentRequest\022\035\n\nmodel_hash\030\001 \001(\tR\t" + + "modelHash\022\037\n\013document_id\030\002 \001(\tR\ndocument" + + "Id\"4\n\026RenderDocumentResponse\022\032\n\010markdown" + + "\030\001 \001(\tR\010markdown*\223\001\n\rFailureReason\022\036\n\032FA" + + "ILURE_REASON_UNSPECIFIED\020\000\022\035\n\031FAILURE_RE" + + "ASON_EVALUATION\020\001\022\035\n\031FAILURE_REASON_WRON" + + "G_KIND\020\002\022$\n FAILURE_REASON_AMBIGUOUS_SUB" + + "JECT\020\003*\235\004\n\013EditFailure\022\034\n\030EDIT_FAILURE_U" + + "NSPECIFIED\020\000\022\036\n\032EDIT_FAILURE_NO_OPERATIO" + + "NS\020\001\022\037\n\033EDIT_FAILURE_UNKNOWN_TARGET\020\002\022!\n" + + "\035EDIT_FAILURE_AMBIGUOUS_TARGET\020\003\022\033\n\027EDIT" + + "_FAILURE_NOT_VALUED\020\004\022\036\n\032EDIT_FAILURE_IN" + + "VALID_VALUE\020\005\022\035\n\031EDIT_FAILURE_INVALID_NA" + + "ME\020\006\022\032\n\026EDIT_FAILURE_NOT_NAMED\020\007\022\"\n\036EDIT" + + "_FAILURE_RENAME_REFERENCED\020\010\022\"\n\036EDIT_FAI" + + "LURE_OVERLAPPING_EDITS\020\t\022\037\n\033EDIT_FAILURE" + + "_RESULT_INVALID\020\n\022\036\n\032EDIT_FAILURE_OWNER_" + + "UNKNOWN\020\013\022$\n EDIT_FAILURE_OWNER_NOT_NAME" + + "SPACE\020\014\022\035\n\031EDIT_FAILURE_ILLEGAL_KIND\020\r\022\"" + + "\n\036EDIT_FAILURE_MEMBER_NAME_TAKEN\020\016\022\"\n\036ED" + + "IT_FAILURE_DELETE_REFERENCED\020\017*\222\001\n\021Primi" + + "tiveOperator\022\"\n\036PRIMITIVE_OPERATOR_UNSPE" + + "CIFIED\020\000\022\034\n\030PRIMITIVE_OPERATOR_EQUAL\020\001\022\036" + + "\n\032PRIMITIVE_OPERATOR_GREATER\020\002\022\033\n\027PRIMIT" + + "IVE_OPERATOR_LESS\020\003*n\n\021CompositeOperator" + + "\022\"\n\036COMPOSITE_OPERATOR_UNSPECIFIED\020\000\022\032\n\026" + + "COMPOSITE_OPERATOR_AND\020\001\022\031\n\025COMPOSITE_OP" + + "ERATOR_OR\020\0022\347\n\n\014SysMLService\022D\n\rGetServe" + + "rInfo\022\030.sysml.ServerInfoRequest\032\031.sysml." + + "ServerInfoResponse\022>\n\tParseFile\022\027.sysml." + + "ParseFileRequest\032\030.sysml.ParseFileRespon" + + "se\022G\n\014ParseSources\022\032.sysml.ParseSourcesR" + + "equest\032\033.sysml.ParseSourcesResponse\022;\n\tG" + + "etSymbol\022\027.sysml.GetSymbolRequest\032\025.sysm" + + "l.SymbolResponse\022G\n\016GetDiagnostics\022\031.sys" + + "ml.DiagnosticsRequest\032\032.sysml.Diagnostic" + + "sResponse\022;\n\010Evaluate\022\026.sysml.EvaluateRe" + + "quest\032\027.sysml.EvaluateResponse\022D\n\013Instan" + + "tiate\022\031.sysml.InstantiateRequest\032\032.sysml" + + ".InstantiateResponse\022J\n\rExecuteAction\022\033." + + "sysml.ExecuteActionRequest\032\034.sysml.Execu" + + "teActionResponse\022G\n\014ExecuteState\022\032.sysml" + + ".ExecuteStateRequest\032\033.sysml.ExecuteStat" + + "eResponse\0228\n\007Convert\022\025.sysml.ConvertRequ" + + "est\032\026.sysml.ConvertResponse\022A\n\nApplyEdit" + + "s\022\030.sysml.ApplyEditsRequest\032\031.sysml.Appl" + + "yEditsResponse\022S\n\020VerifyConstraint\022\036.sys" + + "ml.VerifyConstraintRequest\032\037.sysml.Verif" + + "yConstraintResponse\022V\n\021VerifyRequirement" + + "\022\037.sysml.VerifyRequirementRequest\032 .sysm" + + "l.VerifyRequirementResponse\022Y\n\022VerifySat" + + "isfaction\022 .sysml.VerifySatisfactionRequ" + + "est\032!.sysml.VerifySatisfactionResponse\022G" + + "\n\014EvaluateCalc\022\032.sysml.EvaluateCalcReque" + + "st\032\033.sysml.EvaluateCalcResponse\022D\n\013RunAn" + + "alysis\022\031.sysml.RunAnalysisRequest\032\032.sysm" + + "l.RunAnalysisResponse\0222\n\005Query\022\023.sysml.Q" + + "ueryRequest\032\024.sysml.QueryResponse\022S\n\020Run" + + "DocumentQuery\022\036.sysml.RunDocumentQueryRe" + + "quest\032\037.sysml.RunDocumentQueryResponse\022M" + + "\n\016RenderDocument\022\034.sysml.RenderDocumentR" + + "equest\032\035.sysml.RenderDocumentResponseBJ\n" + + "\034org.openmbee.opensysml.protoP\001Z(github." + + "com/Open-MBEE/OpenSysML/api/protob\006proto" + + "3" }; descriptor = com.google.protobuf.Descriptors.FileDescriptor .internalBuildGeneratedFileFrom(descriptorData, @@ -1138,129 +1146,135 @@ public static void registerAllExtensions( internal_static_sysml_Value_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_sysml_Value_descriptor, - new java.lang.String[] { "IntValue", "RealValue", "BoolValue", "StringValue", "InstanceId", "Sequence", "Null", "Quantity", "EnumLiteral", "Unset", "Complex", "Array", "Vector", "VectorQuantity", "MeasurementRef", "Kind", }); - internal_static_sysml_Array_descriptor = + new java.lang.String[] { "IntValue", "RealValue", "BoolValue", "StringValue", "InstanceId", "Sequence", "Null", "Quantity", "EnumLiteral", "Unset", "Complex", "Array", "Vector", "VectorQuantity", "MeasurementRef", "Function", "Kind", }); + internal_static_sysml_Function_descriptor = getDescriptor().getMessageType(47); + internal_static_sysml_Function_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_sysml_Function_descriptor, + new java.lang.String[] { "CalcId", "SelfId", }); + internal_static_sysml_Array_descriptor = + getDescriptor().getMessageType(48); internal_static_sysml_Array_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_sysml_Array_descriptor, new java.lang.String[] { "Dimensions", "Elements", }); internal_static_sysml_Vector_descriptor = - getDescriptor().getMessageType(48); + getDescriptor().getMessageType(49); internal_static_sysml_Vector_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_sysml_Vector_descriptor, new java.lang.String[] { "Components", }); internal_static_sysml_VectorQuantity_descriptor = - getDescriptor().getMessageType(49); + getDescriptor().getMessageType(50); internal_static_sysml_VectorQuantity_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_sysml_VectorQuantity_descriptor, new java.lang.String[] { "Components", }); internal_static_sysml_Complex_descriptor = - getDescriptor().getMessageType(50); + getDescriptor().getMessageType(51); internal_static_sysml_Complex_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_sysml_Complex_descriptor, new java.lang.String[] { "Real", "Imaginary", }); internal_static_sysml_EnumLiteral_descriptor = - getDescriptor().getMessageType(51); + getDescriptor().getMessageType(52); internal_static_sysml_EnumLiteral_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_sysml_EnumLiteral_descriptor, new java.lang.String[] { "LiteralId", "EnumerationId", "Name", }); internal_static_sysml_ValueSequence_descriptor = - getDescriptor().getMessageType(52); + getDescriptor().getMessageType(53); internal_static_sysml_ValueSequence_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_sysml_ValueSequence_descriptor, new java.lang.String[] { "Elements", }); internal_static_sysml_Quantity_descriptor = - getDescriptor().getMessageType(53); + getDescriptor().getMessageType(54); internal_static_sysml_Quantity_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_sysml_Quantity_descriptor, new java.lang.String[] { "IntMagnitude", "RealMagnitude", "Unit", "UnitTerm", "Magnitude", }); internal_static_sysml_MeasurementRef_descriptor = - getDescriptor().getMessageType(54); + getDescriptor().getMessageType(55); internal_static_sysml_MeasurementRef_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_sysml_MeasurementRef_descriptor, new java.lang.String[] { "Unit", "UnitTerm", "UnitId", }); internal_static_sysml_UnitTerm_descriptor = - getDescriptor().getMessageType(55); + getDescriptor().getMessageType(56); internal_static_sysml_UnitTerm_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_sysml_UnitTerm_descriptor, new java.lang.String[] { "ScaleNum", "ScaleDen", "Factors", }); internal_static_sysml_UnitFactor_descriptor = - getDescriptor().getMessageType(56); + getDescriptor().getMessageType(57); internal_static_sysml_UnitFactor_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_sysml_UnitFactor_descriptor, new java.lang.String[] { "UnitId", "Exponent", }); internal_static_sysml_Diagnostic_descriptor = - getDescriptor().getMessageType(57); + getDescriptor().getMessageType(58); internal_static_sysml_Diagnostic_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_sysml_Diagnostic_descriptor, new java.lang.String[] { "Severity", "Message", "Span", }); internal_static_sysml_Span_descriptor = - getDescriptor().getMessageType(58); + getDescriptor().getMessageType(59); internal_static_sysml_Span_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_sysml_Span_descriptor, new java.lang.String[] { "File", "StartLine", "StartCol", "EndLine", "EndCol", }); internal_static_sysml_ServerInfoRequest_descriptor = - getDescriptor().getMessageType(59); + getDescriptor().getMessageType(60); internal_static_sysml_ServerInfoRequest_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_sysml_ServerInfoRequest_descriptor, new java.lang.String[] { }); internal_static_sysml_ServerInfoResponse_descriptor = - getDescriptor().getMessageType(60); + getDescriptor().getMessageType(61); internal_static_sysml_ServerInfoResponse_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_sysml_ServerInfoResponse_descriptor, new java.lang.String[] { "Version", "Capabilities", }); internal_static_sysml_QueryRequest_descriptor = - getDescriptor().getMessageType(61); + getDescriptor().getMessageType(62); internal_static_sysml_QueryRequest_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_sysml_QueryRequest_descriptor, new java.lang.String[] { "ModelHash", "Query", "OslcQuery", }); internal_static_sysml_QueryResponse_descriptor = - getDescriptor().getMessageType(62); + getDescriptor().getMessageType(63); internal_static_sysml_QueryResponse_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_sysml_QueryResponse_descriptor, new java.lang.String[] { "Elements", }); internal_static_sysml_Query_descriptor = - getDescriptor().getMessageType(63); + getDescriptor().getMessageType(64); internal_static_sysml_Query_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_sysml_Query_descriptor, new java.lang.String[] { "Scope", "Select", "Where", }); internal_static_sysml_Constraint_descriptor = - getDescriptor().getMessageType(64); + getDescriptor().getMessageType(65); internal_static_sysml_Constraint_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_sysml_Constraint_descriptor, new java.lang.String[] { "Primitive", "Composite", "Constraint", }); internal_static_sysml_PrimitiveConstraint_descriptor = - getDescriptor().getMessageType(65); + getDescriptor().getMessageType(66); internal_static_sysml_PrimitiveConstraint_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_sysml_PrimitiveConstraint_descriptor, new java.lang.String[] { "Inverse", "Property", "Operator", "Value", }); internal_static_sysml_CompositeConstraint_descriptor = - getDescriptor().getMessageType(66); + getDescriptor().getMessageType(67); internal_static_sysml_CompositeConstraint_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_sysml_CompositeConstraint_descriptor, new java.lang.String[] { "Operator", "Constraint", }); internal_static_sysml_QueryResultElement_descriptor = - getDescriptor().getMessageType(67); + getDescriptor().getMessageType(68); internal_static_sysml_QueryResultElement_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_sysml_QueryResultElement_descriptor, @@ -1272,55 +1286,55 @@ public static void registerAllExtensions( internal_static_sysml_QueryResultElement_PropertiesEntry_descriptor, new java.lang.String[] { "Key", "Value", }); internal_static_sysml_RunDocumentQueryRequest_descriptor = - getDescriptor().getMessageType(68); + getDescriptor().getMessageType(69); internal_static_sysml_RunDocumentQueryRequest_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_sysml_RunDocumentQueryRequest_descriptor, new java.lang.String[] { "ModelHash", "QueryId", "Bindings", }); internal_static_sysml_DocumentQueryBinding_descriptor = - getDescriptor().getMessageType(69); + getDescriptor().getMessageType(70); internal_static_sysml_DocumentQueryBinding_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_sysml_DocumentQueryBinding_descriptor, new java.lang.String[] { "Parameter", "Values", }); internal_static_sysml_DocumentValue_descriptor = - getDescriptor().getMessageType(70); + getDescriptor().getMessageType(71); internal_static_sysml_DocumentValue_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_sysml_DocumentValue_descriptor, new java.lang.String[] { "ElementId", "StringValue", "IntValue", "RealValue", "BoolValue", "Infinity", "Quantity", "ElementType", "Kind", }); internal_static_sysml_DocumentQueryColumn_descriptor = - getDescriptor().getMessageType(71); + getDescriptor().getMessageType(72); internal_static_sysml_DocumentQueryColumn_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_sysml_DocumentQueryColumn_descriptor, new java.lang.String[] { "Name", }); internal_static_sysml_DocumentQueryCell_descriptor = - getDescriptor().getMessageType(72); + getDescriptor().getMessageType(73); internal_static_sysml_DocumentQueryCell_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_sysml_DocumentQueryCell_descriptor, new java.lang.String[] { "Values", }); internal_static_sysml_DocumentQueryRow_descriptor = - getDescriptor().getMessageType(73); + getDescriptor().getMessageType(74); internal_static_sysml_DocumentQueryRow_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_sysml_DocumentQueryRow_descriptor, new java.lang.String[] { "Element", "Cells", }); internal_static_sysml_RunDocumentQueryResponse_descriptor = - getDescriptor().getMessageType(74); + getDescriptor().getMessageType(75); internal_static_sysml_RunDocumentQueryResponse_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_sysml_RunDocumentQueryResponse_descriptor, new java.lang.String[] { "Columns", "Rows", }); internal_static_sysml_RenderDocumentRequest_descriptor = - getDescriptor().getMessageType(75); + getDescriptor().getMessageType(76); internal_static_sysml_RenderDocumentRequest_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_sysml_RenderDocumentRequest_descriptor, new java.lang.String[] { "ModelHash", "DocumentId", }); internal_static_sysml_RenderDocumentResponse_descriptor = - getDescriptor().getMessageType(76); + getDescriptor().getMessageType(77); internal_static_sysml_RenderDocumentResponse_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_sysml_RenderDocumentResponse_descriptor, diff --git a/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/proto/Value.java b/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/proto/Value.java index 1aee10580..142744812 100644 --- a/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/proto/Value.java +++ b/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/proto/Value.java @@ -68,6 +68,7 @@ public enum KindCase VECTOR(13), VECTOR_QUANTITY(14), MEASUREMENT_REF(15), + FUNCTION(16), KIND_NOT_SET(0); private final int value; private KindCase(int value) { @@ -100,6 +101,7 @@ public static KindCase forNumber(int value) { case 13: return VECTOR; case 14: return VECTOR_QUANTITY; case 15: return MEASUREMENT_REF; + case 16: return FUNCTION; case 0: return KIND_NOT_SET; default: return null; } @@ -674,6 +676,49 @@ public org.openmbee.opensysml.proto.MeasurementRefOrBuilder getMeasurementRefOrB return org.openmbee.opensysml.proto.MeasurementRef.getDefaultInstance(); } + public static final int FUNCTION_FIELD_NUMBER = 16; + /** + *
+   * a calc as a value, named by its declaration
+   * 
+ * + * .sysml.Function function = 16 [json_name = "function"]; + * @return Whether the function field is set. + */ + @java.lang.Override + public boolean hasFunction() { + return kindCase_ == 16; + } + /** + *
+   * a calc as a value, named by its declaration
+   * 
+ * + * .sysml.Function function = 16 [json_name = "function"]; + * @return The function. + */ + @java.lang.Override + public org.openmbee.opensysml.proto.Function getFunction() { + if (kindCase_ == 16) { + return (org.openmbee.opensysml.proto.Function) kind_; + } + return org.openmbee.opensysml.proto.Function.getDefaultInstance(); + } + /** + *
+   * a calc as a value, named by its declaration
+   * 
+ * + * .sysml.Function function = 16 [json_name = "function"]; + */ + @java.lang.Override + public org.openmbee.opensysml.proto.FunctionOrBuilder getFunctionOrBuilder() { + if (kindCase_ == 16) { + return (org.openmbee.opensysml.proto.Function) kind_; + } + return org.openmbee.opensysml.proto.Function.getDefaultInstance(); + } + private byte memoizedIsInitialized = -1; @java.lang.Override public final boolean isInitialized() { @@ -738,6 +783,9 @@ public void writeTo(com.google.protobuf.CodedOutputStream output) if (kindCase_ == 15) { output.writeMessage(15, (org.openmbee.opensysml.proto.MeasurementRef) kind_); } + if (kindCase_ == 16) { + output.writeMessage(16, (org.openmbee.opensysml.proto.Function) kind_); + } getUnknownFields().writeTo(output); } @@ -810,6 +858,10 @@ public int getSerializedSize() { size += com.google.protobuf.CodedOutputStream .computeMessageSize(15, (org.openmbee.opensysml.proto.MeasurementRef) kind_); } + if (kindCase_ == 16) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(16, (org.openmbee.opensysml.proto.Function) kind_); + } size += getUnknownFields().getSerializedSize(); memoizedSize = size; return size; @@ -888,6 +940,10 @@ public boolean equals(final java.lang.Object obj) { if (!getMeasurementRef() .equals(other.getMeasurementRef())) return false; break; + case 16: + if (!getFunction() + .equals(other.getFunction())) return false; + break; case 0: default: } @@ -968,6 +1024,10 @@ public int hashCode() { hash = (37 * hash) + MEASUREMENT_REF_FIELD_NUMBER; hash = (53 * hash) + getMeasurementRef().hashCode(); break; + case 16: + hash = (37 * hash) + FUNCTION_FIELD_NUMBER; + hash = (53 * hash) + getFunction().hashCode(); + break; case 0: default: } @@ -1130,6 +1190,9 @@ public Builder clear() { if (measurementRefBuilder_ != null) { measurementRefBuilder_.clear(); } + if (functionBuilder_ != null) { + functionBuilder_.clear(); + } kindCase_ = 0; kind_ = null; return this; @@ -1203,6 +1266,10 @@ private void buildPartialOneofs(org.openmbee.opensysml.proto.Value result) { measurementRefBuilder_ != null) { result.kind_ = measurementRefBuilder_.build(); } + if (kindCase_ == 16 && + functionBuilder_ != null) { + result.kind_ = functionBuilder_.build(); + } } @java.lang.Override @@ -1282,6 +1349,10 @@ public Builder mergeFrom(org.openmbee.opensysml.proto.Value other) { mergeMeasurementRef(other.getMeasurementRef()); break; } + case FUNCTION: { + mergeFunction(other.getFunction()); + break; + } case KIND_NOT_SET: { break; } @@ -1405,6 +1476,13 @@ public Builder mergeFrom( kindCase_ = 15; break; } // case 122 + case 130: { + input.readMessage( + internalGetFunctionFieldBuilder().getBuilder(), + extensionRegistry); + kindCase_ = 16; + break; + } // case 130 default: { if (!super.parseUnknownField(input, extensionRegistry, tag)) { done = true; // was an endgroup tag @@ -3245,6 +3323,184 @@ public org.openmbee.opensysml.proto.MeasurementRefOrBuilder getMeasurementRefOrB return measurementRefBuilder_; } + private com.google.protobuf.SingleFieldBuilder< + org.openmbee.opensysml.proto.Function, org.openmbee.opensysml.proto.Function.Builder, org.openmbee.opensysml.proto.FunctionOrBuilder> functionBuilder_; + /** + *
+     * a calc as a value, named by its declaration
+     * 
+ * + * .sysml.Function function = 16 [json_name = "function"]; + * @return Whether the function field is set. + */ + @java.lang.Override + public boolean hasFunction() { + return kindCase_ == 16; + } + /** + *
+     * a calc as a value, named by its declaration
+     * 
+ * + * .sysml.Function function = 16 [json_name = "function"]; + * @return The function. + */ + @java.lang.Override + public org.openmbee.opensysml.proto.Function getFunction() { + if (functionBuilder_ == null) { + if (kindCase_ == 16) { + return (org.openmbee.opensysml.proto.Function) kind_; + } + return org.openmbee.opensysml.proto.Function.getDefaultInstance(); + } else { + if (kindCase_ == 16) { + return functionBuilder_.getMessage(); + } + return org.openmbee.opensysml.proto.Function.getDefaultInstance(); + } + } + /** + *
+     * a calc as a value, named by its declaration
+     * 
+ * + * .sysml.Function function = 16 [json_name = "function"]; + */ + public Builder setFunction(org.openmbee.opensysml.proto.Function value) { + if (functionBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + kind_ = value; + onChanged(); + } else { + functionBuilder_.setMessage(value); + } + kindCase_ = 16; + return this; + } + /** + *
+     * a calc as a value, named by its declaration
+     * 
+ * + * .sysml.Function function = 16 [json_name = "function"]; + */ + public Builder setFunction( + org.openmbee.opensysml.proto.Function.Builder builderForValue) { + if (functionBuilder_ == null) { + kind_ = builderForValue.build(); + onChanged(); + } else { + functionBuilder_.setMessage(builderForValue.build()); + } + kindCase_ = 16; + return this; + } + /** + *
+     * a calc as a value, named by its declaration
+     * 
+ * + * .sysml.Function function = 16 [json_name = "function"]; + */ + public Builder mergeFunction(org.openmbee.opensysml.proto.Function value) { + if (functionBuilder_ == null) { + if (kindCase_ == 16 && + kind_ != org.openmbee.opensysml.proto.Function.getDefaultInstance()) { + kind_ = org.openmbee.opensysml.proto.Function.newBuilder((org.openmbee.opensysml.proto.Function) kind_) + .mergeFrom(value).buildPartial(); + } else { + kind_ = value; + } + onChanged(); + } else { + if (kindCase_ == 16) { + functionBuilder_.mergeFrom(value); + } else { + functionBuilder_.setMessage(value); + } + } + kindCase_ = 16; + return this; + } + /** + *
+     * a calc as a value, named by its declaration
+     * 
+ * + * .sysml.Function function = 16 [json_name = "function"]; + */ + public Builder clearFunction() { + if (functionBuilder_ == null) { + if (kindCase_ == 16) { + kindCase_ = 0; + kind_ = null; + onChanged(); + } + } else { + if (kindCase_ == 16) { + kindCase_ = 0; + kind_ = null; + } + functionBuilder_.clear(); + } + return this; + } + /** + *
+     * a calc as a value, named by its declaration
+     * 
+ * + * .sysml.Function function = 16 [json_name = "function"]; + */ + public org.openmbee.opensysml.proto.Function.Builder getFunctionBuilder() { + return internalGetFunctionFieldBuilder().getBuilder(); + } + /** + *
+     * a calc as a value, named by its declaration
+     * 
+ * + * .sysml.Function function = 16 [json_name = "function"]; + */ + @java.lang.Override + public org.openmbee.opensysml.proto.FunctionOrBuilder getFunctionOrBuilder() { + if ((kindCase_ == 16) && (functionBuilder_ != null)) { + return functionBuilder_.getMessageOrBuilder(); + } else { + if (kindCase_ == 16) { + return (org.openmbee.opensysml.proto.Function) kind_; + } + return org.openmbee.opensysml.proto.Function.getDefaultInstance(); + } + } + /** + *
+     * a calc as a value, named by its declaration
+     * 
+ * + * .sysml.Function function = 16 [json_name = "function"]; + */ + private com.google.protobuf.SingleFieldBuilder< + org.openmbee.opensysml.proto.Function, org.openmbee.opensysml.proto.Function.Builder, org.openmbee.opensysml.proto.FunctionOrBuilder> + internalGetFunctionFieldBuilder() { + if (functionBuilder_ == null) { + if (!(kindCase_ == 16)) { + kind_ = org.openmbee.opensysml.proto.Function.getDefaultInstance(); + } + functionBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.openmbee.opensysml.proto.Function, org.openmbee.opensysml.proto.Function.Builder, org.openmbee.opensysml.proto.FunctionOrBuilder>( + (org.openmbee.opensysml.proto.Function) kind_, + getParentForChildren(), + isClean()); + kind_ = null; + } + kindCase_ = 16; + onChanged(); + return functionBuilder_; + } + // @@protoc_insertion_point(builder_scope:sysml.Value) } diff --git a/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/proto/ValueOrBuilder.java b/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/proto/ValueOrBuilder.java index 2f61dd66a..23f2aac65 100644 --- a/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/proto/ValueOrBuilder.java +++ b/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/proto/ValueOrBuilder.java @@ -321,5 +321,32 @@ public interface ValueOrBuilder extends */ org.openmbee.opensysml.proto.MeasurementRefOrBuilder getMeasurementRefOrBuilder(); + /** + *
+   * a calc as a value, named by its declaration
+   * 
+ * + * .sysml.Function function = 16 [json_name = "function"]; + * @return Whether the function field is set. + */ + boolean hasFunction(); + /** + *
+   * a calc as a value, named by its declaration
+   * 
+ * + * .sysml.Function function = 16 [json_name = "function"]; + * @return The function. + */ + org.openmbee.opensysml.proto.Function getFunction(); + /** + *
+   * a calc as a value, named by its declaration
+   * 
+ * + * .sysml.Function function = 16 [json_name = "function"]; + */ + org.openmbee.opensysml.proto.FunctionOrBuilder getFunctionOrBuilder(); + org.openmbee.opensysml.proto.Value.KindCase getKindCase(); } diff --git a/clients/java/opensysml-client/src/test/java/org/openmbee/opensysml/ApiIntegrationTest.java b/clients/java/opensysml-client/src/test/java/org/openmbee/opensysml/ApiIntegrationTest.java index 03d33bd80..c3d8ecaf5 100644 --- a/clients/java/opensysml-client/src/test/java/org/openmbee/opensysml/ApiIntegrationTest.java +++ b/clients/java/opensysml-client/src/test/java/org/openmbee/opensysml/ApiIntegrationTest.java @@ -229,6 +229,41 @@ void aBareMeasurementReferenceArrivesWithItsReductionAndDeclarationOverProtobufA } } + private static final String FUNCTIONS = + """ + package Demo { + private import ScalarValues::*; + calc def Sq { in v : Real; return : Real = v * v; } + calc def Fn { in calc f { in v : Real; return : Real; } in a : Real; return : Real = f(a); } + calc def Identity { in calc f { in v : Real; return : Real; } return r = f; } + attribute pick = Identity(Sq); + attribute nine = Fn(Sq, 3.0); + part def Scaler { + attribute k : Real = 2.0; + calc scale { in x : Real; return : Real = x * k; } + } + part holder : Scaler; + attribute scaler = holder.scale; + } + """; + + @Test + void aCalcHeldAsAValueArrivesAsTheFunctionItNamesOverProtobufAndJson() { + assertTrue(connection.capabilities().has(Capabilities.FUNCTION_VALUES)); + try (Connection json = + Connection.open(ServiceBinary.options().encoding(Encoding.JSON).build())) { + for (Connection each : List.of(connection, json)) { + Model model = each.parse(FUNCTIONS); + assertEquals( + new Value.FunctionValue("Demo::Sq", Optional.empty()), model.eval("Demo::pick")); + assertEquals(new Value.RealValue(9.0), model.eval("Demo::nine")); + Value.FunctionValue scale = (Value.FunctionValue) model.eval("Demo::scaler"); + assertEquals("Demo::Scaler::scale", scale.calcId()); + assertTrue(scale.selfId().orElseThrow() > 0); + } + } + } + @Test void aModelTheServiceDoesNotHoldIsRefused() { Model absent = connection.model("sha256:0000000000000000"); diff --git a/clients/java/opensysml-client/src/test/java/org/openmbee/opensysml/internal/ProtosTest.java b/clients/java/opensysml-client/src/test/java/org/openmbee/opensysml/internal/ProtosTest.java index 8c0744413..68076d20a 100644 --- a/clients/java/opensysml-client/src/test/java/org/openmbee/opensysml/internal/ProtosTest.java +++ b/clients/java/opensysml-client/src/test/java/org/openmbee/opensysml/internal/ProtosTest.java @@ -13,6 +13,7 @@ import org.openmbee.opensysml.proto.AttributeInfo; import org.openmbee.opensysml.proto.Complex; import org.openmbee.opensysml.proto.FeatureValue; +import org.openmbee.opensysml.proto.Function; import org.openmbee.opensysml.proto.MeasurementRef; import org.openmbee.opensysml.proto.SymbolInfo; import org.openmbee.opensysml.proto.UnitFactor; @@ -319,6 +320,39 @@ void aMeasurementReferenceReadsItsUnitItsReductionAndTheDeclarationItNames() { assertThrows(TransportException.class, () -> Protos.value(nested)); } + private static org.openmbee.opensysml.proto.Value function(String calcId, long selfId) { + return org.openmbee.opensysml.proto.Value.newBuilder() + .setFunction(Function.newBuilder().setCalcId(calcId).setSelfId(selfId)) + .build(); + } + + @Test + void aFunctionIsTheCalcItNamesReadAgainstAnObjectOrNone() { + assertEquals( + Optional.of(new Value.FunctionValue("Demo::Sq", Optional.empty())), + Protos.value(function("Demo::Sq", 0))); + assertEquals( + Optional.of(new Value.FunctionValue("Demo::Scaler::scale", Optional.of(7L))), + Protos.value(function("Demo::Scaler::scale", 7))); + // The object is part of the identity: another object's read is another value. + assertNotEquals( + Protos.value(function("Demo::Scaler::scale", 7)), + Protos.value(function("Demo::Scaler::scale", 8))); + + // Naming no calc is malformed at any depth, on the wire and in the record. + org.openmbee.opensysml.proto.Value noCalc = function("", 0); + TransportException nothing = + assertThrows(TransportException.class, () -> Protos.value(noCalc)); + assertTrue(nothing.getMessage().contains("names no calc"), nothing.getMessage()); + org.openmbee.opensysml.proto.Value nested = + org.openmbee.opensysml.proto.Value.newBuilder() + .setSequence(ValueSequence.newBuilder().addElements(function("", 3))) + .build(); + assertThrows(TransportException.class, () -> Protos.value(nested)); + assertThrows( + IllegalArgumentException.class, () -> new Value.FunctionValue("", Optional.empty())); + } + @Test void aQuantityWithoutAMagnitudeIsRefusedRatherThanReadAsZero() { org.openmbee.opensysml.proto.Quantity noMagnitude = diff --git a/clients/java/opensysml-conformance/src/main/java/org/openmbee/opensysml/conformance/Normalizer.java b/clients/java/opensysml-conformance/src/main/java/org/openmbee/opensysml/conformance/Normalizer.java index 2e5f1600b..18331c3b1 100644 --- a/clients/java/opensysml-conformance/src/main/java/org/openmbee/opensysml/conformance/Normalizer.java +++ b/clients/java/opensysml-conformance/src/main/java/org/openmbee/opensysml/conformance/Normalizer.java @@ -27,7 +27,11 @@ final class Normalizer { /** The int64 fields carrying a runtime instance id, which is assigned per call. */ private static final Set NORMALIZED_IDS = - Set.of("sysml.Instance.id", "sysml.Value.instance_id", "sysml.Verdict.instance_id"); + Set.of( + "sysml.Instance.id", + "sysml.Value.instance_id", + "sysml.Verdict.instance_id", + "sysml.Function.self_id"); private final String modelHash; private final Map labels = new HashMap<>(); diff --git a/clients/java/opensysml-conformance/src/main/java/org/openmbee/opensysml/conformance/Rendering.java b/clients/java/opensysml-conformance/src/main/java/org/openmbee/opensysml/conformance/Rendering.java index ca155cd16..85659444b 100644 --- a/clients/java/opensysml-conformance/src/main/java/org/openmbee/opensysml/conformance/Rendering.java +++ b/clients/java/opensysml-conformance/src/main/java/org/openmbee/opensysml/conformance/Rendering.java @@ -85,6 +85,11 @@ static org.openmbee.opensysml.proto.Value value(Value value) { .setUnitTerm(unitTerm(ref.reduction())); ref.unitId().ifPresent(reference::setUnitId); builder.setMeasurementRef(reference); + } else if (value instanceof Value.FunctionValue function) { + builder.setFunction( + org.openmbee.opensysml.proto.Function.newBuilder() + .setCalcId(function.calcId()) + .setSelfId(function.selfId().orElse(0L))); } else { throw new IllegalStateException("no rendering for " + value.getClass()); } diff --git a/clients/java/opensysml-conformance/src/test/java/org/openmbee/opensysml/conformance/NormalizerTest.java b/clients/java/opensysml-conformance/src/test/java/org/openmbee/opensysml/conformance/NormalizerTest.java index 2269e59f4..8b1c3d1aa 100644 --- a/clients/java/opensysml-conformance/src/test/java/org/openmbee/opensysml/conformance/NormalizerTest.java +++ b/clients/java/opensysml-conformance/src/test/java/org/openmbee/opensysml/conformance/NormalizerTest.java @@ -5,6 +5,7 @@ import org.openmbee.opensysml.proto.Diagnostic; import org.openmbee.opensysml.proto.EvaluateResponse; import org.openmbee.opensysml.proto.FeatureValue; +import org.openmbee.opensysml.proto.Function; import org.openmbee.opensysml.proto.Instance; import org.openmbee.opensysml.proto.InstantiateResponse; import org.openmbee.opensysml.proto.ParseFileResponse; @@ -75,6 +76,15 @@ void instanceIdsAreLabelledInOrderOfFirstAppearance() { FeatureValue.newBuilder() .setFeatureName("part") .setValue(Value.newBuilder().setInstanceId(7)) + .build()) + .putFeatureValues( + "scale", + FeatureValue.newBuilder() + .setFeatureName("scale") + .setValue( + Value.newBuilder() + .setFunction( + Function.newBuilder().setCalcId("T::scale").setSelfId(41))) .build())) .addInstances(Instance.newBuilder().setId(41)) .addInstances(Instance.newBuilder().setId(7)) @@ -86,6 +96,9 @@ void instanceIdsAreLabelledInOrderOfFirstAppearance() { Map features = (Map) root.get("feature_values"); Map value = (Map) ((Map) features.get("part")).get("value"); assertEquals("@2", value.get("instance_id")); + Map function = + (Map) ((Map) ((Map) features.get("scale")).get("value")).get("function"); + assertEquals("@1", function.get("self_id")); List instances = (List) normalized.get("instances"); assertEquals("@1", ((Map) instances.get(0)).get("id")); diff --git a/clients/node/README.md b/clients/node/README.md index e49a6dbad..d500b1af2 100644 --- a/clients/node/README.md +++ b/clients/node/README.md @@ -57,6 +57,7 @@ switch (value.kind) { case "string": value.value; case "quantity": value.magnitude; value.unit; // 1500.0 [kg] case "measurementRef": value.unit; value.unitTerm; value.unitId; // a bare unit: km, reduced to 1000·metre + case "function": value.calcId; value.selfId; // a calc as a value: Demo::Sq, or holder.scale read off an object case "array": value.dimensions; value.elements; // row-major, an element is any SysMLValue case "vector": value.components; // { kind: "int" | "real" }[] case "vectorQuantity": value.components; // QuantityValue[], a unit per component @@ -185,9 +186,11 @@ The client checks the advertised list **before** making such a call so it can raise a `MissingCapabilityError` naming the service, its version and the way to get one that has it. A direct capability-gated request to a service without the capability is refused with `UNIMPLEMENTED`; response-population capabilities -instead omit the fields they name. A service without `structured_values` or -`measurement_refs` sends the value kinds those name (`array`, `vector`, -`vectorQuantity`; `measurementRef`) as `null` with an `unsupported: …` reason. +instead omit the fields they name. A service without `structured_values`, +`measurement_refs` or `function_values` sends the value kinds those name (`array`, +`vector`, `vectorQuantity`; `measurementRef`; `function`) as `null` with an +`unsupported: …` reason. A function closing over the bindings of a behavior body +has no wire form and is sent as `null` by every service. ## Failures are typed diff --git a/clients/node/src/core/capabilities.ts b/clients/node/src/core/capabilities.ts index 6d66dc2aa..427da334c 100644 --- a/clients/node/src/core/capabilities.ts +++ b/clients/node/src/core/capabilities.ts @@ -22,6 +22,8 @@ export const CAPABILITY_COMPLEX_VALUES = "complex_values"; export const CAPABILITY_STRUCTURED_VALUES = "structured_values"; /** A bare measurement unit (`SI::m`, `m / s`) as `Value.measurement_ref`, rather than an unsupported null. */ export const CAPABILITY_MEASUREMENT_REFS = "measurement_refs"; +/** A calc held as a value as `Value.function`, named by its declaration, rather than an unsupported null. */ +export const CAPABILITY_FUNCTION_VALUES = "function_values"; /** `ParseFileRequest.language`, which declares the language of inline content. */ export const CAPABILITY_INLINE_LANGUAGE = "inline_language"; /** `ParseFileRequest.strict_conformance`. */ diff --git a/clients/node/src/core/index.ts b/clients/node/src/core/index.ts index 656cf6b33..1ae38c031 100644 --- a/clients/node/src/core/index.ts +++ b/clients/node/src/core/index.ts @@ -28,6 +28,7 @@ export { CAPABILITY_ENUM_VALUES, CAPABILITY_EVALUATE_SUBJECT, CAPABILITY_FEATURE_VALUES, + CAPABILITY_FUNCTION_VALUES, CAPABILITY_INLINE_LANGUAGE, CAPABILITY_MEASUREMENT_REFS, CAPABILITY_QUERY, @@ -70,6 +71,7 @@ export type { ArrayValue, ComplexValue, EnumValue, + FunctionValue, Magnitude, MeasurementRefValue, QuantityValue, diff --git a/clients/node/src/core/values.ts b/clients/node/src/core/values.ts index 1c748eb28..ae0ab459a 100644 --- a/clients/node/src/core/values.ts +++ b/clients/node/src/core/values.ts @@ -5,6 +5,7 @@ import { create } from "@bufbuild/protobuf"; import type { Array as ArrayMessage, EnumLiteral, + Function as FunctionMessage, MeasurementRef, Quantity, UnitTerm, @@ -18,6 +19,7 @@ import { ComplexSchema, EnumLiteralSchema, FailureReason, + FunctionSchema, MeasurementRefSchema, QuantitySchema, UnitFactorSchema, @@ -77,6 +79,17 @@ export interface MeasurementRefValue { unitId?: string; } +/** + * A calc held as a value — a calc definition, or a calc usage with an input no + * read could supply — named by the FQN of its declaration, which is its identity. + * `selfId` is the object its feature names resolve against, for a calc usage + * read off a part (`holder.scale`); absent for a function closing over no object. + */ +export interface FunctionValue { + calcId: string; + selfId?: bigint; +} + /** * A multidimensional array: `dimensions` gives the extent of each dimension and * `elements` the elements flattened row-major, the last dimension varying @@ -103,6 +116,7 @@ export type SysMLValue = | { kind: "sequence"; elements: SysMLValue[] } | ({ kind: "quantity" } & QuantityValue) | ({ kind: "measurementRef" } & MeasurementRefValue) + | ({ kind: "function" } & FunctionValue) | { kind: "enum"; value: EnumValue } | ({ kind: "array" } & ArrayValue) | { kind: "vector"; components: Magnitude[] } @@ -166,6 +180,8 @@ export function decodeValue(value: Value | undefined): SysMLValue { return { kind: "quantity", ...decodeQuantity(kind.value) }; case "measurementRef": return { kind: "measurementRef", ...decodeMeasurementRef(kind.value) }; + case "function": + return { kind: "function", ...decodeFunction(kind.value) }; case "enumLiteral": return { kind: "enum", value: decodeEnumLiteral(kind.value) }; case "array": @@ -219,6 +235,8 @@ export function encodeValue(value: SysMLValue): Value { return create(ValueSchema, { kind: { case: "measurementRef", value: encodeMeasurementRef(value) }, }); + case "function": + return create(ValueSchema, { kind: { case: "function", value: encodeFunction(value) } }); case "enum": return create(ValueSchema, { kind: { case: "enumLiteral", value: create(EnumLiteralSchema, value.value) }, @@ -319,6 +337,8 @@ export function formatValue(value: SysMLValue): string { } case "measurementRef": return value.unit === "" ? formatUnitTerm(value.unitTerm) : value.unit; + case "function": + return value.calcId; case "enum": return value.value.name; case "array": @@ -405,6 +425,20 @@ function encodeMeasurementRef(ref: MeasurementRefValue): MeasurementRef { }); } +function decodeFunction(fn: FunctionMessage): FunctionValue { + if (fn.calcId === "") { + throw new MalformedValueError("a function names no calc"); + } + return { calcId: fn.calcId, ...(fn.selfId === 0n ? {} : { selfId: fn.selfId }) }; +} + +function encodeFunction(fn: FunctionValue): FunctionMessage { + if (fn.calcId === "") { + throw new MalformedValueError("a function names no calc"); + } + return create(FunctionSchema, { calcId: fn.calcId, selfId: fn.selfId ?? 0n }); +} + function encodeUnitTerm(term: UnitFactorization): UnitTerm { return create(UnitTermSchema, { scaleNum: term.scaleNum, diff --git a/clients/node/src/generated/sysml_pb.ts b/clients/node/src/generated/sysml_pb.ts index 3d896409a..aee587205 100644 --- a/clients/node/src/generated/sysml_pb.ts +++ b/clients/node/src/generated/sysml_pb.ts @@ -10,7 +10,7 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file sysml.proto. */ export const file_sysml: GenFile = /*@__PURE__*/ - fileDesc("CgtzeXNtbC5wcm90bxIFc3lzbWwiygEKB1ZlcmRpY3QSDAoEa2luZBgBIAEoCRISCgplbGVtZW50X2lkGAIgASgJEg8KB2VsZW1lbnQYAyABKAkSDQoFaG9sZHMYBCABKAgSEQoJY29uZGl0aW9uGAUgASgJEhMKC2luc3RhbmNlX2lkGAYgASgDEhgKEGluc3RhbmNlX3R5cGVfaWQYByABKAkSDQoFZXJyb3IYCCABKAkSLAoOZmFpbHVyZV9yZWFzb24YCSABKA4yFC5zeXNtbC5GYWlsdXJlUmVhc29uIlsKF1ZlcmlmeUNvbnN0cmFpbnRSZXF1ZXN0EhIKCm1vZGVsX2hhc2gYASABKAkSEQoJc3ltYm9sX2lkGAIgASgJEhkKEXN1YmplY3Rfc3ltYm9sX2lkGAMgASgJIpYBChhWZXJpZnlDb25zdHJhaW50UmVzcG9uc2USHwoHdmVyZGljdBgBIAEoCzIOLnN5c21sLlZlcmRpY3QSIgoJaW5zdGFuY2VzGAIgAygLMg8uc3lzbWwuSW5zdGFuY2USDQoFZXJyb3IYAyABKAkSJgoLZGlhZ25vc3RpY3MYBCADKAsyES5zeXNtbC5EaWFnbm9zdGljIlwKGFZlcmlmeVJlcXVpcmVtZW50UmVxdWVzdBISCgptb2RlbF9oYXNoGAEgASgJEhEKCXN5bWJvbF9pZBgCIAEoCRIZChFzdWJqZWN0X3N5bWJvbF9pZBgDIAEoCSKXAQoZVmVyaWZ5UmVxdWlyZW1lbnRSZXNwb25zZRIfCgd2ZXJkaWN0GAEgASgLMg4uc3lzbWwuVmVyZGljdBIiCglpbnN0YW5jZXMYAiADKAsyDy5zeXNtbC5JbnN0YW5jZRINCgVlcnJvchgDIAEoCRImCgtkaWFnbm9zdGljcxgEIAMoCzIRLnN5c21sLkRpYWdub3N0aWMiQgoZVmVyaWZ5U2F0aXNmYWN0aW9uUmVxdWVzdBISCgptb2RlbF9oYXNoGAEgASgJEhEKCXN5bWJvbF9pZBgCIAEoCSLHAQoaVmVyaWZ5U2F0aXNmYWN0aW9uUmVzcG9uc2USIAoIdmVyZGljdHMYASADKAsyDi5zeXNtbC5WZXJkaWN0EiIKCWluc3RhbmNlcxgCIAMoCzIPLnN5c21sLkluc3RhbmNlEg0KBWVycm9yGAMgASgJEiYKC2RpYWdub3N0aWNzGAQgAygLMhEuc3lzbWwuRGlhZ25vc3RpYxIsCg5mYWlsdXJlX3JlYXNvbhgFIAEoDjIULnN5c21sLkZhaWx1cmVSZWFzb24iXQoTRXZhbHVhdGVDYWxjUmVxdWVzdBISCgptb2RlbF9oYXNoGAEgASgJEhEKCXN5bWJvbF9pZBgCIAEoCRIfCglhcmd1bWVudHMYAyADKAsyDC5zeXNtbC5WYWx1ZSK9AQoURXZhbHVhdGVDYWxjUmVzcG9uc2USHAoGcmVzdWx0GAEgASgLMgwuc3lzbWwuVmFsdWUSIgoHb3V0cHV0cxgCIAMoCzIRLnN5c21sLkNhbGNPdXRwdXQSDQoFZXJyb3IYAyABKAkSJgoLZGlhZ25vc3RpY3MYBCADKAsyES5zeXNtbC5EaWFnbm9zdGljEiwKDmZhaWx1cmVfcmVhc29uGAUgASgOMhQuc3lzbWwuRmFpbHVyZVJlYXNvbiI3CgpDYWxjT3V0cHV0EgwKBG5hbWUYASABKAkSGwoFdmFsdWUYAiABKAsyDC5zeXNtbC5WYWx1ZSKEAgoSUnVuQW5hbHlzaXNSZXF1ZXN0EhIKCm1vZGVsX2hhc2gYASABKAkSEQoJc3ltYm9sX2lkGAIgASgJEhkKEXN1YmplY3Rfc3ltYm9sX2lkGAMgASgJEh8KCWFyZ3VtZW50cxgEIAMoCzIMLnN5c21sLlZhbHVlEkYKD25hbWVkX2FyZ3VtZW50cxgFIAMoCzItLnN5c21sLlJ1bkFuYWx5c2lzUmVxdWVzdC5OYW1lZEFyZ3VtZW50c0VudHJ5GkMKE05hbWVkQXJndW1lbnRzRW50cnkSCwoDa2V5GAEgASgJEhsKBXZhbHVlGAIgASgLMgwuc3lzbWwuVmFsdWU6AjgBIuQBChNSdW5BbmFseXNpc1Jlc3BvbnNlEiIKB291dHB1dHMYASADKAsyES5zeXNtbC5DYWxjT3V0cHV0EiAKCHZlcmRpY3RzGAIgAygLMg4uc3lzbWwuVmVyZGljdBIiCglpbnN0YW5jZXMYAyADKAsyDy5zeXNtbC5JbnN0YW5jZRINCgVlcnJvchgEIAEoCRImCgtkaWFnbm9zdGljcxgFIAMoCzIRLnN5c21sLkRpYWdub3N0aWMSLAoOZmFpbHVyZV9yZWFzb24YBiABKA4yFC5zeXNtbC5GYWlsdXJlUmVhc29uIowBChBQYXJzZUZpbGVSZXF1ZXN0EhMKCWZpbGVfcGF0aBgBIAEoCUgAEhEKB2NvbnRlbnQYAiABKAlIABIYCgxjb250ZW50X2hhc2gYAyABKAlCAhgBEhAKCGxhbmd1YWdlGAQgASgJEhoKEnN0cmljdF9jb25mb3JtYW5jZRgFIAEoCEIICgZzb3VyY2UiYgoOU291cmNlRG9jdW1lbnQSEwoJZmlsZV9wYXRoGAEgASgJSAASEQoHY29udGVudBgCIAEoCUgAEhAKCGxhbmd1YWdlGAMgASgJEgwKBG5hbWUYBCABKAlCCAoGc291cmNlIlsKE1BhcnNlU291cmNlc1JlcXVlc3QSKAoJZG9jdW1lbnRzGAEgAygLMhUuc3lzbWwuU291cmNlRG9jdW1lbnQSGgoSc3RyaWN0X2NvbmZvcm1hbmNlGAIgASgIIoMBChRQYXJzZVNvdXJjZXNSZXNwb25zZRISCgptb2RlbF9oYXNoGAEgASgJEiAKBXJvb3RzGAIgAygLMhEuc3lzbWwuU3ltYm9sSW5mbxImCgtkaWFnbm9zdGljcxgDIAMoCzIRLnN5c21sLkRpYWdub3N0aWMSDQoFZXJyb3IYBCABKAkifwoRUGFyc2VGaWxlUmVzcG9uc2USEgoKbW9kZWxfaGFzaBgBIAEoCRIfCgRyb290GAIgASgLMhEuc3lzbWwuU3ltYm9sSW5mbxImCgtkaWFnbm9zdGljcxgDIAMoCzIRLnN5c21sLkRpYWdub3N0aWMSDQoFZXJyb3IYBCABKAkiOQoQR2V0U3ltYm9sUmVxdWVzdBISCgptb2RlbF9oYXNoGAEgASgJEhEKCXN5bWJvbF9pZBgCIAEoCSJCCg5TeW1ib2xSZXNwb25zZRIhCgZzeW1ib2wYASABKAsyES5zeXNtbC5TeW1ib2xJbmZvEg0KBWVycm9yGAIgASgJIigKEkRpYWdub3N0aWNzUmVxdWVzdBISCgptb2RlbF9oYXNoGAEgASgJIkwKE0RpYWdub3N0aWNzUmVzcG9uc2USJgoLZGlhZ25vc3RpY3MYASADKAsyES5zeXNtbC5EaWFnbm9zdGljEg0KBWVycm9yGAIgASgJIm8KD0V2YWx1YXRlUmVxdWVzdBISCgptb2RlbF9oYXNoGAEgASgJEhIKCmV4cHJlc3Npb24YAiABKAkSGQoRY29udGV4dF9zeW1ib2xfaWQYAyABKAkSGQoRc3ViamVjdF9zeW1ib2xfaWQYBCABKAkiZwoQRXZhbHVhdGVSZXNwb25zZRIcCgZyZXN1bHQYASABKAsyDC5zeXNtbC5WYWx1ZRINCgVlcnJvchgCIAEoCRImCgtkaWFnbm9zdGljcxgDIAMoCzIRLnN5c21sLkRpYWdub3N0aWMiwgEKCEluc3RhbmNlEgoKAmlkGAEgASgDEhYKDnR5cGVfc3ltYm9sX2lkGAIgASgJEjoKDmZlYXR1cmVfdmFsdWVzGAQgAygLMiIuc3lzbWwuSW5zdGFuY2UuRmVhdHVyZVZhbHVlc0VudHJ5GkkKEkZlYXR1cmVWYWx1ZXNFbnRyeRILCgNrZXkYASABKAkSIgoFdmFsdWUYAiABKAsyEy5zeXNtbC5GZWF0dXJlVmFsdWU6AjgBSgQIAxAEUgVzbG90cyKEAQoMRmVhdHVyZVZhbHVlEhQKDGZlYXR1cmVfbmFtZRgBIAEoCRIbCgV2YWx1ZRgCIAEoCzIMLnN5c21sLlZhbHVlEhwKBnZhbHVlcxgDIAMoCzIMLnN5c21sLlZhbHVlEhQKDG1hdGVyaWFsaXplZBgEIAEoCBINCgVlcnJvchgFIAEoCSI7ChJJbnN0YW50aWF0ZVJlcXVlc3QSEgoKbW9kZWxfaGFzaBgBIAEoCRIRCglzeW1ib2xfaWQYAiABKAkikwEKE0luc3RhbnRpYXRlUmVzcG9uc2USIQoIaW5zdGFuY2UYASABKAsyDy5zeXNtbC5JbnN0YW5jZRINCgVlcnJvchgCIAEoCRImCgtkaWFnbm9zdGljcxgDIAMoCzIRLnN5c21sLkRpYWdub3N0aWMSIgoJaW5zdGFuY2VzGAQgAygLMg8uc3lzbWwuSW5zdGFuY2UiugEKFEV4ZWN1dGVBY3Rpb25SZXF1ZXN0EhIKCm1vZGVsX2hhc2gYASABKAkSGAoQYWN0aW9uX3N5bWJvbF9pZBgCIAEoCRI3CgZpbnB1dHMYAyADKAsyJy5zeXNtbC5FeGVjdXRlQWN0aW9uUmVxdWVzdC5JbnB1dHNFbnRyeRo7CgtJbnB1dHNFbnRyeRILCgNrZXkYASABKAkSGwoFdmFsdWUYAiABKAsyDC5zeXNtbC5WYWx1ZToCOAEiyAEKFUV4ZWN1dGVBY3Rpb25SZXNwb25zZRI6CgdvdXRwdXRzGAEgAygLMikuc3lzbWwuRXhlY3V0ZUFjdGlvblJlc3BvbnNlLk91dHB1dHNFbnRyeRINCgVlcnJvchgCIAEoCRImCgtkaWFnbm9zdGljcxgDIAMoCzIRLnN5c21sLkRpYWdub3N0aWMaPAoMT3V0cHV0c0VudHJ5EgsKA2tleRgBIAEoCRIbCgV2YWx1ZRgCIAEoCzIMLnN5c21sLlZhbHVlOgI4ASJaChNFeGVjdXRlU3RhdGVSZXF1ZXN0EhIKCm1vZGVsX2hhc2gYASABKAkSHwoXc3RhdGVfbWFjaGluZV9zeW1ib2xfaWQYAiABKAkSDgoGZXZlbnRzGAMgAygJIu4BChRFeGVjdXRlU3RhdGVSZXNwb25zZRIWCg5zdGF0ZXNfdmlzaXRlZBgBIAMoCRJECg1maW5hbF9jb250ZXh0GAIgAygLMi0uc3lzbWwuRXhlY3V0ZVN0YXRlUmVzcG9uc2UuRmluYWxDb250ZXh0RW50cnkSDQoFZXJyb3IYAyABKAkSJgoLZGlhZ25vc3RpY3MYBCADKAsyES5zeXNtbC5EaWFnbm9zdGljGkEKEUZpbmFsQ29udGV4dEVudHJ5EgsKA2tleRgBIAEoCRIbCgV2YWx1ZRgCIAEoCzIMLnN5c21sLlZhbHVlOgI4ASKgAQoOQ29udmVydFJlcXVlc3QSEwoJZmlsZV9wYXRoGAEgASgJSAASEQoHY29udGVudBgCIAEoCUgAEhQKCm1vZGVsX2hhc2gYBiABKAlIABITCgtmcm9tX2Zvcm1hdBgDIAEoCRIRCgl0b19mb3JtYXQYBCABKAkSHgoWdG9sZXJhdGVfc3ludGF4X2Vycm9ycxgFIAEoCEIICgZzb3VyY2UitAEKD0NvbnZlcnRSZXNwb25zZRIPCgdjb250ZW50GAEgASgJEhMKC2Zyb21fZm9ybWF0GAIgASgJEhEKCXRvX2Zvcm1hdBgDIAEoCRINCgVlcnJvchgEIAEoCRImCgtkaWFnbm9zdGljcxgFIAMoCzIRLnN5c21sLkRpYWdub3N0aWMSFAoMZXhwZXJpbWVudGFsGAYgASgIEhsKE2V4cGVyaW1lbnRhbF9ub3RpY2UYByABKAkiUQoRQXBwbHlFZGl0c1JlcXVlc3QSEgoKbW9kZWxfaGFzaBgBIAEoCRIoCgpvcGVyYXRpb25zGAIgAygLMhQuc3lzbWwuRWRpdE9wZXJhdGlvbiK8AQoNRWRpdE9wZXJhdGlvbhIoCglzZXRfdmFsdWUYASABKAsyEy5zeXNtbC5TZXRWYWx1ZUVkaXRIABIjCgZyZW5hbWUYAiABKAsyES5zeXNtbC5SZW5hbWVFZGl0SAASKgoKYWRkX21lbWJlchgDIAEoCzIULnN5c21sLkFkZE1lbWJlckVkaXRIABIjCgZkZWxldGUYBCABKAsyES5zeXNtbC5EZWxldGVFZGl0SABCCwoJb3BlcmF0aW9uIoIBCg1BZGRNZW1iZXJFZGl0Eg0KBW93bmVyGAEgASgJEgwKBGtpbmQYAiABKAkSDAoEbmFtZRgDIAEoCRIMCgR0eXBlGAQgASgJEhQKDG11bHRpcGxpY2l0eRgFIAEoCRINCgV2YWx1ZRgGIAEoCRITCgtzcGVjaWFsaXplcxgHIAMoCSItCgpEZWxldGVFZGl0Eg4KBnRhcmdldBgBIAEoCRIPCgdjYXNjYWRlGAIgASgIIi0KDFNldFZhbHVlRWRpdBIOCgZ0YXJnZXQYASABKAkSDQoFdmFsdWUYAiABKAkiLgoKUmVuYW1lRWRpdBIOCgZ0YXJnZXQYASABKAkSEAoIbmV3X25hbWUYAiABKAkiwgEKEkFwcGx5RWRpdHNSZXNwb25zZRIPCgdjb250ZW50GAEgASgJEiMKB2FwcGxpZWQYAiADKAsyEi5zeXNtbC5BcHBsaWVkRWRpdBINCgVlcnJvchgDIAEoCRIjCgdmYWlsdXJlGAQgASgOMhIuc3lzbWwuRWRpdEZhaWx1cmUSJgoLZGlhZ25vc3RpY3MYBSADKAsyES5zeXNtbC5EaWFnbm9zdGljEhoKEnJlZmVycmluZ19lbGVtZW50cxgGIAMoCSJ6CgtBcHBsaWVkRWRpdBIXCg9vcGVyYXRpb25faW5kZXgYASABKAUSDgoGdGFyZ2V0GAIgASgJEg4KBm9mZnNldBgDIAEoBRIOCgZsZW5ndGgYBCABKAUSEAoIb2xkX3RleHQYBSABKAkSEAoIbmV3X3RleHQYBiABKAki/QIKClN5bWJvbEluZm8SCgoCaWQYASABKAkSDAoEbmFtZRgCIAEoCRIMCgRraW5kGAMgASgJEjEKCG1ldGFkYXRhGAQgAygLMh8uc3lzbWwuU3ltYm9sSW5mby5NZXRhZGF0YUVudHJ5EhEKCWNoaWxkX2lkcxgFIAMoCRIoCgphdHRyaWJ1dGVzGAYgAygLMhQuc3lzbWwuQXR0cmlidXRlSW5mbxIiCgl0eXBlX2luZm8YByABKAsyDy5zeXNtbC5UeXBlSW5mbxItCgxtdWx0aXBsaWNpdHkYCCABKAsyFy5zeXNtbC5NdWx0aXBsaWNpdHlJbmZvEi4KD3NwZWNpYWxpemF0aW9ucxgJIAMoCzIVLnN5c21sLlNwZWNpYWxpemF0aW9uEiMKG3dpdGhoZWxkX2xpYnJhcnlfYXR0cmlidXRlcxgKIAEoBRovCg1NZXRhZGF0YUVudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEiWAoOU3BlY2lhbGl6YXRpb24SDAoEa2luZBgBIAEoCRIQCghkZWNsYXJlZBgCIAEoCRIRCgl0YXJnZXRfaWQYAyABKAkSEwoLdGFyZ2V0X2tpbmQYBCABKAkilQEKCFR5cGVJbmZvEhAKCGRlY2xhcmVkGAEgASgJEhMKC3Jlc29sdmVkX2lkGAIgASgJEhUKDXJlc29sdmVkX2tpbmQYAyABKAkSEQoJcHJpbWl0aXZlGAQgASgJEhgKEHByaW1pdGl2ZV9zb3VyY2UYBSABKAkSEAoIcXVhbnRpdHkYBiABKAgSDAoEdW5pdBgHIAEoCSIwChBNdWx0aXBsaWNpdHlJbmZvEg0KBWxvd2VyGAEgASgJEg0KBXVwcGVyGAIgASgJIlYKDUF0dHJpYnV0ZUluZm8SDAoEbmFtZRgBIAEoCRIMCgR0eXBlGAIgASgJEhsKBXZhbHVlGAMgASgLMgwuc3lzbWwuVmFsdWUSDAoEdW5pdBgEIAEoCSLiAwoFVmFsdWUSEwoJaW50X3ZhbHVlGAEgASgDSAASFAoKcmVhbF92YWx1ZRgCIAEoAUgAEhQKCmJvb2xfdmFsdWUYAyABKAhIABIWCgxzdHJpbmdfdmFsdWUYBCABKAlIABIVCgtpbnN0YW5jZV9pZBgFIAEoA0gAEigKCHNlcXVlbmNlGAYgASgLMhQuc3lzbWwuVmFsdWVTZXF1ZW5jZUgAEg4KBG51bGwYByABKAlIABIjCghxdWFudGl0eRgIIAEoCzIPLnN5c21sLlF1YW50aXR5SAASKgoMZW51bV9saXRlcmFsGAkgASgLMhIuc3lzbWwuRW51bUxpdGVyYWxIABIPCgV1bnNldBgKIAEoCEgAEiEKB2NvbXBsZXgYCyABKAsyDi5zeXNtbC5Db21wbGV4SAASHQoFYXJyYXkYDCABKAsyDC5zeXNtbC5BcnJheUgAEh8KBnZlY3RvchgNIAEoCzINLnN5c21sLlZlY3RvckgAEjAKD3ZlY3Rvcl9xdWFudGl0eRgOIAEoCzIVLnN5c21sLlZlY3RvclF1YW50aXR5SAASMAoPbWVhc3VyZW1lbnRfcmVmGA8gASgLMhUuc3lzbWwuTWVhc3VyZW1lbnRSZWZIAEIGCgRraW5kIjsKBUFycmF5EhIKCmRpbWVuc2lvbnMYASADKAMSHgoIZWxlbWVudHMYAiADKAsyDC5zeXNtbC5WYWx1ZSIqCgZWZWN0b3ISIAoKY29tcG9uZW50cxgBIAMoCzIMLnN5c21sLlZhbHVlIjUKDlZlY3RvclF1YW50aXR5EiMKCmNvbXBvbmVudHMYASADKAsyDy5zeXNtbC5RdWFudGl0eSIqCgdDb21wbGV4EgwKBHJlYWwYASABKAESEQoJaW1hZ2luYXJ5GAIgASgBIkcKC0VudW1MaXRlcmFsEhIKCmxpdGVyYWxfaWQYASABKAkSFgoOZW51bWVyYXRpb25faWQYAiABKAkSDAoEbmFtZRgDIAEoCSIvCg1WYWx1ZVNlcXVlbmNlEh4KCGVsZW1lbnRzGAEgAygLMgwuc3lzbWwuVmFsdWUifAoIUXVhbnRpdHkSFwoNaW50X21hZ25pdHVkZRgBIAEoA0gAEhgKDnJlYWxfbWFnbml0dWRlGAIgASgBSAASDAoEdW5pdBgDIAEoCRIiCgl1bml0X3Rlcm0YBCABKAsyDy5zeXNtbC5Vbml0VGVybUILCgltYWduaXR1ZGUiUwoOTWVhc3VyZW1lbnRSZWYSDAoEdW5pdBgBIAEoCRIiCgl1bml0X3Rlcm0YAiABKAsyDy5zeXNtbC5Vbml0VGVybRIPCgd1bml0X2lkGAMgASgJIlQKCFVuaXRUZXJtEhEKCXNjYWxlX251bRgBIAEoARIRCglzY2FsZV9kZW4YAiABKAESIgoHZmFjdG9ycxgDIAMoCzIRLnN5c21sLlVuaXRGYWN0b3IiLwoKVW5pdEZhY3RvchIPCgd1bml0X2lkGAEgASgJEhAKCGV4cG9uZW50GAIgASgBIkoKCkRpYWdub3N0aWMSEAoIc2V2ZXJpdHkYASABKAkSDwoHbWVzc2FnZRgCIAEoCRIZCgRzcGFuGAMgASgLMgsuc3lzbWwuU3BhbiJeCgRTcGFuEgwKBGZpbGUYASABKAkSEgoKc3RhcnRfbGluZRgCIAEoBRIRCglzdGFydF9jb2wYAyABKAUSEAoIZW5kX2xpbmUYBCABKAUSDwoHZW5kX2NvbBgFIAEoBSITChFTZXJ2ZXJJbmZvUmVxdWVzdCI7ChJTZXJ2ZXJJbmZvUmVzcG9uc2USDwoHdmVyc2lvbhgBIAEoCRIUCgxjYXBhYmlsaXRpZXMYAiADKAkiUwoMUXVlcnlSZXF1ZXN0EhIKCm1vZGVsX2hhc2gYASABKAkSGwoFcXVlcnkYAiABKAsyDC5zeXNtbC5RdWVyeRISCgpvc2xjX3F1ZXJ5GAMgASgJIjwKDVF1ZXJ5UmVzcG9uc2USKwoIZWxlbWVudHMYASADKAsyGS5zeXNtbC5RdWVyeVJlc3VsdEVsZW1lbnQiSAoFUXVlcnkSDQoFc2NvcGUYASADKAkSDgoGc2VsZWN0GAIgAygJEiAKBXdoZXJlGAMgASgLMhEuc3lzbWwuQ29uc3RyYWludCJ8CgpDb25zdHJhaW50Ei8KCXByaW1pdGl2ZRgBIAEoCzIaLnN5c21sLlByaW1pdGl2ZUNvbnN0cmFpbnRIABIvCgljb21wb3NpdGUYAiABKAsyGi5zeXNtbC5Db21wb3NpdGVDb25zdHJhaW50SABCDAoKY29uc3RyYWludCJzChNQcmltaXRpdmVDb25zdHJhaW50Eg8KB2ludmVyc2UYASABKAgSEAoIcHJvcGVydHkYAiABKAkSKgoIb3BlcmF0b3IYAyABKA4yGC5zeXNtbC5QcmltaXRpdmVPcGVyYXRvchINCgV2YWx1ZRgEIAMoCSJoChNDb21wb3NpdGVDb25zdHJhaW50EioKCG9wZXJhdG9yGAEgASgOMhguc3lzbWwuQ29tcG9zaXRlT3BlcmF0b3ISJQoKY29uc3RyYWludBgCIAMoCzIRLnN5c21sLkNvbnN0cmFpbnQioAEKElF1ZXJ5UmVzdWx0RWxlbWVudBIKCgJpZBgBIAEoCRIMCgR0eXBlGAIgASgJEj0KCnByb3BlcnRpZXMYAyADKAsyKS5zeXNtbC5RdWVyeVJlc3VsdEVsZW1lbnQuUHJvcGVydGllc0VudHJ5GjEKD1Byb3BlcnRpZXNFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBIm4KF1J1bkRvY3VtZW50UXVlcnlSZXF1ZXN0EhIKCm1vZGVsX2hhc2gYASABKAkSEAoIcXVlcnlfaWQYAiABKAkSLQoIYmluZGluZ3MYAyADKAsyGy5zeXNtbC5Eb2N1bWVudFF1ZXJ5QmluZGluZyJPChREb2N1bWVudFF1ZXJ5QmluZGluZxIRCglwYXJhbWV0ZXIYASABKAkSJAoGdmFsdWVzGAIgAygLMhQuc3lzbWwuRG9jdW1lbnRWYWx1ZSLVAQoNRG9jdW1lbnRWYWx1ZRIUCgplbGVtZW50X2lkGAEgASgJSAASFgoMc3RyaW5nX3ZhbHVlGAIgASgJSAASEwoJaW50X3ZhbHVlGAMgASgDSAASFAoKcmVhbF92YWx1ZRgEIAEoAUgAEhQKCmJvb2xfdmFsdWUYBSABKAhIABISCghpbmZpbml0eRgGIAEoCEgAEiMKCHF1YW50aXR5GAggASgLMg8uc3lzbWwuUXVhbnRpdHlIABIUCgxlbGVtZW50X3R5cGUYByABKAlCBgoEa2luZCIjChNEb2N1bWVudFF1ZXJ5Q29sdW1uEgwKBG5hbWUYASABKAkiOQoRRG9jdW1lbnRRdWVyeUNlbGwSJAoGdmFsdWVzGAEgAygLMhQuc3lzbWwuRG9jdW1lbnRWYWx1ZSJiChBEb2N1bWVudFF1ZXJ5Um93EiUKB2VsZW1lbnQYASABKAsyFC5zeXNtbC5Eb2N1bWVudFZhbHVlEicKBWNlbGxzGAIgAygLMhguc3lzbWwuRG9jdW1lbnRRdWVyeUNlbGwibgoYUnVuRG9jdW1lbnRRdWVyeVJlc3BvbnNlEisKB2NvbHVtbnMYASADKAsyGi5zeXNtbC5Eb2N1bWVudFF1ZXJ5Q29sdW1uEiUKBHJvd3MYAiADKAsyFy5zeXNtbC5Eb2N1bWVudFF1ZXJ5Um93IkAKFVJlbmRlckRvY3VtZW50UmVxdWVzdBISCgptb2RlbF9oYXNoGAEgASgJEhMKC2RvY3VtZW50X2lkGAIgASgJIioKFlJlbmRlckRvY3VtZW50UmVzcG9uc2USEAoIbWFya2Rvd24YASABKAkqkwEKDUZhaWx1cmVSZWFzb24SHgoaRkFJTFVSRV9SRUFTT05fVU5TUEVDSUZJRUQQABIdChlGQUlMVVJFX1JFQVNPTl9FVkFMVUFUSU9OEAESHQoZRkFJTFVSRV9SRUFTT05fV1JPTkdfS0lORBACEiQKIEZBSUxVUkVfUkVBU09OX0FNQklHVU9VU19TVUJKRUNUEAMqnQQKC0VkaXRGYWlsdXJlEhwKGEVESVRfRkFJTFVSRV9VTlNQRUNJRklFRBAAEh4KGkVESVRfRkFJTFVSRV9OT19PUEVSQVRJT05TEAESHwobRURJVF9GQUlMVVJFX1VOS05PV05fVEFSR0VUEAISIQodRURJVF9GQUlMVVJFX0FNQklHVU9VU19UQVJHRVQQAxIbChdFRElUX0ZBSUxVUkVfTk9UX1ZBTFVFRBAEEh4KGkVESVRfRkFJTFVSRV9JTlZBTElEX1ZBTFVFEAUSHQoZRURJVF9GQUlMVVJFX0lOVkFMSURfTkFNRRAGEhoKFkVESVRfRkFJTFVSRV9OT1RfTkFNRUQQBxIiCh5FRElUX0ZBSUxVUkVfUkVOQU1FX1JFRkVSRU5DRUQQCBIiCh5FRElUX0ZBSUxVUkVfT1ZFUkxBUFBJTkdfRURJVFMQCRIfChtFRElUX0ZBSUxVUkVfUkVTVUxUX0lOVkFMSUQQChIeChpFRElUX0ZBSUxVUkVfT1dORVJfVU5LTk9XThALEiQKIEVESVRfRkFJTFVSRV9PV05FUl9OT1RfTkFNRVNQQUNFEAwSHQoZRURJVF9GQUlMVVJFX0lMTEVHQUxfS0lORBANEiIKHkVESVRfRkFJTFVSRV9NRU1CRVJfTkFNRV9UQUtFThAOEiIKHkVESVRfRkFJTFVSRV9ERUxFVEVfUkVGRVJFTkNFRBAPKpIBChFQcmltaXRpdmVPcGVyYXRvchIiCh5QUklNSVRJVkVfT1BFUkFUT1JfVU5TUEVDSUZJRUQQABIcChhQUklNSVRJVkVfT1BFUkFUT1JfRVFVQUwQARIeChpQUklNSVRJVkVfT1BFUkFUT1JfR1JFQVRFUhACEhsKF1BSSU1JVElWRV9PUEVSQVRPUl9MRVNTEAMqbgoRQ29tcG9zaXRlT3BlcmF0b3ISIgoeQ09NUE9TSVRFX09QRVJBVE9SX1VOU1BFQ0lGSUVEEAASGgoWQ09NUE9TSVRFX09QRVJBVE9SX0FORBABEhkKFUNPTVBPU0lURV9PUEVSQVRPUl9PUhACMucKCgxTeXNNTFNlcnZpY2USRAoNR2V0U2VydmVySW5mbxIYLnN5c21sLlNlcnZlckluZm9SZXF1ZXN0Ghkuc3lzbWwuU2VydmVySW5mb1Jlc3BvbnNlEj4KCVBhcnNlRmlsZRIXLnN5c21sLlBhcnNlRmlsZVJlcXVlc3QaGC5zeXNtbC5QYXJzZUZpbGVSZXNwb25zZRJHCgxQYXJzZVNvdXJjZXMSGi5zeXNtbC5QYXJzZVNvdXJjZXNSZXF1ZXN0Ghsuc3lzbWwuUGFyc2VTb3VyY2VzUmVzcG9uc2USOwoJR2V0U3ltYm9sEhcuc3lzbWwuR2V0U3ltYm9sUmVxdWVzdBoVLnN5c21sLlN5bWJvbFJlc3BvbnNlEkcKDkdldERpYWdub3N0aWNzEhkuc3lzbWwuRGlhZ25vc3RpY3NSZXF1ZXN0Ghouc3lzbWwuRGlhZ25vc3RpY3NSZXNwb25zZRI7CghFdmFsdWF0ZRIWLnN5c21sLkV2YWx1YXRlUmVxdWVzdBoXLnN5c21sLkV2YWx1YXRlUmVzcG9uc2USRAoLSW5zdGFudGlhdGUSGS5zeXNtbC5JbnN0YW50aWF0ZVJlcXVlc3QaGi5zeXNtbC5JbnN0YW50aWF0ZVJlc3BvbnNlEkoKDUV4ZWN1dGVBY3Rpb24SGy5zeXNtbC5FeGVjdXRlQWN0aW9uUmVxdWVzdBocLnN5c21sLkV4ZWN1dGVBY3Rpb25SZXNwb25zZRJHCgxFeGVjdXRlU3RhdGUSGi5zeXNtbC5FeGVjdXRlU3RhdGVSZXF1ZXN0Ghsuc3lzbWwuRXhlY3V0ZVN0YXRlUmVzcG9uc2USOAoHQ29udmVydBIVLnN5c21sLkNvbnZlcnRSZXF1ZXN0GhYuc3lzbWwuQ29udmVydFJlc3BvbnNlEkEKCkFwcGx5RWRpdHMSGC5zeXNtbC5BcHBseUVkaXRzUmVxdWVzdBoZLnN5c21sLkFwcGx5RWRpdHNSZXNwb25zZRJTChBWZXJpZnlDb25zdHJhaW50Eh4uc3lzbWwuVmVyaWZ5Q29uc3RyYWludFJlcXVlc3QaHy5zeXNtbC5WZXJpZnlDb25zdHJhaW50UmVzcG9uc2USVgoRVmVyaWZ5UmVxdWlyZW1lbnQSHy5zeXNtbC5WZXJpZnlSZXF1aXJlbWVudFJlcXVlc3QaIC5zeXNtbC5WZXJpZnlSZXF1aXJlbWVudFJlc3BvbnNlElkKElZlcmlmeVNhdGlzZmFjdGlvbhIgLnN5c21sLlZlcmlmeVNhdGlzZmFjdGlvblJlcXVlc3QaIS5zeXNtbC5WZXJpZnlTYXRpc2ZhY3Rpb25SZXNwb25zZRJHCgxFdmFsdWF0ZUNhbGMSGi5zeXNtbC5FdmFsdWF0ZUNhbGNSZXF1ZXN0Ghsuc3lzbWwuRXZhbHVhdGVDYWxjUmVzcG9uc2USRAoLUnVuQW5hbHlzaXMSGS5zeXNtbC5SdW5BbmFseXNpc1JlcXVlc3QaGi5zeXNtbC5SdW5BbmFseXNpc1Jlc3BvbnNlEjIKBVF1ZXJ5EhMuc3lzbWwuUXVlcnlSZXF1ZXN0GhQuc3lzbWwuUXVlcnlSZXNwb25zZRJTChBSdW5Eb2N1bWVudFF1ZXJ5Eh4uc3lzbWwuUnVuRG9jdW1lbnRRdWVyeVJlcXVlc3QaHy5zeXNtbC5SdW5Eb2N1bWVudFF1ZXJ5UmVzcG9uc2USTQoOUmVuZGVyRG9jdW1lbnQSHC5zeXNtbC5SZW5kZXJEb2N1bWVudFJlcXVlc3QaHS5zeXNtbC5SZW5kZXJEb2N1bWVudFJlc3BvbnNlQipaKGdpdGh1Yi5jb20vT3Blbi1NQkVFL09wZW5TeXNNTC9hcGkvcHJvdG9iBnByb3RvMw"); + fileDesc("CgtzeXNtbC5wcm90bxIFc3lzbWwiygEKB1ZlcmRpY3QSDAoEa2luZBgBIAEoCRISCgplbGVtZW50X2lkGAIgASgJEg8KB2VsZW1lbnQYAyABKAkSDQoFaG9sZHMYBCABKAgSEQoJY29uZGl0aW9uGAUgASgJEhMKC2luc3RhbmNlX2lkGAYgASgDEhgKEGluc3RhbmNlX3R5cGVfaWQYByABKAkSDQoFZXJyb3IYCCABKAkSLAoOZmFpbHVyZV9yZWFzb24YCSABKA4yFC5zeXNtbC5GYWlsdXJlUmVhc29uIlsKF1ZlcmlmeUNvbnN0cmFpbnRSZXF1ZXN0EhIKCm1vZGVsX2hhc2gYASABKAkSEQoJc3ltYm9sX2lkGAIgASgJEhkKEXN1YmplY3Rfc3ltYm9sX2lkGAMgASgJIpYBChhWZXJpZnlDb25zdHJhaW50UmVzcG9uc2USHwoHdmVyZGljdBgBIAEoCzIOLnN5c21sLlZlcmRpY3QSIgoJaW5zdGFuY2VzGAIgAygLMg8uc3lzbWwuSW5zdGFuY2USDQoFZXJyb3IYAyABKAkSJgoLZGlhZ25vc3RpY3MYBCADKAsyES5zeXNtbC5EaWFnbm9zdGljIlwKGFZlcmlmeVJlcXVpcmVtZW50UmVxdWVzdBISCgptb2RlbF9oYXNoGAEgASgJEhEKCXN5bWJvbF9pZBgCIAEoCRIZChFzdWJqZWN0X3N5bWJvbF9pZBgDIAEoCSKXAQoZVmVyaWZ5UmVxdWlyZW1lbnRSZXNwb25zZRIfCgd2ZXJkaWN0GAEgASgLMg4uc3lzbWwuVmVyZGljdBIiCglpbnN0YW5jZXMYAiADKAsyDy5zeXNtbC5JbnN0YW5jZRINCgVlcnJvchgDIAEoCRImCgtkaWFnbm9zdGljcxgEIAMoCzIRLnN5c21sLkRpYWdub3N0aWMiQgoZVmVyaWZ5U2F0aXNmYWN0aW9uUmVxdWVzdBISCgptb2RlbF9oYXNoGAEgASgJEhEKCXN5bWJvbF9pZBgCIAEoCSLHAQoaVmVyaWZ5U2F0aXNmYWN0aW9uUmVzcG9uc2USIAoIdmVyZGljdHMYASADKAsyDi5zeXNtbC5WZXJkaWN0EiIKCWluc3RhbmNlcxgCIAMoCzIPLnN5c21sLkluc3RhbmNlEg0KBWVycm9yGAMgASgJEiYKC2RpYWdub3N0aWNzGAQgAygLMhEuc3lzbWwuRGlhZ25vc3RpYxIsCg5mYWlsdXJlX3JlYXNvbhgFIAEoDjIULnN5c21sLkZhaWx1cmVSZWFzb24iXQoTRXZhbHVhdGVDYWxjUmVxdWVzdBISCgptb2RlbF9oYXNoGAEgASgJEhEKCXN5bWJvbF9pZBgCIAEoCRIfCglhcmd1bWVudHMYAyADKAsyDC5zeXNtbC5WYWx1ZSK9AQoURXZhbHVhdGVDYWxjUmVzcG9uc2USHAoGcmVzdWx0GAEgASgLMgwuc3lzbWwuVmFsdWUSIgoHb3V0cHV0cxgCIAMoCzIRLnN5c21sLkNhbGNPdXRwdXQSDQoFZXJyb3IYAyABKAkSJgoLZGlhZ25vc3RpY3MYBCADKAsyES5zeXNtbC5EaWFnbm9zdGljEiwKDmZhaWx1cmVfcmVhc29uGAUgASgOMhQuc3lzbWwuRmFpbHVyZVJlYXNvbiI3CgpDYWxjT3V0cHV0EgwKBG5hbWUYASABKAkSGwoFdmFsdWUYAiABKAsyDC5zeXNtbC5WYWx1ZSKEAgoSUnVuQW5hbHlzaXNSZXF1ZXN0EhIKCm1vZGVsX2hhc2gYASABKAkSEQoJc3ltYm9sX2lkGAIgASgJEhkKEXN1YmplY3Rfc3ltYm9sX2lkGAMgASgJEh8KCWFyZ3VtZW50cxgEIAMoCzIMLnN5c21sLlZhbHVlEkYKD25hbWVkX2FyZ3VtZW50cxgFIAMoCzItLnN5c21sLlJ1bkFuYWx5c2lzUmVxdWVzdC5OYW1lZEFyZ3VtZW50c0VudHJ5GkMKE05hbWVkQXJndW1lbnRzRW50cnkSCwoDa2V5GAEgASgJEhsKBXZhbHVlGAIgASgLMgwuc3lzbWwuVmFsdWU6AjgBIuQBChNSdW5BbmFseXNpc1Jlc3BvbnNlEiIKB291dHB1dHMYASADKAsyES5zeXNtbC5DYWxjT3V0cHV0EiAKCHZlcmRpY3RzGAIgAygLMg4uc3lzbWwuVmVyZGljdBIiCglpbnN0YW5jZXMYAyADKAsyDy5zeXNtbC5JbnN0YW5jZRINCgVlcnJvchgEIAEoCRImCgtkaWFnbm9zdGljcxgFIAMoCzIRLnN5c21sLkRpYWdub3N0aWMSLAoOZmFpbHVyZV9yZWFzb24YBiABKA4yFC5zeXNtbC5GYWlsdXJlUmVhc29uIowBChBQYXJzZUZpbGVSZXF1ZXN0EhMKCWZpbGVfcGF0aBgBIAEoCUgAEhEKB2NvbnRlbnQYAiABKAlIABIYCgxjb250ZW50X2hhc2gYAyABKAlCAhgBEhAKCGxhbmd1YWdlGAQgASgJEhoKEnN0cmljdF9jb25mb3JtYW5jZRgFIAEoCEIICgZzb3VyY2UiYgoOU291cmNlRG9jdW1lbnQSEwoJZmlsZV9wYXRoGAEgASgJSAASEQoHY29udGVudBgCIAEoCUgAEhAKCGxhbmd1YWdlGAMgASgJEgwKBG5hbWUYBCABKAlCCAoGc291cmNlIlsKE1BhcnNlU291cmNlc1JlcXVlc3QSKAoJZG9jdW1lbnRzGAEgAygLMhUuc3lzbWwuU291cmNlRG9jdW1lbnQSGgoSc3RyaWN0X2NvbmZvcm1hbmNlGAIgASgIIoMBChRQYXJzZVNvdXJjZXNSZXNwb25zZRISCgptb2RlbF9oYXNoGAEgASgJEiAKBXJvb3RzGAIgAygLMhEuc3lzbWwuU3ltYm9sSW5mbxImCgtkaWFnbm9zdGljcxgDIAMoCzIRLnN5c21sLkRpYWdub3N0aWMSDQoFZXJyb3IYBCABKAkifwoRUGFyc2VGaWxlUmVzcG9uc2USEgoKbW9kZWxfaGFzaBgBIAEoCRIfCgRyb290GAIgASgLMhEuc3lzbWwuU3ltYm9sSW5mbxImCgtkaWFnbm9zdGljcxgDIAMoCzIRLnN5c21sLkRpYWdub3N0aWMSDQoFZXJyb3IYBCABKAkiOQoQR2V0U3ltYm9sUmVxdWVzdBISCgptb2RlbF9oYXNoGAEgASgJEhEKCXN5bWJvbF9pZBgCIAEoCSJCCg5TeW1ib2xSZXNwb25zZRIhCgZzeW1ib2wYASABKAsyES5zeXNtbC5TeW1ib2xJbmZvEg0KBWVycm9yGAIgASgJIigKEkRpYWdub3N0aWNzUmVxdWVzdBISCgptb2RlbF9oYXNoGAEgASgJIkwKE0RpYWdub3N0aWNzUmVzcG9uc2USJgoLZGlhZ25vc3RpY3MYASADKAsyES5zeXNtbC5EaWFnbm9zdGljEg0KBWVycm9yGAIgASgJIm8KD0V2YWx1YXRlUmVxdWVzdBISCgptb2RlbF9oYXNoGAEgASgJEhIKCmV4cHJlc3Npb24YAiABKAkSGQoRY29udGV4dF9zeW1ib2xfaWQYAyABKAkSGQoRc3ViamVjdF9zeW1ib2xfaWQYBCABKAkiZwoQRXZhbHVhdGVSZXNwb25zZRIcCgZyZXN1bHQYASABKAsyDC5zeXNtbC5WYWx1ZRINCgVlcnJvchgCIAEoCRImCgtkaWFnbm9zdGljcxgDIAMoCzIRLnN5c21sLkRpYWdub3N0aWMiwgEKCEluc3RhbmNlEgoKAmlkGAEgASgDEhYKDnR5cGVfc3ltYm9sX2lkGAIgASgJEjoKDmZlYXR1cmVfdmFsdWVzGAQgAygLMiIuc3lzbWwuSW5zdGFuY2UuRmVhdHVyZVZhbHVlc0VudHJ5GkkKEkZlYXR1cmVWYWx1ZXNFbnRyeRILCgNrZXkYASABKAkSIgoFdmFsdWUYAiABKAsyEy5zeXNtbC5GZWF0dXJlVmFsdWU6AjgBSgQIAxAEUgVzbG90cyKEAQoMRmVhdHVyZVZhbHVlEhQKDGZlYXR1cmVfbmFtZRgBIAEoCRIbCgV2YWx1ZRgCIAEoCzIMLnN5c21sLlZhbHVlEhwKBnZhbHVlcxgDIAMoCzIMLnN5c21sLlZhbHVlEhQKDG1hdGVyaWFsaXplZBgEIAEoCBINCgVlcnJvchgFIAEoCSI7ChJJbnN0YW50aWF0ZVJlcXVlc3QSEgoKbW9kZWxfaGFzaBgBIAEoCRIRCglzeW1ib2xfaWQYAiABKAkikwEKE0luc3RhbnRpYXRlUmVzcG9uc2USIQoIaW5zdGFuY2UYASABKAsyDy5zeXNtbC5JbnN0YW5jZRINCgVlcnJvchgCIAEoCRImCgtkaWFnbm9zdGljcxgDIAMoCzIRLnN5c21sLkRpYWdub3N0aWMSIgoJaW5zdGFuY2VzGAQgAygLMg8uc3lzbWwuSW5zdGFuY2UiugEKFEV4ZWN1dGVBY3Rpb25SZXF1ZXN0EhIKCm1vZGVsX2hhc2gYASABKAkSGAoQYWN0aW9uX3N5bWJvbF9pZBgCIAEoCRI3CgZpbnB1dHMYAyADKAsyJy5zeXNtbC5FeGVjdXRlQWN0aW9uUmVxdWVzdC5JbnB1dHNFbnRyeRo7CgtJbnB1dHNFbnRyeRILCgNrZXkYASABKAkSGwoFdmFsdWUYAiABKAsyDC5zeXNtbC5WYWx1ZToCOAEiyAEKFUV4ZWN1dGVBY3Rpb25SZXNwb25zZRI6CgdvdXRwdXRzGAEgAygLMikuc3lzbWwuRXhlY3V0ZUFjdGlvblJlc3BvbnNlLk91dHB1dHNFbnRyeRINCgVlcnJvchgCIAEoCRImCgtkaWFnbm9zdGljcxgDIAMoCzIRLnN5c21sLkRpYWdub3N0aWMaPAoMT3V0cHV0c0VudHJ5EgsKA2tleRgBIAEoCRIbCgV2YWx1ZRgCIAEoCzIMLnN5c21sLlZhbHVlOgI4ASJaChNFeGVjdXRlU3RhdGVSZXF1ZXN0EhIKCm1vZGVsX2hhc2gYASABKAkSHwoXc3RhdGVfbWFjaGluZV9zeW1ib2xfaWQYAiABKAkSDgoGZXZlbnRzGAMgAygJIu4BChRFeGVjdXRlU3RhdGVSZXNwb25zZRIWCg5zdGF0ZXNfdmlzaXRlZBgBIAMoCRJECg1maW5hbF9jb250ZXh0GAIgAygLMi0uc3lzbWwuRXhlY3V0ZVN0YXRlUmVzcG9uc2UuRmluYWxDb250ZXh0RW50cnkSDQoFZXJyb3IYAyABKAkSJgoLZGlhZ25vc3RpY3MYBCADKAsyES5zeXNtbC5EaWFnbm9zdGljGkEKEUZpbmFsQ29udGV4dEVudHJ5EgsKA2tleRgBIAEoCRIbCgV2YWx1ZRgCIAEoCzIMLnN5c21sLlZhbHVlOgI4ASKgAQoOQ29udmVydFJlcXVlc3QSEwoJZmlsZV9wYXRoGAEgASgJSAASEQoHY29udGVudBgCIAEoCUgAEhQKCm1vZGVsX2hhc2gYBiABKAlIABITCgtmcm9tX2Zvcm1hdBgDIAEoCRIRCgl0b19mb3JtYXQYBCABKAkSHgoWdG9sZXJhdGVfc3ludGF4X2Vycm9ycxgFIAEoCEIICgZzb3VyY2UitAEKD0NvbnZlcnRSZXNwb25zZRIPCgdjb250ZW50GAEgASgJEhMKC2Zyb21fZm9ybWF0GAIgASgJEhEKCXRvX2Zvcm1hdBgDIAEoCRINCgVlcnJvchgEIAEoCRImCgtkaWFnbm9zdGljcxgFIAMoCzIRLnN5c21sLkRpYWdub3N0aWMSFAoMZXhwZXJpbWVudGFsGAYgASgIEhsKE2V4cGVyaW1lbnRhbF9ub3RpY2UYByABKAkiUQoRQXBwbHlFZGl0c1JlcXVlc3QSEgoKbW9kZWxfaGFzaBgBIAEoCRIoCgpvcGVyYXRpb25zGAIgAygLMhQuc3lzbWwuRWRpdE9wZXJhdGlvbiK8AQoNRWRpdE9wZXJhdGlvbhIoCglzZXRfdmFsdWUYASABKAsyEy5zeXNtbC5TZXRWYWx1ZUVkaXRIABIjCgZyZW5hbWUYAiABKAsyES5zeXNtbC5SZW5hbWVFZGl0SAASKgoKYWRkX21lbWJlchgDIAEoCzIULnN5c21sLkFkZE1lbWJlckVkaXRIABIjCgZkZWxldGUYBCABKAsyES5zeXNtbC5EZWxldGVFZGl0SABCCwoJb3BlcmF0aW9uIoIBCg1BZGRNZW1iZXJFZGl0Eg0KBW93bmVyGAEgASgJEgwKBGtpbmQYAiABKAkSDAoEbmFtZRgDIAEoCRIMCgR0eXBlGAQgASgJEhQKDG11bHRpcGxpY2l0eRgFIAEoCRINCgV2YWx1ZRgGIAEoCRITCgtzcGVjaWFsaXplcxgHIAMoCSItCgpEZWxldGVFZGl0Eg4KBnRhcmdldBgBIAEoCRIPCgdjYXNjYWRlGAIgASgIIi0KDFNldFZhbHVlRWRpdBIOCgZ0YXJnZXQYASABKAkSDQoFdmFsdWUYAiABKAkiLgoKUmVuYW1lRWRpdBIOCgZ0YXJnZXQYASABKAkSEAoIbmV3X25hbWUYAiABKAkiwgEKEkFwcGx5RWRpdHNSZXNwb25zZRIPCgdjb250ZW50GAEgASgJEiMKB2FwcGxpZWQYAiADKAsyEi5zeXNtbC5BcHBsaWVkRWRpdBINCgVlcnJvchgDIAEoCRIjCgdmYWlsdXJlGAQgASgOMhIuc3lzbWwuRWRpdEZhaWx1cmUSJgoLZGlhZ25vc3RpY3MYBSADKAsyES5zeXNtbC5EaWFnbm9zdGljEhoKEnJlZmVycmluZ19lbGVtZW50cxgGIAMoCSJ6CgtBcHBsaWVkRWRpdBIXCg9vcGVyYXRpb25faW5kZXgYASABKAUSDgoGdGFyZ2V0GAIgASgJEg4KBm9mZnNldBgDIAEoBRIOCgZsZW5ndGgYBCABKAUSEAoIb2xkX3RleHQYBSABKAkSEAoIbmV3X3RleHQYBiABKAki/QIKClN5bWJvbEluZm8SCgoCaWQYASABKAkSDAoEbmFtZRgCIAEoCRIMCgRraW5kGAMgASgJEjEKCG1ldGFkYXRhGAQgAygLMh8uc3lzbWwuU3ltYm9sSW5mby5NZXRhZGF0YUVudHJ5EhEKCWNoaWxkX2lkcxgFIAMoCRIoCgphdHRyaWJ1dGVzGAYgAygLMhQuc3lzbWwuQXR0cmlidXRlSW5mbxIiCgl0eXBlX2luZm8YByABKAsyDy5zeXNtbC5UeXBlSW5mbxItCgxtdWx0aXBsaWNpdHkYCCABKAsyFy5zeXNtbC5NdWx0aXBsaWNpdHlJbmZvEi4KD3NwZWNpYWxpemF0aW9ucxgJIAMoCzIVLnN5c21sLlNwZWNpYWxpemF0aW9uEiMKG3dpdGhoZWxkX2xpYnJhcnlfYXR0cmlidXRlcxgKIAEoBRovCg1NZXRhZGF0YUVudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEiWAoOU3BlY2lhbGl6YXRpb24SDAoEa2luZBgBIAEoCRIQCghkZWNsYXJlZBgCIAEoCRIRCgl0YXJnZXRfaWQYAyABKAkSEwoLdGFyZ2V0X2tpbmQYBCABKAkilQEKCFR5cGVJbmZvEhAKCGRlY2xhcmVkGAEgASgJEhMKC3Jlc29sdmVkX2lkGAIgASgJEhUKDXJlc29sdmVkX2tpbmQYAyABKAkSEQoJcHJpbWl0aXZlGAQgASgJEhgKEHByaW1pdGl2ZV9zb3VyY2UYBSABKAkSEAoIcXVhbnRpdHkYBiABKAgSDAoEdW5pdBgHIAEoCSIwChBNdWx0aXBsaWNpdHlJbmZvEg0KBWxvd2VyGAEgASgJEg0KBXVwcGVyGAIgASgJIlYKDUF0dHJpYnV0ZUluZm8SDAoEbmFtZRgBIAEoCRIMCgR0eXBlGAIgASgJEhsKBXZhbHVlGAMgASgLMgwuc3lzbWwuVmFsdWUSDAoEdW5pdBgEIAEoCSKHBAoFVmFsdWUSEwoJaW50X3ZhbHVlGAEgASgDSAASFAoKcmVhbF92YWx1ZRgCIAEoAUgAEhQKCmJvb2xfdmFsdWUYAyABKAhIABIWCgxzdHJpbmdfdmFsdWUYBCABKAlIABIVCgtpbnN0YW5jZV9pZBgFIAEoA0gAEigKCHNlcXVlbmNlGAYgASgLMhQuc3lzbWwuVmFsdWVTZXF1ZW5jZUgAEg4KBG51bGwYByABKAlIABIjCghxdWFudGl0eRgIIAEoCzIPLnN5c21sLlF1YW50aXR5SAASKgoMZW51bV9saXRlcmFsGAkgASgLMhIuc3lzbWwuRW51bUxpdGVyYWxIABIPCgV1bnNldBgKIAEoCEgAEiEKB2NvbXBsZXgYCyABKAsyDi5zeXNtbC5Db21wbGV4SAASHQoFYXJyYXkYDCABKAsyDC5zeXNtbC5BcnJheUgAEh8KBnZlY3RvchgNIAEoCzINLnN5c21sLlZlY3RvckgAEjAKD3ZlY3Rvcl9xdWFudGl0eRgOIAEoCzIVLnN5c21sLlZlY3RvclF1YW50aXR5SAASMAoPbWVhc3VyZW1lbnRfcmVmGA8gASgLMhUuc3lzbWwuTWVhc3VyZW1lbnRSZWZIABIjCghmdW5jdGlvbhgQIAEoCzIPLnN5c21sLkZ1bmN0aW9uSABCBgoEa2luZCIsCghGdW5jdGlvbhIPCgdjYWxjX2lkGAEgASgJEg8KB3NlbGZfaWQYAiABKAMiOwoFQXJyYXkSEgoKZGltZW5zaW9ucxgBIAMoAxIeCghlbGVtZW50cxgCIAMoCzIMLnN5c21sLlZhbHVlIioKBlZlY3RvchIgCgpjb21wb25lbnRzGAEgAygLMgwuc3lzbWwuVmFsdWUiNQoOVmVjdG9yUXVhbnRpdHkSIwoKY29tcG9uZW50cxgBIAMoCzIPLnN5c21sLlF1YW50aXR5IioKB0NvbXBsZXgSDAoEcmVhbBgBIAEoARIRCglpbWFnaW5hcnkYAiABKAEiRwoLRW51bUxpdGVyYWwSEgoKbGl0ZXJhbF9pZBgBIAEoCRIWCg5lbnVtZXJhdGlvbl9pZBgCIAEoCRIMCgRuYW1lGAMgASgJIi8KDVZhbHVlU2VxdWVuY2USHgoIZWxlbWVudHMYASADKAsyDC5zeXNtbC5WYWx1ZSJ8CghRdWFudGl0eRIXCg1pbnRfbWFnbml0dWRlGAEgASgDSAASGAoOcmVhbF9tYWduaXR1ZGUYAiABKAFIABIMCgR1bml0GAMgASgJEiIKCXVuaXRfdGVybRgEIAEoCzIPLnN5c21sLlVuaXRUZXJtQgsKCW1hZ25pdHVkZSJTCg5NZWFzdXJlbWVudFJlZhIMCgR1bml0GAEgASgJEiIKCXVuaXRfdGVybRgCIAEoCzIPLnN5c21sLlVuaXRUZXJtEg8KB3VuaXRfaWQYAyABKAkiVAoIVW5pdFRlcm0SEQoJc2NhbGVfbnVtGAEgASgBEhEKCXNjYWxlX2RlbhgCIAEoARIiCgdmYWN0b3JzGAMgAygLMhEuc3lzbWwuVW5pdEZhY3RvciIvCgpVbml0RmFjdG9yEg8KB3VuaXRfaWQYASABKAkSEAoIZXhwb25lbnQYAiABKAEiSgoKRGlhZ25vc3RpYxIQCghzZXZlcml0eRgBIAEoCRIPCgdtZXNzYWdlGAIgASgJEhkKBHNwYW4YAyABKAsyCy5zeXNtbC5TcGFuIl4KBFNwYW4SDAoEZmlsZRgBIAEoCRISCgpzdGFydF9saW5lGAIgASgFEhEKCXN0YXJ0X2NvbBgDIAEoBRIQCghlbmRfbGluZRgEIAEoBRIPCgdlbmRfY29sGAUgASgFIhMKEVNlcnZlckluZm9SZXF1ZXN0IjsKElNlcnZlckluZm9SZXNwb25zZRIPCgd2ZXJzaW9uGAEgASgJEhQKDGNhcGFiaWxpdGllcxgCIAMoCSJTCgxRdWVyeVJlcXVlc3QSEgoKbW9kZWxfaGFzaBgBIAEoCRIbCgVxdWVyeRgCIAEoCzIMLnN5c21sLlF1ZXJ5EhIKCm9zbGNfcXVlcnkYAyABKAkiPAoNUXVlcnlSZXNwb25zZRIrCghlbGVtZW50cxgBIAMoCzIZLnN5c21sLlF1ZXJ5UmVzdWx0RWxlbWVudCJICgVRdWVyeRINCgVzY29wZRgBIAMoCRIOCgZzZWxlY3QYAiADKAkSIAoFd2hlcmUYAyABKAsyES5zeXNtbC5Db25zdHJhaW50InwKCkNvbnN0cmFpbnQSLwoJcHJpbWl0aXZlGAEgASgLMhouc3lzbWwuUHJpbWl0aXZlQ29uc3RyYWludEgAEi8KCWNvbXBvc2l0ZRgCIAEoCzIaLnN5c21sLkNvbXBvc2l0ZUNvbnN0cmFpbnRIAEIMCgpjb25zdHJhaW50InMKE1ByaW1pdGl2ZUNvbnN0cmFpbnQSDwoHaW52ZXJzZRgBIAEoCBIQCghwcm9wZXJ0eRgCIAEoCRIqCghvcGVyYXRvchgDIAEoDjIYLnN5c21sLlByaW1pdGl2ZU9wZXJhdG9yEg0KBXZhbHVlGAQgAygJImgKE0NvbXBvc2l0ZUNvbnN0cmFpbnQSKgoIb3BlcmF0b3IYASABKA4yGC5zeXNtbC5Db21wb3NpdGVPcGVyYXRvchIlCgpjb25zdHJhaW50GAIgAygLMhEuc3lzbWwuQ29uc3RyYWludCKgAQoSUXVlcnlSZXN1bHRFbGVtZW50EgoKAmlkGAEgASgJEgwKBHR5cGUYAiABKAkSPQoKcHJvcGVydGllcxgDIAMoCzIpLnN5c21sLlF1ZXJ5UmVzdWx0RWxlbWVudC5Qcm9wZXJ0aWVzRW50cnkaMQoPUHJvcGVydGllc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEibgoXUnVuRG9jdW1lbnRRdWVyeVJlcXVlc3QSEgoKbW9kZWxfaGFzaBgBIAEoCRIQCghxdWVyeV9pZBgCIAEoCRItCghiaW5kaW5ncxgDIAMoCzIbLnN5c21sLkRvY3VtZW50UXVlcnlCaW5kaW5nIk8KFERvY3VtZW50UXVlcnlCaW5kaW5nEhEKCXBhcmFtZXRlchgBIAEoCRIkCgZ2YWx1ZXMYAiADKAsyFC5zeXNtbC5Eb2N1bWVudFZhbHVlItUBCg1Eb2N1bWVudFZhbHVlEhQKCmVsZW1lbnRfaWQYASABKAlIABIWCgxzdHJpbmdfdmFsdWUYAiABKAlIABITCglpbnRfdmFsdWUYAyABKANIABIUCgpyZWFsX3ZhbHVlGAQgASgBSAASFAoKYm9vbF92YWx1ZRgFIAEoCEgAEhIKCGluZmluaXR5GAYgASgISAASIwoIcXVhbnRpdHkYCCABKAsyDy5zeXNtbC5RdWFudGl0eUgAEhQKDGVsZW1lbnRfdHlwZRgHIAEoCUIGCgRraW5kIiMKE0RvY3VtZW50UXVlcnlDb2x1bW4SDAoEbmFtZRgBIAEoCSI5ChFEb2N1bWVudFF1ZXJ5Q2VsbBIkCgZ2YWx1ZXMYASADKAsyFC5zeXNtbC5Eb2N1bWVudFZhbHVlImIKEERvY3VtZW50UXVlcnlSb3cSJQoHZWxlbWVudBgBIAEoCzIULnN5c21sLkRvY3VtZW50VmFsdWUSJwoFY2VsbHMYAiADKAsyGC5zeXNtbC5Eb2N1bWVudFF1ZXJ5Q2VsbCJuChhSdW5Eb2N1bWVudFF1ZXJ5UmVzcG9uc2USKwoHY29sdW1ucxgBIAMoCzIaLnN5c21sLkRvY3VtZW50UXVlcnlDb2x1bW4SJQoEcm93cxgCIAMoCzIXLnN5c21sLkRvY3VtZW50UXVlcnlSb3ciQAoVUmVuZGVyRG9jdW1lbnRSZXF1ZXN0EhIKCm1vZGVsX2hhc2gYASABKAkSEwoLZG9jdW1lbnRfaWQYAiABKAkiKgoWUmVuZGVyRG9jdW1lbnRSZXNwb25zZRIQCghtYXJrZG93bhgBIAEoCSqTAQoNRmFpbHVyZVJlYXNvbhIeChpGQUlMVVJFX1JFQVNPTl9VTlNQRUNJRklFRBAAEh0KGUZBSUxVUkVfUkVBU09OX0VWQUxVQVRJT04QARIdChlGQUlMVVJFX1JFQVNPTl9XUk9OR19LSU5EEAISJAogRkFJTFVSRV9SRUFTT05fQU1CSUdVT1VTX1NVQkpFQ1QQAyqdBAoLRWRpdEZhaWx1cmUSHAoYRURJVF9GQUlMVVJFX1VOU1BFQ0lGSUVEEAASHgoaRURJVF9GQUlMVVJFX05PX09QRVJBVElPTlMQARIfChtFRElUX0ZBSUxVUkVfVU5LTk9XTl9UQVJHRVQQAhIhCh1FRElUX0ZBSUxVUkVfQU1CSUdVT1VTX1RBUkdFVBADEhsKF0VESVRfRkFJTFVSRV9OT1RfVkFMVUVEEAQSHgoaRURJVF9GQUlMVVJFX0lOVkFMSURfVkFMVUUQBRIdChlFRElUX0ZBSUxVUkVfSU5WQUxJRF9OQU1FEAYSGgoWRURJVF9GQUlMVVJFX05PVF9OQU1FRBAHEiIKHkVESVRfRkFJTFVSRV9SRU5BTUVfUkVGRVJFTkNFRBAIEiIKHkVESVRfRkFJTFVSRV9PVkVSTEFQUElOR19FRElUUxAJEh8KG0VESVRfRkFJTFVSRV9SRVNVTFRfSU5WQUxJRBAKEh4KGkVESVRfRkFJTFVSRV9PV05FUl9VTktOT1dOEAsSJAogRURJVF9GQUlMVVJFX09XTkVSX05PVF9OQU1FU1BBQ0UQDBIdChlFRElUX0ZBSUxVUkVfSUxMRUdBTF9LSU5EEA0SIgoeRURJVF9GQUlMVVJFX01FTUJFUl9OQU1FX1RBS0VOEA4SIgoeRURJVF9GQUlMVVJFX0RFTEVURV9SRUZFUkVOQ0VEEA8qkgEKEVByaW1pdGl2ZU9wZXJhdG9yEiIKHlBSSU1JVElWRV9PUEVSQVRPUl9VTlNQRUNJRklFRBAAEhwKGFBSSU1JVElWRV9PUEVSQVRPUl9FUVVBTBABEh4KGlBSSU1JVElWRV9PUEVSQVRPUl9HUkVBVEVSEAISGwoXUFJJTUlUSVZFX09QRVJBVE9SX0xFU1MQAypuChFDb21wb3NpdGVPcGVyYXRvchIiCh5DT01QT1NJVEVfT1BFUkFUT1JfVU5TUEVDSUZJRUQQABIaChZDT01QT1NJVEVfT1BFUkFUT1JfQU5EEAESGQoVQ09NUE9TSVRFX09QRVJBVE9SX09SEAIy5woKDFN5c01MU2VydmljZRJECg1HZXRTZXJ2ZXJJbmZvEhguc3lzbWwuU2VydmVySW5mb1JlcXVlc3QaGS5zeXNtbC5TZXJ2ZXJJbmZvUmVzcG9uc2USPgoJUGFyc2VGaWxlEhcuc3lzbWwuUGFyc2VGaWxlUmVxdWVzdBoYLnN5c21sLlBhcnNlRmlsZVJlc3BvbnNlEkcKDFBhcnNlU291cmNlcxIaLnN5c21sLlBhcnNlU291cmNlc1JlcXVlc3QaGy5zeXNtbC5QYXJzZVNvdXJjZXNSZXNwb25zZRI7CglHZXRTeW1ib2wSFy5zeXNtbC5HZXRTeW1ib2xSZXF1ZXN0GhUuc3lzbWwuU3ltYm9sUmVzcG9uc2USRwoOR2V0RGlhZ25vc3RpY3MSGS5zeXNtbC5EaWFnbm9zdGljc1JlcXVlc3QaGi5zeXNtbC5EaWFnbm9zdGljc1Jlc3BvbnNlEjsKCEV2YWx1YXRlEhYuc3lzbWwuRXZhbHVhdGVSZXF1ZXN0Ghcuc3lzbWwuRXZhbHVhdGVSZXNwb25zZRJECgtJbnN0YW50aWF0ZRIZLnN5c21sLkluc3RhbnRpYXRlUmVxdWVzdBoaLnN5c21sLkluc3RhbnRpYXRlUmVzcG9uc2USSgoNRXhlY3V0ZUFjdGlvbhIbLnN5c21sLkV4ZWN1dGVBY3Rpb25SZXF1ZXN0Ghwuc3lzbWwuRXhlY3V0ZUFjdGlvblJlc3BvbnNlEkcKDEV4ZWN1dGVTdGF0ZRIaLnN5c21sLkV4ZWN1dGVTdGF0ZVJlcXVlc3QaGy5zeXNtbC5FeGVjdXRlU3RhdGVSZXNwb25zZRI4CgdDb252ZXJ0EhUuc3lzbWwuQ29udmVydFJlcXVlc3QaFi5zeXNtbC5Db252ZXJ0UmVzcG9uc2USQQoKQXBwbHlFZGl0cxIYLnN5c21sLkFwcGx5RWRpdHNSZXF1ZXN0Ghkuc3lzbWwuQXBwbHlFZGl0c1Jlc3BvbnNlElMKEFZlcmlmeUNvbnN0cmFpbnQSHi5zeXNtbC5WZXJpZnlDb25zdHJhaW50UmVxdWVzdBofLnN5c21sLlZlcmlmeUNvbnN0cmFpbnRSZXNwb25zZRJWChFWZXJpZnlSZXF1aXJlbWVudBIfLnN5c21sLlZlcmlmeVJlcXVpcmVtZW50UmVxdWVzdBogLnN5c21sLlZlcmlmeVJlcXVpcmVtZW50UmVzcG9uc2USWQoSVmVyaWZ5U2F0aXNmYWN0aW9uEiAuc3lzbWwuVmVyaWZ5U2F0aXNmYWN0aW9uUmVxdWVzdBohLnN5c21sLlZlcmlmeVNhdGlzZmFjdGlvblJlc3BvbnNlEkcKDEV2YWx1YXRlQ2FsYxIaLnN5c21sLkV2YWx1YXRlQ2FsY1JlcXVlc3QaGy5zeXNtbC5FdmFsdWF0ZUNhbGNSZXNwb25zZRJECgtSdW5BbmFseXNpcxIZLnN5c21sLlJ1bkFuYWx5c2lzUmVxdWVzdBoaLnN5c21sLlJ1bkFuYWx5c2lzUmVzcG9uc2USMgoFUXVlcnkSEy5zeXNtbC5RdWVyeVJlcXVlc3QaFC5zeXNtbC5RdWVyeVJlc3BvbnNlElMKEFJ1bkRvY3VtZW50UXVlcnkSHi5zeXNtbC5SdW5Eb2N1bWVudFF1ZXJ5UmVxdWVzdBofLnN5c21sLlJ1bkRvY3VtZW50UXVlcnlSZXNwb25zZRJNCg5SZW5kZXJEb2N1bWVudBIcLnN5c21sLlJlbmRlckRvY3VtZW50UmVxdWVzdBodLnN5c21sLlJlbmRlckRvY3VtZW50UmVzcG9uc2VCKlooZ2l0aHViLmNvbS9PcGVuLU1CRUUvT3BlblN5c01ML2FwaS9wcm90b2IGcHJvdG8z"); /** * Verdict is one verification's answer: whether the condition held, and, when @@ -2029,6 +2029,14 @@ export type Value = Message<"sysml.Value"> & { */ value: MeasurementRef; case: "measurementRef"; + } | { + /** + * a calc as a value, named by its declaration + * + * @generated from field: sysml.Function function = 16; + */ + value: Function; + case: "function"; } | { case: undefined; value?: undefined }; }; @@ -2039,6 +2047,42 @@ export type Value = Message<"sysml.Value"> & { export const ValueSchema: GenMessage = /*@__PURE__*/ messageDesc(file_sysml, 46); +/** + * Function is a calc held as a value: a calc definition, or a calc usage with + * an input no read could supply, as `Sq` in `Fn(Sq, 3.0)` or the `f` of + * `in calc f {...}`. It crosses as the declaration it is a value of, which is + * its identity: two functions are the same exactly when calc_id and self_id + * are. A function closing over the bindings of the behavior body it is + * declared in has no wire form and crosses as the null arm. + * + * @generated from message sysml.Function + */ +export type Function = Message<"sysml.Function"> & { + /** + * FQN of the calc declaration ("Analysis::Sq"). Its identity. + * + * @generated from field: string calc_id = 1; + */ + calcId: string; + + /** + * ID of the object the calc's feature names resolve against, for a calc + * usage read off a part (`holder.scale`); 0 for a function closing over no + * object. Sent by the service; a client sending one must name an object of + * the runtime the value is read in, or the value is rejected. + * + * @generated from field: int64 self_id = 2; + */ + selfId: bigint; +}; + +/** + * Describes the message sysml.Function. + * Use `create(FunctionSchema)` to create a new message. + */ +export const FunctionSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_sysml, 47); + /** * Array is a Collections::Array: its elements flattened in row-major order * under its dimensions, compared by content rather than by the object read. @@ -2067,7 +2111,7 @@ export type Array = Message<"sysml.Array"> & { * Use `create(ArraySchema)` to create a new message. */ export const ArraySchema: GenMessage = /*@__PURE__*/ - messageDesc(file_sysml, 47); + messageDesc(file_sysml, 48); /** * Vector is a VectorValues::NumericalVectorValue: its components in order, @@ -2090,7 +2134,7 @@ export type Vector = Message<"sysml.Vector"> & { * Use `create(VectorSchema)` to create a new message. */ export const VectorSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_sysml, 48); + messageDesc(file_sysml, 49); /** * VectorQuantity is a Quantities::VectorQuantityValue: one Quantity per axis, @@ -2113,7 +2157,7 @@ export type VectorQuantity = Message<"sysml.VectorQuantity"> & { * Use `create(VectorQuantitySchema)` to create a new message. */ export const VectorQuantitySchema: GenMessage = /*@__PURE__*/ - messageDesc(file_sysml, 49); + messageDesc(file_sysml, 50); /** * Complex is one complex number in rectangular form. It crosses as one value @@ -2138,7 +2182,7 @@ export type Complex = Message<"sysml.Complex"> & { * Use `create(ComplexSchema)` to create a new message. */ export const ComplexSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_sysml, 50); + messageDesc(file_sysml, 51); /** * EnumLiteral is one literal of an enumeration definition. A literal is its own @@ -2175,7 +2219,7 @@ export type EnumLiteral = Message<"sysml.EnumLiteral"> & { * Use `create(EnumLiteralSchema)` to create a new message. */ export const EnumLiteralSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_sysml, 51); + messageDesc(file_sysml, 52); /** * @generated from message sysml.ValueSequence @@ -2192,7 +2236,7 @@ export type ValueSequence = Message<"sysml.ValueSequence"> & { * Use `create(ValueSequenceSchema)` to create a new message. */ export const ValueSequenceSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_sysml, 52); + messageDesc(file_sysml, 53); /** * Quantity is a magnitude and the measurement reference it is expressed in, sent @@ -2243,7 +2287,7 @@ export type Quantity = Message<"sysml.Quantity"> & { * Use `create(QuantitySchema)` to create a new message. */ export const QuantitySchema: GenMessage = /*@__PURE__*/ - messageDesc(file_sysml, 53); + messageDesc(file_sysml, 54); /** * MeasurementRef is a MeasurementReferences::ScalarMeasurementReference held as @@ -2291,7 +2335,7 @@ export type MeasurementRef = Message<"sysml.MeasurementRef"> & { * Use `create(MeasurementRefSchema)` to create a new message. */ export const MeasurementRefSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_sysml, 54); + messageDesc(file_sysml, 55); /** * UnitTerm is a unit reduced to a scale factor over base units: `km/h` reduces @@ -2325,7 +2369,7 @@ export type UnitTerm = Message<"sysml.UnitTerm"> & { * Use `create(UnitTermSchema)` to create a new message. */ export const UnitTermSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_sysml, 55); + messageDesc(file_sysml, 56); /** * UnitFactor is one base unit raised to an exponent. @@ -2351,7 +2395,7 @@ export type UnitFactor = Message<"sysml.UnitFactor"> & { * Use `create(UnitFactorSchema)` to create a new message. */ export const UnitFactorSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_sysml, 56); + messageDesc(file_sysml, 57); /** * Diagnostic represents a parse/semantic error or warning @@ -2382,7 +2426,7 @@ export type Diagnostic = Message<"sysml.Diagnostic"> & { * Use `create(DiagnosticSchema)` to create a new message. */ export const DiagnosticSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_sysml, 57); + messageDesc(file_sysml, 58); /** * Span represents a source location @@ -2421,7 +2465,7 @@ export type Span = Message<"sysml.Span"> & { * Use `create(SpanSchema)` to create a new message. */ export const SpanSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_sysml, 58); + messageDesc(file_sysml, 59); /** * ServerInfoRequest asks the service to describe itself. It carries no fields; @@ -2437,7 +2481,7 @@ export type ServerInfoRequest = Message<"sysml.ServerInfoRequest"> & { * Use `create(ServerInfoRequestSchema)` to create a new message. */ export const ServerInfoRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_sysml, 59); + messageDesc(file_sysml, 60); /** * ServerInfoResponse describes the running service. @@ -2494,6 +2538,11 @@ export type ServerInfoResponse = Message<"sysml.ServerInfoResponse"> & { * refused with UNIMPLEMENTED rather than read as another * value. Separate from structured_values, which a client * built before this arm existed may already claim. + * "function_values" - a Value carries a calc held as a value as function, + * named by its declaration, rather than reporting it as an + * unsupported null, and one is accepted as an action input + * or calc argument; without it, one is refused with + * UNIMPLEMENTED rather than read as another value. * "apply_edits" - the ApplyEdits RPC edits a parsed model's own source, * preserving everything the edit did not touch. * "document_query" - the RunDocumentQuery RPC runs a named document query @@ -2511,7 +2560,7 @@ export type ServerInfoResponse = Message<"sysml.ServerInfoResponse"> & { * Use `create(ServerInfoResponseSchema)` to create a new message. */ export const ServerInfoResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_sysml, 60); + messageDesc(file_sysml, 61); /** * QueryRequest runs a Query against a model the service already parsed. @@ -2544,7 +2593,7 @@ export type QueryRequest = Message<"sysml.QueryRequest"> & { * Use `create(QueryRequestSchema)` to create a new message. */ export const QueryRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_sysml, 61); + messageDesc(file_sysml, 62); /** * QueryResponse contains the elements the query selected, in the order they are @@ -2566,7 +2615,7 @@ export type QueryResponse = Message<"sysml.QueryResponse"> & { * Use `create(QueryResponseSchema)` to create a new message. */ export const QueryResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_sysml, 62); + messageDesc(file_sysml, 63); /** * Query is the standard's Query resource (SysML v2 API & Services). Its `@type` @@ -2606,7 +2655,7 @@ export type Query = Message<"sysml.Query"> & { * Use `create(QuerySchema)` to create a new message. */ export const QuerySchema: GenMessage = /*@__PURE__*/ - messageDesc(file_sysml, 63); + messageDesc(file_sysml, 64); /** * Constraint is the standard's Constraint, whose `@type` discriminates between @@ -2638,7 +2687,7 @@ export type Constraint = Message<"sysml.Constraint"> & { * Use `create(ConstraintSchema)` to create a new message. */ export const ConstraintSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_sysml, 64); + messageDesc(file_sysml, 65); /** * PrimitiveConstraint compares one property of an element against a value. @@ -2681,7 +2730,7 @@ export type PrimitiveConstraint = Message<"sysml.PrimitiveConstraint"> & { * Use `create(PrimitiveConstraintSchema)` to create a new message. */ export const PrimitiveConstraintSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_sysml, 65); + messageDesc(file_sysml, 66); /** * CompositeConstraint combines constraints. An empty constraint list fails the @@ -2706,7 +2755,7 @@ export type CompositeConstraint = Message<"sysml.CompositeConstraint"> & { * Use `create(CompositeConstraintSchema)` to create a new message. */ export const CompositeConstraintSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_sysml, 66); + messageDesc(file_sysml, 67); /** * QueryResultElement is one matched element. `id` and `type` are always @@ -2742,7 +2791,7 @@ export type QueryResultElement = Message<"sysml.QueryResultElement"> & { * Use `create(QueryResultElementSchema)` to create a new message. */ export const QueryResultElementSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_sysml, 67); + messageDesc(file_sysml, 68); /** * RunDocumentQueryRequest runs a named document query — a calc def @@ -2782,7 +2831,7 @@ export type RunDocumentQueryRequest = Message<"sysml.RunDocumentQueryRequest"> & * Use `create(RunDocumentQueryRequestSchema)` to create a new message. */ export const RunDocumentQueryRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_sysml, 68); + messageDesc(file_sysml, 69); /** * DocumentQueryBinding binds one entry parameter of a document query. @@ -2806,7 +2855,7 @@ export type DocumentQueryBinding = Message<"sysml.DocumentQueryBinding"> & { * Use `create(DocumentQueryBindingSchema)` to create a new message. */ export const DocumentQueryBindingSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_sysml, 69); + messageDesc(file_sysml, 70); /** * DocumentValue is one typed document-query value. A request binds a model @@ -2881,7 +2930,7 @@ export type DocumentValue = Message<"sysml.DocumentValue"> & { * Use `create(DocumentValueSchema)` to create a new message. */ export const DocumentValueSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_sysml, 70); + messageDesc(file_sysml, 71); /** * DocumentQueryColumn is one projected property, in projection order. @@ -2900,7 +2949,7 @@ export type DocumentQueryColumn = Message<"sysml.DocumentQueryColumn"> & { * Use `create(DocumentQueryColumnSchema)` to create a new message. */ export const DocumentQueryColumnSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_sysml, 71); + messageDesc(file_sysml, 72); /** * DocumentQueryCell is one row's values for one column, in the query's order. @@ -2919,7 +2968,7 @@ export type DocumentQueryCell = Message<"sysml.DocumentQueryCell"> & { * Use `create(DocumentQueryCellSchema)` to create a new message. */ export const DocumentQueryCellSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_sysml, 72); + messageDesc(file_sysml, 73); /** * DocumentQueryRow is one selected element and its projected cells, one per @@ -2946,7 +2995,7 @@ export type DocumentQueryRow = Message<"sysml.DocumentQueryRow"> & { * Use `create(DocumentQueryRowSchema)` to create a new message. */ export const DocumentQueryRowSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_sysml, 73); + messageDesc(file_sysml, 74); /** * RunDocumentQueryResponse is the query's answer: its projected columns and its @@ -2973,7 +3022,7 @@ export type RunDocumentQueryResponse = Message<"sysml.RunDocumentQueryResponse"> * Use `create(RunDocumentQueryResponseSchema)` to create a new message. */ export const RunDocumentQueryResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_sysml, 74); + messageDesc(file_sysml, 75); /** * RenderDocumentRequest renders a named document — a part def specializing @@ -3004,7 +3053,7 @@ export type RenderDocumentRequest = Message<"sysml.RenderDocumentRequest"> & { * Use `create(RenderDocumentRequestSchema)` to create a new message. */ export const RenderDocumentRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_sysml, 75); + messageDesc(file_sysml, 76); /** * RenderDocumentResponse carries the rendered Markdown, byte-for-byte what the @@ -3024,7 +3073,7 @@ export type RenderDocumentResponse = Message<"sysml.RenderDocumentResponse"> & { * Use `create(RenderDocumentResponseSchema)` to create a new message. */ export const RenderDocumentResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_sysml, 76); + messageDesc(file_sysml, 77); /** * FailureReason says what kind of failure an `error` reports, so a client acts diff --git a/clients/node/test/client.test.ts b/clients/node/test/client.test.ts index b614f66ab..7fa2c31f5 100644 --- a/clients/node/test/client.test.ts +++ b/clients/node/test/client.test.ts @@ -9,6 +9,7 @@ import { join } from "node:path"; import { after, before, test } from "node:test"; import { CAPABILITY_COMPLEX_VALUES, + CAPABILITY_FUNCTION_VALUES, CAPABILITY_MEASUREMENT_REFS, CAPABILITY_QUERY, CAPABILITY_STRUCTURED_VALUES, @@ -218,6 +219,37 @@ test("a bare measurement reference arrives as a unit with its reduction and decl } }); +const FUNCTION_MODEL = `package Demo { + private import ScalarValues::*; + calc def Sq { in v : Real; return : Real = v * v; } + calc def Fn { in calc f { in v : Real; return : Real; } in a : Real; return : Real = f(a); } + calc def Identity { in calc f { in v : Real; return : Real; } return r = f; } + attribute pick = Identity(Sq); + attribute nine = Fn(Sq, 3.0); + part def Scaler { + attribute k : Real = 2.0; + calc scale { in x : Real; return : Real = x * k; } + } + part holder : Scaler; + attribute scaler = holder.scale; +}`; + +test("a calc held as a value arrives as the function it names, with the object it was read off", async () => { + for (const options of [{ protocol: "grpc" as const }, {}, { encoding: "json" as const }]) { + await using connection = await connect(options); + assert.ok((await connection.serverInfo()).has(CAPABILITY_FUNCTION_VALUES)); + await using model = await connection.loads(FUNCTION_MODEL); + + assert.deepEqual(await model.eval("Demo::pick"), { kind: "function", calcId: "Demo::Sq" }); + assert.deepEqual(await model.eval("Demo::nine"), { kind: "real", value: 9 }); + const scale = await model.eval("Demo::scaler"); + assert.ok(scale.kind === "function"); + assert.equal(scale.calcId, "Demo::Scaler::scale"); + assert.ok(scale.selfId !== undefined && scale.selfId > 0n); + assert.equal(formatValue(scale), "Demo::Scaler::scale"); + } +}); + test("a file parses, and a syntax error is a diagnostic, not a thrown call", async () => { const dir = mkdtempSync(join(tmpdir(), "client-test-")); const path = join(dir, "sample.sysml"); diff --git a/clients/node/test/values.test.ts b/clients/node/test/values.test.ts index 0d01ace11..c7fde26d4 100644 --- a/clients/node/test/values.test.ts +++ b/clients/node/test/values.test.ts @@ -8,6 +8,7 @@ import { ComplexSchema, EnumLiteralSchema, FailureReason, + FunctionSchema, MeasurementRefSchema, QuantitySchema, UnitFactorSchema, @@ -332,6 +333,28 @@ test("a measurement reference keeps its unit, its reduction and the declaration assert.throws(() => decodeValue(nested), MalformedValueError); }); +test("a function is the calc it names, read against an object or none", () => { + const fn = (calcId: string, selfId: bigint) => + create(ValueSchema, { + kind: { case: "function", value: create(FunctionSchema, { calcId, selfId }) }, + }); + assert.deepEqual(decodeValue(fn("Demo::Sq", 0n)), { kind: "function", calcId: "Demo::Sq" }); + const scale = decodeValue(fn("Demo::Scaler::scale", 7n)); + assert.deepEqual(scale, { kind: "function", calcId: "Demo::Scaler::scale", selfId: 7n }); + assert.equal(formatValue(scale), "Demo::Scaler::scale"); + + // A function naming no calc is malformed, at any depth. + assert.throws( + () => decodeValue(fn("", 0n)), + (error: unknown) => error instanceof MalformedValueError && /names no calc/.test(error.message), + ); + const nested = create(ValueSchema, { + kind: { case: "sequence", value: create(ValueSequenceSchema, { elements: [fn("", 3n)] }) }, + }); + assert.throws(() => decodeValue(nested), MalformedValueError); + assert.throws(() => encodeValue({ kind: "function", calcId: "" }), MalformedValueError); +}); + test("encodeValue is the inverse of decodeValue, through the wire bytes", () => { const values: SysMLValue[] = [ { kind: "int", value: 9007199254740993n }, @@ -356,6 +379,8 @@ test("encodeValue is the inverse of decodeValue, through the wire bytes", () => }, }, { kind: "sequence", elements: [{ kind: "measurementRef", unit: "m", unitTerm: METRE, unitId: "SI::metre" }] }, + { kind: "function", calcId: "Demo::Sq" }, + { kind: "function", calcId: "Demo::Scaler::scale", selfId: 7n }, { kind: "enum", value: { name: "red", literalId: "P::Color::red", enumerationId: "P::Color" } }, { kind: "null", reason: "" }, { kind: "unset" }, diff --git a/clients/python/opensysml/__init__.py b/clients/python/opensysml/__init__.py index 1fb4c46a3..f951d59a3 100644 --- a/clients/python/opensysml/__init__.py +++ b/clients/python/opensysml/__init__.py @@ -23,7 +23,7 @@ TypeFacts, ) from opensysml.capabilities import MissingCapabilityError, ServerInfo -from opensysml.values import UNSET, Array, MeasurementRef, UnsetType, Vector, VectorQuantity +from opensysml.values import UNSET, Array, Function, MeasurementRef, UnsetType, Vector, VectorQuantity from opensysml.verdict import AnalysisResult, CalcResult, Verdict from opensysml.query import QueryElement, QueryError from opensysml.document import ( @@ -54,7 +54,7 @@ "AttributeFacts", "ServerInfo", "UNSET", "UnsetType", - "Array", "Vector", "VectorQuantity", "MeasurementRef", + "Array", "Vector", "VectorQuantity", "MeasurementRef", "Function", "Conversion", "FORMAT_SYSML", "FORMAT_TURTLE", "format_of_path", "ExperimentalFeatureWarning", "is_experimental", "Editor", "EditResult", "AppliedEdit", diff --git a/clients/python/opensysml/capabilities.py b/clients/python/opensysml/capabilities.py index 60c715ee6..d722b8264 100644 --- a/clients/python/opensysml/capabilities.py +++ b/clients/python/opensysml/capabilities.py @@ -100,6 +100,13 @@ #: it with ``UNIMPLEMENTED``. CAPABILITY_MEASUREMENT_REFS = "measurement_refs" +#: A calc held as a value — a calc definition, or a calc usage with an input no +#: read could supply — as ``Value.function``, named by its declaration and read +#: as :class:`~opensysml.values.Function`. Without it the service sends an +#: unsupported null naming the calc, which is an error, and refuses one sent to +#: it with ``UNIMPLEMENTED``. +CAPABILITY_FUNCTION_VALUES = "function_values" + @dataclass(frozen=True) class ServerInfo: diff --git a/clients/python/opensysml/connection.py b/clients/python/opensysml/connection.py index 22812092d..eab2575b8 100644 --- a/clients/python/opensysml/connection.py +++ b/clients/python/opensysml/connection.py @@ -21,6 +21,7 @@ CAPABILITY_DOCUMENT_QUERY, CAPABILITY_EVALUATE_SUBJECT, CAPABILITY_FEATURE_VALUES, + CAPABILITY_FUNCTION_VALUES, CAPABILITY_MEASUREMENT_REFS, CAPABILITY_QUERY, CAPABILITY_RENDER_DOCUMENT, @@ -58,6 +59,7 @@ from opensysml.query import build_query, elements_of from opensysml.values import ( Array, + Function, MeasurementRef, Quantity, Vector, @@ -1156,7 +1158,8 @@ def execute_action(self, action_symbol_id, model_hash, inputs=None): :class:`~opensysml.values.Vector` or :class:`~opensysml.values.VectorQuantity` and the service predates ``structured_values``, or a :class:`~opensysml.values.MeasurementRef` and the service predates - ``measurement_refs``; nothing is sent + ``measurement_refs``, or a :class:`~opensysml.values.Function` + and the service predates ``function_values``; nothing is sent """ # Convert Python inputs to protobuf Values pb_inputs = {name: self._python_to_value(val) for name, val in (inputs or {}).items()} @@ -1346,8 +1349,9 @@ def calc(self, symbol_id, model_hash, arguments=None): MissingCapabilityError: If the service cannot verify, or an argument holds a ``complex`` and the service predates ``complex_values``, an array, vector or vector quantity and - the service predates ``structured_values``, or a measurement - reference and the service predates ``measurement_refs``; nothing + the service predates ``structured_values``, a measurement + reference and the service predates ``measurement_refs``, or a + function and the service predates ``function_values``; nothing is sent ModelNotFoundError: If the service no longer holds the model """ @@ -1363,6 +1367,7 @@ def calc(self, symbol_id, model_hash, arguments=None): CAPABILITY_COMPLEX_VALUES, CAPABILITY_STRUCTURED_VALUES, CAPABILITY_MEASUREMENT_REFS, + CAPABILITY_FUNCTION_VALUES, )) ): response = self._stub.EvaluateCalc(request) @@ -1518,6 +1523,14 @@ def _require_measurement_refs(self): upgrade_remedy(CAPABILITY_MEASUREMENT_REFS), ) + def _require_function_values(self): + """Refuse to send a function a service without ``function_values`` would read as null.""" + require( + self.server_info(), + CAPABILITY_FUNCTION_VALUES, + upgrade_remedy(CAPABILITY_FUNCTION_VALUES), + ) + def _require_feature_values(self): """Refuse instances from a service that populates only the removed `slots` field.""" require( @@ -1565,6 +1578,9 @@ def _python_to_value(self, py_value): elif isinstance(py_value, MeasurementRef): self._require_measurement_refs() return sysml_pb2.Value(measurement_ref=py_value.to_pb()) + elif isinstance(py_value, Function): + self._require_function_values() + return sysml_pb2.Value(function=py_value.to_pb()) elif isinstance(py_value, Array): self._require_structured_values() return sysml_pb2.Value(array=py_value.to_pb(self._python_to_value)) diff --git a/clients/python/opensysml/proto/sysml_pb2.py b/clients/python/opensysml/proto/sysml_pb2.py index 80c55545b..b7c0f4644 100644 --- a/clients/python/opensysml/proto/sysml_pb2.py +++ b/clients/python/opensysml/proto/sysml_pb2.py @@ -24,7 +24,7 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0bsysml.proto\x12\x05sysml\"\xca\x01\n\x07Verdict\x12\x0c\n\x04kind\x18\x01 \x01(\t\x12\x12\n\nelement_id\x18\x02 \x01(\t\x12\x0f\n\x07\x65lement\x18\x03 \x01(\t\x12\r\n\x05holds\x18\x04 \x01(\x08\x12\x11\n\tcondition\x18\x05 \x01(\t\x12\x13\n\x0binstance_id\x18\x06 \x01(\x03\x12\x18\n\x10instance_type_id\x18\x07 \x01(\t\x12\r\n\x05\x65rror\x18\x08 \x01(\t\x12,\n\x0e\x66\x61ilure_reason\x18\t \x01(\x0e\x32\x14.sysml.FailureReason\"[\n\x17VerifyConstraintRequest\x12\x12\n\nmodel_hash\x18\x01 \x01(\t\x12\x11\n\tsymbol_id\x18\x02 \x01(\t\x12\x19\n\x11subject_symbol_id\x18\x03 \x01(\t\"\x96\x01\n\x18VerifyConstraintResponse\x12\x1f\n\x07verdict\x18\x01 \x01(\x0b\x32\x0e.sysml.Verdict\x12\"\n\tinstances\x18\x02 \x03(\x0b\x32\x0f.sysml.Instance\x12\r\n\x05\x65rror\x18\x03 \x01(\t\x12&\n\x0b\x64iagnostics\x18\x04 \x03(\x0b\x32\x11.sysml.Diagnostic\"\\\n\x18VerifyRequirementRequest\x12\x12\n\nmodel_hash\x18\x01 \x01(\t\x12\x11\n\tsymbol_id\x18\x02 \x01(\t\x12\x19\n\x11subject_symbol_id\x18\x03 \x01(\t\"\x97\x01\n\x19VerifyRequirementResponse\x12\x1f\n\x07verdict\x18\x01 \x01(\x0b\x32\x0e.sysml.Verdict\x12\"\n\tinstances\x18\x02 \x03(\x0b\x32\x0f.sysml.Instance\x12\r\n\x05\x65rror\x18\x03 \x01(\t\x12&\n\x0b\x64iagnostics\x18\x04 \x03(\x0b\x32\x11.sysml.Diagnostic\"B\n\x19VerifySatisfactionRequest\x12\x12\n\nmodel_hash\x18\x01 \x01(\t\x12\x11\n\tsymbol_id\x18\x02 \x01(\t\"\xc7\x01\n\x1aVerifySatisfactionResponse\x12 \n\x08verdicts\x18\x01 \x03(\x0b\x32\x0e.sysml.Verdict\x12\"\n\tinstances\x18\x02 \x03(\x0b\x32\x0f.sysml.Instance\x12\r\n\x05\x65rror\x18\x03 \x01(\t\x12&\n\x0b\x64iagnostics\x18\x04 \x03(\x0b\x32\x11.sysml.Diagnostic\x12,\n\x0e\x66\x61ilure_reason\x18\x05 \x01(\x0e\x32\x14.sysml.FailureReason\"]\n\x13\x45valuateCalcRequest\x12\x12\n\nmodel_hash\x18\x01 \x01(\t\x12\x11\n\tsymbol_id\x18\x02 \x01(\t\x12\x1f\n\targuments\x18\x03 \x03(\x0b\x32\x0c.sysml.Value\"\xbd\x01\n\x14\x45valuateCalcResponse\x12\x1c\n\x06result\x18\x01 \x01(\x0b\x32\x0c.sysml.Value\x12\"\n\x07outputs\x18\x02 \x03(\x0b\x32\x11.sysml.CalcOutput\x12\r\n\x05\x65rror\x18\x03 \x01(\t\x12&\n\x0b\x64iagnostics\x18\x04 \x03(\x0b\x32\x11.sysml.Diagnostic\x12,\n\x0e\x66\x61ilure_reason\x18\x05 \x01(\x0e\x32\x14.sysml.FailureReason\"7\n\nCalcOutput\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x1b\n\x05value\x18\x02 \x01(\x0b\x32\x0c.sysml.Value\"\x84\x02\n\x12RunAnalysisRequest\x12\x12\n\nmodel_hash\x18\x01 \x01(\t\x12\x11\n\tsymbol_id\x18\x02 \x01(\t\x12\x19\n\x11subject_symbol_id\x18\x03 \x01(\t\x12\x1f\n\targuments\x18\x04 \x03(\x0b\x32\x0c.sysml.Value\x12\x46\n\x0fnamed_arguments\x18\x05 \x03(\x0b\x32-.sysml.RunAnalysisRequest.NamedArgumentsEntry\x1a\x43\n\x13NamedArgumentsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x1b\n\x05value\x18\x02 \x01(\x0b\x32\x0c.sysml.Value:\x02\x38\x01\"\xe4\x01\n\x13RunAnalysisResponse\x12\"\n\x07outputs\x18\x01 \x03(\x0b\x32\x11.sysml.CalcOutput\x12 \n\x08verdicts\x18\x02 \x03(\x0b\x32\x0e.sysml.Verdict\x12\"\n\tinstances\x18\x03 \x03(\x0b\x32\x0f.sysml.Instance\x12\r\n\x05\x65rror\x18\x04 \x01(\t\x12&\n\x0b\x64iagnostics\x18\x05 \x03(\x0b\x32\x11.sysml.Diagnostic\x12,\n\x0e\x66\x61ilure_reason\x18\x06 \x01(\x0e\x32\x14.sysml.FailureReason\"\x8c\x01\n\x10ParseFileRequest\x12\x13\n\tfile_path\x18\x01 \x01(\tH\x00\x12\x11\n\x07\x63ontent\x18\x02 \x01(\tH\x00\x12\x18\n\x0c\x63ontent_hash\x18\x03 \x01(\tB\x02\x18\x01\x12\x10\n\x08language\x18\x04 \x01(\t\x12\x1a\n\x12strict_conformance\x18\x05 \x01(\x08\x42\x08\n\x06source\"b\n\x0eSourceDocument\x12\x13\n\tfile_path\x18\x01 \x01(\tH\x00\x12\x11\n\x07\x63ontent\x18\x02 \x01(\tH\x00\x12\x10\n\x08language\x18\x03 \x01(\t\x12\x0c\n\x04name\x18\x04 \x01(\tB\x08\n\x06source\"[\n\x13ParseSourcesRequest\x12(\n\tdocuments\x18\x01 \x03(\x0b\x32\x15.sysml.SourceDocument\x12\x1a\n\x12strict_conformance\x18\x02 \x01(\x08\"\x83\x01\n\x14ParseSourcesResponse\x12\x12\n\nmodel_hash\x18\x01 \x01(\t\x12 \n\x05roots\x18\x02 \x03(\x0b\x32\x11.sysml.SymbolInfo\x12&\n\x0b\x64iagnostics\x18\x03 \x03(\x0b\x32\x11.sysml.Diagnostic\x12\r\n\x05\x65rror\x18\x04 \x01(\t\"\x7f\n\x11ParseFileResponse\x12\x12\n\nmodel_hash\x18\x01 \x01(\t\x12\x1f\n\x04root\x18\x02 \x01(\x0b\x32\x11.sysml.SymbolInfo\x12&\n\x0b\x64iagnostics\x18\x03 \x03(\x0b\x32\x11.sysml.Diagnostic\x12\r\n\x05\x65rror\x18\x04 \x01(\t\"9\n\x10GetSymbolRequest\x12\x12\n\nmodel_hash\x18\x01 \x01(\t\x12\x11\n\tsymbol_id\x18\x02 \x01(\t\"B\n\x0eSymbolResponse\x12!\n\x06symbol\x18\x01 \x01(\x0b\x32\x11.sysml.SymbolInfo\x12\r\n\x05\x65rror\x18\x02 \x01(\t\"(\n\x12\x44iagnosticsRequest\x12\x12\n\nmodel_hash\x18\x01 \x01(\t\"L\n\x13\x44iagnosticsResponse\x12&\n\x0b\x64iagnostics\x18\x01 \x03(\x0b\x32\x11.sysml.Diagnostic\x12\r\n\x05\x65rror\x18\x02 \x01(\t\"o\n\x0f\x45valuateRequest\x12\x12\n\nmodel_hash\x18\x01 \x01(\t\x12\x12\n\nexpression\x18\x02 \x01(\t\x12\x19\n\x11\x63ontext_symbol_id\x18\x03 \x01(\t\x12\x19\n\x11subject_symbol_id\x18\x04 \x01(\t\"g\n\x10\x45valuateResponse\x12\x1c\n\x06result\x18\x01 \x01(\x0b\x32\x0c.sysml.Value\x12\r\n\x05\x65rror\x18\x02 \x01(\t\x12&\n\x0b\x64iagnostics\x18\x03 \x03(\x0b\x32\x11.sysml.Diagnostic\"\xc2\x01\n\x08Instance\x12\n\n\x02id\x18\x01 \x01(\x03\x12\x16\n\x0etype_symbol_id\x18\x02 \x01(\t\x12:\n\x0e\x66\x65\x61ture_values\x18\x04 \x03(\x0b\x32\".sysml.Instance.FeatureValuesEntry\x1aI\n\x12\x46\x65\x61tureValuesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\"\n\x05value\x18\x02 \x01(\x0b\x32\x13.sysml.FeatureValue:\x02\x38\x01J\x04\x08\x03\x10\x04R\x05slots\"\x84\x01\n\x0c\x46\x65\x61tureValue\x12\x14\n\x0c\x66\x65\x61ture_name\x18\x01 \x01(\t\x12\x1b\n\x05value\x18\x02 \x01(\x0b\x32\x0c.sysml.Value\x12\x1c\n\x06values\x18\x03 \x03(\x0b\x32\x0c.sysml.Value\x12\x14\n\x0cmaterialized\x18\x04 \x01(\x08\x12\r\n\x05\x65rror\x18\x05 \x01(\t\";\n\x12InstantiateRequest\x12\x12\n\nmodel_hash\x18\x01 \x01(\t\x12\x11\n\tsymbol_id\x18\x02 \x01(\t\"\x93\x01\n\x13InstantiateResponse\x12!\n\x08instance\x18\x01 \x01(\x0b\x32\x0f.sysml.Instance\x12\r\n\x05\x65rror\x18\x02 \x01(\t\x12&\n\x0b\x64iagnostics\x18\x03 \x03(\x0b\x32\x11.sysml.Diagnostic\x12\"\n\tinstances\x18\x04 \x03(\x0b\x32\x0f.sysml.Instance\"\xba\x01\n\x14\x45xecuteActionRequest\x12\x12\n\nmodel_hash\x18\x01 \x01(\t\x12\x18\n\x10\x61\x63tion_symbol_id\x18\x02 \x01(\t\x12\x37\n\x06inputs\x18\x03 \x03(\x0b\x32\'.sysml.ExecuteActionRequest.InputsEntry\x1a;\n\x0bInputsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x1b\n\x05value\x18\x02 \x01(\x0b\x32\x0c.sysml.Value:\x02\x38\x01\"\xc8\x01\n\x15\x45xecuteActionResponse\x12:\n\x07outputs\x18\x01 \x03(\x0b\x32).sysml.ExecuteActionResponse.OutputsEntry\x12\r\n\x05\x65rror\x18\x02 \x01(\t\x12&\n\x0b\x64iagnostics\x18\x03 \x03(\x0b\x32\x11.sysml.Diagnostic\x1a<\n\x0cOutputsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x1b\n\x05value\x18\x02 \x01(\x0b\x32\x0c.sysml.Value:\x02\x38\x01\"Z\n\x13\x45xecuteStateRequest\x12\x12\n\nmodel_hash\x18\x01 \x01(\t\x12\x1f\n\x17state_machine_symbol_id\x18\x02 \x01(\t\x12\x0e\n\x06\x65vents\x18\x03 \x03(\t\"\xee\x01\n\x14\x45xecuteStateResponse\x12\x16\n\x0estates_visited\x18\x01 \x03(\t\x12\x44\n\rfinal_context\x18\x02 \x03(\x0b\x32-.sysml.ExecuteStateResponse.FinalContextEntry\x12\r\n\x05\x65rror\x18\x03 \x01(\t\x12&\n\x0b\x64iagnostics\x18\x04 \x03(\x0b\x32\x11.sysml.Diagnostic\x1a\x41\n\x11\x46inalContextEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x1b\n\x05value\x18\x02 \x01(\x0b\x32\x0c.sysml.Value:\x02\x38\x01\"\xa0\x01\n\x0e\x43onvertRequest\x12\x13\n\tfile_path\x18\x01 \x01(\tH\x00\x12\x11\n\x07\x63ontent\x18\x02 \x01(\tH\x00\x12\x14\n\nmodel_hash\x18\x06 \x01(\tH\x00\x12\x13\n\x0b\x66rom_format\x18\x03 \x01(\t\x12\x11\n\tto_format\x18\x04 \x01(\t\x12\x1e\n\x16tolerate_syntax_errors\x18\x05 \x01(\x08\x42\x08\n\x06source\"\xb4\x01\n\x0f\x43onvertResponse\x12\x0f\n\x07\x63ontent\x18\x01 \x01(\t\x12\x13\n\x0b\x66rom_format\x18\x02 \x01(\t\x12\x11\n\tto_format\x18\x03 \x01(\t\x12\r\n\x05\x65rror\x18\x04 \x01(\t\x12&\n\x0b\x64iagnostics\x18\x05 \x03(\x0b\x32\x11.sysml.Diagnostic\x12\x14\n\x0c\x65xperimental\x18\x06 \x01(\x08\x12\x1b\n\x13\x65xperimental_notice\x18\x07 \x01(\t\"Q\n\x11\x41pplyEditsRequest\x12\x12\n\nmodel_hash\x18\x01 \x01(\t\x12(\n\noperations\x18\x02 \x03(\x0b\x32\x14.sysml.EditOperation\"\xbc\x01\n\rEditOperation\x12(\n\tset_value\x18\x01 \x01(\x0b\x32\x13.sysml.SetValueEditH\x00\x12#\n\x06rename\x18\x02 \x01(\x0b\x32\x11.sysml.RenameEditH\x00\x12*\n\nadd_member\x18\x03 \x01(\x0b\x32\x14.sysml.AddMemberEditH\x00\x12#\n\x06\x64\x65lete\x18\x04 \x01(\x0b\x32\x11.sysml.DeleteEditH\x00\x42\x0b\n\toperation\"\x82\x01\n\rAddMemberEdit\x12\r\n\x05owner\x18\x01 \x01(\t\x12\x0c\n\x04kind\x18\x02 \x01(\t\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x0c\n\x04type\x18\x04 \x01(\t\x12\x14\n\x0cmultiplicity\x18\x05 \x01(\t\x12\r\n\x05value\x18\x06 \x01(\t\x12\x13\n\x0bspecializes\x18\x07 \x03(\t\"-\n\nDeleteEdit\x12\x0e\n\x06target\x18\x01 \x01(\t\x12\x0f\n\x07\x63\x61scade\x18\x02 \x01(\x08\"-\n\x0cSetValueEdit\x12\x0e\n\x06target\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t\".\n\nRenameEdit\x12\x0e\n\x06target\x18\x01 \x01(\t\x12\x10\n\x08new_name\x18\x02 \x01(\t\"\xc2\x01\n\x12\x41pplyEditsResponse\x12\x0f\n\x07\x63ontent\x18\x01 \x01(\t\x12#\n\x07\x61pplied\x18\x02 \x03(\x0b\x32\x12.sysml.AppliedEdit\x12\r\n\x05\x65rror\x18\x03 \x01(\t\x12#\n\x07\x66\x61ilure\x18\x04 \x01(\x0e\x32\x12.sysml.EditFailure\x12&\n\x0b\x64iagnostics\x18\x05 \x03(\x0b\x32\x11.sysml.Diagnostic\x12\x1a\n\x12referring_elements\x18\x06 \x03(\t\"z\n\x0b\x41ppliedEdit\x12\x17\n\x0foperation_index\x18\x01 \x01(\x05\x12\x0e\n\x06target\x18\x02 \x01(\t\x12\x0e\n\x06offset\x18\x03 \x01(\x05\x12\x0e\n\x06length\x18\x04 \x01(\x05\x12\x10\n\x08old_text\x18\x05 \x01(\t\x12\x10\n\x08new_text\x18\x06 \x01(\t\"\xfd\x02\n\nSymbolInfo\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0c\n\x04kind\x18\x03 \x01(\t\x12\x31\n\x08metadata\x18\x04 \x03(\x0b\x32\x1f.sysml.SymbolInfo.MetadataEntry\x12\x11\n\tchild_ids\x18\x05 \x03(\t\x12(\n\nattributes\x18\x06 \x03(\x0b\x32\x14.sysml.AttributeInfo\x12\"\n\ttype_info\x18\x07 \x01(\x0b\x32\x0f.sysml.TypeInfo\x12-\n\x0cmultiplicity\x18\x08 \x01(\x0b\x32\x17.sysml.MultiplicityInfo\x12.\n\x0fspecializations\x18\t \x03(\x0b\x32\x15.sysml.Specialization\x12#\n\x1bwithheld_library_attributes\x18\n \x01(\x05\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"X\n\x0eSpecialization\x12\x0c\n\x04kind\x18\x01 \x01(\t\x12\x10\n\x08\x64\x65\x63lared\x18\x02 \x01(\t\x12\x11\n\ttarget_id\x18\x03 \x01(\t\x12\x13\n\x0btarget_kind\x18\x04 \x01(\t\"\x95\x01\n\x08TypeInfo\x12\x10\n\x08\x64\x65\x63lared\x18\x01 \x01(\t\x12\x13\n\x0bresolved_id\x18\x02 \x01(\t\x12\x15\n\rresolved_kind\x18\x03 \x01(\t\x12\x11\n\tprimitive\x18\x04 \x01(\t\x12\x18\n\x10primitive_source\x18\x05 \x01(\t\x12\x10\n\x08quantity\x18\x06 \x01(\x08\x12\x0c\n\x04unit\x18\x07 \x01(\t\"0\n\x10MultiplicityInfo\x12\r\n\x05lower\x18\x01 \x01(\t\x12\r\n\x05upper\x18\x02 \x01(\t\"V\n\rAttributeInfo\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0c\n\x04type\x18\x02 \x01(\t\x12\x1b\n\x05value\x18\x03 \x01(\x0b\x32\x0c.sysml.Value\x12\x0c\n\x04unit\x18\x04 \x01(\t\"\xe2\x03\n\x05Value\x12\x13\n\tint_value\x18\x01 \x01(\x03H\x00\x12\x14\n\nreal_value\x18\x02 \x01(\x01H\x00\x12\x14\n\nbool_value\x18\x03 \x01(\x08H\x00\x12\x16\n\x0cstring_value\x18\x04 \x01(\tH\x00\x12\x15\n\x0binstance_id\x18\x05 \x01(\x03H\x00\x12(\n\x08sequence\x18\x06 \x01(\x0b\x32\x14.sysml.ValueSequenceH\x00\x12\x0e\n\x04null\x18\x07 \x01(\tH\x00\x12#\n\x08quantity\x18\x08 \x01(\x0b\x32\x0f.sysml.QuantityH\x00\x12*\n\x0c\x65num_literal\x18\t \x01(\x0b\x32\x12.sysml.EnumLiteralH\x00\x12\x0f\n\x05unset\x18\n \x01(\x08H\x00\x12!\n\x07\x63omplex\x18\x0b \x01(\x0b\x32\x0e.sysml.ComplexH\x00\x12\x1d\n\x05\x61rray\x18\x0c \x01(\x0b\x32\x0c.sysml.ArrayH\x00\x12\x1f\n\x06vector\x18\r \x01(\x0b\x32\r.sysml.VectorH\x00\x12\x30\n\x0fvector_quantity\x18\x0e \x01(\x0b\x32\x15.sysml.VectorQuantityH\x00\x12\x30\n\x0fmeasurement_ref\x18\x0f \x01(\x0b\x32\x15.sysml.MeasurementRefH\x00\x42\x06\n\x04kind\";\n\x05\x41rray\x12\x12\n\ndimensions\x18\x01 \x03(\x03\x12\x1e\n\x08\x65lements\x18\x02 \x03(\x0b\x32\x0c.sysml.Value\"*\n\x06Vector\x12 \n\ncomponents\x18\x01 \x03(\x0b\x32\x0c.sysml.Value\"5\n\x0eVectorQuantity\x12#\n\ncomponents\x18\x01 \x03(\x0b\x32\x0f.sysml.Quantity\"*\n\x07\x43omplex\x12\x0c\n\x04real\x18\x01 \x01(\x01\x12\x11\n\timaginary\x18\x02 \x01(\x01\"G\n\x0b\x45numLiteral\x12\x12\n\nliteral_id\x18\x01 \x01(\t\x12\x16\n\x0e\x65numeration_id\x18\x02 \x01(\t\x12\x0c\n\x04name\x18\x03 \x01(\t\"/\n\rValueSequence\x12\x1e\n\x08\x65lements\x18\x01 \x03(\x0b\x32\x0c.sysml.Value\"|\n\x08Quantity\x12\x17\n\rint_magnitude\x18\x01 \x01(\x03H\x00\x12\x18\n\x0ereal_magnitude\x18\x02 \x01(\x01H\x00\x12\x0c\n\x04unit\x18\x03 \x01(\t\x12\"\n\tunit_term\x18\x04 \x01(\x0b\x32\x0f.sysml.UnitTermB\x0b\n\tmagnitude\"S\n\x0eMeasurementRef\x12\x0c\n\x04unit\x18\x01 \x01(\t\x12\"\n\tunit_term\x18\x02 \x01(\x0b\x32\x0f.sysml.UnitTerm\x12\x0f\n\x07unit_id\x18\x03 \x01(\t\"T\n\x08UnitTerm\x12\x11\n\tscale_num\x18\x01 \x01(\x01\x12\x11\n\tscale_den\x18\x02 \x01(\x01\x12\"\n\x07\x66\x61\x63tors\x18\x03 \x03(\x0b\x32\x11.sysml.UnitFactor\"/\n\nUnitFactor\x12\x0f\n\x07unit_id\x18\x01 \x01(\t\x12\x10\n\x08\x65xponent\x18\x02 \x01(\x01\"J\n\nDiagnostic\x12\x10\n\x08severity\x18\x01 \x01(\t\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\x19\n\x04span\x18\x03 \x01(\x0b\x32\x0b.sysml.Span\"^\n\x04Span\x12\x0c\n\x04\x66ile\x18\x01 \x01(\t\x12\x12\n\nstart_line\x18\x02 \x01(\x05\x12\x11\n\tstart_col\x18\x03 \x01(\x05\x12\x10\n\x08\x65nd_line\x18\x04 \x01(\x05\x12\x0f\n\x07\x65nd_col\x18\x05 \x01(\x05\"\x13\n\x11ServerInfoRequest\";\n\x12ServerInfoResponse\x12\x0f\n\x07version\x18\x01 \x01(\t\x12\x14\n\x0c\x63\x61pabilities\x18\x02 \x03(\t\"S\n\x0cQueryRequest\x12\x12\n\nmodel_hash\x18\x01 \x01(\t\x12\x1b\n\x05query\x18\x02 \x01(\x0b\x32\x0c.sysml.Query\x12\x12\n\noslc_query\x18\x03 \x01(\t\"<\n\rQueryResponse\x12+\n\x08\x65lements\x18\x01 \x03(\x0b\x32\x19.sysml.QueryResultElement\"H\n\x05Query\x12\r\n\x05scope\x18\x01 \x03(\t\x12\x0e\n\x06select\x18\x02 \x03(\t\x12 \n\x05where\x18\x03 \x01(\x0b\x32\x11.sysml.Constraint\"|\n\nConstraint\x12/\n\tprimitive\x18\x01 \x01(\x0b\x32\x1a.sysml.PrimitiveConstraintH\x00\x12/\n\tcomposite\x18\x02 \x01(\x0b\x32\x1a.sysml.CompositeConstraintH\x00\x42\x0c\n\nconstraint\"s\n\x13PrimitiveConstraint\x12\x0f\n\x07inverse\x18\x01 \x01(\x08\x12\x10\n\x08property\x18\x02 \x01(\t\x12*\n\x08operator\x18\x03 \x01(\x0e\x32\x18.sysml.PrimitiveOperator\x12\r\n\x05value\x18\x04 \x03(\t\"h\n\x13\x43ompositeConstraint\x12*\n\x08operator\x18\x01 \x01(\x0e\x32\x18.sysml.CompositeOperator\x12%\n\nconstraint\x18\x02 \x03(\x0b\x32\x11.sysml.Constraint\"\xa0\x01\n\x12QueryResultElement\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04type\x18\x02 \x01(\t\x12=\n\nproperties\x18\x03 \x03(\x0b\x32).sysml.QueryResultElement.PropertiesEntry\x1a\x31\n\x0fPropertiesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"n\n\x17RunDocumentQueryRequest\x12\x12\n\nmodel_hash\x18\x01 \x01(\t\x12\x10\n\x08query_id\x18\x02 \x01(\t\x12-\n\x08\x62indings\x18\x03 \x03(\x0b\x32\x1b.sysml.DocumentQueryBinding\"O\n\x14\x44ocumentQueryBinding\x12\x11\n\tparameter\x18\x01 \x01(\t\x12$\n\x06values\x18\x02 \x03(\x0b\x32\x14.sysml.DocumentValue\"\xd5\x01\n\rDocumentValue\x12\x14\n\nelement_id\x18\x01 \x01(\tH\x00\x12\x16\n\x0cstring_value\x18\x02 \x01(\tH\x00\x12\x13\n\tint_value\x18\x03 \x01(\x03H\x00\x12\x14\n\nreal_value\x18\x04 \x01(\x01H\x00\x12\x14\n\nbool_value\x18\x05 \x01(\x08H\x00\x12\x12\n\x08infinity\x18\x06 \x01(\x08H\x00\x12#\n\x08quantity\x18\x08 \x01(\x0b\x32\x0f.sysml.QuantityH\x00\x12\x14\n\x0c\x65lement_type\x18\x07 \x01(\tB\x06\n\x04kind\"#\n\x13\x44ocumentQueryColumn\x12\x0c\n\x04name\x18\x01 \x01(\t\"9\n\x11\x44ocumentQueryCell\x12$\n\x06values\x18\x01 \x03(\x0b\x32\x14.sysml.DocumentValue\"b\n\x10\x44ocumentQueryRow\x12%\n\x07\x65lement\x18\x01 \x01(\x0b\x32\x14.sysml.DocumentValue\x12\'\n\x05\x63\x65lls\x18\x02 \x03(\x0b\x32\x18.sysml.DocumentQueryCell\"n\n\x18RunDocumentQueryResponse\x12+\n\x07\x63olumns\x18\x01 \x03(\x0b\x32\x1a.sysml.DocumentQueryColumn\x12%\n\x04rows\x18\x02 \x03(\x0b\x32\x17.sysml.DocumentQueryRow\"@\n\x15RenderDocumentRequest\x12\x12\n\nmodel_hash\x18\x01 \x01(\t\x12\x13\n\x0b\x64ocument_id\x18\x02 \x01(\t\"*\n\x16RenderDocumentResponse\x12\x10\n\x08markdown\x18\x01 \x01(\t*\x93\x01\n\rFailureReason\x12\x1e\n\x1a\x46\x41ILURE_REASON_UNSPECIFIED\x10\x00\x12\x1d\n\x19\x46\x41ILURE_REASON_EVALUATION\x10\x01\x12\x1d\n\x19\x46\x41ILURE_REASON_WRONG_KIND\x10\x02\x12$\n FAILURE_REASON_AMBIGUOUS_SUBJECT\x10\x03*\x9d\x04\n\x0b\x45\x64itFailure\x12\x1c\n\x18\x45\x44IT_FAILURE_UNSPECIFIED\x10\x00\x12\x1e\n\x1a\x45\x44IT_FAILURE_NO_OPERATIONS\x10\x01\x12\x1f\n\x1b\x45\x44IT_FAILURE_UNKNOWN_TARGET\x10\x02\x12!\n\x1d\x45\x44IT_FAILURE_AMBIGUOUS_TARGET\x10\x03\x12\x1b\n\x17\x45\x44IT_FAILURE_NOT_VALUED\x10\x04\x12\x1e\n\x1a\x45\x44IT_FAILURE_INVALID_VALUE\x10\x05\x12\x1d\n\x19\x45\x44IT_FAILURE_INVALID_NAME\x10\x06\x12\x1a\n\x16\x45\x44IT_FAILURE_NOT_NAMED\x10\x07\x12\"\n\x1e\x45\x44IT_FAILURE_RENAME_REFERENCED\x10\x08\x12\"\n\x1e\x45\x44IT_FAILURE_OVERLAPPING_EDITS\x10\t\x12\x1f\n\x1b\x45\x44IT_FAILURE_RESULT_INVALID\x10\n\x12\x1e\n\x1a\x45\x44IT_FAILURE_OWNER_UNKNOWN\x10\x0b\x12$\n EDIT_FAILURE_OWNER_NOT_NAMESPACE\x10\x0c\x12\x1d\n\x19\x45\x44IT_FAILURE_ILLEGAL_KIND\x10\r\x12\"\n\x1e\x45\x44IT_FAILURE_MEMBER_NAME_TAKEN\x10\x0e\x12\"\n\x1e\x45\x44IT_FAILURE_DELETE_REFERENCED\x10\x0f*\x92\x01\n\x11PrimitiveOperator\x12\"\n\x1ePRIMITIVE_OPERATOR_UNSPECIFIED\x10\x00\x12\x1c\n\x18PRIMITIVE_OPERATOR_EQUAL\x10\x01\x12\x1e\n\x1aPRIMITIVE_OPERATOR_GREATER\x10\x02\x12\x1b\n\x17PRIMITIVE_OPERATOR_LESS\x10\x03*n\n\x11\x43ompositeOperator\x12\"\n\x1e\x43OMPOSITE_OPERATOR_UNSPECIFIED\x10\x00\x12\x1a\n\x16\x43OMPOSITE_OPERATOR_AND\x10\x01\x12\x19\n\x15\x43OMPOSITE_OPERATOR_OR\x10\x02\x32\xe7\n\n\x0cSysMLService\x12\x44\n\rGetServerInfo\x12\x18.sysml.ServerInfoRequest\x1a\x19.sysml.ServerInfoResponse\x12>\n\tParseFile\x12\x17.sysml.ParseFileRequest\x1a\x18.sysml.ParseFileResponse\x12G\n\x0cParseSources\x12\x1a.sysml.ParseSourcesRequest\x1a\x1b.sysml.ParseSourcesResponse\x12;\n\tGetSymbol\x12\x17.sysml.GetSymbolRequest\x1a\x15.sysml.SymbolResponse\x12G\n\x0eGetDiagnostics\x12\x19.sysml.DiagnosticsRequest\x1a\x1a.sysml.DiagnosticsResponse\x12;\n\x08\x45valuate\x12\x16.sysml.EvaluateRequest\x1a\x17.sysml.EvaluateResponse\x12\x44\n\x0bInstantiate\x12\x19.sysml.InstantiateRequest\x1a\x1a.sysml.InstantiateResponse\x12J\n\rExecuteAction\x12\x1b.sysml.ExecuteActionRequest\x1a\x1c.sysml.ExecuteActionResponse\x12G\n\x0c\x45xecuteState\x12\x1a.sysml.ExecuteStateRequest\x1a\x1b.sysml.ExecuteStateResponse\x12\x38\n\x07\x43onvert\x12\x15.sysml.ConvertRequest\x1a\x16.sysml.ConvertResponse\x12\x41\n\nApplyEdits\x12\x18.sysml.ApplyEditsRequest\x1a\x19.sysml.ApplyEditsResponse\x12S\n\x10VerifyConstraint\x12\x1e.sysml.VerifyConstraintRequest\x1a\x1f.sysml.VerifyConstraintResponse\x12V\n\x11VerifyRequirement\x12\x1f.sysml.VerifyRequirementRequest\x1a .sysml.VerifyRequirementResponse\x12Y\n\x12VerifySatisfaction\x12 .sysml.VerifySatisfactionRequest\x1a!.sysml.VerifySatisfactionResponse\x12G\n\x0c\x45valuateCalc\x12\x1a.sysml.EvaluateCalcRequest\x1a\x1b.sysml.EvaluateCalcResponse\x12\x44\n\x0bRunAnalysis\x12\x19.sysml.RunAnalysisRequest\x1a\x1a.sysml.RunAnalysisResponse\x12\x32\n\x05Query\x12\x13.sysml.QueryRequest\x1a\x14.sysml.QueryResponse\x12S\n\x10RunDocumentQuery\x12\x1e.sysml.RunDocumentQueryRequest\x1a\x1f.sysml.RunDocumentQueryResponse\x12M\n\x0eRenderDocument\x12\x1c.sysml.RenderDocumentRequest\x1a\x1d.sysml.RenderDocumentResponseB*Z(github.com/Open-MBEE/OpenSysML/api/protob\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0bsysml.proto\x12\x05sysml\"\xca\x01\n\x07Verdict\x12\x0c\n\x04kind\x18\x01 \x01(\t\x12\x12\n\nelement_id\x18\x02 \x01(\t\x12\x0f\n\x07\x65lement\x18\x03 \x01(\t\x12\r\n\x05holds\x18\x04 \x01(\x08\x12\x11\n\tcondition\x18\x05 \x01(\t\x12\x13\n\x0binstance_id\x18\x06 \x01(\x03\x12\x18\n\x10instance_type_id\x18\x07 \x01(\t\x12\r\n\x05\x65rror\x18\x08 \x01(\t\x12,\n\x0e\x66\x61ilure_reason\x18\t \x01(\x0e\x32\x14.sysml.FailureReason\"[\n\x17VerifyConstraintRequest\x12\x12\n\nmodel_hash\x18\x01 \x01(\t\x12\x11\n\tsymbol_id\x18\x02 \x01(\t\x12\x19\n\x11subject_symbol_id\x18\x03 \x01(\t\"\x96\x01\n\x18VerifyConstraintResponse\x12\x1f\n\x07verdict\x18\x01 \x01(\x0b\x32\x0e.sysml.Verdict\x12\"\n\tinstances\x18\x02 \x03(\x0b\x32\x0f.sysml.Instance\x12\r\n\x05\x65rror\x18\x03 \x01(\t\x12&\n\x0b\x64iagnostics\x18\x04 \x03(\x0b\x32\x11.sysml.Diagnostic\"\\\n\x18VerifyRequirementRequest\x12\x12\n\nmodel_hash\x18\x01 \x01(\t\x12\x11\n\tsymbol_id\x18\x02 \x01(\t\x12\x19\n\x11subject_symbol_id\x18\x03 \x01(\t\"\x97\x01\n\x19VerifyRequirementResponse\x12\x1f\n\x07verdict\x18\x01 \x01(\x0b\x32\x0e.sysml.Verdict\x12\"\n\tinstances\x18\x02 \x03(\x0b\x32\x0f.sysml.Instance\x12\r\n\x05\x65rror\x18\x03 \x01(\t\x12&\n\x0b\x64iagnostics\x18\x04 \x03(\x0b\x32\x11.sysml.Diagnostic\"B\n\x19VerifySatisfactionRequest\x12\x12\n\nmodel_hash\x18\x01 \x01(\t\x12\x11\n\tsymbol_id\x18\x02 \x01(\t\"\xc7\x01\n\x1aVerifySatisfactionResponse\x12 \n\x08verdicts\x18\x01 \x03(\x0b\x32\x0e.sysml.Verdict\x12\"\n\tinstances\x18\x02 \x03(\x0b\x32\x0f.sysml.Instance\x12\r\n\x05\x65rror\x18\x03 \x01(\t\x12&\n\x0b\x64iagnostics\x18\x04 \x03(\x0b\x32\x11.sysml.Diagnostic\x12,\n\x0e\x66\x61ilure_reason\x18\x05 \x01(\x0e\x32\x14.sysml.FailureReason\"]\n\x13\x45valuateCalcRequest\x12\x12\n\nmodel_hash\x18\x01 \x01(\t\x12\x11\n\tsymbol_id\x18\x02 \x01(\t\x12\x1f\n\targuments\x18\x03 \x03(\x0b\x32\x0c.sysml.Value\"\xbd\x01\n\x14\x45valuateCalcResponse\x12\x1c\n\x06result\x18\x01 \x01(\x0b\x32\x0c.sysml.Value\x12\"\n\x07outputs\x18\x02 \x03(\x0b\x32\x11.sysml.CalcOutput\x12\r\n\x05\x65rror\x18\x03 \x01(\t\x12&\n\x0b\x64iagnostics\x18\x04 \x03(\x0b\x32\x11.sysml.Diagnostic\x12,\n\x0e\x66\x61ilure_reason\x18\x05 \x01(\x0e\x32\x14.sysml.FailureReason\"7\n\nCalcOutput\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x1b\n\x05value\x18\x02 \x01(\x0b\x32\x0c.sysml.Value\"\x84\x02\n\x12RunAnalysisRequest\x12\x12\n\nmodel_hash\x18\x01 \x01(\t\x12\x11\n\tsymbol_id\x18\x02 \x01(\t\x12\x19\n\x11subject_symbol_id\x18\x03 \x01(\t\x12\x1f\n\targuments\x18\x04 \x03(\x0b\x32\x0c.sysml.Value\x12\x46\n\x0fnamed_arguments\x18\x05 \x03(\x0b\x32-.sysml.RunAnalysisRequest.NamedArgumentsEntry\x1a\x43\n\x13NamedArgumentsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x1b\n\x05value\x18\x02 \x01(\x0b\x32\x0c.sysml.Value:\x02\x38\x01\"\xe4\x01\n\x13RunAnalysisResponse\x12\"\n\x07outputs\x18\x01 \x03(\x0b\x32\x11.sysml.CalcOutput\x12 \n\x08verdicts\x18\x02 \x03(\x0b\x32\x0e.sysml.Verdict\x12\"\n\tinstances\x18\x03 \x03(\x0b\x32\x0f.sysml.Instance\x12\r\n\x05\x65rror\x18\x04 \x01(\t\x12&\n\x0b\x64iagnostics\x18\x05 \x03(\x0b\x32\x11.sysml.Diagnostic\x12,\n\x0e\x66\x61ilure_reason\x18\x06 \x01(\x0e\x32\x14.sysml.FailureReason\"\x8c\x01\n\x10ParseFileRequest\x12\x13\n\tfile_path\x18\x01 \x01(\tH\x00\x12\x11\n\x07\x63ontent\x18\x02 \x01(\tH\x00\x12\x18\n\x0c\x63ontent_hash\x18\x03 \x01(\tB\x02\x18\x01\x12\x10\n\x08language\x18\x04 \x01(\t\x12\x1a\n\x12strict_conformance\x18\x05 \x01(\x08\x42\x08\n\x06source\"b\n\x0eSourceDocument\x12\x13\n\tfile_path\x18\x01 \x01(\tH\x00\x12\x11\n\x07\x63ontent\x18\x02 \x01(\tH\x00\x12\x10\n\x08language\x18\x03 \x01(\t\x12\x0c\n\x04name\x18\x04 \x01(\tB\x08\n\x06source\"[\n\x13ParseSourcesRequest\x12(\n\tdocuments\x18\x01 \x03(\x0b\x32\x15.sysml.SourceDocument\x12\x1a\n\x12strict_conformance\x18\x02 \x01(\x08\"\x83\x01\n\x14ParseSourcesResponse\x12\x12\n\nmodel_hash\x18\x01 \x01(\t\x12 \n\x05roots\x18\x02 \x03(\x0b\x32\x11.sysml.SymbolInfo\x12&\n\x0b\x64iagnostics\x18\x03 \x03(\x0b\x32\x11.sysml.Diagnostic\x12\r\n\x05\x65rror\x18\x04 \x01(\t\"\x7f\n\x11ParseFileResponse\x12\x12\n\nmodel_hash\x18\x01 \x01(\t\x12\x1f\n\x04root\x18\x02 \x01(\x0b\x32\x11.sysml.SymbolInfo\x12&\n\x0b\x64iagnostics\x18\x03 \x03(\x0b\x32\x11.sysml.Diagnostic\x12\r\n\x05\x65rror\x18\x04 \x01(\t\"9\n\x10GetSymbolRequest\x12\x12\n\nmodel_hash\x18\x01 \x01(\t\x12\x11\n\tsymbol_id\x18\x02 \x01(\t\"B\n\x0eSymbolResponse\x12!\n\x06symbol\x18\x01 \x01(\x0b\x32\x11.sysml.SymbolInfo\x12\r\n\x05\x65rror\x18\x02 \x01(\t\"(\n\x12\x44iagnosticsRequest\x12\x12\n\nmodel_hash\x18\x01 \x01(\t\"L\n\x13\x44iagnosticsResponse\x12&\n\x0b\x64iagnostics\x18\x01 \x03(\x0b\x32\x11.sysml.Diagnostic\x12\r\n\x05\x65rror\x18\x02 \x01(\t\"o\n\x0f\x45valuateRequest\x12\x12\n\nmodel_hash\x18\x01 \x01(\t\x12\x12\n\nexpression\x18\x02 \x01(\t\x12\x19\n\x11\x63ontext_symbol_id\x18\x03 \x01(\t\x12\x19\n\x11subject_symbol_id\x18\x04 \x01(\t\"g\n\x10\x45valuateResponse\x12\x1c\n\x06result\x18\x01 \x01(\x0b\x32\x0c.sysml.Value\x12\r\n\x05\x65rror\x18\x02 \x01(\t\x12&\n\x0b\x64iagnostics\x18\x03 \x03(\x0b\x32\x11.sysml.Diagnostic\"\xc2\x01\n\x08Instance\x12\n\n\x02id\x18\x01 \x01(\x03\x12\x16\n\x0etype_symbol_id\x18\x02 \x01(\t\x12:\n\x0e\x66\x65\x61ture_values\x18\x04 \x03(\x0b\x32\".sysml.Instance.FeatureValuesEntry\x1aI\n\x12\x46\x65\x61tureValuesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\"\n\x05value\x18\x02 \x01(\x0b\x32\x13.sysml.FeatureValue:\x02\x38\x01J\x04\x08\x03\x10\x04R\x05slots\"\x84\x01\n\x0c\x46\x65\x61tureValue\x12\x14\n\x0c\x66\x65\x61ture_name\x18\x01 \x01(\t\x12\x1b\n\x05value\x18\x02 \x01(\x0b\x32\x0c.sysml.Value\x12\x1c\n\x06values\x18\x03 \x03(\x0b\x32\x0c.sysml.Value\x12\x14\n\x0cmaterialized\x18\x04 \x01(\x08\x12\r\n\x05\x65rror\x18\x05 \x01(\t\";\n\x12InstantiateRequest\x12\x12\n\nmodel_hash\x18\x01 \x01(\t\x12\x11\n\tsymbol_id\x18\x02 \x01(\t\"\x93\x01\n\x13InstantiateResponse\x12!\n\x08instance\x18\x01 \x01(\x0b\x32\x0f.sysml.Instance\x12\r\n\x05\x65rror\x18\x02 \x01(\t\x12&\n\x0b\x64iagnostics\x18\x03 \x03(\x0b\x32\x11.sysml.Diagnostic\x12\"\n\tinstances\x18\x04 \x03(\x0b\x32\x0f.sysml.Instance\"\xba\x01\n\x14\x45xecuteActionRequest\x12\x12\n\nmodel_hash\x18\x01 \x01(\t\x12\x18\n\x10\x61\x63tion_symbol_id\x18\x02 \x01(\t\x12\x37\n\x06inputs\x18\x03 \x03(\x0b\x32\'.sysml.ExecuteActionRequest.InputsEntry\x1a;\n\x0bInputsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x1b\n\x05value\x18\x02 \x01(\x0b\x32\x0c.sysml.Value:\x02\x38\x01\"\xc8\x01\n\x15\x45xecuteActionResponse\x12:\n\x07outputs\x18\x01 \x03(\x0b\x32).sysml.ExecuteActionResponse.OutputsEntry\x12\r\n\x05\x65rror\x18\x02 \x01(\t\x12&\n\x0b\x64iagnostics\x18\x03 \x03(\x0b\x32\x11.sysml.Diagnostic\x1a<\n\x0cOutputsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x1b\n\x05value\x18\x02 \x01(\x0b\x32\x0c.sysml.Value:\x02\x38\x01\"Z\n\x13\x45xecuteStateRequest\x12\x12\n\nmodel_hash\x18\x01 \x01(\t\x12\x1f\n\x17state_machine_symbol_id\x18\x02 \x01(\t\x12\x0e\n\x06\x65vents\x18\x03 \x03(\t\"\xee\x01\n\x14\x45xecuteStateResponse\x12\x16\n\x0estates_visited\x18\x01 \x03(\t\x12\x44\n\rfinal_context\x18\x02 \x03(\x0b\x32-.sysml.ExecuteStateResponse.FinalContextEntry\x12\r\n\x05\x65rror\x18\x03 \x01(\t\x12&\n\x0b\x64iagnostics\x18\x04 \x03(\x0b\x32\x11.sysml.Diagnostic\x1a\x41\n\x11\x46inalContextEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x1b\n\x05value\x18\x02 \x01(\x0b\x32\x0c.sysml.Value:\x02\x38\x01\"\xa0\x01\n\x0e\x43onvertRequest\x12\x13\n\tfile_path\x18\x01 \x01(\tH\x00\x12\x11\n\x07\x63ontent\x18\x02 \x01(\tH\x00\x12\x14\n\nmodel_hash\x18\x06 \x01(\tH\x00\x12\x13\n\x0b\x66rom_format\x18\x03 \x01(\t\x12\x11\n\tto_format\x18\x04 \x01(\t\x12\x1e\n\x16tolerate_syntax_errors\x18\x05 \x01(\x08\x42\x08\n\x06source\"\xb4\x01\n\x0f\x43onvertResponse\x12\x0f\n\x07\x63ontent\x18\x01 \x01(\t\x12\x13\n\x0b\x66rom_format\x18\x02 \x01(\t\x12\x11\n\tto_format\x18\x03 \x01(\t\x12\r\n\x05\x65rror\x18\x04 \x01(\t\x12&\n\x0b\x64iagnostics\x18\x05 \x03(\x0b\x32\x11.sysml.Diagnostic\x12\x14\n\x0c\x65xperimental\x18\x06 \x01(\x08\x12\x1b\n\x13\x65xperimental_notice\x18\x07 \x01(\t\"Q\n\x11\x41pplyEditsRequest\x12\x12\n\nmodel_hash\x18\x01 \x01(\t\x12(\n\noperations\x18\x02 \x03(\x0b\x32\x14.sysml.EditOperation\"\xbc\x01\n\rEditOperation\x12(\n\tset_value\x18\x01 \x01(\x0b\x32\x13.sysml.SetValueEditH\x00\x12#\n\x06rename\x18\x02 \x01(\x0b\x32\x11.sysml.RenameEditH\x00\x12*\n\nadd_member\x18\x03 \x01(\x0b\x32\x14.sysml.AddMemberEditH\x00\x12#\n\x06\x64\x65lete\x18\x04 \x01(\x0b\x32\x11.sysml.DeleteEditH\x00\x42\x0b\n\toperation\"\x82\x01\n\rAddMemberEdit\x12\r\n\x05owner\x18\x01 \x01(\t\x12\x0c\n\x04kind\x18\x02 \x01(\t\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x0c\n\x04type\x18\x04 \x01(\t\x12\x14\n\x0cmultiplicity\x18\x05 \x01(\t\x12\r\n\x05value\x18\x06 \x01(\t\x12\x13\n\x0bspecializes\x18\x07 \x03(\t\"-\n\nDeleteEdit\x12\x0e\n\x06target\x18\x01 \x01(\t\x12\x0f\n\x07\x63\x61scade\x18\x02 \x01(\x08\"-\n\x0cSetValueEdit\x12\x0e\n\x06target\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t\".\n\nRenameEdit\x12\x0e\n\x06target\x18\x01 \x01(\t\x12\x10\n\x08new_name\x18\x02 \x01(\t\"\xc2\x01\n\x12\x41pplyEditsResponse\x12\x0f\n\x07\x63ontent\x18\x01 \x01(\t\x12#\n\x07\x61pplied\x18\x02 \x03(\x0b\x32\x12.sysml.AppliedEdit\x12\r\n\x05\x65rror\x18\x03 \x01(\t\x12#\n\x07\x66\x61ilure\x18\x04 \x01(\x0e\x32\x12.sysml.EditFailure\x12&\n\x0b\x64iagnostics\x18\x05 \x03(\x0b\x32\x11.sysml.Diagnostic\x12\x1a\n\x12referring_elements\x18\x06 \x03(\t\"z\n\x0b\x41ppliedEdit\x12\x17\n\x0foperation_index\x18\x01 \x01(\x05\x12\x0e\n\x06target\x18\x02 \x01(\t\x12\x0e\n\x06offset\x18\x03 \x01(\x05\x12\x0e\n\x06length\x18\x04 \x01(\x05\x12\x10\n\x08old_text\x18\x05 \x01(\t\x12\x10\n\x08new_text\x18\x06 \x01(\t\"\xfd\x02\n\nSymbolInfo\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0c\n\x04kind\x18\x03 \x01(\t\x12\x31\n\x08metadata\x18\x04 \x03(\x0b\x32\x1f.sysml.SymbolInfo.MetadataEntry\x12\x11\n\tchild_ids\x18\x05 \x03(\t\x12(\n\nattributes\x18\x06 \x03(\x0b\x32\x14.sysml.AttributeInfo\x12\"\n\ttype_info\x18\x07 \x01(\x0b\x32\x0f.sysml.TypeInfo\x12-\n\x0cmultiplicity\x18\x08 \x01(\x0b\x32\x17.sysml.MultiplicityInfo\x12.\n\x0fspecializations\x18\t \x03(\x0b\x32\x15.sysml.Specialization\x12#\n\x1bwithheld_library_attributes\x18\n \x01(\x05\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"X\n\x0eSpecialization\x12\x0c\n\x04kind\x18\x01 \x01(\t\x12\x10\n\x08\x64\x65\x63lared\x18\x02 \x01(\t\x12\x11\n\ttarget_id\x18\x03 \x01(\t\x12\x13\n\x0btarget_kind\x18\x04 \x01(\t\"\x95\x01\n\x08TypeInfo\x12\x10\n\x08\x64\x65\x63lared\x18\x01 \x01(\t\x12\x13\n\x0bresolved_id\x18\x02 \x01(\t\x12\x15\n\rresolved_kind\x18\x03 \x01(\t\x12\x11\n\tprimitive\x18\x04 \x01(\t\x12\x18\n\x10primitive_source\x18\x05 \x01(\t\x12\x10\n\x08quantity\x18\x06 \x01(\x08\x12\x0c\n\x04unit\x18\x07 \x01(\t\"0\n\x10MultiplicityInfo\x12\r\n\x05lower\x18\x01 \x01(\t\x12\r\n\x05upper\x18\x02 \x01(\t\"V\n\rAttributeInfo\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0c\n\x04type\x18\x02 \x01(\t\x12\x1b\n\x05value\x18\x03 \x01(\x0b\x32\x0c.sysml.Value\x12\x0c\n\x04unit\x18\x04 \x01(\t\"\x87\x04\n\x05Value\x12\x13\n\tint_value\x18\x01 \x01(\x03H\x00\x12\x14\n\nreal_value\x18\x02 \x01(\x01H\x00\x12\x14\n\nbool_value\x18\x03 \x01(\x08H\x00\x12\x16\n\x0cstring_value\x18\x04 \x01(\tH\x00\x12\x15\n\x0binstance_id\x18\x05 \x01(\x03H\x00\x12(\n\x08sequence\x18\x06 \x01(\x0b\x32\x14.sysml.ValueSequenceH\x00\x12\x0e\n\x04null\x18\x07 \x01(\tH\x00\x12#\n\x08quantity\x18\x08 \x01(\x0b\x32\x0f.sysml.QuantityH\x00\x12*\n\x0c\x65num_literal\x18\t \x01(\x0b\x32\x12.sysml.EnumLiteralH\x00\x12\x0f\n\x05unset\x18\n \x01(\x08H\x00\x12!\n\x07\x63omplex\x18\x0b \x01(\x0b\x32\x0e.sysml.ComplexH\x00\x12\x1d\n\x05\x61rray\x18\x0c \x01(\x0b\x32\x0c.sysml.ArrayH\x00\x12\x1f\n\x06vector\x18\r \x01(\x0b\x32\r.sysml.VectorH\x00\x12\x30\n\x0fvector_quantity\x18\x0e \x01(\x0b\x32\x15.sysml.VectorQuantityH\x00\x12\x30\n\x0fmeasurement_ref\x18\x0f \x01(\x0b\x32\x15.sysml.MeasurementRefH\x00\x12#\n\x08\x66unction\x18\x10 \x01(\x0b\x32\x0f.sysml.FunctionH\x00\x42\x06\n\x04kind\",\n\x08\x46unction\x12\x0f\n\x07\x63\x61lc_id\x18\x01 \x01(\t\x12\x0f\n\x07self_id\x18\x02 \x01(\x03\";\n\x05\x41rray\x12\x12\n\ndimensions\x18\x01 \x03(\x03\x12\x1e\n\x08\x65lements\x18\x02 \x03(\x0b\x32\x0c.sysml.Value\"*\n\x06Vector\x12 \n\ncomponents\x18\x01 \x03(\x0b\x32\x0c.sysml.Value\"5\n\x0eVectorQuantity\x12#\n\ncomponents\x18\x01 \x03(\x0b\x32\x0f.sysml.Quantity\"*\n\x07\x43omplex\x12\x0c\n\x04real\x18\x01 \x01(\x01\x12\x11\n\timaginary\x18\x02 \x01(\x01\"G\n\x0b\x45numLiteral\x12\x12\n\nliteral_id\x18\x01 \x01(\t\x12\x16\n\x0e\x65numeration_id\x18\x02 \x01(\t\x12\x0c\n\x04name\x18\x03 \x01(\t\"/\n\rValueSequence\x12\x1e\n\x08\x65lements\x18\x01 \x03(\x0b\x32\x0c.sysml.Value\"|\n\x08Quantity\x12\x17\n\rint_magnitude\x18\x01 \x01(\x03H\x00\x12\x18\n\x0ereal_magnitude\x18\x02 \x01(\x01H\x00\x12\x0c\n\x04unit\x18\x03 \x01(\t\x12\"\n\tunit_term\x18\x04 \x01(\x0b\x32\x0f.sysml.UnitTermB\x0b\n\tmagnitude\"S\n\x0eMeasurementRef\x12\x0c\n\x04unit\x18\x01 \x01(\t\x12\"\n\tunit_term\x18\x02 \x01(\x0b\x32\x0f.sysml.UnitTerm\x12\x0f\n\x07unit_id\x18\x03 \x01(\t\"T\n\x08UnitTerm\x12\x11\n\tscale_num\x18\x01 \x01(\x01\x12\x11\n\tscale_den\x18\x02 \x01(\x01\x12\"\n\x07\x66\x61\x63tors\x18\x03 \x03(\x0b\x32\x11.sysml.UnitFactor\"/\n\nUnitFactor\x12\x0f\n\x07unit_id\x18\x01 \x01(\t\x12\x10\n\x08\x65xponent\x18\x02 \x01(\x01\"J\n\nDiagnostic\x12\x10\n\x08severity\x18\x01 \x01(\t\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\x19\n\x04span\x18\x03 \x01(\x0b\x32\x0b.sysml.Span\"^\n\x04Span\x12\x0c\n\x04\x66ile\x18\x01 \x01(\t\x12\x12\n\nstart_line\x18\x02 \x01(\x05\x12\x11\n\tstart_col\x18\x03 \x01(\x05\x12\x10\n\x08\x65nd_line\x18\x04 \x01(\x05\x12\x0f\n\x07\x65nd_col\x18\x05 \x01(\x05\"\x13\n\x11ServerInfoRequest\";\n\x12ServerInfoResponse\x12\x0f\n\x07version\x18\x01 \x01(\t\x12\x14\n\x0c\x63\x61pabilities\x18\x02 \x03(\t\"S\n\x0cQueryRequest\x12\x12\n\nmodel_hash\x18\x01 \x01(\t\x12\x1b\n\x05query\x18\x02 \x01(\x0b\x32\x0c.sysml.Query\x12\x12\n\noslc_query\x18\x03 \x01(\t\"<\n\rQueryResponse\x12+\n\x08\x65lements\x18\x01 \x03(\x0b\x32\x19.sysml.QueryResultElement\"H\n\x05Query\x12\r\n\x05scope\x18\x01 \x03(\t\x12\x0e\n\x06select\x18\x02 \x03(\t\x12 \n\x05where\x18\x03 \x01(\x0b\x32\x11.sysml.Constraint\"|\n\nConstraint\x12/\n\tprimitive\x18\x01 \x01(\x0b\x32\x1a.sysml.PrimitiveConstraintH\x00\x12/\n\tcomposite\x18\x02 \x01(\x0b\x32\x1a.sysml.CompositeConstraintH\x00\x42\x0c\n\nconstraint\"s\n\x13PrimitiveConstraint\x12\x0f\n\x07inverse\x18\x01 \x01(\x08\x12\x10\n\x08property\x18\x02 \x01(\t\x12*\n\x08operator\x18\x03 \x01(\x0e\x32\x18.sysml.PrimitiveOperator\x12\r\n\x05value\x18\x04 \x03(\t\"h\n\x13\x43ompositeConstraint\x12*\n\x08operator\x18\x01 \x01(\x0e\x32\x18.sysml.CompositeOperator\x12%\n\nconstraint\x18\x02 \x03(\x0b\x32\x11.sysml.Constraint\"\xa0\x01\n\x12QueryResultElement\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04type\x18\x02 \x01(\t\x12=\n\nproperties\x18\x03 \x03(\x0b\x32).sysml.QueryResultElement.PropertiesEntry\x1a\x31\n\x0fPropertiesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"n\n\x17RunDocumentQueryRequest\x12\x12\n\nmodel_hash\x18\x01 \x01(\t\x12\x10\n\x08query_id\x18\x02 \x01(\t\x12-\n\x08\x62indings\x18\x03 \x03(\x0b\x32\x1b.sysml.DocumentQueryBinding\"O\n\x14\x44ocumentQueryBinding\x12\x11\n\tparameter\x18\x01 \x01(\t\x12$\n\x06values\x18\x02 \x03(\x0b\x32\x14.sysml.DocumentValue\"\xd5\x01\n\rDocumentValue\x12\x14\n\nelement_id\x18\x01 \x01(\tH\x00\x12\x16\n\x0cstring_value\x18\x02 \x01(\tH\x00\x12\x13\n\tint_value\x18\x03 \x01(\x03H\x00\x12\x14\n\nreal_value\x18\x04 \x01(\x01H\x00\x12\x14\n\nbool_value\x18\x05 \x01(\x08H\x00\x12\x12\n\x08infinity\x18\x06 \x01(\x08H\x00\x12#\n\x08quantity\x18\x08 \x01(\x0b\x32\x0f.sysml.QuantityH\x00\x12\x14\n\x0c\x65lement_type\x18\x07 \x01(\tB\x06\n\x04kind\"#\n\x13\x44ocumentQueryColumn\x12\x0c\n\x04name\x18\x01 \x01(\t\"9\n\x11\x44ocumentQueryCell\x12$\n\x06values\x18\x01 \x03(\x0b\x32\x14.sysml.DocumentValue\"b\n\x10\x44ocumentQueryRow\x12%\n\x07\x65lement\x18\x01 \x01(\x0b\x32\x14.sysml.DocumentValue\x12\'\n\x05\x63\x65lls\x18\x02 \x03(\x0b\x32\x18.sysml.DocumentQueryCell\"n\n\x18RunDocumentQueryResponse\x12+\n\x07\x63olumns\x18\x01 \x03(\x0b\x32\x1a.sysml.DocumentQueryColumn\x12%\n\x04rows\x18\x02 \x03(\x0b\x32\x17.sysml.DocumentQueryRow\"@\n\x15RenderDocumentRequest\x12\x12\n\nmodel_hash\x18\x01 \x01(\t\x12\x13\n\x0b\x64ocument_id\x18\x02 \x01(\t\"*\n\x16RenderDocumentResponse\x12\x10\n\x08markdown\x18\x01 \x01(\t*\x93\x01\n\rFailureReason\x12\x1e\n\x1a\x46\x41ILURE_REASON_UNSPECIFIED\x10\x00\x12\x1d\n\x19\x46\x41ILURE_REASON_EVALUATION\x10\x01\x12\x1d\n\x19\x46\x41ILURE_REASON_WRONG_KIND\x10\x02\x12$\n FAILURE_REASON_AMBIGUOUS_SUBJECT\x10\x03*\x9d\x04\n\x0b\x45\x64itFailure\x12\x1c\n\x18\x45\x44IT_FAILURE_UNSPECIFIED\x10\x00\x12\x1e\n\x1a\x45\x44IT_FAILURE_NO_OPERATIONS\x10\x01\x12\x1f\n\x1b\x45\x44IT_FAILURE_UNKNOWN_TARGET\x10\x02\x12!\n\x1d\x45\x44IT_FAILURE_AMBIGUOUS_TARGET\x10\x03\x12\x1b\n\x17\x45\x44IT_FAILURE_NOT_VALUED\x10\x04\x12\x1e\n\x1a\x45\x44IT_FAILURE_INVALID_VALUE\x10\x05\x12\x1d\n\x19\x45\x44IT_FAILURE_INVALID_NAME\x10\x06\x12\x1a\n\x16\x45\x44IT_FAILURE_NOT_NAMED\x10\x07\x12\"\n\x1e\x45\x44IT_FAILURE_RENAME_REFERENCED\x10\x08\x12\"\n\x1e\x45\x44IT_FAILURE_OVERLAPPING_EDITS\x10\t\x12\x1f\n\x1b\x45\x44IT_FAILURE_RESULT_INVALID\x10\n\x12\x1e\n\x1a\x45\x44IT_FAILURE_OWNER_UNKNOWN\x10\x0b\x12$\n EDIT_FAILURE_OWNER_NOT_NAMESPACE\x10\x0c\x12\x1d\n\x19\x45\x44IT_FAILURE_ILLEGAL_KIND\x10\r\x12\"\n\x1e\x45\x44IT_FAILURE_MEMBER_NAME_TAKEN\x10\x0e\x12\"\n\x1e\x45\x44IT_FAILURE_DELETE_REFERENCED\x10\x0f*\x92\x01\n\x11PrimitiveOperator\x12\"\n\x1ePRIMITIVE_OPERATOR_UNSPECIFIED\x10\x00\x12\x1c\n\x18PRIMITIVE_OPERATOR_EQUAL\x10\x01\x12\x1e\n\x1aPRIMITIVE_OPERATOR_GREATER\x10\x02\x12\x1b\n\x17PRIMITIVE_OPERATOR_LESS\x10\x03*n\n\x11\x43ompositeOperator\x12\"\n\x1e\x43OMPOSITE_OPERATOR_UNSPECIFIED\x10\x00\x12\x1a\n\x16\x43OMPOSITE_OPERATOR_AND\x10\x01\x12\x19\n\x15\x43OMPOSITE_OPERATOR_OR\x10\x02\x32\xe7\n\n\x0cSysMLService\x12\x44\n\rGetServerInfo\x12\x18.sysml.ServerInfoRequest\x1a\x19.sysml.ServerInfoResponse\x12>\n\tParseFile\x12\x17.sysml.ParseFileRequest\x1a\x18.sysml.ParseFileResponse\x12G\n\x0cParseSources\x12\x1a.sysml.ParseSourcesRequest\x1a\x1b.sysml.ParseSourcesResponse\x12;\n\tGetSymbol\x12\x17.sysml.GetSymbolRequest\x1a\x15.sysml.SymbolResponse\x12G\n\x0eGetDiagnostics\x12\x19.sysml.DiagnosticsRequest\x1a\x1a.sysml.DiagnosticsResponse\x12;\n\x08\x45valuate\x12\x16.sysml.EvaluateRequest\x1a\x17.sysml.EvaluateResponse\x12\x44\n\x0bInstantiate\x12\x19.sysml.InstantiateRequest\x1a\x1a.sysml.InstantiateResponse\x12J\n\rExecuteAction\x12\x1b.sysml.ExecuteActionRequest\x1a\x1c.sysml.ExecuteActionResponse\x12G\n\x0c\x45xecuteState\x12\x1a.sysml.ExecuteStateRequest\x1a\x1b.sysml.ExecuteStateResponse\x12\x38\n\x07\x43onvert\x12\x15.sysml.ConvertRequest\x1a\x16.sysml.ConvertResponse\x12\x41\n\nApplyEdits\x12\x18.sysml.ApplyEditsRequest\x1a\x19.sysml.ApplyEditsResponse\x12S\n\x10VerifyConstraint\x12\x1e.sysml.VerifyConstraintRequest\x1a\x1f.sysml.VerifyConstraintResponse\x12V\n\x11VerifyRequirement\x12\x1f.sysml.VerifyRequirementRequest\x1a .sysml.VerifyRequirementResponse\x12Y\n\x12VerifySatisfaction\x12 .sysml.VerifySatisfactionRequest\x1a!.sysml.VerifySatisfactionResponse\x12G\n\x0c\x45valuateCalc\x12\x1a.sysml.EvaluateCalcRequest\x1a\x1b.sysml.EvaluateCalcResponse\x12\x44\n\x0bRunAnalysis\x12\x19.sysml.RunAnalysisRequest\x1a\x1a.sysml.RunAnalysisResponse\x12\x32\n\x05Query\x12\x13.sysml.QueryRequest\x1a\x14.sysml.QueryResponse\x12S\n\x10RunDocumentQuery\x12\x1e.sysml.RunDocumentQueryRequest\x1a\x1f.sysml.RunDocumentQueryResponse\x12M\n\x0eRenderDocument\x12\x1c.sysml.RenderDocumentRequest\x1a\x1d.sysml.RenderDocumentResponseB*Z(github.com/Open-MBEE/OpenSysML/api/protob\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -48,14 +48,14 @@ _globals['_SYMBOLINFO_METADATAENTRY']._serialized_options = b'8\001' _globals['_QUERYRESULTELEMENT_PROPERTIESENTRY']._loaded_options = None _globals['_QUERYRESULTELEMENT_PROPERTIESENTRY']._serialized_options = b'8\001' - _globals['_FAILUREREASON']._serialized_start=9113 - _globals['_FAILUREREASON']._serialized_end=9260 - _globals['_EDITFAILURE']._serialized_start=9263 - _globals['_EDITFAILURE']._serialized_end=9804 - _globals['_PRIMITIVEOPERATOR']._serialized_start=9807 - _globals['_PRIMITIVEOPERATOR']._serialized_end=9953 - _globals['_COMPOSITEOPERATOR']._serialized_start=9955 - _globals['_COMPOSITEOPERATOR']._serialized_end=10065 + _globals['_FAILUREREASON']._serialized_start=9196 + _globals['_FAILUREREASON']._serialized_end=9343 + _globals['_EDITFAILURE']._serialized_start=9346 + _globals['_EDITFAILURE']._serialized_end=9887 + _globals['_PRIMITIVEOPERATOR']._serialized_start=9890 + _globals['_PRIMITIVEOPERATOR']._serialized_end=10036 + _globals['_COMPOSITEOPERATOR']._serialized_start=10038 + _globals['_COMPOSITEOPERATOR']._serialized_end=10148 _globals['_VERDICT']._serialized_start=23 _globals['_VERDICT']._serialized_end=225 _globals['_VERIFYCONSTRAINTREQUEST']._serialized_start=227 @@ -161,69 +161,71 @@ _globals['_ATTRIBUTEINFO']._serialized_start=6053 _globals['_ATTRIBUTEINFO']._serialized_end=6139 _globals['_VALUE']._serialized_start=6142 - _globals['_VALUE']._serialized_end=6624 - _globals['_ARRAY']._serialized_start=6626 - _globals['_ARRAY']._serialized_end=6685 - _globals['_VECTOR']._serialized_start=6687 - _globals['_VECTOR']._serialized_end=6729 - _globals['_VECTORQUANTITY']._serialized_start=6731 - _globals['_VECTORQUANTITY']._serialized_end=6784 - _globals['_COMPLEX']._serialized_start=6786 - _globals['_COMPLEX']._serialized_end=6828 - _globals['_ENUMLITERAL']._serialized_start=6830 - _globals['_ENUMLITERAL']._serialized_end=6901 - _globals['_VALUESEQUENCE']._serialized_start=6903 - _globals['_VALUESEQUENCE']._serialized_end=6950 - _globals['_QUANTITY']._serialized_start=6952 - _globals['_QUANTITY']._serialized_end=7076 - _globals['_MEASUREMENTREF']._serialized_start=7078 - _globals['_MEASUREMENTREF']._serialized_end=7161 - _globals['_UNITTERM']._serialized_start=7163 - _globals['_UNITTERM']._serialized_end=7247 - _globals['_UNITFACTOR']._serialized_start=7249 - _globals['_UNITFACTOR']._serialized_end=7296 - _globals['_DIAGNOSTIC']._serialized_start=7298 - _globals['_DIAGNOSTIC']._serialized_end=7372 - _globals['_SPAN']._serialized_start=7374 - _globals['_SPAN']._serialized_end=7468 - _globals['_SERVERINFOREQUEST']._serialized_start=7470 - _globals['_SERVERINFOREQUEST']._serialized_end=7489 - _globals['_SERVERINFORESPONSE']._serialized_start=7491 - _globals['_SERVERINFORESPONSE']._serialized_end=7550 - _globals['_QUERYREQUEST']._serialized_start=7552 - _globals['_QUERYREQUEST']._serialized_end=7635 - _globals['_QUERYRESPONSE']._serialized_start=7637 - _globals['_QUERYRESPONSE']._serialized_end=7697 - _globals['_QUERY']._serialized_start=7699 - _globals['_QUERY']._serialized_end=7771 - _globals['_CONSTRAINT']._serialized_start=7773 - _globals['_CONSTRAINT']._serialized_end=7897 - _globals['_PRIMITIVECONSTRAINT']._serialized_start=7899 - _globals['_PRIMITIVECONSTRAINT']._serialized_end=8014 - _globals['_COMPOSITECONSTRAINT']._serialized_start=8016 - _globals['_COMPOSITECONSTRAINT']._serialized_end=8120 - _globals['_QUERYRESULTELEMENT']._serialized_start=8123 - _globals['_QUERYRESULTELEMENT']._serialized_end=8283 - _globals['_QUERYRESULTELEMENT_PROPERTIESENTRY']._serialized_start=8234 - _globals['_QUERYRESULTELEMENT_PROPERTIESENTRY']._serialized_end=8283 - _globals['_RUNDOCUMENTQUERYREQUEST']._serialized_start=8285 - _globals['_RUNDOCUMENTQUERYREQUEST']._serialized_end=8395 - _globals['_DOCUMENTQUERYBINDING']._serialized_start=8397 - _globals['_DOCUMENTQUERYBINDING']._serialized_end=8476 - _globals['_DOCUMENTVALUE']._serialized_start=8479 - _globals['_DOCUMENTVALUE']._serialized_end=8692 - _globals['_DOCUMENTQUERYCOLUMN']._serialized_start=8694 - _globals['_DOCUMENTQUERYCOLUMN']._serialized_end=8729 - _globals['_DOCUMENTQUERYCELL']._serialized_start=8731 - _globals['_DOCUMENTQUERYCELL']._serialized_end=8788 - _globals['_DOCUMENTQUERYROW']._serialized_start=8790 - _globals['_DOCUMENTQUERYROW']._serialized_end=8888 - _globals['_RUNDOCUMENTQUERYRESPONSE']._serialized_start=8890 - _globals['_RUNDOCUMENTQUERYRESPONSE']._serialized_end=9000 - _globals['_RENDERDOCUMENTREQUEST']._serialized_start=9002 - _globals['_RENDERDOCUMENTREQUEST']._serialized_end=9066 - _globals['_RENDERDOCUMENTRESPONSE']._serialized_start=9068 - _globals['_RENDERDOCUMENTRESPONSE']._serialized_end=9110 - _globals['_SYSMLSERVICE']._serialized_start=10068 - _globals['_SYSMLSERVICE']._serialized_end=11451 + _globals['_VALUE']._serialized_end=6661 + _globals['_FUNCTION']._serialized_start=6663 + _globals['_FUNCTION']._serialized_end=6707 + _globals['_ARRAY']._serialized_start=6709 + _globals['_ARRAY']._serialized_end=6768 + _globals['_VECTOR']._serialized_start=6770 + _globals['_VECTOR']._serialized_end=6812 + _globals['_VECTORQUANTITY']._serialized_start=6814 + _globals['_VECTORQUANTITY']._serialized_end=6867 + _globals['_COMPLEX']._serialized_start=6869 + _globals['_COMPLEX']._serialized_end=6911 + _globals['_ENUMLITERAL']._serialized_start=6913 + _globals['_ENUMLITERAL']._serialized_end=6984 + _globals['_VALUESEQUENCE']._serialized_start=6986 + _globals['_VALUESEQUENCE']._serialized_end=7033 + _globals['_QUANTITY']._serialized_start=7035 + _globals['_QUANTITY']._serialized_end=7159 + _globals['_MEASUREMENTREF']._serialized_start=7161 + _globals['_MEASUREMENTREF']._serialized_end=7244 + _globals['_UNITTERM']._serialized_start=7246 + _globals['_UNITTERM']._serialized_end=7330 + _globals['_UNITFACTOR']._serialized_start=7332 + _globals['_UNITFACTOR']._serialized_end=7379 + _globals['_DIAGNOSTIC']._serialized_start=7381 + _globals['_DIAGNOSTIC']._serialized_end=7455 + _globals['_SPAN']._serialized_start=7457 + _globals['_SPAN']._serialized_end=7551 + _globals['_SERVERINFOREQUEST']._serialized_start=7553 + _globals['_SERVERINFOREQUEST']._serialized_end=7572 + _globals['_SERVERINFORESPONSE']._serialized_start=7574 + _globals['_SERVERINFORESPONSE']._serialized_end=7633 + _globals['_QUERYREQUEST']._serialized_start=7635 + _globals['_QUERYREQUEST']._serialized_end=7718 + _globals['_QUERYRESPONSE']._serialized_start=7720 + _globals['_QUERYRESPONSE']._serialized_end=7780 + _globals['_QUERY']._serialized_start=7782 + _globals['_QUERY']._serialized_end=7854 + _globals['_CONSTRAINT']._serialized_start=7856 + _globals['_CONSTRAINT']._serialized_end=7980 + _globals['_PRIMITIVECONSTRAINT']._serialized_start=7982 + _globals['_PRIMITIVECONSTRAINT']._serialized_end=8097 + _globals['_COMPOSITECONSTRAINT']._serialized_start=8099 + _globals['_COMPOSITECONSTRAINT']._serialized_end=8203 + _globals['_QUERYRESULTELEMENT']._serialized_start=8206 + _globals['_QUERYRESULTELEMENT']._serialized_end=8366 + _globals['_QUERYRESULTELEMENT_PROPERTIESENTRY']._serialized_start=8317 + _globals['_QUERYRESULTELEMENT_PROPERTIESENTRY']._serialized_end=8366 + _globals['_RUNDOCUMENTQUERYREQUEST']._serialized_start=8368 + _globals['_RUNDOCUMENTQUERYREQUEST']._serialized_end=8478 + _globals['_DOCUMENTQUERYBINDING']._serialized_start=8480 + _globals['_DOCUMENTQUERYBINDING']._serialized_end=8559 + _globals['_DOCUMENTVALUE']._serialized_start=8562 + _globals['_DOCUMENTVALUE']._serialized_end=8775 + _globals['_DOCUMENTQUERYCOLUMN']._serialized_start=8777 + _globals['_DOCUMENTQUERYCOLUMN']._serialized_end=8812 + _globals['_DOCUMENTQUERYCELL']._serialized_start=8814 + _globals['_DOCUMENTQUERYCELL']._serialized_end=8871 + _globals['_DOCUMENTQUERYROW']._serialized_start=8873 + _globals['_DOCUMENTQUERYROW']._serialized_end=8971 + _globals['_RUNDOCUMENTQUERYRESPONSE']._serialized_start=8973 + _globals['_RUNDOCUMENTQUERYRESPONSE']._serialized_end=9083 + _globals['_RENDERDOCUMENTREQUEST']._serialized_start=9085 + _globals['_RENDERDOCUMENTREQUEST']._serialized_end=9149 + _globals['_RENDERDOCUMENTRESPONSE']._serialized_start=9151 + _globals['_RENDERDOCUMENTRESPONSE']._serialized_end=9193 + _globals['_SYSMLSERVICE']._serialized_start=10151 + _globals['_SYSMLSERVICE']._serialized_end=11534 # @@protoc_insertion_point(module_scope) diff --git a/clients/python/opensysml/proto/sysml_pb2.pyi b/clients/python/opensysml/proto/sysml_pb2.pyi index 6418fbb28..46e1bb1c7 100644 --- a/clients/python/opensysml/proto/sysml_pb2.pyi +++ b/clients/python/opensysml/proto/sysml_pb2.pyi @@ -664,7 +664,7 @@ class AttributeInfo(_message.Message): def __init__(self, name: _Optional[str] = ..., type: _Optional[str] = ..., value: _Optional[_Union[Value, _Mapping]] = ..., unit: _Optional[str] = ...) -> None: ... class Value(_message.Message): - __slots__ = ("int_value", "real_value", "bool_value", "string_value", "instance_id", "sequence", "null", "quantity", "enum_literal", "unset", "complex", "array", "vector", "vector_quantity", "measurement_ref") + __slots__ = ("int_value", "real_value", "bool_value", "string_value", "instance_id", "sequence", "null", "quantity", "enum_literal", "unset", "complex", "array", "vector", "vector_quantity", "measurement_ref", "function") INT_VALUE_FIELD_NUMBER: _ClassVar[int] REAL_VALUE_FIELD_NUMBER: _ClassVar[int] BOOL_VALUE_FIELD_NUMBER: _ClassVar[int] @@ -680,6 +680,7 @@ class Value(_message.Message): VECTOR_FIELD_NUMBER: _ClassVar[int] VECTOR_QUANTITY_FIELD_NUMBER: _ClassVar[int] MEASUREMENT_REF_FIELD_NUMBER: _ClassVar[int] + FUNCTION_FIELD_NUMBER: _ClassVar[int] int_value: int real_value: float bool_value: bool @@ -695,7 +696,16 @@ class Value(_message.Message): vector: Vector vector_quantity: VectorQuantity measurement_ref: MeasurementRef - def __init__(self, int_value: _Optional[int] = ..., real_value: _Optional[float] = ..., bool_value: _Optional[bool] = ..., string_value: _Optional[str] = ..., instance_id: _Optional[int] = ..., sequence: _Optional[_Union[ValueSequence, _Mapping]] = ..., null: _Optional[str] = ..., quantity: _Optional[_Union[Quantity, _Mapping]] = ..., enum_literal: _Optional[_Union[EnumLiteral, _Mapping]] = ..., unset: _Optional[bool] = ..., complex: _Optional[_Union[Complex, _Mapping]] = ..., array: _Optional[_Union[Array, _Mapping]] = ..., vector: _Optional[_Union[Vector, _Mapping]] = ..., vector_quantity: _Optional[_Union[VectorQuantity, _Mapping]] = ..., measurement_ref: _Optional[_Union[MeasurementRef, _Mapping]] = ...) -> None: ... + function: Function + def __init__(self, int_value: _Optional[int] = ..., real_value: _Optional[float] = ..., bool_value: _Optional[bool] = ..., string_value: _Optional[str] = ..., instance_id: _Optional[int] = ..., sequence: _Optional[_Union[ValueSequence, _Mapping]] = ..., null: _Optional[str] = ..., quantity: _Optional[_Union[Quantity, _Mapping]] = ..., enum_literal: _Optional[_Union[EnumLiteral, _Mapping]] = ..., unset: _Optional[bool] = ..., complex: _Optional[_Union[Complex, _Mapping]] = ..., array: _Optional[_Union[Array, _Mapping]] = ..., vector: _Optional[_Union[Vector, _Mapping]] = ..., vector_quantity: _Optional[_Union[VectorQuantity, _Mapping]] = ..., measurement_ref: _Optional[_Union[MeasurementRef, _Mapping]] = ..., function: _Optional[_Union[Function, _Mapping]] = ...) -> None: ... + +class Function(_message.Message): + __slots__ = ("calc_id", "self_id") + CALC_ID_FIELD_NUMBER: _ClassVar[int] + SELF_ID_FIELD_NUMBER: _ClassVar[int] + calc_id: str + self_id: int + def __init__(self, calc_id: _Optional[str] = ..., self_id: _Optional[int] = ...) -> None: ... class Array(_message.Message): __slots__ = ("dimensions", "elements") diff --git a/clients/python/opensysml/values.py b/clients/python/opensysml/values.py index 35cbb7fb4..211c9f16c 100644 --- a/clients/python/opensysml/values.py +++ b/clients/python/opensysml/values.py @@ -400,6 +400,53 @@ def __str__(self) -> str: return str(self.unit) +@dataclass(frozen=True) +class Function: + """A calc held as a value: a calc definition, or a calc usage with an input + no read could supply, as ``Sq`` in ``Fn(Sq, 3.0)`` or the ``f`` of + ``in calc f {...}``. + + It is the declaration it is a value of, which is its identity: two functions + are equal exactly when ``calc_id`` and ``self_id`` are. A function closing + over the bindings of the behavior body it is declared in has no wire form; + the service sends it as an unsupported null. + + Attributes: + calc_id (str): FQN of the calc declaration (``Analysis::Sq``) + self_id (int): ID of the object the calc's feature names resolve + against, for a calc usage read off a part (``holder.scale``); 0 for + a function closing over no object. One sent to the service must name + an object of the runtime the value is read in. + """ + + calc_id: str + self_id: int = 0 + + @classmethod + def from_pb(cls, pb_fn) -> "Function": + """Build from a ``Function`` protobuf message. + + Raises: + UnsupportedValueError: If the message names no calc. + """ + if not pb_fn.calc_id: + raise UnsupportedValueError("function naming no calc") + return cls(pb_fn.calc_id, pb_fn.self_id) + + def to_pb(self) -> "sysml_pb2.Function": + """Encode as a ``Function`` message. + + Raises: + UnsupportedValueError: If the function names no calc. + """ + if not self.calc_id: + raise UnsupportedValueError("function naming no calc") + return sysml_pb2.Function(calc_id=self.calc_id, self_id=self.self_id) + + def __str__(self) -> str: + return self.calc_id + + def _is_number(value: object) -> bool: """Whether a value is an Integer or a Real as the wire keeps them apart: a bool is neither.""" return isinstance(value, (int, float)) and not isinstance(value, bool) @@ -678,8 +725,8 @@ def value_to_python(pb_value, resolve_instance=None): Returns: int, float, complex, bool, str, list, None, :data:`UNSET`, a - :class:`Quantity`, a :class:`MeasurementRef`, an :class:`Array`, a - :class:`Vector`, a :class:`VectorQuantity`, an + :class:`Quantity`, a :class:`MeasurementRef`, a :class:`Function`, an + :class:`Array`, a :class:`Vector`, a :class:`VectorQuantity`, an :class:`~opensysml.enumeration.EnumLiteral`, or the resolved instance object. A Complex is one ``complex``, never two floats; a Vector is one :class:`Vector`, never a list of numbers. @@ -703,6 +750,8 @@ def value_to_python(pb_value, resolve_instance=None): return Quantity.from_pb(pb_value.quantity) if kind == 'measurement_ref': return MeasurementRef.from_pb(pb_value.measurement_ref) + if kind == 'function': + return Function.from_pb(pb_value.function) if kind == 'instance_id': if resolve_instance is None: return pb_value.instance_id diff --git a/clients/python/tests/test_function.py b/clients/python/tests/test_function.py new file mode 100644 index 000000000..3aa6ea781 --- /dev/null +++ b/clients/python/tests/test_function.py @@ -0,0 +1,246 @@ +"""Tests for a calc held as a value as a Python value. + +A calc definition, or a calc usage with an input no read could supply — ``Sq`` +in ``Fn(Sq, 3.0)``, the ``f`` of ``in calc f {...}`` — travels in an arm of its +own, ``Value.function``, so it must arrive as one :class:`Function` naming the +declaration it is a value of and the object it was read against: not as the +unsupported null a service without the capability sends. +""" + +from unittest.mock import Mock + +import pytest + +from opensysml.capabilities import ( + CAPABILITY_COMPLEX_VALUES, + CAPABILITY_FEATURE_VALUES, + CAPABILITY_FUNCTION_VALUES, + CAPABILITY_MEASUREMENT_REFS, + CAPABILITY_STRUCTURED_VALUES, + CAPABILITY_VERIFICATION, + MissingCapabilityError, +) +from opensysml.connection import Connection +from opensysml.errors import ExecutionError, UnsupportedValueError +from opensysml.proto import sysml_pb2 +from opensysml.values import Array, Function, value_to_python + +from tests.service_gate import skip_or_fail_without_service +from tests.test_measurement_ref import make_connection + + +def pb_fn(calc_id, self_id=0): + return sysml_pb2.Value(function=sysml_pb2.Function(calc_id=calc_id, self_id=self_id)) + + +CURRENT = ( + CAPABILITY_COMPLEX_VALUES, + CAPABILITY_STRUCTURED_VALUES, + CAPABILITY_MEASUREMENT_REFS, + CAPABILITY_FUNCTION_VALUES, + CAPABILITY_FEATURE_VALUES, + CAPABILITY_VERIFICATION, +) +OLD = tuple(c for c in CURRENT if c != CAPABILITY_FUNCTION_VALUES) + + +# --- Reading ------------------------------------------------------------- + + +def test_a_function_decodes_as_the_calc_it_names(): + sq = value_to_python(pb_fn("Demo::Sq")) + assert sq == Function("Demo::Sq") + assert sq.self_id == 0 + assert str(sq) == "Demo::Sq" + + +def test_a_function_read_off_an_object_keeps_the_object(): + scale = value_to_python(pb_fn("Demo::Scaler::scale", 7)) + assert scale == Function("Demo::Scaler::scale", 7) + # The object is part of the identity: another object's read is another value. + assert scale != Function("Demo::Scaler::scale", 8) + assert scale != Function("Demo::Scaler::scale") + + +def test_a_function_nested_in_a_sequence_or_array_decodes_in_place(): + seq = sysml_pb2.Value(sequence=sysml_pb2.ValueSequence( + elements=[pb_fn("Demo::Sq"), pb_fn("Demo::Cube")] + )) + assert value_to_python(seq) == [Function("Demo::Sq"), Function("Demo::Cube")] + arr = sysml_pb2.Value(array=sysml_pb2.Array( + dimensions=[1], elements=[pb_fn("Demo::Sq")] + )) + assert value_to_python(arr) == Array((1,), (Function("Demo::Sq"),)) + + +def test_a_function_naming_no_calc_is_reported(): + with pytest.raises(UnsupportedValueError, match="naming no calc"): + value_to_python(pb_fn("")) + with pytest.raises(UnsupportedValueError, match="naming no calc"): + value_to_python(pb_fn("", 3)) + + +def test_a_function_survives_the_wire_bytes(): + for value in (pb_fn("Demo::Sq"), pb_fn("Demo::Scaler::scale", 7)): + again = sysml_pb2.Value() + again.ParseFromString(value.SerializeToString()) + assert again == value + assert value_to_python(again) == value_to_python(value) + + +def test_a_service_without_the_capability_still_reports_unsupported(): + """An older service sends a null naming the calc, which stays an error.""" + null = sysml_pb2.Value(null="unsupported: function Demo::Sq") + with pytest.raises(UnsupportedValueError, match="function Demo::Sq"): + value_to_python(null) + + +# --- Sending ------------------------------------------------------------- + + +def test_a_function_is_sent_as_its_own_arm(): + """Round trip: what the client sends is what it reads back.""" + conn = make_connection(Mock(), CURRENT) + sq = Function("Demo::Sq") + + sent = conn._python_to_value(sq) + assert sent.WhichOneof("kind") == "function" + assert sent.function.calc_id == "Demo::Sq" + assert sent.function.self_id == 0 + assert value_to_python(sent) == sq + + scale = Function("Demo::Scaler::scale", 7) + sent = conn._python_to_value(scale) + assert sent.function.self_id == 7 + assert value_to_python(sent) == scale + + nested = conn._python_to_value([1, [sq]]) + assert value_to_python(nested) == [1, [sq]] + + +def test_a_function_naming_no_calc_is_refused_before_it_is_sent(): + conn = make_connection(Mock(), CURRENT) + with pytest.raises(UnsupportedValueError, match="naming no calc"): + conn._python_to_value(Function("")) + + +def test_a_function_is_not_sent_to_a_service_without_the_capability(): + """An older service would read the unknown arm as null, so nothing is sent.""" + stub = Mock() + conn = make_connection(stub, OLD) + sq = Function("Demo::Sq") + + for value in (sq, [1, [sq]], Array((1,), (sq,))): + with pytest.raises(MissingCapabilityError) as excinfo: + conn.execute_action("Demo::apply", "hash", inputs={"f": value}) + assert excinfo.value.capability == CAPABILITY_FUNCTION_VALUES + with pytest.raises(MissingCapabilityError) as excinfo: + conn.calc("Demo::fn", "hash", arguments=[value, 3.0]) + assert excinfo.value.capability == CAPABILITY_FUNCTION_VALUES + stub.ExecuteAction.assert_not_called() + stub.EvaluateCalc.assert_not_called() + + # A number still travels: it never needed the capability. + stub.EvaluateCalc.return_value = sysml_pb2.EvaluateCalcResponse( + result=sysml_pb2.Value(real_value=9.0) + ) + assert conn.calc("Demo::sq", "hash", arguments=[3.0]).value == 9.0 + stub.EvaluateCalc.assert_called_once() + + +def test_a_function_is_sent_to_a_service_with_the_capability(): + stub = Mock() + stub.EvaluateCalc.return_value = sysml_pb2.EvaluateCalcResponse( + result=sysml_pb2.Value(real_value=9.0) + ) + conn = make_connection(stub, CURRENT) + + got = conn.calc("Demo::fn", "hash", arguments=[Function("Demo::Sq"), 3.0]).value + assert got == 9.0 + request = stub.EvaluateCalc.call_args.args[0] + assert request.arguments[0].WhichOneof("kind") == "function" + assert request.arguments[0].function.calc_id == "Demo::Sq" + + +FUNCTION_MODEL = """ +package Demo { + private import ScalarValues::*; + + calc def Sq { in v : Real; return : Real = v * v; } + calc def Cube { in v : Real; return : Real = v * v * v; } + calc def Fn { in calc f { in v : Real; return : Real; } in a : Real; return : Real = f(a); } + calc fn : Fn; + calc def UseFn { return : Real = Fn(Sq, 3.0); } + calc useFn : UseFn; + + calc def Identity { in calc f { in v : Real; return : Real; } return r = f; } + attribute pick = Identity(Sq); + attribute picks = (Identity(Sq), Identity(Cube)); + + part def Scaler { + attribute k : Real = 2.0; + calc scale { in x : Real; return : Real = x * k; } + } + part holder : Scaler; + attribute scaler = holder.scale; +} +""" + + +@pytest.mark.integration +class TestFunctionsAgainstTheService: + """What a caller actually gets back from the real service for a real model.""" + + def setup_method(self): + import grpc + + try: + self.conn = Connection(auto_start=False) + self.conn._stub.GetDiagnostics(sysml_pb2.DiagnosticsRequest(model_hash="")) + except grpc.RpcError as exc: + if exc.code() != grpc.StatusCode.NOT_FOUND: + self.conn = None + skip_or_fail_without_service( + f"the sysml-grpc service on localhost:50051 answered {exc.code()}" + ) + except Exception as exc: + self.conn = None + skip_or_fail_without_service( + f"no sysml-grpc service could be reached on localhost:50051 ({exc})" + ) + self.model = self.conn.load_from_content(FUNCTION_MODEL) + + def teardown_method(self): + conn = self.__dict__.get("conn") + if conn is not None: + conn.close() + + def test_the_service_advertises_function_values(self): + assert self.conn.server_info().has(CAPABILITY_FUNCTION_VALUES) + + def test_a_calc_definition_reads_as_a_function(self): + sq = self.conn.eval("Demo::pick", self.model.hash) + assert sq == Function("Demo::Sq") + + assert self.conn.eval("Demo::picks", self.model.hash) == [ + Function("Demo::Sq"), Function("Demo::Cube"), + ] + + def test_a_calc_read_off_an_object_names_the_object(self): + scale = self.conn.eval("Demo::scaler", self.model.hash) + assert isinstance(scale, Function) + assert scale.calc_id == "Demo::Scaler::scale" + assert scale.self_id != 0 + + def test_a_function_sent_as_a_calc_argument_is_invoked(self): + assert self.conn.calc("Demo::useFn", self.model.hash).value == 9.0 + got = self.conn.calc( + "Demo::fn", self.model.hash, arguments=[Function("Demo::Cube"), 2.0] + ).value + assert got == 8.0 + + def test_a_function_naming_no_calc_of_the_model_is_refused(self): + with pytest.raises(ExecutionError): + self.conn.calc( + "Demo::fn", self.model.hash, arguments=[Function("Demo::Missing"), 2.0] + ) diff --git a/clients/python/tests/test_wire_compat.py b/clients/python/tests/test_wire_compat.py index 8dd3d963b..bc271807c 100644 --- a/clients/python/tests/test_wire_compat.py +++ b/clients/python/tests/test_wire_compat.py @@ -312,6 +312,29 @@ def test_a_measurement_reference_is_an_added_value_arm(): assert older.SerializeToString() == payload +def test_a_function_is_an_added_value_arm(): + """The function arm is new field 16.""" + fields = sysml_pb2.Value.DESCRIPTOR.fields_by_name + assert fields["function"].number == 16 + fn_fields = sysml_pb2.Function.DESCRIPTOR.fields_by_name + assert {name: f.number for name, f in fn_fields.items()} == { + "calc_id": 1, "self_id": 2, + } + + value = sysml_pb2.Value(function=sysml_pb2.Function( + calc_id="Demo::Scaler::scale", self_id=7, + )) + payload = value.SerializeToString() + again = sysml_pb2.Value() + again.ParseFromString(payload) + assert again == value + + # A client whose schema predates the arm keeps the bytes intact. + older = sysml_pb2.ServerInfoRequest() + older.ParseFromString(payload) + assert older.SerializeToString() == payload + + def test_apply_edits_is_an_added_rpc(): """The edit RPC is new, so it displaces nothing a client already calls.""" service = sysml_pb2.DESCRIPTOR.services_by_name["SysMLService"] diff --git a/clients/rust/README.md b/clients/rust/README.md index 195ae9507..ad438fe3e 100644 --- a/clients/rust/README.md +++ b/clients/rust/README.md @@ -144,10 +144,10 @@ for: Decoding a response is never gated on capabilities: if a service sends an enum, unset value, complex number, array, vector, vector quantity, measurement -reference, or feature-value arm, the client understands that answer. Consumers +reference, function, or feature-value arm, the client understands that answer. Consumers can inspect `Capabilities::has` or use `Capabilities::require` when they need to gate their own use of `enum_values`, `unset_value`, `complex_values`, -`structured_values`, `measurement_refs`, `feature_values`, or another advertised +`structured_values`, `measurement_refs`, `function_values`, `feature_values`, or another advertised operation. ## Conformance runner diff --git a/clients/rust/conformance/src/compare.rs b/clients/rust/conformance/src/compare.rs index 0014f3e01..ce40dd10c 100644 --- a/clients/rust/conformance/src/compare.rs +++ b/clients/rust/conformance/src/compare.rs @@ -260,6 +260,10 @@ fn lookup_parts<'a>(value: &'a Value, parts: &[&str]) -> Option<&'a Value> { } } +/// Fields other than `Instance.id` that carry a runtime instance id: a value's +/// reference to an object and a function's bound object. +pub const RUNTIME_ID_KEYS: [&str; 2] = ["instance_id", "self_id"]; + pub fn label_instance_ids(value: &mut Value) { let mut labels = HashMap::new(); label_ids(value, &mut labels); @@ -276,11 +280,15 @@ fn label_ids(value: &mut Value, labels: &mut HashMap) { label_id(child, labels); } } - if let Some(child) = object.get_mut("instance_id") { - label_id(child, labels); + for key in RUNTIME_ID_KEYS { + if let Some(child) = object.get_mut(key) { + label_id(child, labels); + } } for (key, child) in object.iter_mut() { - if !(is_instance && key == "id") && key != "instance_id" { + let labeled = + (is_instance && key == "id") || RUNTIME_ID_KEYS.contains(&key.as_str()); + if !labeled { label_ids(child, labels); } } @@ -354,13 +362,15 @@ mod tests { "instance": {"id": 9, "type_symbol_id": "T", "feature_values": {}}, "instances": [{"id": 9, "type_symbol_id": "T", "feature_values": {}}, {"id": 12, "type_symbol_id": "T", "feature_values": {}}], - "value": {"instance_id": 12} + "value": {"instance_id": 12}, + "function": {"calc_id": "T::f", "self_id": 9} }); label_instance_ids(&mut actual); assert_eq!(actual["instance"]["id"], "@1"); assert_eq!(actual["instances"][0]["id"], "@1"); assert_eq!(actual["instances"][1]["id"], "@2"); assert_eq!(actual["value"]["instance_id"], "@2"); + assert_eq!(actual["function"]["self_id"], "@1"); } #[test] diff --git a/clients/rust/conformance/src/normalize.rs b/clients/rust/conformance/src/normalize.rs index 437a43956..d19466a04 100644 --- a/clients/rust/conformance/src/normalize.rs +++ b/clients/rust/conformance/src/normalize.rs @@ -1,3 +1,4 @@ +use crate::compare::RUNTIME_ID_KEYS; use serde_json::Value; pub const MODEL_HASH: &str = "${model_hash}"; @@ -16,13 +17,18 @@ fn normalize_inner(value: &mut Value, model_hash: &str) { && object.contains_key("id"); let is_server_info = object.contains_key("capabilities") && object.contains_key("version"); + // A function bound to no object leaves self_id at its default, which + // the wire renders as absent rather than as an id to label. + if object.get("self_id").and_then(Value::as_i64) == Some(0) { + object.remove("self_id"); + } for (key, child) in object.iter_mut() { if is_server_info && key == "version" { *child = Value::String(VERSION.to_owned()); } else if is_instance && key == "id" { // Runtime ids are labelled in one pass after all other // normalization, where the response-wide map is shared. - } else if key == "instance_id" { + } else if RUNTIME_ID_KEYS.contains(&key.as_str()) { // See compare::label_instance_ids. } else { normalize_inner(child, model_hash); @@ -49,3 +55,23 @@ fn is_absolute_path(value: &str) -> bool { value.starts_with('/') || (value.len() > 2 && value.as_bytes()[1] == b':' && value.as_bytes()[2] == b'\\') } + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + #[test] + fn a_function_bound_to_no_object_has_no_self_id() { + let mut unbound = json!({"result": {"function": {"calc_id": "F::Sq", "self_id": 0}}}); + normalize(&mut unbound, ""); + assert_eq!( + unbound, + json!({"result": {"function": {"calc_id": "F::Sq"}}}) + ); + + let mut bound = json!({"result": {"function": {"calc_id": "F::S::f", "self_id": 7}}}); + normalize(&mut bound, ""); + assert_eq!(bound["result"]["function"]["self_id"], 7); + } +} diff --git a/clients/rust/conformance/sysml.descriptor.binpb b/clients/rust/conformance/sysml.descriptor.binpb index 690d46606bfed8ab5a8f828426ed2643d6cc9d8b..4bec97d7cdc8d9fb8e05091a15906a10a56399d9 100644 GIT binary patch delta 7135 zcmZ`;d301&nt%1G-o2F<5O}GC5SBcGC~62xAVLCY76_{f>2_?pPj`x>0yTyzOeH9e za|(|Vq#XpjMr_$}7uvy&w3*XpIyNHcXtM`w7=svu;ULh;a++b8-*@l3#M6C-zwWQT z@AvI@dEwVj2flqi;J1%I|DL}++qbp(FWFC+NoS}XtZ9f<)kov8%$}}qNb1NQd5MLI zd9`_yyq_l)CFgx%_G*+E6&JJq1)7$1%;o7QSMXNJ?u#hr~5u8#@f<&aYh6E-A z{#lIRlAd!ZYxPq@{Qs@iZ}m@=~M?zTl_ zCfVII^bzcyuA!e`W|sY--!=oo9Jbyv-n4vKzxBJwF)3@~M#-IYJU3EX{Uh&%`uFNc zO*9rIx4CY-PXF4F2+xVQk45X}+HN@JMq~3E>fKn}t&4=K-Kuy4Y$Y1z&#zsWOExPj z9<79qTRABu6}OBt*pVa#&#>AAw#Kg|0ORk?_VqP+$r^LRb4!h zh=|IMGm|n{SsUo*x{u9`R?S5QwyaOIIufgo)-Nn{>mQ4|y7@@2E+V}oP)s-Sc(|&* zb|I({6VXIrVa20j_>SI5a4ks=%BcW^ z<&9QKPYp378?CgE{iZ2BLHdSehO%BZ-6>_-QGE74N2=;&oaFQNgj*8{*Eb*?n$QHA zo`}~jh`8Z7;b<&TpG#S)K}lBGt{N3N81b4K{60TiSMLs~oExvLj@0EPU@}s9N0B?+ zqv_WDxb$@>pzW$S^4tn^nzjWEPa;ycAX*hEB7e0}5+owBKW;o0amUPSNMJ6h^_WsG z51KWi)-_;YAfdIIDh!y6Ga3_vb`Kmq%=Dpns0a6|<&)(J_RtW;d&6?l?ALtQVZ$A^ z*)rY}$6MEjhC^|)73^-W_Mxo9se$i`rvF@3f@J_}%T#+95FXyOQdK_yL44Qh)mM4| zgok&n0onFHNiz^`6|p~7lnyt2FtfGGj1Nb_)-E#?=vJ#&AJ2>rX0}@W`r8eNbA-dT zS;l{eK->J#2?h%1Bspjkz5n!h8J2L+rX2vnL7SDDCPM}UugwY#&=3yV ztbsZ98gh{DupO4MOYHyCs$@PCcUb62H#o>A2cKA`BWXaWE}$BIKxm%kAgyL>1MP&>Kle?{q-u86KgK)2SV#j3gD3rZjJPKv6m7)TL z6uiAwR$seV@=PbNUtHWVJ~_%X!Q0=(Gr`-hvxgMC{VIDiFxp}5mT}nf$>V1kY;@O?|CxIaR$_lF16i@^mR-m734G{7IOwka!9hR-?0VzCo zP(5fM3MlDgRzL+01S+5k9#BeC)jk0P^_bPKdw=@{+7AULirFcuc0E4|OE5ciF#*Bs z)Wrk@vy+P1u84rA#Nbc&6%?3$6zG($7a)kItkjSfyq`3!-kJ6V6ZN^;7N@2nuBcT*L95dZ#P|27IsMDv4=1`wJ*=x6|8^aqOO4~husWfA}U0}$z; zxU8cAB$18=5X8$A%~`+ni2QFfKm5gMU%}n705`f^QUPx0iv&_IZ_q`ug!(+jVb3t* z*GxRI`>D_vXgsp`pb3!!tPS$4zx><(?(!4x{|MP|Ir zd|6}NvS@ftES{*3R=Hx%zL9B#;Jrw}-3cFs6kL2e zP;7-1+e^$;l>q`3P{jr)rCwTqpuVKjDx{6Hika$`144}f?_x3$U~pG4o~j|ZtJp0) z>=DST$YHCQu~9s^KQ~zfz11w(O~zA1d8}dHSr3GG098DI&|Sm4vmOvO*D&v_FCvF) z^(+B_3aGAmKq*DJ=m-M@buG4l7e8Ts2sL%iR z&66t{|B~(4?%)3t^&PjRkrlk}@V3WiiuxE4D-LIf?HvQN@uI6~NZ=)|K34vLKkTbJ znR@;;w;Y@AD;+PNR!Bk}2!JR+2TN5Q1(Yc|>OuiT0Xi5SCO<_Is_h8Ip=QJ;}_>tb5%1E2fvaGaDjx3tch)n=zv&$f<=;Z5BWnI@uKi z&FM*sp@}Y~dmMI_8Rwh-`#$4AxY0cQ#7JN0UT9uo$me}Q3d0vV6$B72t}r|WekRR8cvZBV{3*=P^Z9C*8S3%XE;CfutIWyt%urpg zvcCQ7hol+Q^}4wK)K3cTl?T~%W~wi6K=7`!?&{zG1n)W>9P<01*kMm|<5@1gJhd)a z49%xGj+k3uvX~w}&v0`I_e+n!5I;j=8iHcVe=!fJ!U3VIfGSBq=q~1&0~J-~yqMoQ z*rP(&*hQuJ0qlLJr%Z`9c5x}uM&dq%j+8j;Ic~fp+|w1I66ih0gMD!#2LdG&&oVBS zoX#DMHA=UPo9ZYBgyu5NZ;=iFVR0Gn<=AghCrhY6E4VmydQ%c>FjsJI1_7nayL$k^ zT*1A&r-TmOmE2TG0wE7T^>hXVbtMm}_aPvtD|xnSf3BPYHgU6GR;$l_kp8lQPwu!# zInz>FV)`)$kg16UL`Y5C+v|X^+r+(b0+fa@PBU#2-x%yS+*l_9-#yUdH{8mw2ipP5 zq|&@1J~%fpi6z*taMPtykOV2b!Ux@Ir%6Q!S93Ev$8vMV=Sct1p&H=VERW&?Wi{_V zz@q@IK@Es9;U4A8WoVr@5k-MjVFRTNR(rW7>Qd(`?I|3jD zv5k&^<%)>Bwu_?|o-3UwAMM-q24B_}~|ClB_M!#Rmk+U2!30EE~8)fEW{-Cf-KzyO5JUEKRNnM4jh<>sx@ArMX# zKsDEZi0)IKs;(12soW=9eIf(H)TcaWko_XHpxj}5xN)@kg-bnrBg>(>hvT-TYoeU4 ziM`zX(z_;rA>PaJ9;RctoZ{an_Fo>F#1g^py_nRht_zn^?9 zkY^o)bo2shw{%1u?24F<-h*8cqrUB3Zs{!ys5(_{27Uo~X34|2XLo|W;GF$ZVuz2`y!}7~bMr$5WH$NbFhq?E+cR=tCQ@FpBJnjUJ zh!6kv@ThW=!`~4-Re<0f;VEi!0)lshcr>Y#9oE5(uf^0W_uW4kdL8-%o=lxS#=S2e zAlL*{IRHZUm_8f;Ve=Rr4nK#@DGux8#<$|+l?Q)01$vzvUjx*RDP;469$_Gq7*MqX z5b2)KBMb=Y362q_#sb2{37X(KxR~m&)7&^KR{!t3w5iZL&4ZcpZ#+}U#Tn7}>Z~M| zFnETW>QAYFP&~tXswo46!83ZwrqURF$IaWNHz0%ts6qpTrSEvEy8Hp9a{nBy3`^hf z!ME8PXmd?-*adD}4&Z=WDOP?T5* zPgP$4fS_LF+3Ix$2!|Irp0!lfX%zY;eHH`35}>w3pc<*|9XgV~n@?d{?7*D5ougSxBEY_HYYkC?0p?OVj20+-nMw?+h zovG6)zC}T^Pu2?2<3dJLH{YVs_zf2Dqnal`sgt60Q4oJ4dJ;j+aM}41p7~|+(#!KR8@j^ZHmV^{Wuh=UIo6M<;ECk+T^;$!nK=8H$_q{Mt=MEY*n7shbRLI@H*4(NglfCe}}>;5W&AZ~QEO4R%kd+$GVIlIJy&XJ>NZ zjZG~)5}?xu0143P1AquSeV!lzIwj91q!ZW1`dJnHXY${BHkMzD;j=1N5#3LKYbC(e zWYymLT6iSDRv!Q)z*bdOZc_%)ZB_a49tp5jO{k6@mjF{@{elW!O6LB)y*LHK7gTOo zG=ulX%ybg!(u zF|xj4qU@EW_wUlD+E^b@!Mn-q15a1fVtGL2hDW!PcWo|+^+6TBm0UP*QxRhDpvt+a zsg(@6Rd%eM8i=~UQjgentH||(Q6hFrKV(7FO3{Z@)+G-i1zqx>9W8VF1VnmBjU75J zdPMd^ZAdeZB)xxFJC#S2M|?9ult+9sL6k?NnLSPj_9nkN_;y`wSVDt(eZN42y{asq zCSM|!>e5lsry>2Ri}f)ToKam*cDH5nb(kKLz4Nd*t&=XCNG86udIpbVbHZl>VtK-6 z1LE|AWb=v>GTNuY(Us38n-5kd;o(Q8)`fCn@T2PlXl>H8Ks@wGTKy$9G>z!*>OOor zQ&(p@(LWelI?=C6-3i7h%6?Uzj~pBC#+RPeb<`j0%2pN8cpGUK=6bDETMRwc{c zxv|JI^_=xSK`hVu-3H?Gtle!^$lzywqfWC^3c8I7+R=2Qf=E9T={zf?j`J$4uB?g#8wcX?D`}jp+vyVSq6#Z) zI0z}|iUjRw=?FiH^rFg-jOLQej94$z!Gk(eIit|B_>RA8TXN66i|;HXf9jc1J_Fq{ zO>$QeV}>NRJSp^EQRI;T%XJz6#BjMz1Aqirt_dKy&5+zy=+JcrLJGRvKs%bQ2N3BB zot8C2cGOB8y2S#~V{nqTg}`W6YE$MB?Mi*gu;^Oqs*m+UI(Rr4=)JmFkKIE$H^k;s zFLkWa>7fsi59sm%v0bIpLm$NFDxDts^%8KkP7i$uDQIkb(2mkBImC~c^h zTGr@viXgP0*NW#gniI@vWm~NA9jq5x>pKV`1-(>`h_#x1;-qp!to0qNmsNV*Ivu(L z1VjsZZ5Hi1&Fj!yFR(?=(7BESgqH&(Y!Ol?95oV>tKKK*P}D@ zGZ|>txytBmIXPxZXE!D@-|MdDkw6=LTR|*0>U5ex0&SEePgGbTqx*C?vGPboSIeLJ zOS>NH`}<7uOnYtNcelqteD2d_u1TO(#qO{M@wrd)(!Y%?8)AJ>2X7~HPu-kv!0w>V z<)bT!)gVcB>*V3jtBXA2vD;?}V%e?JcPxm#=-Sr|;eCrLLoF4x0eP#DlJNAlV+(W$ruzksj6KukgkM zIjX;TRrHbsnHB5fI{2XL8-JOS$3LOybcbSpKSfACrF)o~0lyAiDtj>+$l*?utC7*LTxpUy^TApd$IUTy= z5ybMGHka4{ARf=@5pmQhGubE&`a&mf4E(ytGs-Xg8U*cFdY*$QzwoC~qa0*k>d+Ml zQ3vSmr6AHTb>3aRK%`&lszUS!7Zkjx!?BgGW(v38@sCUIx~FjOlH`9sdbznVEMXlm z=2k37N*Db)0P%a#FDKACVmaLsg$mBr;I zEJzdNHM(Dn$E1*M1P&H70F8NSoG3B2LQSS`%KLN@e4LuHjAgKwE3l8w_ZT zD+*DioMk#?;& z;kas3sJ^4^Z_}3Wv-{m{cCkXV-muy8Rs?OJ$JM$}z2xq%HR6k(?G{8Mt=}-*urfZC zJ~qetal>bJ^Jizuo3VV{@V08CkIji&KzAzwk#-omEzJgzc1XJ( zl>@mY)=!(@KTM|b7h&PnrEM)s?p*rQySnZ>pUsrFAbi^7s_e_HMT+W7)_p#4Dvu=B z>5~I7>@?}S4J1OR;l1)}*_bU7|Cyxk^UVmQs%Hj;Qq?miJq#E{_>AFlx6TPEYfJL- z7pvyA*emfCzo0-$++xbAY)v43wwS^))ARZ&%xeVVNndnEuZ20+u&NtGqd0G2w3G z-W)K)uNbbKa#FWS{@atS|M#sTk0ig{q~DbwhTHwl1BtlZj2dl|ZpW83hC>9 zDKd)mbyND>8p`(_HNK`4f7s%=CVfeqccO1YHdvw)_1d z0pfGN91=gk=e$^Vo8YYtyEC`{XdZUmhD*0h$2{?Q$S+}t7K3g&K*~MjmoSL*kYNeS z#Dau4BrEuCLfjDRcTDhZ`#)zgKP|rj!*@(>l>JlX28q$relC-_wa7CrdrauQ3_vV< z%y75PKwS3tb#{X+q+SzVY2!g82fE}yJoTC~cOw9;@wjSljHh06`4!OD4>f166KgFa~}gB(qpE|-P1s#95Y;SrJ3_3 z-{bx$hVTS>Pm=F(Q#K}=B<*9^T90){Gl;9>ay0*!yk8c?y3YiEG3{%znV#|mnD&|6 zIC~f`kc8it!?>157Qy>|5r7!J@Am+R&-Y~ybjX>yK=M0n+PhWeF{HBNPY+5Zozte& zeb6zA^t9oxm;1?PVXV&>{&a5Nq%;4tXd#woOm4g_dZA=9VAAhT2(O@9l_0hQerJJr Z9bjkuME~sWCHF3=dHb`HY%UlT{6BulMnV7p diff --git a/clients/rust/opensysml/src/domain.rs b/clients/rust/opensysml/src/domain.rs index f79c0a389..e78a7bacf 100644 --- a/clients/rust/opensysml/src/domain.rs +++ b/clients/rust/opensysml/src/domain.rs @@ -381,6 +381,24 @@ pub struct MeasurementRef { pub unit_id: Option, } +/// A calc held as a value: a calc definition, or a calc usage with an input no +/// read could supply, as `Sq` in `Fn(Sq, 3.0)` or the `f` of `in calc f {...}`. +/// +/// It is the declaration it is a value of, which is its identity: two functions +/// are equal exactly when both fields are. A function closing over the bindings +/// of the behavior body it is declared in has no wire form; the service sends +/// it as an unsupported [`Value::Null`], as does a service without +/// `function_values` for every function. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct Function { + /// FQN of the calc declaration (`Analysis::Sq`). + pub calc_id: String, + /// ID of the object the calc's feature names resolve against, for a calc + /// usage read off a part (`holder.scale`); `None` for a function closing + /// over no object. + pub self_id: Option, +} + /// A runtime value returned by the service. #[derive(Clone, Debug, PartialEq)] pub enum Value { @@ -410,6 +428,8 @@ pub enum Value { VectorQuantity(VectorQuantity), /// A bare measurement unit. MeasurementRef(MeasurementRef), + /// A calc held as a value. + Function(Function), /// Explicit null value. Null, /// A materialized feature with no value. @@ -468,6 +488,15 @@ pub(crate) fn value_from_wire(value: wire::Value) -> Result { wire::value::Kind::MeasurementRef(v) => { Ok(Value::MeasurementRef(measurement_ref_from_wire(v)?)) } + wire::value::Kind::Function(v) => { + if v.calc_id.is_empty() { + return Err(Error::Decode("a function names no calc".to_owned())); + } + Ok(Value::Function(Function { + calc_id: v.calc_id, + self_id: (v.self_id != 0).then_some(v.self_id), + })) + } wire::value::Kind::EnumLiteral(v) => Ok(Value::EnumLiteral(EnumLiteral { literal_id: v.literal_id, enumeration_id: v.enumeration_id, @@ -495,6 +524,7 @@ fn kind_name(kind: &wire::value::Kind) -> &'static str { wire::value::Kind::Vector(_) => "vector", wire::value::Kind::VectorQuantity(_) => "vector_quantity", wire::value::Kind::MeasurementRef(_) => "measurement_ref", + wire::value::Kind::Function(_) => "function", } } @@ -1209,6 +1239,48 @@ mod tests { } } + fn function(calc_id: &str, self_id: i64) -> wire::Value { + wire::Value { + kind: Some(wire::value::Kind::Function(wire::Function { + calc_id: calc_id.to_owned(), + self_id, + })), + } + } + + #[test] + fn a_function_is_the_calc_it_names_read_against_an_object_or_none() { + assert_eq!( + value_from_wire(function("Demo::Sq", 0)).ok(), + Some(Value::Function(Function { + calc_id: "Demo::Sq".to_owned(), + self_id: None, + })) + ); + assert_eq!( + value_from_wire(function("Demo::Scaler::scale", 7)).ok(), + Some(Value::Function(Function { + calc_id: "Demo::Scaler::scale".to_owned(), + self_id: Some(7), + })) + ); + + // Naming no calc is malformed at any depth. + assert!(matches!( + value_from_wire(function("", 0)), + Err(Error::Decode(message)) if message.contains("names no calc") + )); + let nested = wire::Value { + kind: Some(wire::value::Kind::Sequence(wire::ValueSequence { + elements: vec![function("", 3)], + })), + }; + assert!(matches!( + value_from_wire(nested), + Err(Error::Decode(message)) if message.contains("names no calc") + )); + } + #[test] fn a_measurement_reference_keeps_its_unit_reduction_and_declaration() { let km = wire_term(1000.0, &[("SI::metre", 1.0)]); diff --git a/clients/rust/opensysml/src/lib.rs b/clients/rust/opensysml/src/lib.rs index 1ddb74ec6..f6dfa934e 100644 --- a/clients/rust/opensysml/src/lib.rs +++ b/clients/rust/opensysml/src/lib.rs @@ -16,8 +16,8 @@ pub mod wire { pub use connection::Connection; pub use domain::{ Array, Capabilities, Complex, Diagnostic, EnumLiteral, EvalOptions, Evaluation, FeatureValue, - Instance, Instantiation, Language, Magnitude, MeasurementRef, Model, ParseOptions, Quantity, - ServerInfo, Span, Symbol, UnitFactor, UnitTerm, Value, Vector, VectorQuantity, + Function, Instance, Instantiation, Language, Magnitude, MeasurementRef, Model, ParseOptions, + Quantity, ServerInfo, Span, Symbol, UnitFactor, UnitTerm, Value, Vector, VectorQuantity, }; pub use error::{Error, Status}; diff --git a/clients/rust/opensysml/src/proto/sysml/sysml.rs b/clients/rust/opensysml/src/proto/sysml/sysml.rs index 882fc3bbb..d1abbae21 100644 --- a/clients/rust/opensysml/src/proto/sysml/sysml.rs +++ b/clients/rust/opensysml/src/proto/sysml/sysml.rs @@ -781,7 +781,7 @@ pub struct AttributeInfo { /// Value represents a runtime-evaluable value #[derive(Clone, PartialEq, ::prost::Message)] pub struct Value { - #[prost(oneof="value::Kind", tags="1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15")] + #[prost(oneof="value::Kind", tags="1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16")] pub kind: ::core::option::Option, } /// Nested message and enum types in `Value`. @@ -828,8 +828,29 @@ pub mod value { /// a unit by itself, no magnitude #[prost(message, tag="15")] MeasurementRef(super::MeasurementRef), + /// a calc as a value, named by its declaration + #[prost(message, tag="16")] + Function(super::Function), } } +/// Function is a calc held as a value: a calc definition, or a calc usage with +/// an input no read could supply, as `Sq` in `Fn(Sq, 3.0)` or the `f` of +/// `in calc f {...}`. It crosses as the declaration it is a value of, which is +/// its identity: two functions are the same exactly when calc_id and self_id +/// are. A function closing over the bindings of the behavior body it is +/// declared in has no wire form and crosses as the null arm. +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct Function { + /// FQN of the calc declaration ("Analysis::Sq"). Its identity. + #[prost(string, tag="1")] + pub calc_id: ::prost::alloc::string::String, + /// ID of the object the calc's feature names resolve against, for a calc + /// usage read off a part (`holder.scale`); 0 for a function closing over no + /// object. Sent by the service; a client sending one must name an object of + /// the runtime the value is read in, or the value is rejected. + #[prost(int64, tag="2")] + pub self_id: i64, +} /// Array is a Collections::Array: its elements flattened in row-major order /// under its dimensions, compared by content rather than by the object read. #[derive(Clone, PartialEq, ::prost::Message)] @@ -1042,6 +1063,11 @@ pub struct ServerInfoResponse { /// refused with UNIMPLEMENTED rather than read as another /// value. Separate from structured_values, which a client /// built before this arm existed may already claim. + /// "function_values" - a Value carries a calc held as a value as function, + /// named by its declaration, rather than reporting it as an + /// unsupported null, and one is accepted as an action input + /// or calc argument; without it, one is refused with + /// UNIMPLEMENTED rather than read as another value. /// "apply_edits" - the ApplyEdits RPC edits a parsed model's own source, /// preserving everything the edit did not touch. /// "document_query" - the RunDocumentQuery RPC runs a named document query diff --git a/clients/rust/opensysml/tests/client.rs b/clients/rust/opensysml/tests/client.rs index fd0bfe691..c06c4075a 100644 --- a/clients/rust/opensysml/tests/client.rs +++ b/clients/rust/opensysml/tests/client.rs @@ -7,7 +7,7 @@ use std::process::Command; use std::thread; use std::time::Duration; -use opensysml::{Complex, Connection, Error, EvalOptions, Magnitude, Value, Vector}; +use opensysml::{Complex, Connection, Error, EvalOptions, Function, Magnitude, Value, Vector}; fn service_or_skip() -> Option { match Connection::private() { @@ -323,6 +323,52 @@ fn a_bare_measurement_reference_arrives_with_its_reduction_and_declaration() { ); } +#[test] +fn a_calc_held_as_a_value_arrives_as_the_function_it_names() { + let Some(connection) = service_or_skip() else { + return; + }; + assert!(connection.capabilities().has("function_values")); + let model = match connection.parse_content( + "package Demo { + private import ScalarValues::*; + calc def Sq { in v : Real; return : Real = v * v; } + calc def Fn { in calc f { in v : Real; return : Real; } in a : Real; return : Real = f(a); } + calc def Identity { in calc f { in v : Real; return : Real; } return r = f; } + attribute pick = Identity(Sq); + attribute nine = Fn(Sq, 3.0); + part def Scaler { + attribute k : Real = 2.0; + calc scale { in x : Real; return : Real = x * k; } + } + part holder : Scaler; + attribute scaler = holder.scale; + }", + &Default::default(), + ) { + Ok(model) => model, + Err(error) => panic!("parse failed: {error}"), + }; + let eval = |expr: &str| match model.evaluate(expr, &EvalOptions::default()) { + Ok(evaluation) => evaluation.result, + Err(error) => panic!("evaluating {expr} failed: {error}"), + }; + + assert_eq!( + eval("Demo::pick"), + Value::Function(Function { + calc_id: "Demo::Sq".to_owned(), + self_id: None, + }) + ); + assert_eq!(eval("Demo::nine"), Value::Real(9.0)); + let Value::Function(scale) = eval("Demo::scaler") else { + panic!("Demo::scaler should be a function"); + }; + assert_eq!(scale.calc_id, "Demo::Scaler::scale"); + assert!(scale.self_id.is_some_and(|id| id > 0)); +} + #[test] fn blocking_calls_work_inside_a_runtime() { let Some(connection) = service_or_skip() else { diff --git a/cmd/conformance/normalize.go b/cmd/conformance/normalize.go index 882487086..c55688e37 100644 --- a/cmd/conformance/normalize.go +++ b/cmd/conformance/normalize.go @@ -24,6 +24,7 @@ var normalizedIDs = map[string]bool{ "sysml.Instance.id": true, "sysml.Value.instance_id": true, "sysml.Verdict.instance_id": true, + "sysml.Function.self_id": true, } // integer and unsigned are normalized integral values. They are distinct from diff --git a/cmd/conformance/normalize_test.go b/cmd/conformance/normalize_test.go index 0ed50e819..6d91919f6 100644 --- a/cmd/conformance/normalize_test.go +++ b/cmd/conformance/normalize_test.go @@ -45,6 +45,7 @@ func TestInstanceIDsAreLabelledInOrderOfAppearance(t *testing.T) { Id: 41, FeatureValues: map[string]*pb.FeatureValue{ "engine": {Value: &pb.Value{Kind: &pb.Value_InstanceId{InstanceId: 77}}}, + "scale": {Value: &pb.Value{Kind: &pb.Value_Function{Function: &pb.Function{CalcId: "T::scale", SelfId: 41}}}}, }, }, Instances: []*pb.Instance{{Id: 41}, {Id: 77}}, @@ -56,6 +57,9 @@ func TestInstanceIDsAreLabelledInOrderOfAppearance(t *testing.T) { if got, _ := lookup(tree, "instance.feature_values.engine.value.instance_id"); got != "@2" { t.Errorf("nested instance_id = %v, want @2", got) } + if got, _ := lookup(tree, "instance.feature_values.scale.value.function.self_id"); got != "@1" { + t.Errorf("function self_id = %v, want the owner's label @1", got) + } if got, _ := lookup(tree, "instances.1.id"); got != "@2" { t.Errorf("instances.1.id = %v, want the same label @2", got) } diff --git a/cmd/conformance/pkgclient.go b/cmd/conformance/pkgclient.go index 7193dec3a..c2d9cbbfa 100644 --- a/cmd/conformance/pkgclient.go +++ b/cmd/conformance/pkgclient.go @@ -806,6 +806,11 @@ func valueToProto(value opensysml.Value) *pb.Value { UnitTerm: unitTermToProto(v.Term), UnitId: v.UnitID, }}} + case opensysml.Function: + return &pb.Value{Kind: &pb.Value_Function{Function: &pb.Function{ + CalcId: v.CalcID, + SelfId: int64(v.Self), + }}} default: return nil } @@ -895,6 +900,11 @@ func valueFromProto(value *pb.Value) (opensysml.Value, bool) { Term: unitTermFromProto(kind.MeasurementRef.GetUnitTerm()), UnitID: kind.MeasurementRef.GetUnitId(), }, true + case *pb.Value_Function: + return opensysml.Function{ + CalcID: kind.Function.GetCalcId(), + Self: opensysml.InstanceID(kind.Function.GetSelfId()), + }, true default: return nil, true } diff --git a/conformance/README.md b/conformance/README.md index 61051c386..0a2d91e62 100644 --- a/conformance/README.md +++ b/conformance/README.md @@ -108,7 +108,7 @@ These cannot be compared literally, so the runner replaces them before comparing | `ServerInfoResponse.version` | `${version}` | A build string; the contract is capabilities, not versions. | | Any string equal to the model hash of the scenario's model | `${model_hash}` | Content-addressed and free to change with the parser. | | Any absolute path (`Span.file`, echoed request paths) | `${path}` | Names the machine the service ran on. A relative name is kept. | -| Runtime instance ids (`Instance.id`, `Value.instance_id`, `Verdict.instance_id`) | `@1`, `@2`, … | Assigned per call. Labelled in order of first appearance, so a scenario can still state that a feature value names the same object as an entry of `instances`. | +| Runtime instance ids (`Instance.id`, `Value.instance_id`, `Verdict.instance_id`, `Function.self_id`) | `@1`, `@2`, … | Assigned per call. Labelled in order of first appearance, so a scenario can still state that a feature value names the same object as an entry of `instances`. | ### Ignored values @@ -150,6 +150,7 @@ What a request asks for is fixed per capability: | `complex_values` | Response-population capability: encode complex numbers as unsupported nulls; no request asks for them. | | `structured_values` | Encode arrays, vectors and vector quantities as unsupported nulls; refuse a request carrying one, at any depth. | | `measurement_refs` | Encode bare measurement references as unsupported nulls; refuse a request carrying one, at any depth. | +| `function_values` | Encode functions (a calc read as a value) as unsupported nulls; refuse a request carrying one, at any depth. | The default service reports and supports every capability above. `make conformance` also starts a second service with `strict_conformance` and `oslc_query` withheld, verifies that its advertisement diff --git a/conformance/fixtures/function.sysml b/conformance/fixtures/function.sysml new file mode 100644 index 000000000..8c4a16a0a --- /dev/null +++ b/conformance/fixtures/function.sysml @@ -0,0 +1,16 @@ +package F { + private import ScalarValues::*; + calc def Sq { in v : Real; return : Real = v * v; } + calc def Cube { in v : Real; return : Real = v * v * v; } + calc def Apply { in calc f { in v : Real; return : Real; } in a : Real; return : Real = f(a); } + calc apply : Apply; + calc def Identity { in calc f { in v : Real; return : Real; } return r = f; } + attribute pick = Identity(Sq); + attribute nine = Apply(Sq, 3.0); + part def Scaler { + attribute k : Real = 2.0; + calc scale { in x : Real; return : Real = x * k; } + } + part holder : Scaler; + attribute scaler = holder.scale; +} diff --git a/conformance/scenarios/01-server-info.json b/conformance/scenarios/01-server-info.json index 100a31d1a..c12f510e5 100644 --- a/conformance/scenarios/01-server-info.json +++ b/conformance/scenarios/01-server-info.json @@ -26,6 +26,7 @@ "complex_values", "structured_values", "measurement_refs", + "function_values", "apply_edits", "strict_conformance", "feature_values", diff --git a/conformance/scenarios/04-evaluate.json b/conformance/scenarios/04-evaluate.json index 403f06ae6..556fac213 100644 --- a/conformance/scenarios/04-evaluate.json +++ b/conformance/scenarios/04-evaluate.json @@ -415,6 +415,77 @@ ] } }, + { + "id": "evaluate/a_calc_definition_read_as_a_value_is_a_function_naming_it", + "description": "A calc used as a value travels as a function naming the calc by qualified name and no object, so a client can hand it back as an argument of another calc.", + "rpc": "Evaluate", + "requires_capabilities": [ + "function_values" + ], + "model": { + "fixture": "function.sysml" + }, + "request": { + "model_hash": "${model_hash}", + "expression": "F::pick" + }, + "expect": { + "response": { + "result": { + "function": { + "calc_id": "F::Sq" + } + } + }, + "absent": [ + "result.function.self_id" + ] + } + }, + { + "id": "evaluate/a_calc_of_an_object_read_as_a_value_names_the_object_it_computes_over", + "description": "A calc usage owned by a part reads the part's features when invoked, so its function carries the object it is bound to beside the calc's name.", + "rpc": "Evaluate", + "requires_capabilities": [ + "function_values" + ], + "model": { + "fixture": "function.sysml" + }, + "request": { + "model_hash": "${model_hash}", + "expression": "F::scaler" + }, + "expect": { + "response": { + "result": { + "function": { + "calc_id": "F::Scaler::scale", + "self_id": "@1" + } + } + } + } + }, + { + "id": "evaluate/a_calc_invoked_through_a_calc_parameter_answers_its_result", + "description": "A calc passed to another calc's calc-typed parameter is invoked through that parameter, so Apply(Sq, 3.0) is 9.", + "rpc": "Evaluate", + "model": { + "fixture": "function.sysml" + }, + "request": { + "model_hash": "${model_hash}", + "expression": "F::nine" + }, + "expect": { + "response": { + "result": { + "real_value": 9.0 + } + } + } + }, { "id": "evaluate/an_unknown_model_is_not_found", "rpc": "Evaluate", diff --git a/conformance/scenarios/10-evaluate-calc.json b/conformance/scenarios/10-evaluate-calc.json index bfac6c388..febde9d82 100644 --- a/conformance/scenarios/10-evaluate-calc.json +++ b/conformance/scenarios/10-evaluate-calc.json @@ -239,6 +239,118 @@ "result" ] } + }, + { + "id": "evaluate_calc/a_function_argument_is_the_calc_the_calc_invokes", + "description": "A function sent as an argument is bound to the calc it names and invoked through the calc-typed parameter, so applying Cube to 2 is 8.", + "rpc": "EvaluateCalc", + "requires_capabilities": [ + "function_values" + ], + "model": { + "fixture": "function.sysml" + }, + "request": { + "model_hash": "${model_hash}", + "symbol_id": "F::apply", + "arguments": [ + { + "function": { + "calc_id": "F::Cube" + } + }, + { + "real_value": 2.0 + } + ] + }, + "expect": { + "response": { + "result": { + "real_value": 8.0 + } + }, + "absent": [ + "error" + ] + }, + "expect_without_capability": { + "status": "UNIMPLEMENTED", + "status_message_contains": "function_values" + } + }, + { + "id": "evaluate_calc/a_function_argument_naming_no_calc_is_refused", + "description": "A function whose calc_id names nothing the model declares as a calc cannot be bound, so the answer reports the malformed argument in band rather than invoking anything.", + "rpc": "EvaluateCalc", + "requires_capabilities": [ + "function_values" + ], + "model": { + "fixture": "function.sysml" + }, + "request": { + "model_hash": "${model_hash}", + "symbol_id": "F::apply", + "arguments": [ + { + "function": { + "calc_id": "F::holder" + } + }, + { + "real_value": 2.0 + } + ] + }, + "expect": { + "contains": { + "error": "F::holder is not a calc" + }, + "response": { + "failure_reason": "FAILURE_REASON_EVALUATION" + }, + "absent": [ + "result" + ] + } + }, + { + "id": "evaluate_calc/a_function_argument_naming_no_object_is_refused", + "description": "Objects live only within the response that created them, so a function naming an object this call did not create is refused in band.", + "rpc": "EvaluateCalc", + "requires_capabilities": [ + "function_values" + ], + "model": { + "fixture": "function.sysml" + }, + "request": { + "model_hash": "${model_hash}", + "symbol_id": "F::apply", + "arguments": [ + { + "function": { + "calc_id": "F::Scaler::scale", + "self_id": 3 + } + }, + { + "real_value": 2.0 + } + ] + }, + "expect": { + "contains": { + "error": "self_id 3 names no object" + }, + "response": { + "failure_reason": "FAILURE_REASON_EVALUATION" + }, + "absent": [ + "result" + ] + } } ] } diff --git a/docs/internals/architecture.md b/docs/internals/architecture.md index cd71da7f4..d0e091078 100644 --- a/docs/internals/architecture.md +++ b/docs/internals/architecture.md @@ -247,6 +247,7 @@ Full evaluator with **user-defined calc invocation**, **constraint evaluation**, - Feature access `x.y.z` resolved against instance feature values - KerML operator library (`->select`, `->collect`, `size`, string ops) - **Calc invocation:** Resolve calc symbol → extract params/return → bind args to parameters → evaluate return expression +- **Function values** (`function_value.go`, `ValFunction`): a calc definition, a calc usage with an unsupplied input or an `in calc` parameter read as a value is the calc's lowered `calcShape` plus the environment it was read in — declaring scope, the object it was read off, and the enclosing body frames for a calc declared inside a behavior body. Invoking one (`f(a)` through a calc-typed parameter, or `SampledFunctions::Sample` applying its `calculation`) takes the calc invocation path (`invokeCalcShapeIn`), never a closure over statements; `ValExpr` remains the distinct kind for an expression body a collection operation evaluates per element - **Constraint evaluation:** Extract `assert`/`assume` members → evaluate boolean expressions → check satisfaction (with optional `not` negation) - **Requirement evaluation:** Extract `subject`/`assume`/`require`/`actor` members → validate bindings → evaluate conditions - **Scoped evaluation:** `EvalContext.scope` for name resolution, frame stack for parameter bindings diff --git a/docs/project/spec-compliance.md b/docs/project/spec-compliance.md index 2ddcce923..5b88f443f 100644 --- a/docs/project/spec-compliance.md +++ b/docs/project/spec-compliance.md @@ -177,7 +177,7 @@ Each row documents one behavioral semantic feature: | Recursive calc (direct or mutual) evaluates to its result, bounded by the run's calc depth budget | `invoke_calc.go` `Context.enterCalc` (`ErrCalcRecursionLimit`), budget from `budget.go` `BudgetsFromEnv` (`OPENSYSML_MAX_CALC_DEPTH`, default 10000, ceiling 25000) | `calc_recursion_factorial.sysml`, `calc_recursion_fibonacci.sysml`, `calc_recursion_mutual.sysml`, `calc_recursion_descends_sequence.sysml`, `calc_recursion_beyond_nesting_bound.sysml`, `invoke_calc_recursion_test.go:TestRecursiveCalcSpendsTheDepthBudget`, `:TestRecursiveCalcDepthIsNotAFixedBound`, `:TestMutuallyRecursiveCalcsEvaluate` | ✅ Faithful | | A recursion that does not terminate spends a budget and is reported, never hanging or exhausting the stack | `invoke_calc.go` `Context.enterCalc` (`ErrCalcRecursionLimit`) and `context.go` step counter (`ErrStepLimitExceeded`); the ceiling on the depth budget keeps the report ahead of the goroutine stack limit | `robustness_test.go:testCalcDirectRecursion`, `:testCalcMutualRecursion`, `:testCalcRecursionSpendsStepBudget`, `:testCalcRecursionAtDepthCeiling`, `budget_test.go:TestBudgetFromValue` (above the ceiling) | ✅ Faithful | | Step budget bounds calc evaluation | `context.go` step counter (`ErrStepLimitExceeded`), budget from `budget.go` `BudgetsFromEnv` (`OPENSYSML_MAX_STEPS`, default 10000000) | `robustness_test.go:testStepBudgetExceeded`, `budget_test.go:TestBudgetFromValue` | ✅ Faithful | -| Ahead-of-time native compilation of a calc (`sysml -compile`, C or Go): the compiled program computes, prints and fails as the interpreter does, or the calc is refused with a typed error naming the construct | `codegen/compile.go` `Compiler.Compile` (`UnsupportedError`), `codegen/compile_seq.go` (sequences: shapes, literals, ranges, indexing, `for`, the sequence and control libraries), `codegen/emit_c.go` `EmitC` + `emit_c_seq.go`, `codegen/emit_go.go` `EmitGo` + `emit_go_seq.go`, `repl/compile.go` `Session.CompileCalc`; scope and measurements in `native-compilation.md` | `repl/compile_test.go:TestCompiledCalcsAgreeWithInterpreter` (scalar and `Seq::*` cases, value and failure agreement), `:TestCompileRefusesWhatItCannotCompile` | ⚠️ Approximate — scalars and homogeneous sequences of Integer/Real/Boolean with any multiplicity, the element budget included; records, enums, mixed Integer/Real sequences and strings are refused, and compiled code has no step budget | +| Ahead-of-time native compilation of a calc (`sysml -compile`, C or Go): the compiled program computes, prints and fails as the interpreter does, or the calc is refused with a typed error naming the construct | `codegen/compile.go` `Compiler.Compile` (`UnsupportedError`), `codegen/compile_seq.go` (sequences: shapes, literals, ranges, indexing, `for`, the sequence and control libraries), `codegen/emit_c.go` `EmitC` + `emit_c_seq.go`, `codegen/emit_go.go` `EmitGo` + `emit_go_seq.go`, `repl/compile.go` `Session.CompileCalc`; scope and measurements in `native-compilation.md` | `repl/compile_test.go:TestCompiledCalcsAgreeWithInterpreter` (scalar and `Seq::*` cases, value and failure agreement), `:TestCompileRefusesWhatItCannotCompile` | ⚠️ Approximate — scalars and homogeneous sequences of Integer/Real/Boolean with any multiplicity, the element budget included; records, enums, mixed Integer/Real sequences, strings and function values are refused (an `in calc` parameter is refused as `parameter f binds a function value`, and so is every calc invoking one, transitively), and compiled code has no step budget | | Statement body (SysML v2 7.19, `CalculationBodyItem` carries the items of an action body): local declarations, assignment (`assign x := e;`), `if`/`else`, `while`, `loop … until`, `for`, `return` | `parser/behavior.go` `parseCalcBody`/`atCalcStatement` → `lower/calc_body.go` `CalcBody` → `runtime/statements.go` `stmtEngine` driven by `invoke_calc.go` `runCalcBody` | `calc_statement_body.sysml` (golden AST), `calc_iterative_factorial.sysml`, `calc_conditional_branch.sysml`, `calc_for_over_sequence.sysml`, `calc_loop_until_body.sysml` | ✅ Faithful | | A kind-less `x = e;` or `x := e;` in a calculation body, a constraint body or a nested statement body (a `while`/`loop`/`for`/`if` body, a state's entry/do/exit block, a transition effect) declares a feature of that body (`CalculationBodyItem` → `ActionBodyItem` → `NonOccurrenceUsageMember` → `DefaultReferenceUsage` with a `FeatureValue`, `SysML.xtext:632`), as the pilot reads it; `AssignmentNode` (`:1535`) begins with the `assign` keyword, so no kind-less member is an assignment, and the trailing expression reads such a local by name | `parser/behavior.go` `atNamedCalcMember` (`parseCalcBody`, `atConstraintBodyDeclaration`), `parseActionMember`, `atCalcStatement` | `parse/calc_default_reference_usage.golden`, `parser/f61_keywordless_members_test.go:TestF61AssignmentStaysAssignment` (a body right after the name, `twice { doc /* */ }`, is the usage's `UsageBody`, not a body expression), `conformance/calc_body_default_reference_usage.sysml` | ✅ Faithful (the pinned validator accepts every fixture) | | `ReturnParameterMember` (`:1961`) is `'return' UsageElement`, so a result may specialize without a name or a typing, its `Identification` may open with a short name, its `FeatureDeclaration` may open with a multiplicity, and a body may follow the identification directly — while a value or a body alone declares nothing: `return :> ISQ::power = e;`, `return r :> ISQ::speed = e;`, `return :> T[*] = e;`, `return result : T = e;`, `return :> T = e;`, `return ;`, `return [*] = xs;`, `return [*] :> xs;`, `return r { doc /* */ }`; `return = e;` and `return { … }` are reported once | `parser/behavior.go` `atReturnedUsage`, `parseResultMember` (`parseIdentification`, `parseRelationships`, `parseMultiplicity`) | `parse/calc_default_reference_usage.golden`, `behavior_test.go:TestParseResultMember_AnonymousAndBodiedForms`, `negative_test.go:calc_return_subsets_no_target`, `:calc_return_named_subsets_no_target`, `:calc_return_short_name_unclosed`, `:calc_return_short_name_empty`, `:calc_return_value_only`, `:calc_return_body_only`, `:calc_return_multiplicity_unclosed` | ✅ Faithful (the pinned validator accepts every form) | @@ -225,6 +225,12 @@ Each row documents one behavioral semantic feature: | A `return` in a calc that also states an output supplies the invocation's value only: an output keeps the value its own binding computes, so a body returning something else does not change what reading that output answers. Only the result parameter (`return : Real = …`) takes the returned value directly | `runtime/calc_usage.go` `runCalcUsage` (memoizing a returned value under an output's name only for `calcOutput.IsResult`) | `calc_valued_output_with_return.sysml` conformance (`Apart`: `ca.a` is 6 while `Apart(5)` is 500; `Together`, `Both`) | ✅ Faithful | | `%calc` on a calc usage lists the outputs of one evaluation; a chain into a usage evaluates at the prompt | `repl/meta.go` `doCalc`/`calcUsageOutputs` | `repl/runtime_commands_test.go:TestCalcUsageOutputsAtThePrompt` | ✅ Faithful | | A calc usage's outputs are evaluation results, not feature values of an object | `runtime/calc_usage.go` (no instance materialization) | `calc_usage_instance_slots.sysml` (the features fed by the outputs are feature values; the usage itself is not), pilot-exec-diff `w6d:calc-usage` | ⚠️ Approximate (unrefereeable: the pinned artifact answers a `CalculationUsage` node rather than an output value. `%instances` and export show the features valued from outputs, not the usage's outputs themselves) | +| A calc definition, a calc usage with an unsupplied input, or an `in calc` parameter named where a value is expected is a **function value** (KerML 1.1 §7.4.4: a Function is a Behavior with a `result`, an Expression a Step typed by one, and a feature reference to either denotes it; §8.3.4.8 `Function::result`, `FeatureReferenceExpression`; SysML v2 §7.17: a calc def is a Function, a calc usage an Expression). The value is the calc's lowered invocation interface (`calcShape`) together with the environment it was read in — its declaring scope and the object it was read off — and nothing else: no statement closure is built, and the value is invoked through the same path a calc usage invocation takes (`invokeCalcShapeIn`). Reading a calc usage whose inputs are all bound evaluates it as before; a library function the runtime implements natively (`RealFunctions::sqrt`, `floor`) reads as a value carrying that implementation, while a library operation that binds its arguments unevaluated (`SequenceFunctions::size` and the other `->` operations) is refused as `ErrNotAFunction` | `runtime/value.go` `ValFunction`, `runtime/function_value.go` `functionValue`/`EvalContext.functionValueOf`/`Context.readsAsFunction`/`EvalContext.calcAsValue`, `invoke_calc.go` `calcShapeOf` (a natively implemented library function computes), `eval.go` `evalFeatureReference`, `describe.go` (`the function Sq`), `trace.go` `FormatTraceValue` (`calc(Sq)`), `repl/meta.go` | `function_value_read.sysml`, `function_value_probe.sysml` (`Fn(Sq, 3.0)` is `9.0`), `function_value_calc_usage.sysml`, `function_value_library.sysml`, `robustness_test.go:function_value_of_a_built_in`, `value_kinds_test.go:TestFunctionValueIdentity`, `:TestEveryValueKindIsDispatched`, `eval_no_value_test.go`, `repl/evalin_test.go` | ✅ Faithful | +| An `in calc` parameter of a calc or an action (SysML v2 §7.17, §8.3.16 `CalculationUsage` as a parameter) accepts a function value or null and nothing else, positionally or by name; the body invokes it as `f(a)`, through a chain (`p.f(a)`), nested (`f(f(a))`) and as an argument to another calc-typed parameter, binding the callee's inputs positionally and by name as a direct invocation does. A calc usage bound as an action input (`in f = sq;`) is the function value it reads as | `runtime/invoke_calc.go` `calcParameter.checkFunction`, `function_value.go` `EvalContext.invokeFunction`, `eval.go` `evalInvocation`/`evalFeatureChain`, `parser/behavior.go` `parameterKindKeywords` (`calc`) | `function_value_probe.sysml` + `function_value_probe.trace.golden` (`TestExecutionTrace`), `function_value_named_args.sysml`, `function_value_chain_call.sysml`, `function_value_action_parameter.sysml`, parser golden `action_calc_parameter.sysml`, `robustness_test.go:function_value_call_of_a_non_function` (`ErrNotAFunction`), `:function_value_bound_to_a_non_function` (`ErrNotAFunction`), `:function_value_arity_mismatch` (`ErrCalcArity`), `testCalcUnboundParameter` (`ErrUnboundParameter`) | ✅ Faithful | +| A calc read off an object (`twice.scale`, a calc usage owned by a part) is a function value over that object: invoked later it reads that object's features, so the same calc read off two parts is two values. A calc declared inside a behavior body (a calc's or an action's) closes over the bindings of the run it was read in — its enclosing parameters and locals — and a function returned from a calc (`return : Unary = scale;`) keeps them after the run ends | `function_value.go` `functionValue.self`/`.enclosing`, `EvalContext.functionValueOf` (`enclosedByBehaviorBody`), `calc_usage.go` (a nested usage keeps the enclosing frames), `invoke_calc.go` `Context.invokeCalcShapeIn` | `function_value_feature_closure.sysml` (`Fn(twice.scale, 5.0) + Fn(thrice.scale, 5.0)` is `25.0`), `function_value_body_closure.sysml` (`Outer`, `Maker`), `function_value_sampled_closure.sysml` | ✅ Faithful | +| Function values are equal, and key a set, by the calc together with the object it was read off (`Sq == Sq`, `twice.scale != thrice.scale`); a value closing over a behavior body's bindings is equal only to itself, since two reads of it in one run are one value and reads in two runs are not. Adoption into another runtime rebinds an ordinary function value to the same calc of the adopting model, the object it was read off adopted with it; one closing over a body's bindings is refused as a typed error (the run those bindings belonged to has ended and cannot be reconstructed) | `value_equality.go` `valueEqual`/`valueKeyFunc` (`ValFunction` arm), `adopt.go` | `value_kinds_test.go:TestFunctionValueIdentity`, `adopt_test.go:TestAdoptRebindsAFunctionValue` | ✅ Faithful | +| `SampledFunctions::Sample(f, domain)` samples a user calc passed as its `in calc calculation` argument: the library's own body runs, `domainValues->collect { in x; new SamplePair(x, calculation(x)) }` invoking the function value inside the collection body, and `Range` of the result reads the samples back | the library body under `invoke_calc.go` `invokeCalcShapeIn`, `function_value.go` `EvalContext.invokeFunction` (from a collection body's frame), `collections.go` | `function_value_sampled.sysml` (`Range(Sample(Sq, (1.0, 2.0, 3.0)))` is `[1.0, 4.0, 9.0]`), `function_value_sampled_closure.sysml` (a calc read off a part, sampled) | ✅ Faithful | +| `SampledFunctions::SamplePair` arithmetic and `SampledFunctions::interpolateLinear` on the library's own examples: reading a `SamplePair`'s `domainValue` or `rangeValue` yields the one-element sequence `[1.0]`, and `-`/`*` refuse a sequence operand (`type mismatch: operator '-' is not defined for a Real and a sequence`). The cause is the `[0..*]`-inherited member read not reducing a singleton sequence to the scalar it denotes, which is independent of function values (the same failure reproduces with no function value involved) and is not papered over here with a `SamplePair`-specific unwrap | `runtime/eval.go` `chainMemberValue`/`evalArithmetic`, `instance.go` (scalar-feature admission reduces a singleton only where the feature is declared scalar) | reproduced by `SampledFunctions::interpolateLinear` and by `s.samples#(1).domainValue - 1.0` | ❌ Not implemented (the singleton reduction of a `[0..*]`-inherited member read; the failure is a typed error, not a wrong answer) | | Boolean operators evaluated at runtime (`and`, `or`, `xor`, `implies`, short-circuiting where they can) | `eval.go` `evalLogical` | `calc_boolean_operators.sysml` | ✅ Faithful | | Identity (`===`, `!==`), null coalescing (`??`, lazy) and remainder (`%`) evaluated at runtime | `eval.go` `evalIdentity`/`evalNullCoalesce`/`evalArithmetic` | `calc_identity_operators.sysml`, `calc_null_coalesce.sysml`, `calc_modulo_operator.sysml` | ✅ Faithful | | Value classification evaluated at runtime (`istype`, `hastype`, and `x @ T` with a value subject and an ordinary type, the third `ClassificationTestOperator` of the KerML 1.0 ClassificationExpression grammar beside `hastype` and `istype`, which the reference evaluates as `istype`; SysML v2 8.4.4.2 ClassificationExpression) | `eval.go` `evalTypeClassification`, `valueHasType`, `directValueType`, and `evalOperator` routing `OpAt` there through `classifiesValue` — `x @ T` is a value test when a subject is written and T resolves to a type that is not a metadata definition or metaclass (`semantics.IsMetadataType`), and the metadata test of the next row otherwise — a value's *direct* type is derived from what the value already holds (a scalar constant's value kind, a string's `String`, an object's classifier, a selected variant, an enumeration literal's enumeration, a quantity's numeric value), so no type field was added to `runtime.Value`; `istype` is `semantics.Model.Conforms` of that type to the operand and `hastype` is identity with it, and a sequence or set is classified elementwise | `eval_operator_test.go:TestTypeClassificationOperators` (the reference's 20-case truth table case by case), `:TestTypeClassificationFollowsSelectedVariant`, `conformance/w7d_type_classification.sysml` + `.expected.json` (the same table over the library scalar types), `robustness_test.go:type_classification_unresolved_type`, `:type_classification_undetermined_value_type`, `conformance/value_classification_at.sysml` + `.expected.json` (`n3 @ Integer`, `n3 @ Real`, `n3 @ String`, `seqInt @ Integer`, `car @ Vehicle` beside the same subjects under `hastype` and the metadata forms `belt @ Safety`, `belt @@ SysML::PartUsage`), pilot-exec-diff `w6d:istype-*`, `:hastype-*`, `at-*`, `real-at-integer`, `seq-at-integer`, `car-at-*` | ✅ Faithful — **externally refereed**: all 20 cases agree with the pinned reference, which they did not before (they were `ours-error`; the execution report moved from 31 to 51 agreeing of 94 cases), and the ten `@` cases agree too (65 agreeing of 104): `a : Integer = 3` answers `a @ Integer` and `a @ Real` `true`, `a @ String` `false`, and `car : Car` answers `car @ Vehicle` `true`, where before `a @ Integer` was reported as classifying no element. The referee settles what the direct type is: `nat3 : Natural = 3` answers `hastype Integer` `true`, so it is the value's type and not the declaring feature's, while `car : Car` answers `istype Vehicle` `true` and `hastype Vehicle` `false`. A mixed sequence is `false` and an empty one is `true`. A type operand that does not resolve is `ErrUnresolvedType` and a value whose direct type cannot be determined is `ErrUndeterminedValueType` — reported, never answered `false`. One local rule the referee does not cover: a feature declared `[0..1]` with no value classifies as the empty collection, because the evaluator otherwise reports it as valueless | @@ -632,6 +638,7 @@ same declarations through `analysis.go` and is unchanged. | An Array, a Vector and a VectorQuantity cross the API boundary whole, in both directions: `Value.array` carries `dimensions` and the elements flattened in row-major order (each element a `Value`, so an array of quantities or of arrays nests), `Value.vector` carries the numeric components as `Value`s so an Integer and a Real component stay distinct, and `Value.vector_quantity` carries one `Quantity` per component — magnitude, unit as written and reduced unit term, so a composed unit (`m/s`) and per-component units survive. Every client this repository ships maps them to a native shape that checks its own invariants — Go `opensysml.Array`/`Vector`/`VectorQuantity`, Python `Array`/`Vector`/`VectorQuantity` dataclasses, Node `{ kind: "array" | "vector" | "vectorQuantity" }`, Java `Value.ArrayValue`/`VectorValue`/`VectorQuantityValue`, Rust `Value::Array`/`Vector`/`VectorQuantity` — and one sent as an action input or calc argument decodes to the same runtime value; a malformed one (dimensions the elements do not fill, a non-positive extent, a non-numeric vector component, an empty vector quantity, a unit without its reduction) is a typed error on whichever side sees it first, never a value with a different shape | `grpc/convert.go` `arrayToProto`/`protoToArray`, `vectorToProto`/`protoToVector`, `vectorQuantityToProto`/`protoToVectorQuantity`, `ErrArrayDimensionNotPositive`, `ErrArrayShapeMismatch`, `ErrVectorComponentNotNumeric`, `ErrVectorQuantityEmpty`; `capability_response.go` (`structured_values` arm); `api/proto/sysml.proto` `Array`, `Vector`, `VectorQuantity`; `client/opensysml/value.go`, `convert.go`, `client.go` (`structured_values` preflight); `clients/python/opensysml/values.py`, `connection.py`; `clients/node/src/core/values.ts`; `clients/java/.../Value.java`, `internal/Protos.java`; `clients/rust/opensysml/src/domain.rs` | `grpc/convert_structured_test.go`, `client/opensysml/structured_test.go`, `structured_internal_test.go`, `clients/python/tests/test_structured.py`, `clients/node/test/values.test.ts`, `client.test.ts`, `clients/java/.../ProtosTest.java`, `ApiIntegrationTest.java`, `clients/rust/opensysml/src/domain.rs` tests, `tests/client.rs`; `conformance/scenarios/04-evaluate.json`, `10-evaluate-calc.json` (`structured.sysml`) over gRPC, Connect protobuf and Connect JSON | ✅ Faithful (advertised as the `structured_values` capability; a service withholding it reports an unsupported null naming the value and refuses one sent to it with `UNIMPLEMENTED`, the clients refusing before the round trip; a client built before the arms existed reads each as an unknown `Value` arm) | | A bare measurement reference crosses the API boundary whole, in both directions: `Value.measurement_ref` carries the unit as written, its reduced unit term (required wherever the unit names one, as `Quantity` requires it) and the canonical id of the declaration a named unit is (`SI::metre`), which a composed unit (`m / s`, a `DerivedUnit`) omits. Every client this repository ships maps it to a typed reference — Go `opensysml.MeasurementRef`, Python `MeasurementRef` (over `Unit`), Node `{ kind: "measurementRef" }`, Java `Value.MeasurementRefValue`, Rust `Value::MeasurementRef` — and one sent as a calc argument decodes to the same `ValMeasurementRef`, so `ConvertQuantity(q, ref)` converts through it; a malformed one (no unit and no id, a named unit without its reduction, an id naming no unit declaration, a reduction disagreeing with the declaration's own) is a typed error on whichever side sees it first | `grpc/convert.go` `MeasurementRefToProto`/`ProtoToMeasurementRef`/`declaredMeasurementRef`, `ValueCarriesMeasurementRef`, `ErrMeasurementRefEmpty`, `ErrMeasurementRefNeedsIndex`; `capability_response.go` (`measurement_refs` arm); `api/proto/sysml.proto` `MeasurementRef`; `client/opensysml/value.go`, `convert.go`, `client.go` (`measurement_refs` preflight); `clients/python/opensysml/values.py`, `connection.py`; `clients/node/src/core/values.ts`; `clients/java/.../Value.java`, `internal/Protos.java`; `clients/rust/opensysml/src/domain.rs` | `grpc/convert_measurement_ref_test.go`, `client/opensysml/measurement_ref_test.go`, `clients/python/tests/test_measurement_ref.py`, `clients/node/test/values.test.ts`, `client.test.ts`, `clients/java/.../ProtosTest.java`, `ApiIntegrationTest.java`, `clients/rust/opensysml/src/domain.rs` tests, `tests/client.rs`; `conformance/scenarios/01-server-info.json`, `04-evaluate.json`, `10-evaluate-calc.json` (`measurement_ref.sysml`) over gRPC, Connect protobuf and Connect JSON | ✅ Faithful (advertised as the `measurement_refs` capability, separate from `structured_values` so a client built against those arms keeps reading a bare reference as `unsupported: measurement reference m`; a service withholding it reports that unsupported null and refuses one sent to it with `UNIMPLEMENTED`, the clients refusing before the round trip) | | A quantity crosses the API boundary in both directions: `Value.quantity` carries the magnitude with the kind it was written in (Integer or Real), the unit as written and the reduced unit term, so a quantity read from the service can be sent back as an input and evaluates against the unit it names — commensurability is decided over the reduction, so a unit named without one is refused client-side rather than compared by bare magnitude | `opensysml/values.py` `Quantity.to_pb`, `Unit.to_pb`/`Unit.reduced`, `connection.py` `_python_to_value`; service side `grpc/convert.go` `ProtoToQuantity`, `ProtoToValueIn` | `clients/python/tests/test_quantity.py`: `test_a_quantity_encodes_as_the_message_the_service_decodes`, `test_an_unreduced_unit_is_refused_before_it_is_sent`, and against a live service `TestQuantityAgainstTheService::test_a_quantity_sent_as_a_calc_argument_round_trips`, `::test_a_quantity_input_binds_into_an_action`, `::test_a_sent_quantity_is_commensurable_with_the_models_own_units` | ✅ Faithful | +| A function value crosses the API boundary as `Value.function`: `calc_id`, the qualified name of the calc it is a value of, and `self_id`, the id (within the answering response) of the object it was read off, 0 for none — identity being the pair. One sent as an argument (`EvaluateCalc`, `ExecuteAction`, `RunAnalysis`) is rebound to that calc of the named model, read off the object `self_id` names, and invoked through the calc-typed parameter it binds; an empty `calc_id`, one naming no calc, or a `self_id` naming no object of this call is refused in band (`ErrFunctionUnbound`), never a null, and a function value nested in a sequence or array is found wherever it sits. A value closing over a behavior body's bindings cannot be named by calc and object, so it crosses as `unsupported: function ` (`ErrFunctionNeedsRuntime` on the way in). Every client this repository ships maps the arm to a typed value — Go `opensysml.Function`, Python `opensysml.Function`, Node `{ kind: "function" }`, Java `Value.FunctionValue`, Rust `Value::Function` — and refuses to send one to a service lacking the capability | `grpc/convert.go` `functionToProto`/`functionFromProto`/`ProtoToRuntimeValue`, `ValueCarriesFunction`, `ErrFunctionUnbound`, `ErrFunctionNeedsRuntime`; `capability_response.go` (`function_values` arm); `service.go` `CapabilityFunctionValues`; `api/proto/sysml.proto` `Function`; `client/opensysml/value.go`, `convert.go`, `client.go` (`function_values` preflight); `clients/python/opensysml/values.py`, `connection.py`; `clients/node/src/core/values.ts`; `clients/java/.../Value.java`, `internal/Protos.java`; `clients/rust/opensysml/src/domain.rs` | `grpc/convert_function_test.go:TestFunctionRoundTrip`, `:TestMalformedFunctionsAreRejected`, `:TestFunctionCapability`, `:TestValueCarriesFunction`; `client/opensysml/function_test.go`; `clients/python/tests/test_function.py`; `clients/node/test/values.test.ts`, `client.test.ts`; `clients/java/.../ProtosTest.java`, `ApiIntegrationTest.java`; `clients/rust/opensysml/tests/client.rs`; `conformance/scenarios/01-server-info.json`, `04-evaluate.json`, `10-evaluate-calc.json` (`function.sysml`) over gRPC, Connect protobuf and Connect JSON | ✅ Faithful (advertised as the `function_values` capability; a service withholding it reports the unsupported null and refuses a function argument with `UNIMPLEMENTED`, the clients refusing before the round trip) | | An unqualified name resolves as a written reference does — the enclosing scope chain, inherited members, imports, then the global index — and the declaration it finds is evaluated in *its own* declaring scope, so the imports in force where a value was written answer the names that value uses | `runtime/eval.go` `evalFeatureReference` (scope arm) via `resolve/unqualified.go` `Resolver.LookupName`, `EvalContext.evalIn` | `action_body_package_member.sysml`, `action_body_declarer_scope.sysml`, `body_scope_test.go:TestBodyScopeImportSpellings`, `robustness_test.go:action_body_unresolved_feature` | ✅ Faithful | #### Scope of an expression in a behavior body @@ -2290,7 +2297,7 @@ operations are defined: set value, rename, add member, and delete. | RPC | Implementation | Status | Tests | |-----|---------------|--------|-------| -| GetServerInfo | service.go `Service.GetServerInfo`, `capabilityAvailability` | ✅ Faithful — reports the build version (informational; a source build reports `dev`) and this service instance's capabilities in append-only canonical order: `type_facts`, `convert`, `verification`, `query`, `oslc_query`, `enum_values`, `evaluate_subject`, `symbol_attributes`, `unset_value`, `feature_values`, `apply_edits`, `authoring`, `inline_language`, `strict_conformance`, `document_query`, `render_document`. The same availability object drives request refusal and response population, so a capability cannot be advertised while withheld or withheld while advertised. A service predating this RPC answers `UNIMPLEMENTED`, which the client reads as supporting no capability | service_test.go:TestGetServerInfo, capability_test.go:`TestCapabilityAvailabilityDrivesAdvertisementAndRefusal`, clients/python/tests/test_capabilities.py | +| GetServerInfo | service.go `Service.GetServerInfo`, `capabilityAvailability` | ✅ Faithful — reports the build version (informational; a source build reports `dev`) and this service instance's capabilities in append-only canonical order: `type_facts`, `convert`, `verification`, `query`, `oslc_query`, `enum_values`, `evaluate_subject`, `symbol_attributes`, `unset_value`, `feature_values`, `apply_edits`, `authoring`, `inline_language`, `strict_conformance`, `document_query`, `render_document`, `parse_sources`, `complex_values`, `structured_values`, `measurement_refs`, `function_values`. The same availability object drives request refusal and response population, so a capability cannot be advertised while withheld or withheld while advertised. A service predating this RPC answers `UNIMPLEMENTED`, which the client reads as supporting no capability | service_test.go:TestGetServerInfo, capability_test.go:`TestCapabilityAvailabilityDrivesAdvertisementAndRefusal`, clients/python/tests/test_capabilities.py | | ParseFile | service.go `Service.ParseFile` (parser + passes.Analyze + stdlib load) | ✅ Faithful — the cache is keyed by the file name and content the service read, so repeated parses of an unchanged source hit it whatever the request's (ignored) `content_hash` says, a hash disagreeing with its content cannot serve another model, and identical content read under two names keeps a record each, since their diagnostics name different files. An explicit inline language requires `inline_language`, and `strict_conformance=true` requires `strict_conformance`; an unavailable requested field is `UNIMPLEMENTED`, while an unset field retains the default parse | runtime_test.go:TestParseFile_*, service_test.go:`TestParseFileCachesByContentRead`, service_test.go:`TestParseFileCachesPerFileName`, capability_test.go, conformance parse cases | | ParseFile (standard library) | `grpc/libindex.go` `libraryBase`, `buildLibraryIndex`, `indexPrewarmFromEnv` (`OPENSYSML_GRPC_INDEX_POOL`, positive prewarms, 0 disables), `symbols/layer.go`, `symbols/index.go` `Freeze`/`NewOverlay`, `libs/shared.go` `SharedBase`/`NewModelIndex`, `grpc/service.go` `Service.Prewarm`/`Close`/`ParseFile` | ✅ Faithful — the library does not depend on the model and is immutable once loaded, so it is built once, frozen, and read by every model through an overlay holding that model's own document. What a model resolves against is unchanged (same source list, same expansion, same persist step, same `Index.Library` marking); a model writes only into its overlay, so no two cached models see each other's documents and eviction removes only the evicting model's state; a request arriving before the shared index exists builds it, so a result never depends on prewarming. A cold `ParseFile` on `examples/combined-behavioral-demo.sysml` measures ~0.5–0.9 ms against ~100–128 ms building the library per model, and 100 cached models cost ~1.1 MiB rather than ~1598 MiB | `grpc/libindex_test.go:TestSharedIndexMatchesFreshlyBuiltIndex` (identical diagnostics and identical qualified lookups over the whole index, shared vs built inline), `:TestParseFileServesEveryModelFromOneLibraryIndex`, `:TestParseFileTakesNoIndexOnACacheHit`, `:TestCachedModelsOwnTheirIndex`, `:TestLibraryBaseFallsBackToBuildingInline`, `:TestLibraryBaseCloseReleasesTheIndexAndStillServes`, `:TestIndexPrewarmFromEnv`, `:TestLibraryBaseBuildsOnceUnderConcurrentDemand`, `symbols/overlay_test.go:TestOverlayEqualsAnIndexBuiltWhole`, `:TestOverlayAnswersEveryLookupAsAnIndexBuiltWhole`, `:TestOverlayRemovalOfABaseDocumentLeavesTheBaseIntact`, `:TestOverlaySuppressesAnAmbiguatedBaseImport`, `:TestConcurrentOverlaysDoNotSeeEachOther`, `:TestFrozenIndexRejectsWrites`, `libs/shared_index_test.go:TestModelsOverASharedLibraryAgreeWithModelsOverTheirOwn`, `:TestLibraryMarkingReadsThroughTheSharedBase`, `:TestAModelOverTheSharedBaseCostsFarLessThanItsOwnIndex`, `robustness_test.go:parse_with_unavailable_standard_library`, `BenchmarkParseFileColdShared`/`ColdInline` | | Convert | export.go `Service.Convert`, conversion in `internal/core/export` | ✅ Faithful for what OpenSysML writes — SysML/KerML notation and RDF Turtle, from a loaded model named by its `model_hash`, a path the service opens, or inline content, with the format names `sysml -convert` takes and canonical names reported back. A `model_hash` converts the source that parse read, so a file edited since then does not change what is written, and a model evicted from the cache is `NOT_FOUND` rather than converted as something else; a `file_path` is read afresh, for a caller who does want the file as it stands. Notation to notation is source-preserving (comments and layout survive); a graph direction returns an equivalent model, not identical bytes, and drops comments, per docs/reference/rdf-mapping.md. A conversion that cannot be written faithfully returns `error` plus the diagnostics rather than partial output, and `tolerate_syntax_errors` is honored for notation to notation only, since a graph built from an unparsed declaration would lose it silently | export_test.go:TestConvert*, `TestConvertModelHashConvertsWhatWasParsed`, `TestConvertUncachedModelHashIsNotFound`, clients/python/tests/test_conversion.py | diff --git a/docs/reference/java-api.md b/docs/reference/java-api.md index 906fb3526..cf5a55d2a 100644 --- a/docs/reference/java-api.md +++ b/docs/reference/java-api.md @@ -104,6 +104,7 @@ else if (value instanceof Value.ArrayValue v) rendered = v.dimensions( else if (value instanceof Value.VectorValue v) rendered = v.components().toString(); // IntegerValue | RealValue else if (value instanceof Value.VectorQuantityValue v) rendered = v.components().toString(); // one Quantity each else if (value instanceof Value.MeasurementRefValue v) rendered = v.unit(); // a bare unit and its reduction +else if (value instanceof Value.FunctionValue v) rendered = v.calcId(); // a calc held as a value; selfId() when read off an object else if (value instanceof Value.EnumerationValue v) rendered = v.literal().name(); else if (value instanceof Value.InstanceReference v) rendered = "instance " + v.instanceId(); else if (value instanceof Value.Sequence v) rendered = v.elements().toString(); diff --git a/docs/reference/node-api.md b/docs/reference/node-api.md index b1e2e24e3..2135220b4 100644 --- a/docs/reference/node-api.md +++ b/docs/reference/node-api.md @@ -99,6 +99,7 @@ switch (value.kind) { case "array": value.dimensions; value.elements; // row-major SysMLValue[] case "vector": value.components; // Magnitude[]: int | real, kept apart case "vectorQuantity": value.components; // QuantityValue[], one unit each + case "function": value.calcId; value.selfId; // a calc held as a value; selfId when read off an object case "enum": value.value.name; // and its literal/enumeration ids case "instance": value.id; // an object in the same tree case "sequence": value.elements; // SysMLValue[] diff --git a/docs/reference/python-api.md b/docs/reference/python-api.md index 78abece52..9d31ea437 100644 --- a/docs/reference/python-api.md +++ b/docs/reference/python-api.md @@ -161,6 +161,7 @@ collection property. | `CartesianVectorValue` and the other numeric vectors | `opensysml.Vector`: a tuple of `int`/`float` components, kept apart | | `VectorQuantityValue` | `opensysml.VectorQuantity`: a tuple of `Quantity`, one unit per component | | `MeasurementUnit` and the other measurement references (`SI::m`, `m / s`, a quantity's `mRef`) | `opensysml.MeasurementRef`: the `Unit` with its reduction, and `unit_id` naming the declaration a named unit is (`SI::metre`), empty for a composed unit | +| a calc definition, calc usage or `in calc` parameter read as a value | `opensysml.Function`: `calc_id` naming the calc, `self_id` the object it was read off (0 for none) | | `Integer`, `Natural` | `int` | | `Boolean` | `bool` | | `String` | `str` | diff --git a/docs/reference/rust-api.md b/docs/reference/rust-api.md index c8023533f..fdbccf44d 100644 --- a/docs/reference/rust-api.md +++ b/docs/reference/rust-api.md @@ -108,6 +108,7 @@ match value { Value::Vector(v) => (), // v.components: Vec, Integer and Real apart Value::VectorQuantity(q) => (), // q.components(): one Quantity per component; q.unit() when shared Value::MeasurementRef(m) => (), // a bare unit: m.unit, m.unit_term, m.unit_id when it names a declaration + Value::Function(f) => (), // a calc held as a value: f.calc_id, f.self_id when read off an object Value::EnumLiteral(l) => (), // literal_id, enumeration_id, name Value::Null => (), // evaluated, no value Value::Unset => (), // a materialized feature with no value diff --git a/docs/reference/service-transports.md b/docs/reference/service-transports.md index f128a9e6b..252378228 100644 --- a/docs/reference/service-transports.md +++ b/docs/reference/service-transports.md @@ -52,10 +52,10 @@ capability's definition rather than something a client has to guess: | The capability describes | A request that needs it | What a client should do | |---|---|---| | what the service can be *asked*: `strict_conformance`, `inline_language`, `parse_sources`, `evaluate_subject`, `verification`, `convert`, `apply_edits`, `authoring`, `query`, `oslc_query`, `document_query`, `render_document` | is **refused** with `UNIMPLEMENTED`, naming the capability | check the advertised list first, and report the missing capability locally rather than spending a round trip | -| how a response is *populated*: `type_facts`, `symbol_attributes`, `feature_values`, `enum_values`, `unset_value`, `complex_values`, `structured_values`, `measurement_refs` | is answered with those fields **omitted** | check before reading the fields; an omitted field is not an error | +| how a response is *populated*: `type_facts`, `symbol_attributes`, `feature_values`, `enum_values`, `unset_value`, `complex_values`, `structured_values`, `measurement_refs`, `function_values` | is answered with those fields **omitted** | check before reading the fields; an omitted field is not an error | -`complex_values`, `structured_values` and `measurement_refs` sit in both rows: a complex — or -an array, vector or vector quantity, or a bare measurement reference — in a response is +`complex_values`, `structured_values`, `measurement_refs` and `function_values` sit in both rows: a complex — or +an array, vector or vector quantity, a bare measurement reference, or a calc held as a value — in a response is reported as an `unsupported` null without it, and one in an action input or calc argument is refused with `UNIMPLEMENTED` rather than read as another value — a service that predates the arm would read it as an unknown field, so every client checks the list before sending one diff --git a/docs/reference/wire-contract.md b/docs/reference/wire-contract.md index d76789b3e..1aaf8690f 100644 --- a/docs/reference/wire-contract.md +++ b/docs/reference/wire-contract.md @@ -192,10 +192,10 @@ Note that `not_found` is also the status for an unknown *symbol* on some methods which (`model not found:`, `symbol not found:`, `file not found:`), and a client that recovers by re-parsing must read it. -## `Value`: fifteen arms, exactly one present +## `Value`: sixteen arms, exactly one present Every value the engine returns — an expression result, a feature of an instance, an action -output, a state-machine context variable — is a `Value`, which is a proto `oneof` of fifteen +output, a state-machine context variable — is a `Value`, which is a proto `oneof` of sixteen arms. In JSON that is **an object with exactly one key**, and the key is the discriminator. A decoder therefore does not look for a `kind` field: it looks at which key is present. The arms, each captured from `Evaluate` against the model at the end of this section: @@ -217,10 +217,12 @@ arms, each captured from `Evaluate` against the model at the end of this section | `vector` | object | `{"result":{"vector":{"components":[{"realValue":3},{"realValue":4}]}}}` | Numeric vector; each component an `intValue` or `realValue` | | `vectorQuantity` | object | `{"result":{"vectorQuantity":{"components":[{"realMagnitude":3,"unit":"m","unitTerm":{…}},…]}}}` | Vector of quantities; one `quantity` body per component | | `measurementRef` | object | `{"result":{"measurementRef":{"unit":"m","unitTerm":{…},"unitId":"SI::metre"}}}` | A measurement reference on its own: a unit, its reduction, and the declaration it names | +| `function` | object | `{"result":{"function":{"calcId":"F::Sq"}}}` | A calc held as a value: the calc it names and, when it was read off an object, that object | The `array`, `vector` and `vectorQuantity` rows were captured against `conformance/fixtures/structured.sysml` (`S::grid`, `S::v`, `S::d`), `measurementRef` against -`conformance/fixtures/measurement_ref.sysml` (`M::u`); the rest against the model below, with requests of the form +`conformance/fixtures/measurement_ref.sysml` (`M::u`), `function` against +`conformance/fixtures/function.sysml` (`F::pick`); the rest against the model below, with requests of the form `{"modelHash":"59c4…a654","expression":"","contextSymbolId":"Rover"}` with `rover.count`, `1.0 / 3.0`, `rover.armed`, `"abc"`, `rover.wheel`, `rover.tags`, `null`, `rover.speed`, `Mode::idle`, `rover.serial` and `rover.z`, and the model was: @@ -281,6 +283,9 @@ decode(v): measurementRef → unit := v.measurementRef.unit, id := v.measurementRef.unitId; require v.measurementRef.unitTerm when either is present, else an error; require unit or id, else an error; id absent means a composed unit + function → calc := v.function.calcId, require it non-empty, else an error; + self := v.function.selfId when present and not "0", an opaque reference + under the instanceId rule, else no object anything else → an error: a newer service than this decoder ``` @@ -449,6 +454,37 @@ $ … /Evaluate -d '{"modelHash":"5b0f…40d5","expression":"M::speed"}' - A `measurementRef` is not a `quantity` with magnitude one: `ConvertQuantity(q, ref)` takes one, `q * ref` does not. +**`function`.** A calc held as a value — a calc definition or usage named where a value is +expected, or bound to an `in calc` parameter — travels as the calc it names, not as what the +calc would compute: + +```console +$ … /Evaluate -d '{"modelHash":"e587…f81e","expression":"F::pick"}' +{"result":{"function":{"calcId":"F::Sq"}}} + +$ … /Evaluate -d '{"modelHash":"e587…f81e","expression":"F::scaler"}' +{"result":{"function":{"calcId":"F::Scaler::scale", "selfId":"1"}}} +``` + +- `calcId` is the fully qualified name of the calc declaration, and is the identity a client + keeps to send the same function back. It is never empty: a `function` with no `calcId` is + malformed, and a decoder refuses it rather than reading it as "no function". +- `selfId` is present when the calc is a usage owned by an object and was read off that object + (`holder.scale` above reads `holder.k` when invoked). It is an `instanceId` under that arm's + rules: a 64-bit integer sent as a string, valid within the response it arrived in, and + indexing that response's `instances` where the method returns them. Absent (or `"0"`, the + proto default) means the calc computes over no object. +- Two functions are the same function when their `calcId`s are equal and both name the same + object or neither names one; the engine's `==` says the same. +- A calc that closes over the bindings of a behavior body — one returned by another calc, or + read inside an action step — has no wire form: the frames it captured belong to a run that + has ended and cannot be reconstructed remotely. It is sent as the unsupported null + `{"null":"unsupported: function closing over a body's bindings"}`, under the `null` + arm's rule. +- The arm is gated by the `function_values` capability (see [`GetServerInfo`](#getserverinfo)). + A service without it sends every function, at any depth, as + `{"null":"unsupported: function "}` and refuses a request that carries one. + ### What a client must not do - **Do not compare enum literals by `name`.** Compare `literalId`. @@ -467,6 +503,10 @@ $ … /Evaluate -d '{"modelHash":"5b0f…40d5","expression":"M::speed"}' - **Do not index an `array` before checking `len(elements) == product(dimensions)`.** - **Do not invent a `unitId` for a `measurementRef` that has none.** A composed unit names no declaration; send it back as it came, with its `unit` and `unitTerm` only. +- **Do not read a `function` as the value the calc computes, or invoke it locally.** It is a + reference: hand it back as an argument (`EvaluateCalc`) and let the service invoke it. +- **Do not keep a `function`'s `selfId` past the response it arrived in.** It is an + `instanceId`, with that arm's lifetime. ## Three places a failure can be @@ -777,9 +817,26 @@ $ … /EvaluateCalc -d '{"modelHash":"5b0f…40d5","symbolId":"M::toUnit","argum {"error":"calc argument could not be read: unit as written does not reduce to its unit_term: SI::metre reduces to metre, unit_term is 1000·metre", "failureReason":"FAILURE_REASON_EVALUATION"} ``` -A service without the `structured_values` capability refuses a structured argument, and one -without `measurement_refs` a `measurementRef` argument, with the `unimplemented` Connect -error instead, naming the capability; check `GetServerInfo` first. +A `function` argument binds an `in calc` parameter to the calc it names, resolved against the +model and, when it carries a `selfId`, against the objects of the runtime the model was +instantiated into. A name that is empty, names nothing, names something that is not a calc, +or a `selfId` that names no object, is an in-body failure, at any depth: + +```console +$ … /EvaluateCalc -d '{"modelHash":"e587…f81e","symbolId":"F::apply","arguments":[{"function":{"calcId":"F::Sq"}},{"realValue":3.0}]}' +{"result":{"realValue":9}} + +$ … /EvaluateCalc -d '{"modelHash":"e587…f81e","symbolId":"F::apply","arguments":[{"function":{"calcId":"F::holder"}},{"realValue":2.0}]}' +{"error":"calc argument could not be read: function names no calc of this model: F::holder is not a calc", "failureReason":"FAILURE_REASON_EVALUATION"} + +$ … /EvaluateCalc -d '{"modelHash":"e587…f81e","symbolId":"F::apply","arguments":[{"function":{"calcId":"F::Scaler::scale","selfId":"3"}},{"realValue":2.0}]}' +{"error":"calc argument could not be read: function names no calc of this model: F::Scaler::scale: self_id 3 names no object of this runtime", "failureReason":"FAILURE_REASON_EVALUATION"} +``` + +A service without the `structured_values` capability refuses a structured argument, one +without `measurement_refs` a `measurementRef` argument, and one without `function_values` a +`function` argument, with the `unimplemented` Connect error instead, naming the capability; +check `GetServerInfo` first. A calc *usage* whose output features are evaluated from its own members (no `arguments`) answers them as `outputs`, a list of `{"name":…,"value":}` in declaration order, in diff --git a/internal/core/codegen/compile.go b/internal/core/codegen/compile.go index 3e11683c9..c84007b72 100644 --- a/internal/core/codegen/compile.go +++ b/internal/core/codegen/compile.go @@ -139,6 +139,9 @@ func (c *Compiler) compileCalc(sym *symbols.Symbol) (*Func, error) { if u.Value != nil { return nil, fc.unsupported(fmt.Sprintf("parameter %s has a default value", name)) } + if u.Kind == ast.UsageCalc { + return nil, fc.unsupported(fmt.Sprintf("parameter %s binds a function value", name)) + } b, err := fc.declaredBinding(sym.Scope, u, name) if err != nil { return nil, err diff --git a/internal/core/parser/behavior.go b/internal/core/parser/behavior.go index 98222c4b1..1746c91af 100644 --- a/internal/core/parser/behavior.go +++ b/internal/core/parser/behavior.go @@ -275,6 +275,7 @@ var parameterKindKeywords = map[string]ast.UsageKind{ "attribute": ast.UsageAttribute, "occurrence": ast.UsageOccurrence, "action": ast.UsageAction, + "calc": ast.UsageCalc, } // parameterKindKeyword reports whether parseDirectionParameter reads the token as diff --git a/internal/core/parser/testdata/parse/action_calc_parameter.golden b/internal/core/parser/testdata/parse/action_calc_parameter.golden new file mode 100644 index 000000000..2a3a3494f --- /dev/null +++ b/internal/core/parser/testdata/parse/action_calc_parameter.golden @@ -0,0 +1,44 @@ +(RootNamespace + (Membership visibility="default" + (Definition kind="calc" abstract=false variation=false name="Unary" + (Membership visibility="default" + (Usage kind="attribute" name="v" ref=false direction="in" composite=false derived=false ordered=false nonunique=false + (Relationship kind="typing" target=Real + (*ast.QualifiedName)))) + (Usage kind="attribute" name="" ref=false direction="out" composite=false derived=false ordered=false nonunique=false + (Relationship kind="typing" target=Real + (*ast.QualifiedName))))) + (Membership visibility="default" + (Definition kind="action" abstract=false variation=false name="Apply" + (Membership visibility="default" + (Usage kind="calc" name="f" ref=false direction="in" composite=false derived=false ordered=false nonunique=false + (Membership visibility="default" + (Usage kind="attribute" name="v" ref=false direction="in" composite=false derived=false ordered=false nonunique=false + (Relationship kind="typing" target=Real + (*ast.QualifiedName)))) + (Usage kind="attribute" name="" ref=false direction="out" composite=false derived=false ordered=false nonunique=false + (Relationship kind="typing" target=Real + (*ast.QualifiedName))))) + (Membership visibility="default" + (Usage kind="calc" name="g" ref=false direction="in" composite=false derived=false ordered=false nonunique=false + (Relationship kind="typing" target=Unary + (*ast.QualifiedName)))) + (Membership visibility="default" + (Usage kind="attribute" name="a" ref=false direction="in" composite=false derived=false ordered=false nonunique=false + (Relationship kind="typing" target=Real + (*ast.QualifiedName)))) + (Membership visibility="default" + (Usage kind="attribute" name="y" ref=false direction="out" composite=false derived=false ordered=false nonunique=false + (Relationship kind="typing" target=Real + (*ast.QualifiedName)))) + (Membership visibility="default" + (Usage kind="action" name="step" ref=false direction="none" composite=false derived=false ordered=false nonunique=false + (*ast.AssignmentActionNode))))) + (Membership visibility="default" + (Usage kind="action" name="apply" ref=false direction="none" composite=false derived=false ordered=false nonunique=false + (Relationship kind="typing" target=Apply + (*ast.QualifiedName)) + (Membership visibility="default" + (Usage kind="calc" name="h" ref=false direction="in" composite=false derived=false ordered=false nonunique=false + (Relationship kind="typing" target=Unary + (*ast.QualifiedName))))))) \ No newline at end of file diff --git a/internal/core/parser/testdata/parse/action_calc_parameter.sysml b/internal/core/parser/testdata/parse/action_calc_parameter.sysml new file mode 100644 index 000000000..57c411787 --- /dev/null +++ b/internal/core/parser/testdata/parse/action_calc_parameter.sysml @@ -0,0 +1,11 @@ +calc def Unary { in v : Real; return : Real; } +action def Apply { + in calc f { in v : Real; return : Real; } + in calc g : Unary; + in a : Real; + out y : Real; + action step { assign y := f(a) + g(a); } +} +action apply : Apply { + in calc h : Unary; +} diff --git a/internal/core/runtime/adopt.go b/internal/core/runtime/adopt.go index d38e208c8..de0df5ff2 100644 --- a/internal/core/runtime/adopt.go +++ b/internal/core/runtime/adopt.go @@ -95,6 +95,9 @@ func (ctx *Context) recordShapes(obj *Instance, shapes *Shapes, seen map[int64]b if v.Kind == ValVariant { ctx.recordShape(v.Variant(), shapes) } + if self := v.FunctionSelf(); self != nil { + ctx.recordShapes(self, shapes, seen) + } if id, ok := carriedObject(v); ok { if held, found := ctx.instances[id]; found { ctx.recordShapes(held, shapes, seen) @@ -624,6 +627,13 @@ func (a *adoption) planValue(owner string, val Value) error { err = &AdoptError{Type: owner, Reason: "it holds an expression that was never evaluated"} return } + // A function value denotes its calc by name, so it is rebound as a variant is; + // the object it closes over is carried with it. + if v.Kind == ValFunction { + if err = a.planFunction(owner, v); err != nil { + return + } + } if v.Kind == ValVariant { if _, rebindErr := a.rebind(v.Variant(), "a variant it selected"); rebindErr != nil { err = rebindErr @@ -651,6 +661,25 @@ func (a *adoption) planValue(owner string, val Value) error { return err } +// planFunction rebinds the calc a function value is of to its declaration here, +// refusing one that is no longer a calc that can be invoked. +func (a *adoption) planFunction(owner string, v Value) error { + if v.FunctionClosesOverBody() { + return &AdoptError{Type: owner, Reason: "the function " + v.FunctionName() + " it holds closes over the bindings of a run that has ended"} + } + found, err := a.rebind(v.Function(), "the function "+v.FunctionName()+" it holds") + if err != nil { + return err + } + if _, err := a.ctx.calcShapeOf(found); err != nil { + return &AdoptError{Type: owner, Reason: "the function " + v.FunctionName() + " it holds cannot be invoked here: " + err.Error()} + } + if self := v.FunctionSelf(); self != nil { + return a.planHeld(owner, self.ID) + } + return nil +} + // unitsOf is the measurement units a quantity, reference or empty quantity // sequence names. func unitsOf(v Value) []Unit { @@ -1000,6 +1029,18 @@ func (a *adoption) rewrite(val Value) Value { return NewVariantValue(found, val.Instance) } return val + case ValFunction: + found, ok := a.rebound[val.Function()] + if !ok { + return val + } + shape, err := a.ctx.calcShapeOf(found) + if err != nil { + return val + } + fn := &functionValue{shape: shape, scope: found.OwnerScope, self: val.FunctionSelf()} + fn.library, _ = a.ctx.libraryFunctionFor(found) + return Value{Kind: ValFunction, ref: fn} case ValSequence: if val.Sequence() == nil { return val diff --git a/internal/core/runtime/adopt_test.go b/internal/core/runtime/adopt_test.go index 5ce432e70..19d6d6d9c 100644 --- a/internal/core/runtime/adopt_test.go +++ b/internal/core/runtime/adopt_test.go @@ -1626,3 +1626,70 @@ func TestAdoptRebindsTheFramesAWrittenValueNames(t *testing.T) { t.Errorf("Adopt refused for %q, want the missing frame named", err) } } + +const adoptFunctionSrc = `package Demo { + private import ScalarValues::*; + calc def Sq { in v : Real; return : Real = v * v; } + calc def Fn { in calc f { in v : Real; return : Real; } in a : Real; return : Real = f(a); } + part def Scaler { + attribute k : Real = 2.0; + calc scale { in x : Real; return : Real = x * k; } + } + part def Holder { attribute fn; attribute scaled; part scaler : Scaler; } + part holder : Holder; +}` + +// A function value a run wrote is carried over as a function of the calc the +// re-analysis declares, so applying it runs the calc as it is declared now; one +// closing over an object keeps that object with it. +func TestAdoptRebindsAFunctionValue(t *testing.T) { + prev := libraryContextOver(t, adoptFunctionSrc) + scope := lookupOne(t, prev.resolver.Index(), "Demo").Scope + holder, err := prev.Instantiate(lookupOne(t, prev.resolver.Index(), "Demo::holder")) + if err != nil { + t.Fatalf("Instantiate: %v", err) + } + for feature, expr := range map[string]string{"fn": "Sq", "scaled": "holder.scaler.scale"} { + val, err := evalIn(t, prev, scope, expr) + if err != nil || val.Kind != ValFunction { + t.Fatalf("%s = %s, %v; want a function", expr, FormatValue(val), err) + } + if err := holder.SetFeatureValue(prev, feature, val); err != nil { + t.Fatalf("write %s: %v", feature, err) + } + } + shapes := prev.ShapesOf(holder) + + ctx := libraryContextOver(t, strings.Replace(adoptFunctionSrc, "v * v", "v * v * v", 1)) + if _, err := ctx.Adopt(prev, shapes, holder); err != nil { + t.Fatalf("Adopt: %v", err) + } + fn, err := holder.GetFeatureValue(ctx, "fn") + if err != nil { + t.Fatalf("GetFeatureValue(fn): %v", err) + } + if want := lookupOne(t, ctx.resolver.Index(), "Demo::Sq"); fn.Value.Function() != want { + t.Errorf("fn is of %p, want the Sq declared by the re-analysis %p", fn.Value.Function(), want) + } + newScope := lookupOne(t, ctx.resolver.Index(), "Demo").Scope + for expr, want := range map[string]string{ + "holder.fn": "Demo::Sq", + "holder.fn == Sq": "true", + "Fn(holder.fn, 2.0)": "8.0", + "Fn(holder.scaled, 5.0)": "10.0", + "holder.scaled == holder.scaler.scale": "true", + } { + got, err := evalIn(t, ctx, newScope, expr) + if err != nil || FormatValue(got) != want { + t.Errorf("%s after the carry-over = %s, %v; want %s", expr, FormatValue(got), err, want) + } + } + + gone := libraryContextOver(t, strings.Replace(adoptFunctionSrc, "calc def Sq { in v : Real; return : Real = v * v; }", "", 1)) + var adoptErr *AdoptError + if _, err := gone.Adopt(prev, shapes, holder); !errors.As(err, &adoptErr) { + t.Fatalf("Adopt into a re-analysis without the calc: %v, want an AdoptError", err) + } else if !strings.Contains(err.Error(), "the function Demo::Sq it holds is no longer declared") { + t.Errorf("Adopt refused for %q, want the missing calc named", err) + } +} diff --git a/internal/core/runtime/calc_usage.go b/internal/core/runtime/calc_usage.go index b51abb561..ddcf7fb9d 100644 --- a/internal/core/runtime/calc_usage.go +++ b/internal/core/runtime/calc_usage.go @@ -688,7 +688,13 @@ func (ctx *Context) runCalcUsage( shape *calcShape, ec, nested *EvalContext, env frame, reader *EvalContext, ) (*calcRun, error) { host := &calcStmtHost{ctx: ctx, shape: shape, self: reader.self} - engine := newStmtEngineIn(ctx, host, env, nil) + // A usage nested in a behavior body computes over that body's bindings, as + // an invocation of it does. + var enclosing []frame + if nested != nil { + enclosing = nested.frames + } + engine := newStmtEngineIn(ctx, host, env, enclosing) host.attachPerformances(engine) result, returned, err := runCalcSteps(engine, host, shape) if err != nil { @@ -989,6 +995,9 @@ func (ec *EvalContext) occurrenceOperand(operand ast.Node) (*symbols.Symbol, boo // against that object so its inputs read the object's feature values. Naming the usage // itself names no value: its outputs are what it computes. func (ec *EvalContext) calcUsageMemberValue(sym *symbols.Symbol, self *Instance, parts []ast.NameSegment) (Value, error) { + if len(parts) == 0 && ec.ctx.readsAsFunction(sym) { + return NewEvalContextIn(ec.ctx, sym.OwnerScope, self).functionValueOf(sym) + } if len(parts) == 0 && ec.ctx.returnsResult(sym) { parts = resultSegments } diff --git a/internal/core/runtime/compile.go b/internal/core/runtime/compile.go index 3393f9c8c..2c07d05f9 100644 --- a/internal/core/runtime/compile.go +++ b/internal/core/runtime/compile.go @@ -248,6 +248,9 @@ func (c *calcCompiler) compile(cell *compiledCalc) error { cell.params = make([]compiledParam, len(shape.Params)) for i := range shape.Params { param := &shape.Params[i] + if param.IsCalc { + return ineligible(fmt.Sprintf("parameter %q binds a function value", param.Name)) + } check, ok := c.scalarCheckFor(¶m.Decl) if !ok { return ineligible(fmt.Sprintf("parameter %q declares a type outside the scalar lattice", param.Name)) @@ -455,6 +458,9 @@ func (c *calcCompiler) compileName(qn *ast.QualifiedName, scope *symbols.Scope, if !ok || sym == nil { return nil, ineligible(fmt.Sprintf("name %q is not bound in the frame", name)) } + if c.ctx.readsAsFunction(sym) { + return nil, ineligible(fmt.Sprintf("name %q is a function value", name)) + } node, err := c.libraryConstant(sym, name) if err != nil { return nil, err diff --git a/internal/core/runtime/conformance_test.go b/internal/core/runtime/conformance_test.go index 2715c1656..9517e6310 100644 --- a/internal/core/runtime/conformance_test.go +++ b/internal/core/runtime/conformance_test.go @@ -1352,6 +1352,8 @@ func expectedToRuntimeValue(t *testing.T, ev ExpectedValue) Value { t.Fatalf("a measurement reference names a unit the model declares, so it cannot be built from a case value") case "CoordinateFrame", "CoordinateTransformation": t.Fatalf("a %s is declared by the model, so it cannot be built from a case value", ev.Type) + case "Function": + t.Fatalf("a function is a calc the model declares, so it cannot be built from a case value") case "Complex": v, ok := ev.Value.(float64) if !ok || ev.Im == nil { @@ -1539,6 +1541,14 @@ func validateValue(t *testing.T, ctx *Context, name string, expected ExpectedVal if got := actual.MeasurementRef().Unit.String(); got != expected.Unit { t.Errorf("%s: unit = %q, want %q", name, got, expected.Unit) } + case "Function": + if actual.Kind != ValFunction || actual.Function() == nil { + t.Errorf("%s: type = %v, want Function", name, actual.Kind) + return + } + if want := expected.Value.(string); actual.FunctionName() != want { + t.Errorf("%s: function = %q, want %q", name, actual.FunctionName(), want) + } case "CoordinateFrame": actual = denotedObjectValue(t, ctx, name, actual) if actual.Kind != ValCoordinateFrame || actual.CoordinateFrame() == nil { diff --git a/internal/core/runtime/describe.go b/internal/core/runtime/describe.go index fa1792937..c55886dc6 100644 --- a/internal/core/runtime/describe.go +++ b/internal/core/runtime/describe.go @@ -68,6 +68,8 @@ func describeOperand(val Value) string { return "a coordinate frame" case ValCoordinateTransformation: return "a coordinate transformation" + case ValFunction: + return "the function " + val.FunctionName() } return "a value" } diff --git a/internal/core/runtime/errors.go b/internal/core/runtime/errors.go index 1b73ba84a..d964f842f 100644 --- a/internal/core/runtime/errors.go +++ b/internal/core/runtime/errors.go @@ -51,6 +51,10 @@ var ( // not a calc definition or usage. ErrNotACalc = errors.New("not a calc") + // ErrNotAFunction is returned when a value that is no function is called, + // or is bound where a calc-typed feature needs one. + ErrNotAFunction = errors.New("not a function") + // ErrNotAConstraint is returned when a symbol asked to be evaluated as a // constraint declares something else. It is a usage error about the request, // not a verdict about the model, so callers can tell the two apart. diff --git a/internal/core/runtime/eval.go b/internal/core/runtime/eval.go index c2911178d..8c8ad831f 100644 --- a/internal/core/runtime/eval.go +++ b/internal/core/runtime/eval.go @@ -336,6 +336,10 @@ func (ctx *Context) EvalDeclaredValue(sym *symbols.Symbol) (Value, error) { if val, ok, err := ctx.declaredArrayValue(sym); ok { return val, err } + // A calc definition, or a calc usage awaiting arguments, is a function. + if val, ok, err := ctx.FunctionValue(sym); ok { + return val, err + } // A calc usage returning one unnamed result is read as that result. if isCalcUsageSymbol(sym) && ctx.returnsResult(sym) { return NewEvalContext(ctx, sym.OwnerScope).evalCalcUsageMembers(sym, resultSegments) @@ -617,6 +621,10 @@ func (ec *EvalContext) evalNameGeneral(qn *ast.QualifiedName) (Value, error) { if val, ok, err := ec.occurrenceReference(sym); ok { return val, err } + // A calc definition, or a calc usage awaiting arguments, is a function. + if val, ok, err := ec.calcAsValue(sym); ok { + return val, err + } // A calc usage returning one unnamed result is read as that result. if isCalcUsageSymbol(sym) && ec.ctx.returnsResult(sym) { return ec.evalCalcUsageMembers(sym, resultSegments) @@ -709,6 +717,11 @@ func (ec *EvalContext) evalNameGeneral(qn *ast.QualifiedName) (Value, error) { return val, err } + // A calc definition, or a calc usage awaiting arguments, is a function. + if val, ok, err := ec.calcAsValue(currentSym); ok { + return val, err + } + // Evaluate the final symbol's declaration if decl, ok := currentSym.Decl.(*ast.Usage); ok { // An enumerated value is a value of its enumeration, whether or not it @@ -1481,6 +1494,12 @@ func (ctx *Context) directValueType(scope *symbols.Scope, value Value) (*symbols ErrUndeterminedValueType, value.Literal().Name) } return enum, nil + case ValFunction: + // A function is of the calc it is a value of: a usage's type is that usage. + if value.Function() == nil { + return nil, fmt.Errorf("%w: function", ErrUndeterminedValueType) + } + return value.Function(), nil case ValQuantity: if value.Quantity() == nil { return nil, fmt.Errorf("%w: quantity", ErrUndeterminedValueType) @@ -2235,6 +2254,7 @@ type invocationTarget struct { builtinName string // the built-in's registered name, keying its declared signature library *libraryFunction // the library function the name denotes: the library declaration calc is shape *calcShape // calc's invocation interface, nil when it has none + enclosed bool // calc is declared in a behavior body, whose bindings it reads names []string // the parameter each named argument binds, as calc's signature spells it unbound []error // per named argument, why calc has no parameter for it; nil when it binds } @@ -2304,6 +2324,7 @@ func (ctx *Context) implementInvocation(target *invocationTarget, sym *symbols.S target.library = fn } else if shape, err := ctx.calcShapeOf(sym); err == nil { target.shape = shape + target.enclosed = enclosedByBehaviorBody(sym) } } @@ -2328,6 +2349,10 @@ func (ec *EvalContext) unresolvedInvocation(qn *ast.QualifiedName, written strin // evalInvocation evaluates a function/calc invocation. func (ec *EvalContext) evalInvocation(n *ast.InvocationExpr) (Value, error) { + // `holder.f(a)`: the chain names the function applied, not the callee's type. + if n.Type == nil && n.Operand != nil { + return ec.evalChainInvocation(n) + } target := ec.invocationTarget(n) qualName := target.qualName if len(target.ambiguous) > 0 { @@ -2351,72 +2376,136 @@ func (ec *EvalContext) evalInvocation(n *ast.InvocationExpr) (Value, error) { if n.Operand != nil { exprs = append([]ast.Node{n.Operand}, n.Args...) } + // A calc-typed feature bound to a function value here — a parameter given a + // calc as its argument — applies that value, not the feature's own declaration. + if fn, ok, err := ec.boundFunction(target.calc, n.Type); ok { + if err != nil { + return Value{}, err + } + // Named arguments bind parameters of the calc applied, not of the feature named. + applied := *target + if len(n.NamedArgs) > 0 { + applied.names, applied.unbound = ec.ctx.boundParameterNames(ec.scope, fn.Function(), n.NamedArgs) + } + callArgs, err := ec.evalInvocationArgs(qualName, exprs, n.NamedArgs, &applied) + if err != nil { + return Value{}, err + } + return ec.invokeFunction(qualName, fn, callArgs) + } // A calc bound by position alone consumes its arguments within the call, so // they live on the context's argument stack rather than in a slice of their own. if target.shape != nil && len(n.NamedArgs) == 0 { - return ec.invokeCalcShapeStacked(target.shape, exprs) + return ec.invokeCalcShapeStacked(target.shape, exprs, ec.enclosingFor(target)) } // A built-in binds its arguments by its declared signature. if target.builtin != nil { return ec.invokeBuiltin(target.builtinName, target.builtin, exprs, n.NamedArgs, target.names, target.unbound) } + callArgs, err := ec.evalInvocationArgs(qualName, exprs, n.NamedArgs, target) + if err != nil { + return Value{}, err + } + + // An argument that fails is reported before the target is judged. A name + // that resolves to nothing denotes nothing, not the library function of + // that name: the validator reports the same expression unresolved. + if target.calc == nil && target.library == nil { + return Value{}, ec.unresolvedInvocation(n.Type, qualName) + } + // Every invocation goes through the one calc path, so an expression and a + // direct InvokeCalc bind parameters and trace identically. + if target.library != nil { + return target.library.invoke(ec.ctx, callArgs) + } + if target.shape == nil { + return ec.ctx.invokeCalcWithSelf(target.calc, callArgs, ec.scope, ec.self) + } + return ec.ctx.invokeCalcShapeIn(target.shape, callArgs, ec.scope, ec.self, ec.enclosingFor(target)) +} + +// enclosingFor is the environment a call of target runs under: this one's bindings +// for a calc declared in the body being evaluated, none for any other. +func (ec *EvalContext) enclosingFor(target *invocationTarget) []frame { + if !target.enclosed { + return nil + } + return ec.frames +} + +// evalChainInvocation applies the function value a feature chain denotes to the +// arguments written after it (KerMLExpressions InstantiatedTypeMember → OwnedFeatureChain). +func (ec *EvalContext) evalChainInvocation(n *ast.InvocationExpr) (Value, error) { + callee := chainText(n.Operand) + fn, err := ec.Eval(n.Operand) + if err != nil { + return Value{}, err + } + if fn.Kind != ValFunction { + return Value{}, fmt.Errorf("%w: %s is %s, not a function", ErrNotAFunction, callee, describeValue(fn)) + } + target := &invocationTarget{qualName: callee, calc: fn.Function()} + if len(n.NamedArgs) > 0 { + target.names, target.unbound = ec.ctx.boundParameterNames(ec.scope, fn.Function(), n.NamedArgs) + } + callArgs, err := ec.evalInvocationArgs(callee, n.Args, n.NamedArgs, target) + if err != nil { + return Value{}, err + } + return ec.invokeFunction(callee, fn, callArgs) +} + +// chainText spells a feature chain as written, `holder.scale`. +func chainText(n ast.Node) string { + switch c := n.(type) { + case *ast.FeatureChainExpr: + return chainText(c.Operand) + "." + qualifiedNameToString(c.Member) + case *ast.FeatureReference: + return qualifiedNameToString(c.Name) + } + return TraceLabel(n) +} + +// evalInvocationArgs evaluates an invocation's arguments in source order into the +// calc arguments they bind: positional, or named against target's parameter names. +// The notation keeps the two forms mutually exclusive. +func (ec *EvalContext) evalInvocationArgs(qualName string, exprs []ast.Node, namedArgs []ast.NamedArg, target *invocationTarget) (calcArgs, error) { args := make([]Value, len(exprs)) for i, arg := range exprs { val, err := ec.Eval(arg) if err != nil { - return Value{}, err + return calcArgs{}, err } args[i] = val } - - var named map[string]Value - if len(n.NamedArgs) > 0 { - named = make(map[string]Value, len(n.NamedArgs)) + if len(namedArgs) == 0 { + return calcArgs{positional: args}, nil } - for i, arg := range n.NamedArgs { + named := make(map[string]Value, len(namedArgs)) + for i, arg := range namedArgs { name := target.names[i] if name == "" { - return Value{}, fmt.Errorf("unnamed argument in invocation of %s", qualName) + return calcArgs{}, fmt.Errorf("unnamed argument in invocation of %s", qualName) } if err := target.unbound[i]; err != nil { - return Value{}, err + return calcArgs{}, err } if _, dup := named[name]; dup { - return Value{}, fmt.Errorf("%w: %s binds parameter %q twice", ErrCalcArity, qualName, name) + return calcArgs{}, fmt.Errorf("%w: %s binds parameter %q twice", ErrCalcArity, qualName, name) } val, err := ec.Eval(arg.Value) if err != nil { - return Value{}, err + return calcArgs{}, err } named[name] = val } - - // An argument that fails is reported before the target is judged. A name - // that resolves to nothing denotes nothing, not the library function of - // that name: the validator reports the same expression unresolved. - if target.calc == nil && target.library == nil { - return Value{}, ec.unresolvedInvocation(n.Type, qualName) - } - // Every invocation goes through the one calc path, so an expression and a - // direct InvokeCalc bind parameters and trace identically. The notation keeps - // the argument forms mutually exclusive. - callArgs := calcArgs{positional: args} - if len(named) > 0 { - callArgs = calcArgs{named: named} - } - if target.library != nil { - return target.library.invoke(ec.ctx, callArgs) - } - if target.shape == nil { - return ec.ctx.invokeCalcWithSelf(target.calc, callArgs, ec.scope, ec.self) - } - return ec.ctx.invokeCalcShape(target.shape, callArgs, ec.scope, ec.self) + return calcArgs{named: named}, nil } // invokeCalcShapeStacked evaluates exprs onto the context's argument stack and // invokes shape with them, popping them however the invocation ends. -func (ec *EvalContext) invokeCalcShapeStacked(shape *calcShape, exprs []ast.Node) (Value, error) { +func (ec *EvalContext) invokeCalcShapeStacked(shape *calcShape, exprs []ast.Node, enclosing []frame) (Value, error) { ctx := ec.ctx base := len(ctx.argStack) for _, arg := range exprs { @@ -2429,7 +2518,7 @@ func (ec *EvalContext) invokeCalcShapeStacked(shape *calcShape, exprs []ast.Node } top := len(ctx.argStack) args := ctx.argStack[base:top:top] - result, err := ctx.invokeCalcShape(shape, calcArgs{positional: args}, ec.scope, ec.self) + result, err := ctx.invokeCalcShapeIn(shape, calcArgs{positional: args}, ec.scope, ec.self, enclosing) ctx.popArgs(base) return result, err } @@ -2507,6 +2596,13 @@ func valueEqual(a, b Value) bool { return a.CoordinateFrame().equal(b.CoordinateFrame()) case ValCoordinateTransformation: return a.CoordinateTransformation().equal(b.CoordinateTransformation()) + case ValFunction: + // A function is the calc it is a value of, read against the same object; one + // closing over a body's bindings is equal only to the same read. + if a.FunctionClosesOverBody() || b.FunctionClosesOverBody() { + return a.function() == b.function() + } + return a.Function() == b.Function() && a.FunctionSelf() == b.FunctionSelf() default: return false } diff --git a/internal/core/runtime/eval_no_value_test.go b/internal/core/runtime/eval_no_value_test.go index ecd48901c..09ea6abbe 100644 --- a/internal/core/runtime/eval_no_value_test.go +++ b/internal/core/runtime/eval_no_value_test.go @@ -85,8 +85,8 @@ func TestChainOverValuelessOperandResolvesItsMembers(t *testing.T) { } // TestTypeDeclarationIsNotAValuelessFeature: a KerML type the parser records as -// a usage — a class, struct, behavior, datatype or function — is a type like a -// definition, not a feature that lacks a value. +// a usage — a class, struct, behavior or datatype — is a type like a definition, +// not a feature that lacks a value; a function, being a calc, is a function value. func TestTypeDeclarationIsNotAValuelessFeature(t *testing.T) { model, resolver, root := parseAndBuildModel(t, ` package test { @@ -104,7 +104,7 @@ package test { pkg, _ := root.LookupLocal("test") scope := pkg.Scope - for _, name := range []string{"Vehicle", "Frame", "Drive", "Mass", "Twice", "Car"} { + for _, name := range []string{"Vehicle", "Frame", "Drive", "Mass", "Car"} { _, err := ctx.EvalWithScope(parseExpr(t, name), scope) if err == nil || !strings.Contains(err.Error(), "cannot evaluate definition "+name) { t.Errorf("%s: err = %v; want cannot evaluate definition", name, err) @@ -115,6 +115,10 @@ package test { } } + if val, err := ctx.EvalWithScope(parseExpr(t, "Twice"), scope); err != nil || val.Kind != ValFunction || val.FunctionName() != "test::Twice" { + t.Errorf("Twice = %s, %v; want the function test::Twice", FormatValue(val), err) + } + car, _ := scope.LookupLocal("car") var noValue *NoValueError if _, err := ctx.EvalWithScope(parseExpr(t, "unsetMass"), car.Scope); !errors.As(err, &noValue) { diff --git a/internal/core/runtime/function_value.go b/internal/core/runtime/function_value.go new file mode 100644 index 000000000..a7e0ebef0 --- /dev/null +++ b/internal/core/runtime/function_value.go @@ -0,0 +1,189 @@ +package runtime + +import ( + "fmt" + + "github.com/Open-MBEE/OpenSysML/internal/core/ast" + "github.com/Open-MBEE/OpenSysML/internal/core/symbols" +) + +// functionValue is a calc read as a value: its lowered invocation interface and +// the environment it was read in, which an invocation of the value runs it against. +// A library calc the runtime implements natively carries that implementation instead. +type functionValue struct { + shape *calcShape + library *libraryFunction + scope *symbols.Scope // names the calc's defaults and body resolve against + self *Instance // object the calc's feature names resolve against, nil for none + // enclosing are the bindings of the behavior body the calc is declared in, as + // they stood when it was read; nil for a calc declared outside any body. + enclosing []frame +} + +// function is the payload of a ValFunction; nil for every other kind. +func (v Value) function() *functionValue { + if v.Kind != ValFunction { + return nil + } + fn, _ := v.ref.(*functionValue) + return fn +} + +// Function is the calc a ValFunction is a value of — the definition or usage it +// was read from; nil for every other kind. +func (v Value) Function() *symbols.Symbol { + if fn := v.function(); fn != nil && fn.shape != nil { + return fn.shape.Sym + } + return nil +} + +// FunctionName is the qualified name of the calc a ValFunction is a value of, as +// the wire and diagnostics spell it. +func (v Value) FunctionName() string { + if fn := v.function(); fn != nil && fn.shape != nil { + return fn.shape.Name + } + return "" +} + +// FunctionSelf is the object a ValFunction's feature names resolve against, nil +// for a function closing over none. +func (v Value) FunctionSelf() *Instance { + if fn := v.function(); fn != nil { + return fn.self + } + return nil +} + +// FunctionClosesOverBody reports a ValFunction closing over the bindings of the +// behavior body its calc is declared in, which nothing outside that run can rebuild. +func (v Value) FunctionClosesOverBody() bool { + fn := v.function() + return fn != nil && len(fn.enclosing) > 0 +} + +// functionValueOf is the value of the calc sym denotes in this environment: its +// lowered shape closed over the scope and object the read resolves against. A +// library calc applied natively is the value of that implementation; one bound +// by an unevaluated argument (a built-in) has no value to pass on. +func (ec *EvalContext) functionValueOf(sym *symbols.Symbol) (Value, error) { + if _, builtin := ec.ctx.builtinFor(sym); builtin { + return Value{}, fmt.Errorf("%w: %s binds its arguments unevaluated and cannot be read as a value", + ErrNotAFunction, ec.ctx.qualifiedSymbolName(sym)) + } + shape, err := ec.ctx.calcShapeOf(sym) + if err != nil { + return Value{}, err + } + fn := &functionValue{shape: shape, scope: ec.scope, self: ec.self} + fn.library, _ = ec.ctx.libraryFunctionFor(sym) + if enclosedByBehaviorBody(sym) && len(ec.frames) > 0 { + fn.enclosing = ec.closure().frames + } + return Value{Kind: ValFunction, ref: fn}, nil +} + +// isCalcDefSymbol reports a symbol declaring a calc definition or KerML function. +func isCalcDefSymbol(sym *symbols.Symbol) bool { + return sym != nil && sym.Kind == symbols.SymbolCalcDef +} + +// readsAsFunction reports a calc a bare read of its name denotes as a function: a +// calc definition, or a calc usage with an input no read could supply, which +// therefore computes no result to read. +func (ctx *Context) readsAsFunction(sym *symbols.Symbol) bool { + if isCalcDefSymbol(sym) { + return true + } + if !isCalcUsageSymbol(sym) { + return false + } + shape, err := ctx.calcShapeOf(sym) + return err == nil && shape.hasUnsuppliedInput() +} + +// hasUnsuppliedInput reports an input parameter no read of the calc could bind: +// neither an argument, a default nor an omitted optional supplies it. +func (shape *calcShape) hasUnsuppliedInput() bool { + for i := range shape.Params { + param := &shape.Params[i] + if param.Default == nil && !param.IsSubject && !param.optional() { + return true + } + } + return false +} + +// FunctionValue is the function a read of the declaration sym denotes — a calc +// definition, or a calc usage with an input no read could supply — closed over +// its own scope; false when sym is no calc read as one. +func (ctx *Context) FunctionValue(sym *symbols.Symbol) (Value, bool, error) { + return ctx.FunctionValueOn(sym, nil) +} + +// FunctionValueOn is FunctionValue with the calc's feature names resolving +// against the object self, as a calc usage read off a part does; nil for none. +func (ctx *Context) FunctionValueOn(sym *symbols.Symbol, self *Instance) (Value, bool, error) { + if !ctx.readsAsFunction(sym) { + return Value{}, false, nil + } + val, err := NewEvalContextIn(ctx, sym.OwnerScope, self).functionValueOf(sym) + return val, true, err +} + +// calcAsValue is the function value a bare read of sym denotes, false when sym +// is no calc read as one. +func (ec *EvalContext) calcAsValue(sym *symbols.Symbol) (Value, bool, error) { + if !ec.ctx.readsAsFunction(sym) { + return Value{}, false, nil + } + val, err := ec.functionValueOf(sym) + return val, true, err +} + +// boundFunction is the value the calc-typed feature callee holds in this +// environment — a parameter bound by argument, or a feature of the bound object — +// which an invocation of callee applies; false when nothing here binds it. +func (ec *EvalContext) boundFunction(callee *symbols.Symbol, qn *ast.QualifiedName) (Value, bool, error) { + if qn == nil || len(qn.Parts) != 1 || qn.Global || !isCalcUsageSymbol(callee) { + return Value{}, false, nil + } + name := qn.Parts[0].Text + if val, ok := ec.Lookup(name); ok { + return val, true, nil + } + if ec.self != nil && ec.selfFeatureInScope(name) { + val, ok, err := ec.selfFeatureValue(name) + if err != nil { + return Value{}, true, err + } + if ok && val.Kind == ValFunction { + return val, true, nil + } + } + return Value{}, false, nil +} + +// checkFunction refuses a value bound to a calc usage parameter that is no +// function; an omitted optional parameter holds null. +func (param *calcParameter) checkFunction(value *Value, what func() string) error { + if !param.IsCalc || value.Kind == ValFunction || value.Kind == ValNull { + return nil + } + return fmt.Errorf("%s: %w: %s is %s, not a function", + what(), ErrNotAFunction, FormatValue(*value), describeValue(*value)) +} + +// invokeFunction applies a value called as a function to args through the calc +// invocation path: a value that is no function is refused with a typed error. +func (ec *EvalContext) invokeFunction(callee string, val Value, args calcArgs) (Value, error) { + fn := val.function() + if fn == nil || fn.shape == nil { + return Value{}, fmt.Errorf("%w: %s is %s, not a function", ErrNotAFunction, callee, describeValue(val)) + } + if fn.library != nil { + return fn.library.invoke(ec.ctx, args) + } + return ec.ctx.invokeCalcShapeIn(fn.shape, args, fn.scope, fn.self, fn.enclosing) +} diff --git a/internal/core/runtime/invoke_calc.go b/internal/core/runtime/invoke_calc.go index 70edcb4e3..99c1ae55a 100644 --- a/internal/core/runtime/invoke_calc.go +++ b/internal/core/runtime/invoke_calc.go @@ -19,6 +19,8 @@ type calcParameter struct { Decl calcMemberDecl // the declaration, closest to the invoked calc, a bound value answers to // IsSubject marks a case's subject parameter, the object the case is about. IsSubject bool + // IsCalc marks a calc usage parameter (`in calc f`), which a function value binds. + IsCalc bool } // calcMemberDecl is the type and multiplicity a calc's parameter or output @@ -225,9 +227,11 @@ func (ctx *Context) calcShapeOf(sym *symbols.Symbol) (*calcShape, error) { shape.Bindings = calcBindings(chain) shape.ResultExpr = resultBindingExpr(shape.Bindings) // A calc computes nothing unless it returns or binds an output; an analysis - // also computes through its steps, or answers with its verdicts alone. + // also computes through its steps, or answers with its verdicts alone, and a + // library function the runtime implements natively computes through that. performs := shape.Kind == "analysis" && (len(shape.Nodes) > 0 || ctx.analysisChecks(sym)) - computes := lower.Returns(shape.Body) || len(shape.BodyOutputs) > 0 || shape.ResultExpr != nil || shape.hasInitialOutput() || performs + _, native := ctx.libraryFunctionFor(sym) + computes := lower.Returns(shape.Body) || len(shape.BodyOutputs) > 0 || shape.ResultExpr != nil || shape.hasInitialOutput() || performs || native if !computes { if len(shape.Outputs) > 0 && shape.resultOutput() == nil { return nil, fmt.Errorf("%w: %s binds none of its outputs (%s)", @@ -303,7 +307,10 @@ func (ctx *Context) calcParameters(chain []*symbols.Symbol, aliases *map[string] continue } sym := memberSymbol(declScope(link), usage) - param := calcParameter{Name: name, Default: usage.Value, Owner: link, Decl: ctx.calcMemberDeclOf(link, sym, name)} + param := calcParameter{ + Name: name, Default: usage.Value, Owner: link, + Decl: ctx.calcMemberDeclOf(link, sym, name), IsCalc: isCalcUsageSymbol(sym), + } if at, seen := ctx.redeclaredIndex(index, sym, name); seen { // A redeclaration binding no value keeps the inherited default, // which is written in the scope of the calc that stated it. @@ -599,14 +606,21 @@ func (ctx *Context) releaseInvocationFrame(frame *invocationFrame) { // in the calc's own environment: defaults and the result expression see the // parameters and the calc's lexical scope, never the caller's frames. func (ctx *Context) invokeCalcShape(shape *calcShape, args calcArgs, callerScope *symbols.Scope, self *Instance) (Value, error) { + return ctx.invokeCalcShapeIn(shape, args, callerScope, self, nil) +} + +// invokeCalcShapeIn is invokeCalcShape for a calc declared in a behavior body: +// enclosing holds that body's bindings, outermost first, which the calc's own shadow. +func (ctx *Context) invokeCalcShapeIn(shape *calcShape, args calcArgs, callerScope *symbols.Scope, self *Instance, enclosing []frame) (Value, error) { if err := shape.checkArgs(args); err != nil { return Value{}, err } // A pure body runs compiled unless the run is traced, which records every - // sub-expression, an argument is not a scalar, or a bound object may answer - // a library constant the body reads before the library does. - if ctx.compileCalcs && ctx.trace == nil { + // sub-expression, an argument is not a scalar, a bound object may answer + // a library constant the body reads before the library does, or the body + // reads the bindings enclosing it. + if ctx.compileCalcs && ctx.trace == nil && len(enclosing) == 0 { if compiled := ctx.compiledCalcOf(shape); compiled != nil && (self == nil || !compiled.readsLibrary) { if result, ran, err := compiled.invokeBoxed(ctx, args); ran { return result, err @@ -635,7 +649,7 @@ func (ctx *Context) invokeCalcShape(shape *calcShape, args calcArgs, callerScope ctx: ctx, scope: ctx.calcScope(shape.BodyOwner, shape.Sym, callerScope), self: self, - frames: append(frame.frames[:0], locals), + frames: append(append(frame.frames[:0], enclosing...), locals), trace: ctx.trace, activation: activation, } @@ -651,7 +665,7 @@ func (ctx *Context) invokeCalcShape(shape *calcShape, args calcArgs, callerScope return Value{}, err } - result, err := ctx.runCalcBody(shape, frame, callerScope, self, activation) + result, err := ctx.runCalcBody(shape, frame, callerScope, self, activation, enclosing) if ec.trace != nil { if err != nil { ec.trace.RecordCalculationExitError(shape.Kind, shape.Name, err) @@ -718,9 +732,13 @@ func (ctx *Context) bindCalcParameters( } // The parameter holds the value bound to it, so that value answers to the // parameter's declaration as a written one does. - if err := param.Decl.check(ctx, &value, func() string { + what := func() string { return fmt.Sprintf("%s: %s for parameter %q", shape.Label, source, param.Name) - }); err != nil { + } + if err := param.Decl.check(ctx, &value, what); err != nil { + return err + } + if err := param.checkFunction(&value, what); err != nil { return err } bindings.bindParam(i, param.Name, value) @@ -736,9 +754,9 @@ func (ctx *Context) bindCalcParameters( // the invocation yields: what the body returned, or, for a body that returns // nothing, the calc's designated output feature, evaluated in the invocation's // activation, which the caller ends after it. -func (ctx *Context) runCalcBody(shape *calcShape, frame *invocationFrame, callerScope *symbols.Scope, self *Instance, activation int64) (Value, error) { +func (ctx *Context) runCalcBody(shape *calcShape, frame *invocationFrame, callerScope *symbols.Scope, self *Instance, activation int64, enclosing []frame) (Value, error) { frame.host = calcStmtHost{ctx: ctx, shape: shape, self: self} - frame.env = stmtEnv{data: frame.locals()} + frame.env = stmtEnv{data: frame.locals(), enclosing: enclosing} frame.engine = stmtEngine{ctx: ctx, host: &frame.host, env: &frame.env, activation: activation, frameBuf: frame.engine.frameBuf} frame.host.attachPerformances(&frame.engine) result, returned, err := runCalcSteps(&frame.engine, &frame.host, shape) @@ -757,6 +775,9 @@ func (ctx *Context) runCalcBody(shape *calcShape, frame *invocationFrame, caller // through the same run bookkeeping a calc usage's outputs use. run := newCalcRun(shape, callerScope, self, frame.locals()) run.activation, run.perf = activation, frame.host.performance() + if len(enclosing) > 0 { + run.outer = &EvalContext{ctx: ctx, scope: callerScope, self: self, frames: enclosing, trace: ctx.trace, activation: activation} + } // The invocation already holds this evaluation's nesting feature value. run.onStack = true return run.value(ctx, out) @@ -952,7 +973,7 @@ func (ctx *Context) resolveLibraryPerformance(sym *symbols.Symbol) *libraryPerfo // written, declared and defaulted by the nearest of its redefinitions the model // states — with the position of the library input it redefines, -1 for none. func (ctx *Context) effectiveParameter(sym *symbols.Symbol, libInputs []*symbols.Symbol) (calcParameter, int) { - param := calcParameter{Name: sym.Name} + param := calcParameter{Name: sym.Name, IsCalc: isCalcUsageSymbol(sym)} if effective, _ := ast.EffectiveName(sym.Decl.(*ast.Usage)); effective != "" { param.Name = effective } diff --git a/internal/core/runtime/robustness_test.go b/internal/core/runtime/robustness_test.go index f6ab56774..2bf9ea8c1 100644 --- a/internal/core/runtime/robustness_test.go +++ b/internal/core/runtime/robustness_test.go @@ -358,6 +358,14 @@ func TestRuntimeRobustness(t *testing.T) { t.Run("action_local_write_of_a_wrong_typed_value", testActionLocalWriteOfAWrongTypedValue) t.Run("action_output_write_of_a_wrong_typed_value", testActionOutputWriteOfAWrongTypedValue) t.Run("performance_occurrence_write_of_a_wrong_typed_value", testPerformanceOccurrenceWriteOfAWrongTypedValue) + t.Run("function_value_call_of_a_non_function", testFunctionValueCallOfANonFunction) + t.Run("function_value_bound_to_a_non_function", testFunctionValueBoundToANonFunction) + t.Run("function_value_arity_mismatch", testFunctionValueArityMismatch) + t.Run("function_value_unknown_named_argument", testFunctionValueUnknownNamedArgument) + t.Run("function_value_unbound_calc_parameter", testFunctionValueUnboundCalcParameter) + t.Run("function_value_of_a_wrong_typed_calc", testFunctionValueOfAWrongTypedCalc) + t.Run("function_value_of_a_built_in", testFunctionValueOfABuiltIn) + t.Run("function_value_applied_to_itself_forever", testFunctionValueAppliedToItselfForever) } func testBindingConflict(t *testing.T) { @@ -11247,3 +11255,137 @@ func testNodeFlowIntoAPinTheTargetDoesNotDeclare(t *testing.T) { t.Fatalf("error = %v, want ErrNodePin", err) } } + +// functionValueFixture declares Fn, which applies its calc-typed parameter f to a. +const functionValueFixture = ` + private import ScalarValues::*; + calc def Sq { in v : Real; return : Real = v * v; } + calc def Add { in x : Real; in y : Real; return : Real = x + y; } + calc def Fn { in calc f { in v : Real; return : Real; } in a : Real; return : Real = f(a); } +` + +// invokeCalcExpecting evaluates the calc call expr against src with the standard library, +// on its own goroutine so a body that never terminates fails the case instead of stalling it. +func invokeCalcExpecting(t *testing.T, src, expr string) error { + t.Helper() + idx, _, ctx := buildRuntimeWithLibraries(t, "", parseAndBuild(t, src)) + ctx.maxSteps = 100000 + scope := idx.DocumentRoot("") + + done := make(chan error, 1) + go func() { + node := parser.New(source.New("", []byte(expr))).ParseExpression() + result, err := ctx.EvalWithScopeOn(node, scope, nil) + if err == nil { + err = fmt.Errorf("%s = %s, expected it to fail", expr, FormatTraceValue(result)) + } + done <- err + }() + select { + case err := <-done: + return err + case <-time.After(10 * time.Second): + t.Fatalf("%s did not terminate", expr) + return nil + } +} + +// testFunctionValueCallOfANonFunction: a scalar passed where a calc-typed parameter +// is declared is refused when it is bound, before the body calls it. +func testFunctionValueCallOfANonFunction(t *testing.T) { + err := invokeCalcExpecting(t, `package test {`+functionValueFixture+`}`, "test::Fn(3.0, 3.0)") + if !errors.Is(err, ErrNotAFunction) || !strings.Contains(err.Error(), `parameter "f"`) { + t.Fatalf("error = %v, want ErrNotAFunction naming f", err) + } +} + +// testFunctionValueBoundToANonFunction: a named argument binding a calc-typed +// parameter to an object is refused the same way. +func testFunctionValueBoundToANonFunction(t *testing.T) { + src := `package test {` + functionValueFixture + ` + part def Box; + part box : Box; + }` + err := invokeCalcExpecting(t, src, "test::Fn(a = 3.0, f = test::box)") + if !errors.Is(err, ErrNotAFunction) { + t.Fatalf("error = %v, want ErrNotAFunction", err) + } +} + +// testFunctionValueArityMismatch: applying a function value to more arguments +// than its calc declares is a calc arity error naming the calc the value is of. +func testFunctionValueArityMismatch(t *testing.T) { + src := `package test {` + functionValueFixture + ` + calc def Two { in calc f { in v : Real; return : Real; } return : Real = f(1.0, 2.0); } + }` + err := invokeCalcExpecting(t, src, "test::Two(test::Sq)") + if !errors.Is(err, ErrCalcArity) || !strings.Contains(err.Error(), "test::Sq") { + t.Fatalf("error = %v, want ErrCalcArity for test::Sq", err) + } +} + +// testFunctionValueUnknownNamedArgument: a named argument the applied calc does +// not declare is reported against that calc, not the parameter it was passed through. +func testFunctionValueUnknownNamedArgument(t *testing.T) { + src := `package test {` + functionValueFixture + ` + calc def Named { in calc f { in v : Real; return : Real; } return : Real = f(w = 1.0); } + }` + err := invokeCalcExpecting(t, src, "test::Named(test::Sq)") + if !errors.Is(err, ErrUnknownParameter) || !strings.Contains(err.Error(), "test::Sq") { + t.Fatalf("error = %v, want ErrUnknownParameter for test::Sq", err) + } +} + +// testFunctionValueUnboundCalcParameter: a calc-typed parameter no argument binds +// is reported as unbound when the calc is invoked, and applying a function value +// to fewer arguments than its calc needs is reported the same way. +func testFunctionValueUnboundCalcParameter(t *testing.T) { + src := `package test {` + functionValueFixture + ` + calc def Partial { in calc f { in x : Real; in y : Real; return : Real; } return : Real = f(1.0); } + }` + err := invokeCalcExpecting(t, src, "test::Fn(a = 3.0)") + if !errors.Is(err, ErrUnboundParameter) || !strings.Contains(err.Error(), `parameter "f"`) { + t.Fatalf("error = %v, want ErrUnboundParameter naming f", err) + } + err = invokeCalcExpecting(t, src, "test::Partial(test::Add)") + if !errors.Is(err, ErrUnboundParameter) || !strings.Contains(err.Error(), `parameter "y"`) { + t.Fatalf("error = %v, want ErrUnboundParameter naming y", err) + } +} + +// testFunctionValueOfAWrongTypedCalc: a calc-typed parameter typed by a calc def +// refuses a function value of an unrelated calc. +func testFunctionValueOfAWrongTypedCalc(t *testing.T) { + src := `package test {` + functionValueFixture + ` + calc def Typed { in calc f : Sq; return : Real = f(2.0); } + }` + err := invokeCalcExpecting(t, src, "test::Typed(test::Add)") + if !errors.Is(err, ErrTypeMismatch) { + t.Fatalf("error = %v, want ErrTypeMismatch", err) + } +} + +// testFunctionValueOfABuiltIn: a library function the runtime binds unevaluated +// has no value to pass on, and says so. +func testFunctionValueOfABuiltIn(t *testing.T) { + src := `package test {` + functionValueFixture + ` + private import ControlFunctions::*; + calc def PassIf { return : Real = Fn(ControlFunctions::'if', 3.0); } + }` + err := invokeCalcExpecting(t, src, "test::PassIf()") + if !errors.Is(err, ErrNotAFunction) { + t.Fatalf("error = %v, want ErrNotAFunction", err) + } +} + +// testFunctionValueAppliedToItselfForever: a calc passing itself as a function +// value to itself without end spends the recursion budget rather than hanging. +func testFunctionValueAppliedToItselfForever(t *testing.T) { + src := `package test {` + functionValueFixture + ` + calc def Loop { in calc f { in v : Real; return : Real; } in v : Real; return : Real = Loop(f, f(v)); } + }` + err := invokeCalcExpecting(t, src, "test::Loop(test::Sq, 1.0)") + if !errors.Is(err, ErrCalcRecursionLimit) && !errors.Is(err, ErrStepLimitExceeded) { + t.Fatalf("error = %v, want the recursion or step budget spent", err) + } +} diff --git a/internal/core/runtime/testdata/conformance/README.md b/internal/core/runtime/testdata/conformance/README.md index 2900020cd..286c217c9 100644 --- a/internal/core/runtime/testdata/conformance/README.md +++ b/internal/core/runtime/testdata/conformance/README.md @@ -261,6 +261,8 @@ Supported types: string (`{"type": "Variant", "value": "cutIdeal"}`) - `EnumLiteral`: the enumeration literal a value is, written as the enumeration declaring it qualifies it (`{"type": "EnumLiteral", "value": "Color::red"}`) +- `Function`: the qualified name of the calc a function value is a value of + (`{"type": "Function", "value": "test::Sq"}`) In place of a value, `error` states the text producing that value must fail with, for a slot or result whose contract is a diagnostic (`{"error": "not a diff --git a/internal/core/runtime/testdata/conformance/function_value_action_parameter.expected.json b/internal/core/runtime/testdata/conformance/function_value_action_parameter.expected.json new file mode 100644 index 000000000..f1bd8cb03 --- /dev/null +++ b/internal/core/runtime/testdata/conformance/function_value_action_parameter.expected.json @@ -0,0 +1,10 @@ +{ + "type": "action", + "evaluate": "test::outer", + "libraries": true, + "outputs": { + "result": {"type": "Real", "value": 12.0}, + "apply.a": {"type": "Real", "value": 2.0}, + "apply.y": {"type": "Real", "value": 12.0} + } +} diff --git a/internal/core/runtime/testdata/conformance/function_value_action_parameter.sysml b/internal/core/runtime/testdata/conformance/function_value_action_parameter.sysml new file mode 100644 index 000000000..ecaf223ff --- /dev/null +++ b/internal/core/runtime/testdata/conformance/function_value_action_parameter.sysml @@ -0,0 +1,34 @@ +package test { + private import ScalarValues::*; + + calc def Unary { in v : Real; return : Real; } + calc def Sq :> Unary { in :>> v; return : Real = v * v; } + calc def Cube :> Unary { in :>> v; return : Real = v * v * v; } + calc sq : Sq; + calc cube : Cube; + + // An action's `in calc` parameters hold the functions bound to them, and a + // body applies them as it applies any calc. + action def Apply { + in calc f { in v : Real; return : Real; } + in calc g : Unary; + in a : Real; + out y : Real; + + first step; + action step { assign y := f(a) + g(a); } + } + + action outer { + out attribute result : Real; + + first start; + then action apply : Apply { + in f = sq; + in g = cube; + in a = 2.0; + } + then action fin { assign result := apply.y; } + then done; + } +} diff --git a/internal/core/runtime/testdata/conformance/function_value_body_closure.expected.json b/internal/core/runtime/testdata/conformance/function_value_body_closure.expected.json new file mode 100644 index 000000000..2cb489289 --- /dev/null +++ b/internal/core/runtime/testdata/conformance/function_value_body_closure.expected.json @@ -0,0 +1,6 @@ +{ + "type": "calc", + "evaluate": "test::UseOuter", + "libraries": true, + "result": {"type": "Real", "value": 65.0} +} diff --git a/internal/core/runtime/testdata/conformance/function_value_body_closure.sysml b/internal/core/runtime/testdata/conformance/function_value_body_closure.sysml new file mode 100644 index 000000000..3e0ca1077 --- /dev/null +++ b/internal/core/runtime/testdata/conformance/function_value_body_closure.sysml @@ -0,0 +1,23 @@ +package test { + private import ScalarValues::*; + calc def Unary { in x : Real; return : Real; } + calc def Fn { in calc f { in x : Real; return : Real; } in a : Real; return : Real = f(a); } + calc def Outer { + in k : Real; + calc inner { in x : Real; return : Real = x * k; } + return : Real = Fn(inner, 2.0) + inner(3.0); + } + // A nested usage read bare computes over the enclosing run's bindings. + calc def Bare { + in k : Real; + calc inner { in x : Real = 2.0; return : Real = x * k; } + return : Real = inner; + } + // A nested calc returned as a function keeps the bindings of the run that returned it. + calc def Maker { + in k : Real; + calc scale :> Unary { in :>> x; return : Real = x * k; } + return : Unary = scale; + } + calc def UseOuter { return : Real = Outer(3.0) + Outer(4.0) + Bare(5.0) + Fn(Maker(10.0), 2.0); } +} diff --git a/internal/core/runtime/testdata/conformance/function_value_calc_usage.expected.json b/internal/core/runtime/testdata/conformance/function_value_calc_usage.expected.json new file mode 100644 index 000000000..367d7a89f --- /dev/null +++ b/internal/core/runtime/testdata/conformance/function_value_calc_usage.expected.json @@ -0,0 +1,6 @@ +{ + "type": "calc", + "evaluate": "test::UseCube", + "libraries": true, + "result": {"type": "Real", "value": 8.0} +} diff --git a/internal/core/runtime/testdata/conformance/function_value_calc_usage.sysml b/internal/core/runtime/testdata/conformance/function_value_calc_usage.sysml new file mode 100644 index 000000000..e15160c84 --- /dev/null +++ b/internal/core/runtime/testdata/conformance/function_value_calc_usage.sysml @@ -0,0 +1,6 @@ +package test { + private import ScalarValues::*; + calc cube { in v : Real; return : Real = v * v * v; } + calc def Fn { in calc f { in v : Real; return : Real; } in a : Real; return : Real = f(a); } + calc def UseCube { return : Real = Fn(cube, 2.0); } +} diff --git a/internal/core/runtime/testdata/conformance/function_value_chain_call.expected.json b/internal/core/runtime/testdata/conformance/function_value_chain_call.expected.json new file mode 100644 index 000000000..0e1ad5ed3 --- /dev/null +++ b/internal/core/runtime/testdata/conformance/function_value_chain_call.expected.json @@ -0,0 +1,6 @@ +{ + "type": "calc", + "evaluate": "test::UseChain", + "libraries": true, + "result": {"type": "Real", "value": 14.0} +} diff --git a/internal/core/runtime/testdata/conformance/function_value_chain_call.sysml b/internal/core/runtime/testdata/conformance/function_value_chain_call.sysml new file mode 100644 index 000000000..327e5eddf --- /dev/null +++ b/internal/core/runtime/testdata/conformance/function_value_chain_call.sysml @@ -0,0 +1,9 @@ +package test { + private import ScalarValues::*; + part def Holder { + attribute k : Real = 2.0; + calc scale { in x : Real; return : Real = x * k; } + } + part holder : Holder; + calc def UseChain { return : Real = holder.scale(3.0) + holder.scale(x = 4.0); } +} diff --git a/internal/core/runtime/testdata/conformance/function_value_feature_closure.expected.json b/internal/core/runtime/testdata/conformance/function_value_feature_closure.expected.json new file mode 100644 index 000000000..4ee42cb57 --- /dev/null +++ b/internal/core/runtime/testdata/conformance/function_value_feature_closure.expected.json @@ -0,0 +1,6 @@ +{ + "type": "calc", + "evaluate": "test::UseScalers", + "libraries": true, + "result": {"type": "Real", "value": 25.0} +} diff --git a/internal/core/runtime/testdata/conformance/function_value_feature_closure.sysml b/internal/core/runtime/testdata/conformance/function_value_feature_closure.sysml new file mode 100644 index 000000000..f90782adc --- /dev/null +++ b/internal/core/runtime/testdata/conformance/function_value_feature_closure.sysml @@ -0,0 +1,11 @@ +package test { + private import ScalarValues::*; + part def Scaler { + attribute k : Real; + calc scale { in x : Real; return : Real = x * k; } + } + part twice : Scaler { attribute :>> k = 2.0; } + part thrice : Scaler { attribute :>> k = 3.0; } + calc def Fn { in calc f { in x : Real; return : Real; } in a : Real; return : Real = f(a); } + calc def UseScalers { return : Real = Fn(twice.scale, 5.0) + Fn(thrice.scale, 5.0); } +} diff --git a/internal/core/runtime/testdata/conformance/function_value_library.expected.json b/internal/core/runtime/testdata/conformance/function_value_library.expected.json new file mode 100644 index 000000000..f928f7ba1 --- /dev/null +++ b/internal/core/runtime/testdata/conformance/function_value_library.expected.json @@ -0,0 +1,10 @@ +{ + "type": "instance", + "instantiate": "test::Holder", + "libraries": true, + "slots": { + "root": {"type": "Real", "value": 4.0}, + "floored": {"type": "Integer", "value": 2}, + "fn": {"type": "Function", "value": "RealFunctions::sqrt"} + } +} diff --git a/internal/core/runtime/testdata/conformance/function_value_library.sysml b/internal/core/runtime/testdata/conformance/function_value_library.sysml new file mode 100644 index 000000000..fe1429ffc --- /dev/null +++ b/internal/core/runtime/testdata/conformance/function_value_library.sysml @@ -0,0 +1,12 @@ +package test { + private import ScalarValues::*; + private import RealFunctions::*; + calc def Fn { in calc f { in v : Real; return : Real; } in a : Real; return : Real = f(a); } + calc def FnI { in calc f { in v : Real; return : Integer; } in a : Real; return : Integer = f(a); } + calc def Identity { in calc f { in v : Real; return : Real; } return r = f; } + part def Holder { + attribute root : Real = Fn(sqrt, 16.0); + attribute floored : Integer = FnI(floor, 2.75); + attribute fn = Identity(sqrt); + } +} diff --git a/internal/core/runtime/testdata/conformance/function_value_named_args.expected.json b/internal/core/runtime/testdata/conformance/function_value_named_args.expected.json new file mode 100644 index 000000000..eb50d6d28 --- /dev/null +++ b/internal/core/runtime/testdata/conformance/function_value_named_args.expected.json @@ -0,0 +1,6 @@ +{ + "type": "calc", + "evaluate": "test::UseApply", + "libraries": true, + "result": {"type": "Real", "value": 9.0} +} diff --git a/internal/core/runtime/testdata/conformance/function_value_named_args.sysml b/internal/core/runtime/testdata/conformance/function_value_named_args.sysml new file mode 100644 index 000000000..7d4014eda --- /dev/null +++ b/internal/core/runtime/testdata/conformance/function_value_named_args.sysml @@ -0,0 +1,10 @@ +package test { + private import ScalarValues::*; + calc def Pow { in base : Real; in exp : Real; return : Real = base ** exp; } + calc def Apply { + in calc f { in base : Real; in exp : Real; return : Real; } + in x : Real; + return : Real = f(exp = 2.0, base = x); + } + calc def UseApply { return : Real = Apply(x = 3.0, f = Pow); } +} diff --git a/internal/core/runtime/testdata/conformance/function_value_probe.expected.json b/internal/core/runtime/testdata/conformance/function_value_probe.expected.json new file mode 100644 index 000000000..f0e3942d0 --- /dev/null +++ b/internal/core/runtime/testdata/conformance/function_value_probe.expected.json @@ -0,0 +1,7 @@ +{ + "type": "calc", + "evaluate": "test::UseFn", + "libraries": true, + "result": {"type": "Real", "value": 9.0}, + "trace": true +} diff --git a/internal/core/runtime/testdata/conformance/function_value_probe.sysml b/internal/core/runtime/testdata/conformance/function_value_probe.sysml new file mode 100644 index 000000000..afb2dc484 --- /dev/null +++ b/internal/core/runtime/testdata/conformance/function_value_probe.sysml @@ -0,0 +1,6 @@ +package test { + private import ScalarValues::*; + calc def Sq { in v : Real; return : Real = v * v; } + calc def Fn { in calc f { in v : Real; return : Real; } in a : Real; return : Real = f(a); } + calc def UseFn { return : Real = Fn(Sq, 3.0); } +} diff --git a/internal/core/runtime/testdata/conformance/function_value_probe.trace.golden b/internal/core/runtime/testdata/conformance/function_value_probe.trace.golden new file mode 100644 index 000000000..256616302 --- /dev/null +++ b/internal/core/runtime/testdata/conformance/function_value_probe.trace.golden @@ -0,0 +1,20 @@ +enter calc test::UseFn + stmt return + eval feature Sq -> calc(test::Sq) + eval literal 3.0 -> 3.0 + enter calc test::Fn + bind f = calc(test::Sq) [argument] + bind a = 3.0 [argument] + stmt return + eval feature a -> 3.0 + enter calc test::Sq + bind v = 3.0 [argument] + stmt return + eval feature v -> 3.0 + eval feature v -> 3.0 + eval operator * -> 9.0 + exit calc test::Sq -> 9.0 + eval invoke f -> 9.0 + exit calc test::Fn -> 9.0 + eval invoke Fn -> 9.0 +exit calc test::UseFn -> 9.0 diff --git a/internal/core/runtime/testdata/conformance/function_value_read.expected.json b/internal/core/runtime/testdata/conformance/function_value_read.expected.json new file mode 100644 index 000000000..ef1594488 --- /dev/null +++ b/internal/core/runtime/testdata/conformance/function_value_read.expected.json @@ -0,0 +1,10 @@ +{ + "type": "instance", + "instantiate": "test::Holder", + "libraries": true, + "slots": { + "fn": {"type": "Function", "value": "test::Sq"}, + "same": {"type": "Boolean", "value": true}, + "other": {"type": "Boolean", "value": false} + } +} diff --git a/internal/core/runtime/testdata/conformance/function_value_read.sysml b/internal/core/runtime/testdata/conformance/function_value_read.sysml new file mode 100644 index 000000000..a7758eae3 --- /dev/null +++ b/internal/core/runtime/testdata/conformance/function_value_read.sysml @@ -0,0 +1,16 @@ +package test { + private import ScalarValues::*; + calc def Sq { in v : Real; return : Real = v * v; } + calc def Cube { in v : Real; return : Real = v * v * v; } + calc def Identity { in calc f { in v : Real; return : Real; } return r = f; } + calc def Same { + in calc f { in v : Real; return : Real; } + in calc g { in v : Real; return : Real; } + return : Boolean = f == g; + } + part def Holder { + attribute fn = Identity(Sq); + attribute same : Boolean = Same(Sq, Sq); + attribute other : Boolean = Same(Sq, Cube); + } +} diff --git a/internal/core/runtime/testdata/conformance/function_value_sampled.expected.json b/internal/core/runtime/testdata/conformance/function_value_sampled.expected.json new file mode 100644 index 000000000..644311e5d --- /dev/null +++ b/internal/core/runtime/testdata/conformance/function_value_sampled.expected.json @@ -0,0 +1,6 @@ +{ + "type": "calc", + "evaluate": "test::SquaresOf", + "libraries": true, + "result": {"type": "Sequence", "elements": [{"type": "Real", "value": 1.0}, {"type": "Real", "value": 4.0}, {"type": "Real", "value": 9.0}]} +} diff --git a/internal/core/runtime/testdata/conformance/function_value_sampled.sysml b/internal/core/runtime/testdata/conformance/function_value_sampled.sysml new file mode 100644 index 000000000..8ebe45ac7 --- /dev/null +++ b/internal/core/runtime/testdata/conformance/function_value_sampled.sysml @@ -0,0 +1,9 @@ +package test { + private import ScalarValues::*; + private import SampledFunctions::*; + calc def Sq { in v : Real; return : Real = v * v; } + calc def SquaresOf { + attribute sampled : SampledFunction = Sample(Sq, (1.0, 2.0, 3.0)); + return : Real[0..*] = Range(sampled); + } +} diff --git a/internal/core/runtime/testdata/conformance/function_value_sampled_closure.expected.json b/internal/core/runtime/testdata/conformance/function_value_sampled_closure.expected.json new file mode 100644 index 000000000..340515046 --- /dev/null +++ b/internal/core/runtime/testdata/conformance/function_value_sampled_closure.expected.json @@ -0,0 +1,6 @@ +{ + "type": "calc", + "evaluate": "test::ScaledDomain", + "libraries": true, + "result": {"type": "Sequence", "elements": [{"type": "Real", "value": 1.0}, {"type": "Real", "value": 2.0}, {"type": "Real", "value": 10.0}, {"type": "Real", "value": 20.0}]} +} diff --git a/internal/core/runtime/testdata/conformance/function_value_sampled_closure.sysml b/internal/core/runtime/testdata/conformance/function_value_sampled_closure.sysml new file mode 100644 index 000000000..44d85cbe5 --- /dev/null +++ b/internal/core/runtime/testdata/conformance/function_value_sampled_closure.sysml @@ -0,0 +1,13 @@ +package test { + private import ScalarValues::*; + private import SampledFunctions::*; + part def Scaler { + attribute k : Real = 10.0; + calc scale { in x : Real; return : Real = x * k; } + } + part scaler : Scaler; + calc def ScaledDomain { + attribute sampled : SampledFunction = Sample(calculation = scaler.scale, domainValues = (1.0, 2.0)); + return : Real[0..*] = (Domain(sampled), Range(sampled)); + } +} diff --git a/internal/core/runtime/trace.go b/internal/core/runtime/trace.go index 4705439eb..20abefd48 100644 --- a/internal/core/runtime/trace.go +++ b/internal/core/runtime/trace.go @@ -396,6 +396,8 @@ func FormatTraceValue(v Value) string { return v.CoordinateTransformation().String() case ValExpr: return fmt.Sprintf("expr(%s)", TraceLabel(v.Expr())) + case ValFunction: + return fmt.Sprintf("calc(%s)", v.FunctionName()) default: return v.Kind.String() } diff --git a/internal/core/runtime/value.go b/internal/core/runtime/value.go index 5ba79c3f5..ca48037b3 100644 --- a/internal/core/runtime/value.go +++ b/internal/core/runtime/value.go @@ -34,6 +34,7 @@ const ( ValTensorQuantity // a TensorQuantityValue: an array of numbers with a measurement unit per component ValCoordinateFrame // a VectorMeasurementReference: a frame's axes, or a measurement scale's one ValCoordinateTransformation // a CoordinateTransformation: a placement of one frame in another + ValFunction // a calc as a value: its lowered shape closed over the environment it was read in // valueKindCount bounds the kinds; TestEveryValueKindIsDispatched walks them. valueKindCount @@ -102,6 +103,8 @@ func FormatValue(v Value) string { return v.CoordinateTransformation().String() case ValExpr: return "" + case ValFunction: + return v.FunctionName() default: return unknownText } @@ -165,6 +168,8 @@ func (k ValueKind) String() string { return "coordinate frame" case ValCoordinateTransformation: return "coordinate transformation" + case ValFunction: + return "function" default: return "invalid" } @@ -179,8 +184,9 @@ type Value struct { Instance int64 // ValInstance: instance ID; ValVariant: materialized object, 0 for none // ref holds the kind-specific payload of the remaining kinds: a string // (ValString), *Sequence, *Set, *exprValue (ValExpr), *Quantity, a complex128 - // (ValComplex), *Array, *Vector, *VectorQuantity, *MeasurementRef, *TensorQuantity, or the - // *symbols.Symbol of a variant (ValVariant) or enumeration literal (ValEnumLiteral). + // (ValComplex), *Array, *Vector, *VectorQuantity, *MeasurementRef, *TensorQuantity, + // *functionValue (ValFunction), or the *symbols.Symbol of a variant (ValVariant) or + // enumeration literal (ValEnumLiteral). ref any } diff --git a/internal/core/runtime/value_equality.go b/internal/core/runtime/value_equality.go index 217a1181e..aab5efb4c 100644 --- a/internal/core/runtime/value_equality.go +++ b/internal/core/runtime/value_equality.go @@ -20,6 +20,8 @@ type valueKey struct { colHash uint64 variant *symbols.Symbol literal *symbols.Symbol + calc *symbols.Symbol + closure *functionValue } // valueKeyFunc extracts a comparable key from a Value. Values valueEqual holds @@ -77,6 +79,15 @@ func valueKeyFunc(v Value) valueKey { key.strVal = v.CoordinateFrame().key() case ValCoordinateTransformation: key.strVal = v.CoordinateTransformation().key() + case ValFunction: + key.calc = v.Function() + if self := v.FunctionSelf(); self != nil { + key.instID = self.ID + } + // A function closing over a body's bindings is one only with itself. + if v.FunctionClosesOverBody() { + key.closure = v.function() + } } return key } diff --git a/internal/core/runtime/value_kinds_test.go b/internal/core/runtime/value_kinds_test.go index 2e3ec946c..c19444029 100644 --- a/internal/core/runtime/value_kinds_test.go +++ b/internal/core/runtime/value_kinds_test.go @@ -34,6 +34,9 @@ func kindSamples() map[ValueKind][2]Value { return &CoordinateFrame{Dimensions: []int64{int64(len(axes))}, Axes: axes, Text: text} } spatial, temporal := frameOf("spatial", metre, metre), frameOf("temporal", second, second) + functionOf := func(name string, sym *symbols.Symbol) Value { + return Value{Kind: ValFunction, ref: &functionValue{shape: &calcShape{Sym: sym, Name: name}}} + } placementOf := func(source, target *CoordinateFrame, origin int64) *CoordinateTransformation { return &CoordinateTransformation{Source: source, Target: target, Placement: &FramePlacement{ Origin: NewVectorQuantityValue([]semantics.Value{integerValue(origin).Const, integerValue(0).Const}, source.Axes), @@ -62,6 +65,7 @@ func kindSamples() map[ValueKind][2]Value { NewTensorQuantityValue([]int64{2, 2}, []semantics.Value{integerValue(1).Const, integerValue(2).Const, integerValue(3).Const, integerValue(4).Const}, []Unit{second, second, second, second}), }, ValMeasurementRef: {NewMeasurementRefValue(metre), NewMeasurementRefValue(second)}, + ValFunction: {functionOf("a", symA), functionOf("b", symB)}, ValCoordinateFrame: {NewCoordinateFrameValue(spatial), NewCoordinateFrameValue(temporal)}, ValCoordinateTransformation: { NewCoordinateTransformationValue(placementOf(spatial, temporal, 1)), @@ -70,6 +74,32 @@ func kindSamples() map[ValueKind][2]Value { } } +// A function read against the same object is one value however often it is +// read; one closing over a body's bindings is equal only to the same read, since +// two runs of the body bind the names it closes over differently. +func TestFunctionValueIdentity(t *testing.T) { + sym := &symbols.Symbol{Name: "inner"} + shape := &calcShape{Sym: sym, Name: "inner"} + self := &Instance{ID: 7} + bare := func() Value { return Value{Kind: ValFunction, ref: &functionValue{shape: shape, self: self}} } + closing := func() Value { + return Value{Kind: ValFunction, ref: &functionValue{shape: shape, self: self, enclosing: []frame{{vars: map[string]Value{"k": integerValue(1)}}}}} + } + if a, b := bare(), bare(); !valueEqual(a, b) || valueKeyFunc(a) != valueKeyFunc(b) { + t.Errorf("two reads of %s against one object are not one value", FormatValue(a)) + } + c := closing() + if !valueEqual(c, c) || valueKeyFunc(c) != valueKeyFunc(c) { + t.Errorf("a body-closing function is not equal to itself") + } + if d := closing(); valueEqual(c, d) || valueKeyFunc(c) == valueKeyFunc(d) { + t.Errorf("two body-closing reads of %s compare equal", FormatValue(c)) + } + if b := bare(); valueEqual(b, c) || valueEqual(c, b) || valueKeyFunc(b) == valueKeyFunc(c) { + t.Errorf("a body-closing read of %s equals a bare one", FormatValue(c)) + } +} + // TestEveryValueKindIsDispatched walks every ValueKind through the surfaces that // switch on it — its name, its renderings, its description, equality and set // keying — so a new kind cannot fall through to a fallback arm unnoticed. diff --git a/internal/grpc/analysis.go b/internal/grpc/analysis.go index fc600bc82..44ed67699 100644 --- a/internal/grpc/analysis.go +++ b/internal/grpc/analysis.go @@ -89,7 +89,7 @@ func (v *verifyContext) analysisArgument(arg *pb.Value) (runtime.Value, *pb.RunA if err := v.service.requireValueCapabilities(arg); err != nil { return runtime.Value{}, nil, err } - val, err := ProtoToValueIn(arg, v.cached.Index, v.sem) + val, err := ProtoToRuntimeValue(v.runtime, arg, v.cached.Index, v.sem) if err != nil { return runtime.Value{}, &pb.RunAnalysisResponse{ Error: fmt.Sprintf("analysis argument could not be read: %v", err), diff --git a/internal/grpc/capability_response.go b/internal/grpc/capability_response.go index c8651db28..a7beffa15 100644 --- a/internal/grpc/capability_response.go +++ b/internal/grpc/capability_response.go @@ -93,6 +93,10 @@ func (s *Service) filterValueCapabilities(value *pb.Value) { shown := displayValue(value) value.Kind = &pb.Value_Null{Null: "unsupported: " + shown.Kind.String() + " " + runtime.FormatValue(shown)} } + case *pb.Value_Function: + if !s.capabilities.has(CapabilityFunctionValues) { + value.Kind = &pb.Value_Null{Null: "unsupported: " + runtime.ValFunction.String() + " " + kind.Function.GetCalcId()} + } } } diff --git a/internal/grpc/convert.go b/internal/grpc/convert.go index 09b4fd013..41734bee1 100644 --- a/internal/grpc/convert.go +++ b/internal/grpc/convert.go @@ -302,6 +302,11 @@ func ValueToProtoIn(rt *runtime.Context, val runtime.Value, idx *symbols.Index) return &pb.Value{Kind: &pb.Value_VectorQuantity{VectorQuantity: pvq}} case runtime.ValMeasurementRef: return &pb.Value{Kind: &pb.Value_MeasurementRef{MeasurementRef: MeasurementRefToProto(val.MeasurementRef())}} + case runtime.ValFunction: + if val.FunctionClosesOverBody() { + return &pb.Value{Kind: &pb.Value_Null{Null: "unsupported: function " + val.FunctionName() + " closing over a body's bindings"}} + } + return &pb.Value{Kind: &pb.Value_Function{Function: functionToProto(val, idx)}} case runtime.ValTensorQuantity: // No wire arm carries dimensions above one with a unit per component. return &pb.Value{Kind: &pb.Value_Null{Null: "unsupported: " + val.Kind.String() + " " + runtime.FormatValue(val)}} @@ -313,6 +318,19 @@ func ValueToProtoIn(rt *runtime.Context, val runtime.Value, idx *symbols.Index) } } +// functionToProto names a function by the calc declaration it is a value of and +// the object it closes over, if any. +func functionToProto(val runtime.Value, idx *symbols.Index) *pb.Function { + fn := &pb.Function{CalcId: val.FunctionName()} + if idx != nil && val.Function() != nil { + fn.CalcId = idx.GetFQN(val.Function()) + } + if self := val.FunctionSelf(); self != nil { + fn.SelfId = self.ID + } + return fn +} + // enumLiteralToProto names a literal by the declaration it is, which is its // identity, and by the enumeration declaring it. Nil for an unresolved literal. func enumLiteralToProto(val runtime.Value, idx *symbols.Index) *pb.EnumLiteral { @@ -477,6 +495,14 @@ var ( // ErrUnitIDMismatch reports a unit_id whose declaration the unit text sent // with it does not spell, so the two name different units. ErrUnitIDMismatch = errors.New("unit as written does not spell unit_id") + + // ErrFunctionNeedsRuntime reports a Function read with no runtime to bind + // its calc in: only a runtime can turn a calc's name into a callable value. + ErrFunctionNeedsRuntime = errors.New("function needs a runtime to bind its calc") + + // ErrFunctionUnbound reports a Function naming no calc of the model read as + // a function, or an object the runtime does not hold. + ErrFunctionUnbound = errors.New("function names no calc of this model") ) // ValueCarriesMeasurementRef reports whether a value, or any value nested in @@ -488,6 +514,15 @@ func ValueCarriesMeasurementRef(pv *pb.Value) bool { }) } +// ValueCarriesFunction reports whether a value, or any value nested in it, is a +// Function: the kind function_values governs. +func ValueCarriesFunction(pv *pb.Value) bool { + return valueCarries(pv, func(v *pb.Value) bool { + _, ok := v.GetKind().(*pb.Value_Function) + return ok + }) +} + // ValueCarriesComplex reports whether a value, or any value nested in it, is a // Complex: the kind the complex_values capability governs. func ValueCarriesComplex(pv *pb.Value) bool { @@ -538,8 +573,16 @@ func nestedValues(pv *pb.Value) []*pb.Value { // ProtoToValueIn converts a protobuf Value to a runtime.Value in the model idx // and sem describe, resolving a quantity's base units against them. Inverse of -// ValueToProto. +// ValueToProto. A function, which only a runtime can bind, is refused: read +// one with ProtoToRuntimeValue. func ProtoToValueIn(pv *pb.Value, idx *symbols.Index, sem *semantics.Model) (runtime.Value, error) { + return ProtoToRuntimeValue(nil, pv, idx, sem) +} + +// ProtoToRuntimeValue is ProtoToValueIn for a value bound for the runtime rt, +// which is what a function's calc is bound in and its object looked up in; +// rt may be nil for a value naming no function. +func ProtoToRuntimeValue(rt *runtime.Context, pv *pb.Value, idx *symbols.Index, sem *semantics.Model) (runtime.Value, error) { if pv == nil { return runtime.Value{Kind: runtime.ValNull}, nil } @@ -550,11 +593,13 @@ func ProtoToValueIn(pv *pb.Value, idx *symbols.Index, sem *semantics.Model) (run return ProtoToQuantity(k.Quantity, idx, sem) case *pb.Value_EnumLiteral: return enumLiteralFromProto(k.EnumLiteral, idx) + case *pb.Value_Function: + return functionFromProto(rt, k.Function, idx) case *pb.Value_Sequence: seq := runtime.NewSequence() if k.Sequence != nil { for _, elem := range k.Sequence.Elements { - val, err := ProtoToValueIn(elem, idx, sem) + val, err := ProtoToRuntimeValue(rt, elem, idx, sem) if err != nil { return runtime.Value{}, err } @@ -563,7 +608,7 @@ func ProtoToValueIn(pv *pb.Value, idx *symbols.Index, sem *semantics.Model) (run } return runtime.NewSequenceValue(seq), nil case *pb.Value_Array: - return protoToArray(k.Array, idx, sem) + return protoToArray(rt, k.Array, idx, sem) case *pb.Value_Vector: return protoToVector(k.Vector) case *pb.Value_VectorQuantity: @@ -575,16 +620,47 @@ func ProtoToValueIn(pv *pb.Value, idx *symbols.Index, sem *semantics.Model) (run } } +// functionFromProto binds a function to the calc its calc_id names in rt's +// model, read as a value in its own scope, against the object self_id names. +func functionFromProto(rt *runtime.Context, fn *pb.Function, idx *symbols.Index) (runtime.Value, error) { + if fn == nil || fn.GetCalcId() == "" { + return runtime.Value{}, fmt.Errorf("%w: calc_id is empty", ErrFunctionUnbound) + } + if rt == nil || idx == nil { + return runtime.Value{}, fmt.Errorf("%w: function %s", ErrFunctionNeedsRuntime, fn.GetCalcId()) + } + var self *runtime.Instance + if fn.GetSelfId() != 0 { + inst, ok := rt.Instance(fn.GetSelfId()) + if !ok { + return runtime.Value{}, fmt.Errorf("%w: %s: self_id %d names no object of this runtime", + ErrFunctionUnbound, fn.GetCalcId(), fn.GetSelfId()) + } + self = inst + } + for _, sym := range idx.LookupQualified(fn.GetCalcId()) { + val, isFunction, err := rt.FunctionValueOn(sym, self) + if !isFunction { + continue + } + if err != nil { + return runtime.Value{}, fmt.Errorf("%w: %s: %v", ErrFunctionUnbound, fn.GetCalcId(), err) + } + return val, nil + } + return runtime.Value{}, fmt.Errorf("%w: %s is not a calc", ErrFunctionUnbound, fn.GetCalcId()) +} + // protoToArray rebuilds an array, refusing a shape its elements do not fill // rather than reading them under some other shape. -func protoToArray(pa *pb.Array, idx *symbols.Index, sem *semantics.Model) (runtime.Value, error) { +func protoToArray(rt *runtime.Context, pa *pb.Array, idx *symbols.Index, sem *semantics.Model) (runtime.Value, error) { dimensions := slices.Clone(pa.GetDimensions()) if err := CheckArrayShape(dimensions, len(pa.GetElements())); err != nil { return runtime.Value{}, err } elements := make([]runtime.Value, 0, len(pa.GetElements())) for _, elem := range pa.GetElements() { - val, err := ProtoToValueIn(elem, idx, sem) + val, err := ProtoToRuntimeValue(rt, elem, idx, sem) if err != nil { return runtime.Value{}, err } diff --git a/internal/grpc/convert_function_test.go b/internal/grpc/convert_function_test.go new file mode 100644 index 000000000..f83dc9d6d --- /dev/null +++ b/internal/grpc/convert_function_test.go @@ -0,0 +1,318 @@ +package grpc + +import ( + "context" + "errors" + "strings" + "testing" + + "connectrpc.com/connect" + + pb "github.com/Open-MBEE/OpenSysML/api/proto" + "github.com/Open-MBEE/OpenSysML/internal/core/runtime" +) + +// functionWireModel yields calcs as values — a definition, a usage with an +// unsupplied input, one read off a part, and one closing over a body — and +// takes one back as a calc argument and an action input that apply it. +const functionWireModel = ` +package F { + private import ScalarValues::*; + + calc def Unary { in v : Real; return : Real; } + calc def Sq :> Unary { in :>> v; return : Real = v * v; } + calc def Cube :> Unary { in :>> v; return : Real = v * v * v; } + calc def Apply { in calc f : Unary; in a : Real; return : Real = f(a); } + calc apply : Apply; + calc sq : Sq; + calc def PickSq { return : Unary = sq; } + calc pickSq : PickSq; + attribute fns = (Sq, Cube); + + part def Holder { + attribute k : Real = 2.0; + calc scale { in x : Real; return : Real = x * k; } + } + part holder : Holder; + + calc def Outer { + in k : Real; + calc inner :> Unary { in :>> v; return : Real = v * k; } + return : Unary = inner; + } + calc outer : Outer; + + action run { + in calc f { in v : Real; return : Real; } + in a : Real; + out y : Real; + first start; + action inner { assign y := f(a); } + then done; + succession first start then inner; + } +} +` + +// mustEvaluateIn evaluates expr with the subject named instantiated as self. +func mustEvaluateIn(t *testing.T, srv *Service, modelHash, subject, expr string) *pb.Value { + t.Helper() + resp, err := srv.Evaluate(context.Background(), &pb.EvaluateRequest{ModelHash: modelHash, SubjectSymbolId: subject, Expression: expr}) + if err != nil { + t.Fatalf("Evaluate(%s in %s): %v", expr, subject, err) + } + if resp.Error != "" { + t.Fatalf("Evaluate(%s in %s): %s", expr, subject, resp.Error) + } + return resp.Result +} + +func functionValue(calcID string, selfID int64) *pb.Value { + return &pb.Value{Kind: &pb.Value_Function{Function: &pb.Function{CalcId: calcID, SelfId: selfID}}} +} + +// A calc held as a value crosses as the declaration it is a value of; a client +// echoing what the service sent reads back the same function, which applies. +func TestFunctionRoundTrip(t *testing.T) { + ctx := context.Background() + srv := mustNewService(t, 4) + modelHash := mustParse(t, srv, functionWireModel) + cached, ok := srv.cache.Get(modelHash) + if !ok { + t.Fatal("parsed model is not cached") + } + idx, sem := cached.Index, NewSymbolContext(cached.Index).Semantics + + for expr, want := range map[string]string{ + "F::Sq": "F::Sq", + "F::sq": "F::sq", + "F::pickSq": "F::sq", + "F::apply": "F::apply", + } { + pv := mustEvaluate(t, srv, modelHash, expr) + fn := pv.GetFunction() + if fn == nil { + t.Fatalf("%s crossed as %T: %v", expr, pv.GetKind(), pv) + } + if fn.GetCalcId() != want || fn.GetSelfId() != 0 { + t.Errorf("%s = %v, want calc_id %q closing over no object", expr, fn, want) + } + + rt, _, release := srv.newRuntime(cached) + back, err := ProtoToRuntimeValue(rt, pv, idx, sem) + if err != nil { + release() + t.Fatalf("ProtoToRuntimeValue(%s): %v", expr, err) + } + if back.Kind != runtime.ValFunction || runtime.FormatValue(back) != want { + t.Errorf("%s read back as %s %s, want the function %s", expr, back.Kind, runtime.FormatValue(back), want) + } + release() + } + + fns := mustEvaluate(t, srv, modelHash, "F::fns") + elems := fns.GetSequence().GetElements() + if len(elems) != 2 || elems[0].GetFunction().GetCalcId() != "F::Sq" || elems[1].GetFunction().GetCalcId() != "F::Cube" { + t.Fatalf("F::fns = %v, want a sequence of the functions Sq and Cube", fns) + } + + // A calc usage read off a part closes over that part, which crosses by ID and + // resolves the calc's feature names when the value comes back. + scale := mustEvaluateIn(t, srv, modelHash, "F::holder", "scale") + if scale.GetFunction().GetCalcId() != "F::Holder::scale" || scale.GetFunction().GetSelfId() == 0 { + t.Fatalf("holder.scale = %v, want the calc F::Holder::scale closing over holder", scale) + } + func() { + rt, _, release := srv.newRuntime(cached) + defer release() + holder, err := rt.Instantiate(lookupNamed(idx, "F::holder")[0]) + if err != nil { + t.Fatalf("Instantiate(holder): %v", err) + } + fn, isFunction, err := rt.FunctionValueOn(lookupNamed(idx, "F::Holder::scale")[0], holder) + if err != nil || !isFunction { + t.Fatalf("FunctionValueOn(scale, holder) = %v, %v, %v", fn, isFunction, err) + } + pv := ValueToProtoIn(rt, fn, idx) + if pv.GetFunction().GetCalcId() != "F::Holder::scale" || pv.GetFunction().GetSelfId() != holder.ID { + t.Fatalf("scale over holder crossed as %v, want calc_id F::Holder::scale, self_id %d", pv, holder.ID) + } + back, err := ProtoToRuntimeValue(rt, pv, idx, sem) + if err != nil || back.Kind != runtime.ValFunction || back.FunctionSelf() != holder { + t.Errorf("scale over holder read back as %v, %v; want the function over holder", back, err) + } + if back.Function() != fn.Function() { + t.Errorf("scale over holder read back as the calc %v, want %v", back.Function(), fn.Function()) + } + }() + + // A function read in applies as the argument of a calc and an action. + calc, err := srv.EvaluateCalc(ctx, &pb.EvaluateCalcRequest{ModelHash: modelHash, SymbolId: "F::apply", Arguments: []*pb.Value{functionValue("F::Sq", 0), realValue(3)}}) + if err != nil || calc.Error != "" { + t.Fatalf("EvaluateCalc(apply): err = %v, error = %q", err, calc.GetError()) + } + if got := calc.Result.GetRealValue(); got != 9 { + t.Errorf("apply(Sq, 3.0) = %v, want 9.0", calc.Result) + } + act, err := srv.ExecuteAction(ctx, &pb.ExecuteActionRequest{ + ModelHash: modelHash, + ActionSymbolId: "F::run", + Inputs: map[string]*pb.Value{"f": functionValue("F::Cube", 0), "a": realValue(2)}, + }) + if err != nil || act.Error != "" { + t.Fatalf("ExecuteAction: err = %v, error = %q", err, act.GetError()) + } + if y := act.Outputs["y"].GetRealValue(); y != 8 { + t.Errorf("output y = %v, want 8.0", act.Outputs["y"]) + } + + // A function closing over a body's bindings has no wire form: it is named + // as unsupported rather than sent as a calc it could not be rebuilt from. + closed := mustEvaluate(t, srv, modelHash, "F::outer(3.0)") + if closed.GetFunction() != nil || !strings.Contains(closed.GetNull(), "unsupported: function F::Outer::inner") { + t.Errorf("F::outer(3.0) = %v, want an unsupported null naming F::Outer::inner", closed) + } +} + +// A function naming no calc of the model, an object the runtime does not +// hold, or read with no runtime to bind it in, is refused with a typed error. +func TestMalformedFunctionsAreRejected(t *testing.T) { + ctx := context.Background() + srv := mustNewService(t, 4) + modelHash := mustParse(t, srv, functionWireModel) + cached, _ := srv.cache.Get(modelHash) + idx, sem := cached.Index, NewSymbolContext(cached.Index).Semantics + rt, _, release := srv.newRuntime(cached) + + cases := []struct { + name string + val *pb.Value + want error + }{ + {"empty", functionValue("", 0), ErrFunctionUnbound}, + {"unknown declaration", functionValue("F::Nope", 0), ErrFunctionUnbound}, + {"declaration that is not a calc", functionValue("F::holder", 0), ErrFunctionUnbound}, + {"calc usage computing a result", functionValue("F::pickSq", 0), ErrFunctionUnbound}, + {"object the runtime does not hold", functionValue("F::Sq", 12345), ErrFunctionUnbound}, + {"nested in a sequence", &pb.Value{Kind: &pb.Value_Sequence{Sequence: &pb.ValueSequence{Elements: []*pb.Value{ + intValue(1), functionValue("F::Nope", 0), + }}}}, ErrFunctionUnbound}, + {"nested in an array", arrayValue([]int64{1}, functionValue("", 0)), ErrFunctionUnbound}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + val, err := ProtoToRuntimeValue(rt, tc.val, idx, sem) + if !errors.Is(err, tc.want) { + t.Fatalf("ProtoToRuntimeValue = %v, %v; want %v", val, err, tc.want) + } + if val.Kind != runtime.ValInvalid { + t.Errorf("a rejected value was still returned: %v", val) + } + }) + } + + for name, val := range map[string]*pb.Value{ + "bare": functionValue("F::Sq", 0), + "in array": arrayValue([]int64{1}, functionValue("F::Sq", 0)), + } { + if _, err := ProtoToValueIn(val, idx, sem); !errors.Is(err, ErrFunctionNeedsRuntime) { + t.Errorf("%s without a runtime: err = %v, want %v", name, err, ErrFunctionNeedsRuntime) + } + } + release() + + // Over the service, a malformed argument is an in-band error, as a + // malformed quantity is; a function where a number is due is a type error. + calc, err := srv.EvaluateCalc(ctx, &pb.EvaluateCalcRequest{ModelHash: modelHash, SymbolId: "F::apply", Arguments: []*pb.Value{functionValue("F::Nope", 0), realValue(3)}}) + if err != nil { + t.Fatalf("EvaluateCalc(unknown): %v", err) + } + if !strings.Contains(calc.Error, ErrFunctionUnbound.Error()) { + t.Errorf("EvaluateCalc(unknown) error = %q, want one naming %v", calc.Error, ErrFunctionUnbound) + } + calc, err = srv.EvaluateCalc(ctx, &pb.EvaluateCalcRequest{ModelHash: modelHash, SymbolId: "F::apply", Arguments: []*pb.Value{realValue(3), realValue(3)}}) + if err != nil { + t.Fatalf("EvaluateCalc(scalar as function): %v", err) + } + if !strings.Contains(calc.Error, runtime.ErrTypeMismatch.Error()) { + t.Errorf("EvaluateCalc(scalar as function) error = %q, want one naming %v", calc.Error, runtime.ErrTypeMismatch) + } +} + +// The arm is advertised under its own capability; a service withholding it +// names the function as unsupported and refuses one sent to it. +func TestFunctionCapability(t *testing.T) { + ctx := context.Background() + found := false + for _, c := range Capabilities() { + found = found || c == CapabilityFunctionValues + } + if !found { + t.Errorf("capabilities %v do not include %q", Capabilities(), CapabilityFunctionValues) + } + + withheld := mustNewServiceWithout(t, CapabilityFunctionValues) + modelHash := mustParse(t, withheld, functionWireModel) + for expr, want := range map[string]string{ + "F::Sq": "unsupported: function F::Sq", + "F::pickSq": "unsupported: function F::sq", + } { + got := mustEvaluate(t, withheld, modelHash, expr) + if got.GetFunction() != nil { + t.Errorf("%s crossed as a function without %s: %v", expr, CapabilityFunctionValues, got) + } + if got.GetNull() != want { + t.Errorf("%s without %s = %v, want null %q", expr, CapabilityFunctionValues, got, want) + } + } + fns := mustEvaluate(t, withheld, modelHash, "F::fns") + for i, want := range []string{"F::Sq", "F::Cube"} { + if got := fns.GetSequence().GetElements()[i].GetNull(); got != "unsupported: function "+want { + t.Errorf("F::fns#%d without %s = %q", i+1, CapabilityFunctionValues, got) + } + } + + sq := functionValue("F::Sq", 0) + nested := &pb.Value{Kind: &pb.Value_Sequence{Sequence: &pb.ValueSequence{Elements: []*pb.Value{sq}}}} + for name, input := range map[string]*pb.Value{"function": sq, "nested": nested} { + _, err := withheld.ExecuteAction(ctx, &pb.ExecuteActionRequest{ + ModelHash: modelHash, + ActionSymbolId: "F::run", + Inputs: map[string]*pb.Value{"f": input, "a": realValue(2)}, + }) + if connect.CodeOf(err) != connect.CodeUnimplemented || !strings.Contains(err.Error(), CapabilityFunctionValues) { + t.Errorf("ExecuteAction with %s input without %s: err = %v, want UNIMPLEMENTED naming the capability", name, CapabilityFunctionValues, err) + } + _, err = withheld.EvaluateCalc(ctx, &pb.EvaluateCalcRequest{ModelHash: modelHash, SymbolId: "F::apply", Arguments: []*pb.Value{input, realValue(3)}}) + if connect.CodeOf(err) != connect.CodeUnimplemented || !strings.Contains(err.Error(), CapabilityFunctionValues) { + t.Errorf("EvaluateCalc with %s argument without %s: err = %v, want UNIMPLEMENTED naming the capability", name, CapabilityFunctionValues, err) + } + } +} + +func TestValueCarriesFunction(t *testing.T) { + one := intValue(1) + sq := functionValue("F::Sq", 0) + sequence := func(elements ...*pb.Value) *pb.Value { + return &pb.Value{Kind: &pb.Value_Sequence{Sequence: &pb.ValueSequence{Elements: elements}}} + } + for _, tc := range []struct { + name string + value *pb.Value + want bool + }{ + {"nil", nil, false}, + {"int", one, false}, + {"function", sq, true}, + {"sequence of ints", sequence(one, one), false}, + {"sequence with a function", sequence(one, sequence(sq)), true}, + {"array of functions", arrayValue([]int64{1}, sq), true}, + } { + if got := ValueCarriesFunction(tc.value); got != tc.want { + t.Errorf("ValueCarriesFunction(%s) = %v, want %v", tc.name, got, tc.want) + } + } + if ValueCarriesStructured(sq) { + t.Error("a bare function is not a structured value") + } +} diff --git a/internal/grpc/service.go b/internal/grpc/service.go index 1da05b7b5..dc8edd6b7 100644 --- a/internal/grpc/service.go +++ b/internal/grpc/service.go @@ -104,6 +104,11 @@ const CapabilityStructuredValues = "structured_values" // unsupported null. Distinct from structured_values, which predates the arm. const CapabilityMeasurementRefs = "measurement_refs" +// CapabilityFunctionValues names the capability of carrying a calc held as a +// value as Value.function, named by its declaration, rather than reporting it +// as an unsupported null. +const CapabilityFunctionValues = "function_values" + // capabilities is what this build supports, in report order. A capability is // only ever added: renaming or dropping one breaks clients that require it. var capabilities = []string{ @@ -113,7 +118,7 @@ var capabilities = []string{ CapabilityApplyEdits, CapabilityAuthoring, CapabilityInlineLanguage, CapabilityStrictConformance, CapabilityDocumentQuery, CapabilityRenderDocument, CapabilityParseSources, CapabilityComplexValues, CapabilityStructuredValues, - CapabilityMeasurementRefs, + CapabilityMeasurementRefs, CapabilityFunctionValues, } type capabilityAvailability struct { @@ -264,7 +269,12 @@ func (s *Service) requireValueCapabilities(pv *pb.Value) error { } } if ValueCarriesMeasurementRef(pv) { - return s.requireCapability(CapabilityMeasurementRefs) + if err := s.requireCapability(CapabilityMeasurementRefs); err != nil { + return err + } + } + if ValueCarriesFunction(pv) { + return s.requireCapability(CapabilityFunctionValues) } return nil } @@ -717,7 +727,7 @@ func (s *Service) ExecuteAction(ctx context.Context, req *pb.ExecuteActionReques if err := s.requireValueCapabilities(pv); err != nil { return nil, err } - val, cerr := ProtoToValueIn(pv, cached.Index, semModel) + val, cerr := ProtoToRuntimeValue(runtimeCtx, pv, cached.Index, semModel) if cerr != nil { return &pb.ExecuteActionResponse{ Error: fmt.Sprintf("input %q could not be read: %v", name, cerr), diff --git a/internal/grpc/verify.go b/internal/grpc/verify.go index 814264665..8c744229c 100644 --- a/internal/grpc/verify.go +++ b/internal/grpc/verify.go @@ -339,7 +339,7 @@ func (s *Service) EvaluateCalc(ctx context.Context, req *pb.EvaluateCalcRequest) if err := s.requireValueCapabilities(arg); err != nil { return nil, err } - val, cerr := ProtoToValueIn(arg, v.cached.Index, v.sem) + val, cerr := ProtoToRuntimeValue(v.runtime, arg, v.cached.Index, v.sem) if cerr != nil { return &pb.EvaluateCalcResponse{ Error: fmt.Sprintf("calc argument could not be read: %v", cerr), diff --git a/internal/repl/compile_test.go b/internal/repl/compile_test.go index b2ba4788c..ba1ad7273 100644 --- a/internal/repl/compile_test.go +++ b/internal/repl/compile_test.go @@ -424,6 +424,8 @@ func TestCompileRefusesWhatItCannotCompile(t *testing.T) { {"MixedEquality", "a Integer[0..*] at the left operand of '==', which holds Real[0..*]"}, {"MixedSame", "same over Integer and Real collections"}, {"MixedUnion", "union over Integer and Real collections"}, + {"CalcParam", "parameter f binds a function value"}, + {"FunctionArgument", "parameter f binds a function value"}, } { _, err := s.CompileCalc("Refused::" + tc.calc) if err == nil { diff --git a/internal/repl/evalin_test.go b/internal/repl/evalin_test.go index 481cc469b..f0cc4c2b0 100644 --- a/internal/repl/evalin_test.go +++ b/internal/repl/evalin_test.go @@ -238,9 +238,10 @@ func TestEvalInDeclarationScopeChainOverValuelessOperandStillResolvesItsMembers( wants(t, run(t, s, "%eval in car : wheels.radius"), "✓ wheels.radius (in car)", "= "+runtime.UnsetText) } -// A KerML type declaration — a class, struct, behavior, datatype or function — -// is a type, not a feature: reading one in declaration scope is the error a -// definition gets, never unset. +// A KerML type declaration — a class, struct, behavior or datatype — is a type, +// not a feature: reading one in declaration scope is the error a definition +// gets, never unset. A function is the one type that is a value: reading it +// denotes the function itself. func TestEvalInDeclarationScopeDoesNotReadTypeDeclarationsAsUnset(t *testing.T) { s := NewSession() if errs := errorDiagnostics(s.Submit(`private import ScalarValues::*; @@ -255,11 +256,14 @@ package K { }`).Diagnostics); len(errs) > 0 { t.Fatalf("model has errors: %v", errs) } - for _, name := range []string{"Vehicle", "Frame", "Drive", "Mass", "Twice", "Car"} { + for _, name := range []string{"Vehicle", "Frame", "Drive", "Mass", "Car"} { got := run(t, s, "%eval in K::car : "+name) wants(t, got, "error", "cannot evaluate definition "+name) rejects(t, got, "✓", "= "+runtime.UnsetText, "unresolved reference", "no value") } + got := run(t, s, "%eval in K::car : Twice") + wants(t, got, "✓ Twice (in K::car)", "= K::Twice") + rejects(t, got, "error", "= "+runtime.UnsetText) wants(t, run(t, s, "%eval in K::car : unsetMass"), "✓ unsetMass (in K::car)", "= "+runtime.UnsetText) } diff --git a/internal/repl/meta.go b/internal/repl/meta.go index e2e478541..c56456372 100644 --- a/internal/repl/meta.go +++ b/internal/repl/meta.go @@ -775,6 +775,16 @@ func (s *Session) evalExpr(expr string) ([]string, error) { fmt.Sprintf(" = %s", formatValue(ctx, val)), }, nil } + // A calc definition is the function it declares. + if val, isFunction, err := ctx.FunctionValue(sym); isFunction { + if err != nil { + return nil, fmt.Errorf("evaluation failed: %w", err) + } + return []string{ + fmt.Sprintf("✓ %s", expr), + fmt.Sprintf(" = %s", formatValue(ctx, val)), + }, nil + } // A valueless usage may still name a value its own features shape. if _, isUsage := sym.Decl.(*ast.Usage); !isUsage && !declaresValue(sym) { return nil, fmt.Errorf("%q has no value to evaluate", expr) diff --git a/internal/repl/testdata/compile_calcs.sysml b/internal/repl/testdata/compile_calcs.sysml index 4c3a3721c..05b5875b0 100644 --- a/internal/repl/testdata/compile_calcs.sysml +++ b/internal/repl/testdata/compile_calcs.sysml @@ -380,6 +380,9 @@ package Refused { calc def MixedEquality { in x : Integer[0..*]; in y : Real[0..*]; return : Boolean = x == y; } calc def MixedSame { in x : Integer[0..*]; in y : Real[0..*]; return : Boolean = same(x, y); } calc def MixedUnion { in x : Integer[0..*]; in y : Real[0..*]; return : Real[0..*] = union(x, y); } + calc def Sq { in v : Real; return : Real = v * v; } + calc def CalcParam { in calc f { in v : Real; return : Real; } in a : Real; return : Real = f(a); } + calc def FunctionArgument { in a : Real; return : Real = CalcParam(Sq, a); } calc def StringResult { in s : String; From 9a376740658aac3cc0655d4358ea588ff4cdb58b Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 8 Sep 2026 03:03:44 +0000 Subject: [PATCH 02/11] fix(runtime): close a nested calc over its enclosing body only where that body is written A calc usage nested in a behavior body keeps the enclosing frames only when the body it runs is declared inside that behavior; a body inherited from a calc declared elsewhere reads none of them. A case performing itself as a step no longer nests the caller's frames at every level of the recursion, which multiplied the frames cloned per step. Co-Authored-By: jason.han --- docs/project/spec-compliance.md | 2 +- internal/core/runtime/calc_usage.go | 26 +++++++++++++++++++++++- internal/core/runtime/invoke_calc.go | 2 +- internal/core/runtime/robustness_test.go | 19 +++++++++++++++++ 4 files changed, 46 insertions(+), 3 deletions(-) diff --git a/docs/project/spec-compliance.md b/docs/project/spec-compliance.md index 5b88f443f..71c41cc54 100644 --- a/docs/project/spec-compliance.md +++ b/docs/project/spec-compliance.md @@ -227,7 +227,7 @@ Each row documents one behavioral semantic feature: | A calc usage's outputs are evaluation results, not feature values of an object | `runtime/calc_usage.go` (no instance materialization) | `calc_usage_instance_slots.sysml` (the features fed by the outputs are feature values; the usage itself is not), pilot-exec-diff `w6d:calc-usage` | ⚠️ Approximate (unrefereeable: the pinned artifact answers a `CalculationUsage` node rather than an output value. `%instances` and export show the features valued from outputs, not the usage's outputs themselves) | | A calc definition, a calc usage with an unsupplied input, or an `in calc` parameter named where a value is expected is a **function value** (KerML 1.1 §7.4.4: a Function is a Behavior with a `result`, an Expression a Step typed by one, and a feature reference to either denotes it; §8.3.4.8 `Function::result`, `FeatureReferenceExpression`; SysML v2 §7.17: a calc def is a Function, a calc usage an Expression). The value is the calc's lowered invocation interface (`calcShape`) together with the environment it was read in — its declaring scope and the object it was read off — and nothing else: no statement closure is built, and the value is invoked through the same path a calc usage invocation takes (`invokeCalcShapeIn`). Reading a calc usage whose inputs are all bound evaluates it as before; a library function the runtime implements natively (`RealFunctions::sqrt`, `floor`) reads as a value carrying that implementation, while a library operation that binds its arguments unevaluated (`SequenceFunctions::size` and the other `->` operations) is refused as `ErrNotAFunction` | `runtime/value.go` `ValFunction`, `runtime/function_value.go` `functionValue`/`EvalContext.functionValueOf`/`Context.readsAsFunction`/`EvalContext.calcAsValue`, `invoke_calc.go` `calcShapeOf` (a natively implemented library function computes), `eval.go` `evalFeatureReference`, `describe.go` (`the function Sq`), `trace.go` `FormatTraceValue` (`calc(Sq)`), `repl/meta.go` | `function_value_read.sysml`, `function_value_probe.sysml` (`Fn(Sq, 3.0)` is `9.0`), `function_value_calc_usage.sysml`, `function_value_library.sysml`, `robustness_test.go:function_value_of_a_built_in`, `value_kinds_test.go:TestFunctionValueIdentity`, `:TestEveryValueKindIsDispatched`, `eval_no_value_test.go`, `repl/evalin_test.go` | ✅ Faithful | | An `in calc` parameter of a calc or an action (SysML v2 §7.17, §8.3.16 `CalculationUsage` as a parameter) accepts a function value or null and nothing else, positionally or by name; the body invokes it as `f(a)`, through a chain (`p.f(a)`), nested (`f(f(a))`) and as an argument to another calc-typed parameter, binding the callee's inputs positionally and by name as a direct invocation does. A calc usage bound as an action input (`in f = sq;`) is the function value it reads as | `runtime/invoke_calc.go` `calcParameter.checkFunction`, `function_value.go` `EvalContext.invokeFunction`, `eval.go` `evalInvocation`/`evalFeatureChain`, `parser/behavior.go` `parameterKindKeywords` (`calc`) | `function_value_probe.sysml` + `function_value_probe.trace.golden` (`TestExecutionTrace`), `function_value_named_args.sysml`, `function_value_chain_call.sysml`, `function_value_action_parameter.sysml`, parser golden `action_calc_parameter.sysml`, `robustness_test.go:function_value_call_of_a_non_function` (`ErrNotAFunction`), `:function_value_bound_to_a_non_function` (`ErrNotAFunction`), `:function_value_arity_mismatch` (`ErrCalcArity`), `testCalcUnboundParameter` (`ErrUnboundParameter`) | ✅ Faithful | -| A calc read off an object (`twice.scale`, a calc usage owned by a part) is a function value over that object: invoked later it reads that object's features, so the same calc read off two parts is two values. A calc declared inside a behavior body (a calc's or an action's) closes over the bindings of the run it was read in — its enclosing parameters and locals — and a function returned from a calc (`return : Unary = scale;`) keeps them after the run ends | `function_value.go` `functionValue.self`/`.enclosing`, `EvalContext.functionValueOf` (`enclosedByBehaviorBody`), `calc_usage.go` (a nested usage keeps the enclosing frames), `invoke_calc.go` `Context.invokeCalcShapeIn` | `function_value_feature_closure.sysml` (`Fn(twice.scale, 5.0) + Fn(thrice.scale, 5.0)` is `25.0`), `function_value_body_closure.sysml` (`Outer`, `Maker`), `function_value_sampled_closure.sysml` | ✅ Faithful | +| A calc read off an object (`twice.scale`, a calc usage owned by a part) is a function value over that object: invoked later it reads that object's features, so the same calc read off two parts is two values. A calc declared inside a behavior body (a calc's or an action's) closes over the bindings of the run it was read in — its enclosing parameters and locals — and a function returned from a calc (`return : Unary = scale;`) keeps them after the run ends. The closure reaches only the code written inside that body: a body the nested calc inherits from a calc declared elsewhere (`calc again : RecursiveStep;`) reads none of the enclosing run's bindings, so a case performing itself as a step nests its frames no deeper than its recursion | `function_value.go` `functionValue.self`/`.enclosing`, `EvalContext.functionValueOf` (`enclosedByBehaviorBody`), `calc_usage.go` (a nested usage keeps the enclosing frames; `calcShape.bodyEnclosing`/`declaredWithin`), `invoke_calc.go` `Context.invokeCalcShapeIn` | `function_value_feature_closure.sysml` (`Fn(twice.scale, 5.0) + Fn(thrice.scale, 5.0)` is `25.0`), `function_value_body_closure.sysml` (`Outer`, `Maker`), `function_value_sampled_closure.sysml`; `robustness_test.go:function_value_inherited_body_outside_the_closure`; `analysis_robustness_test.go:recursion_through_a_step` | ✅ Faithful | | Function values are equal, and key a set, by the calc together with the object it was read off (`Sq == Sq`, `twice.scale != thrice.scale`); a value closing over a behavior body's bindings is equal only to itself, since two reads of it in one run are one value and reads in two runs are not. Adoption into another runtime rebinds an ordinary function value to the same calc of the adopting model, the object it was read off adopted with it; one closing over a body's bindings is refused as a typed error (the run those bindings belonged to has ended and cannot be reconstructed) | `value_equality.go` `valueEqual`/`valueKeyFunc` (`ValFunction` arm), `adopt.go` | `value_kinds_test.go:TestFunctionValueIdentity`, `adopt_test.go:TestAdoptRebindsAFunctionValue` | ✅ Faithful | | `SampledFunctions::Sample(f, domain)` samples a user calc passed as its `in calc calculation` argument: the library's own body runs, `domainValues->collect { in x; new SamplePair(x, calculation(x)) }` invoking the function value inside the collection body, and `Range` of the result reads the samples back | the library body under `invoke_calc.go` `invokeCalcShapeIn`, `function_value.go` `EvalContext.invokeFunction` (from a collection body's frame), `collections.go` | `function_value_sampled.sysml` (`Range(Sample(Sq, (1.0, 2.0, 3.0)))` is `[1.0, 4.0, 9.0]`), `function_value_sampled_closure.sysml` (a calc read off a part, sampled) | ✅ Faithful | | `SampledFunctions::SamplePair` arithmetic and `SampledFunctions::interpolateLinear` on the library's own examples: reading a `SamplePair`'s `domainValue` or `rangeValue` yields the one-element sequence `[1.0]`, and `-`/`*` refuse a sequence operand (`type mismatch: operator '-' is not defined for a Real and a sequence`). The cause is the `[0..*]`-inherited member read not reducing a singleton sequence to the scalar it denotes, which is independent of function values (the same failure reproduces with no function value involved) and is not papered over here with a `SamplePair`-specific unwrap | `runtime/eval.go` `chainMemberValue`/`evalArithmetic`, `instance.go` (scalar-feature admission reduces a singleton only where the feature is declared scalar) | reproduced by `SampledFunctions::interpolateLinear` and by `s.samples#(1).domainValue - 1.0` | ❌ Not implemented (the singleton reduction of a `[0..*]`-inherited member read; the failure is a typed error, not a wrong answer) | diff --git a/internal/core/runtime/calc_usage.go b/internal/core/runtime/calc_usage.go index ddcf7fb9d..f6a66b85c 100644 --- a/internal/core/runtime/calc_usage.go +++ b/internal/core/runtime/calc_usage.go @@ -665,6 +665,30 @@ func enclosedByBehaviorBody(sym *symbols.Symbol) bool { return isCalcSymbol(owner) || isActionSymbol(owner) || isStateSymbol(owner) } +// bodyEnclosing is the part of enclosing, the bindings of the behavior body the +// calc is declared in, its body reads: all of it for a body written inside that +// behavior, none for a body inherited from a calc declared elsewhere. +func (shape *calcShape) bodyEnclosing(enclosing []frame) []frame { + if len(enclosing) == 0 || !declaredWithin(shape.BodyOwner, enclosingBehavior(shape.Sym)) { + return nil + } + return enclosing +} + +// declaredWithin reports sym declared in the body of behavior, directly or in a +// behavior nested in it. +func declaredWithin(sym, behavior *symbols.Symbol) bool { + if behavior == nil { + return false + } + for owner := enclosingBehavior(sym); owner != nil; owner = enclosingBehavior(owner) { + if owner == behavior { + return true + } + } + return false +} + // checkCalcTyping rejects a calc usage typed by something that is not a calc: it // inherits no parameters, no outputs and no body from it, so reading an output // of it would report a missing feature rather than the specialization error. @@ -692,7 +716,7 @@ func (ctx *Context) runCalcUsage( // an invocation of it does. var enclosing []frame if nested != nil { - enclosing = nested.frames + enclosing = shape.bodyEnclosing(nested.frames) } engine := newStmtEngineIn(ctx, host, env, enclosing) host.attachPerformances(engine) diff --git a/internal/core/runtime/invoke_calc.go b/internal/core/runtime/invoke_calc.go index 99c1ae55a..63b0ab5f1 100644 --- a/internal/core/runtime/invoke_calc.go +++ b/internal/core/runtime/invoke_calc.go @@ -756,7 +756,7 @@ func (ctx *Context) bindCalcParameters( // activation, which the caller ends after it. func (ctx *Context) runCalcBody(shape *calcShape, frame *invocationFrame, callerScope *symbols.Scope, self *Instance, activation int64, enclosing []frame) (Value, error) { frame.host = calcStmtHost{ctx: ctx, shape: shape, self: self} - frame.env = stmtEnv{data: frame.locals(), enclosing: enclosing} + frame.env = stmtEnv{data: frame.locals(), enclosing: shape.bodyEnclosing(enclosing)} frame.engine = stmtEngine{ctx: ctx, host: &frame.host, env: &frame.env, activation: activation, frameBuf: frame.engine.frameBuf} frame.host.attachPerformances(&frame.engine) result, returned, err := runCalcSteps(&frame.engine, &frame.host, shape) diff --git a/internal/core/runtime/robustness_test.go b/internal/core/runtime/robustness_test.go index 2bf9ea8c1..601029f0b 100644 --- a/internal/core/runtime/robustness_test.go +++ b/internal/core/runtime/robustness_test.go @@ -366,6 +366,7 @@ func TestRuntimeRobustness(t *testing.T) { t.Run("function_value_of_a_wrong_typed_calc", testFunctionValueOfAWrongTypedCalc) t.Run("function_value_of_a_built_in", testFunctionValueOfABuiltIn) t.Run("function_value_applied_to_itself_forever", testFunctionValueAppliedToItselfForever) + t.Run("function_value_inherited_body_outside_the_closure", testFunctionValueInheritedBodyOutsideTheClosure) } func testBindingConflict(t *testing.T) { @@ -11389,3 +11390,21 @@ func testFunctionValueAppliedToItselfForever(t *testing.T) { t.Fatalf("error = %v, want the recursion or step budget spent", err) } } + +// testFunctionValueInheritedBodyOutsideTheClosure: a usage nested in a calc body +// closes over that body only for the code written there; the body it inherits from +// a calc declared outside reads no binding of the enclosing run, however it is applied. +func testFunctionValueInheritedBodyOutsideTheClosure(t *testing.T) { + src := `package test {` + functionValueFixture + ` + calc def Leaky { in v : Real; return : Real = v * k; } + calc def Bare { in k : Real; calc inner : Leaky { in v = 2.0; } return : Real = inner; } + calc def Called { in k : Real; calc inner : Leaky; return : Real = inner(2.0); } + calc def Passed { in k : Real; calc inner : Leaky; return : Real = Fn(inner, 2.0); } + }` + for _, expr := range []string{"test::Bare(3.0)", "test::Called(3.0)", "test::Passed(3.0)"} { + err := invokeCalcExpecting(t, src, expr) + if !errors.Is(err, ErrNoValue) && !errors.Is(err, ErrUnresolvedReference) { + t.Fatalf("%s: error = %v, want k unresolved in Leaky's body", expr, err) + } + } +} From 718d1f42ff54f49a9431d5011563a00f621b13a4 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 8 Sep 2026 03:07:42 +0000 Subject: [PATCH 03/11] test(runtime): compare a function value's key across two computations Co-Authored-By: jason.han --- internal/core/runtime/value_kinds_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/core/runtime/value_kinds_test.go b/internal/core/runtime/value_kinds_test.go index c19444029..00dc7fd05 100644 --- a/internal/core/runtime/value_kinds_test.go +++ b/internal/core/runtime/value_kinds_test.go @@ -89,7 +89,7 @@ func TestFunctionValueIdentity(t *testing.T) { t.Errorf("two reads of %s against one object are not one value", FormatValue(a)) } c := closing() - if !valueEqual(c, c) || valueKeyFunc(c) != valueKeyFunc(c) { + if first, again := valueKeyFunc(c), valueKeyFunc(c); !valueEqual(c, c) || first != again { t.Errorf("a body-closing function is not equal to itself") } if d := closing(); valueEqual(c, d) || valueKeyFunc(c) == valueKeyFunc(d) { From 65250be4ae3ea522d04016e8e38e40191449e93a Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 8 Sep 2026 03:10:33 +0000 Subject: [PATCH 04/11] docs(wire-contract): point the function arm at the capabilities section Co-Authored-By: jason.han --- docs/reference/wire-contract.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/wire-contract.md b/docs/reference/wire-contract.md index 1aaf8690f..1777b3989 100644 --- a/docs/reference/wire-contract.md +++ b/docs/reference/wire-contract.md @@ -481,7 +481,7 @@ $ … /Evaluate -d '{"modelHash":"e587…f81e","expression":"F::scaler"}' has ended and cannot be reconstructed remotely. It is sent as the unsupported null `{"null":"unsupported: function closing over a body's bindings"}`, under the `null` arm's rule. -- The arm is gated by the `function_values` capability (see [`GetServerInfo`](#getserverinfo)). +- The arm is gated by the `function_values` capability (see [Capabilities, and what an absent one does](service-transports.md#capabilities-and-what-an-absent-one-does)). A service without it sends every function, at any depth, as `{"null":"unsupported: function "}` and refuses a request that carries one. From 58ba8ebfde85e6a1dcc7c77b58d691fe535f93f7 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 8 Sep 2026 03:15:25 +0000 Subject: [PATCH 05/11] test(node): label a function's self_id as the instance it names in conformance Co-Authored-By: jason.han --- clients/node/conformance/normalize.ts | 1 + clients/node/test/normalize.test.ts | 32 +++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/clients/node/conformance/normalize.ts b/clients/node/conformance/normalize.ts index a3767a0e8..4910da76f 100644 --- a/clients/node/conformance/normalize.ts +++ b/clients/node/conformance/normalize.ts @@ -16,6 +16,7 @@ const NORMALIZED_IDS = new Set([ "sysml.Instance.id", "sysml.Value.instance_id", "sysml.Verdict.instance_id", + "sysml.Function.self_id", ]); /** diff --git a/clients/node/test/normalize.test.ts b/clients/node/test/normalize.test.ts index 4423cf3f8..b3867dea1 100644 --- a/clients/node/test/normalize.test.ts +++ b/clients/node/test/normalize.test.ts @@ -14,6 +14,7 @@ import { SymbolInfoSchema, ValueSchema, FeatureValueSchema, + FunctionSchema, } from "../src/generated/sysml_pb.js"; import { Integer, Normalizer, MODEL_HASH_PLACEHOLDER, PATH_PLACEHOLDER, VERSION_PLACEHOLDER } from "../conformance/normalize.js"; @@ -93,6 +94,37 @@ test("instance ids are relabelled in the order they appear, consistently", () => assert.equal((tree["instances"] as Record[])[0]?.["id"], "@2"); }); +test("the object a function was read off is labelled with the instance it names", () => { + const response = create(InstantiateResponseSchema, { + instance: create(InstanceSchema, { + id: 41n, + typeSymbolId: "Sample::Scaler", + featureValues: { + scale: create(FeatureValueSchema, { + featureName: "scale", + values: [ + create(ValueSchema, { + kind: { case: "function", value: create(FunctionSchema, { calcId: "Sample::Scaler::scale", selfId: 41n }) }, + }), + create(ValueSchema, { + kind: { case: "function", value: create(FunctionSchema, { calcId: "Sample::Sq" }) }, + }), + ], + }), + }, + }), + }); + const tree = new Normalizer(HASH).normalize(InstantiateResponseSchema, response); + const instance = tree["instance"] as Record; + const values = (instance["feature_values"] as Record>)["scale"]["values"] as Record< + string, + Record + >[]; + assert.deepEqual(values[0]?.["function"], { calc_id: "Sample::Scaler::scale", self_id: "@1" }); + // A function bound to no object leaves self_id unset, so it does not appear. + assert.deepEqual(values[1]?.["function"], { calc_id: "Sample::Sq" }); +}); + test("an integral field is an Integer, so it is never compared as a float", () => { const tree = new Normalizer(HASH).normalize( ParseFileResponseSchema, From fcff0e4f4c2b9325b62c8834e0cbfef661b5f2c9 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 8 Sep 2026 04:03:36 +0000 Subject: [PATCH 06/11] fix(runtime): scope function-value capture, qualified calc calls and object-bound wire functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A function value read off a nested calc now captures the enclosing body's bindings only when the calc reads them — through a body written in that behavior or a default it declares there — so a usage inheriting its body from a calc declared elsewhere no longer carries frames it cannot read, and is no longer refused on the wire as closing over a body. A calc-typed parameter invoked by a qualified name (`Apply::f(a)`, or through the calc a run specializes and a redeclaration) applies what the run bound it to, found through the active frames' owner qualification, as the bare name does. A request function carrying a non-zero self_id is refused: every call instantiates the model afresh, so the object it names lived only within the response that sent it and another call's object may carry the same number. The wire contract, proto comment, clients and conformance scenario say so. Co-Authored-By: jason.han --- api/proto/sysml.pb.go | 5 +- api/proto/sysml.proto | 5 +- changes/unreleased/function-values.added.md | 2 +- client/opensysml/value.go | 3 +- .../openmbee/opensysml/proto/Function.java | 20 +-- .../opensysml/proto/FunctionOrBuilder.java | 5 +- clients/node/src/generated/sysml_pb.ts | 5 +- clients/python/opensysml/values.py | 5 +- .../rust/conformance/sysml.descriptor.binpb | Bin 67430 -> 67463 bytes .../rust/opensysml/src/proto/sysml/sysml.rs | 5 +- conformance/scenarios/10-evaluate-calc.json | 4 +- docs/project/spec-compliance.md | 2 +- docs/reference/wire-contract.md | 22 ++-- internal/core/runtime/calc_usage.go | 18 +++ internal/core/runtime/function_value.go | 33 +++-- .../function_value_body_closure.expected.json | 2 +- .../function_value_body_closure.sysml | 6 +- ...unction_value_qualified_call.expected.json | 6 + .../function_value_qualified_call.sysml | 17 +++ internal/grpc/convert.go | 16 ++- internal/grpc/convert_function_test.go | 119 +++++++++++++----- 21 files changed, 214 insertions(+), 86 deletions(-) create mode 100644 internal/core/runtime/testdata/conformance/function_value_qualified_call.expected.json create mode 100644 internal/core/runtime/testdata/conformance/function_value_qualified_call.sysml diff --git a/api/proto/sysml.pb.go b/api/proto/sysml.pb.go index 33aa3cf89..069c864eb 100644 --- a/api/proto/sysml.pb.go +++ b/api/proto/sysml.pb.go @@ -4058,8 +4058,9 @@ type Function struct { CalcId string `protobuf:"bytes,1,opt,name=calc_id,json=calcId,proto3" json:"calc_id,omitempty"` // ID of the object the calc's feature names resolve against, for a calc // usage read off a part (`holder.scale`); 0 for a function closing over no - // object. Sent by the service; a client sending one must name an object of - // the runtime the value is read in, or the value is rejected. + // object. An instance id, living only within the response that sent it: a + // request function with a non-zero self_id is rejected, since no later call + // holds that object. SelfId int64 `protobuf:"varint,2,opt,name=self_id,json=selfId,proto3" json:"self_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache diff --git a/api/proto/sysml.proto b/api/proto/sysml.proto index d16e49426..85d6cab8b 100644 --- a/api/proto/sysml.proto +++ b/api/proto/sysml.proto @@ -674,8 +674,9 @@ message Function { string calc_id = 1; // ID of the object the calc's feature names resolve against, for a calc // usage read off a part (`holder.scale`); 0 for a function closing over no - // object. Sent by the service; a client sending one must name an object of - // the runtime the value is read in, or the value is rejected. + // object. An instance id, living only within the response that sent it: a + // request function with a non-zero self_id is rejected, since no later call + // holds that object. int64 self_id = 2; } diff --git a/changes/unreleased/function-values.added.md b/changes/unreleased/function-values.added.md index 6f55ebe2b..aae537875 100644 --- a/changes/unreleased/function-values.added.md +++ b/changes/unreleased/function-values.added.md @@ -1,2 +1,2 @@ - **A calc is a value.** A calc definition, a calc usage awaiting an input, or an `in calc` parameter named where a value is expected is a function value: the calc together with the scope and object it was read in, invoked through a calc-typed parameter (`calc def Fn { in calc f { in v : Real; return : Real; } in a : Real; return : Real = f(a); }` makes `Fn(Sq, 3.0)` `9.0`), passed positionally or by name, read off a part, returned from a calc, compared and adopted. `SampledFunctions::Sample` now samples a user calc, and a library function the runtime implements (`RealFunctions::sqrt`) is a value too. Calling a non-function, an arity mismatch and an unbound calc parameter are typed errors; `in calc` parameters parse in action bodies as they do in calc bodies. -- **Function values cross the API.** `Value.function` carries the calc's qualified name and the id of the object it was read off, under the new `function_values` capability, which the Go, Python, Node, Rust and Java clients expose as a typed value and refuse to send to a service without the capability. A function closing over a behavior body's bindings crosses as an unsupported null, since no name reconstructs it. Native compilation refuses a calc that binds or applies a function value with a typed error. +- **Function values cross the API.** `Value.function` carries the calc's qualified name and the id of the object it was read off, under the new `function_values` capability, which the Go, Python, Node, Rust and Java clients expose as a typed value and refuse to send to a service without the capability. A function closing over a behavior body's bindings crosses as an unsupported null, since no name reconstructs it; one read off an object is refused as an argument to a later call, since that object lived only within the response that sent it. Native compilation refuses a calc that binds or applies a function value with a typed error. diff --git a/client/opensysml/value.go b/client/opensysml/value.go index e127a84fe..73d6a1493 100644 --- a/client/opensysml/value.go +++ b/client/opensysml/value.go @@ -105,7 +105,8 @@ type Function struct { // CalcID is the FQN of the calc declaration ("M::Sq"). CalcID string // Self is the object the calc computes over, an id of the answer that - // reported it; 0 for a calc bound to no object. + // reported it; 0 for a calc bound to no object. As any InstanceID it lives + // only within that answer: sent as an argument, a non-zero Self is refused. Self InstanceID } diff --git a/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/proto/Function.java b/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/proto/Function.java index 1e70e194c..b1cbc7c69 100644 --- a/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/proto/Function.java +++ b/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/proto/Function.java @@ -106,8 +106,9 @@ public java.lang.String getCalcId() { *
    * ID of the object the calc's feature names resolve against, for a calc
    * usage read off a part (`holder.scale`); 0 for a function closing over no
-   * object. Sent by the service; a client sending one must name an object of
-   * the runtime the value is read in, or the value is rejected.
+   * object. An instance id, living only within the response that sent it: a
+   * request function with a non-zero self_id is rejected, since no later call
+   * holds that object.
    * 
* * int64 self_id = 2 [json_name = "selfId"]; @@ -542,8 +543,9 @@ public Builder setCalcIdBytes( *
      * ID of the object the calc's feature names resolve against, for a calc
      * usage read off a part (`holder.scale`); 0 for a function closing over no
-     * object. Sent by the service; a client sending one must name an object of
-     * the runtime the value is read in, or the value is rejected.
+     * object. An instance id, living only within the response that sent it: a
+     * request function with a non-zero self_id is rejected, since no later call
+     * holds that object.
      * 
* * int64 self_id = 2 [json_name = "selfId"]; @@ -557,8 +559,9 @@ public long getSelfId() { *
      * ID of the object the calc's feature names resolve against, for a calc
      * usage read off a part (`holder.scale`); 0 for a function closing over no
-     * object. Sent by the service; a client sending one must name an object of
-     * the runtime the value is read in, or the value is rejected.
+     * object. An instance id, living only within the response that sent it: a
+     * request function with a non-zero self_id is rejected, since no later call
+     * holds that object.
      * 
* * int64 self_id = 2 [json_name = "selfId"]; @@ -576,8 +579,9 @@ public Builder setSelfId(long value) { *
      * ID of the object the calc's feature names resolve against, for a calc
      * usage read off a part (`holder.scale`); 0 for a function closing over no
-     * object. Sent by the service; a client sending one must name an object of
-     * the runtime the value is read in, or the value is rejected.
+     * object. An instance id, living only within the response that sent it: a
+     * request function with a non-zero self_id is rejected, since no later call
+     * holds that object.
      * 
* * int64 self_id = 2 [json_name = "selfId"]; diff --git a/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/proto/FunctionOrBuilder.java b/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/proto/FunctionOrBuilder.java index 026edcc62..d1f0bc5d1 100644 --- a/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/proto/FunctionOrBuilder.java +++ b/clients/java/opensysml-client/src/main/java/org/openmbee/opensysml/proto/FunctionOrBuilder.java @@ -34,8 +34,9 @@ public interface FunctionOrBuilder extends *
    * ID of the object the calc's feature names resolve against, for a calc
    * usage read off a part (`holder.scale`); 0 for a function closing over no
-   * object. Sent by the service; a client sending one must name an object of
-   * the runtime the value is read in, or the value is rejected.
+   * object. An instance id, living only within the response that sent it: a
+   * request function with a non-zero self_id is rejected, since no later call
+   * holds that object.
    * 
* * int64 self_id = 2 [json_name = "selfId"]; diff --git a/clients/node/src/generated/sysml_pb.ts b/clients/node/src/generated/sysml_pb.ts index aee587205..8a41f92ad 100644 --- a/clients/node/src/generated/sysml_pb.ts +++ b/clients/node/src/generated/sysml_pb.ts @@ -2068,8 +2068,9 @@ export type Function = Message<"sysml.Function"> & { /** * ID of the object the calc's feature names resolve against, for a calc * usage read off a part (`holder.scale`); 0 for a function closing over no - * object. Sent by the service; a client sending one must name an object of - * the runtime the value is read in, or the value is rejected. + * object. An instance id, living only within the response that sent it: a + * request function with a non-zero self_id is rejected, since no later call + * holds that object. * * @generated from field: int64 self_id = 2; */ diff --git a/clients/python/opensysml/values.py b/clients/python/opensysml/values.py index 211c9f16c..a1cbccad8 100644 --- a/clients/python/opensysml/values.py +++ b/clients/python/opensysml/values.py @@ -415,8 +415,9 @@ class Function: calc_id (str): FQN of the calc declaration (``Analysis::Sq``) self_id (int): ID of the object the calc's feature names resolve against, for a calc usage read off a part (``holder.scale``); 0 for - a function closing over no object. One sent to the service must name - an object of the runtime the value is read in. + a function closing over no object. An instance ID, valid only within + the response that sent it: the service rejects a function argument + whose ``self_id`` is not 0, since no later call holds that object. """ calc_id: str diff --git a/clients/rust/conformance/sysml.descriptor.binpb b/clients/rust/conformance/sysml.descriptor.binpb index 4bec97d7cdc8d9fb8e05091a15906a10a56399d9..7b3e4353499fbdb28f85009c5b75b6de592c421b 100644 GIT binary patch delta 5718 zcmYM2d2|)k9mi)TGxtvB0SQAALfD2NSVe*w)Q+3T}=ISL-H2KZSSozk@&rq;oI9p;+{1lwmFv4#TazAIgw;3 z1cdH3r(;)*(B0pqIBTHM&XA+K7?Vhpm?S{4Y|Y8@94vegtymamZkqQBWbY^08u7;Gf61%SvKWJGjT7l4!vjLL#!2a+5wbN-uS{<>$!7cP zbH_L!^7pNZeeR&v?I4>?vils%mnI<859n%y;65j@y+#P`bCNrTrkLFCSe?U3T2i1k zrONGh616Z@ZoiY7=CvXg*B>_I-OcZ`#c=|#K*dl0^?cidE2fCTvyW$vgt11tdn#osa11uq>A(V8B=zU~ac8hk7F(|cY=Rhd6kn?{k6U<(7 ztS%W_#M&=2gmrZ70H8{Ch)njHjsghVYZS%B1nCaB&S6#mI(o?*Fo%VGJw#?Zhb5{L z1_+}$tW%0tPK!9yXY-ixhPZHSe$qUKMSK)WLn-ib@oL@UGt0FO#t>RL3-<>QI^_)e zgBEEhm2?3Mw-yMC1XNoKC}(e*Qrrv3^Z{v#uosHF;}dfivM|%3mW57S$Req--arT} zWNFWOh{Tz5!?;E9ExCt z2NtFNz9s?^m3!4#-XX#-bMgDX?oz?Hgs!z~zS1@B%HNj3)Z$aY}d2!vd71; zUC(gY&!ZhQ!nfZSPn;}IexJEraAI3_uFp0xX=~?FMYoC(U)5!8 z)rG_uq_^sh1VVZ%g>|rtG{J0RRo>TMw99SOdm~pCw2?(r6@c^+^VRtXghV659r^@9 z$@AHF%s40V&pw{~9dlE>J0Y7#ku))}gV|Jv^E-~XuRrR?P^H-Nxc%_Y`Y%VjapL7etS_0LP2}F#SSwx*2 zK*V^N_2{cVhJ(xOuDiXD$iXO|wK8K4ue$5}ATzm@A(gYVbfd_{brJpH`w`c*GR9E2 z>v|c0P`=K5b%z0=e4XJAds+%$vQ4~o;h7O_aRJ&GZE*oAUmNq&WoV;h`Px|5Zr)Q; z0Oh+S`dyqh;1;vmg`d!J++yw3RvIOD^)1>;@)0`PXRmSNb+O~(hS+Ny`>g|XM$`2? zk6R0Pg46&*V;)V@0*|Id=W|J+`TUMPp(!TIR_(8<(C;dXu|daV**P7>Z?)c=mF3)*>FBWEg|YBF;O= z5P*7sX zUB*+?s|5(tWxQwLeWAPoS8%Ia#wTVlwV)V3LV~iAV0y{(FBFLLS3b_goa(|c@-SM# z!z~Mh?g}3E3Q!86S03>!ylmM!+*l_X|NC&#JKRb0`gpcuQ88ADkw4xUTg9z_vZpZw zzl!(1!|NnJf#_;(^~`jF%-l@r8Q)O$7|jSxOqi_Z-Ft*4pf$*zO!97lUUH}iYq`~fY)0I+p^PvppJA{`fX!NtXV_diBFFk{12^6mJzM6)HqiP~uZ^XY zHtN!YPTYY2~jI~T`d7vsb5K8ay@a+JUAE(Kp*=b-% zy+>7}YA4{V=Ehbr@TZ59HtA|7P-vS`?Orkb3G>am=0Ie&nXFS$^8_l@hurF($%D*- z=!~!&7*^wQFVj~r`jBHUr${5v7SuPB1(_wYZrhL+hUJcd4V8UMGDlamPz}fVtcn{S ziLqCIOse9zR7RtQ<0#M_Vtnh^{yTKTV+=w&c%r)Ofyj3U$7N5it#RaKr`Xv#F}5>q zLE-M?;dPErV6l^j*ZDXKWS1^B2-O9uM+y+8yLeb#AWU~rc~;VaI^JiWaN{#>X1s0% zkIs&cE15p~dHi&z!@ue;*DN#X6Ylnum(qAjWw%)T^IciHbzm5S`feWHMnI_V=C}{u zr42ZqeAkHPpEqXJ#7!ud8ok}|2~28eyDwHINM^72_f zKET6gFc88Ac=&>zKwb}W>kjD^gq{Yfo(7`o2YEzY4nX;F7Zq`5Fr*Ih%--HxRQ7zI z9p%R9s_2bQX4X-Ti;~X%d^-DUx%E|e_Jg5P%kko)^ERJiKPI-dWyg;3@aGlA@X0Z~ zmw@ndjHh>z+2&Kobv*o02ST<$6*5rHM%3j2glQeMGwtAfYO3ShR{4Nn3RJ%bV_?`G z=QwfbDFKA-aawm}3p&AVd8A>I9(q9SmQJD*@rdbcJrR!>RjrS^rFR}stxvv1@4R}R zI90o)hhBZ$Ek17Gw)%aLZ#fFJtpnQz-FBFTX#=%gw#*i*cTx<#HK_kdZmZin-(s*h z$=j=q1cb#&3iQ{~g8TLLfL?=h!MYvOp>Qt2Ngy=L8{ zAe0lRN)JSu&ALf}kZ&b= z>FZU}wwdI!pSW=idwZ!Be010{yO^<`xU24*Nfg>u9zHWc@B>tSfbesbN7N%62tQYO zPxX!h!p~KXXC@VK5=Gpq4^$A8fLe(nZsig6AHcqtmS1;M*B1~{t#rViqFZ6I&)T^0 zYQ>wj`E^noce~58bTY+y6K84ff%p?^ep9bG5OO#5i3)_=O=|IVbUaR`(B@Wr>6q)Y z=DJ~@V30=LMD@=PAZ+Hk__xC=h+vA(=DWs%iq*{g^F#Apw};Ge3Pn)vhCil32m+`Y h4+z0>H++%-Az1FFq&~}Dm{n3%l3jZ>!FG*wguIS zNQy2DIK-g51*-^Up@AA()5aptLLm?m6qdmn1T6$sfuL!C?r-Ld_h0tR+56kyH{U#N zDk`0CUUS0UzhCd{-7 z?I|Y4pu3etk`09JR@S|@fzaK``u6i4Qv#UWCc~dSk-se=5aJk22#~RDtb4COAOwMJ ztWTylAHMGN`3`1%EZ_XBEO`fWyLs!$*PZ06R%Y(nFs9bXVhpmiMivmVwZ<19WNXRS zI?DGhpVu+#Q+a*Y+Grhf)4Z1;dl$*p%PqTqH>TdmVhpnNEYd^S1B7fnOX+JMWb0Y~ zEN>Ob=J~MwnO z8BEfW0vak+ZVyW`!c@6EEH%x$3NNlR)aM78)hJ)t+a5j0+#cQoFf)|Q9Fmp$9vXbe z2w@CDhgg!%5fDO$SXxhSnqpwGK}PrgbZ|q0L7^L1k~TKfrqB&6qqjFsF>K#CDqq<@ zA$~N$plxu}}eyJQDws zxw`bjC~gaL`l!+aq4a=B1|m=k^K|~0MVuDOpB8)=rFx1v+AEkH)`TIKf#mS?w`;$aM&R#QAca9XK&O|%_`5j-pZa`5<&v!-t`2Fh6$NeK*x z=^NHF-Meg~62pCdky#6BKK<9cQ1V6E082?}I3;~aPCWd2&LtBZV^F$ef&)V75(WRO zreO9evwLN1k$*p!AqO<983L%29j=nSYFq$<>T$ zFXz}Fv`8Z;NCgkJ77!K*P;V_jHTxr#d{+?l9z})i#qybBQ}P${Akz`HjZR$5BdMz1 zfDl;B(=rW&z+xWj?~O+aBYnPvTg&7($HqmMaM$;yLUkmiP$`EsO)suAJ}?HEN*QjDeCPE#>}kOH7i0Z`2*bvLu1F6AkyUI848^7(RZt&r!MCP$Z(gIl3D ziX2qQam}~KtBefBAXCKy89>NXkqkK)MGoHJwoV!d4gj?SKs6hf2L$yEV}2C%c{R87 zA_Jj(fLys92n=pD7ZHU4!L8=qqTZcwpXc+H+*&WYAHO5MlDl1$mppR6iU-Fm5WE0t zFM!Zn#e-uO5PGY4aLndWu+^p)fItP*Hz%N)MUm(t1q5}qX`^@^<*~*r0uZ=>hD*w8 zIL;K!r8L%<>g5qyYpMqXDxjg#23|`IuBo(v*P80(**FH*aXV#rFzbK|Y`6}%>v(Y4 z<705waa{I`Xa|kc=kIarlaF+TsATW96$vkxbK&D|95X2^~qC#_t5@I#htJIiOc8atSPRx1y#EBX#LJmuqrxJ z@Ai2cw=ULv@oh@z={6qRUw4!B3*5dL3VHcI62!^+GvuA4lEC z`}|d5y&*F%J{EmdxV>=(InH?UvP8aoF(-S82#zg`L1u{%-IN7D$Se`veeYdbr}0#f zrLy(nrub43984IaK@psA{e2&3aDyp5 zAfj#{6Pk+2jlv$Nx?qAG!j=cr#Ri1ZMiD#|0M*B7s%TCcFr+q8)u`HG9L&PnA|JVO zfAl?5?J!x}glhMH0)K+O$*O4>oXC`SZv20oL@4rbmsgy_|5&m{Gz!vYrnTc z(NBciPu(|@D3v<7?T1^k>x?msLA_1{R}CQ4>jX~!w`sdgqS*CPbZpG7Pf#eAdb7Fl z2`KfnxhpjV$?TM$cPtyf)3nDVmC{ZT>8F|p5DGg*>?a07VW+t5cJCo%G}-5SgtZ?T zO^V6&x24GX|Ba0A5pEyVhLb6Yy}=SXKse6<^@9iy!h1#V!UcrzUJ*QwCsWjY!oEpG z1wsb{>JA1()%S^rz6t=<$Gx=Up1_dWC$a{3%c<-IK0hq1lQor{Jwn-s1+GOp=?mzj zZxHt9!ATDcl?H*w9G$KO|d@Uz_LJ&8F=z3u-g9U5?5Yt9M*Jb|ZK2apCB@y1?dO92Z^nMgj!mI2rx9VhG(YAjIdiVOgRaoCtwT7O2vQ^;GN~OP-^iG>51wuIib?E_-=4sQUfRH~ef+huo z{AudZxsadY^Y4V!R=pw=`c?Eh;ijv93``;YbJgi~=<)bDVe5aKVGu&+M6&KAKnR^P zoiv47<-D+OR;>aAOMu!EAS|615q)0+s*f|X%*RkVF9r_s*3q_^>hsIOx{AHM%#M9@ z!4CBb#V-q2-!N0j+7%HzDuECOP{#p8oGT)t-`9YMb4B#iuO>jmxgzkWq#{lw$L;1o z1%eWwQ6k6fBBKAfHwe?}>puFj0)$jM9k3_qR+#4VPGSAN`cF>iM6^@5nd&T^MqaPs zEFCZue`3wAnKcK5+%9|JMKnXMv0V zD7*v*(|x|kwJNIXc<8tHEppwyD#z*Mpxh09Cj-F*pl&=s2$s9S0}K#?` (`ErrFunctionNeedsRuntime` on the way in). Every client this repository ships maps the arm to a typed value — Go `opensysml.Function`, Python `opensysml.Function`, Node `{ kind: "function" }`, Java `Value.FunctionValue`, Rust `Value::Function` — and refuses to send one to a service lacking the capability | `grpc/convert.go` `functionToProto`/`functionFromProto`/`ProtoToRuntimeValue`, `ValueCarriesFunction`, `ErrFunctionUnbound`, `ErrFunctionNeedsRuntime`; `capability_response.go` (`function_values` arm); `service.go` `CapabilityFunctionValues`; `api/proto/sysml.proto` `Function`; `client/opensysml/value.go`, `convert.go`, `client.go` (`function_values` preflight); `clients/python/opensysml/values.py`, `connection.py`; `clients/node/src/core/values.ts`; `clients/java/.../Value.java`, `internal/Protos.java`; `clients/rust/opensysml/src/domain.rs` | `grpc/convert_function_test.go:TestFunctionRoundTrip`, `:TestMalformedFunctionsAreRejected`, `:TestFunctionCapability`, `:TestValueCarriesFunction`; `client/opensysml/function_test.go`; `clients/python/tests/test_function.py`; `clients/node/test/values.test.ts`, `client.test.ts`; `clients/java/.../ProtosTest.java`, `ApiIntegrationTest.java`; `clients/rust/opensysml/tests/client.rs`; `conformance/scenarios/01-server-info.json`, `04-evaluate.json`, `10-evaluate-calc.json` (`function.sysml`) over gRPC, Connect protobuf and Connect JSON | ✅ Faithful (advertised as the `function_values` capability; a service withholding it reports the unsupported null and refuses a function argument with `UNIMPLEMENTED`, the clients refusing before the round trip) | +| A function value crosses the API boundary as `Value.function`: `calc_id`, the qualified name of the calc it is a value of, and `self_id`, the id (within the answering response) of the object it was read off, 0 for none — identity being the pair. One sent as an argument (`EvaluateCalc`, `ExecuteAction`, `RunAnalysis`) is rebound to that calc of the named model and invoked through the calc-typed parameter it binds; an empty `calc_id`, one naming no calc, or any non-zero `self_id` is refused in band (`ErrFunctionUnbound`), never a null — objects live only within the call that created them, so a `self_id` is never matched to whichever object a later call numbered the same — and a function value nested in a sequence or array is found wherever it sits. A value closing over a behavior body's bindings cannot be named by calc and object, so it crosses as `unsupported: function ` (`ErrFunctionNeedsRuntime` on the way in). Every client this repository ships maps the arm to a typed value — Go `opensysml.Function`, Python `opensysml.Function`, Node `{ kind: "function" }`, Java `Value.FunctionValue`, Rust `Value::Function` — and refuses to send one to a service lacking the capability | `grpc/convert.go` `functionToProto`/`functionFromProto`/`ProtoToRuntimeValue`, `ValueCarriesFunction`, `ErrFunctionUnbound`, `ErrFunctionNeedsRuntime`; `capability_response.go` (`function_values` arm); `service.go` `CapabilityFunctionValues`; `api/proto/sysml.proto` `Function`; `client/opensysml/value.go`, `convert.go`, `client.go` (`function_values` preflight); `clients/python/opensysml/values.py`, `connection.py`; `clients/node/src/core/values.ts`; `clients/java/.../Value.java`, `internal/Protos.java`; `clients/rust/opensysml/src/domain.rs` | `grpc/convert_function_test.go:TestFunctionRoundTrip`, `:TestObjectBoundFunctionsDoNotCrossCalls`, `:TestMalformedFunctionsAreRejected`, `:TestFunctionCapability`, `:TestValueCarriesFunction`; `client/opensysml/function_test.go`; `clients/python/tests/test_function.py`; `clients/node/test/values.test.ts`, `client.test.ts`; `clients/java/.../ProtosTest.java`, `ApiIntegrationTest.java`; `clients/rust/opensysml/tests/client.rs`; `conformance/scenarios/01-server-info.json`, `04-evaluate.json`, `10-evaluate-calc.json` (`function.sysml`) over gRPC, Connect protobuf and Connect JSON | ✅ Faithful (advertised as the `function_values` capability; a service withholding it reports the unsupported null and refuses a function argument with `UNIMPLEMENTED`, the clients refusing before the round trip) | | An unqualified name resolves as a written reference does — the enclosing scope chain, inherited members, imports, then the global index — and the declaration it finds is evaluated in *its own* declaring scope, so the imports in force where a value was written answer the names that value uses | `runtime/eval.go` `evalFeatureReference` (scope arm) via `resolve/unqualified.go` `Resolver.LookupName`, `EvalContext.evalIn` | `action_body_package_member.sysml`, `action_body_declarer_scope.sysml`, `body_scope_test.go:TestBodyScopeImportSpellings`, `robustness_test.go:action_body_unresolved_feature` | ✅ Faithful | #### Scope of an expression in a behavior body diff --git a/docs/reference/wire-contract.md b/docs/reference/wire-contract.md index 1777b3989..3497311b6 100644 --- a/docs/reference/wire-contract.md +++ b/docs/reference/wire-contract.md @@ -285,7 +285,7 @@ decode(v): require unit or id, else an error; id absent means a composed unit function → calc := v.function.calcId, require it non-empty, else an error; self := v.function.selfId when present and not "0", an opaque reference - under the instanceId rule, else no object + under the instanceId rule (this response only), else no object anything else → an error: a newer service than this decoder ``` @@ -474,6 +474,13 @@ $ … /Evaluate -d '{"modelHash":"e587…f81e","expression":"F::scaler"}' rules: a 64-bit integer sent as a string, valid within the response it arrived in, and indexing that response's `instances` where the method returns them. Absent (or `"0"`, the proto default) means the calc computes over no object. +- A function carrying a `selfId` cannot be sent back. Every call instantiates the model afresh + and numbers its objects from 1, so the object the function was read off does not exist in any + later call — and another call's object may well carry the same number. A request `function` + with a non-zero `selfId` is therefore refused in band, whatever the number, rather than bound + to whichever object that call numbered the same. To apply a calc over an object, name both + in one expression — `Evaluate` of `F::apply(F::holder.scale, 3.0)` reads the object and + applies the calc within the call that holds it. - Two functions are the same function when their `calcId`s are equal and both name the same object or neither names one; the engine's `==` says the same. - A calc that closes over the bindings of a behavior body — one returned by another calc, or @@ -505,8 +512,9 @@ $ … /Evaluate -d '{"modelHash":"e587…f81e","expression":"F::scaler"}' declaration; send it back as it came, with its `unit` and `unitTerm` only. - **Do not read a `function` as the value the calc computes, or invoke it locally.** It is a reference: hand it back as an argument (`EvaluateCalc`) and let the service invoke it. -- **Do not keep a `function`'s `selfId` past the response it arrived in.** It is an - `instanceId`, with that arm's lifetime. +- **Do not send back a `function` that carries a `selfId`.** It is an `instanceId`, with that + arm's lifetime: no later call holds the object, and the service refuses the function rather + than guess. Only a function over no object (`selfId` absent or `"0"`) is an argument. ## Three places a failure can be @@ -818,9 +826,9 @@ $ … /EvaluateCalc -d '{"modelHash":"5b0f…40d5","symbolId":"M::toUnit","argum ``` A `function` argument binds an `in calc` parameter to the calc it names, resolved against the -model and, when it carries a `selfId`, against the objects of the runtime the model was -instantiated into. A name that is empty, names nothing, names something that is not a calc, -or a `selfId` that names no object, is an in-body failure, at any depth: +model, over no object. A name that is empty, names nothing, or names something that is not a +calc is an in-body failure, at any depth; so is any non-zero `selfId`, since the object it +named lived only in the response that sent it and no call can hold it again: ```console $ … /EvaluateCalc -d '{"modelHash":"e587…f81e","symbolId":"F::apply","arguments":[{"function":{"calcId":"F::Sq"}},{"realValue":3.0}]}' @@ -830,7 +838,7 @@ $ … /EvaluateCalc -d '{"modelHash":"e587…f81e","symbolId":"F::apply","argume {"error":"calc argument could not be read: function names no calc of this model: F::holder is not a calc", "failureReason":"FAILURE_REASON_EVALUATION"} $ … /EvaluateCalc -d '{"modelHash":"e587…f81e","symbolId":"F::apply","arguments":[{"function":{"calcId":"F::Scaler::scale","selfId":"3"}},{"realValue":2.0}]}' -{"error":"calc argument could not be read: function names no calc of this model: F::Scaler::scale: self_id 3 names no object of this runtime", "failureReason":"FAILURE_REASON_EVALUATION"} +{"error":"calc argument could not be read: function names no calc of this model: F::Scaler::scale: self_id 3 names no object of this call: an object lives only within the response that created it", "failureReason":"FAILURE_REASON_EVALUATION"} ``` A service without the `structured_values` capability refuses a structured argument, one diff --git a/internal/core/runtime/calc_usage.go b/internal/core/runtime/calc_usage.go index f6a66b85c..8ec9c97c4 100644 --- a/internal/core/runtime/calc_usage.go +++ b/internal/core/runtime/calc_usage.go @@ -675,6 +675,24 @@ func (shape *calcShape) bodyEnclosing(enclosing []frame) []frame { return enclosing } +// closesOverBody reports the calc reading the bindings of the behavior body it is +// declared in: through a body written there, or a default it declares there. +func (shape *calcShape) closesOverBody() bool { + if !enclosedByBehaviorBody(shape.Sym) { + return false + } + behavior := enclosingBehavior(shape.Sym) + if declaredWithin(shape.BodyOwner, behavior) { + return true + } + for i := range shape.Params { + if param := &shape.Params[i]; param.Default != nil && declaredWithin(param.Owner, behavior) { + return true + } + } + return false +} + // declaredWithin reports sym declared in the body of behavior, directly or in a // behavior nested in it. func declaredWithin(sym, behavior *symbols.Symbol) bool { diff --git a/internal/core/runtime/function_value.go b/internal/core/runtime/function_value.go index a7e0ebef0..efcca9413 100644 --- a/internal/core/runtime/function_value.go +++ b/internal/core/runtime/function_value.go @@ -16,7 +16,7 @@ type functionValue struct { scope *symbols.Scope // names the calc's defaults and body resolve against self *Instance // object the calc's feature names resolve against, nil for none // enclosing are the bindings of the behavior body the calc is declared in, as - // they stood when it was read; nil for a calc declared outside any body. + // they stood when it was read; nil for a calc reading none of them. enclosing []frame } @@ -78,7 +78,7 @@ func (ec *EvalContext) functionValueOf(sym *symbols.Symbol) (Value, error) { } fn := &functionValue{shape: shape, scope: ec.scope, self: ec.self} fn.library, _ = ec.ctx.libraryFunctionFor(sym) - if enclosedByBehaviorBody(sym) && len(ec.frames) > 0 { + if len(ec.frames) > 0 && shape.closesOverBody() { fn.enclosing = ec.closure().frames } return Value{Kind: ValFunction, ref: fn}, nil @@ -117,18 +117,12 @@ func (shape *calcShape) hasUnsuppliedInput() bool { // FunctionValue is the function a read of the declaration sym denotes — a calc // definition, or a calc usage with an input no read could supply — closed over -// its own scope; false when sym is no calc read as one. +// its own scope and no object; false when sym is no calc read as one. func (ctx *Context) FunctionValue(sym *symbols.Symbol) (Value, bool, error) { - return ctx.FunctionValueOn(sym, nil) -} - -// FunctionValueOn is FunctionValue with the calc's feature names resolving -// against the object self, as a calc usage read off a part does; nil for none. -func (ctx *Context) FunctionValueOn(sym *symbols.Symbol, self *Instance) (Value, bool, error) { if !ctx.readsAsFunction(sym) { return Value{}, false, nil } - val, err := NewEvalContextIn(ctx, sym.OwnerScope, self).functionValueOf(sym) + val, err := NewEvalContextIn(ctx, sym.OwnerScope, nil).functionValueOf(sym) return val, true, err } @@ -146,7 +140,13 @@ func (ec *EvalContext) calcAsValue(sym *symbols.Symbol) (Value, bool, error) { // environment — a parameter bound by argument, or a feature of the bound object — // which an invocation of callee applies; false when nothing here binds it. func (ec *EvalContext) boundFunction(callee *symbols.Symbol, qn *ast.QualifiedName) (Value, bool, error) { - if qn == nil || len(qn.Parts) != 1 || qn.Global || !isCalcUsageSymbol(callee) { + if qn == nil || len(qn.Parts) == 0 || !isCalcUsageSymbol(callee) { + return Value{}, false, nil + } + if len(qn.Parts) > 1 { + return ec.qualifiedBoundFunction(callee, qn) + } + if qn.Global { return Value{}, false, nil } name := qn.Parts[0].Text @@ -165,6 +165,17 @@ func (ec *EvalContext) boundFunction(callee *symbols.Symbol, qn *ast.QualifiedNa return Value{}, false, nil } +// qualifiedBoundFunction reads what the innermost run of the qualifying calc +// (`Apply::f(2.0)`), or of one specializing it, bound the calc-typed callee to. +func (ec *EvalContext) qualifiedBoundFunction(callee *symbols.Symbol, qn *ast.QualifiedName) (Value, bool, error) { + qualifier, ok := ec.ctx.resolver.ReadQualified(ec.scope, qn).Part(len(qn.Parts) - 2) + if !ok { + return Value{}, false, nil + } + val, ok := ec.frameFeatureValue(qualifier, callee) + return val, ok, nil +} + // checkFunction refuses a value bound to a calc usage parameter that is no // function; an omitted optional parameter holds null. func (param *calcParameter) checkFunction(value *Value, what func() string) error { diff --git a/internal/core/runtime/testdata/conformance/function_value_body_closure.expected.json b/internal/core/runtime/testdata/conformance/function_value_body_closure.expected.json index 2cb489289..02f149502 100644 --- a/internal/core/runtime/testdata/conformance/function_value_body_closure.expected.json +++ b/internal/core/runtime/testdata/conformance/function_value_body_closure.expected.json @@ -2,5 +2,5 @@ "type": "calc", "evaluate": "test::UseOuter", "libraries": true, - "result": {"type": "Real", "value": 65.0} + "result": {"type": "Real", "value": 105.0} } diff --git a/internal/core/runtime/testdata/conformance/function_value_body_closure.sysml b/internal/core/runtime/testdata/conformance/function_value_body_closure.sysml index 3e0ca1077..91c7e692f 100644 --- a/internal/core/runtime/testdata/conformance/function_value_body_closure.sysml +++ b/internal/core/runtime/testdata/conformance/function_value_body_closure.sysml @@ -19,5 +19,9 @@ package test { calc scale :> Unary { in :>> x; return : Real = x * k; } return : Unary = scale; } - calc def UseOuter { return : Real = Outer(3.0) + Outer(4.0) + Bare(5.0) + Fn(Maker(10.0), 2.0); } + // A nested usage inheriting its body still closes over a default it declares in the run. + calc def Mul { in a : Real; in b : Real; return : Real = a * b; } + calc def Fixer { in k : Real; calc byK : Mul { in :>> b = k; } return : Mul = byK; } + calc def ApplyOne { in calc f { in a : Real; return : Real; } in a : Real; return : Real = f(a); } + calc def UseOuter { return : Real = Outer(3.0) + Outer(4.0) + Bare(5.0) + Fn(Maker(10.0), 2.0) + ApplyOne(Fixer(10.0), 4.0); } } diff --git a/internal/core/runtime/testdata/conformance/function_value_qualified_call.expected.json b/internal/core/runtime/testdata/conformance/function_value_qualified_call.expected.json new file mode 100644 index 000000000..de9618d27 --- /dev/null +++ b/internal/core/runtime/testdata/conformance/function_value_qualified_call.expected.json @@ -0,0 +1,6 @@ +{ + "type": "calc", + "evaluate": "test::UseQualified", + "libraries": true, + "result": {"type": "Real", "value": 26.0} +} diff --git a/internal/core/runtime/testdata/conformance/function_value_qualified_call.sysml b/internal/core/runtime/testdata/conformance/function_value_qualified_call.sysml new file mode 100644 index 000000000..0ad6e2b91 --- /dev/null +++ b/internal/core/runtime/testdata/conformance/function_value_qualified_call.sysml @@ -0,0 +1,17 @@ +package test { + private import ScalarValues::*; + calc def Sq { in v : Real; return : Real = v * v; } + calc def Cube { in v : Real; return : Real = v * v * v; } + // A calc-typed parameter called by its qualified name applies what the run bound it to. + calc def Apply { + in calc f { in v : Real; return : Real; } + in a : Real; + return : Real = Apply::f(a) + test::Apply::f(1.0); + } + // So does a name qualified by the calc the run specializes, and by the redeclaration. + calc def Twice :> Apply { + in calc g :>> f; + return : Real = Apply::f(a) + Twice::g(a); + } + calc def UseQualified { return : Real = Apply(Sq, 3.0) + Twice(Cube, 2.0); } +} diff --git a/internal/grpc/convert.go b/internal/grpc/convert.go index 41734bee1..041ea1fba 100644 --- a/internal/grpc/convert.go +++ b/internal/grpc/convert.go @@ -621,7 +621,9 @@ func ProtoToRuntimeValue(rt *runtime.Context, pv *pb.Value, idx *symbols.Index, } // functionFromProto binds a function to the calc its calc_id names in rt's -// model, read as a value in its own scope, against the object self_id names. +// model, read as a value in its own scope. Objects live only within the call that +// created them, so a self_id names none of this call's: it is refused rather +// than matched to whichever object this call happened to number the same. func functionFromProto(rt *runtime.Context, fn *pb.Function, idx *symbols.Index) (runtime.Value, error) { if fn == nil || fn.GetCalcId() == "" { return runtime.Value{}, fmt.Errorf("%w: calc_id is empty", ErrFunctionUnbound) @@ -629,17 +631,13 @@ func functionFromProto(rt *runtime.Context, fn *pb.Function, idx *symbols.Index) if rt == nil || idx == nil { return runtime.Value{}, fmt.Errorf("%w: function %s", ErrFunctionNeedsRuntime, fn.GetCalcId()) } - var self *runtime.Instance if fn.GetSelfId() != 0 { - inst, ok := rt.Instance(fn.GetSelfId()) - if !ok { - return runtime.Value{}, fmt.Errorf("%w: %s: self_id %d names no object of this runtime", - ErrFunctionUnbound, fn.GetCalcId(), fn.GetSelfId()) - } - self = inst + return runtime.Value{}, fmt.Errorf( + "%w: %s: self_id %d names no object of this call: an object lives only within the response that created it", + ErrFunctionUnbound, fn.GetCalcId(), fn.GetSelfId()) } for _, sym := range idx.LookupQualified(fn.GetCalcId()) { - val, isFunction, err := rt.FunctionValueOn(sym, self) + val, isFunction, err := rt.FunctionValue(sym) if !isFunction { continue } diff --git a/internal/grpc/convert_function_test.go b/internal/grpc/convert_function_test.go index f83dc9d6d..8ffc96a1e 100644 --- a/internal/grpc/convert_function_test.go +++ b/internal/grpc/convert_function_test.go @@ -31,9 +31,17 @@ package F { part def Holder { attribute k : Real = 2.0; - calc scale { in x : Real; return : Real = x * k; } + calc scale :> Unary { in :>> v; return : Real = v * k; } } part holder : Holder; + part def Other { attribute k : Real = 100.0; } + part other : Other; + analysis def ApplyCase { + subject s : Other; + in calc f : Unary; + in a : Real; + out y : Real = f(a); + } calc def Outer { in k : Real; @@ -42,6 +50,12 @@ package F { } calc outer : Outer; + calc def Mul { in a : Real; in b : Real; return : Real = a * b; } + calc def Fixed { in k : Real; calc inner : Sq; return : Unary = inner; } + calc fixed : Fixed; + calc def Scaled { in k : Real; calc inner : Mul { in :>> b = k; } return : Mul = inner; } + calc scaled : Scaled; + action run { in calc f { in v : Real; return : Real; } in a : Real; @@ -116,36 +130,6 @@ func TestFunctionRoundTrip(t *testing.T) { t.Fatalf("F::fns = %v, want a sequence of the functions Sq and Cube", fns) } - // A calc usage read off a part closes over that part, which crosses by ID and - // resolves the calc's feature names when the value comes back. - scale := mustEvaluateIn(t, srv, modelHash, "F::holder", "scale") - if scale.GetFunction().GetCalcId() != "F::Holder::scale" || scale.GetFunction().GetSelfId() == 0 { - t.Fatalf("holder.scale = %v, want the calc F::Holder::scale closing over holder", scale) - } - func() { - rt, _, release := srv.newRuntime(cached) - defer release() - holder, err := rt.Instantiate(lookupNamed(idx, "F::holder")[0]) - if err != nil { - t.Fatalf("Instantiate(holder): %v", err) - } - fn, isFunction, err := rt.FunctionValueOn(lookupNamed(idx, "F::Holder::scale")[0], holder) - if err != nil || !isFunction { - t.Fatalf("FunctionValueOn(scale, holder) = %v, %v, %v", fn, isFunction, err) - } - pv := ValueToProtoIn(rt, fn, idx) - if pv.GetFunction().GetCalcId() != "F::Holder::scale" || pv.GetFunction().GetSelfId() != holder.ID { - t.Fatalf("scale over holder crossed as %v, want calc_id F::Holder::scale, self_id %d", pv, holder.ID) - } - back, err := ProtoToRuntimeValue(rt, pv, idx, sem) - if err != nil || back.Kind != runtime.ValFunction || back.FunctionSelf() != holder { - t.Errorf("scale over holder read back as %v, %v; want the function over holder", back, err) - } - if back.Function() != fn.Function() { - t.Errorf("scale over holder read back as the calc %v, want %v", back.Function(), fn.Function()) - } - }() - // A function read in applies as the argument of a calc and an action. calc, err := srv.EvaluateCalc(ctx, &pb.EvaluateCalcRequest{ModelHash: modelHash, SymbolId: "F::apply", Arguments: []*pb.Value{functionValue("F::Sq", 0), realValue(3)}}) if err != nil || calc.Error != "" { @@ -172,6 +156,77 @@ func TestFunctionRoundTrip(t *testing.T) { if closed.GetFunction() != nil || !strings.Contains(closed.GetNull(), "unsupported: function F::Outer::inner") { t.Errorf("F::outer(3.0) = %v, want an unsupported null naming F::Outer::inner", closed) } + // So does one whose inherited body reads nothing of the run, but whose own default does. + scaled := mustEvaluate(t, srv, modelHash, "F::scaled(3.0)") + if scaled.GetFunction() != nil || !strings.Contains(scaled.GetNull(), "unsupported: function F::Scaled::inner") { + t.Errorf("F::scaled(3.0) = %v, want an unsupported null naming F::Scaled::inner", scaled) + } + + // A nested usage reading nothing of the run that returned it crosses as the + // calc it names, and comes back applying that calc's inherited body. + fixed := mustEvaluate(t, srv, modelHash, "F::fixed(3.0)") + if fixed.GetFunction().GetCalcId() != "F::Fixed::inner" || fixed.GetFunction().GetSelfId() != 0 { + t.Fatalf("F::fixed(3.0) = %v, want the calc F::Fixed::inner closing over no object", fixed) + } + applied, err := srv.EvaluateCalc(ctx, &pb.EvaluateCalcRequest{ + ModelHash: modelHash, SymbolId: "F::apply", Arguments: []*pb.Value{fixed, realValue(3)}, + }) + if err != nil || applied.Error != "" { + t.Fatalf("EvaluateCalc(apply, fixed inner): %v %s", err, applied.GetError()) + } + if got := applied.Result.GetRealValue(); got != 9 { + t.Errorf("apply(F::Fixed::inner, 3.0) = %v, want 9.0", applied.Result) + } +} + +// A calc usage read off an object crosses with the object's id, which lives as +// long as the response; sent back to any later call, it is refused rather than +// matched to whatever object that call numbers the same. The object-bound calc +// applies within the one call that reads it off its object. +func TestObjectBoundFunctionsDoNotCrossCalls(t *testing.T) { + ctx := context.Background() + srv := mustNewService(t, 4) + modelHash := mustParse(t, srv, functionWireModel) + + scale := mustEvaluateIn(t, srv, modelHash, "F::holder", "scale") + if scale.GetFunction().GetCalcId() != "F::Holder::scale" || scale.GetFunction().GetSelfId() == 0 { + t.Fatalf("holder.scale = %v, want the calc F::Holder::scale closing over holder", scale) + } + refused := func(what, msg string) { + t.Helper() + if !strings.Contains(msg, "self_id") || !strings.Contains(msg, "lives only within the response") { + t.Errorf("%s with holder.scale: error %q, want the self_id refused as outliving its response", what, msg) + } + } + + calc, err := srv.EvaluateCalc(ctx, &pb.EvaluateCalcRequest{ModelHash: modelHash, SymbolId: "F::apply", Arguments: []*pb.Value{scale, realValue(3)}}) + if err != nil || calc.Result != nil { + t.Fatalf("EvaluateCalc(apply, holder.scale): err = %v, result = %v, want an in-body refusal", err, calc.GetResult()) + } + refused("EvaluateCalc", calc.Error) + + act, err := srv.ExecuteAction(ctx, &pb.ExecuteActionRequest{ + ModelHash: modelHash, ActionSymbolId: "F::run", Inputs: map[string]*pb.Value{"f": scale, "a": realValue(2)}, + }) + if err != nil || len(act.Outputs) != 0 { + t.Fatalf("ExecuteAction(run, holder.scale): err = %v, outputs = %v, want an in-body refusal", err, act.GetOutputs()) + } + refused("ExecuteAction", act.Error) + + // An analysis instantiates its subject before reading its arguments, so this + // call holds an object numbered as holder was; the function does not bind to it. + an, err := srv.RunAnalysis(ctx, &pb.RunAnalysisRequest{ + ModelHash: modelHash, SymbolId: "F::ApplyCase", SubjectSymbolId: "F::other", Arguments: []*pb.Value{scale, realValue(3)}, + }) + if err != nil || len(an.Outputs) != 0 { + t.Fatalf("RunAnalysis(ApplyCase over other, holder.scale): err = %v, outputs = %v, want an in-body refusal", err, an.GetOutputs()) + } + refused("RunAnalysis", an.Error) + + // Within one call, the object is read and the calc applied over it. + if got := mustEvaluate(t, srv, modelHash, "F::apply(F::holder.scale, 3.0)").GetRealValue(); got != 6 { + t.Errorf("apply(holder.scale, 3.0) = %v, want 6.0", got) + } } // A function naming no calc of the model, an object the runtime does not @@ -193,7 +248,7 @@ func TestMalformedFunctionsAreRejected(t *testing.T) { {"unknown declaration", functionValue("F::Nope", 0), ErrFunctionUnbound}, {"declaration that is not a calc", functionValue("F::holder", 0), ErrFunctionUnbound}, {"calc usage computing a result", functionValue("F::pickSq", 0), ErrFunctionUnbound}, - {"object the runtime does not hold", functionValue("F::Sq", 12345), ErrFunctionUnbound}, + {"object of another call", functionValue("F::Sq", 12345), ErrFunctionUnbound}, {"nested in a sequence", &pb.Value{Kind: &pb.Value_Sequence{Sequence: &pb.ValueSequence{Elements: []*pb.Value{ intValue(1), functionValue("F::Nope", 0), }}}}, ErrFunctionUnbound}, From 333e6fafe267cc96a551a31fd351abfdc239aeb7 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 8 Sep 2026 04:25:15 +0000 Subject: [PATCH 07/11] fix(runtime): scope nested calc environments to the run of their owning behavior A calc declared in a behavior body used to close over every frame on the evaluation stack, so a caller's same-named parameter could satisfy a name in the nested body. Frames are now selected by ownership: a nested calc closes over the frames through the innermost active run of the behavior it is declared in (a calc invocation or usage of that behavior or one specializing it, or an action performance typed by it), and over nothing when no such run is active. Direct invocation, function-value snapshots and nested calc usages all apply the rule. Co-Authored-By: jason.han --- changes/unreleased/function-values.added.md | 2 +- docs/internals/architecture.md | 2 +- docs/project/spec-compliance.md | 2 +- internal/core/runtime/calc_usage.go | 39 ++++++++++++++++--- internal/core/runtime/eval.go | 35 +++++++++-------- internal/core/runtime/frame.go | 14 +++++++ internal/core/runtime/function_value.go | 4 +- internal/core/runtime/robustness_test.go | 18 +++++++++ ...ested_calc_reads_performance.expected.json | 9 +++++ ...action_nested_calc_reads_performance.sysml | 33 ++++++++++++++++ .../function_value_body_closure.expected.json | 2 +- .../function_value_body_closure.sysml | 10 ++++- 12 files changed, 141 insertions(+), 29 deletions(-) create mode 100644 internal/core/runtime/testdata/conformance/action_nested_calc_reads_performance.expected.json create mode 100644 internal/core/runtime/testdata/conformance/action_nested_calc_reads_performance.sysml diff --git a/changes/unreleased/function-values.added.md b/changes/unreleased/function-values.added.md index aae537875..fa99cddec 100644 --- a/changes/unreleased/function-values.added.md +++ b/changes/unreleased/function-values.added.md @@ -1,2 +1,2 @@ -- **A calc is a value.** A calc definition, a calc usage awaiting an input, or an `in calc` parameter named where a value is expected is a function value: the calc together with the scope and object it was read in, invoked through a calc-typed parameter (`calc def Fn { in calc f { in v : Real; return : Real; } in a : Real; return : Real = f(a); }` makes `Fn(Sq, 3.0)` `9.0`), passed positionally or by name, read off a part, returned from a calc, compared and adopted. `SampledFunctions::Sample` now samples a user calc, and a library function the runtime implements (`RealFunctions::sqrt`) is a value too. Calling a non-function, an arity mismatch and an unbound calc parameter are typed errors; `in calc` parameters parse in action bodies as they do in calc bodies. +- **A calc is a value.** A calc definition, a calc usage awaiting an input, or an `in calc` parameter named where a value is expected is a function value: the calc together with the scope and object it was read in, invoked through a calc-typed parameter (`calc def Fn { in calc f { in v : Real; return : Real; } in a : Real; return : Real = f(a); }` makes `Fn(Sq, 3.0)` `9.0`), passed positionally or by name, read off a part, returned from a calc, compared and adopted. `SampledFunctions::Sample` now samples a user calc, and a library function the runtime implements (`RealFunctions::sqrt`) is a value too. A calc declared in a behavior body closes over the innermost active run of that behavior alone — never a caller's parameters, and nothing when no such run is active. Calling a non-function, an arity mismatch and an unbound calc parameter are typed errors; `in calc` parameters parse in action bodies as they do in calc bodies. - **Function values cross the API.** `Value.function` carries the calc's qualified name and the id of the object it was read off, under the new `function_values` capability, which the Go, Python, Node, Rust and Java clients expose as a typed value and refuse to send to a service without the capability. A function closing over a behavior body's bindings crosses as an unsupported null, since no name reconstructs it; one read off an object is refused as an argument to a later call, since that object lived only within the response that sent it. Native compilation refuses a calc that binds or applies a function value with a typed error. diff --git a/docs/internals/architecture.md b/docs/internals/architecture.md index d0e091078..e97ed269b 100644 --- a/docs/internals/architecture.md +++ b/docs/internals/architecture.md @@ -247,7 +247,7 @@ Full evaluator with **user-defined calc invocation**, **constraint evaluation**, - Feature access `x.y.z` resolved against instance feature values - KerML operator library (`->select`, `->collect`, `size`, string ops) - **Calc invocation:** Resolve calc symbol → extract params/return → bind args to parameters → evaluate return expression -- **Function values** (`function_value.go`, `ValFunction`): a calc definition, a calc usage with an unsupplied input or an `in calc` parameter read as a value is the calc's lowered `calcShape` plus the environment it was read in — declaring scope, the object it was read off, and the enclosing body frames for a calc declared inside a behavior body. Invoking one (`f(a)` through a calc-typed parameter, or `SampledFunctions::Sample` applying its `calculation`) takes the calc invocation path (`invokeCalcShapeIn`), never a closure over statements; `ValExpr` remains the distinct kind for an expression body a collection operation evaluates per element +- **Function values** (`function_value.go`, `ValFunction`): a calc definition, a calc usage with an unsupplied input or an `in calc` parameter read as a value is the calc's lowered `calcShape` plus the environment it was read in — declaring scope, the object it was read off, and, for a calc declared inside a behavior body, the frames through the innermost active run of that behavior (`EvalContext.enclosingRun`, by `frame.runs`) — never a caller's frames, and none when no such run is active. Invoking one (`f(a)` through a calc-typed parameter, or `SampledFunctions::Sample` applying its `calculation`) takes the calc invocation path (`invokeCalcShapeIn`), never a closure over statements; `ValExpr` remains the distinct kind for an expression body a collection operation evaluates per element - **Constraint evaluation:** Extract `assert`/`assume` members → evaluate boolean expressions → check satisfaction (with optional `not` negation) - **Requirement evaluation:** Extract `subject`/`assume`/`require`/`actor` members → validate bindings → evaluate conditions - **Scoped evaluation:** `EvalContext.scope` for name resolution, frame stack for parameter bindings diff --git a/docs/project/spec-compliance.md b/docs/project/spec-compliance.md index a73f6aea3..d569c6927 100644 --- a/docs/project/spec-compliance.md +++ b/docs/project/spec-compliance.md @@ -227,7 +227,7 @@ Each row documents one behavioral semantic feature: | A calc usage's outputs are evaluation results, not feature values of an object | `runtime/calc_usage.go` (no instance materialization) | `calc_usage_instance_slots.sysml` (the features fed by the outputs are feature values; the usage itself is not), pilot-exec-diff `w6d:calc-usage` | ⚠️ Approximate (unrefereeable: the pinned artifact answers a `CalculationUsage` node rather than an output value. `%instances` and export show the features valued from outputs, not the usage's outputs themselves) | | A calc definition, a calc usage with an unsupplied input, or an `in calc` parameter named where a value is expected is a **function value** (KerML 1.1 §7.4.4: a Function is a Behavior with a `result`, an Expression a Step typed by one, and a feature reference to either denotes it; §8.3.4.8 `Function::result`, `FeatureReferenceExpression`; SysML v2 §7.17: a calc def is a Function, a calc usage an Expression). The value is the calc's lowered invocation interface (`calcShape`) together with the environment it was read in — its declaring scope and the object it was read off — and nothing else: no statement closure is built, and the value is invoked through the same path a calc usage invocation takes (`invokeCalcShapeIn`). Reading a calc usage whose inputs are all bound evaluates it as before; a library function the runtime implements natively (`RealFunctions::sqrt`, `floor`) reads as a value carrying that implementation, while a library operation that binds its arguments unevaluated (`SequenceFunctions::size` and the other `->` operations) is refused as `ErrNotAFunction` | `runtime/value.go` `ValFunction`, `runtime/function_value.go` `functionValue`/`EvalContext.functionValueOf`/`Context.readsAsFunction`/`EvalContext.calcAsValue`, `invoke_calc.go` `calcShapeOf` (a natively implemented library function computes), `eval.go` `evalFeatureReference`, `describe.go` (`the function Sq`), `trace.go` `FormatTraceValue` (`calc(Sq)`), `repl/meta.go` | `function_value_read.sysml`, `function_value_probe.sysml` (`Fn(Sq, 3.0)` is `9.0`), `function_value_calc_usage.sysml`, `function_value_library.sysml`, `robustness_test.go:function_value_of_a_built_in`, `value_kinds_test.go:TestFunctionValueIdentity`, `:TestEveryValueKindIsDispatched`, `eval_no_value_test.go`, `repl/evalin_test.go` | ✅ Faithful | | An `in calc` parameter of a calc or an action (SysML v2 §7.17, §8.3.16 `CalculationUsage` as a parameter) accepts a function value or null and nothing else, positionally or by name; the body invokes it as `f(a)`, through a chain (`p.f(a)`), nested (`f(f(a))`) and as an argument to another calc-typed parameter, binding the callee's inputs positionally and by name as a direct invocation does. A calc usage bound as an action input (`in f = sq;`) is the function value it reads as | `runtime/invoke_calc.go` `calcParameter.checkFunction`, `function_value.go` `EvalContext.invokeFunction`, `eval.go` `evalInvocation`/`evalFeatureChain`, `parser/behavior.go` `parameterKindKeywords` (`calc`) | `function_value_probe.sysml` + `function_value_probe.trace.golden` (`TestExecutionTrace`), `function_value_named_args.sysml`, `function_value_chain_call.sysml`, `function_value_action_parameter.sysml`, parser golden `action_calc_parameter.sysml`, `robustness_test.go:function_value_call_of_a_non_function` (`ErrNotAFunction`), `:function_value_bound_to_a_non_function` (`ErrNotAFunction`), `:function_value_arity_mismatch` (`ErrCalcArity`), `testCalcUnboundParameter` (`ErrUnboundParameter`) | ✅ Faithful | -| A calc read off an object (`twice.scale`, a calc usage owned by a part) is a function value over that object: invoked later it reads that object's features, so the same calc read off two parts is two values. A calc declared inside a behavior body (a calc's or an action's) closes over the bindings of the run it was read in — its enclosing parameters and locals — and a function returned from a calc (`return : Unary = scale;`) keeps them after the run ends. The closure reaches only the code written inside that body: a body the nested calc inherits from a calc declared elsewhere (`calc again : RecursiveStep;`) reads none of the enclosing run's bindings, so a case performing itself as a step nests its frames no deeper than its recursion | `function_value.go` `functionValue.self`/`.enclosing`, `EvalContext.functionValueOf` (`enclosedByBehaviorBody`), `calc_usage.go` (a nested usage keeps the enclosing frames; `calcShape.bodyEnclosing`/`declaredWithin`), `invoke_calc.go` `Context.invokeCalcShapeIn` | `function_value_feature_closure.sysml` (`Fn(twice.scale, 5.0) + Fn(thrice.scale, 5.0)` is `25.0`), `function_value_body_closure.sysml` (`Outer`, `Maker`), `function_value_sampled_closure.sysml`; `robustness_test.go:function_value_inherited_body_outside_the_closure`; `analysis_robustness_test.go:recursion_through_a_step` | ✅ Faithful | +| A calc read off an object (`twice.scale`, a calc usage owned by a part) is a function value over that object: invoked later it reads that object's features, so the same calc read off two parts is two values. A calc declared inside a behavior body (a calc's or an action's) closes over the bindings of the run it was read in — its enclosing parameters and locals — and a function returned from a calc (`return : Unary = scale;`) keeps them after the run ends. The closure reaches only the code written inside that body: a body the nested calc inherits from a calc declared elsewhere (`calc again : RecursiveStep;`) reads none of the enclosing run's bindings, so a case performing itself as a step nests its frames no deeper than its recursion. The run closed over is a run of the behavior the calc is declared in — the innermost active invocation of that calc (or of one specializing it) or performance of that action — not whatever calc happens to be evaluating: a nested calc applied from a calc between it and its owner reads the owner's `k`, not the caller's, and one applied (`Outer::inner(2.0)`) or read (`Fn(Outer::inner, 2.0)`) while no run of its owner is active closes over nothing, so its body's `k` is unresolved rather than a same-named parameter of the caller | `function_value.go` `functionValue.self`/`.enclosing`, `EvalContext.functionValueOf` (`enclosedByBehaviorBody`); `eval.go` `EvalContext.enclosingRun`; `frame.go` `frame.runs` (a calc frame by `calcShape.qualifiedBy`, a performance frame by its scope's owner); `calc_usage.go` `runOf`, `calcShape.bodyEnclosing`/`declaredWithin`; `invoke_calc.go` `Context.invokeCalcShapeIn` | `function_value_feature_closure.sysml` (`Fn(twice.scale, 5.0) + Fn(thrice.scale, 5.0)` is `25.0`), `function_value_body_closure.sysml` (`Outer`, `Maker`, `Shadowed`), `function_value_sampled_closure.sysml`, `action_nested_calc_reads_performance.sysml`; `robustness_test.go:function_value_inherited_body_outside_the_closure`, `:function_value_nested_calc_outside_its_run`; `analysis_robustness_test.go:recursion_through_a_step` | ✅ Faithful | | Function values are equal, and key a set, by the calc together with the object it was read off (`Sq == Sq`, `twice.scale != thrice.scale`); a value closing over a behavior body's bindings is equal only to itself, since two reads of it in one run are one value and reads in two runs are not. Adoption into another runtime rebinds an ordinary function value to the same calc of the adopting model, the object it was read off adopted with it; one closing over a body's bindings is refused as a typed error (the run those bindings belonged to has ended and cannot be reconstructed) | `value_equality.go` `valueEqual`/`valueKeyFunc` (`ValFunction` arm), `adopt.go` | `value_kinds_test.go:TestFunctionValueIdentity`, `adopt_test.go:TestAdoptRebindsAFunctionValue` | ✅ Faithful | | `SampledFunctions::Sample(f, domain)` samples a user calc passed as its `in calc calculation` argument: the library's own body runs, `domainValues->collect { in x; new SamplePair(x, calculation(x)) }` invoking the function value inside the collection body, and `Range` of the result reads the samples back | the library body under `invoke_calc.go` `invokeCalcShapeIn`, `function_value.go` `EvalContext.invokeFunction` (from a collection body's frame), `collections.go` | `function_value_sampled.sysml` (`Range(Sample(Sq, (1.0, 2.0, 3.0)))` is `[1.0, 4.0, 9.0]`), `function_value_sampled_closure.sysml` (a calc read off a part, sampled) | ✅ Faithful | | `SampledFunctions::SamplePair` arithmetic and `SampledFunctions::interpolateLinear` on the library's own examples: reading a `SamplePair`'s `domainValue` or `rangeValue` yields the one-element sequence `[1.0]`, and `-`/`*` refuse a sequence operand (`type mismatch: operator '-' is not defined for a Real and a sequence`). The cause is the `[0..*]`-inherited member read not reducing a singleton sequence to the scalar it denotes, which is independent of function values (the same failure reproduces with no function value involved) and is not papered over here with a `SamplePair`-specific unwrap | `runtime/eval.go` `chainMemberValue`/`evalArithmetic`, `instance.go` (scalar-feature admission reduces a singleton only where the feature is declared scalar) | reproduced by `SampledFunctions::interpolateLinear` and by `s.samples#(1).domainValue - 1.0` | ❌ Not implemented (the singleton reduction of a `[0..*]`-inherited member read; the failure is a typed error, not a wrong answer) | diff --git a/internal/core/runtime/calc_usage.go b/internal/core/runtime/calc_usage.go index 8ec9c97c4..c07acd194 100644 --- a/internal/core/runtime/calc_usage.go +++ b/internal/core/runtime/calc_usage.go @@ -273,11 +273,19 @@ func (shape *calcShape) memberName(ctx *Context, sym *symbols.Symbol) (string, b // qualifiedBy reports whether a name qualified by qualifier (`MassCase::result`, // `Cases::Case::result`) denotes this calc's run: the calc itself or one it specializes. func (shape *calcShape) qualifiedBy(ctx *Context, qualifier *symbols.Symbol) bool { - if qualifier == shape.Sym { + return ctx.isOrSpecializes(shape.Sym, qualifier) +} + +// isOrSpecializes reports sym being general, or inheriting its members from it. +func (ctx *Context) isOrSpecializes(sym, general *symbols.Symbol) bool { + if sym == nil || general == nil { + return false + } + if sym == general { return true } - for _, general := range ctx.model.MemberSources(shape.Sym) { - if general == qualifier { + for _, source := range ctx.model.MemberSources(sym) { + if source == general { return true } } @@ -661,8 +669,13 @@ func (ctx *Context) bindCalcUsage(shape *calcShape, reader *EvalContext, args ca // members or in a body-local block of it, which declares no owner of its own — // rather than in a part or a package, whose members hold no running values. func enclosedByBehaviorBody(sym *symbols.Symbol) bool { - owner := enclosingBehavior(sym) - return isCalcSymbol(owner) || isActionSymbol(owner) || isStateSymbol(owner) + return holdsRunningValues(enclosingBehavior(sym)) +} + +// holdsRunningValues reports a behavior whose runs bind values: a calc, an action +// or a state machine. +func holdsRunningValues(sym *symbols.Symbol) bool { + return isCalcSymbol(sym) || isActionSymbol(sym) || isStateSymbol(sym) } // bodyEnclosing is the part of enclosing, the bindings of the behavior body the @@ -675,6 +688,20 @@ func (shape *calcShape) bodyEnclosing(enclosing []frame) []frame { return enclosing } +// runOf is the environment of the innermost run of behavior among frames: the +// frames through the one holding that run; nil when none of them does. +func runOf(ctx *Context, frames []frame, behavior *symbols.Symbol) []frame { + if !holdsRunningValues(behavior) { + return nil + } + for i := len(frames) - 1; i >= 0; i-- { + if frames[i].runs(ctx, behavior) { + return frames[:i+1] + } + } + return nil +} + // closesOverBody reports the calc reading the bindings of the behavior body it is // declared in: through a body written there, or a default it declares there. func (shape *calcShape) closesOverBody() bool { @@ -734,7 +761,7 @@ func (ctx *Context) runCalcUsage( // an invocation of it does. var enclosing []frame if nested != nil { - enclosing = shape.bodyEnclosing(nested.frames) + enclosing = shape.bodyEnclosing(nested.enclosingRun(shape)) } engine := newStmtEngineIn(ctx, host, env, enclosing) host.attachPerformances(engine) diff --git a/internal/core/runtime/eval.go b/internal/core/runtime/eval.go index 8c8ad831f..32f727cd4 100644 --- a/internal/core/runtime/eval.go +++ b/internal/core/runtime/eval.go @@ -134,15 +134,23 @@ func (ec *EvalContext) nestedEnv(scope *symbols.Scope) *EvalContext { // are copied since an invocation's frame storage is reused once it returns, and // the calc evaluation whose outputs it may name is detached from that storage. func (ec *EvalContext) closure() *EvalContext { - frames := make([]frame, len(ec.frames)) - for i, f := range ec.frames { - frames[i] = f.snapshot() - } - out := ec.over(ec.scope, frames) + out := ec.over(ec.scope, snapshotFrames(ec.frames)) out.calcRun = ec.calcRun.detached() return out } +// snapshotFrames copies the bindings of frames, which their runs may reuse or drop. +func snapshotFrames(frames []frame) []frame { + if len(frames) == 0 { + return nil + } + out := make([]frame, len(frames)) + for i, f := range frames { + out[i] = f.snapshot() + } + return out +} + // over is this environment resolving names in scope over frames of its own. func (ec *EvalContext) over(scope *symbols.Scope, frames []frame) *EvalContext { return &EvalContext{ @@ -2254,7 +2262,6 @@ type invocationTarget struct { builtinName string // the built-in's registered name, keying its declared signature library *libraryFunction // the library function the name denotes: the library declaration calc is shape *calcShape // calc's invocation interface, nil when it has none - enclosed bool // calc is declared in a behavior body, whose bindings it reads names []string // the parameter each named argument binds, as calc's signature spells it unbound []error // per named argument, why calc has no parameter for it; nil when it binds } @@ -2324,7 +2331,6 @@ func (ctx *Context) implementInvocation(target *invocationTarget, sym *symbols.S target.library = fn } else if shape, err := ctx.calcShapeOf(sym); err == nil { target.shape = shape - target.enclosed = enclosedByBehaviorBody(sym) } } @@ -2396,7 +2402,7 @@ func (ec *EvalContext) evalInvocation(n *ast.InvocationExpr) (Value, error) { // A calc bound by position alone consumes its arguments within the call, so // they live on the context's argument stack rather than in a slice of their own. if target.shape != nil && len(n.NamedArgs) == 0 { - return ec.invokeCalcShapeStacked(target.shape, exprs, ec.enclosingFor(target)) + return ec.invokeCalcShapeStacked(target.shape, exprs, ec.enclosingRun(target.shape)) } // A built-in binds its arguments by its declared signature. if target.builtin != nil { @@ -2422,16 +2428,13 @@ func (ec *EvalContext) evalInvocation(n *ast.InvocationExpr) (Value, error) { if target.shape == nil { return ec.ctx.invokeCalcWithSelf(target.calc, callArgs, ec.scope, ec.self) } - return ec.ctx.invokeCalcShapeIn(target.shape, callArgs, ec.scope, ec.self, ec.enclosingFor(target)) + return ec.ctx.invokeCalcShapeIn(target.shape, callArgs, ec.scope, ec.self, ec.enclosingRun(target.shape)) } -// enclosingFor is the environment a call of target runs under: this one's bindings -// for a calc declared in the body being evaluated, none for any other. -func (ec *EvalContext) enclosingFor(target *invocationTarget) []frame { - if !target.enclosed { - return nil - } - return ec.frames +// enclosingRun is the environment a nested calc closes over here: the frames through +// the innermost run of the behavior it is declared in, none when no such run is active. +func (ec *EvalContext) enclosingRun(shape *calcShape) []frame { + return runOf(ec.ctx, ec.frames, enclosingBehavior(shape.Sym)) } // evalChainInvocation applies the function value a feature chain denotes to the diff --git a/internal/core/runtime/frame.go b/internal/core/runtime/frame.go index f011bb2b5..7aa81ba41 100644 --- a/internal/core/runtime/frame.go +++ b/internal/core/runtime/frame.go @@ -1,5 +1,7 @@ package runtime +import "github.com/Open-MBEE/OpenSysML/internal/core/symbols" + // frame is one level of local bindings an evaluation reads: a calc invocation's // parameter slots, a map of named values, or both. type frame struct { @@ -53,6 +55,18 @@ func ownedFrame(owner *calcShape, vars map[string]Value) frame { return frame{vars: vars, owner: owner} } +// runs reports whether the frame holds a run of behavior: an invocation or usage of +// that calc or one specializing it, or a performance of that action or one typed by it. +func (f frame) runs(ctx *Context, behavior *symbols.Symbol) bool { + if f.owner != nil { + return f.owner.qualifiedBy(ctx, behavior) + } + if f.perf != nil && f.perf.scope != nil { + return ctx.isOrSpecializes(f.perf.scope.Owner(), behavior) + } + return false +} + // withVars is the frame holding vars in place of its own, still answering for // the same run and performance. func (f frame) withVars(vars map[string]Value) frame { diff --git a/internal/core/runtime/function_value.go b/internal/core/runtime/function_value.go index efcca9413..6b7c7989b 100644 --- a/internal/core/runtime/function_value.go +++ b/internal/core/runtime/function_value.go @@ -78,8 +78,8 @@ func (ec *EvalContext) functionValueOf(sym *symbols.Symbol) (Value, error) { } fn := &functionValue{shape: shape, scope: ec.scope, self: ec.self} fn.library, _ = ec.ctx.libraryFunctionFor(sym) - if len(ec.frames) > 0 && shape.closesOverBody() { - fn.enclosing = ec.closure().frames + if shape.closesOverBody() { + fn.enclosing = snapshotFrames(ec.enclosingRun(shape)) } return Value{Kind: ValFunction, ref: fn}, nil } diff --git a/internal/core/runtime/robustness_test.go b/internal/core/runtime/robustness_test.go index 601029f0b..261b04321 100644 --- a/internal/core/runtime/robustness_test.go +++ b/internal/core/runtime/robustness_test.go @@ -367,6 +367,7 @@ func TestRuntimeRobustness(t *testing.T) { t.Run("function_value_of_a_built_in", testFunctionValueOfABuiltIn) t.Run("function_value_applied_to_itself_forever", testFunctionValueAppliedToItselfForever) t.Run("function_value_inherited_body_outside_the_closure", testFunctionValueInheritedBodyOutsideTheClosure) + t.Run("function_value_nested_calc_outside_its_run", testFunctionValueNestedCalcOutsideItsRun) } func testBindingConflict(t *testing.T) { @@ -11408,3 +11409,20 @@ func testFunctionValueInheritedBodyOutsideTheClosure(t *testing.T) { } } } + +// testFunctionValueNestedCalcOutsideItsRun: a calc nested in another calc's body +// closes over a run of that calc alone; applied from a calc that binds the same +// parameter name while no such run is active, it reads no binding of the caller's. +func testFunctionValueNestedCalcOutsideItsRun(t *testing.T) { + src := `package test {` + functionValueFixture + ` + calc def Outer { in k : Real; calc inner { in v : Real; return : Real = v * k; } return : Real = inner(1.0); } + calc def Called { in k : Real; return : Real = Outer::inner(2.0); } + calc def Passed { in k : Real; return : Real = Fn(Outer::inner, 2.0); } + }` + for _, expr := range []string{"test::Called(3.0)", "test::Passed(3.0)"} { + err := invokeCalcExpecting(t, src, expr) + if !errors.Is(err, ErrNoValue) && !errors.Is(err, ErrUnresolvedReference) { + t.Fatalf("%s: error = %v, want k unresolved in inner's body", expr, err) + } + } +} diff --git a/internal/core/runtime/testdata/conformance/action_nested_calc_reads_performance.expected.json b/internal/core/runtime/testdata/conformance/action_nested_calc_reads_performance.expected.json new file mode 100644 index 000000000..adf1d9624 --- /dev/null +++ b/internal/core/runtime/testdata/conformance/action_nested_calc_reads_performance.expected.json @@ -0,0 +1,9 @@ +{ + "type": "action", + "evaluate": "test::run", + "libraries": true, + "outputs": { + "total": {"type": "Real", "value": 60.0}, + "s.r": {"type": "Real", "value": 60.0} + } +} diff --git a/internal/core/runtime/testdata/conformance/action_nested_calc_reads_performance.sysml b/internal/core/runtime/testdata/conformance/action_nested_calc_reads_performance.sysml new file mode 100644 index 000000000..8f3df7d67 --- /dev/null +++ b/internal/core/runtime/testdata/conformance/action_nested_calc_reads_performance.sysml @@ -0,0 +1,33 @@ +// A calc declared in an action's body reads the performance of that action it is +// applied in — the one typed by the action def, or the subaction's own — whether +// invoked directly or passed on as a function value. +package test { + private import ScalarValues::*; + + calc def Fn { in calc f { in x : Real; return : Real; } in a : Real; return : Real = f(a); } + + action def Scale { + in k : Real; + out r : Real; + calc byK { in x : Real; return : Real = x * k; } + + first start; + then action direct { assign r := byK(3.0); } + then action nested { + attribute m : Real = 2.0; + calc byM { in x : Real; return : Real = x * m * k; } + assign r := r + byM(1.0); + } + then action passed { assign r := r + Fn(byK, 1.0); } + then done; + } + + action run { + out attribute total : Real = 0.0; + + first start; + then action s : Scale { in k = 10.0; } + then action tally { assign total := s.r; } + then done; + } +} diff --git a/internal/core/runtime/testdata/conformance/function_value_body_closure.expected.json b/internal/core/runtime/testdata/conformance/function_value_body_closure.expected.json index 02f149502..9feea1801 100644 --- a/internal/core/runtime/testdata/conformance/function_value_body_closure.expected.json +++ b/internal/core/runtime/testdata/conformance/function_value_body_closure.expected.json @@ -2,5 +2,5 @@ "type": "calc", "evaluate": "test::UseOuter", "libraries": true, - "result": {"type": "Real", "value": 105.0} + "result": {"type": "Real", "value": 211.0} } diff --git a/internal/core/runtime/testdata/conformance/function_value_body_closure.sysml b/internal/core/runtime/testdata/conformance/function_value_body_closure.sysml index 91c7e692f..d7b20550c 100644 --- a/internal/core/runtime/testdata/conformance/function_value_body_closure.sysml +++ b/internal/core/runtime/testdata/conformance/function_value_body_closure.sysml @@ -23,5 +23,13 @@ package test { calc def Mul { in a : Real; in b : Real; return : Real = a * b; } calc def Fixer { in k : Real; calc byK : Mul { in :>> b = k; } return : Mul = byK; } calc def ApplyOne { in calc f { in a : Real; return : Real; } in a : Real; return : Real = f(a); } - calc def UseOuter { return : Real = Outer(3.0) + Outer(4.0) + Bare(5.0) + Fn(Maker(10.0), 2.0) + ApplyOne(Fixer(10.0), 4.0); } + // A nested calc reads the run of the calc it is declared in, not the caller + // between them, even where that caller binds the same name. + calc def Shadowed { + in k : Real; + calc inner { in x : Real; return : Real = x * k; } + calc wrap { in k : Real; return : Real = inner(1.0) + Fn(inner, 1.0) + k; } + return : Real = wrap(100.0); + } + calc def UseOuter { return : Real = Outer(3.0) + Outer(4.0) + Bare(5.0) + Fn(Maker(10.0), 2.0) + ApplyOne(Fixer(10.0), 4.0) + Shadowed(3.0); } } From ae947a54f08e6a253113879605a5051f202de8ca Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 8 Sep 2026 04:57:38 +0000 Subject: [PATCH 08/11] fix(runtime): keep frame aliases in closures and type-check chain calls A function value returned from a specialized run snapshots the frames it closes over; the snapshot now carries the frames' redefinition aliases, so a nested calc reading an inherited parameter name a subclass renamed resolves it instead of failing. A call through a feature chain, holder.scale(3.0), is checked statically as a direct call is: the chain's terminal calc feature is resolved, its positional and named arguments are held to that feature's effective inputs, a chain to a non-behavior is refused, and the call is typed by the calc's result so binding it to an incompatible declared type is reported. Co-Authored-By: jason.han --- changes/unreleased/function-values.added.md | 2 +- docs/project/spec-compliance.md | 1 + internal/core/passes/invocation.go | 15 ++++++-- internal/core/passes/typecheck_expr.go | 36 ++++++++++++++++++- internal/core/passes/typecheck_expr_test.go | 32 +++++++++++++++++ internal/core/passes/typecheck_value.go | 9 +++-- internal/core/runtime/frame.go | 13 +++++-- .../function_value_body_closure.expected.json | 2 +- .../function_value_body_closure.sysml | 11 +++++- 9 files changed, 110 insertions(+), 11 deletions(-) diff --git a/changes/unreleased/function-values.added.md b/changes/unreleased/function-values.added.md index fa99cddec..0d89ce876 100644 --- a/changes/unreleased/function-values.added.md +++ b/changes/unreleased/function-values.added.md @@ -1,2 +1,2 @@ -- **A calc is a value.** A calc definition, a calc usage awaiting an input, or an `in calc` parameter named where a value is expected is a function value: the calc together with the scope and object it was read in, invoked through a calc-typed parameter (`calc def Fn { in calc f { in v : Real; return : Real; } in a : Real; return : Real = f(a); }` makes `Fn(Sq, 3.0)` `9.0`), passed positionally or by name, read off a part, returned from a calc, compared and adopted. `SampledFunctions::Sample` now samples a user calc, and a library function the runtime implements (`RealFunctions::sqrt`) is a value too. A calc declared in a behavior body closes over the innermost active run of that behavior alone — never a caller's parameters, and nothing when no such run is active. Calling a non-function, an arity mismatch and an unbound calc parameter are typed errors; `in calc` parameters parse in action bodies as they do in calc bodies. +- **A calc is a value.** A calc definition, a calc usage awaiting an input, or an `in calc` parameter named where a value is expected is a function value: the calc together with the scope and object it was read in, invoked through a calc-typed parameter (`calc def Fn { in calc f { in v : Real; return : Real; } in a : Real; return : Real = f(a); }` makes `Fn(Sq, 3.0)` `9.0`), passed positionally or by name, read off a part, returned from a calc, compared and adopted. `SampledFunctions::Sample` now samples a user calc, and a library function the runtime implements (`RealFunctions::sqrt`) is a value too. A calc declared in a behavior body closes over the innermost active run of that behavior alone — never a caller's parameters, and nothing when no such run is active. Calling a non-function, an arity mismatch and an unbound calc parameter are typed errors; `in calc` parameters parse in action bodies as they do in calc bodies. A call through a feature chain (`holder.scale(3.0)`) is now checked statically as a direct call is — arguments against the calc feature's inputs, the result against the declared type it binds to. - **Function values cross the API.** `Value.function` carries the calc's qualified name and the id of the object it was read off, under the new `function_values` capability, which the Go, Python, Node, Rust and Java clients expose as a typed value and refuse to send to a service without the capability. A function closing over a behavior body's bindings crosses as an unsupported null, since no name reconstructs it; one read off an object is refused as an argument to a later call, since that object lived only within the response that sent it. Native compilation refuses a calc that binds or applies a function value with a typed error. diff --git a/docs/project/spec-compliance.md b/docs/project/spec-compliance.md index 8f75b969f..aac5f7b7f 100644 --- a/docs/project/spec-compliance.md +++ b/docs/project/spec-compliance.md @@ -227,6 +227,7 @@ Each row documents one behavioral semantic feature: | A calc usage's outputs are evaluation results, not feature values of an object | `runtime/calc_usage.go` (no instance materialization) | `calc_usage_instance_slots.sysml` (the features fed by the outputs are feature values; the usage itself is not), pilot-exec-diff `w6d:calc-usage` | ⚠️ Approximate (unrefereeable: the pinned artifact answers a `CalculationUsage` node rather than an output value. `%instances` and export show the features valued from outputs, not the usage's outputs themselves) | | A calc definition, a calc usage with an unsupplied input, or an `in calc` parameter named where a value is expected is a **function value** (KerML 1.1 §7.4.4: a Function is a Behavior with a `result`, an Expression a Step typed by one, and a feature reference to either denotes it; §8.3.4.8 `Function::result`, `FeatureReferenceExpression`; SysML v2 §7.17: a calc def is a Function, a calc usage an Expression). The value is the calc's lowered invocation interface (`calcShape`) together with the environment it was read in — its declaring scope and the object it was read off — and nothing else: no statement closure is built, and the value is invoked through the same path a calc usage invocation takes (`invokeCalcShapeIn`). Reading a calc usage whose inputs are all bound evaluates it as before; a library function the runtime implements natively (`RealFunctions::sqrt`, `floor`) reads as a value carrying that implementation, while a library operation that binds its arguments unevaluated (`SequenceFunctions::size` and the other `->` operations) is refused as `ErrNotAFunction` | `runtime/value.go` `ValFunction`, `runtime/function_value.go` `functionValue`/`EvalContext.functionValueOf`/`Context.readsAsFunction`/`EvalContext.calcAsValue`, `invoke_calc.go` `calcShapeOf` (a natively implemented library function computes), `eval.go` `evalFeatureReference`, `describe.go` (`the function Sq`), `trace.go` `FormatTraceValue` (`calc(Sq)`), `repl/meta.go` | `function_value_read.sysml`, `function_value_probe.sysml` (`Fn(Sq, 3.0)` is `9.0`), `function_value_calc_usage.sysml`, `function_value_library.sysml`, `robustness_test.go:function_value_of_a_built_in`, `value_kinds_test.go:TestFunctionValueIdentity`, `:TestEveryValueKindIsDispatched`, `eval_no_value_test.go`, `repl/evalin_test.go` | ✅ Faithful | | An `in calc` parameter of a calc or an action (SysML v2 §7.17, §8.3.16 `CalculationUsage` as a parameter) accepts a function value or null and nothing else, positionally or by name; the body invokes it as `f(a)`, through a chain (`p.f(a)`), nested (`f(f(a))`) and as an argument to another calc-typed parameter, binding the callee's inputs positionally and by name as a direct invocation does. A calc usage bound as an action input (`in f = sq;`) is the function value it reads as | `runtime/invoke_calc.go` `calcParameter.checkFunction`, `function_value.go` `EvalContext.invokeFunction`, `eval.go` `evalInvocation`/`evalFeatureChain`, `parser/behavior.go` `parameterKindKeywords` (`calc`) | `function_value_probe.sysml` + `function_value_probe.trace.golden` (`TestExecutionTrace`), `function_value_named_args.sysml`, `function_value_chain_call.sysml`, `function_value_action_parameter.sysml`, parser golden `action_calc_parameter.sysml`, `robustness_test.go:function_value_call_of_a_non_function` (`ErrNotAFunction`), `:function_value_bound_to_a_non_function` (`ErrNotAFunction`), `:function_value_arity_mismatch` (`ErrCalcArity`), `testCalcUnboundParameter` (`ErrUnboundParameter`) | ✅ Faithful | +| A call through a feature chain, `holder.scale(3.0)` (KerMLExpressions `InstantiatedTypeMember` → `OwnedFeatureChain`), is checked statically as a direct call is: the chain names the calc feature applied, its arguments are held to that feature's effective inputs positionally and by name (a wrong type, an unknown name, too many arguments, an unbound default-less input), a chain to a non-behavior is refused, and the call is typed by the calc's result, so binding it to an incompatible declared type is reported | `passes/typecheck_expr.go` `inferChainInvocation`, `passes/invocation.go` `chainCallee`/`invocationArgs`, `passes/typecheck_value.go` `invocationResultParameter` | `passes/typecheck_expr_test.go:TestExprChainInvocationChecked` | ✅ Faithful | | A calc read off an object (`twice.scale`, a calc usage owned by a part) is a function value over that object: invoked later it reads that object's features, so the same calc read off two parts is two values. A calc declared inside a behavior body (a calc's or an action's) closes over the bindings of the run it was read in — its enclosing parameters and locals — and a function returned from a calc (`return : Unary = scale;`) keeps them after the run ends. The closure reaches only the code written inside that body: a body the nested calc inherits from a calc declared elsewhere (`calc again : RecursiveStep;`) reads none of the enclosing run's bindings, so a case performing itself as a step nests its frames no deeper than its recursion. The run closed over is a run of the behavior the calc is declared in — the innermost active invocation of that calc (or of one specializing it) or performance of that action — not whatever calc happens to be evaluating: a nested calc applied from a calc between it and its owner reads the owner's `k`, not the caller's, and one applied (`Outer::inner(2.0)`) or read (`Fn(Outer::inner, 2.0)`) while no run of its owner is active closes over nothing, so its body's `k` is unresolved rather than a same-named parameter of the caller | `function_value.go` `functionValue.self`/`.enclosing`, `EvalContext.functionValueOf` (`enclosedByBehaviorBody`); `eval.go` `EvalContext.enclosingRun`; `frame.go` `frame.runs` (a calc frame by `calcShape.qualifiedBy`, a performance frame by its scope's owner); `calc_usage.go` `runOf`, `calcShape.bodyEnclosing`/`declaredWithin`; `invoke_calc.go` `Context.invokeCalcShapeIn` | `function_value_feature_closure.sysml` (`Fn(twice.scale, 5.0) + Fn(thrice.scale, 5.0)` is `25.0`), `function_value_body_closure.sysml` (`Outer`, `Maker`, `Shadowed`), `function_value_sampled_closure.sysml`, `action_nested_calc_reads_performance.sysml`; `robustness_test.go:function_value_inherited_body_outside_the_closure`, `:function_value_nested_calc_outside_its_run`; `analysis_robustness_test.go:recursion_through_a_step` | ✅ Faithful | | Function values are equal, and key a set, by the calc together with the object it was read off (`Sq == Sq`, `twice.scale != thrice.scale`); a value closing over a behavior body's bindings is equal only to itself, since two reads of it in one run are one value and reads in two runs are not. Adoption into another runtime rebinds an ordinary function value to the same calc of the adopting model, the object it was read off adopted with it; one closing over a body's bindings is refused as a typed error (the run those bindings belonged to has ended and cannot be reconstructed) | `value_equality.go` `valueEqual`/`valueKeyFunc` (`ValFunction` arm), `adopt.go` | `value_kinds_test.go:TestFunctionValueIdentity`, `adopt_test.go:TestAdoptRebindsAFunctionValue` | ✅ Faithful | | `SampledFunctions::Sample(f, domain)` samples a user calc passed as its `in calc calculation` argument: the library's own body runs, `domainValues->collect { in x; new SamplePair(x, calculation(x)) }` invoking the function value inside the collection body, and `Range` of the result reads the samples back | the library body under `invoke_calc.go` `invokeCalcShapeIn`, `function_value.go` `EvalContext.invokeFunction` (from a collection body's frame), `collections.go` | `function_value_sampled.sysml` (`Range(Sample(Sq, (1.0, 2.0, 3.0)))` is `[1.0, 4.0, 9.0]`), `function_value_sampled_closure.sysml` (a calc read off a part, sampled) | ✅ Faithful | diff --git a/internal/core/passes/invocation.go b/internal/core/passes/invocation.go index 4ee1e44c6..5a4bd8e40 100644 --- a/internal/core/passes/invocation.go +++ b/internal/core/passes/invocation.go @@ -58,14 +58,25 @@ func (ec *exprChecker) argumentTypes(scope *symbols.Scope, e *ast.InvocationExpr return types } -// invocationArgs returns e's positional arguments, the receiver first. +// invocationArgs returns e's positional arguments, the receiver of `x->f(a)` +// first; the operand of a chain call `x.f(a)` is the calc applied, not an argument. func invocationArgs(e *ast.InvocationExpr) []ast.Node { - if e.Operand == nil { + if e.Operand == nil || chainCallee(e) != nil { return e.Args } return append([]ast.Node{e.Operand}, e.Args...) } +// chainCallee is the feature chain a call `x.f(a)` applies (KerMLExpressions +// InstantiatedTypeMember → OwnedFeatureChain), nil for `T(a)` and `x->T(a)`. +func chainCallee(e *ast.InvocationExpr) *ast.FeatureChainExpr { + if e.Type != nil { + return nil + } + chain, _ := e.Operand.(*ast.FeatureChainExpr) + return chain +} + // selectInvocation records the declaration e calls given the types of its arguments. func (ec *exprChecker) selectInvocation(scope *symbols.Scope, e *ast.InvocationExpr, argTypes argumentTypes, performs semantics.Performs) *semantics.InvocationSelection { return ec.model.SelectInvocation(scope, e, argTypes.arguments(), performs) diff --git a/internal/core/passes/typecheck_expr.go b/internal/core/passes/typecheck_expr.go index d354725eb..8b5b086cf 100644 --- a/internal/core/passes/typecheck_expr.go +++ b/internal/core/passes/typecheck_expr.go @@ -801,6 +801,9 @@ func (ec *exprChecker) inferNodeInvocation(scope *symbols.Scope, e *ast.Invocati args := invocationArgs(e) // Typed once and reused by checkArguments, so nested errors report once. argTypes := ec.argumentTypes(scope, e) + if chain := chainCallee(e); chain != nil { + return ec.inferChainInvocation(scope, e, chain, args, argTypes, node) + } if e.Type == nil { for _, arg := range e.NamedArgs { ec.infer(scope, arg.Value) @@ -868,6 +871,37 @@ func (ec *exprChecker) inferNodeInvocation(scope *symbols.Scope, e *ast.Invocati return ec.model.PrimTypeOf(ec.model.ResultParameterOf(sym)) } +// inferChainInvocation is inferNodeInvocation for `x.f(a)`: the chain names the +// calc feature applied, whose effective inputs the arguments bind. +func (ec *exprChecker) inferChainInvocation(scope *symbols.Scope, e *ast.InvocationExpr, chain *ast.FeatureChainExpr, args []ast.Node, argTypes argumentTypes, node *symbols.Symbol) semantics.PrimType { + ec.infer(scope, chain) + sym, ok := ec.resolver.ResolveTarget(scope, chain) + if !ok || sym == nil { + return semantics.PrimUnknown + } + if !ec.isInvocationBehavior(sym, map[*symbols.Symbol]bool{}) { + if ec.isDefinitelyNonBehavior(sym) { + ec.diags = append(ec.diags, Diagnostic{ + Severity: SeverityError, + Span: chain.Span(), + Message: "Must invoke a behavior or a behavioral feature", + Code: "invocation-not-behavior", + Source: "type", + }) + } + return semantics.PrimUnknown + } + if isInvocationBehaviorKind(sym.Kind) && !isBehaviorKind(sym.Kind) { + return semantics.PrimUnknown + } + params, ok := ec.effectiveInParameters(sym, node) + if !ok { + return semantics.PrimUnknown + } + ec.checkArguments(scope, invocation{e, sym, args, argTypes, params}, nil) + return ec.model.PrimTypeOf(ec.model.ResultParameterOf(sym)) +} + // reporter reports a finding about an invocation's arguments. type reporter func(span source.Span, format string, args ...any) @@ -1091,7 +1125,7 @@ func (ec *exprChecker) checkNamedArguments(scope *symbols.Scope, call invocation e, sym, args, argTypes, params := call.e, call.sym, call.args, call.argTypes, call.params // A receiver binds by position, which named arguments leave unstated; runtime/eval.go // reports the same call. - if e.Operand != nil { + if e.Operand != nil && chainCallee(e) == nil { report(e.Span(), "%s cannot be called with a receiver and named arguments", sym.Name) return } diff --git a/internal/core/passes/typecheck_expr_test.go b/internal/core/passes/typecheck_expr_test.go index 497444250..a3e04afe1 100644 --- a/internal/core/passes/typecheck_expr_test.go +++ b/internal/core/passes/typecheck_expr_test.go @@ -490,6 +490,38 @@ func TestExprInvocationReceiverWithNamedArguments(t *testing.T) { "add cannot be called with a receiver and named arguments") } +// calcHolder owns a calc feature reached through a feature chain, `holder.scale`. +const calcHolder = `part def Holder { + calc scale { in x : ScalarValues::Real; in k : ScalarValues::Real = 2.0; return : ScalarValues::Real = x * k; } +} +part holder : Holder; +` + +// A chain call `holder.scale(3.0)` applies the calc feature the chain names: its +// arguments are held to that calc's inputs and the call is typed by its result. +func TestExprChainInvocationChecked(t *testing.T) { + wantNoDiags(t, `package P { `+calcHolder+` attribute a : ScalarValues::Real = holder.scale(3.0); }`) + wantNoDiags(t, `package P { `+calcHolder+` attribute a : ScalarValues::Real = holder.scale(x = 3.0, k = 4.0); }`) + wantOneDiag(t, + `package P { `+calcHolder+` attribute a : ScalarValues::Real = holder.scale("three"); }`, + "argument 1 of scale expects Real, found String") + wantOneDiag(t, + `package P { `+calcHolder+` attribute a : ScalarValues::Real = holder.scale(x = 3.0, factor = 4.0); }`, + `scale has no parameter named "factor"`) + wantOneDiag(t, + `package P { `+calcHolder+` attribute a : ScalarValues::Real = holder.scale(1.0, 2.0, 3.0); }`, + "scale takes 2 argument(s), found 3") + wantOneWarning(t, + `package P { `+calcHolder+` attribute a : ScalarValues::Real = holder.scale(); }`, + CodeUnboundParameter, "scale leaves parameter x unbound, so the call cannot be evaluated") + wantOneDiag(t, + `package P { `+calcHolder+` attribute flag : ScalarValues::Boolean = holder.scale(3.0); }`, + "cannot bind Real value to a feature typed by Boolean") + wantOneDiag(t, + `package P { part def Box { attribute n : ScalarValues::Real; } part box : Box; attribute a : ScalarValues::Real = box.n(3.0); }`, + "Must invoke a behavior or a behavioral feature") +} + func TestExprInvocationNamedArgumentsOK(t *testing.T) { wantNoDiags(t, `package P { `+calcAdd+` calc c { add(a = 1, b = 2) } }`) wantNoDiags(t, `package P { `+calcAdd+` calc c { add(b = 2, a = 1) } }`) diff --git a/internal/core/passes/typecheck_value.go b/internal/core/passes/typecheck_value.go index 58303a539..e400297b5 100644 --- a/internal/core/passes/typecheck_value.go +++ b/internal/core/passes/typecheck_value.go @@ -327,10 +327,15 @@ func (ec *exprChecker) invocationResultTypeSymbol(scope *symbols.Scope, value as // invocation names; nil for any other value or an unresolved invocation. func (ec *exprChecker) invocationResultParameter(scope *symbols.Scope, value ast.Node) *symbols.Symbol { inv, ok := value.(*ast.InvocationExpr) - if !ok || inv.Type == nil { + if !ok { return nil } - sym := SelectInvocation(ec.resolver, ec.model, scope, inv, ec.performs(inv)).Selected + var sym *symbols.Symbol + if chain := chainCallee(inv); chain != nil { + sym, _ = ec.resolver.ResolveTarget(scope, chain) + } else if inv.Type != nil { + sym = SelectInvocation(ec.resolver, ec.model, scope, inv, ec.performs(inv)).Selected + } if sym == nil || !ec.isInvocationBehavior(sym, map[*symbols.Symbol]bool{}) { return nil } diff --git a/internal/core/runtime/frame.go b/internal/core/runtime/frame.go index 7aa81ba41..ccd5124bb 100644 --- a/internal/core/runtime/frame.go +++ b/internal/core/runtime/frame.go @@ -119,12 +119,19 @@ func (f frame) each(fn func(name string, value Value)) { } } -// snapshot copies the frame's bindings into storage of its own, unchanged by -// whatever later reuses the frame's. +// snapshot copies the frame's bindings, and the aliases they are read through, +// into storage of its own, unchanged by whatever later reuses the frame's. func (f frame) snapshot() frame { vars := make(map[string]Value, f.width()) f.each(func(name string, value Value) { vars[name] = value }) - return ownedFrame(f.owner, vars) + out := ownedFrame(f.owner, vars) + if len(f.aliases) > 0 { + out.aliases = make(map[string]string, len(f.aliases)) + for name, alias := range f.aliases { + out.aliases[name] = alias + } + } + return out } // width is the number of names the frame binds. diff --git a/internal/core/runtime/testdata/conformance/function_value_body_closure.expected.json b/internal/core/runtime/testdata/conformance/function_value_body_closure.expected.json index 9feea1801..69c7bf64f 100644 --- a/internal/core/runtime/testdata/conformance/function_value_body_closure.expected.json +++ b/internal/core/runtime/testdata/conformance/function_value_body_closure.expected.json @@ -2,5 +2,5 @@ "type": "calc", "evaluate": "test::UseOuter", "libraries": true, - "result": {"type": "Real", "value": 211.0} + "result": {"type": "Real", "value": 233.0} } diff --git a/internal/core/runtime/testdata/conformance/function_value_body_closure.sysml b/internal/core/runtime/testdata/conformance/function_value_body_closure.sysml index d7b20550c..7c4cc1080 100644 --- a/internal/core/runtime/testdata/conformance/function_value_body_closure.sysml +++ b/internal/core/runtime/testdata/conformance/function_value_body_closure.sysml @@ -31,5 +31,14 @@ package test { calc wrap { in k : Real; return : Real = inner(1.0) + Fn(inner, 1.0) + k; } return : Real = wrap(100.0); } - calc def UseOuter { return : Real = Outer(3.0) + Outer(4.0) + Bare(5.0) + Fn(Maker(10.0), 2.0) + ApplyOne(Fixer(10.0), 4.0) + Shadowed(3.0); } + // A specialization renaming the enclosing parameter still answers the nested + // calc's read of the inherited name, whether it is returned or passed on. + calc def Tripler :> Maker { in m :>> k = 3.0; } + calc def PassOn { + in k : Real; + calc inner { in x : Real; return : Real = x * k; } + return : Real = Fn(inner, 5.0); + } + calc def PassOnRenamed :> PassOn { in m :>> k; } + calc def UseOuter { return : Real = Outer(3.0) + Outer(4.0) + Bare(5.0) + Fn(Maker(10.0), 2.0) + ApplyOne(Fixer(10.0), 4.0) + Shadowed(3.0) + Fn(Tripler(), 4.0) + PassOnRenamed(2.0); } } From e2ca0578ef2cc26e73fb2f462c07357fd7f5d664 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 8 Sep 2026 05:19:07 +0000 Subject: [PATCH 09/11] fix(runtime): keep a snapshot's action run and resolve chain-call holders A frame snapshot copied an action performance's bindings but not which action it was a run of, so a calc captured from an action body could no longer find the performance when it applied a second body-local calc. The snapshot now records the action it was copied from (frame.performed), which frame.runs answers for as it does for the live performance. The holder analysis prepended every invocation operand to the positional arguments, so a call through a feature chain (picker.pick(lead, trail)) was read with the chain as its first argument and its callee unresolved. It now resolves the chain to the calc applied and maps the written arguments against that calc's returned parameters, sharing ChainCallee/InvocationArgs with the checker. Co-Authored-By: jason.han --- changes/unreleased/function-values.added.md | 2 +- docs/project/spec-compliance.md | 4 +- internal/core/passes/invocation.go | 12 ++--- internal/core/passes/typecheck_expr.go | 6 +-- internal/core/passes/typecheck_value.go | 2 +- internal/core/runtime/classify_test.go | 44 +++++++++++++++++++ internal/core/runtime/eval.go | 19 ++++---- internal/core/runtime/frame.go | 22 ++++++++-- internal/core/runtime/holders.go | 26 +++++++++-- ...unction_value_action_closure.expected.json | 9 ++++ .../function_value_action_closure.sysml | 30 +++++++++++++ .../function_value_chain_holder.expected.json | 15 +++++++ .../function_value_chain_holder.sysml | 18 ++++++++ 13 files changed, 177 insertions(+), 32 deletions(-) create mode 100644 internal/core/runtime/testdata/conformance/function_value_action_closure.expected.json create mode 100644 internal/core/runtime/testdata/conformance/function_value_action_closure.sysml create mode 100644 internal/core/runtime/testdata/conformance/function_value_chain_holder.expected.json create mode 100644 internal/core/runtime/testdata/conformance/function_value_chain_holder.sysml diff --git a/changes/unreleased/function-values.added.md b/changes/unreleased/function-values.added.md index 0d89ce876..68f734729 100644 --- a/changes/unreleased/function-values.added.md +++ b/changes/unreleased/function-values.added.md @@ -1,2 +1,2 @@ -- **A calc is a value.** A calc definition, a calc usage awaiting an input, or an `in calc` parameter named where a value is expected is a function value: the calc together with the scope and object it was read in, invoked through a calc-typed parameter (`calc def Fn { in calc f { in v : Real; return : Real; } in a : Real; return : Real = f(a); }` makes `Fn(Sq, 3.0)` `9.0`), passed positionally or by name, read off a part, returned from a calc, compared and adopted. `SampledFunctions::Sample` now samples a user calc, and a library function the runtime implements (`RealFunctions::sqrt`) is a value too. A calc declared in a behavior body closes over the innermost active run of that behavior alone — never a caller's parameters, and nothing when no such run is active. Calling a non-function, an arity mismatch and an unbound calc parameter are typed errors; `in calc` parameters parse in action bodies as they do in calc bodies. A call through a feature chain (`holder.scale(3.0)`) is now checked statically as a direct call is — arguments against the calc feature's inputs, the result against the declared type it binds to. +- **A calc is a value.** A calc definition, a calc usage awaiting an input, or an `in calc` parameter named where a value is expected is a function value: the calc together with the scope and object it was read in, invoked through a calc-typed parameter (`calc def Fn { in calc f { in v : Real; return : Real; } in a : Real; return : Real = f(a); }` makes `Fn(Sq, 3.0)` `9.0`), passed positionally or by name, read off a part, returned from a calc, compared and adopted. `SampledFunctions::Sample` now samples a user calc, and a library function the runtime implements (`RealFunctions::sqrt`) is a value too. A calc declared in a behavior body closes over the innermost active run of that behavior alone — never a caller's parameters, and nothing when no such run is active. Calling a non-function, an arity mismatch and an unbound calc parameter are typed errors; `in calc` parameters parse in action bodies as they do in calc bodies. A call through a feature chain (`holder.scale(3.0)`) is now checked statically as a direct call is — arguments against the calc feature's inputs, the result against the declared type it binds to — and a typed feature valued by such a call (`item t : Tallied = picker.pick(lead, trail)`) classifies the argument the calc returns as one valued by a direct call does. - **Function values cross the API.** `Value.function` carries the calc's qualified name and the id of the object it was read off, under the new `function_values` capability, which the Go, Python, Node, Rust and Java clients expose as a typed value and refuse to send to a service without the capability. A function closing over a behavior body's bindings crosses as an unsupported null, since no name reconstructs it; one read off an object is refused as an argument to a later call, since that object lived only within the response that sent it. Native compilation refuses a calc that binds or applies a function value with a typed error. diff --git a/docs/project/spec-compliance.md b/docs/project/spec-compliance.md index af2391894..8f0e7b900 100644 --- a/docs/project/spec-compliance.md +++ b/docs/project/spec-compliance.md @@ -228,7 +228,7 @@ Each row documents one behavioral semantic feature: | A calc definition, a calc usage with an unsupplied input, or an `in calc` parameter named where a value is expected is a **function value** (KerML 1.1 §7.4.4: a Function is a Behavior with a `result`, an Expression a Step typed by one, and a feature reference to either denotes it; §8.3.4.8 `Function::result`, `FeatureReferenceExpression`; SysML v2 §7.17: a calc def is a Function, a calc usage an Expression). The value is the calc's lowered invocation interface (`calcShape`) together with the environment it was read in — its declaring scope and the object it was read off — and nothing else: no statement closure is built, and the value is invoked through the same path a calc usage invocation takes (`invokeCalcShapeIn`). Reading a calc usage whose inputs are all bound evaluates it as before; a library function the runtime implements natively (`RealFunctions::sqrt`, `floor`) reads as a value carrying that implementation, while a library operation that binds its arguments unevaluated (`SequenceFunctions::size` and the other `->` operations) is refused as `ErrNotAFunction` | `runtime/value.go` `ValFunction`, `runtime/function_value.go` `functionValue`/`EvalContext.functionValueOf`/`Context.readsAsFunction`/`EvalContext.calcAsValue`, `invoke_calc.go` `calcShapeOf` (a natively implemented library function computes), `eval.go` `evalFeatureReference`, `describe.go` (`the function Sq`), `trace.go` `FormatTraceValue` (`calc(Sq)`), `repl/meta.go` | `function_value_read.sysml`, `function_value_probe.sysml` (`Fn(Sq, 3.0)` is `9.0`), `function_value_calc_usage.sysml`, `function_value_library.sysml`, `robustness_test.go:function_value_of_a_built_in`, `value_kinds_test.go:TestFunctionValueIdentity`, `:TestEveryValueKindIsDispatched`, `eval_no_value_test.go`, `repl/evalin_test.go` | ✅ Faithful | | An `in calc` parameter of a calc or an action (SysML v2 §7.17, §8.3.16 `CalculationUsage` as a parameter) accepts a function value or null and nothing else, positionally or by name; the body invokes it as `f(a)`, through a chain (`p.f(a)`), nested (`f(f(a))`) and as an argument to another calc-typed parameter, binding the callee's inputs positionally and by name as a direct invocation does. A calc usage bound as an action input (`in f = sq;`) is the function value it reads as | `runtime/invoke_calc.go` `calcParameter.checkFunction`, `function_value.go` `EvalContext.invokeFunction`, `eval.go` `evalInvocation`/`evalFeatureChain`, `parser/behavior.go` `parameterKindKeywords` (`calc`) | `function_value_probe.sysml` + `function_value_probe.trace.golden` (`TestExecutionTrace`), `function_value_named_args.sysml`, `function_value_chain_call.sysml`, `function_value_action_parameter.sysml`, parser golden `action_calc_parameter.sysml`, `robustness_test.go:function_value_call_of_a_non_function` (`ErrNotAFunction`), `:function_value_bound_to_a_non_function` (`ErrNotAFunction`), `:function_value_arity_mismatch` (`ErrCalcArity`), `testCalcUnboundParameter` (`ErrUnboundParameter`) | ✅ Faithful | | A call through a feature chain, `holder.scale(3.0)` (KerMLExpressions `InstantiatedTypeMember` → `OwnedFeatureChain`), is checked statically as a direct call is: the chain names the calc feature applied, its arguments are held to that feature's effective inputs positionally and by name (a wrong type, an unknown name, too many arguments, an unbound default-less input), a chain to a non-behavior is refused, and the call is typed by the calc's result, so binding it to an incompatible declared type is reported | `passes/typecheck_expr.go` `inferChainInvocation`, `passes/invocation.go` `chainCallee`/`invocationArgs`, `passes/typecheck_value.go` `invocationResultParameter` | `passes/typecheck_expr_test.go:TestExprChainInvocationChecked` | ✅ Faithful | -| A calc read off an object (`twice.scale`, a calc usage owned by a part) is a function value over that object: invoked later it reads that object's features, so the same calc read off two parts is two values. A calc declared inside a behavior body (a calc's or an action's) closes over the bindings of the run it was read in — its enclosing parameters and locals — and a function returned from a calc (`return : Unary = scale;`) keeps them after the run ends. The closure reaches only the code written inside that body: a body the nested calc inherits from a calc declared elsewhere (`calc again : RecursiveStep;`) reads none of the enclosing run's bindings, so a case performing itself as a step nests its frames no deeper than its recursion. The run closed over is a run of the behavior the calc is declared in — the innermost active invocation of that calc (or of one specializing it) or performance of that action — not whatever calc happens to be evaluating: a nested calc applied from a calc between it and its owner reads the owner's `k`, not the caller's, and one applied (`Outer::inner(2.0)`) or read (`Fn(Outer::inner, 2.0)`) while no run of its owner is active closes over nothing, so its body's `k` is unresolved rather than a same-named parameter of the caller | `function_value.go` `functionValue.self`/`.enclosing`, `EvalContext.functionValueOf` (`enclosedByBehaviorBody`); `eval.go` `EvalContext.enclosingRun`; `frame.go` `frame.runs` (a calc frame by `calcShape.qualifiedBy`, a performance frame by its scope's owner); `calc_usage.go` `runOf`, `calcShape.bodyEnclosing`/`declaredWithin`; `invoke_calc.go` `Context.invokeCalcShapeIn` | `function_value_feature_closure.sysml` (`Fn(twice.scale, 5.0) + Fn(thrice.scale, 5.0)` is `25.0`), `function_value_body_closure.sysml` (`Outer`, `Maker`, `Shadowed`), `function_value_sampled_closure.sysml`, `action_nested_calc_reads_performance.sysml`; `robustness_test.go:function_value_inherited_body_outside_the_closure`, `:function_value_nested_calc_outside_its_run`; `analysis_robustness_test.go:recursion_through_a_step` | ✅ Faithful | +| A calc read off an object (`twice.scale`, a calc usage owned by a part) is a function value over that object: invoked later it reads that object's features, so the same calc read off two parts is two values. A calc declared inside a behavior body (a calc's or an action's) closes over the bindings of the run it was read in — its enclosing parameters and locals — and a function returned from a calc (`return : Unary = scale;`) keeps them after the run ends. The closure reaches only the code written inside that body: a body the nested calc inherits from a calc declared elsewhere (`calc again : RecursiveStep;`) reads none of the enclosing run's bindings, so a case performing itself as a step nests its frames no deeper than its recursion. The run closed over is a run of the behavior the calc is declared in — the innermost active invocation of that calc (or of one specializing it) or performance of that action — not whatever calc happens to be evaluating: a nested calc applied from a calc between it and its owner reads the owner's `k`, not the caller's, and one applied (`Outer::inner(2.0)`) or read (`Fn(Outer::inner, 2.0)`) while no run of its owner is active closes over nothing, so its body's `k` is unresolved rather than a same-named parameter of the caller | `function_value.go` `functionValue.self`/`.enclosing`, `EvalContext.functionValueOf` (`enclosedByBehaviorBody`); `eval.go` `EvalContext.enclosingRun`; `frame.go` `frame.runs` (a calc frame by `calcShape.qualifiedBy`, a performance frame by its scope's owner, a snapshot by the action it was copied from: `frame.performs`/`frame.snapshot`); `calc_usage.go` `runOf`, `calcShape.bodyEnclosing`/`declaredWithin`; `invoke_calc.go` `Context.invokeCalcShapeIn` | `function_value_feature_closure.sysml` (`Fn(twice.scale, 5.0) + Fn(thrice.scale, 5.0)` is `25.0`), `function_value_body_closure.sysml` (`Outer`, `Maker`, `Shadowed`), `function_value_action_closure.sysml` (an action-local calc passed as a function applies another that reads the performance's input and local), `function_value_sampled_closure.sysml`, `action_nested_calc_reads_performance.sysml`; `robustness_test.go:function_value_inherited_body_outside_the_closure`, `:function_value_nested_calc_outside_its_run`; `analysis_robustness_test.go:recursion_through_a_step` | ✅ Faithful | | Function values are equal, and key a set, by the calc together with the object it was read off (`Sq == Sq`, `twice.scale != thrice.scale`); a value closing over a behavior body's bindings is equal only to itself, since two reads of it in one run are one value and reads in two runs are not. Adoption into another runtime rebinds an ordinary function value to the same calc of the adopting model, the object it was read off adopted with it; one closing over a body's bindings is refused as a typed error (the run those bindings belonged to has ended and cannot be reconstructed) | `value_equality.go` `valueEqual`/`valueKeyFunc` (`ValFunction` arm), `adopt.go` | `value_kinds_test.go:TestFunctionValueIdentity`, `adopt_test.go:TestAdoptRebindsAFunctionValue` | ✅ Faithful | | `SampledFunctions::Sample(f, domain)` samples a user calc passed as its `in calc calculation` argument: the library's own body runs, `domainValues->collect { in x; new SamplePair(x, calculation(x)) }` invoking the function value inside the collection body, and `Range` of the result reads the samples back | the library body under `invoke_calc.go` `invokeCalcShapeIn`, `function_value.go` `EvalContext.invokeFunction` (from a collection body's frame), `collections.go` | `function_value_sampled.sysml` (`Range(Sample(Sq, (1.0, 2.0, 3.0)))` is `[1.0, 4.0, 9.0]`), `function_value_sampled_closure.sysml` (a calc read off a part, sampled) | ✅ Faithful | | `SampledFunctions::SamplePair` arithmetic and `SampledFunctions::interpolateLinear` on the library's own examples: reading a `SamplePair`'s `domainValue` or `rangeValue` yields the one-element sequence `[1.0]`, and `-`/`*` refuse a sequence operand (`type mismatch: operator '-' is not defined for a Real and a sequence`). The cause is the `[0..*]`-inherited member read not reducing a singleton sequence to the scalar it denotes, which is independent of function values (the same failure reproduces with no function value involved) and is not papered over here with a `SamplePair`-specific unwrap | `runtime/eval.go` `chainMemberValue`/`evalArithmetic`, `instance.go` (scalar-feature admission reduces a singleton only where the feature is declared scalar) | reproduced by `SampledFunctions::interpolateLinear` and by `s.samples#(1).domainValue - 1.0` | ❌ Not implemented (the singleton reduction of a `[0..*]`-inherited member read; the failure is a typed error, not a wrong answer) | @@ -387,7 +387,7 @@ by name is refused naming what is missing rather than approximated. | A relationship target that resolves outside the object names no feature of it, so `attribute totalmass :> ISQ::mass` specializes the library feature and contributes nothing to a same-named feature of the object; a target the object carries under its name — including one a restating declaration masks — is a feature of it, and an unqualified target the declaring scope cannot see is looked up among the object's members | `subsetting.go` `relatedFeatures`/`isFeatureOf` | `subsetting_test.go:TestSubsettingIgnoresALibraryFeatureOfTheSameName`, conformance `cubesat_mass_rollup` | ✅ Faithful | | An object carries the features the Systems, Domain and OpenSysML libraries declare for its kind — `Items::Item::voids`, `isSolid = isEmpty(voids)`, `shape :>> spaceBoundary`, `subitems`, `Parts::Part::ownedPorts`, `Requirements::RequirementCheck::subj`/`actors`/`stakeholders`/`assumptions`/`constraints`, the Geometry `ShapeItems` fields — with their defaults, derived expressions and multiplicity, masked by a model's own `:>>` like any inherited feature, while the Kernel Semantic Library frame every object restates (`Anything::self`, `Occurrence::portions`, `timeSlices`, `snapshots`, `startShot`, `endShot`, the transfer features) stays out of the shape. Which is which is the library *tier* the loader records for each library document (`Kernel Semantic`, `Kernel Data Type`, `Kernel Function`, `Systems`, `Domain`, `OpenSysML`), kept through the symbol cache, not a path the runtime inspects | `symbols/library_tier.go` `LibraryTier` (`Frame()`), `symbols/index.go` `MarkLibraryTier`/`LibraryTier`, `libs/loader.go` `TierOf`, `libs/snapshot.go` (format 3, carrying each library document's tier and text digest); `runtime/library_frame.go` `frameFeature` (a frame tier, a member a value-held type's value carries itself — `semantics/shape.go` `HeldByValue`, the library records staying objects; *Structured values* below — a directed or result parameter, or a Systems/Domain member that redefines or subsets a frame root), `runtime/shape.go` `buildFeatures`; `runtime/adopt.go` `writeShape` names a library type with the index's `LibraryIdentity` — a digest of every library document's name, tier and text — rather than expanding it, so the digest stays bounded and two contexts loaded over libraries that differ do not agree on it | `libs/library_tier_test.go` (tiers, identity through the snapshot, identity moved by an edited document), `symbols/library_tier_test.go`, `runtime/library_shape_test.go` (shape, classification, inheritance and masking, stable order, no extra objects, digest, typed error, subject/objective aliasing, redefinition through masked targets), `runtime/adopt_test.go` (`TestAdoptRefusesASameNamedLibraryTypeOfAnotherLibrary`, `TestShapeDigestExpandsALibraryOfUnknownText`), `runtime/connector_test.go:TestOptionalConnectorLinksNothingOfItsOwn`, conformance `instance_library_geometry_box`, `instance_library_item_part_features`, `instance_library_requirement_features`, `instance_library_requirement_subject_binding` (`subject v : Vehicle = car;` binds `v` and the inherited `subj`, through `shape.go` `extractDefaultValue` reading a `SubjectMember`'s binding), `repl/library_features_test.go` (`%features`, `%eval box.isSolid`, `box.voids`), `grpc/library_feature_values_test.go` | ⚠️ Approximate (the features materialize and evaluate — `box.isSolid` is `true`, `box.voids` and `box.shape` empty, `box.faces` six objects, `box.edges` their twenty-four edges, `rect.e1.length` the rectangle's own `length` — and a model inheriting nothing from these libraries keeps its shape digest. One residual: the Kernel frame roots a Systems member may restate (`Anything::self`, `Occurrence::timeSlices`, `incomingTransfers`, `outgoingTransfers`) are named in `library_frame.go` `frameRoots` rather than derived from the library, since nothing in the library marks them. The Geometry graph's own rules — a listed item classified by the collection it is listed in, a nested usage reading `Rectangle::length` from its enclosing object, `faces.edges` collected across the faces — are the five rows below. Unrefereeable: the pinned pilot artifact answers declaration nodes, not instance values, for these features) | | An optional composite feature fills to its lower bound like a collection: `part spare : Wheel[0..1]` holds no object of its own and reads as the empty sequence, holds the object a feature subsetting it holds, and an abstract feature holds only what subsets it (KerML 1.0 §7.3.3.1), so a library's `shape :>> spaceBoundary [0..1]` or `voids [0..*]` materializes no anonymous object; a required abstract feature whose contributions fall short of its lower bound is `ErrMultiplicityViolation`. A required feature holding nothing is still `ErrUninitializedFeatureValue` when read | `runtime/instance.go` `materializeFeatureValueIntrinsic`, `holdContributions`, `FeatureValue.ReadValue`; `runtime/eval.go` `selfFeatureValue`, `evalFeatureChain`, `emptyDeclaredFeature` | `runtime/optional_feature_test.go`, `runtime/library_shape_test.go:TestInheritedLibraryFeaturesMaterializeNoObjects`, conformance `instance_library_geometry_box` (`voids`, `shape`) | ⚠️ Approximate (a `[0..1]` part used to materialize one object; the lower bound is now the population as it already was for `[0..*]` and `[n..m]`. Unrefereeable: the pinned artifact answers the declaration node for a valueless optional part) | -| The values of a feature are classified by every type of that feature, and a feature value binds the feature to its value's, so an object written into a typed feature is classified by the feature's type rather than refused: `item e1 [1];` listed in `item :>> edges [4] : Line = (e1, e2, e3, e4)` makes the `e1` object a `Line` and it carries `Line`'s features (`vertices`, `length`) from then on, and its relationships too — the binding connectors, subsettings, connections and connectors a classifier declares hold for the object as its declared type's do, in type order and each once. A classifier redefining a feature the object already carries refines it: the object reads the redefinition's default, type and multiplicity, keeps what it held where the redefinition admits it (a written value, an object now classified by the narrower type) and is refused whole where it does not, while a usage's own redefinition (`item raw : Car { :>> doors default = 3; }`) is not refined by a classifier redefining what it redefines (KerML 1.0 §7.3.4.5 Redefinition). Two comparable classifiers declaring one name without redefinition (`A { attribute n : Real = 1.0; }`, `B :> A { attribute n : Real = 2.0; }`) read the declaration of the narrower type — it masks the wider type's as it does on an object created as that type (KerML 1.0 §7.3.2.1 Type Membership) — whichever classified the object first, so what the object holds under the name is settled by its types alone; a held value the governing declaration does not admit refuses the classification and the object stays as it was. A classifier's renamed redefinition of a carried feature (`grossMass :>> mass = …`) refines it the same way, and the walks over an object's feature values — `MaterializationErrors`, the nested objects a constraint's subject holds, `%features`, signal routing — cover the features every type of the object declares, the declared type's first then each classifier's in declaration order, each shared feature value once. A collection is classified whole: one object refused leaves every object of it as it was, the objects the classifications' behaviors made abandoned with them. The object a selected variant materialized is held as any object is: a typed feature holding a variation's value (`item tallied : Tallied = car.engine`) classifies that object by the feature, which keeps its identity as the variant's object. A classifier renaming a behavior the object already runs (`exhibit state fancyModes :>> modes`), or one whose behavior a running one renames, starts no second execution: the one behavior answers to every name redefinition gives it through any type of the object (KerML 1.0 §7.3.4.5), and a classifier's behavior writes the features the classifier itself declares, the performer being the object under every type of it. `istype` and `hastype` judge an object by every type of it too — the object a selected variant materialized included, and a type the object already conforms to is still recorded as a direct type when a feature of it holds the object, so `hastype` answers alike in either classification order — `hastype` when one is the stated type itself, `istype` when one conforms to it — and a name subject to them is read as any name is: a feature of the enclosing object reads that object's value, not a fresh occurrence of the declaration (KerML 1.0 §7.4.9 Classification Expressions). Object identity is kept — `rect.e1` and `rect.edges#(1)` are the one object — and a classification that cannot hold is still the write's typed error: an `Integer` into `edges : Line`, or an object of a type disjoint by specialization from the feature's, is `ErrTypeMismatch` (KerML 1.0 §7.3.4.3 Feature Typing, §7.4.11 Feature Values, §7.4.6.3 Binding Connector Declaration) | `runtime/classify.go` `Context.classify`, `canClassify`, `instanceConforms`, `comparableTypes` (comparable: neither type refuses the other along the specialization graph), `classifyHeld` (one journal over the value, every object of it, a selected variant's included), `refineFeatureValue` (a redefining or masking declaration of the classifier governs a carried value), `subsetting.go` `aliasRedefinedFeatureValuesOf` (a carried value refined by the redefinition renaming it), `outer_feature.go` `Context.FeaturesOfObject` (the features of an object under every type of it, read by `materialize.go`, `condition.go` `nestedObjects`, `signal.go`, `repl/meta.go`), `declaredBy`/`bindingsOf`/`connectionsOf`/`anonymousConnectorsOf`/`subsettingFeaturesOf`/`subsettedNamesOf` (relationships over every type of an object, read by `binding.go`, `subsetting.go`, `routing.go`, `connector.go`); `runtime/write_conformance.go` `valueConforms` (an object the feature's type can classify conforms, `Value.Object` naming a variant's object too); `runtime/classifier_behavior.go` `runsBound` (a member bound under a redefinition name runs once), `BehaviorNamed` (redefinition names over every type of the object), `namesPerformerFeature` (a body's name denotes a feature of the performer under any type of it); `runtime/eval.go` `directValueTypes` (an object's declared type then the type of each feature holding it), `valueHasType`; `runtime/instance.go` `Instance.classifiers`, `CompositeTypeOf` (an untyped occurrence usage names one object), `Context.declaresFeatures` (a redefining usage materializes the features the redefined usage's body declares); `runtime/holders.go` `holdingFeatures`/`mentionedFeatures`/`passedFeatureReferences`/`materializeHolders` (a feature whose objects another object-holding feature's value may answer as its own — listed, chosen by a condition or an index, an argument a calc's returns pass on, directly or through the locals its body declares, assigns or iterates with (`calc def choose { in x; attribute y = x; return y; }`) — any argument of a function whose body the model does not write — a body's result — is classified before its own features are read, whichever is read first; a chain's values, a condition tested, an operand computed from, an argument no return passes on and an attribute's data hold nothing; a holder that cannot materialize — too few values for its multiplicity, a type its object is refused by — holds nothing, and its error is reported when it is read, so the held feature reads alike in either order) | `runtime/classify_test.go:TestListedValueIsClassifiedByTheFeatureType`, `:TestIncomparableValueIsRefusedByTheFeatureType`, `:TestCalcLocalsPassArgumentsOnToTheReturn`, `:TestFailingHolderDoesNotFailTheFeatureItWouldHold`, `:TestRelationshipsComeFromEveryTypeOfTheObject`, `:TestRefusedClassificationUndoesWhatItsBehaviorsDid`, `:TestRefusedCollectionClassificationUndoesTheEarlierObjects`, `:TestRefusedCollectionClassificationAbandonsWhatItsBehaviorsMade`, `:TestComputedHoldingIsClassifiedWhicheverIsReadFirst`, `:TestOnlyFeaturesPassingObjectsOnHoldThem`, `:TestArgumentNotReturnedIsNotHeldByTheCall`, `:TestNarrowerClassifierRefinesTheCarriedFeatures`, `:TestSameNameFeaturesOfClassifiersReadTheNarrowerDeclaration`, `:TestObjectWalksCoverTheFeaturesClassifiersAdd`, `:TestClassifierBehaviorsWriteTheFeaturesTheClassifierAdds`, `:TestTypePredicatesSeeTheClassifiersOfAnObject`, `:TestTypePredicatesSeeTheTypesOfASelectedVariantObject`, `:TestHoldingByAWiderTypeRecordsItAsADirectType`, `:TestSelectedVariantObjectIsClassifiedByTheFeatureHoldingIt`, `runtime/classifier_behavior_test.go:TestBehaviorNamedFollowsRedefinitionByAClassifier`, conformance `instance_binding_classifies_value`, `instance_classifier_relationships` (an untyped `raw` made a `Site` sends through `Site`'s connection and reads its binding and subsetting) (model-owned `Segment`/`Loop`/`Square` mirror, `Broken` binding `3` into `edges : Segment` stays `type mismatch`), `instance_library_geometry_rectangle` (`rect.edges` four `Line`s, `rect.e1.vertices` two objects), `instance_library_geometry_triangle`, `instance_library_geometry_box` | ✅ Faithful (unrefereeable: the pinned artifact answers `rect.edges` with the four usage nodes `e1`…`e4` unevaluated and stack-overflows on `rect.e1.length`; the rule is the spec's, not the pilot's) | +| The values of a feature are classified by every type of that feature, and a feature value binds the feature to its value's, so an object written into a typed feature is classified by the feature's type rather than refused: `item e1 [1];` listed in `item :>> edges [4] : Line = (e1, e2, e3, e4)` makes the `e1` object a `Line` and it carries `Line`'s features (`vertices`, `length`) from then on, and its relationships too — the binding connectors, subsettings, connections and connectors a classifier declares hold for the object as its declared type's do, in type order and each once. A classifier redefining a feature the object already carries refines it: the object reads the redefinition's default, type and multiplicity, keeps what it held where the redefinition admits it (a written value, an object now classified by the narrower type) and is refused whole where it does not, while a usage's own redefinition (`item raw : Car { :>> doors default = 3; }`) is not refined by a classifier redefining what it redefines (KerML 1.0 §7.3.4.5 Redefinition). Two comparable classifiers declaring one name without redefinition (`A { attribute n : Real = 1.0; }`, `B :> A { attribute n : Real = 2.0; }`) read the declaration of the narrower type — it masks the wider type's as it does on an object created as that type (KerML 1.0 §7.3.2.1 Type Membership) — whichever classified the object first, so what the object holds under the name is settled by its types alone; a held value the governing declaration does not admit refuses the classification and the object stays as it was. A classifier's renamed redefinition of a carried feature (`grossMass :>> mass = …`) refines it the same way, and the walks over an object's feature values — `MaterializationErrors`, the nested objects a constraint's subject holds, `%features`, signal routing — cover the features every type of the object declares, the declared type's first then each classifier's in declaration order, each shared feature value once. A collection is classified whole: one object refused leaves every object of it as it was, the objects the classifications' behaviors made abandoned with them. The object a selected variant materialized is held as any object is: a typed feature holding a variation's value (`item tallied : Tallied = car.engine`) classifies that object by the feature, which keeps its identity as the variant's object. A classifier renaming a behavior the object already runs (`exhibit state fancyModes :>> modes`), or one whose behavior a running one renames, starts no second execution: the one behavior answers to every name redefinition gives it through any type of the object (KerML 1.0 §7.3.4.5), and a classifier's behavior writes the features the classifier itself declares, the performer being the object under every type of it. `istype` and `hastype` judge an object by every type of it too — the object a selected variant materialized included, and a type the object already conforms to is still recorded as a direct type when a feature of it holds the object, so `hastype` answers alike in either classification order — `hastype` when one is the stated type itself, `istype` when one conforms to it — and a name subject to them is read as any name is: a feature of the enclosing object reads that object's value, not a fresh occurrence of the declaration (KerML 1.0 §7.4.9 Classification Expressions). Object identity is kept — `rect.e1` and `rect.edges#(1)` are the one object — and a classification that cannot hold is still the write's typed error: an `Integer` into `edges : Line`, or an object of a type disjoint by specialization from the feature's, is `ErrTypeMismatch` (KerML 1.0 §7.3.4.3 Feature Typing, §7.4.11 Feature Values, §7.4.6.3 Binding Connector Declaration) | `runtime/classify.go` `Context.classify`, `canClassify`, `instanceConforms`, `comparableTypes` (comparable: neither type refuses the other along the specialization graph), `classifyHeld` (one journal over the value, every object of it, a selected variant's included), `refineFeatureValue` (a redefining or masking declaration of the classifier governs a carried value), `subsetting.go` `aliasRedefinedFeatureValuesOf` (a carried value refined by the redefinition renaming it), `outer_feature.go` `Context.FeaturesOfObject` (the features of an object under every type of it, read by `materialize.go`, `condition.go` `nestedObjects`, `signal.go`, `repl/meta.go`), `declaredBy`/`bindingsOf`/`connectionsOf`/`anonymousConnectorsOf`/`subsettingFeaturesOf`/`subsettedNamesOf` (relationships over every type of an object, read by `binding.go`, `subsetting.go`, `routing.go`, `connector.go`); `runtime/write_conformance.go` `valueConforms` (an object the feature's type can classify conforms, `Value.Object` naming a variant's object too); `runtime/classifier_behavior.go` `runsBound` (a member bound under a redefinition name runs once), `BehaviorNamed` (redefinition names over every type of the object), `namesPerformerFeature` (a body's name denotes a feature of the performer under any type of it); `runtime/eval.go` `directValueTypes` (an object's declared type then the type of each feature holding it), `valueHasType`; `runtime/instance.go` `Instance.classifiers`, `CompositeTypeOf` (an untyped occurrence usage names one object), `Context.declaresFeatures` (a redefining usage materializes the features the redefined usage's body declares); `runtime/holders.go` `holdingFeatures`/`mentionedFeatures`/`passedFeatureReferences`/`materializeHolders` (a feature whose objects another object-holding feature's value may answer as its own — listed, chosen by a condition or an index, an argument a calc's returns pass on, directly or through the locals its body declares, assigns or iterates with (`calc def choose { in x; attribute y = x; return y; }`), the calc a call through a feature chain applies (`picker.pickChosen(lead, trail)`) resolved from the chain, which is the callee rather than an argument (`returnedArguments`/`chainTarget`) — any argument of a function whose body the model does not write — a body's result — is classified before its own features are read, whichever is read first; a chain's values, a condition tested, an operand computed from, an argument no return passes on and an attribute's data hold nothing; a holder that cannot materialize — too few values for its multiplicity, a type its object is refused by — holds nothing, and its error is reported when it is read, so the held feature reads alike in either order) | `runtime/classify_test.go:TestListedValueIsClassifiedByTheFeatureType`, `:TestIncomparableValueIsRefusedByTheFeatureType`, `:TestCalcLocalsPassArgumentsOnToTheReturn`, `:TestFailingHolderDoesNotFailTheFeatureItWouldHold`, `:TestRelationshipsComeFromEveryTypeOfTheObject`, `:TestRefusedClassificationUndoesWhatItsBehaviorsDid`, `:TestRefusedCollectionClassificationUndoesTheEarlierObjects`, `:TestRefusedCollectionClassificationAbandonsWhatItsBehaviorsMade`, `:TestComputedHoldingIsClassifiedWhicheverIsReadFirst`, `:TestOnlyFeaturesPassingObjectsOnHoldThem`, `:TestArgumentNotReturnedIsNotHeldByTheCall`, `:TestChainCallReturnedArgumentsAreHeldByTheCall`, `:TestNarrowerClassifierRefinesTheCarriedFeatures`, `:TestSameNameFeaturesOfClassifiersReadTheNarrowerDeclaration`, `:TestObjectWalksCoverTheFeaturesClassifiersAdd`, `:TestClassifierBehaviorsWriteTheFeaturesTheClassifierAdds`, `:TestTypePredicatesSeeTheClassifiersOfAnObject`, `:TestTypePredicatesSeeTheTypesOfASelectedVariantObject`, `:TestHoldingByAWiderTypeRecordsItAsADirectType`, `:TestSelectedVariantObjectIsClassifiedByTheFeatureHoldingIt`, `runtime/classifier_behavior_test.go:TestBehaviorNamedFollowsRedefinitionByAClassifier`, conformance `instance_binding_classifies_value`, `function_value_chain_holder` (`lead`, returned by `picker.pickChosen`, is a `Tallied`; `trail` is not), `instance_classifier_relationships` (an untyped `raw` made a `Site` sends through `Site`'s connection and reads its binding and subsetting) (model-owned `Segment`/`Loop`/`Square` mirror, `Broken` binding `3` into `edges : Segment` stays `type mismatch`), `instance_library_geometry_rectangle` (`rect.edges` four `Line`s, `rect.e1.vertices` two objects), `instance_library_geometry_triangle`, `instance_library_geometry_box` | ✅ Faithful (unrefereeable: the pinned artifact answers `rect.edges` with the four usage nodes `e1`…`e4` unevaluated and stack-overflows on `rect.e1.length`; the rule is the spec's, not the pilot's) | | A qualified name naming a feature of an enclosing type, read from an expression nested in a usage of that type, is that feature of the enclosing *object*: `item :>> e1 { attribute :>> length = Rectangle::length; }` reads the rectangle's `length`, a level deeper `RectangularCuboid::length` inside `tf : Rectangle` reads the cuboid's, and `e3.length = e1.length` is the sibling feature of the same enclosing object. A qualified name that does not name a feature of an object on the evaluation's ownership chain still resolves as a declaration (a type, a calc, a library constant), and imports, aliases, visibility and a model's own shadowing are untouched, since the name is resolved first and only its referent is looked up among the enclosing objects (KerML 1.0 §7.4.9.3 Primary Expressions — a feature reference is evaluated relative to the object featuring the referenced feature; §7.2.5.2 Namespace Declaration, qualified names) | `runtime/outer_feature.go` `EvalContext.outerFeatureValue` (walks `self` and its owners, maps the resolved feature symbol to the effective feature name each object carries, reads its value), called from `runtime/eval.go` `evalNameGeneral` and `evalFeatureChain` (sibling chain heads) | `runtime/classify_test.go:TestQualifiedOuterFeatureReadsTheEnclosingObject`, conformance `instance_nested_usage_outer_feature` (model-owned `Frame::span`, sibling `bar.len`, `bar.len + Frame::gap`), `instance_library_geometry_rectangle` (`rect.e1.length` = `4 [m]`, `rect.e3.length` = `4 [m]`), `instance_library_geometry_box` (`box.tf.length` = `2 [m]`, `box.slf.length` = `1 [m]` — the side faces read `RectangularCuboid::height`) | ✅ Faithful | | A feature chain valued over a collection collects across the collection: `item :>> edges [24] = faces.edges` is every edge of every face, in face order, and a chain end that is itself a collection (`edges.vertices`) flattens the same way; a binding whose end path crosses a collection (`bind [0..*] base.edges = [0..*] be`) visits every object the path reaches. A collection valued this way is checked against the feature's multiplicity and type like any other value, and a required lower bound the named subsetting features fall short of is filled from an optional subsetting feature with spare upper bound before an anonymous object is made up for it — `CuboidOrTriangularPrism::srf : Quadrilateral [0..1]` is the sixth face of a `Box`, not an anonymous `Polygon` beside it (KerML 1.0 §7.3.4.6 Feature Chaining, §7.3.4.4 Subsetting, §7.4.12 Multiplicities) | `runtime/eval.go` `evalFeatureChain` (collection-valued heads), `runtime/binding.go` `resolveBindingLocation`, `bindingEndpointValue` (collection-valued paths), `runtime/subsetting.go` `subsettingContributions`, `fillsFromSubsetted`, `fillOptionalSubsetters`, `materializeSubsettedCollections` (guarded by `context.go` `readingSubsetted`, so mutually subsetting features stay `ErrCyclicFeatureValue`) | `runtime/classify_test.go:TestChainValueCollectsAcrossTheCollection`, `:TestOptionalSubsetterFillsTheCollection`, conformance `instance_chain_valued_subsetting` (model-owned `Panel.sides`, `corners = sides.corners`), `instance_library_geometry_box` (`box.edges` twenty-four, `box.faces` six), `instance_library_geometry_rectangle` (`rect.vertices` eight from `edges.vertices`), `robustness_test.go:mutually_subsetting_features` | ✅ Faithful (unrefereeable: the pinned artifact answers `box.edges` with the unevaluated `edges` usage node and `box.tf.length` with its `[` operator node) | | A binding connector's end multiplicities bound how many values of each end take part, so `bind [0..*] a.edges = [0..*] b` equates the two collections whole — two whole bindings of one collection agree or are `ErrBindingConflict` — while `bind [0..1] tf.edges = [0..1] tfe` links *one unspecified* value of each end: the model states that `tfe` is some edge of `tf`, not which, so reading `tfe` — and `tflv`, `tfe.length`, and `vertices`, subsetted by `tflv = tfe.vertices` — is the typed `ErrBindingEnd` naming the binding and both of its ends (`box.tfe is bound by … which makes some value of tfe a value of tf.edges without saying which value of either; the model does not state what tfe holds`) rather than a witness the runtime picked. Each partial binding is read on its own: the runtime does not solve the conjunction of several partial bindings of one feature, so a feature they would jointly pin down (`[1]` bound `[0..1]` to two collections sharing exactly one value) is reported the same way. A partial binding determines nothing by itself, so it never decides a feature bound whole as well: every binding of the feature is read, a whole binding — or a written value or default — answers whatever order the bindings are declared in, and the partial-binding error is reported only where no whole binding does. An end whose path crosses a collection (`bind [0..*] groups.items = [0..*] allItems`, `groups` multi-valued) reaches that feature on *every* object the collection holds, in the collection's order, and holds their values together: `allItems` is every group's items in group order, an end multiplicity counts those values as one sequence (`[4]` is met by two groups of two, `[5..*]` is `ErrMultiplicityViolation` over the four), and a step of the path holding a non-object, a feature the reached objects lack, a destroyed object or an unmaterialized one is `ErrBindingEnd`. The union determines none of its objects' parts: a group's `items` keeps what it holds on its own — a default, a write, or the objects it materialized — and one holding nothing of its own is the typed `ErrBindingEnd` naming the collection (`Group.shares is bound by … through every object groups holds`) rather than a partition the runtime picked, whichever end is read first. The end multiplicity is carried from the syntax to the runtime rather than re-read from the declaration, and `bind [m] a = [m] b` states `m` as the first end's multiplicity, as `binding [1] bind [m] a = [m] b` does — `bind` declares no connector for a multiplicity of its own (KerML 1.0 §7.4.6.2 Connector Declaration — end multiplicities; §7.4.6.3 Binding Connector Declaration; §7.3.4.6 Feature Chains — a chain's values are those of its last feature on every value of the preceding ones; SysML v2 §8.2.2.6 BindingConnectorAsUsage) | `ast/defusage.go` `ConnectorEnd.Multiplicity` (each of `Usage.ConnectorEnds`); `parser/defusage.go` `parseBindingEnds`, `parseConnectorEnd` (the `bind` shorthand's leading multiplicity is end 0's); `lower/binding.go` `lowerBinding`, `BindingEnd.Multiplicity`; `libs/record.go` (cache format 26); `runtime/binding.go` `resolveBindingSet`, `partialBinding`, `attemptBinding`, `resolveBindingLocations`, `bindingEndpoint.spread`, `bindingEndpointValue`, `readBindingEnds`, `heldOnItsOwn`, `UndeterminedBindingError.Across` | `parse/binding_indexed_ends.golden`, `parse/keyword_as_name.golden`, `robustness_test.go:binding_multiple_collection_contributors` (`partial` → `ErrBindingEnd`, `whole_unequal` → `ErrBindingConflict`, `whole_equal` succeeds, `whole_beside_partial` in either declaration order), `:binding_multiple_scalar_contributors`, `classify_test.go:TestBindingEndAcrossACollection` (the union in either read order, members undetermined by the union in either order, the union against its own value, end multiplicity over the union, a collection holding no object, a non-object step, a missing feature), `runtime/shape_items_limits_test.go` (`box.tfe`, `box.tfe.length`, `box.tflv`, `box.vertices`), conformance `instance_library_geometry_box` (`box.tfe`, `box.tfe.length`, `box.tflv`, `box.vertices` errors) | ⚠️ Approximate (the partial case is reported, not resolved: nothing in the spec, the library or the pinned artifact — which answers `box.vertices`, `box.tfe` and `box.tflv` with the unevaluated usage node — determines which edge each `[0..1]` binding links — the bindings of `tfe` on `tf.edges` and `ff.edges` pick one member each from disjoint collections, `MatesWith` holds for every choice and `size(edges)` counts `faces.edges` — so `box.vertices`, `box.tfe`…`box.urre` and `box.tflv`…`box.brrv` stay `ErrBindingEnd`. A rule choosing a witness would be an invention; the library's own `size(vertices) == size(edges)` is unsatisfiable beside `faces.vertices subsets vertices`, see [omg-issues.md](omg-issues.md). Solving a conjunction of partial bindings that does determine a value is not implemented) | diff --git a/internal/core/passes/invocation.go b/internal/core/passes/invocation.go index 5a4bd8e40..bedd3b68a 100644 --- a/internal/core/passes/invocation.go +++ b/internal/core/passes/invocation.go @@ -44,7 +44,7 @@ type argumentTypes struct { // argumentTypes types e's arguments once, so nested errors report once. func (ec *exprChecker) argumentTypes(scope *symbols.Scope, e *ast.InvocationExpr) argumentTypes { - args := invocationArgs(e) + args := InvocationArgs(e) types := argumentTypes{ positional: make([]semantics.Argument, len(args)), named: make([]semantics.Argument, len(e.NamedArgs)), @@ -58,18 +58,18 @@ func (ec *exprChecker) argumentTypes(scope *symbols.Scope, e *ast.InvocationExpr return types } -// invocationArgs returns e's positional arguments, the receiver of `x->f(a)` +// InvocationArgs returns e's positional arguments, the receiver of `x->f(a)` // first; the operand of a chain call `x.f(a)` is the calc applied, not an argument. -func invocationArgs(e *ast.InvocationExpr) []ast.Node { - if e.Operand == nil || chainCallee(e) != nil { +func InvocationArgs(e *ast.InvocationExpr) []ast.Node { + if e.Operand == nil || ChainCallee(e) != nil { return e.Args } return append([]ast.Node{e.Operand}, e.Args...) } -// chainCallee is the feature chain a call `x.f(a)` applies (KerMLExpressions +// ChainCallee is the feature chain a call `x.f(a)` applies (KerMLExpressions // InstantiatedTypeMember → OwnedFeatureChain), nil for `T(a)` and `x->T(a)`. -func chainCallee(e *ast.InvocationExpr) *ast.FeatureChainExpr { +func ChainCallee(e *ast.InvocationExpr) *ast.FeatureChainExpr { if e.Type != nil { return nil } diff --git a/internal/core/passes/typecheck_expr.go b/internal/core/passes/typecheck_expr.go index 8b5b086cf..74746b58d 100644 --- a/internal/core/passes/typecheck_expr.go +++ b/internal/core/passes/typecheck_expr.go @@ -798,10 +798,10 @@ func (ec *exprChecker) inferInvocation(scope *symbols.Scope, e *ast.InvocationEx // inferNodeInvocation is inferInvocation for an invocation performed by node (nil for a bare // call). func (ec *exprChecker) inferNodeInvocation(scope *symbols.Scope, e *ast.InvocationExpr, node *symbols.Symbol) semantics.PrimType { - args := invocationArgs(e) + args := InvocationArgs(e) // Typed once and reused by checkArguments, so nested errors report once. argTypes := ec.argumentTypes(scope, e) - if chain := chainCallee(e); chain != nil { + if chain := ChainCallee(e); chain != nil { return ec.inferChainInvocation(scope, e, chain, args, argTypes, node) } if e.Type == nil { @@ -1125,7 +1125,7 @@ func (ec *exprChecker) checkNamedArguments(scope *symbols.Scope, call invocation e, sym, args, argTypes, params := call.e, call.sym, call.args, call.argTypes, call.params // A receiver binds by position, which named arguments leave unstated; runtime/eval.go // reports the same call. - if e.Operand != nil && chainCallee(e) == nil { + if e.Operand != nil && ChainCallee(e) == nil { report(e.Span(), "%s cannot be called with a receiver and named arguments", sym.Name) return } diff --git a/internal/core/passes/typecheck_value.go b/internal/core/passes/typecheck_value.go index e400297b5..5bd2852be 100644 --- a/internal/core/passes/typecheck_value.go +++ b/internal/core/passes/typecheck_value.go @@ -331,7 +331,7 @@ func (ec *exprChecker) invocationResultParameter(scope *symbols.Scope, value ast return nil } var sym *symbols.Symbol - if chain := chainCallee(inv); chain != nil { + if chain := ChainCallee(inv); chain != nil { sym, _ = ec.resolver.ResolveTarget(scope, chain) } else if inv.Type != nil { sym = SelectInvocation(ec.resolver, ec.model, scope, inv, ec.performs(inv)).Selected diff --git a/internal/core/runtime/classify_test.go b/internal/core/runtime/classify_test.go index 4c815c665..4b93e3b7a 100644 --- a/internal/core/runtime/classify_test.go +++ b/internal/core/runtime/classify_test.go @@ -221,6 +221,50 @@ func TestArgumentNotReturnedIsNotHeldByTheCall(t *testing.T) { } } +// A call through a feature chain, `picker.pickChosen(lead, trail)`, applies the calc the +// chain denotes: the feature it values holds the arguments that calc's returns pass on, +// and the chain itself is the callee, not an argument. +func TestChainCallReturnedArgumentsAreHeldByTheCall(t *testing.T) { + ctx, idx := libraryShapeContext(t, `package test { + private import ScalarValues::*; + item def Tallied { attribute tally : Integer = 7; } + item def Picker { + calc pickChosen { in chosen; in other; return : Anything = chosen; } + calc pickOther { in chosen; in other; return : Anything = other; } + } + item def Rack { + item picker : Picker; + item lead [1]; + item trail [1]; + item spare [1]; + item tallied : Tallied [1] = picker.pickChosen(lead, trail); + item named : Tallied [1] = picker.pickOther(other = spare, chosen = trail); + } + item rack : Rack; + }`) + rack := instantiateQualified(t, ctx, idx, "test::rack") + tallied := idx.LookupQualified("test::Tallied")[0] + + trail := readInstance(t, ctx, rack, "trail") + if ctx.instanceConforms(trail, tallied) { + t.Fatal("trail, which neither call returns, is a Tallied") + } + if rack.FeatureValues["tallied"].Materialized || rack.FeatureValues["named"].Materialized { + t.Fatal("reading trail computed a call that does not hold it") + } + lead := readInstance(t, ctx, rack, "lead") + if !ctx.instanceConforms(lead, tallied) { + t.Fatalf("lead, read first, is classified by %v, want Tallied", lead.classifiers) + } + spare := readInstance(t, ctx, rack, "spare") + if !ctx.instanceConforms(spare, tallied) { + t.Fatalf("spare, read first, is classified by %v, want Tallied", spare.classifiers) + } + if picker := readInstance(t, ctx, rack, "picker"); ctx.instanceConforms(picker, tallied) { + t.Fatal("picker, the callee's holder, is a Tallied") + } +} + // A holder that cannot materialize holds nothing: the held feature reads alike in either // order, and the holder's own error is reported when the holder is read. func TestFailingHolderDoesNotFailTheFeatureItWouldHold(t *testing.T) { diff --git a/internal/core/runtime/eval.go b/internal/core/runtime/eval.go index 32f727cd4..7cef1be95 100644 --- a/internal/core/runtime/eval.go +++ b/internal/core/runtime/eval.go @@ -2356,8 +2356,8 @@ func (ec *EvalContext) unresolvedInvocation(qn *ast.QualifiedName, written strin // evalInvocation evaluates a function/calc invocation. func (ec *EvalContext) evalInvocation(n *ast.InvocationExpr) (Value, error) { // `holder.f(a)`: the chain names the function applied, not the callee's type. - if n.Type == nil && n.Operand != nil { - return ec.evalChainInvocation(n) + if chain := passes.ChainCallee(n); chain != nil { + return ec.evalChainInvocation(n, chain) } target := ec.invocationTarget(n) qualName := target.qualName @@ -2376,12 +2376,9 @@ func (ec *EvalContext) evalInvocation(n *ast.InvocationExpr) (Value, error) { // Eval args in source order. An operand is the first argument of the // invocation it is written before: `seq->size()` invokes size with seq, which - // is how the semantics layer reads the same expression (passes/ - // typecheck_expr.go), so the two agree on which parameter an argument binds. - exprs := n.Args - if n.Operand != nil { - exprs = append([]ast.Node{n.Operand}, n.Args...) - } + // is how the semantics layer reads the same expression, so the two agree on + // which parameter an argument binds. + exprs := passes.InvocationArgs(n) // A calc-typed feature bound to a function value here — a parameter given a // calc as its argument — applies that value, not the feature's own declaration. if fn, ok, err := ec.boundFunction(target.calc, n.Type); ok { @@ -2439,9 +2436,9 @@ func (ec *EvalContext) enclosingRun(shape *calcShape) []frame { // evalChainInvocation applies the function value a feature chain denotes to the // arguments written after it (KerMLExpressions InstantiatedTypeMember → OwnedFeatureChain). -func (ec *EvalContext) evalChainInvocation(n *ast.InvocationExpr) (Value, error) { - callee := chainText(n.Operand) - fn, err := ec.Eval(n.Operand) +func (ec *EvalContext) evalChainInvocation(n *ast.InvocationExpr, chain *ast.FeatureChainExpr) (Value, error) { + callee := chainText(chain) + fn, err := ec.Eval(chain) if err != nil { return Value{}, err } diff --git a/internal/core/runtime/frame.go b/internal/core/runtime/frame.go index ccd5124bb..e631a62dd 100644 --- a/internal/core/runtime/frame.go +++ b/internal/core/runtime/frame.go @@ -16,6 +16,9 @@ type frame struct { // owner is the calc whose parameters, locals and outputs the frame binds, so a // qualified name of one of its members (`MassCase::result`) reads the binding. owner *calcShape + // performed is the action whose performance a snapshot copied its bindings from, + // so the copy still answers for a run of that action without the live perf. + performed *symbols.Symbol } // canonical is the name aliases bind name under: its redefinition's, else its own. @@ -61,16 +64,25 @@ func (f frame) runs(ctx *Context, behavior *symbols.Symbol) bool { if f.owner != nil { return f.owner.qualifiedBy(ctx, behavior) } - if f.perf != nil && f.perf.scope != nil { - return ctx.isOrSpecializes(f.perf.scope.Owner(), behavior) + if performed := f.performs(); performed != nil { + return ctx.isOrSpecializes(performed, behavior) } return false } +// performs is the action the frame holds a performance of: the live one's, or +// the one a snapshot copied; nil for a frame of a calc run or of plain bindings. +func (f frame) performs() *symbols.Symbol { + if f.perf != nil && f.perf.scope != nil { + return f.perf.scope.Owner() + } + return f.performed +} + // withVars is the frame holding vars in place of its own, still answering for // the same run and performance. func (f frame) withVars(vars map[string]Value) frame { - return frame{vars: vars, aliases: f.aliases, perf: f.perf, owner: f.owner} + return frame{vars: vars, aliases: f.aliases, perf: f.perf, owner: f.owner, performed: f.performed} } // lookup finds name in the frame: a slot binding it, else the map. @@ -120,11 +132,13 @@ func (f frame) each(fn func(name string, value Value)) { } // snapshot copies the frame's bindings, and the aliases they are read through, -// into storage of its own, unchanged by whatever later reuses the frame's. +// into storage of its own, unchanged by whatever later reuses the frame's. The +// copy still answers for the run it was taken from, though not for its flow's nodes. func (f frame) snapshot() frame { vars := make(map[string]Value, f.width()) f.each(func(name string, value Value) { vars[name] = value }) out := ownedFrame(f.owner, vars) + out.performed = f.performs() if len(f.aliases) > 0 { out.aliases = make(map[string]string, len(f.aliases)) for name, alias := range f.aliases { diff --git a/internal/core/runtime/holders.go b/internal/core/runtime/holders.go index 0c93afefb..02feac1f2 100644 --- a/internal/core/runtime/holders.go +++ b/internal/core/runtime/holders.go @@ -5,6 +5,7 @@ import ( "github.com/Open-MBEE/OpenSysML/internal/core/ast" "github.com/Open-MBEE/OpenSysML/internal/core/lower" + "github.com/Open-MBEE/OpenSysML/internal/core/passes" "github.com/Open-MBEE/OpenSysML/internal/core/symbols" ) @@ -214,11 +215,13 @@ func (ctx *Context) bodyReferences(scope *symbols.Scope, body *ast.BodyExpr, ope // to parameters a calc's returns pass on; every one for a function whose body is not // written in the model; none for a call that denotes nothing or computes no result. func (ctx *Context) returnedArguments(scope *symbols.Scope, call *ast.InvocationExpr) []ast.Node { - target := NewEvalContext(ctx, scope).invocationTarget(call) - positional := call.Args - if call.Operand != nil { - positional = append([]ast.Node{call.Operand}, call.Args...) + var target *invocationTarget + if chain := passes.ChainCallee(call); chain != nil { + target = ctx.chainTarget(scope, chain, call.NamedArgs) + } else { + target = NewEvalContext(ctx, scope).invocationTarget(call) } + positional := passes.InvocationArgs(call) var args []ast.Node switch { case target.shape != nil: @@ -242,6 +245,21 @@ func (ctx *Context) returnedArguments(scope *symbols.Scope, call *ast.Invocation return args } +// chainTarget is how a call `x.f(a)` is applied as far as the model states it: by the +// shape of the calc feature the chain denotes, which the named arguments bind parameters of. +func (ctx *Context) chainTarget(scope *symbols.Scope, chain *ast.FeatureChainExpr, named []ast.NamedArg) *invocationTarget { + target := &invocationTarget{qualName: chainText(chain)} + if sym, ok := ctx.resolver.ResolveTarget(scope, chain); ok && sym != nil { + if shape, err := ctx.calcShapeOf(sym); err == nil { + target.calc, target.shape = sym, shape + } + } + if len(named) > 0 { + target.names, target.unbound = ctx.boundParameterNames(scope, target.calc, named) + } + return target +} + // returnedAnalysis is the parameters a calc shape's returns pass on, as far as known. // Shapes calling each other are analysed together: each is provisional until the first // of them entered — the root of the cycle — is stable, when all of them are final. diff --git a/internal/core/runtime/testdata/conformance/function_value_action_closure.expected.json b/internal/core/runtime/testdata/conformance/function_value_action_closure.expected.json new file mode 100644 index 000000000..4dbb7205a --- /dev/null +++ b/internal/core/runtime/testdata/conformance/function_value_action_closure.expected.json @@ -0,0 +1,9 @@ +{ + "type": "action", + "evaluate": "test::outer", + "libraries": true, + "outputs": { + "result": {"type": "Real", "value": 150.0}, + "scale.y": {"type": "Real", "value": 150.0} + } +} diff --git a/internal/core/runtime/testdata/conformance/function_value_action_closure.sysml b/internal/core/runtime/testdata/conformance/function_value_action_closure.sysml new file mode 100644 index 000000000..e47e20e1e --- /dev/null +++ b/internal/core/runtime/testdata/conformance/function_value_action_closure.sysml @@ -0,0 +1,30 @@ +package test { + private import ScalarValues::*; + + calc def Unary { in x : Real; return : Real; } + calc def Fn { in calc f { in x : Real; return : Real; } in a : Real; return : Real = f(a); } + + // A calc declared in an action body closes over the performance it is read in; + // passed on as a function, it still reaches that performance's inputs and locals, + // and so does a second body-local calc it applies. + action def Scaler { + in k : Real; + out y : Real; + attribute offset : Real = 100.0; + + calc byK { in x : Real; return : Real = x * k; } + calc shifted { in x : Real; return : Real = byK(x) + offset; } + + first step; + action step { assign y := Fn(shifted, 3.0) + Fn(byK, 2.0); } + } + + action outer { + out attribute result : Real; + + first start; + then action scale : Scaler { in k = 10.0; } + then action fin { assign result := scale.y; } + then done; + } +} diff --git a/internal/core/runtime/testdata/conformance/function_value_chain_holder.expected.json b/internal/core/runtime/testdata/conformance/function_value_chain_holder.expected.json new file mode 100644 index 000000000..6eeaf2b12 --- /dev/null +++ b/internal/core/runtime/testdata/conformance/function_value_chain_holder.expected.json @@ -0,0 +1,15 @@ +{ + "type": "instance", + "instantiate": "test::Rack", + "libraries": true, + "slots": { + "lead.tally": {"type": "Integer", "value": 7}, + "trail.tally": {"error": "object has no such feature"} + }, + "identical": [ + ["tallied", "lead"] + ], + "distinct": [ + ["tallied", "trail"] + ] +} diff --git a/internal/core/runtime/testdata/conformance/function_value_chain_holder.sysml b/internal/core/runtime/testdata/conformance/function_value_chain_holder.sysml new file mode 100644 index 000000000..6bfaa7b60 --- /dev/null +++ b/internal/core/runtime/testdata/conformance/function_value_chain_holder.sysml @@ -0,0 +1,18 @@ +// A feature valued by a call through a feature chain holds the arguments the calc +// applied passes on: `lead`, returned by `picker.pickChosen`, is a Tallied and +// carries its features; `trail`, which the calc never returns, is not. +package test { + private import ScalarValues::*; + + item def Tallied { attribute tally : Integer = 7; } + item def Picker { + calc pickChosen { in chosen; in other; return : Anything = chosen; } + } + item def Rack { + item picker : Picker; + item lead [1]; + item trail [1]; + item tallied : Tallied [1] = picker.pickChosen(lead, trail); + } + item rack : Rack; +} From 9e158a3e62e87a28e9f0c6e08fe89d8b705d217d Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 8 Sep 2026 05:45:50 +0000 Subject: [PATCH 10/11] fix(runtime): apply an object's calc in call position whatever its inputs A chain call such as holder.scaled() now denotes the calc feature applied over the receiver's object directly, instead of first reading the chain as a value: a calc every input of which a default supplies, or one with no input, reads bare as its result and so could not be called. Co-Authored-By: jason.han --- changes/unreleased/function-values.added.md | 2 +- docs/project/spec-compliance.md | 2 +- internal/core/runtime/eval.go | 25 ++++++++++++++++- ...unction_value_chain_defaults.expected.json | 10 +++++++ .../function_value_chain_defaults.sysml | 28 +++++++++++++++++++ 5 files changed, 64 insertions(+), 3 deletions(-) create mode 100644 internal/core/runtime/testdata/conformance/function_value_chain_defaults.expected.json create mode 100644 internal/core/runtime/testdata/conformance/function_value_chain_defaults.sysml diff --git a/changes/unreleased/function-values.added.md b/changes/unreleased/function-values.added.md index 68f734729..f7235facb 100644 --- a/changes/unreleased/function-values.added.md +++ b/changes/unreleased/function-values.added.md @@ -1,2 +1,2 @@ -- **A calc is a value.** A calc definition, a calc usage awaiting an input, or an `in calc` parameter named where a value is expected is a function value: the calc together with the scope and object it was read in, invoked through a calc-typed parameter (`calc def Fn { in calc f { in v : Real; return : Real; } in a : Real; return : Real = f(a); }` makes `Fn(Sq, 3.0)` `9.0`), passed positionally or by name, read off a part, returned from a calc, compared and adopted. `SampledFunctions::Sample` now samples a user calc, and a library function the runtime implements (`RealFunctions::sqrt`) is a value too. A calc declared in a behavior body closes over the innermost active run of that behavior alone — never a caller's parameters, and nothing when no such run is active. Calling a non-function, an arity mismatch and an unbound calc parameter are typed errors; `in calc` parameters parse in action bodies as they do in calc bodies. A call through a feature chain (`holder.scale(3.0)`) is now checked statically as a direct call is — arguments against the calc feature's inputs, the result against the declared type it binds to — and a typed feature valued by such a call (`item t : Tallied = picker.pick(lead, trail)`) classifies the argument the calc returns as one valued by a direct call does. +- **A calc is a value.** A calc definition, a calc usage awaiting an input, or an `in calc` parameter named where a value is expected is a function value: the calc together with the scope and object it was read in, invoked through a calc-typed parameter (`calc def Fn { in calc f { in v : Real; return : Real; } in a : Real; return : Real = f(a); }` makes `Fn(Sq, 3.0)` `9.0`), passed positionally or by name, read off a part, returned from a calc, compared and adopted. `SampledFunctions::Sample` now samples a user calc, and a library function the runtime implements (`RealFunctions::sqrt`) is a value too. A calc declared in a behavior body closes over the innermost active run of that behavior alone — never a caller's parameters, and nothing when no such run is active. Calling a non-function, an arity mismatch and an unbound calc parameter are typed errors; `in calc` parameters parse in action bodies as they do in calc bodies. A call through a feature chain (`holder.scale(3.0)`) is now checked statically as a direct call is — arguments against the calc feature's inputs, the result against the declared type it binds to — applies the calc even when defaults supply every input or it has none (`holder.scaled()`, where the bare read `holder.scaled` computes its result), and a typed feature valued by such a call (`item t : Tallied = picker.pick(lead, trail)`) classifies the argument the calc returns as one valued by a direct call does. - **Function values cross the API.** `Value.function` carries the calc's qualified name and the id of the object it was read off, under the new `function_values` capability, which the Go, Python, Node, Rust and Java clients expose as a typed value and refuse to send to a service without the capability. A function closing over a behavior body's bindings crosses as an unsupported null, since no name reconstructs it; one read off an object is refused as an argument to a later call, since that object lived only within the response that sent it. Native compilation refuses a calc that binds or applies a function value with a typed error. diff --git a/docs/project/spec-compliance.md b/docs/project/spec-compliance.md index 8f0e7b900..598233f41 100644 --- a/docs/project/spec-compliance.md +++ b/docs/project/spec-compliance.md @@ -227,7 +227,7 @@ Each row documents one behavioral semantic feature: | A calc usage's outputs are evaluation results, not feature values of an object | `runtime/calc_usage.go` (no instance materialization) | `calc_usage_instance_slots.sysml` (the features fed by the outputs are feature values; the usage itself is not), pilot-exec-diff `w6d:calc-usage` | ⚠️ Approximate (unrefereeable: the pinned artifact answers a `CalculationUsage` node rather than an output value. `%instances` and export show the features valued from outputs, not the usage's outputs themselves) | | A calc definition, a calc usage with an unsupplied input, or an `in calc` parameter named where a value is expected is a **function value** (KerML 1.1 §7.4.4: a Function is a Behavior with a `result`, an Expression a Step typed by one, and a feature reference to either denotes it; §8.3.4.8 `Function::result`, `FeatureReferenceExpression`; SysML v2 §7.17: a calc def is a Function, a calc usage an Expression). The value is the calc's lowered invocation interface (`calcShape`) together with the environment it was read in — its declaring scope and the object it was read off — and nothing else: no statement closure is built, and the value is invoked through the same path a calc usage invocation takes (`invokeCalcShapeIn`). Reading a calc usage whose inputs are all bound evaluates it as before; a library function the runtime implements natively (`RealFunctions::sqrt`, `floor`) reads as a value carrying that implementation, while a library operation that binds its arguments unevaluated (`SequenceFunctions::size` and the other `->` operations) is refused as `ErrNotAFunction` | `runtime/value.go` `ValFunction`, `runtime/function_value.go` `functionValue`/`EvalContext.functionValueOf`/`Context.readsAsFunction`/`EvalContext.calcAsValue`, `invoke_calc.go` `calcShapeOf` (a natively implemented library function computes), `eval.go` `evalFeatureReference`, `describe.go` (`the function Sq`), `trace.go` `FormatTraceValue` (`calc(Sq)`), `repl/meta.go` | `function_value_read.sysml`, `function_value_probe.sysml` (`Fn(Sq, 3.0)` is `9.0`), `function_value_calc_usage.sysml`, `function_value_library.sysml`, `robustness_test.go:function_value_of_a_built_in`, `value_kinds_test.go:TestFunctionValueIdentity`, `:TestEveryValueKindIsDispatched`, `eval_no_value_test.go`, `repl/evalin_test.go` | ✅ Faithful | | An `in calc` parameter of a calc or an action (SysML v2 §7.17, §8.3.16 `CalculationUsage` as a parameter) accepts a function value or null and nothing else, positionally or by name; the body invokes it as `f(a)`, through a chain (`p.f(a)`), nested (`f(f(a))`) and as an argument to another calc-typed parameter, binding the callee's inputs positionally and by name as a direct invocation does. A calc usage bound as an action input (`in f = sq;`) is the function value it reads as | `runtime/invoke_calc.go` `calcParameter.checkFunction`, `function_value.go` `EvalContext.invokeFunction`, `eval.go` `evalInvocation`/`evalFeatureChain`, `parser/behavior.go` `parameterKindKeywords` (`calc`) | `function_value_probe.sysml` + `function_value_probe.trace.golden` (`TestExecutionTrace`), `function_value_named_args.sysml`, `function_value_chain_call.sysml`, `function_value_action_parameter.sysml`, parser golden `action_calc_parameter.sysml`, `robustness_test.go:function_value_call_of_a_non_function` (`ErrNotAFunction`), `:function_value_bound_to_a_non_function` (`ErrNotAFunction`), `:function_value_arity_mismatch` (`ErrCalcArity`), `testCalcUnboundParameter` (`ErrUnboundParameter`) | ✅ Faithful | -| A call through a feature chain, `holder.scale(3.0)` (KerMLExpressions `InstantiatedTypeMember` → `OwnedFeatureChain`), is checked statically as a direct call is: the chain names the calc feature applied, its arguments are held to that feature's effective inputs positionally and by name (a wrong type, an unknown name, too many arguments, an unbound default-less input), a chain to a non-behavior is refused, and the call is typed by the calc's result, so binding it to an incompatible declared type is reported | `passes/typecheck_expr.go` `inferChainInvocation`, `passes/invocation.go` `chainCallee`/`invocationArgs`, `passes/typecheck_value.go` `invocationResultParameter` | `passes/typecheck_expr_test.go:TestExprChainInvocationChecked` | ✅ Faithful | +| A call through a feature chain, `holder.scale(3.0)` (KerMLExpressions `InstantiatedTypeMember` → `OwnedFeatureChain`), is checked statically as a direct call is: the chain names the calc feature applied, its arguments are held to that feature's effective inputs positionally and by name (a wrong type, an unknown name, too many arguments, an unbound default-less input), a chain to a non-behavior is refused, and the call is typed by the calc's result, so binding it to an incompatible declared type is reported. At run time the chain in call position denotes the calc feature applied over the receiver's object whatever its inputs — one every input of which a default supplies, or one with no input, is applied (`holder.scaled()` is `10.0`) where the bare read `holder.scaled` computes its result — and any other member is read as written and must hold a function | `passes/typecheck_expr.go` `inferChainInvocation`, `passes/invocation.go` `ChainCallee`/`InvocationArgs`, `passes/typecheck_value.go` `invocationResultParameter`; `runtime/eval.go` `evalChainInvocation`/`chainCallee` | `passes/typecheck_expr_test.go:TestExprChainInvocationChecked`; `function_value_chain_call.sysml`, `function_value_chain_defaults.sysml` | ✅ Faithful | | A calc read off an object (`twice.scale`, a calc usage owned by a part) is a function value over that object: invoked later it reads that object's features, so the same calc read off two parts is two values. A calc declared inside a behavior body (a calc's or an action's) closes over the bindings of the run it was read in — its enclosing parameters and locals — and a function returned from a calc (`return : Unary = scale;`) keeps them after the run ends. The closure reaches only the code written inside that body: a body the nested calc inherits from a calc declared elsewhere (`calc again : RecursiveStep;`) reads none of the enclosing run's bindings, so a case performing itself as a step nests its frames no deeper than its recursion. The run closed over is a run of the behavior the calc is declared in — the innermost active invocation of that calc (or of one specializing it) or performance of that action — not whatever calc happens to be evaluating: a nested calc applied from a calc between it and its owner reads the owner's `k`, not the caller's, and one applied (`Outer::inner(2.0)`) or read (`Fn(Outer::inner, 2.0)`) while no run of its owner is active closes over nothing, so its body's `k` is unresolved rather than a same-named parameter of the caller | `function_value.go` `functionValue.self`/`.enclosing`, `EvalContext.functionValueOf` (`enclosedByBehaviorBody`); `eval.go` `EvalContext.enclosingRun`; `frame.go` `frame.runs` (a calc frame by `calcShape.qualifiedBy`, a performance frame by its scope's owner, a snapshot by the action it was copied from: `frame.performs`/`frame.snapshot`); `calc_usage.go` `runOf`, `calcShape.bodyEnclosing`/`declaredWithin`; `invoke_calc.go` `Context.invokeCalcShapeIn` | `function_value_feature_closure.sysml` (`Fn(twice.scale, 5.0) + Fn(thrice.scale, 5.0)` is `25.0`), `function_value_body_closure.sysml` (`Outer`, `Maker`, `Shadowed`), `function_value_action_closure.sysml` (an action-local calc passed as a function applies another that reads the performance's input and local), `function_value_sampled_closure.sysml`, `action_nested_calc_reads_performance.sysml`; `robustness_test.go:function_value_inherited_body_outside_the_closure`, `:function_value_nested_calc_outside_its_run`; `analysis_robustness_test.go:recursion_through_a_step` | ✅ Faithful | | Function values are equal, and key a set, by the calc together with the object it was read off (`Sq == Sq`, `twice.scale != thrice.scale`); a value closing over a behavior body's bindings is equal only to itself, since two reads of it in one run are one value and reads in two runs are not. Adoption into another runtime rebinds an ordinary function value to the same calc of the adopting model, the object it was read off adopted with it; one closing over a body's bindings is refused as a typed error (the run those bindings belonged to has ended and cannot be reconstructed) | `value_equality.go` `valueEqual`/`valueKeyFunc` (`ValFunction` arm), `adopt.go` | `value_kinds_test.go:TestFunctionValueIdentity`, `adopt_test.go:TestAdoptRebindsAFunctionValue` | ✅ Faithful | | `SampledFunctions::Sample(f, domain)` samples a user calc passed as its `in calc calculation` argument: the library's own body runs, `domainValues->collect { in x; new SamplePair(x, calculation(x)) }` invoking the function value inside the collection body, and `Range` of the result reads the samples back | the library body under `invoke_calc.go` `invokeCalcShapeIn`, `function_value.go` `EvalContext.invokeFunction` (from a collection body's frame), `collections.go` | `function_value_sampled.sysml` (`Range(Sample(Sq, (1.0, 2.0, 3.0)))` is `[1.0, 4.0, 9.0]`), `function_value_sampled_closure.sysml` (a calc read off a part, sampled) | ✅ Faithful | diff --git a/internal/core/runtime/eval.go b/internal/core/runtime/eval.go index 7cef1be95..eca5c478f 100644 --- a/internal/core/runtime/eval.go +++ b/internal/core/runtime/eval.go @@ -2438,7 +2438,7 @@ func (ec *EvalContext) enclosingRun(shape *calcShape) []frame { // arguments written after it (KerMLExpressions InstantiatedTypeMember → OwnedFeatureChain). func (ec *EvalContext) evalChainInvocation(n *ast.InvocationExpr, chain *ast.FeatureChainExpr) (Value, error) { callee := chainText(chain) - fn, err := ec.Eval(chain) + fn, err := ec.chainCallee(chain) if err != nil { return Value{}, err } @@ -2456,6 +2456,29 @@ func (ec *EvalContext) evalChainInvocation(n *ast.InvocationExpr, chain *ast.Fea return ec.invokeFunction(callee, fn, callArgs) } +// chainCallee is what a feature chain denotes in call position: a calc of the receiver's +// object is the function applied over it even where a bare read would compute its result. +func (ec *EvalContext) chainCallee(chain *ast.FeatureChainExpr) (Value, error) { + if chain.Member == nil || len(chain.Member.Parts) != 1 { + return ec.Eval(chain) + } + receiver, err := ec.Eval(chain.Operand) + if err != nil { + return Value{}, err + } + name := chain.Member.Parts[0].Text + if id, isObject := receiver.Object(); isObject { + if inst, ok := ec.ctx.instances[id]; ok { + if _, held := inst.FeatureValues[name]; !held { + if sym, found := ec.ctx.model.LookupMember(inst.Type, name); found && isCalcUsageSymbol(sym) { + return NewEvalContextIn(ec.ctx, sym.OwnerScope, inst).functionValueOf(sym) + } + } + } + } + return ec.chainMemberValue(receiver, chain.Member.Parts, "") +} + // chainText spells a feature chain as written, `holder.scale`. func chainText(n ast.Node) string { switch c := n.(type) { diff --git a/internal/core/runtime/testdata/conformance/function_value_chain_defaults.expected.json b/internal/core/runtime/testdata/conformance/function_value_chain_defaults.expected.json new file mode 100644 index 000000000..e785fff20 --- /dev/null +++ b/internal/core/runtime/testdata/conformance/function_value_chain_defaults.expected.json @@ -0,0 +1,10 @@ +{ + "type": "instance", + "instantiate": "test::Reads", + "libraries": true, + "slots": { + "bare": {"type": "Real", "value": 10.0}, + "called": {"type": "Real", "value": 10.0}, + "total": {"type": "Real", "value": 48.0} + } +} diff --git a/internal/core/runtime/testdata/conformance/function_value_chain_defaults.sysml b/internal/core/runtime/testdata/conformance/function_value_chain_defaults.sysml new file mode 100644 index 000000000..23201f4b2 --- /dev/null +++ b/internal/core/runtime/testdata/conformance/function_value_chain_defaults.sysml @@ -0,0 +1,28 @@ +package test { + private import ScalarValues::*; + + part def Holder { + attribute k : Real = 2.0; + calc scaled { in x : Real = 5.0; return : Real = x * k; } + calc squared { return : Real = k * k; } + } + part def Rig { + part holder : Holder; + part other : Holder { attribute :>> k = 3.0; } + } + part rig : Rig; + + // A calc every input of which a default supplies, or that has no input, is + // still applied when written in call position: `holder.scaled()` is 10.0, not + // the bare read `holder.scaled`. + calc def UseDefaults { + return : Real = rig.holder.scaled() + rig.holder.scaled(4.0) + rig.holder.scaled(x = 1.0) + + rig.holder.squared() + rig.other.squared() + rig.other.scaled(); + } + // 10 + 8 + 2 + 4 + 9 + 15 + part def Reads { + attribute bare : Real = rig.holder.scaled; + attribute called : Real = rig.holder.scaled(); + attribute total : Real = UseDefaults(); + } +} From c1ca1906978926b41914d53bb9aa63b2dedb684e Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 8 Sep 2026 06:17:21 +0000 Subject: [PATCH 11/11] fix(runtime): identify a body-closing function by the run it closes over Two reads of a calc declared in a behavior body within one run of that body are one function: valueEqual and valueKeyFunc compare the run each read closed over (a Context-numbered identity every invocation, usage evaluation and performance frame carries into its snapshots) rather than the pointer of the read, so `inner == inner` holds and a set of both reads holds one value, while functions returned by two runs stay distinct. Co-Authored-By: jason.han --- docs/project/spec-compliance.md | 2 +- internal/core/runtime/action_frame.go | 6 ++- internal/core/runtime/analysis_run.go | 2 +- internal/core/runtime/calc_statements.go | 1 + internal/core/runtime/calc_usage.go | 2 +- internal/core/runtime/context.go | 10 ++++ internal/core/runtime/eval.go | 10 ++-- internal/core/runtime/frame.go | 7 ++- internal/core/runtime/function_value.go | 10 ++++ internal/core/runtime/invoke_calc.go | 5 +- internal/core/runtime/state_statements.go | 1 + ...ction_value_closure_equality.expected.json | 11 ++++ .../function_value_closure_equality.sysml | 51 +++++++++++++++++++ internal/core/runtime/value_equality.go | 8 +-- internal/core/runtime/value_kinds_test.go | 20 ++++---- 15 files changed, 117 insertions(+), 29 deletions(-) create mode 100644 internal/core/runtime/testdata/conformance/function_value_closure_equality.expected.json create mode 100644 internal/core/runtime/testdata/conformance/function_value_closure_equality.sysml diff --git a/docs/project/spec-compliance.md b/docs/project/spec-compliance.md index 598233f41..e29934be1 100644 --- a/docs/project/spec-compliance.md +++ b/docs/project/spec-compliance.md @@ -229,7 +229,7 @@ Each row documents one behavioral semantic feature: | An `in calc` parameter of a calc or an action (SysML v2 §7.17, §8.3.16 `CalculationUsage` as a parameter) accepts a function value or null and nothing else, positionally or by name; the body invokes it as `f(a)`, through a chain (`p.f(a)`), nested (`f(f(a))`) and as an argument to another calc-typed parameter, binding the callee's inputs positionally and by name as a direct invocation does. A calc usage bound as an action input (`in f = sq;`) is the function value it reads as | `runtime/invoke_calc.go` `calcParameter.checkFunction`, `function_value.go` `EvalContext.invokeFunction`, `eval.go` `evalInvocation`/`evalFeatureChain`, `parser/behavior.go` `parameterKindKeywords` (`calc`) | `function_value_probe.sysml` + `function_value_probe.trace.golden` (`TestExecutionTrace`), `function_value_named_args.sysml`, `function_value_chain_call.sysml`, `function_value_action_parameter.sysml`, parser golden `action_calc_parameter.sysml`, `robustness_test.go:function_value_call_of_a_non_function` (`ErrNotAFunction`), `:function_value_bound_to_a_non_function` (`ErrNotAFunction`), `:function_value_arity_mismatch` (`ErrCalcArity`), `testCalcUnboundParameter` (`ErrUnboundParameter`) | ✅ Faithful | | A call through a feature chain, `holder.scale(3.0)` (KerMLExpressions `InstantiatedTypeMember` → `OwnedFeatureChain`), is checked statically as a direct call is: the chain names the calc feature applied, its arguments are held to that feature's effective inputs positionally and by name (a wrong type, an unknown name, too many arguments, an unbound default-less input), a chain to a non-behavior is refused, and the call is typed by the calc's result, so binding it to an incompatible declared type is reported. At run time the chain in call position denotes the calc feature applied over the receiver's object whatever its inputs — one every input of which a default supplies, or one with no input, is applied (`holder.scaled()` is `10.0`) where the bare read `holder.scaled` computes its result — and any other member is read as written and must hold a function | `passes/typecheck_expr.go` `inferChainInvocation`, `passes/invocation.go` `ChainCallee`/`InvocationArgs`, `passes/typecheck_value.go` `invocationResultParameter`; `runtime/eval.go` `evalChainInvocation`/`chainCallee` | `passes/typecheck_expr_test.go:TestExprChainInvocationChecked`; `function_value_chain_call.sysml`, `function_value_chain_defaults.sysml` | ✅ Faithful | | A calc read off an object (`twice.scale`, a calc usage owned by a part) is a function value over that object: invoked later it reads that object's features, so the same calc read off two parts is two values. A calc declared inside a behavior body (a calc's or an action's) closes over the bindings of the run it was read in — its enclosing parameters and locals — and a function returned from a calc (`return : Unary = scale;`) keeps them after the run ends. The closure reaches only the code written inside that body: a body the nested calc inherits from a calc declared elsewhere (`calc again : RecursiveStep;`) reads none of the enclosing run's bindings, so a case performing itself as a step nests its frames no deeper than its recursion. The run closed over is a run of the behavior the calc is declared in — the innermost active invocation of that calc (or of one specializing it) or performance of that action — not whatever calc happens to be evaluating: a nested calc applied from a calc between it and its owner reads the owner's `k`, not the caller's, and one applied (`Outer::inner(2.0)`) or read (`Fn(Outer::inner, 2.0)`) while no run of its owner is active closes over nothing, so its body's `k` is unresolved rather than a same-named parameter of the caller | `function_value.go` `functionValue.self`/`.enclosing`, `EvalContext.functionValueOf` (`enclosedByBehaviorBody`); `eval.go` `EvalContext.enclosingRun`; `frame.go` `frame.runs` (a calc frame by `calcShape.qualifiedBy`, a performance frame by its scope's owner, a snapshot by the action it was copied from: `frame.performs`/`frame.snapshot`); `calc_usage.go` `runOf`, `calcShape.bodyEnclosing`/`declaredWithin`; `invoke_calc.go` `Context.invokeCalcShapeIn` | `function_value_feature_closure.sysml` (`Fn(twice.scale, 5.0) + Fn(thrice.scale, 5.0)` is `25.0`), `function_value_body_closure.sysml` (`Outer`, `Maker`, `Shadowed`), `function_value_action_closure.sysml` (an action-local calc passed as a function applies another that reads the performance's input and local), `function_value_sampled_closure.sysml`, `action_nested_calc_reads_performance.sysml`; `robustness_test.go:function_value_inherited_body_outside_the_closure`, `:function_value_nested_calc_outside_its_run`; `analysis_robustness_test.go:recursion_through_a_step` | ✅ Faithful | -| Function values are equal, and key a set, by the calc together with the object it was read off (`Sq == Sq`, `twice.scale != thrice.scale`); a value closing over a behavior body's bindings is equal only to itself, since two reads of it in one run are one value and reads in two runs are not. Adoption into another runtime rebinds an ordinary function value to the same calc of the adopting model, the object it was read off adopted with it; one closing over a body's bindings is refused as a typed error (the run those bindings belonged to has ended and cannot be reconstructed) | `value_equality.go` `valueEqual`/`valueKeyFunc` (`ValFunction` arm), `adopt.go` | `value_kinds_test.go:TestFunctionValueIdentity`, `adopt_test.go:TestAdoptRebindsAFunctionValue` | ✅ Faithful | +| Function values are equal, and key a set, by the calc together with the object it was read off (`Sq == Sq`, `twice.scale != thrice.scale`); a value closing over a behavior body's bindings is equal to every read of the same calc within the same run of that body and to none from another run (`inner == inner` within one calc body; `Maker(2.0) != Maker(2.0)` across two invocations). Adoption into another runtime rebinds an ordinary function value to the same calc of the adopting model, the object it was read off adopted with it; one closing over a body's bindings is refused as a typed error (the run those bindings belonged to has ended and cannot be reconstructed) | `value_equality.go` `valueEqual`/`valueKeyFunc` (`ValFunction` arm), `adopt.go` | `value_kinds_test.go:TestFunctionValueIdentity`, conformance `function_value_closure_equality`, `adopt_test.go:TestAdoptRebindsAFunctionValue` | ✅ Faithful | | `SampledFunctions::Sample(f, domain)` samples a user calc passed as its `in calc calculation` argument: the library's own body runs, `domainValues->collect { in x; new SamplePair(x, calculation(x)) }` invoking the function value inside the collection body, and `Range` of the result reads the samples back | the library body under `invoke_calc.go` `invokeCalcShapeIn`, `function_value.go` `EvalContext.invokeFunction` (from a collection body's frame), `collections.go` | `function_value_sampled.sysml` (`Range(Sample(Sq, (1.0, 2.0, 3.0)))` is `[1.0, 4.0, 9.0]`), `function_value_sampled_closure.sysml` (a calc read off a part, sampled) | ✅ Faithful | | `SampledFunctions::SamplePair` arithmetic and `SampledFunctions::interpolateLinear` on the library's own examples: reading a `SamplePair`'s `domainValue` or `rangeValue` yields the one-element sequence `[1.0]`, and `-`/`*` refuse a sequence operand (`type mismatch: operator '-' is not defined for a Real and a sequence`). The cause is the `[0..*]`-inherited member read not reducing a singleton sequence to the scalar it denotes, which is independent of function values (the same failure reproduces with no function value involved) and is not papered over here with a `SamplePair`-specific unwrap | `runtime/eval.go` `chainMemberValue`/`evalArithmetic`, `instance.go` (scalar-feature admission reduces a singleton only where the feature is declared scalar) | reproduced by `SampledFunctions::interpolateLinear` and by `s.samples#(1).domainValue - 1.0` | ❌ Not implemented (the singleton reduction of a `[0..*]`-inherited member read; the failure is a typed error, not a wrong answer) | | Boolean operators evaluated at runtime (`and`, `or`, `xor`, `implies`, short-circuiting where they can) | `eval.go` `evalLogical` | `calc_boolean_operators.sysml` | ✅ Faithful | diff --git a/internal/core/runtime/action_frame.go b/internal/core/runtime/action_frame.go index d44f4a8d9..f752f6c38 100644 --- a/internal/core/runtime/action_frame.go +++ b/internal/core/runtime/action_frame.go @@ -72,6 +72,8 @@ type actionFrame struct { result string // began is the activation the performance began in, which orders performances. began int64 + // run is the identity of this performance among the context's runs (Context.newRun). + run int64 // performs is the flow of the action a typed or invoked node performed, whose // subactions the node's performance adopted as its own. nil otherwise. performs *lower.ActionGraph @@ -131,6 +133,7 @@ func (e *ActionExecutor) newRootFrame() *actionFrame { data: make(map[string]Value), features: make(map[string]ast.FeatureDirection), subactions: make(map[ast.Node]*actionFrame), + run: e.ctx.newRun(), } for _, attr := range e.graph.Attributes { root.features[attr.Name] = ast.DirNone @@ -212,6 +215,7 @@ func (e *performances) beginPerformance( connections: parent.connections, data: make(map[string]Value), features: make(map[string]ast.FeatureDirection), + run: e.ctx.newRun(), } if perf.scope == nil { perf.scope = parent.scope @@ -1114,5 +1118,5 @@ func checkInputsBound(inv actionInvocation, params []actionParameter, inputs map // performanceFrame is the frame an evaluation reads a performance's values // through, which also answers for the nodes of its flow. func performanceFrame(f *actionFrame) frame { - return frame{vars: f.data, aliases: f.aliases, perf: f} + return frame{vars: f.data, aliases: f.aliases, perf: f, run: f.run} } diff --git a/internal/core/runtime/analysis_run.go b/internal/core/runtime/analysis_run.go index 387e34870..ee3179b10 100644 --- a/internal/core/runtime/analysis_run.go +++ b/internal/core/runtime/analysis_run.go @@ -549,7 +549,7 @@ func (ctx *Context) analysisVerdict(kind, name string, check conditionCheck, con // bindingsFrame is the run's bindings as a frame the case owns, so a condition reads // its features by qualified name (`MassCase::result`) and its steps' pins (`step.out`). func (run *calcRun) bindingsFrame(ctx *Context) frame { - return frame{vars: run.bindings(ctx), perf: run.perf, owner: run.shape} + return frame{vars: run.bindings(ctx), perf: run.perf, owner: run.shape, run: run.env.run} } // bindings are the values a run bound, by name: its parameters and locals, and diff --git a/internal/core/runtime/calc_statements.go b/internal/core/runtime/calc_statements.go index e1e825176..d407c7185 100644 --- a/internal/core/runtime/calc_statements.go +++ b/internal/core/runtime/calc_statements.go @@ -36,6 +36,7 @@ func (h *calcStmtHost) attachPerformances(engine *stmtEngine) { nodes: h.shape.Nodes, label: h.shape.Label, outer: append(append([]frame{}, engine.env.enclosing...), engine.env.data), + run: h.ctx.newRun(), } h.flow = &ActionExecutor{ performances: performances{ctx: h.ctx, self: h.self, root: root, owner: h}, diff --git a/internal/core/runtime/calc_usage.go b/internal/core/runtime/calc_usage.go index 60ef3a5e0..ddb7ce350 100644 --- a/internal/core/runtime/calc_usage.go +++ b/internal/core/runtime/calc_usage.go @@ -641,7 +641,7 @@ func (ctx *Context) bindCalcUsage(shape *calcShape, reader *EvalContext, args ca ec.trace.RecordCalculationEnter(shape.Kind, shape.Name) } - env := frame{vars: make(map[string]Value, len(shape.Params)), aliases: shape.Aliases, owner: shape} + env := frame{vars: make(map[string]Value, len(shape.Params)), aliases: shape.Aliases, owner: shape, run: ctx.newRun()} ec.pushFrame(env) // A usage declared in a behavior's body is written in that body, so its own diff --git a/internal/core/runtime/context.go b/internal/core/runtime/context.go index 6051e6f63..fae72d53b 100644 --- a/internal/core/runtime/context.go +++ b/internal/core/runtime/context.go @@ -104,6 +104,9 @@ type Context struct { // activations numbers the body activations begun in this context: a calc // invocation, a block entry, a loop iteration, a body application. activations int64 + // runs numbers the behavior runs begun in this context — calc invocations, calc + // usage evaluations, action performances — which functions closing over one carry. + runs int64 // occurrences holds the object each usage carrying no value of its own // denotes, so a feature chain through a part reads one occurrence of it. @@ -622,6 +625,13 @@ func (ctx *Context) newActivation() int64 { return ctx.activations } +// newRun begins one behavior run: the identity a function closing over it carries, +// which no other run of the same behavior shares. +func (ctx *Context) newRun() int64 { + ctx.runs++ + return ctx.runs +} + // endActivation forgets what an activation computed, once it has ended, and the // activations of the calc usage evaluations it held. func (ctx *Context) endActivation(activation int64) { diff --git a/internal/core/runtime/eval.go b/internal/core/runtime/eval.go index eca5c478f..d35fe07dc 100644 --- a/internal/core/runtime/eval.go +++ b/internal/core/runtime/eval.go @@ -2620,12 +2620,10 @@ func valueEqual(a, b Value) bool { case ValCoordinateTransformation: return a.CoordinateTransformation().equal(b.CoordinateTransformation()) case ValFunction: - // A function is the calc it is a value of, read against the same object; one - // closing over a body's bindings is equal only to the same read. - if a.FunctionClosesOverBody() || b.FunctionClosesOverBody() { - return a.function() == b.function() - } - return a.Function() == b.Function() && a.FunctionSelf() == b.FunctionSelf() + // A function is the calc it is a value of, read against the same object and, for + // one closing over a body's bindings, within the same run of that body. + return a.Function() == b.Function() && a.FunctionSelf() == b.FunctionSelf() && + a.functionRun() == b.functionRun() default: return false } diff --git a/internal/core/runtime/frame.go b/internal/core/runtime/frame.go index e631a62dd..ef89eea14 100644 --- a/internal/core/runtime/frame.go +++ b/internal/core/runtime/frame.go @@ -19,6 +19,9 @@ type frame struct { // performed is the action whose performance a snapshot copied its bindings from, // so the copy still answers for a run of that action without the live perf. performed *symbols.Symbol + // run numbers the behavior run the frame binds (Context.newRun), 0 for bindings + // that are no run's; a function closing over the run is identified by it. + run int64 } // canonical is the name aliases bind name under: its redefinition's, else its own. @@ -82,7 +85,7 @@ func (f frame) performs() *symbols.Symbol { // withVars is the frame holding vars in place of its own, still answering for // the same run and performance. func (f frame) withVars(vars map[string]Value) frame { - return frame{vars: vars, aliases: f.aliases, perf: f.perf, owner: f.owner, performed: f.performed} + return frame{vars: vars, aliases: f.aliases, perf: f.perf, owner: f.owner, performed: f.performed, run: f.run} } // lookup finds name in the frame: a slot binding it, else the map. @@ -138,7 +141,7 @@ func (f frame) snapshot() frame { vars := make(map[string]Value, f.width()) f.each(func(name string, value Value) { vars[name] = value }) out := ownedFrame(f.owner, vars) - out.performed = f.performs() + out.performed, out.run = f.performs(), f.run if len(f.aliases) > 0 { out.aliases = make(map[string]string, len(f.aliases)) for name, alias := range f.aliases { diff --git a/internal/core/runtime/function_value.go b/internal/core/runtime/function_value.go index 6b7c7989b..42a7061a3 100644 --- a/internal/core/runtime/function_value.go +++ b/internal/core/runtime/function_value.go @@ -63,6 +63,16 @@ func (v Value) FunctionClosesOverBody() bool { return fn != nil && len(fn.enclosing) > 0 } +// functionRun identifies the behavior run a ValFunction closes over (frame.run), so +// every read of the calc within that run is one function; 0 for one closing over none. +func (v Value) functionRun() int64 { + fn := v.function() + if fn == nil || len(fn.enclosing) == 0 { + return 0 + } + return fn.enclosing[len(fn.enclosing)-1].run +} + // functionValueOf is the value of the calc sym denotes in this environment: its // lowered shape closed over the scope and object the read resolves against. A // library calc applied natively is the value of that implementation; one bound diff --git a/internal/core/runtime/invoke_calc.go b/internal/core/runtime/invoke_calc.go index 47a22bba2..f31583d11 100644 --- a/internal/core/runtime/invoke_calc.go +++ b/internal/core/runtime/invoke_calc.go @@ -568,6 +568,7 @@ type invocationFrame struct { bindings map[string]Value aliases map[string]string owner *calcShape // the calc invoked, whose members the locals bind + run int64 // the run this invocation is (Context.newRun) host calcStmtHost env stmtEnv engine stmtEngine @@ -575,7 +576,7 @@ type invocationFrame struct { // locals is the frame the invocation's parameters and body locals are bound in. func (f *invocationFrame) locals() frame { - return frame{slots: &f.slots, vars: f.bindings, aliases: f.aliases, owner: f.owner} + return frame{slots: &f.slots, vars: f.bindings, aliases: f.aliases, owner: f.owner, run: f.run} } // maxFreeInvocationFrames bounds the frames kept, so one deep recursion does not @@ -659,7 +660,7 @@ func (ctx *Context) invokeCalcShapeIn(shape *calcShape, args calcArgs, callerSco defer ctx.endActivation(activation) frame.slots.reset(shape.ParamNames) - frame.aliases, frame.owner = shape.Aliases, shape + frame.aliases, frame.owner, frame.run = shape.Aliases, shape, ctx.newRun() locals := frame.locals() ec := &frame.ec *ec = EvalContext{ diff --git a/internal/core/runtime/state_statements.go b/internal/core/runtime/state_statements.go index 1509ffd99..6c8ddc310 100644 --- a/internal/core/runtime/state_statements.go +++ b/internal/core/runtime/state_statements.go @@ -47,6 +47,7 @@ func (h *stateStmtHost) rootFrame(attrs []map[string]Value) *actionFrame { nodes: h.behavior.Nodes, label: h.describe(), outer: []frame{mapFrame(h.exec.stateData)}, + run: h.exec.ctx.newRun(), } if root.scope == nil { root.scope = h.exec.stateMachine.Scope diff --git a/internal/core/runtime/testdata/conformance/function_value_closure_equality.expected.json b/internal/core/runtime/testdata/conformance/function_value_closure_equality.expected.json new file mode 100644 index 000000000..2dacf9c50 --- /dev/null +++ b/internal/core/runtime/testdata/conformance/function_value_closure_equality.expected.json @@ -0,0 +1,11 @@ +{ + "type": "action", + "evaluate": "test::rebinding", + "libraries": true, + "outputs": { + "sameRun": {"type": "Boolean", "value": true}, + "acrossRuns": {"type": "Boolean", "value": true}, + "before": {"type": "Boolean", "value": true}, + "after": {"type": "Boolean", "value": true} + } +} diff --git a/internal/core/runtime/testdata/conformance/function_value_closure_equality.sysml b/internal/core/runtime/testdata/conformance/function_value_closure_equality.sysml new file mode 100644 index 000000000..aed565cab --- /dev/null +++ b/internal/core/runtime/testdata/conformance/function_value_closure_equality.sysml @@ -0,0 +1,51 @@ +package test { + private import ScalarValues::*; + private import SequenceFunctions::*; + + calc def Unary { in x : Real; return : Real; } + calc def Same { + in calc f { in x : Real; return : Real; } + in calc g { in x : Real; return : Real; } + return : Boolean = f == g; + } + calc def Maker { + in k : Real; + calc scale :> Unary { in :>> x; return : Real = x * k; } + return : Unary = scale; + } + // A nested calc read twice in one run is one function: the two reads are equal, + // and a sequence of both holds one distinct value. + calc def SameRun { + in k : Real; + calc inner { in x : Real; return : Real = x * k; } + return : Boolean = Same(inner, inner) and inner == inner + and includes((inner, inner), inner) and isEmpty(excluding((inner, inner), inner)) + and size(intersection((inner, inner), inner)) == 2; + } + // Functions returned by two runs are distinct even when the runs bind the + // closed-over name alike: each run's function is its own. + calc def AcrossRuns { + in calc once :> Unary = Maker(2.0); + return : Boolean = Maker(2.0) != Maker(2.0) and Maker(2.0) != Maker(3.0) + and once == once and excludes((Maker(2.0), Maker(3.0)), once) + and size(excluding((once, Maker(2.0), once), once)) == 1; + } + // Within one performance, a read taken before the body rebinds a closed-over + // name and a read taken after are the same function. + action def Rebinding { + out sameRun : Boolean = SameRun(3.0); + out acrossRuns : Boolean = AcrossRuns(); + out before : Boolean; + out after : Boolean; + attribute k : Real = 2.0; + calc byK { in x : Real; return : Real = x * k; } + attribute held = byK; + + first start; + then action same { assign before := held == byK; } + then action rebind { assign k := 3.0; } + then action changed { assign after := held == byK; } + then done; + } + action rebinding : Rebinding; +} diff --git a/internal/core/runtime/value_equality.go b/internal/core/runtime/value_equality.go index aab5efb4c..645ed1961 100644 --- a/internal/core/runtime/value_equality.go +++ b/internal/core/runtime/value_equality.go @@ -21,7 +21,7 @@ type valueKey struct { variant *symbols.Symbol literal *symbols.Symbol calc *symbols.Symbol - closure *functionValue + run int64 // the body run a function closes over (Value.functionRun) } // valueKeyFunc extracts a comparable key from a Value. Values valueEqual holds @@ -80,14 +80,10 @@ func valueKeyFunc(v Value) valueKey { case ValCoordinateTransformation: key.strVal = v.CoordinateTransformation().key() case ValFunction: - key.calc = v.Function() + key.calc, key.run = v.Function(), v.functionRun() if self := v.FunctionSelf(); self != nil { key.instID = self.ID } - // A function closing over a body's bindings is one only with itself. - if v.FunctionClosesOverBody() { - key.closure = v.function() - } } return key } diff --git a/internal/core/runtime/value_kinds_test.go b/internal/core/runtime/value_kinds_test.go index 00dc7fd05..2aa3deaef 100644 --- a/internal/core/runtime/value_kinds_test.go +++ b/internal/core/runtime/value_kinds_test.go @@ -75,25 +75,27 @@ func kindSamples() map[ValueKind][2]Value { } // A function read against the same object is one value however often it is -// read; one closing over a body's bindings is equal only to the same read, since -// two runs of the body bind the names it closes over differently. +// read; one closing over a body's bindings is one value with every read within +// the same run of the body, and another with a read in a different run, since +// two runs bind the names it closes over differently. func TestFunctionValueIdentity(t *testing.T) { sym := &symbols.Symbol{Name: "inner"} shape := &calcShape{Sym: sym, Name: "inner"} self := &Instance{ID: 7} bare := func() Value { return Value{Kind: ValFunction, ref: &functionValue{shape: shape, self: self}} } - closing := func() Value { - return Value{Kind: ValFunction, ref: &functionValue{shape: shape, self: self, enclosing: []frame{{vars: map[string]Value{"k": integerValue(1)}}}}} + closing := func(run int64) Value { + enclosing := []frame{{vars: map[string]Value{"k": integerValue(1)}, run: run}} + return Value{Kind: ValFunction, ref: &functionValue{shape: shape, self: self, enclosing: enclosing}} } if a, b := bare(), bare(); !valueEqual(a, b) || valueKeyFunc(a) != valueKeyFunc(b) { t.Errorf("two reads of %s against one object are not one value", FormatValue(a)) } - c := closing() - if first, again := valueKeyFunc(c), valueKeyFunc(c); !valueEqual(c, c) || first != again { - t.Errorf("a body-closing function is not equal to itself") + c := closing(1) + if again := closing(1); !valueEqual(c, again) || valueKeyFunc(c) != valueKeyFunc(again) { + t.Errorf("two reads of %s within one run are not one value", FormatValue(c)) } - if d := closing(); valueEqual(c, d) || valueKeyFunc(c) == valueKeyFunc(d) { - t.Errorf("two body-closing reads of %s compare equal", FormatValue(c)) + if d := closing(2); valueEqual(c, d) || valueKeyFunc(c) == valueKeyFunc(d) { + t.Errorf("reads of %s in two runs compare equal", FormatValue(c)) } if b := bare(); valueEqual(b, c) || valueEqual(c, b) || valueKeyFunc(b) == valueKeyFunc(c) { t.Errorf("a body-closing read of %s equals a bare one", FormatValue(c))