From 87e3428bfdeb17ab718b928dcee0b08359c0d820 Mon Sep 17 00:00:00 2001 From: zackteo Date: Sun, 14 Jun 2020 21:17:19 +0800 Subject: [PATCH 1/3] Parsing of characters 1. need to insert \ and for str need to insert "char..." - but the str one is prob the str function 2. Incorrect Uni code mapping Rust vs Java 3. 3 tests still not working.... 4. Need to handle '\f' and '\b' or rather \formfeed and \backspace --- src/reader.rs | 118 ++++++++++++++++++++++++++++++++++++++++++++++-- src/type_tag.rs | 4 +- src/value.rs | 48 +++++++++++--------- 3 files changed, 142 insertions(+), 28 deletions(-) diff --git a/src/reader.rs b/src/reader.rs index 9dbd530..ecba1da 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -35,6 +35,7 @@ use std::io::BufRead; // integer_parser // And our 'try readers' // try_read_i32 +// try_read_char // try_read_string // try_read_map // try_read_list @@ -210,7 +211,7 @@ fn identifier_tail(input: &str) -> IResult<&str, &str> { /// Parses valid Clojure identifiers /// Example Successes: ab, cat, -12+3, |blah|, -/// Example Failures: 'a, 12b, ,cat +/// Example Failures: 'a, 12b, ,cat pub fn identifier_parser(input: &str) -> IResult<&str, String> { named!(identifier_head<&str, char>, map!( @@ -237,10 +238,10 @@ pub fn identifier_parser(input: &str) -> IResult<&str, String> { /// namespace.subnamespace/a cat/b a.b.c/|ab123| pub fn symbol_parser(input: &str) -> IResult<&str, Symbol> { named!(namespace_parser <&str,String>, - do_parse!( - ns: identifier_parser >> - complete!(tag!("/")) >> - (ns))); + do_parse!( + ns: identifier_parser >> + complete!(tag!("/")) >> + (ns))); let (rest_input, ns) = opt(namespace_parser)(input)?; let (rest_input, name) = identifier_parser(rest_input)?; @@ -415,6 +416,97 @@ pub fn try_read_nil(input: &str) -> IResult<&str, Value> { Ok((rest_input, Value::Nil)) } +mod try_read_char_tests { + use crate::reader::try_read_char; + use crate::value::Value; + + // #[test] + // fn try_read_char_test() { + // assert_eq!(Value::Char("\\f"), try_read_char("\\formfeed")) + // } + + #[test] + fn try_read_char_space() { + assert_eq!(Value::Char(' '), try_read_char("\\space").ok().unwrap().1); + } + + #[test] + fn try_read_char_return() { + assert_eq!(Value::Char('\r'), try_read_char("\\return").ok().unwrap().1); + } + + #[test] + fn try_read_char_hashtag() { + assert_eq!(Value::Char('#'), try_read_char("\\#").ok().unwrap().1); + } + #[test] + fn try_read_char_n() { + assert_eq!(Value::Char('n'), try_read_char("\\n").ok().unwrap().1); + } + #[test] + fn try_read_char_f() { + assert_eq!(Value::Char('r'), try_read_char("\\r").ok().unwrap().1); + } + #[test] + fn try_read_unicode() { + assert_eq!(Value::Char('Ω'), try_read_char("\\u03A9").ok().unwrap().1); + } + #[test] + fn try_read_unicode2() { + assert_eq!(Value::Char('张'), try_read_char("\\u5920").ok().unwrap().1); + } + #[test] + fn try_read_char_fail() { + assert!(try_read_char("d").is_err()); + } +} + +/// Tries to parse &str into Value::Char +/// Example Successes: +/// "\newline" => Value::Char("\n") +/// Example Failures: +/// +pub fn try_read_char(input: &str) -> IResult<&str, Value> { + named!(backslash<&str, &str>, preceded!(consume_clojure_whitespaces_parser, tag!("\\"))); + + fn str_to_unicode(s: &str) -> char { + u32::from_str_radix(s, 16) + .ok() + .and_then(std::char::from_u32) + .unwrap() + } + + named!(unicode < &str, char>, alt!( + preceded!( + tag!("u"), + alt!( + map!(take_while_m_n!(4,4, |c :char| c.is_digit(16)), str_to_unicode) + ) + ) + )); + + named!(special_escapes < &str, char>, alt!( + tag!("newline") => { |_| '\n'} | + tag!("space") => { |_| ' ' } | + tag!("tab") => { |_| '\t'} | + //tag!("formfeed") => { |_| '\f'} | + //tag!("backspace") => { |_| '\b'} | + tag!("return") => { |_| '\r' } )); + + named!(normal_char < &str, char>, + // accept anything after \ + map!(take_while_m_n!(1,1,|_| true), first_char)); + + named!(char_parser<&str,char>, + alt!(unicode | special_escapes | normal_char)); + + let (rest_input, _) = backslash(input)?; + + let (rest_input, char_value) = char_parser(rest_input)?; + + Ok((rest_input, Value::Char(char_value))) +} + // @TODO allow escaped strings /// Tries to parse &str into Value::String /// Example Successes: @@ -586,6 +678,7 @@ pub fn try_read(input: &str) -> IResult<&str, Value> { try_read_quoted, try_read_nil, try_read_map, + try_read_char, try_read_string, try_read_f64, try_read_i32, @@ -831,6 +924,21 @@ mod tests { } } + // mod try_read_char_tests { + // use crate::reader::try_read_char; + // use crate::value::Value; + // + // #[test] + // fn try_read_char_test() { + // assert_eq!(Value::Char('f'), try_read_character("\\f")) + // } + // + // #[test] + // fn try_read_newline_test() { + // assert_eq!(Value::Char('\n'), try_read_character("\newline")) + // } + // } + mod try_read_tests { use crate::persistent_list; use crate::persistent_list_map; diff --git a/src/type_tag.rs b/src/type_tag.rs index a4cba11..f25c102 100644 --- a/src/type_tag.rs +++ b/src/type_tag.rs @@ -7,6 +7,7 @@ pub enum TypeTag { Boolean, Symbol, Var, + Char, Keyword, IFn, Condition, @@ -28,10 +29,11 @@ impl fmt::Display for TypeTag { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let str = match self { I32 => std::string::String::from("rust.std.i32"), - Boolean => std::string::String::from("rust.std.bool"), F64 => std::string::String::from("rust.std.f64"), + Boolean => std::string::String::from("rust.std.bool"), Symbol => std::string::String::from("clojure.lang.Symbol"), Var => std::string::String::from("clojure.lang.Var"), + Char => std::string::String::from("clojure.lang.Char"), Keyword => std::string::String::from("clojure.lang.Keyword"), IFn => std::string::String::from("clojure.lang.Function"), Condition => std::string::String::from("clojure.lang.Condition"), diff --git a/src/value.rs b/src/value.rs index c702847..235de0e 100644 --- a/src/value.rs +++ b/src/value.rs @@ -8,8 +8,8 @@ use crate::persistent_list::{PersistentList, ToPersistentList, ToPersistentListI use crate::persistent_list_map::{PersistentListMap, ToPersistentListMapIter}; use crate::persistent_vector::PersistentVector; use crate::symbol::Symbol; -use crate::var::Var; use crate::type_tag::TypeTag; +use crate::var::Var; use core::fmt::Display; extern crate rand; @@ -34,6 +34,7 @@ pub enum Value { Boolean(bool), Symbol(Symbol), Var(Var), + Char(char), Keyword(Keyword), IFn(Rc), // @@ -78,6 +79,7 @@ impl PartialEq for Value { (Boolean(b), Boolean(b2)) => b == b2, (Symbol(sym), Symbol(sym2)) => sym == sym2, (Var(var), Var(var2)) => var == var2, + (Char(c), Char(c2)) => c == c2, (Keyword(kw), Keyword(kw2)) => kw == kw2, // Equality not defined on functions, similar to Clojure // Change this perhaps? Diverge? @@ -122,6 +124,7 @@ impl Hash for Value { Boolean(b) => b.hash(state), Symbol(sym) => sym.hash(state), Var(var) => var.hash(state), + Char(c) => c.hash(state), Keyword(kw) => kw.hash(state), IFn(_) => { let mut rng = rand::thread_rng(); @@ -163,6 +166,7 @@ impl fmt::Display for Value { Boolean(val) => val.to_string(), Symbol(sym) => sym.to_string(), Var(var) => var.to_string(), + Char(c) => c.to_string(), Keyword(kw) => kw.to_string(), IFn(_) => std::string::String::from("#function[]"), LexicalEvalFn => std::string::String::from("#function[lexical-eval*]"), @@ -208,6 +212,7 @@ impl Value { Value::Boolean(_) => TypeTag::Boolean, Value::Symbol(_) => TypeTag::Symbol, Value::Var(_) => TypeTag::Var, + Value::Char(_) => TypeTag::Char, Value::Keyword(_) => TypeTag::Keyword, Value::IFn(_) => TypeTag::IFn, Value::LexicalEvalFn => TypeTag::IFn, @@ -348,7 +353,6 @@ impl Value { match &**defname { Value::Symbol(sym) => { - println!("Def: meta on sym is {}",sym.meta()); // TODO: environment.insert with meta? let s = if doc_string != Value::Nil { let ss = Symbol::intern_with_ns( @@ -400,9 +404,9 @@ impl Value { .into_list() .eval(Rc::clone(&environment)); let macro_value = match ¯o_invokable_body { - Value::IFn(ifn) => Rc::new(Value::Macro(Rc::clone(&ifn))), - _ => Rc::new(Value::Condition(std::string::String::from("Compiler Error: your macro_value somehow compiled into something else entirely. I don't even know how that happened, this behavior is hardcoded, that's impressive"))) - }; + Value::IFn(ifn) => Rc::new(Value::Macro(Rc::clone(&ifn))), + _ => Rc::new(Value::Condition(std::string::String::from("Compiler Error: your macro_value somehow compiled into something else entirely. I don't even know how that happened, this behavior is hardcoded, that's impressive"))) + }; Some( vec![ Symbol::intern("def").to_rc_value(), @@ -446,23 +450,23 @@ impl Value { } let fn_body = - // (fn [x y] ) -> nil - if arg_rc_values.len() <= 1 { - Rc::new(Value::Nil) - // (fn [x y] expr) -> expr - } else if arg_rc_values.len() == 2 { - Rc::clone(arg_rc_values.get(1).unwrap()) - // (fn [x y] expr1 expr2 expr3) -> (do expr1 expr2 expr3) - } else { - // (&[expr1 expr2 expr3] - let body_exprs = arg_rc_values.get(1..).unwrap(); - // vec![do] - let mut do_body = vec![Symbol::intern("do").to_rc_value()]; - // vec![do expr1 expr2 expr3] - do_body.extend_from_slice(body_exprs); - // (do expr1 expr2 expr3) - do_body.into_list().to_rc_value() - }; + // (fn [x y] ) -> nil + if arg_rc_values.len() <= 1 { + Rc::new(Value::Nil) + // (fn [x y] expr) -> expr + } else if arg_rc_values.len() == 2 { + Rc::clone(arg_rc_values.get(1).unwrap()) + // (fn [x y] expr1 expr2 expr3) -> (do expr1 expr2 expr3) + } else { + // (&[expr1 expr2 expr3] + let body_exprs = arg_rc_values.get(1..).unwrap(); + // vec![do] + let mut do_body = vec![Symbol::intern("do").to_rc_value()]; + // vec![do expr1 expr2 expr3] + do_body.extend_from_slice(body_exprs); + // (do expr1 expr2 expr3) + do_body.into_list().to_rc_value() + }; Some(Rc::new( lambda::Fn { From 626ab9b588236e8b5d06a80f72aa713d8b851d54 Mon Sep 17 00:00:00 2001 From: zackteo Date: Mon, 15 Jun 2020 10:49:04 +0800 Subject: [PATCH 2/3] Fixed try_read_char to parse \\n without the space --- src/reader.rs | 135 ++++++++++++++++++++++++++++---------------------- 1 file changed, 77 insertions(+), 58 deletions(-) diff --git a/src/reader.rs b/src/reader.rs index ecba1da..39d0d8a 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -8,26 +8,26 @@ //! not; since this is about being a 'free-er' Clojure, especially since it can't compete with it in raw //! power, neither speed or ecosystem, it might be worth it to leave in reader macros. -use nom::combinator::{verify}; +use nom::combinator::verify; use nom::{ branch::alt, bytes::complete::tag, combinator::opt, map, sequence::preceded, take_until, - Err::Incomplete, IResult + Err::Incomplete, IResult, }; +use crate::error_message; use crate::keyword::Keyword; use crate::maps::MapEntry; use crate::persistent_list::ToPersistentList; -use crate::persistent_list_map::{PersistentListMap,ToPersistentListMap, ToPersistentListMapIter}; +use crate::persistent_list_map::{PersistentListMap, ToPersistentListMap, ToPersistentListMapIter}; use crate::persistent_vector::ToPersistentVector; -use crate::protocol::ProtocolCastable; use crate::protocol::Protocol; -use crate::symbol::Symbol; -use crate::error_message; -use crate::value::{ToValue, Value}; -use std::rc::Rc; +use crate::protocol::ProtocolCastable; use crate::protocols; +use crate::symbol::Symbol; use crate::traits::IObj; +use crate::value::{ToValue, Value}; use std::io::BufRead; +use std::rc::Rc; // // Note; the difference between ours 'parsers' // identifier_parser @@ -485,13 +485,13 @@ pub fn try_read_char(input: &str) -> IResult<&str, Value> { ) )); - named!(special_escapes < &str, char>, alt!( + named!(special_escapes < &str, char>, complete!( alt!( tag!("newline") => { |_| '\n'} | tag!("space") => { |_| ' ' } | tag!("tab") => { |_| '\t'} | //tag!("formfeed") => { |_| '\f'} | //tag!("backspace") => { |_| '\b'} | - tag!("return") => { |_| '\r' } )); + tag!("return") => { |_| '\r' } ))); named!(normal_char < &str, char>, // accept anything after \ @@ -533,7 +533,7 @@ pub fn try_read_var(input: &str) -> IResult<&str, Value> { let (rest_input, val) = try_read(rest_input)?; // #'x just expands to (var x), just like 'x is just a shorthand for (quote x) // So here we return (var val) - Ok((rest_input,list_val!(sym!("var") val))) + Ok((rest_input, list_val!(sym!("var") val))) } // @TODO Perhaps generalize this, or even generalize it as a reader macro @@ -565,53 +565,54 @@ pub fn try_read_meta(input: &str) -> IResult<&str, Value> { named!(meta_start<&str, &str>, preceded!(consume_clojure_whitespaces_parser, tag!("^"))); let (rest_input, _) = meta_start(input)?; - let (rest_input,meta_value) = try_read(rest_input)?; + let (rest_input, meta_value) = try_read(rest_input)?; let mut meta = PersistentListMap::Empty; match &meta_value { Value::Symbol(symbol) => { // @TODO Note; do NOT hardcode this, make some global for TAG_KEY, like Clojure does - meta = persistent_list_map!{"tag" => symbol}; - }, + meta = persistent_list_map! {"tag" => symbol}; + } Value::Keyword(keyword) => { - meta = persistent_list_map!( - MapEntry { - key: meta_value.to_rc_value(), - val: true.to_rc_value() - } - ); - }, + meta = persistent_list_map!(MapEntry { + key: meta_value.to_rc_value(), + val: true.to_rc_value() + }); + } Value::String(string) => { // @TODO Note; do NOT hardcode this, make some global for TAG_KEY, like Clojure does - meta = persistent_list_map!{"tag" => string}; - }, + meta = persistent_list_map! {"tag" => string}; + } Value::PersistentListMap(plist_map) => { meta = plist_map.clone(); - // Then we're already set + // Then we're already set } _ => { // @TODO check instanceof IPersistentMap here instead // @TODO Clojure has basically this one off error here, but another thing we wish to do - // is write clear errors - return Ok((rest_input,error_message::custom("When trying to read meta: metadata must be Symbol, Keyword, String, or Map"))) + // is write clear errors + return Ok(( + rest_input, + error_message::custom( + "When trying to read meta: metadata must be Symbol, Keyword, String, or Map", + ), + )); } } - let (rest_input,iobj_value) = try_read(rest_input)?; + let (rest_input, iobj_value) = try_read(rest_input)?; - // Extra clone, implement these functions for plain Values - if let Some(iobj_value) = iobj_value.to_rc_value().try_as_protocol::() { + // Extra clone, implement these functions for plain Values + if let Some(iobj_value) = iobj_value + .to_rc_value() + .try_as_protocol::() + { // @TODO get actual line and column info let line = 1; let column = 1; // @TODO merge the meta iobj_value *already* has - // @TODO define some better macros and / or functions for map handling - meta = merge!( - meta, - map_entry!("line",line), - map_entry!("column",column) - ); - Ok((rest_input,iobj_value.with_meta(meta).unwrap().to_value())) - } - else { + // @TODO define some better macros and / or functions for map handling + meta = merge!(meta, map_entry!("line", line), map_entry!("column", column)); + Ok((rest_input, iobj_value.with_meta(meta).unwrap().to_value())) + } else { Ok((rest_input,error_message::custom("In meta reader: metadata can only be applied to types who are an instance of IMeta"))) } } @@ -940,15 +941,15 @@ mod tests { // } mod try_read_tests { + use crate::keyword::Keyword; use crate::persistent_list; use crate::persistent_list_map; use crate::persistent_list_map::IPersistentMap; - use crate::keyword::Keyword; use crate::persistent_vector; use crate::reader::try_read; use crate::symbol::Symbol; - use crate::value::{ToValue,Value}; use crate::value::Value::{PersistentList, PersistentListMap, PersistentVector}; + use crate::value::{ToValue, Value}; #[test] fn try_read_empty_map_test() { @@ -1056,16 +1057,18 @@ mod tests { } #[test] fn try_read_meta_symbol() { - let with_meta = "^cat a"; + let with_meta = "^cat a"; match try_read(with_meta).ok().unwrap().1 { Value::Symbol(symbol) => { - assert!(symbol.meta().contains_key(&Keyword::intern("tag").to_rc_value())); + assert!(symbol + .meta() + .contains_key(&Keyword::intern("tag").to_rc_value())); assert_eq!( Symbol::intern("cat").to_value(), *symbol.meta().get(&Keyword::intern("tag").to_rc_value()) ); - }, - _ => panic!("try_read_meta \"^cat a\" should return a symbol") + } + _ => panic!("try_read_meta \"^cat a\" should return a symbol"), } } #[test] @@ -1073,14 +1076,16 @@ mod tests { let with_meta = "^\"cat\" a"; match try_read(with_meta).ok().unwrap().1 { Value::Symbol(symbol) => { - assert_eq!(String::from("a"),symbol.name); - assert!(symbol.meta().contains_key(&Keyword::intern("tag").to_rc_value())); + assert_eq!(String::from("a"), symbol.name); + assert!(symbol + .meta() + .contains_key(&Keyword::intern("tag").to_rc_value())); assert_eq!( "cat".to_value(), *symbol.meta().get(&Keyword::intern("tag").to_rc_value()) ); - }, - _ => panic!("try_read_meta '^\"cat\" a' should return a symbol") + } + _ => panic!("try_read_meta '^\"cat\" a' should return a symbol"), } } #[test] @@ -1088,13 +1093,25 @@ mod tests { let with_meta = "^{:cat 1 :dog 2} a"; match try_read(with_meta).ok().unwrap().1 { Value::Symbol(symbol) => { - assert!(symbol.meta().contains_key(&Keyword::intern("cat").to_rc_value())); - assert_eq!(Value::I32(1),*symbol.meta().get(&Keyword::intern("cat").to_rc_value())); - assert!(symbol.meta().contains_key(&Keyword::intern("dog").to_rc_value())); - assert_eq!(Value::I32(2),*symbol.meta().get(&Keyword::intern("dog").to_rc_value())); - assert!(!symbol.meta().contains_key(&Keyword::intern("chicken").to_rc_value())); - }, - _ => panic!("try_read_meta \"^{:cat 1 :dog 2} a\" should return a symbol") + assert!(symbol + .meta() + .contains_key(&Keyword::intern("cat").to_rc_value())); + assert_eq!( + Value::I32(1), + *symbol.meta().get(&Keyword::intern("cat").to_rc_value()) + ); + assert!(symbol + .meta() + .contains_key(&Keyword::intern("dog").to_rc_value())); + assert_eq!( + Value::I32(2), + *symbol.meta().get(&Keyword::intern("dog").to_rc_value()) + ); + assert!(!symbol + .meta() + .contains_key(&Keyword::intern("chicken").to_rc_value())); + } + _ => panic!("try_read_meta \"^{:cat 1 :dog 2} a\" should return a symbol"), } } #[test] @@ -1102,9 +1119,11 @@ mod tests { let with_meta = "^:cat a"; match try_read(with_meta).ok().unwrap().1 { Value::Symbol(symbol) => { - assert!(symbol.meta().contains_key(&Keyword::intern("cat").to_rc_value())); - }, - _ => panic!("try_read_meta \"^:cat a\" should return a symbol") + assert!(symbol + .meta() + .contains_key(&Keyword::intern("cat").to_rc_value())); + } + _ => panic!("try_read_meta \"^:cat a\" should return a symbol"), } } } From 198f2a9e9651e40e3ad15ce65c57dc325d6e4eee Mon Sep 17 00:00:00 2001 From: zackteo Date: Thu, 2 Jul 2020 20:01:01 +0800 Subject: [PATCH 3/3] Fixed Test and shifted tests down Previously might have confused \u5920 with \u5F20 --- src/reader.rs | 86 ++++++++++++++++++++++++--------------------------- 1 file changed, 41 insertions(+), 45 deletions(-) diff --git a/src/reader.rs b/src/reader.rs index 39d0d8a..581433f 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -416,51 +416,6 @@ pub fn try_read_nil(input: &str) -> IResult<&str, Value> { Ok((rest_input, Value::Nil)) } -mod try_read_char_tests { - use crate::reader::try_read_char; - use crate::value::Value; - - // #[test] - // fn try_read_char_test() { - // assert_eq!(Value::Char("\\f"), try_read_char("\\formfeed")) - // } - - #[test] - fn try_read_char_space() { - assert_eq!(Value::Char(' '), try_read_char("\\space").ok().unwrap().1); - } - - #[test] - fn try_read_char_return() { - assert_eq!(Value::Char('\r'), try_read_char("\\return").ok().unwrap().1); - } - - #[test] - fn try_read_char_hashtag() { - assert_eq!(Value::Char('#'), try_read_char("\\#").ok().unwrap().1); - } - #[test] - fn try_read_char_n() { - assert_eq!(Value::Char('n'), try_read_char("\\n").ok().unwrap().1); - } - #[test] - fn try_read_char_f() { - assert_eq!(Value::Char('r'), try_read_char("\\r").ok().unwrap().1); - } - #[test] - fn try_read_unicode() { - assert_eq!(Value::Char('Ω'), try_read_char("\\u03A9").ok().unwrap().1); - } - #[test] - fn try_read_unicode2() { - assert_eq!(Value::Char('张'), try_read_char("\\u5920").ok().unwrap().1); - } - #[test] - fn try_read_char_fail() { - assert!(try_read_char("d").is_err()); - } -} - /// Tries to parse &str into Value::Char /// Example Successes: /// "\newline" => Value::Char("\n") @@ -911,6 +866,47 @@ mod tests { } } + mod try_read_char_tests { + use crate::reader::try_read_char; + use crate::value::Value; + + // #[test] + // fn try_read_char_test() { + // assert_eq!(Value::Char("\\f"), try_read_char("\\formfeed")) + // } + + #[test] + fn try_read_char_space() { + assert_eq!(Value::Char(' '), try_read_char("\\space").ok().unwrap().1); + } + + #[test] + fn try_read_char_return() { + assert_eq!(Value::Char('\r'), try_read_char("\\return").ok().unwrap().1); + } + + #[test] + fn try_read_char_hashtag() { + assert_eq!(Value::Char('#'), try_read_char("\\#").ok().unwrap().1); + } + #[test] + fn try_read_char_n() { + assert_eq!(Value::Char('n'), try_read_char("\\n").ok().unwrap().1); + } + #[test] + fn try_read_char_f() { + assert_eq!(Value::Char('r'), try_read_char("\\r").ok().unwrap().1); + } + #[test] + fn try_read_unicode() { + assert_eq!(Value::Char('张'), try_read_char("\\u5F20").ok().unwrap().1); + } + #[test] + fn try_read_char_fail() { + assert!(try_read_char("d").is_err()); + } + } + mod try_read_symbol_tests { use crate::reader::try_read_symbol; use crate::symbol::Symbol;