use crate::{
ast::Number,
error::LexicalError,
utils::{parse_number_base, parse_number_sci},
};
use logos::Logos;
use std::ops::Range;
fn symbolic_string_prefix_and_length<'input>(
lex: &mut logos::Lexer<'input, NormalToken<'input>>,
) -> SymbolicStringStart<'input> {
let slice = lex.slice();
let (prefix, postfix) = slice
.rsplit_once('-')
.expect("The logos regexp ensures this succeeds");
SymbolicStringStart {
prefix,
length: postfix.len(),
}
}
#[derive(Logos, Debug, PartialEq, Clone)]
#[logos(skip "((\r\n)+|[ \t\n]+)")]
pub enum NormalToken<'input> {
#[regex("'m(%)+\"")]
#[regex("\r[^\n]")]
Error,
#[regex("_*[a-zA-Z][_a-zA-Z0-9-']*")]
Identifier(&'input str),
#[regex("[0-9]*\\.?[0-9]+([eE][+\\-]?[0-9]+)?", |lex| parse_number_sci(lex.slice()).ok())]
DecNumLiteral(Number),
#[regex("0x[A-Fa-f0-9]+", |lex| parse_number_base(16, &lex.slice()[2..]).ok())]
HexNumLiteral(Number),
#[regex("0o[0-7]+", |lex| parse_number_base(8, &lex.slice()[2..]).ok())]
OctNumLiteral(Number),
#[regex("0b[01]+", |lex| parse_number_base(2, &lex.slice()[2..]).ok())]
BinNumLiteral(Number),
#[regex("'_*[a-zA-Z][_a-zA-Z0-9-']*", |lex| lex.slice().split_at(1).1)]
RawEnumTag(&'input str),
#[token("'\"")]
StrEnumTagBegin,
#[token("Dyn")]
Dyn,
#[token("Number")]
Number,
#[token("Bool")]
Bool,
#[token("String")]
String,
#[token("Array")]
Array,
#[token("if")]
If,
#[token("then")]
Then,
#[token("else")]
Else,
#[token("forall")]
Forall,
#[token("in")]
In,
#[token("let")]
Let,
#[token("rec")]
Rec,
#[token("match")]
Match,
#[token("null")]
Null,
#[token("true")]
True,
#[token("false")]
False,
#[token("or")]
Or,
#[token("as")]
As,
#[token("include")]
Include,
#[token("?")]
QuestionMark,
#[token(",")]
Comma,
#[token(";")]
Semicolon,
#[token(":")]
Colon,
#[token("$")]
Dollar,
#[token("=")]
Equals,
#[token("!=")]
NotEquals,
#[token("&")]
Ampersand,
#[token(".")]
Dot,
#[token("\"")]
DoubleQuote,
#[token("+")]
Plus,
#[token("-")]
Minus,
#[token("*")]
Times,
#[token("/")]
Div,
#[token("%")]
Percent,
#[token("++")]
DoublePlus,
#[token("==")]
DoubleEq,
#[token("@")]
At,
#[token("&&")]
DoubleAnd,
#[token("||")]
DoublePipe,
#[token("!")]
Bang,
#[token("..")]
Ellipsis,
#[token("fun")]
Fun,
#[token("import")]
Import,
#[token("|")]
Pipe,
#[token("|>")]
RightPipe,
#[token("->")]
SimpleArrow,
#[token("=>")]
DoubleArrow,
#[token("_")]
Underscore,
#[regex("m(%+)\"", |lex| lex.slice().len())]
MultiStringStart(usize),
#[regex("[a-zA-Z][_a-zA-Z0-9-']*-s(%+)\"", symbolic_string_prefix_and_length)]
SymbolicStringStart(SymbolicStringStart<'input>),
#[token("%typeof%")]
Typeof,
#[token("%cast%")]
Cast,
#[token("%contract/apply%")]
ContractApply,
#[token("%contract/check%")]
ContractCheck,
#[token("%contract/array_lazy_apply%")]
ContractArrayLazyApp,
#[token("%contract/record_lazy_apply%")]
ContractRecordLazyApp,
#[token("%contract/custom%")]
ContractCustom,
#[token("%blame%")]
Blame,
#[token("%label/flip_polarity%")]
LabelFlipPol,
#[token("%label/polarity%")]
LabelPol,
#[token("%label/go_dom%")]
LabelGoDom,
#[token("%label/go_codom%")]
LabelGoCodom,
#[token("%label/go_field%")]
LabelGoField,
#[token("%label/go_array%")]
LabelGoArray,
#[token("%label/go_dict%")]
LabelGoDict,
#[token("%label/insert_type_variable%")]
LabelInsertTypeVar,
#[token("%label/lookup_type_variable%")]
LabelLookupTypeVar,
#[token("%seal%")]
Seal,
#[token("%unseal%")]
Unseal,
#[token("%enum/embed%")]
EnumEmbed,
#[token("%record/map%")]
RecordMap,
#[token("%record/insert%")]
RecordInsert,
#[token("%record/insert_with_opts%")]
RecordInsertWithOpts,
#[token("%record/remove%")]
RecordRemove,
#[token("%record/remove_with_opts%")]
RecordRemoveWithOpts,
#[token("%record/empty_with_tail%")]
RecordEmptyWithTail,
#[token("%record/seal_tail%")]
RecordSealTail,
#[token("%record/unseal_tail%")]
RecordUnsealTail,
#[token("%seq%")]
Seq,
#[token("%deep_seq%")]
DeepSeq,
#[token("%force%")]
OpForce,
#[token("%array/length%")]
ArrayLength,
#[token("%record/fields%")]
RecordFields,
#[token("%record/fields_with_opts%")]
RecordFieldsWithOpts,
#[token("%record/values%")]
RecordValues,
#[token("%number/arccos%")]
NumberArcCos,
#[token("%number/arcsin%")]
NumberArcSin,
#[token("%number/arctan%")]
NumberArcTan,
#[token("%number/arctan2%")]
NumberArcTan2,
#[token("%number/cos%")]
NumberCos,
#[token("%number/sin%")]
NumberSin,
#[token("%number/tan%")]
NumberTan,
#[token("%number/log%")]
NumberLog,
#[token("%pow%")]
Pow,
#[token("%trace%")]
Trace,
#[token("%record/has_field%")]
RecordHasField,
#[token("%record/has_field_with_opts%")]
RecordHasFieldWithOpts,
#[token("%array/map%")]
ArrayMap,
#[token("%array/at%")]
ArrayAt,
#[token("%array/generate%")]
ArrayGen,
#[token("%rec_force%")]
OpRecForce,
#[token("%rec_default%")]
OpRecDefault,
#[token("%record/field_is_defined%")]
RecordFieldIsDefined,
#[token("%record/field_is_defined_with_opts%")]
RecordFieldIsDefinedWithOpts,
#[token("%record/split_pair%")]
RecordSplitPair,
#[token("%record/disjoint_merge%")]
RecordDisjointMerge,
#[token("%record/merge_contract%")]
RecordMergeContract,
#[token("%record/freeze%")]
RecordFreeze,
#[token("default")]
Default,
#[token("doc")]
Doc,
#[token("optional")]
Optional,
#[token("priority")]
Priority,
#[token("force")]
Force,
#[token("not_exported")]
NotExported,
#[token("%hash%")]
OpHash,
#[token("%serialize%")]
Serialize,
#[token("%deserialize%")]
Deserialize,
#[token("%string/split%")]
StringSplit,
#[token("%string/trim%")]
StringTrim,
#[token("%string/chars%")]
StringChars,
#[token("%string/uppercase%")]
StringUppercase,
#[token("%string/lowercase%")]
StringLowercase,
#[token("%string/contains%")]
StringContains,
#[token("%string/compare%")]
StringCompare,
#[token("%string/replace%")]
StringReplace,
#[token("%string/replace_regex%")]
StringReplaceRegex,
#[token("%string/is_match%")]
StringIsMatch,
#[token("%string/find%")]
StringFind,
#[token("%string/find_all%")]
StringFindAll,
#[token("%string/length%")]
StringLength,
#[token("%string/substr%")]
StringSubstr,
#[token("%string/base64_encode%")]
StringBase64Encode,
#[token("%string/base64_decode%")]
StringBase64Decode,
#[token("%to_string%")]
ToString,
#[token("%number/from_string%")]
NumberFromString,
#[token("%enum/from_string%")]
EnumFromString,
#[token("%enum/get_arg%")]
EnumGetArg,
#[token("%enum/make_variant%")]
EnumMakeVariant,
#[token("%enum/is_variant%")]
EnumIsVariant,
#[token("%enum/get_tag%")]
EnumGetTag,
#[token("%label/with_message%")]
LabelWithMessage,
#[token("%label/with_notes%")]
LabelWithNotes,
#[token("%label/append_note%")]
LabelAppendNote,
#[token("%label/push_diag%")]
LabelPushDiag,
#[token("%array/slice%")]
ArraySlice,
#[token("%eval_nix%")]
EvalNix,
#[token("{")]
LBrace,
#[token("}")]
RBrace,
#[token("[")]
LBracket,
#[token("]")]
RBracket,
#[token("(")]
LParen,
#[token(")")]
RParen,
#[token("<")]
LAngleBracket,
#[token("<=")]
LessOrEq,
#[token(">")]
RAngleBracket,
#[token(">=")]
GreaterOrEq,
#[token("[|")]
EnumOpen,
#[token("|]")]
EnumClose,
#[regex("#[^\n]*", allow_greedy = true)]
LineComment,
}
pub const KEYWORDS: &[&str] = &[
"Dyn",
"Number",
"Bool",
"String",
"Array",
"if",
"then",
"else",
"forall",
"in",
"let",
"rec",
"match",
"null",
"true",
"false",
"fun",
"import",
"merge",
"default",
"doc",
"optional",
"priority",
"force",
"not_exported",
];
#[derive(Debug, Clone, PartialEq)]
pub struct SymbolicStringStart<'input> {
pub prefix: &'input str,
pub length: usize,
}
#[derive(Logos, Debug, PartialEq, Eq, Clone)]
pub enum StringToken<'input> {
#[regex("\r[^\n]")]
Error,
#[regex("[^\"%\\\\]+", |lex| normalize_line_endings(lex.slice()))]
#[token("%", |lex| String::from(lex.slice()))]
Literal(String),
#[token("\"")]
DoubleQuote,
#[token("%{")]
Interpolation,
#[regex("\\\\.", |lex| lex.slice().chars().nth(1))]
EscapedChar(char),
#[regex("\\\\x[A-Fa-f0-9][A-Fa-f0-9]", |lex| &lex.slice()[2..4])]
EscapedAscii(&'input str),
#[regex("\\\\u\\{[A-Fa-f0-9]{1,6}\\}", |lex| {
let len = lex.slice().len();
&lex.slice()[3..(len - 1)]
})]
EscapedUnicode(&'input str),
}
#[derive(Logos, Debug, PartialEq, Eq, Clone)]
pub enum MultiStringToken<'input> {
#[regex("\r[^\n]")]
Error,
#[regex("[^\"%]+", |lex| normalize_line_endings(lex.slice()))]
#[token("\"", |lex| String::from(lex.slice()))]
#[regex("%+", |lex| String::from(lex.slice()))]
Literal(String),
#[regex("\"%+")]
CandidateEnd(&'input str),
#[regex("%+\\{")]
CandidateInterpolation(&'input str),
#[regex("\"%+\\{")]
QuotesCandidateInterpolation(&'input str),
End,
Interpolation,
}
#[derive(Debug, PartialEq, Clone)]
pub enum Token<'input> {
Normal(NormalToken<'input>),
Str(StringToken<'input>),
MultiStr(MultiStringToken<'input>),
}
pub type SpannedToken<'input> = (usize, Token<'input>, usize);
type NormalLexer<'input> = logos::Lexer<'input, NormalToken<'input>>;
type StringLexer<'input> = logos::Lexer<'input, StringToken<'input>>;
type MultiStringLexer<'input> = logos::Lexer<'input, MultiStringToken<'input>>;
pub enum ModalLexer<'input> {
Normal {
mode_data: NormalData,
logos_lexer: NormalLexer<'input>,
},
String {
logos_lexer: StringLexer<'input>,
},
MultiString {
mode_data: MultiStrData,
buffer: Option<(MultiStringToken<'input>, Range<usize>)>,
logos_lexer: MultiStringLexer<'input>,
},
}
impl<'input> Iterator for ModalLexer<'input> {
type Item = Result<Token<'input>, ()>;
fn next(&mut self) -> Option<Self::Item> {
match self {
ModalLexer::Normal { logos_lexer, .. } => Some(logos_lexer.next()?.map(Token::Normal)),
ModalLexer::String { logos_lexer } => Some(logos_lexer.next()?.map(Token::Str)),
ModalLexer::MultiString { logos_lexer, .. } => {
Some(logos_lexer.next()?.map(Token::MultiStr))
}
}
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct MultiStrData {
percent_count: usize,
opening_delimiter: Range<usize>,
}
#[derive(Clone, PartialEq, Eq, Debug, Default)]
pub struct NormalData {
brace_count: usize,
}
impl NormalData {
pub fn new() -> Self {
Default::default()
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum Mode {
String,
MultiString(MultiStrData),
Normal(NormalData),
}
pub struct Lexer<'input> {
pub lexer: Option<ModalLexer<'input>>,
pub modes: Vec<Mode>,
}
impl<'input> Lexer<'input> {
pub fn new(s: &'input str) -> Self {
Lexer {
lexer: Some(ModalLexer::Normal {
mode_data: NormalData { brace_count: 0 },
logos_lexer: NormalToken::lexer(s),
}),
modes: Vec::new(),
}
}
fn enter_strlike<F>(&mut self, morph: F)
where
F: FnOnce(NormalLexer<'input>) -> ModalLexer<'input>,
{
match self.lexer.take() {
Some(ModalLexer::Normal {
mode_data,
logos_lexer,
}) => {
self.modes.push(Mode::Normal(mode_data));
self.lexer = Some(morph(logos_lexer));
}
_ => panic!("lexer::enter_strlike"),
}
}
fn enter_str(&mut self) {
self.enter_strlike(|lexer| ModalLexer::String {
logos_lexer: lexer.morph(),
});
}
fn enter_indstr(&mut self, percent_count: usize, opening_delimiter: Range<usize>) {
self.enter_strlike(|lexer| ModalLexer::MultiString {
mode_data: MultiStrData {
percent_count,
opening_delimiter,
},
buffer: None,
logos_lexer: lexer.morph(),
});
}
fn enter_normal(&mut self) {
match self.lexer.take() {
Some(ModalLexer::String { logos_lexer }) => {
self.lexer = Some(ModalLexer::Normal {
mode_data: NormalData::new(),
logos_lexer: logos_lexer.morph(),
});
self.modes.push(Mode::String);
}
Some(ModalLexer::MultiString {
mode_data,
logos_lexer,
buffer: _,
}) => {
self.lexer = Some(ModalLexer::Normal {
mode_data: NormalData::new(),
logos_lexer: logos_lexer.morph(),
});
self.modes.push(Mode::MultiString(mode_data));
}
_ => panic!("lexer::enter_normal"),
}
}
fn leave_str(&mut self) {
match self.lexer.take() {
Some(ModalLexer::String { logos_lexer }) => {
let Some(Mode::Normal(mode_data)) = self.modes.pop() else {
panic!("lexer::leave_str (popped wrong mode)");
};
self.lexer = Some(ModalLexer::Normal {
mode_data,
logos_lexer: logos_lexer.morph(),
});
}
_ => panic!("lexer::leave_str"),
}
}
fn leave_indstr(&mut self) {
match self.lexer.take() {
Some(ModalLexer::MultiString { logos_lexer, .. }) => {
let Some(Mode::Normal(data)) = self.modes.pop() else {
panic!("lexer::leave_str (popped wrong mode)");
};
self.lexer = Some(ModalLexer::Normal {
mode_data: data,
logos_lexer: logos_lexer.morph(),
});
}
_ => panic!("lexer::leave_str"),
}
}
fn leave_normal(&mut self) {
match self.lexer.take() {
Some(ModalLexer::Normal { logos_lexer, .. }) => {
match self.modes.pop() {
Some(Mode::String) => {
self.lexer = Some(ModalLexer::String {
logos_lexer: logos_lexer.morph(),
})
}
Some(Mode::MultiString(data)) => {
self.lexer = Some(ModalLexer::MultiString {
mode_data: data,
buffer: None,
logos_lexer: logos_lexer.morph(),
})
}
mode => panic!("lexer::leave_normal (popped mode {mode:?})"),
};
}
_ => panic!("lexer::leave_normal"),
}
}
fn split_candidate_interp(
&mut self,
s: &'input str,
span: Range<usize>,
percent_count: usize,
) -> (Token<'input>, Range<usize>) {
let split_at = s.len() - percent_count;
let next_token = MultiStringToken::Interpolation;
let next_span = Range {
start: span.start + split_at,
end: span.end,
};
self.bufferize(next_token, next_span);
let token = Token::MultiStr(MultiStringToken::Literal(s[0..split_at].to_owned()));
let span = Range {
start: span.start,
end: span.start + split_at,
};
(token, span)
}
fn handle_normal_token(
&mut self,
span: Range<usize>,
token: NormalToken<'input>,
) -> Option<Result<SpannedToken<'input>, LexicalError>> {
match token {
NormalToken::DoubleQuote | NormalToken::StrEnumTagBegin => self.enter_str(),
NormalToken::MultiStringStart(delim_size)
| NormalToken::SymbolicStringStart(SymbolicStringStart {
length: delim_size, ..
}) => {
let size_without_kind_marker = delim_size - 1;
self.enter_indstr(size_without_kind_marker, span.clone())
}
NormalToken::LBrace => {
self.normal_mode_data_mut().brace_count += 1;
}
NormalToken::RBrace => {
let data = self.normal_mode_data_mut();
if data.brace_count == 0 {
if self.modes.is_empty() {
return Some(Err(LexicalError::UnmatchedCloseBrace(span.start)));
}
self.leave_normal();
} else {
data.brace_count -= 1;
}
}
NormalToken::LineComment => return self.next(),
NormalToken::Error => {
return Some(Err(LexicalError::Generic(span)));
}
_ => (),
};
Some(Ok((span.start, Token::Normal(token), span.end)))
}
fn handle_string_token(
&mut self,
span: Range<usize>,
token: StringToken<'input>,
) -> Option<Result<SpannedToken<'input>, LexicalError>> {
let result = match token {
StringToken::DoubleQuote => {
self.leave_str();
Token::Normal(NormalToken::DoubleQuote)
}
tok @ StringToken::Interpolation => {
self.enter_normal();
Token::Str(tok)
}
StringToken::EscapedChar(c) => {
if let Some(esc) = escape_char(c) {
Token::Str(StringToken::EscapedChar(esc))
} else {
return Some(Err(LexicalError::InvalidEscapeSequence(span.start + 1)));
}
}
StringToken::EscapedAscii(code) => {
if let Some(esc) = escape_ascii(code) {
Token::Str(StringToken::EscapedChar(esc))
} else {
return Some(Err(LexicalError::InvalidAsciiEscapeCode(span.start + 2)));
}
}
StringToken::EscapedUnicode(code) => {
if let Some(esc) = escape_unicode(code) {
Token::Str(StringToken::EscapedChar(esc))
} else {
let start = span.start + 3;
let end = start + code.len();
return Some(Err(LexicalError::InvalidUnicodeEscapeCode(start..end)));
}
}
StringToken::Error => {
return Some(Err(LexicalError::Generic(span)));
}
token => Token::Str(token),
};
Some(Ok((span.start, result, span.end)))
}
fn handle_multistr_token(
&mut self,
mut span: Range<usize>,
token: MultiStringToken<'input>,
) -> Option<Result<SpannedToken<'input>, LexicalError>> {
let data = self.multistring_mode_data();
let result = match token {
MultiStringToken::CandidateInterpolation(s) if s.len() >= data.percent_count => {
if s.len() == data.percent_count {
self.enter_normal();
Token::MultiStr(MultiStringToken::Interpolation)
} else {
let (token_fst, span_fst) =
self.split_candidate_interp(s, span, data.percent_count);
span = span_fst;
token_fst
}
}
tok @ MultiStringToken::Interpolation => {
self.enter_normal();
Token::MultiStr(tok)
}
MultiStringToken::QuotesCandidateInterpolation(s) if s.len() > data.percent_count => {
let (token_fst, span_fst) =
self.split_candidate_interp(s, span, data.percent_count);
span = span_fst;
token_fst
}
MultiStringToken::CandidateInterpolation(s)
| MultiStringToken::QuotesCandidateInterpolation(s) => {
Token::MultiStr(MultiStringToken::Literal(s.to_owned()))
}
MultiStringToken::CandidateEnd(s) if s.len() > data.percent_count => {
return Some(Err(LexicalError::StringDelimiterMismatch {
opening_delimiter: data.opening_delimiter.clone(),
closing_delimiter: span,
}));
}
MultiStringToken::CandidateEnd(s) if s.len() == data.percent_count => {
self.leave_indstr();
Token::MultiStr(MultiStringToken::End)
}
MultiStringToken::CandidateEnd(s) => {
Token::MultiStr(MultiStringToken::Literal(s.to_owned()))
}
MultiStringToken::Error => {
return Some(Err(LexicalError::Generic(span)));
}
token => Token::MultiStr(token),
};
Some(Ok((span.start, result, span.end)))
}
fn normal_mode_data_mut(&mut self) -> &mut NormalData {
match self.lexer {
Some(ModalLexer::Normal {
ref mut mode_data, ..
}) => mode_data,
_ => panic!("lexer: normal_mode_data() called while not in normal mode"),
}
}
fn multistring_mode_data(&self) -> &MultiStrData {
match self.lexer {
Some(ModalLexer::MultiString { ref mode_data, .. }) => mode_data,
_ => panic!("lexer: multistring_mode_data() called while not in multistring mode"),
}
}
fn bufferize(&mut self, token: MultiStringToken<'input>, span: Range<usize>) {
match self.lexer {
Some(ModalLexer::MultiString { ref mut buffer, .. }) => *buffer = Some((token, span)),
_ => panic!("lexer: bufferize() called while not in normal mode"),
}
}
}
impl<'input> Iterator for Lexer<'input> {
type Item = Result<SpannedToken<'input>, LexicalError>;
fn next(&mut self) -> Option<Self::Item> {
match self.lexer.as_mut().unwrap() {
ModalLexer::Normal { logos_lexer, .. } => {
let normal_token = logos_lexer.next()?.unwrap_or(NormalToken::Error);
let span = logos_lexer.span();
self.handle_normal_token(span, normal_token)
}
ModalLexer::String { logos_lexer } => {
let string_token = logos_lexer.next()?.unwrap_or(StringToken::Error);
let span = logos_lexer.span();
self.handle_string_token(span, string_token)
}
ModalLexer::MultiString {
buffer,
logos_lexer,
..
} => {
let (multistr_token, span) = buffer.take().or_else(|| {
Some((
logos_lexer.next()?.unwrap_or(MultiStringToken::Error),
logos_lexer.span(),
))
})?;
self.handle_multistr_token(span, multistr_token)
}
}
}
}
pub struct OffsetLexer<'input> {
lexer: Lexer<'input>,
offset: usize,
}
impl<'input> OffsetLexer<'input> {
pub fn new(s: &'input str, offset: usize) -> Self {
OffsetLexer {
lexer: Lexer::new(s),
offset,
}
}
}
impl<'input> Iterator for OffsetLexer<'input> {
type Item = Result<SpannedToken<'input>, LexicalError>;
fn next(&mut self) -> Option<Self::Item> {
self.lexer.next().map(|result| {
result.map(|(start, tok, end)| (start + self.offset, tok, end + self.offset))
})
}
}
fn escape_char(chr: char) -> Option<char> {
match chr {
'\'' => Some('\''),
'"' => Some('"'),
'\\' => Some('\\'),
'%' => Some('%'),
'n' => Some('\n'),
'r' => Some('\r'),
't' => Some('\t'),
_ => None,
}
}
fn escape_ascii(code: &str) -> Option<char> {
let code = u8::from_str_radix(code, 16).ok()?;
if code > 0x7F {
None
} else {
Some(code as char)
}
}
fn escape_unicode(code: &str) -> Option<char> {
u32::from_str_radix(code, 16).ok().and_then(char::from_u32)
}
pub fn normalize_line_endings(s: impl AsRef<str>) -> String {
let normalized = s.as_ref().replace("\r\n", "\n");
debug_assert!(
normalized.find('\r').is_none(),
"The lexer throws an error when it finds a lone carriage return"
);
normalized
}