From 4dd811b73976f6d820a115bf7c682b8834df07f1 Mon Sep 17 00:00:00 2001 From: Eduard Burtescu Date: Wed, 13 Jan 2016 03:54:00 +0200 Subject: [PATCH] Rewrite the whole combinators module to use HRTB only where necessary. --- src/lib.rs | 2 + src/parser/combinators.rs | 221 ++++++++++++++++---------------------- src/parser/lexer.rs | 46 +++----- 3 files changed, 112 insertions(+), 157 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a310c76..dc72eb6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,4 @@ +#![feature(core, unboxed_closures)] + pub mod ast; pub mod parser; diff --git a/src/parser/combinators.rs b/src/parser/combinators.rs index cbb71bb..f67d1f9 100644 --- a/src/parser/combinators.rs +++ b/src/parser/combinators.rs @@ -4,53 +4,22 @@ use self::BufferedParserState::{Beginning, Middle, EndMatch, EndFail}; use self::MatchResult::{Undecided, Matched, Failed}; use self::StringParserState::{AtOffset, AtEndMatched, AtEndFailed}; -use std::marker::PhantomData; - -// ----------- Types with lifetimes ------------- - -// Borrowing encoding of paramaterized types from -// https://github.com/rust-lang/rfcs/blob/master/text/0195-associated-items.md#encoding-higher-kinded-types - -pub trait TypeWithLifetime<'a> { - type Type: Copy; -} - -pub type At<'a,T> where T: TypeWithLifetime<'a> = T::Type; - -pub struct Always (PhantomData); - -impl<'a,T> TypeWithLifetime<'a> for Always where T: Copy { - type Type = T; -} - -pub type Unit = Always<()>; - -pub trait Function where S: for<'a> TypeWithLifetime<'a>, T: for<'a> TypeWithLifetime<'a> { - fn apply<'a>(&self, arg: At<'a,S>) -> At<'a,T>; -} - // ----------- Types for consumers ------------ -pub trait Consumer where T: for<'a> TypeWithLifetime<'a> { - fn accept<'a>(&mut self, arg: At<'a,T>); +pub trait Consumer { + fn accept(&mut self, arg: T); } -impl Consumer for F where T: for<'a> TypeWithLifetime<'a>, F: for<'a> FnMut(At<'a,T>) { - fn accept<'a>(&mut self, arg: At<'a,T>) { +impl Consumer for F where F: FnMut(T) { + fn accept(&mut self, arg: T) { self(arg) } } -struct DiscardConsumer (PhantomData); - -impl Consumer for DiscardConsumer where T: for<'a> TypeWithLifetime<'a> { - fn accept<'a>(&mut self, _: At<'a,T>) {} -} +pub struct DiscardConsumer; -impl DiscardConsumer { - fn new() -> DiscardConsumer { - DiscardConsumer(PhantomData) - } +impl Consumer for DiscardConsumer { + fn accept(&mut self, _: T) {} } // ----------- Types for parsers ------------ @@ -80,27 +49,30 @@ pub enum MatchResult { Failed(Option), } -pub trait Parser where S: for<'a> TypeWithLifetime<'a>, T: for<'a> TypeWithLifetime<'a> { +pub trait ParseTo: Parser { // If push_to returns Failed(Some(s)), it is side-effect-free // push_to should be called with non-empty input, // when it returns Matched(Some(s)) or Failed(Some(s)) then s is non-empty. // In the case where T is "list-like" (e.g. &str or &[T]) // push_to(a ++ b, d) is the same as push_to(a,d); push_to(b,d) - fn push_to<'a,D>(&mut self, value: At<'a,S>, downstream: &mut D) -> MatchResult> where D: Consumer; + fn push_to(&mut self, value: S, downstream: &mut D) -> MatchResult; // Resets the parser state back to its initial state // Returns true if there was a match. - fn done_to(&mut self, downstream: &mut D) -> bool where D: Consumer; - // Helper methods - fn push<'a>(&mut self, value: At<'a,S>) -> MatchResult> { - self.push_to(value, &mut DiscardConsumer::new()) + fn done_to(&mut self, downstream: &mut D) -> bool; +} + +// Helper methods +pub trait Parser { + fn push(&mut self, value: S) -> MatchResult where Self: ParseTo { + self.push_to(value, &mut DiscardConsumer) } - fn done(&mut self) -> bool { - self.done_to(&mut DiscardConsumer::new()) + fn done(&mut self) -> bool where Self: ParseTo { + self.done_to(&mut DiscardConsumer) } - fn and_then(self, other: R) -> AndThenParser where Self: Sized, R: Parser { + fn and_then(self, other: R) -> AndThenParser where Self: Sized, R: Parser { AndThenParser{ lhs: self, rhs: CommittedParser{ parser: other }, in_lhs: true } } - fn or_else(self, other: R) -> OrElseParser where Self: Sized, R: Parser { + fn or_else(self, other: R) -> OrElseParser where Self: Sized, R: Parser { OrElseParser{ lhs: self, rhs: other, in_lhs: true } } fn star(self) -> StarParser where Self: Sized { @@ -109,55 +81,57 @@ pub trait Parser where S: for<'a> TypeWithLifetime<'a>, T: for<'a> TypeWith fn plus(self) -> PlusParser where Self: Sized { PlusParser{ parser: self, matched: false, first_time: true } } - fn map(self, function: F) -> MapParser where F: Function, U: for<'a> TypeWithLifetime<'a>, Self: Sized { - MapParser{ function: function, parser: self, phantom: PhantomData } + fn map(self, function: F) -> MapParser where F: Fn(T) -> U, Self: Sized { + MapParser{ function: function, parser: self } } - fn filter(self, function: F) -> FilterParser where F: Fn(At) -> bool, Self: Sized { - FilterParser{ function: function, parser: self, phantom: PhantomData } + fn filter(self, function: F) -> FilterParser where F: Fn(T) -> bool, T: Copy, Self: Sized { + FilterParser{ function: function, parser: self } } } -pub trait StrParser: Parser { +pub trait StrParser<'a>: ParseTo<&'a str,DiscardConsumer> { fn buffer(self) -> BufferedParser where Self: Sized { BufferedParser{ parser: self, state: Beginning } } } -impl

