use std::ops::Range;
use std::path::Path;
use std::sync::OnceLock;
mod bytes;
pub mod inputs;
mod lexer;
mod parser;
pub use bytes::{
ByteParsedFile, byte_line_column, is_utf8_boundary, parse_bytes, parse_bytes_with_mode,
};
pub fn parse(source: impl Into<String>) -> ParsedFile {
parse_with_mode(source, FileMode::Model)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FileMode {
Model,
Data,
}
impl FileMode {
pub fn from_path(path: impl AsRef<Path>) -> Self {
if path
.as_ref()
.extension()
.and_then(|ext| ext.to_str())
.is_some_and(|ext| ext.eq_ignore_ascii_case("dzn"))
{
Self::Data
} else {
Self::Model
}
}
}
pub fn parse_with_mode(source: impl Into<String>, mode: FileMode) -> ParsedFile {
parse_lexed(lex(source), mode)
}
fn parse_lexed(mut lexed: LexedSource, mode: FileMode) -> ParsedFile {
if lexed.tokens.len() >= 65_536 {
lexed.tokens.shrink_to_fit();
}
let (tree, diagnostics) = parser::parse(&lexed.tokens, &lexed.source, mode);
lexed.diagnostics.extend(diagnostics);
ParsedFile {
lexed,
tree,
line_positions: OnceLock::new(),
}
}
#[derive(Debug)]
pub struct ParsedFile {
lexed: LexedSource,
tree: SyntaxNode,
line_positions: OnceLock<LinePositions>,
}
#[derive(Debug)]
struct LinePositions {
starts: Vec<usize>,
utf8_extra_bytes: Vec<(usize, usize)>,
}
impl ParsedFile {
pub fn source(&self) -> &str {
self.lexed.source()
}
pub fn line_column(&self, byte_offset: usize) -> (usize, usize) {
let source = self.source();
let prefix = &source[..byte_offset];
if byte_offset == 0 {
return (1, 1);
}
let positions = self.line_positions.get_or_init(|| {
let mut starts = vec![0];
let mut utf8_extra_bytes = Vec::new();
let mut extra_bytes = 0;
let bytes = source.as_bytes();
for (index, character) in source.char_indices() {
if character == '\r'
|| (character == '\n' && (index == 0 || bytes[index - 1] != b'\r'))
{
starts.push(index + 1);
}
let width = character.len_utf8();
if width > 1 {
extra_bytes += width - 1;
utf8_extra_bytes.push((index + width, extra_bytes));
}
}
LinePositions {
starts,
utf8_extra_bytes,
}
});
let line = positions
.starts
.partition_point(|&start| start <= prefix.len());
let mut start = positions.starts[line - 1];
if start < byte_offset
&& start > 0
&& source.as_bytes()[start - 1] == b'\r'
&& source.as_bytes()[start] == b'\n'
{
start += 1;
}
let extra_bytes_at = |offset| {
let count = positions
.utf8_extra_bytes
.partition_point(|&(end, _)| end <= offset);
positions.utf8_extra_bytes[..count]
.last()
.map_or(0, |&(_, extra)| extra)
};
let column =
byte_offset - start - (extra_bytes_at(byte_offset) - extra_bytes_at(start)) + 1;
(line, column)
}
pub fn tokens(&self) -> &[Token] {
self.lexed.tokens()
}
pub fn tree(&self) -> &SyntaxNode {
&self.tree
}
pub fn diagnostics(&self) -> &[Diagnostic] {
self.lexed.diagnostics()
}
}
#[derive(Debug)]
pub struct SyntaxNode {
data: Box<NodeData>,
}
#[derive(Debug)]
struct NodeData {
kind: NodeKind,
range: Range<usize>,
children: NodeChildren,
}
enum NodeChildren {
One(SyntaxElement),
Slice(Box<[SyntaxElement]>),
}
impl NodeChildren {
fn as_slice(&self) -> &[SyntaxElement] {
match self {
Self::One(child) => std::slice::from_ref(child),
Self::Slice(children) => children,
}
}
}
impl std::fmt::Debug for NodeChildren {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Debug::fmt(self.as_slice(), formatter)
}
}
impl SyntaxNode {
fn new(kind: NodeKind, range: Range<usize>, children: Box<[SyntaxElement]>) -> Self {
Self {
data: Box::new(NodeData {
kind,
range,
children: NodeChildren::Slice(children),
}),
}
}
fn with_child(kind: NodeKind, range: Range<usize>, child: SyntaxElement) -> Self {
Self {
data: Box::new(NodeData {
kind,
range,
children: NodeChildren::One(child),
}),
}
}
pub fn kind(&self) -> NodeKind {
self.data.kind
}
pub fn range(&self) -> Range<usize> {
self.data.range.clone()
}
pub fn children(&self) -> &[SyntaxElement] {
self.data.children.as_slice()
}
pub fn child_nodes(&self) -> impl Iterator<Item = &SyntaxNode> {
self.children().iter().filter_map(|child| match child {
SyntaxElement::Node(node) => Some(node),
SyntaxElement::Token(_) => None,
})
}
}
#[derive(Debug)]
pub enum SyntaxElement {
Node(SyntaxNode),
Token(usize),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NodeKind {
Root,
EnumDeclaration,
TypeAlias,
EnumDefinition,
EnumCases,
EnumCase,
EnumConstructor,
TypeInstConcatenation,
Declaration,
FunctionDeclaration,
PredicateDeclaration,
AnnotationCapture,
TestDeclaration,
AnnotationDeclaration,
ParameterList,
Parameter,
TypeInstVariable,
ScalarType,
TupleType,
RecordType,
RecordField,
TupleLiteral,
RecordLiteral,
RecordLiteralField,
FieldAccessExpression,
DomainType,
SetType,
SetCardinality,
ArrayType,
ArrayIndexBinding,
ListType,
Assignment,
Constraint,
Include,
Output,
Solve,
SolveMinimize,
SolveMaximize,
Expression,
InterpolatedString,
UnaryExpression,
BinaryExpression,
ParenthesizedExpression,
CallExpression,
GeneratorCallExpression,
ConditionalExpression,
ConditionalBranch,
ElseBranch,
LetExpression,
LetBlock,
SetComprehension,
ArrayComprehension,
IndexedArrayComprehension,
GeneratorList,
Generator,
WhereFilter,
SetLiteral,
ArrayLiteral,
IndexedArrayEntry,
IndexTuple,
MatrixLiteral,
MatrixColumnIndices,
MatrixRow,
ArrayAccessExpression,
NamedArgument,
AnnotatedExpression,
Annotation,
RangeExpression,
Error,
}
pub fn lex(source: impl Into<String>) -> LexedSource {
let source = source.into();
let (tokens, diagnostics) = lexer::scan(&source);
LexedSource {
source,
tokens,
diagnostics,
}
}
pub fn lexical_protected_ranges(source: &str) -> Result<Vec<Range<usize>>, Vec<Diagnostic>> {
lexer::protected_ranges(source)
}
#[derive(Debug)]
pub struct LexedSource {
source: String,
tokens: Vec<Token>,
diagnostics: Vec<Diagnostic>,
}
impl LexedSource {
pub fn source(&self) -> &str {
&self.source
}
pub fn into_source(self) -> String {
self.source
}
pub fn tokens(&self) -> &[Token] {
&self.tokens
}
pub fn diagnostics(&self) -> &[Diagnostic] {
&self.diagnostics
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Token {
pub kind: TokenKind,
pub range: Range<usize>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Diagnostic {
pub range: Range<usize>,
pub message: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TokenKind {
Whitespace,
LineComment,
BlockComment,
Identifier,
QuotedIdentifier,
InfixIdentifier,
TypeInstVariable,
IntegerLiteral,
FloatLiteral,
StringLiteral,
StringHead,
StringMiddle,
StringTail,
Error,
Ann,
Annotation,
Any,
Array,
Bool,
Case,
Constraint,
Default,
Diff,
Div,
Else,
ElseIf,
EndIf,
Enum,
False,
Float,
Function,
If,
In,
Include,
Infinity,
Int,
Intersect,
Let,
List,
Maximize,
Minimize,
Mod,
Not,
Of,
Opt,
Output,
Par,
Predicate,
Record,
Satisfy,
Set,
Solve,
String,
Subset,
Superset,
SymDiff,
Test,
Then,
True,
Tuple,
Type,
Union,
Var,
VariantRecord,
Where,
Xor,
LeftParen,
RightParen,
LeftBracket,
RightBracket,
LeftBrace,
RightBrace,
MatrixStart,
MatrixEnd,
Comma,
Colon,
Semicolon,
Dot,
Pipe,
Anonymous,
Absent,
AnnotationMarker,
Equivalence,
Implies,
ReverseImplies,
Or,
And,
Less,
Greater,
LessEqual,
GreaterEqual,
Equal,
DoubleEqual,
NotEqual,
WeakEqual,
WeakNotEqual,
RangeInclusive,
RangeExclusiveStart,
RangeExclusiveEnd,
RangeExclusive,
Plus,
Minus,
Star,
Slash,
Power,
Concat,
WeakPlus,
WeakMinus,
WeakStar,
WeakSlash,
WeakDiv,
Inverse,
}
pub fn literal_include_path(parsed: &ParsedFile, item: &SyntaxNode) -> Option<Vec<u8>> {
if item.kind() != NodeKind::Include {
return None;
}
let path = item.child_nodes().next()?;
if path.kind() == NodeKind::InterpolatedString {
return None;
}
let text = path.children().iter().find_map(|child| match child {
SyntaxElement::Token(index) if parsed.tokens()[*index].kind == TokenKind::StringLiteral => {
Some(&parsed.source()[parsed.tokens()[*index].range.clone()])
}
_ => None,
})?;
let mut decoded = Vec::new();
let mut bytes = text[1..text.len() - 1].bytes().peekable();
while let Some(byte) = bytes.next() {
if byte != b'\\' {
decoded.push(byte);
continue;
}
let escaped = bytes.next()?;
decoded.push(match escaped {
b'n' => b'\n',
b't' => b'\t',
b'"' | b'\'' | b'\\' => escaped,
b'x' | b'0'..=b'7' => {
let radix = if escaped == b'x' { 16 } else { 8 };
let mut value = if radix == 8 {
u32::from(escaped - b'0')
} else {
0
};
for _ in 0..2 {
let Some(digit) = bytes
.peek()
.and_then(|byte| char::from(*byte).to_digit(radix))
else {
break;
};
bytes.next();
value = value * radix + digit;
}
value as u8
}
_ => return None,
});
}
Some(decoded)
}