Skip to content

Instantly share code, notes, and snippets.

@kkoyung
Last active September 12, 2025 05:21
Show Gist options
  • Save kkoyung/47f24c39d0957bd9a7b0992213161070 to your computer and use it in GitHub Desktop.
Save kkoyung/47f24c39d0957bd9a7b0992213161070 to your computer and use it in GitHub Desktop.
JS identifier name
use nom::branch::alt;
use nom::bytes::complete::take_while_m_n;
use nom::character::complete::char as ch;
use nom::character::satisfy;
use nom::combinator::{all_consuming, complete, verify};
use nom::multi::many0;
use nom::sequence::{delimited, preceded};
use nom::{IResult, Parser};
use unicode_ident::{is_xid_continue, is_xid_start};
/// <https://tc39.es/ecma262/#prod-IdentifierName>
pub(crate) fn is_identifier_name(name: &str) -> bool {
// IdentifierName ::
// IdentifierStart
// IdentifierName IdentifierPart
fn identifier_name(input: &str) -> IResult<&str, Vec<&str>> {
all_consuming(preceded(identifier_start, many0(identifier_part))).parse(input)
}
// IdentifierStart ::
// IdentifierStartChar
// \ UnicodeEscapeSequence
fn identifier_start(input: &str) -> IResult<&str, &str> {
complete(alt((
identifier_start_char,
preceded(ch('\\'), unicode_escape_sequence),
)))
.parse(input)
}
// IdentifierPart ::
// IdentifierPartChar
// \ UnicodeEscapeSequence
fn identifier_part(input: &str) -> IResult<&str, &str> {
complete(alt((
identifier_part_char,
preceded(ch('\\'), unicode_escape_sequence),
)))
.parse(input)
}
// IdentifierStartChar ::
// UnicodeIDStart
// $
// _
fn identifier_start_char(input: &str) -> IResult<&str, &str> {
let (rest, matched) = satisfy(|c| is_xid_start(c) || c == '$' || c == '_').parse(input)?;
Ok((rest, &input[..matched.len_utf8()]))
}
// IdentifierPartChar ::
// UnicodeIDContinue
// $
fn identifier_part_char(input: &str) -> IResult<&str, &str> {
let (rest, matched) = satisfy(|c| is_xid_continue(c) || c == '$').parse(input)?;
Ok((rest, &input[..matched.len_utf8()]))
}
// UnicodeEscapeSequence ::
// u Hex4Digits
// u{ CodePoint }
fn unicode_escape_sequence(input: &str) -> IResult<&str, &str> {
alt((
preceded(ch('u'), hex4digits),
preceded(ch('u'), delimited(ch('{'), codepoint, ch('}'))),
))
.parse(input)
}
// Hex4Digits ::
// HexDigit HexDigit HexDigit HexDigit
fn hex4digits(input: &str) -> IResult<&str, &str> {
take_while_m_n(4, 4, |c: char| c.is_ascii_hexdigit()).parse(input)
}
// CodePoint ::
// HexDigits[~Sep] but only if the MV of HexDigits ≤ 0x10FFFF
fn codepoint(input: &str) -> IResult<&str, &str> {
verify(
take_while_m_n(1, 6, |c: char| c.is_ascii_hexdigit()),
|hex: &str| u32::from_str_radix(hex, 16).is_ok_and(|value| value <= 0x10FFFF),
)
.parse(input)
}
identifier_name(name).is_ok()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment