Skip to content

Commit f67d5e3

Browse files
fix test and add hauntedc support
1 parent a27dea2 commit f67d5e3

2 files changed

Lines changed: 55 additions & 20 deletions

File tree

src/hauntedc.rs

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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

215247
fn 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()

test/character_literals.c

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,53 +7,45 @@
77
reach trRValue with a CharacterLiteral: return expression,
88
arithmetic operand, and switch case label.
99
10-
Note on postconditions: the inline-Pulse spec parser in
11-
src/hauntedc.rs is a separate code path that doesn't yet recognise
12-
character literals at all, so we deliberately use the integer
13-
ASCII equivalents in _requires / _ensures. Supporting them in spec
14-
contexts would require new lexer + parser rules and is out of
15-
scope here. */
16-
1710
/* Position 1: return expression — plain CharacterLiteral as rvalue */
1811
int32_t letter_a(void)
19-
_ensures(return == 65)
12+
_ensures(return == 'A')
2013
{
2114
return 'A';
2215
}
2316

2417
int32_t newline_char(void)
25-
_ensures(return == 10)
18+
_ensures(return == '\n')
2619
{
2720
return '\n';
2821
}
2922

3023
int32_t null_char(void)
31-
_ensures(return == 0)
24+
_ensures(return == '\0')
3225
{
3326
return '\0';
3427
}
3528

3629
/* Position 2: inside an arithmetic expression — confirms the literal
3730
participates in binary ops the same way an IntegerLiteral does. */
3831
int32_t alphabet_index(int32_t c)
39-
_requires(c >= 65 && c <= 90)
40-
_ensures(return == c - 65)
32+
_requires(c >= 'A' && c <= 'Z')
33+
_ensures(return == c - 'A')
4134
{
4235
return c - 'A';
4336
}
4437

45-
/* Position 3: switch case label — case values also flow through
46-
trRValue (cpp/impl.cpp ~line 1096), so character literals must
47-
work as case labels too. */
38+
/* Position 3: switch case label */
4839
int32_t classify_char(int32_t c)
49-
_ensures(c == 65 ==> return == 1)
50-
_ensures(c == 10 ==> return == 2)
51-
_ensures(c == 0 ==> return == 3)
40+
_ensures(c == 'A' ==> return == 1)
41+
_ensures(c == '\n' ==> return == 2)
42+
_ensures(c == '\0' ==> return == 3)
5243
{
5344
switch (c) {
5445
case 'A': return 1;
5546
case '\n': return 2;
5647
case '\0': return 3;
5748
default: return 0;
5849
}
50+
return 0;
5951
}

0 commit comments

Comments
 (0)