use rdlfmt::syntax::{SyntaxKind, lex};
const SAMPLE: &str = include_str!("../samples/sample.rdl");
const SNIPPETS: &[&str] = &[
"",
" ",
"\n\n\n",
"reg",
"\\reg",
"regwidth",
"r w rw hw sw na wr rw1 w1",
"8'hA5",
"32'(x)",
"0x1F 0X1f 0 007 1_000",
"8'b1010 4'd12 16'hDEAD_BEEF",
"\"a\\\"b\" \"\" \"\\\\\"",
"/**/",
"/***/",
"/* a * b */",
"/* multi\nline */",
"// trailing",
"// no newline at eof",
"a = b; a == b; a += b; a %= b; a ** b; a * b",
"~& ~| ~^ ^~ && || << >> <= >= != -> :: @ ?",
"x[7:0]",
"field {} STATUS[7:0] = 8'hA5;",
"`include \"other.rdl\"",
"`define W 32",
"`define W 32\n",
"`define BODY a + \\\n b\nreg r {};",
"`undef W",
"`line 5 \"f.rdl\" 0",
"`ifdef A\n`else\n`endif",
"f[`W-1:0]",
"`MAX(1, 2)",
"$ ` ยง",
"`",
"`define",
"`define trailing backslash \\",
"\"unterminated",
"/* unterminated",
"reg $$ field",
];
fn concat(src: &str) -> String {
lex(src).iter().map(|(_, text)| text).collect()
}
#[test]
fn round_trips_the_sample() {
assert_eq!(concat(SAMPLE), SAMPLE);
}
#[test]
fn round_trips_snippets() {
for src in SNIPPETS {
assert_eq!(&concat(src), src, "round-trip failed for {src:?}");
}
}
#[test]
fn ranges_are_contiguous_and_complete() {
for src in SNIPPETS.iter().chain(std::iter::once(&SAMPLE)) {
let tokens = lex(src);
let mut offset = 0;
for i in 0..tokens.len() {
let range = tokens.range(i);
let start = usize::from(range.start());
assert_eq!(start, offset, "gap before token {i} in {src:?}");
offset = usize::from(range.end());
}
assert_eq!(offset, src.len(), "tokens do not reach end of {src:?}");
}
}
fn kinds(src: &str) -> Vec<(SyntaxKind, &str)> {
lex(src)
.iter()
.filter(|(kind, _)| !kind.is_trivia())
.collect()
}
fn all(src: &str) -> Vec<(SyntaxKind, &str)> {
lex(src).iter().collect()
}
#[test]
fn keywords_beat_the_identifier_rule_but_only_when_unescaped() {
use SyntaxKind::*;
assert_eq!(kinds("reg"), [(REG_KW, "reg")]);
assert_eq!(kinds("\\reg"), [(IDENT, "\\reg")]);
assert_eq!(kinds("regwidth"), [(IDENT, "regwidth")]);
assert_eq!(kinds("r_field"), [(IDENT, "r_field")]);
assert_eq!(
kinds("r w rw hw sw"),
[
(R_KW, "r"),
(W_KW, "w"),
(RW_KW, "rw"),
(HW_KW, "hw"),
(SW_KW, "sw")
]
);
assert_eq!(kinds("\\r \\w"), [(IDENT, "\\r"), (IDENT, "\\w")]);
}
#[test]
fn apostrophe_has_three_meanings() {
use SyntaxKind::*;
assert_eq!(kinds("8'hA5"), [(VLOG_NUMBER, "8'hA5")]);
assert_eq!(
kinds("32'(x)"),
[
(INT_NUMBER, "32"),
(TICK, "'"),
(L_PAREN, "("),
(IDENT, "x"),
(R_PAREN, ")")
]
);
assert_eq!(kinds("'{}"), [(TICK, "'"), (L_BRACE, "{"), (R_BRACE, "}")]);
}
#[test]
fn multi_character_operators_win_over_their_prefixes() {
use SyntaxKind::*;
assert_eq!(
kinds("= == + += * ** % %= < <= << > >= >> ! != ~ ~& ~| ~^ ^ ^~ & && | || - -> : ::"),
[
(ASSIGN, "="),
(EQ, "=="),
(PLUS, "+"),
(INC, "+="),
(MULT, "*"),
(EXP, "**"),
(MOD, "%"),
(ALIGN, "%="),
(LT, "<"),
(LEQ, "<="),
(LSHIFT, "<<"),
(GT, ">"),
(GEQ, ">="),
(RSHIFT, ">>"),
(BNOT, "!"),
(NEQ, "!="),
(NOT, "~"),
(NAND, "~&"),
(NOR, "~|"),
(XNOR, "~^"),
(XOR, "^"),
(XNOR, "^~"),
(AND, "&"),
(BAND, "&&"),
(OR, "|"),
(BOR, "||"),
(MINUS, "-"),
(ARROW, "->"),
(COLON, ":"),
(DOUBLE_COLON, "::"),
]
);
}
#[test]
fn comments_are_tokens_not_holes() {
use SyntaxKind::*;
let src = "reg // hi\n/* there */ x";
let toks = lex(src);
let comments: Vec<_> = toks.iter().filter(|(kind, _)| kind.is_comment()).collect();
assert_eq!(
comments,
[(LINE_COMMENT, "// hi"), (BLOCK_COMMENT, "/* there */")]
);
assert!(
toks.iter()
.any(|(kind, text)| kind == WHITESPACE && text == "\n")
);
}
#[test]
fn block_comment_edge_cases() {
use SyntaxKind::*;
for src in ["/**/", "/***/", "/****/", "/* * */", "/** a **/"] {
let toks: Vec<_> = lex(src).iter().collect();
assert_eq!(toks, [(BLOCK_COMMENT, src)], "failed on {src:?}");
}
}
#[test]
fn directives_are_one_opaque_token_per_line() {
use SyntaxKind::*;
for src in [
"`define W 32",
"`include \"other.rdl\"",
"`undef W",
"`line 5 \"f.rdl\" 0",
"`define X reg r {};",
] {
assert_eq!(all(src), [(DIRECTIVE, src)], "failed on {src:?}");
}
for src in ["`ifdef FOO", "`ifndef FOO", "`elsif FOO", "`else", "`endif"] {
assert_eq!(all(src), [(COND_DIRECTIVE, src)], "failed on {src:?}");
}
assert_eq!(
all("`define W 32\nreg"),
[
(DIRECTIVE, "`define W 32"),
(WHITESPACE, "\n"),
(REG_KW, "reg")
]
);
assert_eq!(
all("`define B a + \\\n b\nreg"),
[
(DIRECTIVE, "`define B a + \\\n b"),
(WHITESPACE, "\n"),
(REG_KW, "reg")
]
);
assert_eq!(
all("`define E \\esc\nreg"),
[
(DIRECTIVE, "`define E \\esc"),
(WHITESPACE, "\n"),
(REG_KW, "reg")
]
);
}
#[test]
fn backtick_forms_are_told_apart_by_longest_match() {
use SyntaxKind::*;
assert_eq!(all("`define"), [(DIRECTIVE, "`define")]);
assert_eq!(all("`defineFOO"), [(MACRO_REF, "`defineFOO")]);
assert_eq!(all("`else"), [(COND_DIRECTIVE, "`else")]);
assert_eq!(all("`elsewhere"), [(MACRO_REF, "`elsewhere")]);
assert_eq!(all("`ifdef"), [(COND_DIRECTIVE, "`ifdef")]);
assert_eq!(all("`"), [(LEX_ERROR, "`")]);
}
#[test]
fn macro_references_are_atoms() {
use SyntaxKind::*;
assert_eq!(
kinds("f[`W-1:0]"),
[
(IDENT, "f"),
(L_BRACK, "["),
(MACRO_REF, "`W"),
(MINUS, "-"),
(INT_NUMBER, "1"),
(COLON, ":"),
(INT_NUMBER, "0"),
(R_BRACK, "]")
]
);
}
#[test]
fn unrecognised_bytes_become_a_single_error_run() {
use SyntaxKind::*;
assert_eq!(
kinds("reg $$$ field"),
[(REG_KW, "reg"), (LEX_ERROR, "$$$"), (FIELD_KW, "field")]
);
}