@@ -143,6 +143,7 @@ enum Token<'src> {
143143
144144 String ( & ' src str ) ,
145145 Integer ( & ' src str , IntegerSuffix ) ,
146+ Char ( u32 ) ,
146147 Ident ( & ' src str ) ,
147148
148149 Punct ( Punct ) ,
@@ -156,6 +157,7 @@ impl<'src> Display for Token<'src> {
156157 Token :: Whitespace => write ! ( f, " " ) ,
157158 Token :: String ( tok) => write ! ( f, "{}" , tok) ,
158159 Token :: Integer ( i, suffix) => write ! ( f, "{}{}" , i, suffix) ,
160+ Token :: Char ( n) => write ! ( f, "'\\ x{:x}'" , n) ,
159161 Token :: Ident ( id) => write ! ( f, "{}" , id) ,
160162 Token :: Punct ( punct) => write ! ( f, "{}" , punct. to_str( ) ) ,
161163 Token :: Error => write ! ( f, "(LEXING ERROR)" ) ,
@@ -203,13 +205,43 @@ fn lex_core_token<'src>() -> impl Parser<'src, &'src str, Token<'src>> {
203205 . delimited_by ( just ( '"' ) , just ( '"' ) )
204206 . map ( Token :: String ) ;
205207
208+ // C-style character literal: 'X' or '\E' for a small set of common
209+ // escapes. The value is the codepoint of the character (for plain
210+ // chars) or the escape-mapped byte value. Wide-char prefixes (L'…',
211+ // u'…', U'…'), multi-character constants, octal escapes, and
212+ // \xHH+ / \uHHHH numeric escapes are unsupported
213+ let char_escape = just ( '\\' ) . ignore_then ( choice ( (
214+ just ( 'n' ) . to ( b'\n' as u32 ) ,
215+ just ( 't' ) . to ( b'\t' as u32 ) ,
216+ just ( 'r' ) . to ( b'\r' as u32 ) ,
217+ just ( '0' ) . to ( 0u32 ) ,
218+ just ( '\\' ) . to ( b'\\' as u32 ) ,
219+ just ( '\'' ) . to ( b'\'' as u32 ) ,
220+ just ( '"' ) . to ( b'"' as u32 ) ,
221+ just ( 'a' ) . to ( 0x07u32 ) ,
222+ just ( 'b' ) . to ( 0x08u32 ) ,
223+ just ( 'f' ) . to ( 0x0Cu32 ) ,
224+ just ( 'v' ) . to ( 0x0Bu32 ) ,
225+ just ( '?' ) . to ( b'?' as u32 ) ,
226+ ) ) ) ;
227+ let plain_char = none_of ( "'\\ \n " ) . map ( |c : char | c as u32 ) ;
228+ let char_literal = char_escape
229+ . or ( plain_char)
230+ . delimited_by ( just ( '\'' ) , just ( '\'' ) )
231+ . map ( Token :: Char ) ;
232+
206233 let fallback = text:: whitespace ( )
207234 . not ( )
208235 . repeated ( )
209236 . at_least ( 1 )
210237 . to ( Token :: Error ) ;
211238
212- integer_literal. or ( op) . or ( ident) . or ( string) . or ( fallback)
239+ integer_literal
240+ . or ( op)
241+ . or ( ident)
242+ . or ( string)
243+ . or ( char_literal)
244+ . or ( fallback)
213245}
214246
215247fn ws < ' tokens , ' src : ' tokens , I : ValueInput < ' tokens , Token = Token < ' src > , Span = Span > , Span > ( )
@@ -600,7 +632,18 @@ fn expr_parser<
600632 }
601633 } ) ;
602634
603- let constant = integer_constant;
635+ // Character constants are emitted as SpecInt-typed integer
636+ // literals — matches how an unsuffixed `65` is treated in a
637+ // spec, which is what `'A'` is.
638+ let char_constant = select ! { Token :: Char ( n) => n } . map_with ( |n, extra| -> Expr {
639+ let loc = sift. resolve_source_info ( & extra. span ( ) ) ;
640+ let ty = TypeT :: SpecInt . with_loc ( loc. clone ( ) ) ;
641+ ExprT :: IntLit ( Rc :: new ( BigInt :: from ( n) ) , ty)
642+ . with_loc ( loc)
643+ . into ( )
644+ } ) ;
645+
646+ let constant = integer_constant. or ( char_constant) ;
604647
605648 let parenthesized = expr
606649 . clone ( )
0 commit comments