StrParser for P where P: Parser {} +impl<'a,P> StrParser<'a> for P where P: ParseTo<&'a str,DiscardConsumer> {} -pub trait ParserConsumer where S: for<'a> TypeWithLifetime<'a>, T: for<'a> TypeWithLifetime<'a> { - fn accept

(&mut self, parser: P) where P: Parser; +pub trait ParserConsumer { + fn accept

(&mut self, parser: P) where P: ParseTo; } // ----------- Map --------------- #[derive(Debug)] -pub struct MapConsumer<'b,T,U,F,C> where F: 'b, C: 'b { - function: &'b F, - consumer: &'b mut C, - phantom: PhantomData<(T,U)>, +pub struct MapConsumer<'a,F:'a,C:'a> { + function: &'a F, + consumer: &'a mut C } -impl<'b,T,U,F,C> Consumer for MapConsumer<'b,T,U,F,C> where T: for<'a> TypeWithLifetime<'a>, U: for<'a> TypeWithLifetime<'a>, F: Function, C: Consumer { - fn accept<'a>(&mut self, arg: At<'a,T>) { - self.consumer.accept(self.function.apply(arg)); +// NOTE(eddyb): a generic over U where F: Fn(T) -> U doesn't allow HRTB in both T and U. +// See https://github.com/rust-lang/rust/issues/30867 for more details. +impl<'a,T,F,C> Consumer for MapConsumer<'a,F,C> +where F: Fn<(T,)>, C: Consumer<>::Output> { + fn accept(&mut self, arg: T) { + self.consumer.accept((self.function)(arg)); } } #[derive(Clone, Debug)] -pub struct MapParser { +pub struct MapParser { function: F, - parser: P, - phantom: PhantomData<(S,T,U)>, + parser: P } -impl Parser for MapParser where S: for<'a> TypeWithLifetime<'a>, T: for<'a> TypeWithLifetime<'a>, U: for<'a> TypeWithLifetime<'a>, F: Function, P: Parser { - fn push_to<'a,D>(&mut self, value: At<'a,S>, downstream: &mut D) -> MatchResult> where D: Consumer { - let mut downstream: MapConsumer = MapConsumer{ function: &mut self.function, consumer: downstream, phantom: PhantomData }; +impl Parser for MapParser where P: Parser {} +impl ParseTo for MapParser where P: for<'a> ParseTo> { + fn push_to(&mut self, value: S, downstream: &mut D) -> MatchResult { + let mut downstream = MapConsumer{ function: &mut self.function, consumer: downstream }; self.parser.push_to(value, &mut downstream) } - fn done_to(&mut self, downstream: &mut D) -> bool where D: Consumer { - let mut downstream: MapConsumer = MapConsumer{ function: &mut self.function, consumer: downstream, phantom: PhantomData }; + fn done_to(&mut self, downstream: &mut D) -> bool { + let mut downstream = MapConsumer{ function: &mut self.function, consumer: downstream }; self.parser.done_to(&mut downstream) } } @@ -165,14 +139,13 @@ impl Parser for MapParser where S: for<'a> TypeWithLi // ----------- Filter --------------- #[derive(Debug)] -pub struct FilterConsumer<'b,T,F,C> where F: 'b, C: 'b { - function: &'b F, - consumer: &'b mut C, - phantom: PhantomData, +pub struct FilterConsumer<'a,F,C> where F: 'a, C: 'a { + function: &'a F, + consumer: &'a mut C } -impl<'b,T,F,C> Consumer for FilterConsumer<'b,T,F,C> where T: for<'a> TypeWithLifetime<'a>, F: Fn(At) -> bool, C: Consumer { - fn accept<'a>(&mut self, arg: At<'a,T>) { +impl<'a,T,F,C> Consumer for FilterConsumer<'a,F,C> where F: Fn(T) -> bool, T: Copy, C: Consumer { + fn accept(&mut self, arg: T) { if (self.function)(arg) { self.consumer.accept(arg) } @@ -180,19 +153,19 @@ impl<'b,T,F,C> Consumer for FilterConsumer<'b,T,F,C> where T: for<'a> TypeWit } #[derive(Clone, Debug)] -pub struct FilterParser { +pub struct FilterParser { function: F, - parser: P, - phantom: PhantomData<(S,T)>, + parser: P } -impl Parser for FilterParser where S: for<'a> TypeWithLifetime<'a>, T: for<'a> TypeWithLifetime<'a>, F: Fn(At) -> bool, P: Parser { - fn push_to<'a,D>(&mut self, value: At<'a,S>, downstream: &mut D) -> MatchResult> where D: Consumer { - let mut downstream: FilterConsumer = FilterConsumer{ function: &mut self.function, consumer: downstream, phantom: PhantomData }; +impl Parser for FilterParser where P: Parser {} +impl ParseTo for FilterParser where P: for<'a> ParseTo> { + fn push_to(&mut self, value: S, downstream: &mut D) -> MatchResult { + let mut downstream = FilterConsumer{ function: &mut self.function, consumer: downstream }; self.parser.push_to(value, &mut downstream) } - fn done_to(&mut self, downstream: &mut D) -> bool where D: Consumer { - let mut downstream: FilterConsumer = FilterConsumer{ function: &mut self.function, consumer: downstream, phantom: PhantomData }; + fn done_to(&mut self, downstream: &mut D) -> bool { + let mut downstream = FilterConsumer{ function: &mut self.function, consumer: downstream }; self.parser.done_to(&mut downstream) } } @@ -204,15 +177,16 @@ pub struct CommittedParser

