lutaml-lml 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,30 +10,86 @@
10
10
  # `[ x as k ]` yields k: nil when absent (Parslet omits the key —
11
11
  # consumers treat nil and absent as equivalent).
12
12
  grammar Lml version "1.0.0" {
13
+ # ---- Trivia (parsanol-ruby#134 skip rule) ----
14
+ # Grammar-native trivia replaces the pre-parse comment stripping:
15
+ # `//` line and `/* */` block comments (RS 3001 par. Comment) plus
16
+ # blanks/tabs are consumed and ignored between tokens. Newlines stay
17
+ # grammar-visible (line-oriented rules keep their semantics; the
18
+ # preprocessor preserved them too). Atomic token rules keep string
19
+ # literals and identifiers contiguous.
20
+ skip = trivia
21
+ trivia = 1*( ( 1*( " " / %x09 ) ) / ( line_comment [ ( [ %x0D ] %x0A / %x0D ) ] ) / block_comment )
22
+ line_comment = "//" ( *( !newline any_char ) )
23
+ block_comment = "/*" ( *( !"*/" any_char ) ) "*/"
24
+ atomic kw_association_sp = "association" 1*" "
25
+ atomic kw_artifact_sp = "artifact" 1*" "
26
+ atomic kw_attribute_sp = "attribute" 1*" "
27
+ atomic kw_caption_sp = "caption" 1*" "
28
+ atomic kw_class_sp = "class" 1*" "
29
+ atomic kw_visibility_sp = "visibility" 1*" "
30
+ atomic kw_version_sp = "version" 1*" "
31
+ version_declaration = kw_version_sp quoted_string as version
32
+ atomic kw_abstract_sp = "abstract" 1*" "
33
+ atomic kw_collection_sp = "collection" 1*" "
34
+ atomic kw_condition_sp = "condition" 1*" "
35
+ atomic kw_data_type_sp = "data_type" 1*" "
36
+ atomic kw_definition_sp = "definition" 1*" "
37
+ atomic kw_derive_sp = "derive" 1*" "
38
+ atomic kw_element_sp = "element" 1*" "
39
+ atomic kw_enum_sp = "enum" 1*" "
40
+ atomic kw_fontname_sp = "fontname" 1*" "
41
+ atomic kw_format_sp = "format" 1*" "
42
+ atomic kw_hide_sp = "hide" 1*" "
43
+ atomic kw_import_sp = "import" 1*" "
44
+ atomic kw_instance_sp = "instance" 1*" "
45
+ atomic kw_isa_sp = "isa" 1*" "
46
+ atomic kw_map_sp = "map" 1*" "
47
+ atomic kw_map_attribute_sp = "map_attribute" 1*" "
48
+ atomic kw_map_content_sp = "map_content" 1*" "
49
+ atomic kw_map_element_sp = "map_element" 1*" "
50
+ atomic kw_map_key_sp = "map_key" 1*" "
51
+ atomic kw_map_value_sp = "map_value" 1*" "
52
+ atomic kw_mapping_sp = "mapping" 1*" "
53
+ atomic kw_member_sp = "member" 1*" "
54
+ atomic kw_member_type_sp = "member_type" 1*" "
55
+ atomic kw_namespace_sp = "namespace" 1*" "
56
+ atomic kw_owner_sp = "owner" 1*" "
57
+ atomic kw_owner_type_sp = "owner_type" 1*" "
58
+ atomic kw_package_sp = "package" 1*" "
59
+ atomic kw_pattern_sp = "pattern" 1*" "
60
+ atomic kw_prefix_sp = "prefix" 1*" "
61
+ atomic kw_primitive_sp = "primitive" 1*" "
62
+ atomic kw_require_sp = "require" 1*" "
63
+ atomic kw_show_sp = "show" 1*" "
64
+ atomic kw_string_format_sp = "string_format" 1*" "
65
+ atomic kw_title_sp = "title" 1*" "
66
+ atomic kw_to_instance_sp = "to_instance" 1*" "
67
+ atomic kw_uri_sp = "uri" 1*" "
68
+ atomic kw_value_sp = "value" 1*" "
69
+ atomic kw_values_sp = "values" 1*" "
13
70
  # ---- Character primitives ----
14
71
  alpha = %x41-5A / %x61-7A
15
- word = %x41-5A / %x61-7A / %x30-39 / "_"
72
+ atomic word = %x41-5A / %x61-7A / %x30-39 / "_"
16
73
  cardchar = %x30-39 / %x61-7A / "*"
17
74
  # Parslet's `any`: any Unicode character (not just bytes)
18
75
  any_char = %x00-10FFFF
19
76
  # [^\s\n\r] — regex \s covers space, tab, CR, LF, VT, FF
20
- nonspace = !" " !%x09 !%x0A !%x0B !%x0C !%x0D any_char
77
+ atomic nonspace = !" " !%x09 !%x0A !%x0B !%x0C !%x0D any_char
21
78
  quote = %x22 / %x27
22
79
  newline = %x0D / %x0A
23
80
 
24
- ws = 1*" "
25
- whitespace = 1*( " " / %x09 / [ %x0D ] %x0A / %x0D / ";" )
81
+ whitespace = 1*( ( 1*( " " / %x09 ) ) / ( [ %x0D ] %x0A ) / %x0D / ";" / line_comment / block_comment )
26
82
 
27
83
  # match['"\'']
28
84
  quotes = %x22 / %x27
29
85
 
30
86
  # quoted('"', :string) | quoted("'", :string)
31
- dq_string = %x22 ( *( !%x22 any_char ) ) as string %x22
32
- sq_string = %x27 ( *( !%x27 any_char ) ) as string %x27
87
+ atomic dq_string = %x22 until "\"" as string %x22
88
+ atomic sq_string = %x27 until "'" as string %x27
33
89
  quoted_string = dq_string / sq_string
34
90
  # any_quoted(:path) for view import
35
- dq_path = %x22 ( *( !%x22 any_char ) ) as path %x22
36
- sq_path = %x27 ( *( !%x27 any_char ) ) as path %x27
91
+ atomic dq_path = %x22 until "\"" as path %x22
92
+ atomic sq_path = %x27 until "'" as path %x27
37
93
 
38
94
  boolean = ( "true" / "false" ) as boolean
39
95
  number = ( 1*%x30-39 "." 1*%x30-39 ) as float / ( 1*%x30-39 ) as number
@@ -41,8 +97,8 @@ grammar Lml version "1.0.0" {
41
97
  reference = ( "reference:(" / "ref:(" ) ( variable *( "." variable ) ) as reference ")"
42
98
  range = ( variable as start ".." variable as end ) as range
43
99
  namespaced_identifier = variable *( "::" variable )
44
- comment_definition = [ ws ] ( "**" / "#" ) ( *( !newline any_char ) ) as comments
45
- comment_multiline_definition = [ ws ] "*|" ( *( !"|*" any_char ) ) as comments [ whitespace ] "|*"
100
+ comment_definition = ( "**" / "#" ) ( *( !newline ANY ) ) as comments
101
+ comment_multiline_definition = "*|" until "|*" as comments [ whitespace ] "|*"
46
102
 
47
103
  class_name_chars = 1*( %x41-5A / %x61-7A / %x30-39 / " " / "_" / "-" / ":" / "." )
48
104
  class_name = class_name_chars [ "(" class_name_chars ")" ]
@@ -50,99 +106,129 @@ grammar Lml version "1.0.0" {
50
106
  cardinality = "[" cardinality_body_definition as cardinality "]"
51
107
 
52
108
  data_value = boolean / reference / range / number / quoted_string
53
- key_value_pair = variable as key ws [ "=" ] [ ws ] data_value as value
54
- key_value_map = "{" [ whitespace ] ( *( key_value_pair whitespace ) ) as key_value_map "}"
109
+ key_value_pair = variable as key [ "=" ] data_value as value
110
+ key_value_map = "{" [ whitespace ] ( *( key_value_pair [ whitespace ] ) ) as key_value_map "}"
55
111
  kw_visibility_modifier = "+" / "-" / "#" / "~"
56
112
 
57
113
  # ---- Attributes ----
58
- attribute = comment_definition / ( variable as key [ ws ] [ "+" as add ] [ "=" ] [ ws ] attribute_value as value )
59
- attributes = ( *( attribute_line / whitespace ) ) as attributes
60
- attribute_line = [ ws ] attribute [ [ "," ] whitespace ]
114
+ attribute = comment_definition / ( variable as key [ "+" as add ] [ "=" ] attribute_value as value )
115
+ attributes = ( *( definition_body / definition_line / attribute_line / whitespace ) ) as attributes
116
+ attribute_line = attribute [ [ "," ] whitespace ]
61
117
  visibility = kw_visibility_modifier as visibility_modifier
62
118
  attribute_stereotype = "<<" ( 1*( %x41-5A / %x61-7A / %x30-39 / "_" / "-" / "/" ) ) as stereotype ">>"
63
- typechar = %x41-5A / %x61-7A / %x30-39 / "_" / "-" / " " / ":" / "/" / "+"
64
- namechar = %x41-5A / %x61-7A / %x30-39 / "_" / "-" / "/" / "+"
65
- attribute_type = [ ":" ] [ ws ] [ attribute_stereotype ] [ ws ] [ quotes as type_literal ] ( 1*typechar ) as type [ quotes ] [ ws ]
119
+ atomic type_token = 1*( !( "//" / "/*" ) typechar )
120
+ atomic typechar = %x41-5A / %x61-7A / %x30-39 / "_" / "-" / " " / ":" / "/" / "+"
121
+ atomic namechar = %x41-5A / %x61-7A / %x30-39 / "_" / "-" / "/" / "+"
122
+ attribute_type = [ ":" ] [ attribute_stereotype ] [ quotes as type_literal ] ( type_token ) as type [ quotes ]
66
123
  attribute_name = ( 1*namechar ) as name
67
124
  attribute_definition_name = ( quotes ( 1*( %x41-5A / %x61-7A / %x30-39 / "_" / "-" / " " / "/" / "+" ) ) as name quotes ) / attribute_name
68
- attribute_definition = ( [ visibility ] as visibility [ ws ] attribute_definition_name [ ws ] [ attribute_type ] [ cardinality ] [ class_body ] ) as attributes
125
+ attribute_definition = ( [ visibility ] as visibility attribute_definition_name [ attribute_type ] [ cardinality ] [ class_body ] ) as attributes
69
126
 
70
- keyword_type_argument = "type" [ ws ] ( 1*nonspace ) as type [ whitespace ]
71
- keyword_cardinality_argument = "cardinality" [ ws ] cardinality_body_definition as cardinality [ whitespace ]
72
- keyword_any_argument = [ ws ] ( 1*nonspace ) as name ws [ "=" / ":" ] [ ws ] attribute_value as value [ whitespace ]
73
- keyword_values_argument = "values" ws ( ( value_set_body ) as value_set / ( variable ) as value_set_ref ) [ whitespace ]
74
- keyword_pattern_argument = ( "pattern" ws "/" ( *( ( "\\" any_char ) / ( !"/" any_char ) ) ) as pattern "/" [ whitespace ] )
127
+ keyword_type_argument = "type" ( 1*nonspace ) as type [ whitespace ]
128
+ keyword_cardinality_argument = "cardinality" cardinality_body_definition as cardinality [ whitespace ]
129
+ keyword_any_argument = ( keyword_any_name ) as name [ "=" / ":" ] attribute_value as value [ whitespace ]
130
+ atomic keyword_any_name = 1*( !( "{" / "}" ) nonspace )
131
+ keyword_values_argument = kw_values_sp ( ( value_set_body ) as value_set / ( variable ) as value_set_ref ) [ whitespace ]
132
+ keyword_pattern_argument = ( kw_pattern_sp "/" ( *( ( "\\" any_char ) / ( !"/" any_char ) ) ) as pattern "/" [ whitespace ] )
75
133
  value_set_body = "{" [ whitespace ] ( *( ( ( data_value / variable ) as item ) [ "," ] [ whitespace ] ) ) as items "}"
76
134
  keyword_attribute_options = *( keyword_type_argument / keyword_cardinality_argument / keyword_values_argument / keyword_pattern_argument / ( keyword_any_argument ) as properties )
77
135
  keyword_attribute_body = "{" [ whitespace ] keyword_attribute_options [ whitespace ] "}"
78
- keyword_attribute_definition = ( "attribute" ws attribute_name [ ws ] keyword_attribute_body ) as attributes
136
+ keyword_attribute_definition = ( kw_attribute_sp attribute_name keyword_attribute_body ) as attributes
79
137
 
80
138
  # ---- Associations ----
81
- owner_cardinality = [ ws ] "[" cardinality_body_definition as owner_end_cardinality "]"
82
- owner_attribute_name = "#" [ visibility ] [ ws ] variable as owner_end_attribute_name [ ws ]
83
- owner_definition = [ whitespace ] "owner" ws variable as owner_end [ owner_attribute_name ] [ owner_cardinality ]
84
- owner_type = [ whitespace ] "owner_type" ws variable as owner_end_type
85
- member_cardinality = [ ws ] "[" cardinality_body_definition as member_end_cardinality "]"
86
- member_attribute_name = "#" [ visibility ] [ ws ] variable as member_end_attribute_name [ ws ]
87
- member_definition = [ whitespace ] "member" ws variable as member_end [ member_attribute_name ] [ member_cardinality ]
88
- member_type = [ whitespace ] "member_type" ws variable as member_end_type
139
+ owner_cardinality = "[" cardinality_body_definition as owner_end_cardinality "]"
140
+ owner_attribute_name = "#" [ visibility ] variable as owner_end_attribute_name
141
+ owner_definition = [ whitespace ] kw_owner_sp variable as owner_end [ owner_attribute_name ] [ owner_cardinality ]
142
+ owner_type = [ whitespace ] kw_owner_type_sp variable as owner_end_type
143
+ member_cardinality = "[" cardinality_body_definition as member_end_cardinality "]"
144
+ member_attribute_name = "#" [ visibility ] variable as member_end_attribute_name
145
+ member_definition = [ whitespace ] kw_member_sp variable as member_end [ member_attribute_name ] [ member_cardinality ]
146
+ member_type = [ whitespace ] kw_member_type_sp variable as member_end_type
89
147
  association_inner_definition = ( owner_type / member_type / owner_definition / member_definition / comment_definition / comment_multiline_definition ) [ whitespace ]
90
- association_body = [ ws ] "{" [ whitespace ] ( *( association_inner_definition ) ) as members "}"
91
- association_definition = [ whitespace ] "association" ws [ variable as name ] [ ws ] association_body
148
+ association_body = "{" [ whitespace ] ( *( association_inner_definition ) ) as members "}"
149
+ association_definition = [ whitespace ] kw_association_sp [ variable as name ] association_body
92
150
 
93
151
  # ---- Definitions ----
94
- class_modifier = [ ( [ whitespace ] ( "abstract" / "interface" ) ) as modifier ws ]
152
+ # ---- Operations (class methods) ----
153
+ # Symbol visibility reuses the attribute prefix (+ - # ~); the
154
+ # transform maps it to public/private/protected/friendly.
155
+ operation_definition = [ whitespace ] [ visibility ] ( 1*word ) as name "(" [ ( operation_params ) as owned_parameter ] ")" [ operation_return ] [ operation_body ] [ whitespace ]
156
+ operation_params = operation_param *( "," [ whitespace ] operation_param )
157
+ operation_return = ":" ( type_token ) as return_type [ cardinality ]
158
+ # A parenthesized alternative is not an Entity, so an optional body
159
+ # of `x as k` around it gets no skip injection - the blank after "="
160
+ # was unconsumable. Reference a rule instead.
161
+ operation_param = ( [ ( "in" / "out" / "inout" ) as direction 1*" " ] ( 1*word ) as name [ ":" ] param_type as type [ "=" ] param_default_value as param_default )
162
+ param_default_value = [ quoted_string / ( 1*word ) / number ]
163
+ # The attribute pattern: separator as its own optional, value as a
164
+ # rule sibling. A `x as k` optional body gets no interior skip
165
+ # injection, so [ ":" type as t ] cannot consume the blank after
166
+ # the separator.
167
+ param_type = [ type_token ]
168
+ operation_body = "{" [ whitespace ] [ ( *( ( 1*word ) as stereotype [ "," ] [ whitespace ] ) ) as stereotype ] [ whitespace ] "}"
169
+
170
+ class_modifier = [ [ whitespace ] modifier_token ]
171
+ atomic modifier_token = ( ( "abstract" / "interface" ) as modifier 1*" " )
172
+ # RS 3001 par. Declaration modifiers: keyword prefix (sugar) and the
173
+ # canonical in-block declarations. The `package` visibility keyword is
174
+ # guarded: it must be followed by a declaration keyword, so a bare
175
+ # `package P { }` still binds as a package declaration.
176
+ atomic element_visibility = ( ( "public" / "private" / "protected" / ( "package" &( 1*" " ( kw_class_sp / kw_enum_sp / kw_data_type_sp / kw_primitive_sp / kw_package_sp ) ) ) ) as visibility 1*" " )
177
+ visibility_declaration = kw_visibility_sp ( "public" / "private" / "protected" / "package" ) as visibility
178
+ abstract_declaration = kw_abstract_sp ( "true" / "false" ) as abstract
95
179
  # ---- Namespaces (RS 3010 extension: lutaml-model XmlNamespace) ----
96
- namespace_uri_line = ( [ ws ] "uri" ws ( quoted_string / ( 1*word ) ) as uri [ whitespace ] )
97
- namespace_prefix_line = ( [ ws ] "prefix" ws ( quoted_string / ( 1*word ) ) as prefix [ whitespace ] )
180
+ namespace_uri_line = ( kw_uri_sp ( quoted_string / ( 1*word ) ) as uri [ whitespace ] )
181
+ namespace_prefix_line = ( kw_prefix_sp ( quoted_string / ( 1*word ) ) as prefix [ whitespace ] )
98
182
  namespace_body = "{" [ whitespace ] ( *( namespace_uri_line / namespace_prefix_line ) ) as members "}"
99
- namespace_inline = ( ws ( quoted_string ) as uri [ ws "prefix" ws ( quoted_string / ( 1*word ) ) as prefix ] )
100
- namespace_definition = ( [ whitespace ] "namespace" ws ( 1*word ) as name [ ws ] ( namespace_body / namespace_inline ) [ whitespace ] ) as namespaces
183
+ namespace_inline = ( ( quoted_string ) as uri [ kw_prefix_sp ( quoted_string / ( 1*word ) ) as prefix ] )
184
+ namespace_definition = ( [ whitespace ] kw_namespace_sp ( 1*word ) as name ( namespace_body / namespace_inline ) [ whitespace ] ) as namespaces
101
185
 
102
186
  # ---- Serialization mappings (RS 3010 extension: sibling-linked) ----
103
187
  serialization_body = "{" [ whitespace ] ( *( serialization_line ) ) as rules "}"
104
188
  serialization_line = ( mapping_element_name_line / mapping_element_line / mapping_attribute_line / mapping_content_line / mapping_namespace_line / mapping_mixed_line / legacy_map_line / mapping_map_line / mapping_map_key_line / mapping_map_value_line ) [ whitespace ]
105
- legacy_map_line = ( [ ws ] "map" ws dq_string as wire "," ws "to:" ws ( quoted_string / ( 1*word ) ) as to [ whitespace ] ) as legacy_map
106
- mapping_element_name_line = ( [ ws ] "element" ws dq_string as element_name [ whitespace ] ) as element_name
107
- mapping_element_line = ( [ ws ] "map_element" ws ( element_args ) ) as map_element
108
- element_args = ( dq_string as wire [ ws ( 1*word ) as field ] / ( 1*word ) as field )
109
- mapping_attribute_line = ( [ ws ] "map_attribute" ws ( attribute_args ) ) as map_attribute
110
- attribute_args = ( dq_string as wire [ ws ( 1*word ) as field ] / ( 1*word ) as field )
111
- mapping_content_line = ( [ ws ] "map_content" ws ( 1*word ) as field ) as map_content
112
- mapping_namespace_line = ( [ ws ] "namespace" ws ( 1*word ) as namespace_ref ) as map_namespace
113
- mapping_mixed_line = ( [ ws ] "mixed_content" ) as mixed_content
114
- mapping_map_line = ( [ ws ] "map" ws map_args ) as map
115
- map_args = ( dq_string as wire ws ( 1*word ) as field / dq_string as wire / ( 1*word ) as field )
116
- mapping_map_key_line = ( [ ws ] "map_key" ws "to_instance" ws ( 1*word ) as field ) as map_key
117
- mapping_map_value_line = ( [ ws ] "map_value" ws ( "to_instance" / "as_attribute" ) as mode ws ( 1*word ) as field ) as map_value
118
- mapping_definition = ( [ whitespace ] "mapping" ws ( 1*word ) as format ws ( 1*word ) as target [ ws ] serialization_body [ whitespace ] ) as serialization_mappings
189
+ legacy_map_line = ( kw_map_sp dq_string as wire "," "to:" ( quoted_string / ( 1*word ) ) as to [ whitespace ] ) as legacy_map
190
+ mapping_element_name_line = ( kw_element_sp dq_string as element_name [ whitespace ] ) as element_name
191
+ mapping_element_line = ( kw_map_element_sp ( element_args ) ) as map_element
192
+ element_args = ( dq_string as wire [ ( 1*word ) as field ] / ( 1*word ) as field )
193
+ mapping_attribute_line = ( kw_map_attribute_sp ( attribute_args ) ) as map_attribute
194
+ attribute_args = ( dq_string as wire [ ( 1*word ) as field ] / ( 1*word ) as field )
195
+ mapping_content_line = ( kw_map_content_sp ( 1*word ) as field ) as map_content
196
+ mapping_namespace_line = ( kw_namespace_sp ( 1*word ) as namespace_ref ) as map_namespace
197
+ mapping_mixed_line = ( "mixed_content" ) as mixed_content
198
+ mapping_map_line = ( kw_map_sp map_args ) as map
199
+ map_args = ( dq_string as wire ( 1*word ) as field / dq_string as wire / ( 1*word ) as field )
200
+ mapping_map_key_line = ( kw_map_key_sp kw_to_instance_sp ( 1*word ) as field ) as map_key
201
+ mapping_map_value_line = ( kw_map_value_sp map_value_mode ( 1*word ) as field ) as map_value
202
+ atomic map_value_mode = ( ( "to_instance" / "as_attribute" ) as mode 1*" " )
203
+ mapping_definition = ( [ whitespace ] kw_mapping_sp ( 1*word ) as format ( 1*word ) as target serialization_body [ whitespace ] ) as serialization_mappings
119
204
 
120
205
  # ---- Mapping blocks (lutaml-model wire names) ----
121
206
  map_name = ( quoted_string / 1*( word / "-" / "." ) )
122
- map_line = [ ws ] "map" ws ( map_name ) as wire "," ws "to:" ws ( map_name ) as to [ whitespace ]
123
- mapping_body = [ ws ] "{" [ whitespace ] ( *( map_line ) ) as maps "}"
124
- class_mapping_section = ( [ whitespace ] "mapping" ws ( 1*word ) as format [ ws ] serialization_body [ whitespace ] ) as serialization_mappings
207
+ map_line = kw_map_sp ( map_name ) as wire "," "to:" ( map_name ) as to [ whitespace ]
208
+ mapping_body = "{" [ whitespace ] ( *( map_line ) ) as maps "}"
209
+ class_mapping_section = ( [ whitespace ] kw_mapping_sp ( 1*word ) as format serialization_body [ whitespace ] ) as serialization_mappings
125
210
 
126
- derive_definition = ( [ whitespace ] "derive" ws attribute_name [ ws ] ":" [ ws ] [ quotes ] ( 1*typechar ) as type [ quotes ] ) as derived_field [ whitespace ]
127
- string_format_item = ( [ ws ] ( "artifact" / "root" ) as key ws ( quoted_string / 1*nonspace ) as value [ whitespace ] )
211
+ derive_definition = ( [ whitespace ] kw_derive_sp attribute_name ":" [ quotes ] ( type_token ) as type [ quotes ] ) as derived_field [ whitespace ]
212
+ string_format_item = ( sf_key_token ( quoted_string / 1*nonspace ) as value [ whitespace ] )
213
+ atomic sf_key_token = ( ( "artifact" / "root" ) as key 1*" " )
128
214
  string_format_body = "{" [ whitespace ] ( *( string_format_item ) ) as items [ whitespace ] "}"
129
- string_format_definition = ( [ whitespace ] "string_format" ws ( variable ) as format [ ws ] string_format_body ) as string_format
215
+ string_format_definition = ( [ whitespace ] kw_string_format_sp ( variable ) as format string_format_body ) as string_format
130
216
 
131
- class_inner_definitions = definition_body / definition_line / class_mapping_section / derive_definition / string_format_definition / attribute_comma_definition / ( !( "attribute" ws ) attribute_definition ) / keyword_attribute_definition / comment_definition / comment_multiline_definition
217
+ class_inner_definitions = visibility_declaration / abstract_declaration / definition_body / definition_line / class_mapping_section / derive_definition / string_format_definition / ( operation_definition ) as operations / attribute_comma_definition / ( !( kw_attribute_sp ) attribute_definition ) / keyword_attribute_definition / comment_definition / comment_multiline_definition
132
218
  class_inner_definition = class_inner_definitions [ whitespace ]
133
- class_body = [ ws ] "{" [ whitespace ] ( *( class_inner_definition ) ) as members "}"
134
- parent_class = [ ws ] "<" [ ws ] ( class_name_chars ) as parent_class
135
- class_definition = class_modifier [ whitespace ] "class" ws class_name as name [ parent_class ] [ ws ] [ attribute_stereotype ] [ class_body ]
219
+ class_body = "{" [ whitespace ] ( *( class_inner_definition ) ) as members "}"
220
+ parent_class = "<" ( class_name_chars ) as parent_class
221
+ class_definition = [ element_visibility ] class_modifier [ whitespace ] kw_class_sp class_name as name [ parent_class ] [ attribute_stereotype ] [ class_body ]
136
222
 
137
223
  # Balanced-brace definition text: nested "{...}" and "\"-escapes allowed
138
224
  definition_content = *( ( "\\" any_char ) / ( "{" definition_content "}" ) / ( !"}" any_char ) )
139
- definition_body = [ ws ] "definition" [ whitespace ] "{" ( definition_content ) as definition "}"
140
- definition_line = ( [ ws ] "definition" ws ( dq_string / sq_string ) ) as definition [ whitespace ]
225
+ definition_body = "definition" [ whitespace ] "{" ( definition_content ) as definition "}"
226
+ definition_line = ( kw_definition_sp ( dq_string / sq_string ) ) as definition [ whitespace ]
141
227
 
142
- member_field_line = [ ws ] attribute_name [ ws ] ( 1*nonspace ) as member_field_type [ whitespace ]
228
+ member_field_line = attribute_name ( 1*nonspace ) as member_field_type [ whitespace ]
143
229
  member_fields_definition = ( [ whitespace ] "member_fields" [ whitespace ] "{" [ whitespace ] ( *( member_field_line ) ) as member_fields [ whitespace ] "}" ) as member_fields_decl
144
- attribute_comma_definition = ( [ whitespace ] "attribute" ws attribute_name "," [ ws ] ( 1*typechar ) as type [ ws ] [ "{" [ whitespace ] keyword_attribute_options [ whitespace ] "}" ] [ whitespace ] ) as attributes
145
- enum_member_with_payload = ( [ whitespace ] attribute_name [ ws ] ( class_body / keyword_attribute_body ) ) as attributes
230
+ attribute_comma_definition = ( [ whitespace ] kw_attribute_sp attribute_name "," ( type_token ) as type [ "{" [ whitespace ] keyword_attribute_options [ whitespace ] "}" ] [ whitespace ] ) as attributes
231
+ enum_member_with_payload = ( [ whitespace ] attribute_name ( class_body / keyword_attribute_body ) ) as attributes
146
232
 
147
233
  # ---- Canonical value construct (RS 3001 par. Value / par. Enums) ----
148
234
  # `value <name> { block }` is the canonical enum member; the bare
@@ -151,81 +237,81 @@ grammar Lml version "1.0.0" {
151
237
  value_name = quoted_string / map_name
152
238
  value_inner_definition = ( definition_body / definition_line / instance / attribute_line / comment_definition / comment_multiline_definition ) [ whitespace ]
153
239
  value_body = "{" [ whitespace ] ( *( value_inner_definition ) ) as members "}"
154
- value_definition = ( [ whitespace ] "value" ws ( value_name ) as value_name [ ws ] [ value_body ] [ whitespace ] ) as value_decl
240
+ value_definition = ( [ whitespace ] kw_value_sp ( value_name ) as value_name [ value_body ] [ whitespace ] ) as value_decl
155
241
 
156
- from_table_definition = ( [ whitespace ] "from_table" [ ws ] ( map_name ) as from_table_name [ ws ] [ "{" [ whitespace ] *( "artifact" [ ws ] ( map_name ) as artifact_table [ whitespace ] ) "}" ] [ whitespace ] ) as from_table_decl
242
+ from_table_definition = ( [ whitespace ] "from_table" ( map_name ) as from_table_name [ "{" [ whitespace ] *( kw_artifact_sp ( map_name ) as artifact_table [ whitespace ] ) "}" ] [ whitespace ] ) as from_table_decl
157
243
 
158
- enum_values_shorthand = ( [ whitespace ] "values" ws value_set_body [ whitespace ] ) as value_set_decl
159
- enum_inner_definitions = definition_body / definition_line / member_fields_definition / from_table_definition / enum_values_shorthand / value_definition / enum_member_with_payload / attribute_comma_definition / attribute_definition / comment_definition / comment_multiline_definition
244
+ enum_values_shorthand = ( [ whitespace ] kw_values_sp value_set_body [ whitespace ] ) as value_set_decl
245
+ enum_inner_definitions = visibility_declaration / abstract_declaration / definition_body / definition_line / member_fields_definition / from_table_definition / enum_values_shorthand / value_definition / enum_member_with_payload / attribute_comma_definition / attribute_definition / comment_definition / comment_multiline_definition
160
246
  enum_inner_definition = enum_inner_definitions [ whitespace ]
161
- enum_body = [ ws ] "{" [ whitespace ] ( *( enum_inner_definition ) ) as members "}"
162
- enum_definition = [ whitespace ] "enum" ws [ quotes ] class_name as name [ quotes ] [ attribute_stereotype ] [ enum_body ]
163
- data_type_body = [ ws ] "{" [ whitespace ] ( *( enum_inner_definition ) ) as members "}"
164
- data_type_definition = [ whitespace ] "data_type" ws [ quotes ] class_name as name [ quotes ] [ attribute_stereotype ] [ data_type_body ]
165
- primitive_definition = [ whitespace ] "primitive" ws [ quotes ] class_name as name [ quotes ]
166
-
167
- package_body = [ ws ] "{" [ whitespace ] ( *( diagram_inner_definition ) ) as members "}"
168
- package_definition = ( [ whitespace ] "package" ws class_name as name [ ws ] package_body [ whitespace ] ) as packages
169
- diagram_inner_definitions = namespace_definition / mapping_definition / package_definition / title_definition / caption_definition / fontname_definition / ( class_definition ) as classes / ( enum_definition ) as enums / ( primitive_definition ) as primitives / ( data_type_definition ) as data_types / ( association_definition ) as associations / comment_definition / comment_multiline_definition
247
+ enum_body = "{" [ whitespace ] ( *( enum_inner_definition ) ) as members "}"
248
+ enum_definition = [ whitespace ] [ element_visibility ] kw_enum_sp [ quotes ] class_name as name [ quotes ] [ attribute_stereotype ] [ enum_body ]
249
+ data_type_body = "{" [ whitespace ] ( *( enum_inner_definition ) ) as members "}"
250
+ data_type_definition = [ whitespace ] [ element_visibility ] kw_data_type_sp [ quotes ] class_name as name [ quotes ] [ attribute_stereotype ] [ data_type_body ]
251
+ primitive_definition = [ whitespace ] [ element_visibility ] kw_primitive_sp [ quotes ] class_name as name [ quotes ]
252
+
253
+ package_body = "{" [ whitespace ] ( *( diagram_inner_definition ) ) as members "}"
254
+ package_definition = ( [ whitespace ] [ element_visibility ] kw_package_sp class_name as name package_body [ whitespace ] ) as packages
255
+ diagram_inner_definitions = namespace_definition / mapping_definition / ( class_definition ) as classes / ( enum_definition ) as enums / ( primitive_definition ) as primitives / ( data_type_definition ) as data_types / package_definition / title_definition / caption_definition / fontname_definition / ( association_definition ) as associations / comment_definition / comment_multiline_definition
170
256
  diagram_inner_definition = diagram_inner_definitions [ whitespace ]
171
- diagram_body = [ ws ] "{" [ whitespace ] ( *( diagram_inner_definition ) ) as members "}"
172
- diagram_definition = [ whitespace ] "diagram" [ ws ] class_name as name diagram_body [ whitespace ]
257
+ diagram_body = "{" [ whitespace ] ( *( diagram_inner_definition ) ) as members "}"
258
+ diagram_definition = [ whitespace ] "diagram" class_name as name diagram_body [ whitespace ]
173
259
  diagram_definitions = diagram_definition [ whitespace ]
174
260
  model_definitions = ( 1*( diagram_inner_definition ) ) as members
175
261
 
176
262
  view_only_definitions = ( view_import ) as view_imports / show_directive / hide_directive
177
263
  view_inner_definitions = view_only_definitions / diagram_inner_definitions
178
264
  view_inner_definition = view_inner_definitions [ whitespace ]
179
- view_body = [ ws ] "{" [ whitespace ] ( *( view_inner_definition ) ) as members "}"
180
- view_definition = [ whitespace ] "view" [ ws ] class_name as name view_body [ whitespace ]
265
+ view_body = "{" [ whitespace ] ( *( view_inner_definition ) ) as members "}"
266
+ view_definition = [ whitespace ] "view" class_name as name view_body [ whitespace ]
181
267
  view_definitions = view_definition [ whitespace ]
182
268
 
183
- title_text = ( %x22 ( 1*( !%x22 any_char ) ) as title %x22 ) / ( %x27 ( 1*( !%x27 any_char ) ) as title %x27 ) / ( 1*( %x41-5A / %x61-7A / %x30-39 / "_" / "-" / " " / "," / "." / ":" / ";" / "(" / ")" / "+" / "'" / "/" ) ) as title
184
- title_definition = [ whitespace ] "title" ws title_text
185
- caption_text = ( %x22 ( 1*( !%x22 any_char ) ) as caption %x22 ) / ( %x27 ( 1*( !%x27 any_char ) ) as caption %x27 ) / ( 1*( %x41-5A / %x61-7A / %x30-39 / "_" / "-" / " " / "," / "." / ":" / ";" / "(" / ")" / "+" / "'" / "/" ) ) as caption
186
- caption_definition = [ whitespace ] "caption" ws caption_text
269
+ atomic title_text = ( %x22 ( 1*( !%x22 ANY ) ) as title %x22 ) / ( %x27 ( 1*( !%x27 ANY ) ) as title %x27 ) / ( 1*( %x41-5A / %x61-7A / %x30-39 / "_" / "-" / " " / "," / "." / ":" / ";" / "(" / ")" / "+" / "'" / "/" ) ) as title
270
+ title_definition = [ whitespace ] kw_title_sp title_text
271
+ atomic caption_text = ( %x22 ( 1*( !%x22 ANY ) ) as caption %x22 ) / ( %x27 ( 1*( !%x27 ANY ) ) as caption %x27 ) / ( 1*( %x41-5A / %x61-7A / %x30-39 / "_" / "-" / " " / "," / "." / ":" / ";" / "(" / ")" / "+" / "'" / "/" ) ) as caption
272
+ caption_definition = [ whitespace ] kw_caption_sp caption_text
187
273
  fontname_text = [ quotes ] ( 1*( %x41-5A / %x61-7A / %x30-39 / "_" / "-" / " " ) ) as fontname [ quotes ]
188
- fontname_definition = [ whitespace ] "fontname" ws fontname_text
274
+ fontname_definition = [ whitespace ] kw_fontname_sp fontname_text
189
275
 
190
276
  # ---- Views ----
191
277
  # `import` here carries a required trailing space (word boundary)
192
- view_import = [ whitespace ] "import" ws ( dq_path / sq_path ) [ whitespace ]
278
+ view_import = [ whitespace ] kw_import_sp ( dq_path / sq_path ) [ whitespace ]
193
279
  entity_name_atom = ( 1*( %x41-5A / %x61-7A / %x30-39 / "_" / "." / ":" / "-" ) ) as entity_name
194
- entity_name_list = entity_name_atom *( [ ws ] "," [ ws ] entity_name_atom )
195
- show_directive = [ whitespace ] "show" ws ( entity_name_list ) as show_list [ whitespace ]
196
- hide_directive = [ whitespace ] "hide" ws ( entity_name_list ) as hide_list [ whitespace ]
280
+ entity_name_list = entity_name_atom *( "," entity_name_atom )
281
+ show_directive = [ whitespace ] kw_show_sp ( entity_name_list ) as show_list [ whitespace ]
282
+ hide_directive = [ whitespace ] kw_hide_sp ( entity_name_list ) as hide_list [ whitespace ]
197
283
 
198
284
  # ---- Requires ----
199
- require_stmt = [ whitespace ] "require" ws quoted_string as require [ whitespace ]
285
+ require_stmt = [ whitespace ] kw_require_sp quoted_string as require [ whitespace ]
200
286
  require_block = ( *( require_stmt [ whitespace ] ) ) as requires
201
287
 
202
288
  # ---- Instances ----
203
289
  instance = instance_named / keyword_instance / class_instance
204
- instance_named = ( [ whitespace ] "instance" ws quoted_string as name [ ws ] [ ( namespaced_identifier ) as instance_type ] [ ws ] "{" [ whitespace ] ( ( [ ws ] instance ) / attributes ) "}" [ whitespace ] ) as instance
205
- isa_definition = [ whitespace ] "isa" ws ( namespaced_identifier ) as isa [ whitespace ]
206
- keyword_instance = ( [ whitespace ] "instance" [ ws ( namespaced_identifier ) as instance_type ] [ ws ] "{" [ whitespace ] [ isa_definition ] ( ( [ ws ] instance ) / attributes ) "}" ) as instance [ whitespace ]
290
+ instance_named = ( [ whitespace ] kw_instance_sp quoted_string as name [ ( namespaced_identifier ) as instance_type ] "{" [ whitespace ] ( ( instance ) / attributes ) "}" [ whitespace ] ) as instance
291
+ isa_definition = [ whitespace ] kw_isa_sp ( namespaced_identifier ) as isa [ whitespace ]
292
+ keyword_instance = ( [ whitespace ] "instance" [ ( namespaced_identifier ) as instance_type ] "{" [ whitespace ] [ isa_definition ] ( ( instance ) / attributes ) "}" ) as instance [ whitespace ]
207
293
  class_instance = ( variable as instance_type [ whitespace ] quoted_string as name [ whitespace ] [ [ whitespace ] "extends" [ whitespace ] quoted_string as parent [ whitespace ] ] "{" [ whitespace ] [ lml_instance_body ] "}" ) as instance [ whitespace ]
208
294
  lml_instance_body = lml_instance_members [ whitespace ]
209
295
  instance_template = [ whitespace ] "template" [ whitespace ] "{" [ whitespace ] attributes [ whitespace ] "}"
210
296
  lml_instance_members = ( instance_template ) as template / attributes
211
- models = [ whitespace ] "models" [ whitespace ] variable as name [ whitespace ] "{" ( *( ( ( string_format_definition ) as default_string_formats ) / model_body ) ) as members "}" [ whitespace ]
297
+ models = [ whitespace ] "models" [ whitespace ] variable as name [ version_declaration ] [ whitespace ] "{" ( *( ( ( string_format_definition ) as default_string_formats ) / model_body ) ) as members "}" [ whitespace ]
212
298
  model_body = diagram_inner_definition
213
299
 
214
300
  # ---- Data structures ----
215
- attribute_value = instance / list / key_value_map / data_value / 1*( !%x0A any_char )
301
+ attribute_value = instance / list / key_value_map / data_value / 1*( !%x0A ANY )
216
302
  list_item = instance / data_value
217
- list = "[" [ whitespace ] ( *( list_item [ ws ] [ "," ] [ whitespace ] ) ) as list [ whitespace ] "]"
218
- collection = [ whitespace ] "collection" ws quoted_string as name [ ws ] "{" [ whitespace ] [ includes ] [ whitespace ] [ validation ] [ whitespace ] "}" [ whitespace ]
219
- includes = [ whitespace ] "includes" [ ws ] ( list ) as includes
220
- validation = [ whitespace ] "validation" [ ws ] "{" [ whitespace ] ( *( condition ) ) as validations "}"
221
- condition = [ whitespace ] "condition" ws quoted_string as condition [ whitespace ]
222
- import = [ whitespace ] "import" [ ws ] "{" [ whitespace ] ( *( import_definition ) ) as imports "}" [ whitespace ]
223
- import_definition = ( 1*nonspace ) as format_type [ ws ] quoted_string as file [ whitespace ] "{" [ whitespace ] attributes [ whitespace ] "}" [ whitespace ]
224
- instances = [ whitespace ] "instances" [ ws ] [ ( quoted_string / variable ) as name ] [ whitespace ] "{" [ whitespace ] [ instances_body ] "}" [ whitespace ]
303
+ list = "[" [ whitespace ] ( *( list_item [ "," ] [ whitespace ] ) ) as list [ whitespace ] "]"
304
+ collection = [ whitespace ] kw_collection_sp quoted_string as name "{" [ whitespace ] [ includes ] [ whitespace ] [ validation ] [ whitespace ] "}" [ whitespace ]
305
+ includes = [ whitespace ] "includes" ( list ) as includes
306
+ validation = [ whitespace ] "validation" "{" [ whitespace ] ( *( condition ) ) as validations "}"
307
+ condition = [ whitespace ] kw_condition_sp quoted_string as condition [ whitespace ]
308
+ import = [ whitespace ] "import" "{" [ whitespace ] ( *( import_definition ) ) as imports "}" [ whitespace ]
309
+ import_definition = ( 1*nonspace ) as format_type quoted_string as file [ whitespace ] "{" [ whitespace ] attributes [ whitespace ] "}" [ whitespace ]
310
+ instances = [ whitespace ] "instances" [ ( quoted_string / variable ) as name ] [ whitespace ] "{" [ whitespace ] [ instances_body ] "}" [ whitespace ]
225
311
  instances_body = ( *( instances_member [ whitespace ] ) ) as instances
226
312
  instances_member = import / ( collection ) as collections / export / instance
227
313
  export = [ whitespace ] "export" [ whitespace ] "{" [ whitespace ] ( *( export_format [ whitespace ] ) ) as exports "}" [ whitespace ]
228
- export_format = [ whitespace ] "format" ws variable as format_type [ whitespace ] "{" [ whitespace ] attributes "}" [ whitespace ]
314
+ export_format = [ whitespace ] kw_format_sp variable as format_type [ whitespace ] "{" [ whitespace ] attributes "}" [ whitespace ]
229
315
 
230
316
  # ---- Root (RS 3001: a document interleaves declarations) ----
231
317
  diagram = [ require_block ] ( *( top_level_definition ) ) as members
@@ -234,3 +320,16 @@ grammar Lml version "1.0.0" {
234
320
  }
235
321
 
236
322
  entry diagram: diagram
323
+
324
+ # Compile-time contract tests for the trivia/skip behavior (run by
325
+ # `rake parg` and any in-memory compile; a failure fails the build).
326
+ test diagram {
327
+ accept "// leading\nclass Ceramic { }\n"
328
+ accept "class /* inline */ Ceramic { }\n"
329
+ accept "class Ceramic { } // trailing"
330
+ accept "instances {\n P \"p\" {\n url = \"http://a//b\"\n }\n}\n"
331
+ reject "classCeramic Foo { }"
332
+ reject "abstractclass Ceramic { }"
333
+ reject "class\tCeramic { }"
334
+ reject "class Ceramic { /* unterminated"
335
+ }
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Layout
5
+ # In-process layout engine backed by elkrb (pure-Ruby ELK). The input
6
+ # is an already-built Elkrb::Graph; rendering produces standalone SVG.
7
+ class ElkEngine < Engine
8
+ def render(_type)
9
+ require 'elkrb'
10
+ Elkrb::Layout::LayoutEngine.layout(@input, algorithm: 'layered')
11
+ ElkSvgRenderer.new.render(@input)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Layout
5
+ # Builds an Elkrb::Graph: a node per classifier (name, definition,
6
+ # members); an edge per association and parent specializer.
7
+ class ElkGraphBuilder
8
+ CHAR_WIDTH = 7.2
9
+ LINE_HEIGHT = 16.0
10
+ PADDING = 14.0
11
+ MIN_WIDTH = 120.0
12
+ COLLECTIONS = %i[classes enums primitives data_types].freeze
13
+ def build(document)
14
+ graph = elkrb_graph
15
+ add_entities(graph, document)
16
+ add_association_edges(graph, document)
17
+ add_parent_edges(graph, document)
18
+ graph
19
+ end
20
+
21
+ private
22
+
23
+ def elkrb_graph
24
+ require 'elkrb'
25
+ Elkrb::Graph::Graph.new(id: 'diagram')
26
+ rescue LoadError
27
+ raise Error, 'The elkrb gem is required for ELK layout: add `gem "elkrb"` to your Gemfile'
28
+ end
29
+
30
+ def entities(document)
31
+ ([document] + Array(document.packages)).flat_map { |scope| scope_entities(scope) }.flatten.compact
32
+ end
33
+
34
+ def scope_entities(scope)
35
+ COLLECTIONS.filter_map do |collection|
36
+ next unless scope.respond_to?(collection)
37
+
38
+ scope.public_send(collection)
39
+ end
40
+ end
41
+
42
+ def node_for(entity)
43
+ lines = label_lines(entity)
44
+ Elkrb::Graph::Node.new(
45
+ id: entity_id_for(entity.name),
46
+ width: node_width(lines),
47
+ height: PADDING + lines.size * LINE_HEIGHT,
48
+ labels: lines.each_with_index.map do |line, i|
49
+ Elkrb::Graph::Label.new(text: line, x: 0, y: i * LINE_HEIGHT)
50
+ end
51
+ )
52
+ end
53
+
54
+ def label_lines(entity)
55
+ lines = [entity.name.to_s]
56
+ if entity.respond_to?(:definition) && !entity.definition.to_s.empty?
57
+ lines << entity.definition.to_s.gsub("\n", ' ')
58
+ end
59
+ Array(entity.attributes).each do |attr|
60
+ lines << attr_line(attr)
61
+ end
62
+ lines
63
+ end
64
+
65
+ def attr_line(attr)
66
+ line = "+ #{attr.name}"
67
+ line += ": #{attr.type}" if attr.type && !attr.type.to_s.empty?
68
+ line
69
+ end
70
+
71
+ def node_width(lines)
72
+ widest = lines.map(&:length).max || 0
73
+ [widest * CHAR_WIDTH + PADDING, MIN_WIDTH].max
74
+ end
75
+
76
+ def entity_id(entity)
77
+ entity.name.to_s.gsub(/[^0-9a-zA-Z_]/, '_')
78
+ end
79
+
80
+ def associations(document)
81
+ Array(document.associations)
82
+ end
83
+
84
+ def parent_edges(document)
85
+ Array(document.classes).filter_map do |klass|
86
+ next unless klass.respond_to?(:parent_class) && klass.parent_class
87
+
88
+ { owner: klass.parent_class, member: klass.name.to_s,
89
+ member_end_type: 'generalization', name: nil }
90
+ end
91
+ end
92
+
93
+ def add_entities(graph, document)
94
+ entities(document).each { |entity| graph.children << node_for(entity) }
95
+ end
96
+
97
+ def add_association_edges(graph, document)
98
+ associations(document).each { |assoc| graph.edges << edge_for(association_spec(assoc)) }
99
+ end
100
+
101
+ def add_parent_edges(graph, document)
102
+ parent_edges(document).each { |edge| graph.edges << edge_for(edge) }
103
+ end
104
+
105
+ def edge_for(spec)
106
+ edge = elkrb_edge(spec)
107
+ edge.properties = { 'member_end_type' => spec[:member_end_type].to_s }
108
+ edge.labels = [Elkrb::Graph::Label.new(text: spec[:name].to_s)] if spec[:name]
109
+ edge
110
+ end
111
+
112
+ def elkrb_edge(spec)
113
+ require 'elkrb'
114
+ Elkrb::Graph::Edge.new(
115
+ id: "edge_#{spec[:owner]}_#{spec[:member]}",
116
+ sources: [entity_id_for(spec[:owner])],
117
+ targets: [entity_id_for(spec[:member])]
118
+ )
119
+ end
120
+
121
+ def association_spec(assoc)
122
+ { owner: assoc.owner_end.to_s, member: assoc.member_end.to_s,
123
+ member_end_type: assoc.member_end_type.to_s, name: assoc.name }
124
+ end
125
+
126
+ def entity_id_for(name) = name.to_s.gsub(/[^0-9a-zA-Z_]/, '_')
127
+ end
128
+ end
129
+ end