Skip to content

Commit b1cbc55

Browse files
Robert Grimmbenesch
authored andcommitted
Turn type Ident into struct Ident
The Ident type was previously an alias for a String. Turn it into a full fledged struct, so that the parser can preserve the distinction between identifier value and quote style already made by the tokenizer's Word structure.
1 parent 9b2287f commit b1cbc55

6 files changed

Lines changed: 160 additions & 88 deletions

File tree

src/ast/mod.rs

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,61 @@ where
6868
DisplaySeparated { slice, sep: ", " }
6969
}
7070

71-
/// Identifier name, in the originally quoted form (e.g. `"id"`)
72-
pub type Ident = String;
71+
/// An identifier, decomposed into its value or character data and the quote style.
72+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
73+
pub struct Ident {
74+
/// The value of the identifier without quotes.
75+
pub value: String,
76+
/// The starting quote if any. Valid quote characters are the single quote,
77+
/// double quote, backtick, and opening square bracket.
78+
pub quote_style: Option<char>,
79+
}
80+
81+
impl Ident {
82+
/// Create a new identifier with the given value and no quotes.
83+
pub fn new<S>(value: S) -> Self
84+
where
85+
S: Into<String>,
86+
{
87+
Ident {
88+
value: value.into(),
89+
quote_style: None,
90+
}
91+
}
92+
93+
/// Create a new quoted identifier with the given quote and value. This function
94+
/// panics if the given quote is not a valid quote character.
95+
pub fn with_quote<S>(quote: char, value: S) -> Self
96+
where
97+
S: Into<String>,
98+
{
99+
assert!(quote == '\'' || quote == '"' || quote == '`' || quote == '[');
100+
Ident {
101+
value: value.into(),
102+
quote_style: Some(quote),
103+
}
104+
}
105+
}
106+
107+
impl From<&str> for Ident {
108+
fn from(value: &str) -> Self {
109+
Ident {
110+
value: value.to_string(),
111+
quote_style: None,
112+
}
113+
}
114+
}
115+
116+
impl fmt::Display for Ident {
117+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
118+
match self.quote_style {
119+
Some(q) if q == '"' || q == '\'' || q == '`' => write!(f, "{}{}{}", q, self.value, q),
120+
Some(q) if q == '[' => write!(f, "[{}]", self.value),
121+
None => f.write_str(&self.value),
122+
_ => panic!("unexpected quote style"),
123+
}
124+
}
125+
}
73126