{ parser: P, } -impl Parser for CommittedParser

where P: Parser, S: for<'a> TypeWithLifetime<'a>, T: for<'a> TypeWithLifetime<'a> { - fn push_to<'a,D>(&mut self, value: At<'a,S>, downstream: &mut D) -> MatchResult> where D: Consumer { +impl Parser for CommittedParser

where P: Parser {} +impl ParseTo for CommittedParser

where P: ParseTo { + fn push_to(&mut self, value: S, downstream: &mut D) -> MatchResult { match self.parser.push_to(value, downstream) { Undecided => Undecided, Matched(rest) => Matched(rest), Failed(_) => Failed(None), } } - fn done_to(&mut self, downstream: &mut D) -> bool where D: Consumer { + fn done_to(&mut self, downstream: &mut D) -> bool { self.parser.done_to(downstream) } } @@ -226,8 +200,9 @@ pub struct AndThenParser { in_lhs: bool, } -impl Parser for AndThenParser where L: Parser, R: Parser, S: for<'a> TypeWithLifetime<'a>, T: for<'a> TypeWithLifetime<'a> { - fn push_to<'a,D>(&mut self, value: At<'a,S>, downstream: &mut D) -> MatchResult> where D: Consumer { +impl Parser for AndThenParser where L: Parser, R: Parser {} +impl ParseTo for AndThenParser where L: ParseTo, R: ParseTo { + fn push_to(&mut self, value: S, downstream: &mut D) -> MatchResult { if self.in_lhs { match self.lhs.push_to(value, downstream) { Undecided => Undecided, @@ -239,7 +214,7 @@ impl Parser for AndThenParser where L: Parser, R: Parser self.rhs.push_to(value, downstream) } } - fn done_to(&mut self, downstream: &mut D) -> bool where D: Consumer { + fn done_to(&mut self, downstream: &mut D) -> bool { if self.in_lhs { self.lhs.done_to(downstream) && self.rhs.done_to(downstream) } else { @@ -258,8 +233,9 @@ pub struct OrElseParser { in_lhs: bool, } -impl Parser for OrElseParser where L: Parser, R: Parser, S: for<'a> TypeWithLifetime<'a>, T: for<'a> TypeWithLifetime<'a> { - fn push_to<'a,D>(&mut self, value: At<'a,S>, downstream: &mut D) -> MatchResult> where D: Consumer { +impl Parser for OrElseParser where L: Parser, R: Parser {} +impl ParseTo for OrElseParser where L: ParseTo, R: ParseTo { + fn push_to(&mut self, value: S, downstream: &mut D) -> MatchResult { if self.in_lhs { match self.lhs.push_to(value, downstream) { Failed(Some(rest)) => { self.in_lhs = false; self.lhs.done_to(downstream); self.rhs.push_to(rest, downstream) }, @@ -269,7 +245,7 @@ impl Parser for OrElseParser where L: Parser, R: Parser< self.rhs.push_to(value, downstream) } } - fn done_to(&mut self, downstream: &mut D) -> bool where D: Consumer { + fn done_to(&mut self, downstream: &mut D) -> bool { if self.in_lhs { self.lhs.done_to(downstream) } else { @@ -288,8 +264,9 @@ pub struct StarParser

{ first_time: bool, } -impl Parser for StarParser

where P: Parser, S: for<'a> TypeWithLifetime<'a>, T: for<'a> TypeWithLifetime<'a> { - fn push_to<'a,D>(&mut self, mut value: At<'a,S>, downstream: &mut D) -> MatchResult> where D: Consumer { +impl Parser for StarParser

where P: Parser {} +impl ParseTo for StarParser

where P: ParseTo { + fn push_to(&mut self, mut value: S, downstream: &mut D) -> MatchResult { loop { match self.parser.push_to(value, downstream) { Undecided => { self.matched = false; return Undecided }, @@ -300,7 +277,7 @@ impl Parser for StarParser

where P: Parser, S: for<'a> TypeW } } } - fn done_to(&mut self, downstream: &mut D) -> bool where D: Consumer { + fn done_to(&mut self, downstream: &mut D) -> bool { let result = self.parser.done_to(downstream) | self.matched; self.first_time = true; self.matched = true; @@ -315,8 +292,9 @@ pub struct PlusParser

{ first_time: bool, } -impl Parser for PlusParser

where P: Parser, S: for<'a> TypeWithLifetime<'a>, T: for<'a> TypeWithLifetime<'a> { - fn push_to<'a,D>(&mut self, mut value: At<'a,S>, downstream: &mut D) -> MatchResult> where D: Consumer { +impl Parser for PlusParser

where P: Parser {} +impl ParseTo for PlusParser

where P: ParseTo { + fn push_to(&mut self, mut value: S, downstream: &mut D) -> MatchResult { loop { match self.parser.push_to(value, downstream) { Undecided => { self.matched = false; return Undecided }, @@ -327,7 +305,7 @@ impl Parser for PlusParser

where P: Parser, S: for<'a> TypeW } } } - fn done_to(&mut self, downstream: &mut D) -> bool where D: Consumer { + fn done_to(&mut self, downstream: &mut D) -> bool { let result = self.parser.done_to(downstream) | self.matched; self.first_time = true; self.matched = false; @@ -337,14 +315,8 @@ impl Parser for PlusParser

where P: Parser, S: for<'a> TypeW // ----------- Matching strings ------------- -pub struct Str; - -impl<'a> TypeWithLifetime<'a> for Str { - type Type = &'a str; -} - -impl Consumer for String { - fn accept<'a>(&mut self, arg: &'a str) { +impl<'a> Consumer<&'a str> for String { + fn accept(&mut self, arg: &'a str) { self.push_str(arg); } } @@ -364,8 +336,9 @@ pub struct StringParser { state: StringParserState, } -impl Parser for StringParser { - fn push_to<'a,D>(&mut self, string: &'a str, downstream: &mut D) -> MatchResult<&'a str> where D: Consumer { +impl<'a> Parser<&'a str> for StringParser {} +impl<'a,D> ParseTo<&'a str,D> for StringParser where D: Consumer<()> { + fn push_to(&mut self, string: &'a str, downstream: &mut D) -> MatchResult<&'a str> { match self.state { AtOffset(index) if string == &self.constant[index..] => { downstream.accept(()); self.state = AtEndMatched(true); Matched(None) }, AtOffset(index) if string.starts_with(&self.constant[index..]) => { downstream.accept(()); self.state = AtEndMatched(false); Matched(Some(&string[(self.constant.len() - index)..])) }, @@ -377,7 +350,7 @@ impl Parser for StringParser { AtEndFailed(false) => { Failed(None) }, } } - fn done_to(&mut self, _: &mut D) -> bool where D: Consumer { + fn done_to(&mut self, _: &mut D) -> bool { let result = self.state == AtEndMatched(true); self.state = AtOffset(0); result @@ -394,8 +367,9 @@ pub struct CharParser

{ state: StringParserState, } -impl

Parser for CharParser

where P: Fn(char) -> bool { - fn push_to<'a,D>(&mut self, string: &'a str, downstream: &mut D) -> MatchResult<&'a str> where D: Consumer { +impl<'a,P> Parser<&'a str> for CharParser

where P: Fn(char) -> bool {} +impl<'a,D,P> ParseTo<&'a str,D> for CharParser

where P: Fn(char) -> bool, D: Consumer<()> { + fn push_to(&mut self, string: &'a str, downstream: &mut D) -> MatchResult<&'a str> { let ch = string.chars().next().unwrap(); let len = ch.len_utf8(); match self.state { @@ -406,7 +380,7 @@ impl

Parser for CharParser

where P: Fn(char) -> bool { AtEndFailed(_) => { Failed(Some(string)) }, } } - fn done_to(&mut self, _: &mut D) -> bool where D: Consumer { + fn done_to(&mut self, _: &mut D) -> bool { let result = self.state == AtEndMatched(true); self.state = AtOffset(0); result @@ -416,7 +390,8 @@ pub fn character

(pattern: P) -> CharParser

{ CharParser{ pattern: pattern, state: AtOffset(0) } } -// If m is a Parser then m.buffer() is a Parser. +// If m is a ParseTo<&'a str, DiscardConsumer> then +// m.buffer() is a ParseTo<&'a str, C: Consumer<&str>>. // It does as little buffering as it can, but it does allocate as buffer for the case // where the boundary marker of the input is misaligned with that of the parser. // For example, m is matching string literals, and the input is '"abc' followed by 'def"' @@ -436,8 +411,9 @@ pub struct BufferedParser

{ state: BufferedParserState, } -impl

Parser for BufferedParser

where P: Parser { - fn push_to<'a,D>(&mut self, string: &'a str, downstream: &mut D) -> MatchResult<&'a str> where D: Consumer { +impl<'a,P> Parser<&'a str> for BufferedParser

where P: Parser<&'a str> {} +impl<'a,P,D> ParseTo<&'a str,D> for BufferedParser

where P: ParseTo<&'a str,DiscardConsumer>, D: for<'b> Consumer<&'b str> { + fn push_to(&mut self, string: &'a str, downstream: &mut D) -> MatchResult<&'a str> { match mem::replace(&mut self.state, EndMatch) { Beginning => { let result = self.parser.push(string); @@ -466,7 +442,7 @@ impl

Parser for BufferedParser

where P: Parser { EndFail(false) => Failed(None), } } - fn done_to(&mut self, downstream: &mut D) -> bool where D: Consumer { + fn done_to(&mut self, downstream: &mut D) -> bool { let result = self.parser.done(); if result { if let Middle(ref buffer) = self.state { downstream.accept(&*buffer) } } self.state = Beginning; @@ -642,14 +618,7 @@ fn test_plus() { #[test] fn test_map() { - // Having to do this by hand for now, - // due to problems with normalization of associated types. - // e.g. https://play.rust-lang.org/?gist=94d94f44371224c7798c - struct Hello; - impl Function for Hello { - fn apply<'a>(&self, _:()) -> &'a str { "hello" } - } - let mut parser = string("abc").map(Hello); + let mut parser = string("abc").map(|_| "hello"); let mut result = String::new(); assert_eq!(parser.done_to(&mut result), false); assert_eq!(result, ""); diff --git a/src/parser/lexer.rs b/src/parser/lexer.rs index d06b7b9..8104511 100644 --- a/src/parser/lexer.rs +++ b/src/parser/lexer.rs @@ -1,40 +1,23 @@ -use parser::combinators::{TypeWithLifetime, Str, Unit, Function, Parser, StrParser, Consumer, ParserConsumer, string, character}; -use self::TokenAt::{LParen, RParen, Whitespace, Identifier}; +use parser::combinators::{Parser, StrParser, Consumer, ParserConsumer, string, character}; +use self::Token::{LParen, RParen, Whitespace, Identifier}; #[derive(Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Debug)] -pub enum TokenAt<'a> { +pub enum Token<'a> { LParen, RParen, Whitespace, Identifier(&'a str), } -pub struct Token; - -impl<'a> TypeWithLifetime<'a> for Token { - type Type = TokenAt<'a>; -} - -impl Function for TokenAt<'static> { - fn apply<'a>(&self, _: ()) -> TokenAt<'a> { - self.clone() - } -} - -struct MkIdentifier; - -impl Function for MkIdentifier { - fn apply<'a>(&self, name: &'a str) -> TokenAt<'a> { - Identifier(name) - } -} +fn mk_identifier<'a>(s: &'a str) -> Token<'a> { Identifier(s) } #[allow(non_snake_case)] -pub fn lexer(consumer: &mut C) where C: ParserConsumer { - let LPAREN = string("(").map(LParen); - let RPAREN = string(")").map(RParen); - let WHITESPACE = character(char::is_whitespace).map(Whitespace); - let IDENTIFIER = character(char::is_alphabetic).and_then(character(char::is_alphanumeric).star()).buffer().map(MkIdentifier); +pub fn lexer(consumer: &mut C) where C: for<'a> ParserConsumer<&'a str,D>, D: for<'a> Consumer> { + let LPAREN = string("(").map(|_| LParen); + let RPAREN = string(")").map(|_| RParen); + let WHITESPACE = character(char::is_whitespace).map(|_| Whitespace); + let IDENTIFIER = character(char::is_alphabetic).and_then(character(char::is_alphanumeric).star()) + .buffer().map(mk_identifier); let TOKEN = LPAREN.or_else(RPAREN).or_else(WHITESPACE).or_else(IDENTIFIER); consumer.accept(TOKEN.star()) } @@ -42,14 +25,15 @@ pub fn lexer(consumer: &mut C) where C: ParserConsumer { #[test] fn test_lexer() { - impl Consumer for Vec> { - fn accept<'a>(&mut self, token: TokenAt<'a>) { + use parser::combinators::ParseTo; + impl<'a> Consumer> for Vec> { + fn accept(&mut self, token: Token<'a>) { assert_eq!(self.remove(0), token); } } struct TestConsumer; - impl ParserConsumer for TestConsumer { - fn accept

(&mut self, mut lex: P) where P: Parser { + impl<'a> ParserConsumer<&'a str, Vec>> for TestConsumer { + fn accept

(&mut self, mut lex: P) where P: ParseTo<&'a str,Vec>> { let mut tokens = vec![LParen, Identifier("a123"), Whitespace, Whitespace, Identifier("bcd"), RParen]; lex.push_to("(a123 bcd)", &mut tokens); assert_eq!(tokens, []);