diff options
Diffstat (limited to 'xtask')
-rw-r--r-- | xtask/src/ast_src.rs | 13 | ||||
-rw-r--r-- | xtask/src/codegen.rs | 5 | ||||
-rw-r--r-- | xtask/src/codegen/gen_syntax.rs | 265 | ||||
-rw-r--r-- | xtask/src/lib.rs | 1 |
4 files changed, 163 insertions, 121 deletions
diff --git a/xtask/src/ast_src.rs b/xtask/src/ast_src.rs index 5fed777ac..eba66ff4d 100644 --- a/xtask/src/ast_src.rs +++ b/xtask/src/ast_src.rs | |||
@@ -227,6 +227,7 @@ pub(crate) const KINDS_SRC: KindsSrc = KindsSrc { | |||
227 | pub(crate) struct AstSrc<'a> { | 227 | pub(crate) struct AstSrc<'a> { |
228 | pub(crate) nodes: &'a [AstNodeSrc<'a>], | 228 | pub(crate) nodes: &'a [AstNodeSrc<'a>], |
229 | pub(crate) enums: &'a [AstEnumSrc<'a>], | 229 | pub(crate) enums: &'a [AstEnumSrc<'a>], |
230 | pub(crate) token_enums: &'a [AstEnumSrc<'a>], | ||
230 | } | 231 | } |
231 | 232 | ||
232 | pub(crate) struct AstNodeSrc<'a> { | 233 | pub(crate) struct AstNodeSrc<'a> { |
@@ -753,6 +754,13 @@ pub(crate) const AST_SRC: AstSrc = AstSrc { | |||
753 | // macro calls are parsed as expression statements */ | 754 | // macro calls are parsed as expression statements */ |
754 | } | 755 | } |
755 | 756 | ||
757 | enum FieldDefList { | ||
758 | RecordFieldDefList, | ||
759 | TupleFieldDefList, | ||
760 | } | ||
761 | }, | ||
762 | |||
763 | token_enums: &ast_enums! { | ||
756 | enum LeftDelimiter { LParen, LBrack, LCurly } | 764 | enum LeftDelimiter { LParen, LBrack, LCurly } |
757 | enum RightDelimiter { RParen, RBrack, RCurly } | 765 | enum RightDelimiter { RParen, RBrack, RCurly } |
758 | enum RangeSeparator { Dotdot, Dotdotdot, Dotdoteq} | 766 | enum RangeSeparator { Dotdot, Dotdotdot, Dotdoteq} |
@@ -817,10 +825,5 @@ pub(crate) const AST_SRC: AstSrc = AstSrc { | |||
817 | Ident, | 825 | Ident, |
818 | IntNumber | 826 | IntNumber |
819 | } | 827 | } |
820 | |||
821 | enum FieldDefList { | ||
822 | RecordFieldDefList, | ||
823 | TupleFieldDefList, | ||
824 | } | ||
825 | }, | 828 | }, |
826 | }; | 829 | }; |
diff --git a/xtask/src/codegen.rs b/xtask/src/codegen.rs index a53d57335..678b40133 100644 --- a/xtask/src/codegen.rs +++ b/xtask/src/codegen.rs | |||
@@ -22,8 +22,9 @@ const GRAMMAR_DIR: &str = "crates/ra_parser/src/grammar"; | |||
22 | const OK_INLINE_TESTS_DIR: &str = "crates/ra_syntax/test_data/parser/inline/ok"; | 22 | const OK_INLINE_TESTS_DIR: &str = "crates/ra_syntax/test_data/parser/inline/ok"; |
23 | const ERR_INLINE_TESTS_DIR: &str = "crates/ra_syntax/test_data/parser/inline/err"; | 23 | const ERR_INLINE_TESTS_DIR: &str = "crates/ra_syntax/test_data/parser/inline/err"; |
24 | 24 | ||
25 | pub const SYNTAX_KINDS: &str = "crates/ra_parser/src/syntax_kind/generated.rs"; | 25 | const SYNTAX_KINDS: &str = "crates/ra_parser/src/syntax_kind/generated.rs"; |
26 | pub const AST: &str = "crates/ra_syntax/src/ast/generated.rs"; | 26 | const AST_NODES: &str = "crates/ra_syntax/src/ast/generated/nodes.rs"; |
27 | const AST_TOKENS: &str = "crates/ra_syntax/src/ast/generated/tokens.rs"; | ||
27 | 28 | ||
28 | const ASSISTS_DIR: &str = "crates/ra_assists/src/handlers"; | 29 | const ASSISTS_DIR: &str = "crates/ra_assists/src/handlers"; |
29 | const ASSISTS_TESTS: &str = "crates/ra_assists/src/doc_tests/generated.rs"; | 30 | const ASSISTS_TESTS: &str = "crates/ra_assists/src/doc_tests/generated.rs"; |
diff --git a/xtask/src/codegen/gen_syntax.rs b/xtask/src/codegen/gen_syntax.rs index 6dae93aa2..6657c9fc5 100644 --- a/xtask/src/codegen/gen_syntax.rs +++ b/xtask/src/codegen/gen_syntax.rs | |||
@@ -3,10 +3,13 @@ | |||
3 | //! Specifically, it generates the `SyntaxKind` enum and a number of newtype | 3 | //! Specifically, it generates the `SyntaxKind` enum and a number of newtype |
4 | //! wrappers around `SyntaxNode` which implement `ra_syntax::AstNode`. | 4 | //! wrappers around `SyntaxNode` which implement `ra_syntax::AstNode`. |
5 | 5 | ||
6 | use std::{ | ||
7 | borrow::Cow, | ||
8 | collections::{BTreeSet, HashSet}, | ||
9 | }; | ||
10 | |||
6 | use proc_macro2::{Punct, Spacing}; | 11 | use proc_macro2::{Punct, Spacing}; |
7 | use quote::{format_ident, quote}; | 12 | use quote::{format_ident, quote}; |
8 | use std::borrow::Cow; | ||
9 | use std::collections::{BTreeSet, HashMap, HashSet}; | ||
10 | 13 | ||
11 | use crate::{ | 14 | use crate::{ |
12 | ast_src::{AstSrc, FieldSrc, KindsSrc, AST_SRC, KINDS_SRC}, | 15 | ast_src::{AstSrc, FieldSrc, KindsSrc, AST_SRC, KINDS_SRC}, |
@@ -19,9 +22,13 @@ pub fn generate_syntax(mode: Mode) -> Result<()> { | |||
19 | let syntax_kinds = generate_syntax_kinds(KINDS_SRC)?; | 22 | let syntax_kinds = generate_syntax_kinds(KINDS_SRC)?; |
20 | update(syntax_kinds_file.as_path(), &syntax_kinds, mode)?; | 23 | update(syntax_kinds_file.as_path(), &syntax_kinds, mode)?; |
21 | 24 | ||
22 | let ast_file = project_root().join(codegen::AST); | 25 | let ast_nodes_file = project_root().join(codegen::AST_NODES); |
23 | let ast = generate_ast(KINDS_SRC, AST_SRC)?; | 26 | let contents = generate_nodes(KINDS_SRC, AST_SRC)?; |
24 | update(ast_file.as_path(), &ast, mode)?; | 27 | update(ast_nodes_file.as_path(), &contents, mode)?; |
28 | |||
29 | let ast_tokens_file = project_root().join(codegen::AST_TOKENS); | ||
30 | let contents = generate_tokens(KINDS_SRC, AST_SRC)?; | ||
31 | update(ast_tokens_file.as_path(), &contents, mode)?; | ||
25 | 32 | ||
26 | Ok(()) | 33 | Ok(()) |
27 | } | 34 | } |
@@ -33,7 +40,7 @@ struct ElementKinds { | |||
33 | has_tokens: bool, | 40 | has_tokens: bool, |
34 | } | 41 | } |
35 | 42 | ||
36 | fn generate_ast(kinds: KindsSrc<'_>, grammar: AstSrc<'_>) -> Result<String> { | 43 | fn generate_tokens(kinds: KindsSrc<'_>, grammar: AstSrc<'_>) -> Result<String> { |
37 | let all_token_kinds: Vec<_> = kinds | 44 | let all_token_kinds: Vec<_> = kinds |
38 | .punct | 45 | .punct |
39 | .into_iter() | 46 | .into_iter() |
@@ -51,46 +58,6 @@ fn generate_ast(kinds: KindsSrc<'_>, grammar: AstSrc<'_>) -> Result<String> { | |||
51 | .chain(kinds.tokens.into_iter().copied().map(|x| x.into())) | 58 | .chain(kinds.tokens.into_iter().copied().map(|x| x.into())) |
52 | .collect(); | 59 | .collect(); |
53 | 60 | ||
54 | let mut element_kinds_map = HashMap::new(); | ||
55 | for kind in &all_token_kinds { | ||
56 | let kind = &**kind; | ||
57 | let name = to_pascal_case(kind); | ||
58 | element_kinds_map.insert( | ||
59 | name, | ||
60 | ElementKinds { | ||
61 | kinds: Some(format_ident!("{}", kind)).into_iter().collect(), | ||
62 | has_nodes: false, | ||
63 | has_tokens: true, | ||
64 | }, | ||
65 | ); | ||
66 | } | ||
67 | |||
68 | for kind in kinds.nodes { | ||
69 | let name = to_pascal_case(kind); | ||
70 | element_kinds_map.insert( | ||
71 | name, | ||
72 | ElementKinds { | ||
73 | kinds: Some(format_ident!("{}", *kind)).into_iter().collect(), | ||
74 | has_nodes: true, | ||
75 | has_tokens: false, | ||
76 | }, | ||
77 | ); | ||
78 | } | ||
79 | |||
80 | for en in grammar.enums { | ||
81 | let mut element_kinds: ElementKinds = Default::default(); | ||
82 | for variant in en.variants { | ||
83 | if let Some(variant_element_kinds) = element_kinds_map.get(*variant) { | ||
84 | element_kinds.kinds.extend(variant_element_kinds.kinds.iter().cloned()); | ||
85 | element_kinds.has_tokens |= variant_element_kinds.has_tokens; | ||
86 | element_kinds.has_nodes |= variant_element_kinds.has_nodes; | ||
87 | } else { | ||
88 | panic!("Enum variant has type that does not exist or was not declared before the enum: {}", *variant); | ||
89 | } | ||
90 | } | ||
91 | element_kinds_map.insert(en.name.to_string(), element_kinds); | ||
92 | } | ||
93 | |||
94 | let tokens = all_token_kinds.iter().map(|kind_str| { | 61 | let tokens = all_token_kinds.iter().map(|kind_str| { |
95 | let kind_str = &**kind_str; | 62 | let kind_str = &**kind_str; |
96 | let kind = format_ident!("{}", kind_str); | 63 | let kind = format_ident!("{}", kind_str); |
@@ -108,12 +75,7 @@ fn generate_ast(kinds: KindsSrc<'_>, grammar: AstSrc<'_>) -> Result<String> { | |||
108 | } | 75 | } |
109 | 76 | ||
110 | impl AstToken for #name { | 77 | impl AstToken for #name { |
111 | fn can_cast(kind: SyntaxKind) -> bool { | 78 | fn can_cast(kind: SyntaxKind) -> bool { kind == #kind } |
112 | match kind { | ||
113 | #kind => true, | ||
114 | _ => false, | ||
115 | } | ||
116 | } | ||
117 | fn cast(syntax: SyntaxToken) -> Option<Self> { | 79 | fn cast(syntax: SyntaxToken) -> Option<Self> { |
118 | if Self::can_cast(syntax.kind()) { Some(Self { syntax }) } else { None } | 80 | if Self::can_cast(syntax.kind()) { Some(Self { syntax }) } else { None } |
119 | } | 81 | } |
@@ -122,6 +84,99 @@ fn generate_ast(kinds: KindsSrc<'_>, grammar: AstSrc<'_>) -> Result<String> { | |||
122 | } | 84 | } |
123 | }); | 85 | }); |
124 | 86 | ||
87 | let enums = grammar.token_enums.iter().map(|en| { | ||
88 | let variants = en.variants.iter().map(|var| format_ident!("{}", var)).collect::<Vec<_>>(); | ||
89 | let name = format_ident!("{}", en.name); | ||
90 | let kinds = variants | ||
91 | .iter() | ||
92 | .map(|name| format_ident!("{}", to_upper_snake_case(&name.to_string()))) | ||
93 | .collect::<Vec<_>>(); | ||
94 | assert!(en.traits.is_empty()); | ||
95 | |||
96 | quote! { | ||
97 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] | ||
98 | pub enum #name { | ||
99 | #(#variants(#variants),)* | ||
100 | } | ||
101 | |||
102 | #( | ||
103 | impl From<#variants> for #name { | ||
104 | fn from(node: #variants) -> #name { | ||
105 | #name::#variants(node) | ||
106 | } | ||
107 | } | ||
108 | )* | ||
109 | |||
110 | impl std::fmt::Display for #name { | ||
111 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { | ||
112 | std::fmt::Display::fmt(self.syntax(), f) | ||
113 | } | ||
114 | } | ||
115 | |||
116 | impl AstToken for #name { | ||
117 | fn can_cast(kind: SyntaxKind) -> bool { | ||
118 | match kind { | ||
119 | #(#kinds)|* => true, | ||
120 | _ => false, | ||
121 | } | ||
122 | } | ||
123 | fn cast(syntax: SyntaxToken) -> Option<Self> { | ||
124 | let res = match syntax.kind() { | ||
125 | #( | ||
126 | #kinds => #name::#variants(#variants { syntax }), | ||
127 | )* | ||
128 | _ => return None, | ||
129 | }; | ||
130 | Some(res) | ||
131 | } | ||
132 | fn syntax(&self) -> &SyntaxToken { | ||
133 | match self { | ||
134 | #( | ||
135 | #name::#variants(it) => &it.syntax, | ||
136 | )* | ||
137 | } | ||
138 | } | ||
139 | } | ||
140 | } | ||
141 | }); | ||
142 | |||
143 | crate::reformat(quote! { | ||
144 | use crate::{SyntaxToken, SyntaxKind::{self, *}, ast::AstToken}; | ||
145 | |||
146 | #(#tokens)* | ||
147 | #(#enums)* | ||
148 | }) | ||
149 | } | ||
150 | |||
151 | fn generate_nodes(kinds: KindsSrc<'_>, grammar: AstSrc<'_>) -> Result<String> { | ||
152 | let all_token_kinds: Vec<_> = kinds | ||
153 | .punct | ||
154 | .into_iter() | ||
155 | .map(|(_, kind)| kind) | ||
156 | .copied() | ||
157 | .map(|x| x.into()) | ||
158 | .chain( | ||
159 | kinds | ||
160 | .keywords | ||
161 | .into_iter() | ||
162 | .chain(kinds.contextual_keywords.into_iter()) | ||
163 | .map(|name| Cow::Owned(format!("{}_KW", to_upper_snake_case(&name)))), | ||
164 | ) | ||
165 | .chain(kinds.literals.into_iter().copied().map(|x| x.into())) | ||
166 | .chain(kinds.tokens.into_iter().copied().map(|x| x.into())) | ||
167 | .collect(); | ||
168 | |||
169 | let mut token_kinds = HashSet::new(); | ||
170 | for kind in &all_token_kinds { | ||
171 | let kind = &**kind; | ||
172 | let name = to_pascal_case(kind); | ||
173 | token_kinds.insert(name); | ||
174 | } | ||
175 | |||
176 | for en in grammar.token_enums { | ||
177 | token_kinds.insert(en.name.to_string()); | ||
178 | } | ||
179 | |||
125 | let nodes = grammar.nodes.iter().map(|node| { | 180 | let nodes = grammar.nodes.iter().map(|node| { |
126 | let name = format_ident!("{}", node.name); | 181 | let name = format_ident!("{}", node.name); |
127 | let kind = format_ident!("{}", to_upper_snake_case(&name.to_string())); | 182 | let kind = format_ident!("{}", to_upper_snake_case(&name.to_string())); |
@@ -151,7 +206,7 @@ fn generate_ast(kinds: KindsSrc<'_>, grammar: AstSrc<'_>) -> Result<String> { | |||
151 | } | 206 | } |
152 | } | 207 | } |
153 | FieldSrc::Optional(_) | FieldSrc::Shorthand => { | 208 | FieldSrc::Optional(_) | FieldSrc::Shorthand => { |
154 | let is_token = element_kinds_map[&ty.to_string()].has_tokens; | 209 | let is_token = token_kinds.contains(&ty.to_string()); |
155 | if is_token { | 210 | if is_token { |
156 | quote! { | 211 | quote! { |
157 | pub fn #method_name(&self) -> Option<#ty> { | 212 | pub fn #method_name(&self) -> Option<#ty> { |
@@ -175,18 +230,9 @@ fn generate_ast(kinds: KindsSrc<'_>, grammar: AstSrc<'_>) -> Result<String> { | |||
175 | pub(crate) syntax: SyntaxNode, | 230 | pub(crate) syntax: SyntaxNode, |
176 | } | 231 | } |
177 | 232 | ||
178 | impl std::fmt::Display for #name { | ||
179 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { | ||
180 | std::fmt::Display::fmt(self.syntax(), f) | ||
181 | } | ||
182 | } | ||
183 | |||
184 | impl AstNode for #name { | 233 | impl AstNode for #name { |
185 | fn can_cast(kind: SyntaxKind) -> bool { | 234 | fn can_cast(kind: SyntaxKind) -> bool { |
186 | match kind { | 235 | kind == #kind |
187 | #kind => true, | ||
188 | _ => false, | ||
189 | } | ||
190 | } | 236 | } |
191 | fn cast(syntax: SyntaxNode) -> Option<Self> { | 237 | fn cast(syntax: SyntaxNode) -> Option<Self> { |
192 | if Self::can_cast(syntax.kind()) { Some(Self { syntax }) } else { None } | 238 | if Self::can_cast(syntax.kind()) { Some(Self { syntax }) } else { None } |
@@ -214,48 +260,6 @@ fn generate_ast(kinds: KindsSrc<'_>, grammar: AstSrc<'_>) -> Result<String> { | |||
214 | quote!(impl ast::#trait_name for #name {}) | 260 | quote!(impl ast::#trait_name for #name {}) |
215 | }); | 261 | }); |
216 | 262 | ||
217 | let element_kinds = &element_kinds_map[&en.name.to_string()]; | ||
218 | assert!( | ||
219 | element_kinds.has_nodes ^ element_kinds.has_tokens, | ||
220 | "{}: {:#?}", | ||
221 | name, | ||
222 | element_kinds | ||
223 | ); | ||
224 | let specific_ast_trait = { | ||
225 | let (ast_trait, syntax_type) = if element_kinds.has_tokens { | ||
226 | (quote!(AstToken), quote!(SyntaxToken)) | ||
227 | } else { | ||
228 | (quote!(AstNode), quote!(SyntaxNode)) | ||
229 | }; | ||
230 | |||
231 | quote! { | ||
232 | impl #ast_trait for #name { | ||
233 | fn can_cast(kind: SyntaxKind) -> bool { | ||
234 | match kind { | ||
235 | #(#kinds)|* => true, | ||
236 | _ => false, | ||
237 | } | ||
238 | } | ||
239 | fn cast(syntax: #syntax_type) -> Option<Self> { | ||
240 | let res = match syntax.kind() { | ||
241 | #( | ||
242 | #kinds => #name::#variants(#variants { syntax }), | ||
243 | )* | ||
244 | _ => return None, | ||
245 | }; | ||
246 | Some(res) | ||
247 | } | ||
248 | fn syntax(&self) -> &#syntax_type { | ||
249 | match self { | ||
250 | #( | ||
251 | #name::#variants(it) => &it.syntax, | ||
252 | )* | ||
253 | } | ||
254 | } | ||
255 | } | ||
256 | } | ||
257 | }; | ||
258 | |||
259 | quote! { | 263 | quote! { |
260 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] | 264 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] |
261 | pub enum #name { | 265 | pub enum #name { |
@@ -270,18 +274,50 @@ fn generate_ast(kinds: KindsSrc<'_>, grammar: AstSrc<'_>) -> Result<String> { | |||
270 | } | 274 | } |
271 | )* | 275 | )* |
272 | 276 | ||
273 | impl std::fmt::Display for #name { | 277 | impl AstNode for #name { |
274 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { | 278 | fn can_cast(kind: SyntaxKind) -> bool { |
275 | std::fmt::Display::fmt(self.syntax(), f) | 279 | match kind { |
280 | #(#kinds)|* => true, | ||
281 | _ => false, | ||
282 | } | ||
283 | } | ||
284 | fn cast(syntax: SyntaxNode) -> Option<Self> { | ||
285 | let res = match syntax.kind() { | ||
286 | #( | ||
287 | #kinds => #name::#variants(#variants { syntax }), | ||
288 | )* | ||
289 | _ => return None, | ||
290 | }; | ||
291 | Some(res) | ||
292 | } | ||
293 | fn syntax(&self) -> &SyntaxNode { | ||
294 | match self { | ||
295 | #( | ||
296 | #name::#variants(it) => &it.syntax, | ||
297 | )* | ||
298 | } | ||
276 | } | 299 | } |
277 | } | 300 | } |
278 | 301 | ||
279 | #specific_ast_trait | ||
280 | |||
281 | #(#traits)* | 302 | #(#traits)* |
282 | } | 303 | } |
283 | }); | 304 | }); |
284 | 305 | ||
306 | let displays = grammar | ||
307 | .enums | ||
308 | .iter() | ||
309 | .map(|it| format_ident!("{}", it.name)) | ||
310 | .chain(grammar.nodes.iter().map(|it| format_ident!("{}", it.name))) | ||
311 | .map(|name| { | ||
312 | quote! { | ||
313 | impl std::fmt::Display for #name { | ||
314 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { | ||
315 | std::fmt::Display::fmt(self.syntax(), f) | ||
316 | } | ||
317 | } | ||
318 | } | ||
319 | }); | ||
320 | |||
285 | let defined_nodes: HashSet<_> = grammar.nodes.iter().map(|node| node.name).collect(); | 321 | let defined_nodes: HashSet<_> = grammar.nodes.iter().map(|node| node.name).collect(); |
286 | 322 | ||
287 | for node in kinds | 323 | for node in kinds |
@@ -294,15 +330,16 @@ fn generate_ast(kinds: KindsSrc<'_>, grammar: AstSrc<'_>) -> Result<String> { | |||
294 | } | 330 | } |
295 | 331 | ||
296 | let ast = quote! { | 332 | let ast = quote! { |
297 | #[allow(unused_imports)] | ||
298 | use crate::{ | 333 | use crate::{ |
299 | SyntaxNode, SyntaxToken, SyntaxElement, NodeOrToken, SyntaxKind::{self, *}, | 334 | SyntaxNode, SyntaxKind::{self, *}, |
300 | ast::{self, AstNode, AstToken, AstChildren, support}, | 335 | ast::{self, AstNode, AstChildren, support}, |
301 | }; | 336 | }; |
302 | 337 | ||
303 | #(#tokens)* | 338 | use super::tokens::*; |
339 | |||
304 | #(#nodes)* | 340 | #(#nodes)* |
305 | #(#enums)* | 341 | #(#enums)* |
342 | #(#displays)* | ||
306 | }; | 343 | }; |
307 | 344 | ||
308 | let pretty = crate::reformat(ast)?; | 345 | let pretty = crate::reformat(ast)?; |
diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index 9d087daa2..ec824a518 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs | |||
@@ -67,6 +67,7 @@ fn reformat(text: impl std::fmt::Display) -> Result<String> { | |||
67 | let mut rustfmt = Command::new("rustup") | 67 | let mut rustfmt = Command::new("rustup") |
68 | .args(&["run", TOOLCHAIN, "--", "rustfmt", "--config-path"]) | 68 | .args(&["run", TOOLCHAIN, "--", "rustfmt", "--config-path"]) |
69 | .arg(project_root().join("rustfmt.toml")) | 69 | .arg(project_root().join("rustfmt.toml")) |
70 | .args(&["--config", "fn_single_line=true"]) | ||
70 | .stdin(Stdio::piped()) | 71 | .stdin(Stdio::piped()) |
71 | .stdout(Stdio::piped()) | 72 | .stdout(Stdio::piped()) |
72 | .spawn()?; | 73 | .spawn()?; |