74127
/// A name of a table, view, custom type, etc., possibly multi-part, i.e. db.schema.obj
75128
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -175,7 +228,7 @@ pub enum Expr {
175228
impl fmt::Display for Expr {
176229
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
177230
match self {
178-
Expr::Identifier(s) => f.write_str(s),
231+
Expr::Identifier(s) => write!(f, "{}", s),
179232
Expr::Wildcard => f.write_str("*"),
180233
Expr::QualifiedWildcard(q) => write!(f, "{}.*", display_separated(q, ".")),
181234
Expr::CompoundIdentifier(s) => write!(f, "{}", display_separated(s, ".")),
@@ -864,7 +917,7 @@ impl fmt::Display for SetVariableValue {
864917
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
865918
use SetVariableValue::*;
866919
match self {
867-
Ident(ident) => f.write_str(ident),
920+
Ident(ident) => write!(f, "{}", ident),
868921
Literal(literal) => write!(f, "{}", literal),
869922
}
870923
}

src/parser.rs

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -201,11 +201,11 @@ impl Parser {
201201
// identifier, a function call, or a simple identifier:
202202
_ => match self.peek_token() {
203203
Some(Token::LParen) | Some(Token::Period) => {
204-
let mut id_parts: Vec<Ident> = vec![w.as_ident()];
204+
let mut id_parts: Vec<Ident> = vec![w.to_ident()];
205205
let mut ends_with_wildcard = false;
206206
while self.consume_token(&Token::Period) {
207207
match self.next_token() {
208-
Some(Token::Word(w)) => id_parts.push(w.as_ident()),
208+
Some(Token::Word(w)) => id_parts.push(w.to_ident()),
209209
Some(Token::Mult) => {
210210
ends_with_wildcard = true;
211211
break;
@@ -225,7 +225,7 @@ impl Parser {
225225
Ok(Expr::CompoundIdentifier(id_parts))
226226
}
227227
}
228-
_ => Ok(Expr::Identifier(w.as_ident())),
228+
_ => Ok(Expr::Identifier(w.to_ident())),
229229
},
230230
}, // End of Token::Word
231231
Token::Mult => Ok(Expr::Wildcard),
@@ -871,7 +871,7 @@ impl Parser {
871871
let table_name = self.parse_object_name()?;
872872
let (columns, constraints) = self.parse_columns()?;
873873
self.expect_keywords(&["STORED", "AS"])?;
874-
let file_format = self.parse_identifier()?.parse::<FileFormat>()?;
874+
let file_format = self.parse_identifier()?.value.parse::<FileFormat>()?;
875875

876876
self.expect_keyword("LOCATION")?;
877877
let location = self.parse_literal_string()?;
@@ -976,7 +976,7 @@ impl Parser {
976976
}
977977

978978
columns.push(ColumnDef {
979-
name: column_name.as_ident(),
979+
name: column_name.to_ident(),
980980
data_type,
981981
collation,
982982
options,
@@ -1318,11 +1318,11 @@ impl Parser {
13181318
Some(Token::Word(ref w))
13191319
if after_as || !reserved_kwds.contains(&w.keyword.as_str()) =>
13201320
{
1321-
Ok(Some(w.as_ident()))
1321+
Ok(Some(w.to_ident()))
13221322
}
13231323
// MSSQL supports single-quoted strings as aliases for columns
13241324
// We accept them as table aliases too, although MSSQL does not.
1325-
Some(Token::SingleQuotedString(ref s)) => Ok(Some(format!("'{}'", s))),
1325+
Some(Token::SingleQuotedString(ref s)) => Ok(Some(Ident::with_quote('\'', s.clone()))),
13261326
not_an_ident => {
13271327
if after_as {
13281328
return self.expected("an identifier after AS", not_an_ident);
@@ -1366,7 +1366,7 @@ impl Parser {
13661366
/// Parse a simple one-word identifier (possibly quoted, possibly a keyword)
13671367
pub fn parse_identifier(&mut self) -> Result<Ident, ParserError> {
13681368
match self.next_token() {
1369-
Some(Token::Word(w)) => Ok(w.as_ident()),
1369+
Some(Token::Word(w)) => Ok(w.to_ident()),
13701370
unexpected => self.expected("identifier", unexpected),
13711371
}
13721372
}
@@ -1609,15 +1609,15 @@ impl Parser {
16091609
let token = self.peek_token();
16101610
let value = match (self.parse_value(), token) {
16111611
(Ok(value), _) => SetVariableValue::Literal(value),
1612-
(Err(_), Some(Token::Word(ident))) => SetVariableValue::Ident(ident.as_ident()),
1612+
(Err(_), Some(Token::Word(ident))) => SetVariableValue::Ident(ident.to_ident()),
16131613
(Err(_), other) => self.expected("variable value", other)?,
16141614
};
16151615
Ok(Statement::SetVariable {
16161616
local: modifier == Some("LOCAL"),
16171617
variable,
16181618
value,
16191619
})
1620-
} else if variable == "TRANSACTION" && modifier.is_none() {
1620+
} else if variable.value == "TRANSACTION" && modifier.is_none() {
16211621
Ok(Statement::SetTransaction {
16221622
modes: self.parse_transaction_modes()?,
16231623
})
@@ -2066,8 +2066,11 @@ impl Parser {
20662066
}
20672067

20682068
impl Word {
2069-
pub fn as_ident(&self) -> Ident {
2070-
self.to_string()
2069+
pub fn to_ident(&self) -> Ident {
2070+
Ident {
2071+
value: self.value.clone(),
2072+
quote_style: self.quote_style,
2073+
}
20712074
}
20722075
}
20732076

0 commit comments

Comments
 (0)