Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/parser/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ pub const DELIMITER: u32 = 0x0100;
pub const TERM: u32 = 0x1000;
pub const LTERM: u32 = 0x3000;
pub const BTERM: u32 = 0x11000;
pub const LIST_TERM: u32 = 0x5000;

pub const NEGATIVE_SIGN: u32 = 0x0200;

Expand Down
6 changes: 6 additions & 0 deletions src/parser/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ pub enum Token {
OpenCurly, // '{'
CloseCurly, // '}'
HeadTailSeparator, // '|'
DoubleBar, // '||'
Comma, // ','
End,
}
Expand Down Expand Up @@ -1035,6 +1036,11 @@ impl<'a, R: CharRead> Lexer<'a, R> {

if c == '|' {
self.skip_char(c);
let next = self.lookahead_char()?;
if next == '|' {
self.skip_char(next);
return Ok(Token::DoubleBar);
}
return Ok(Token::HeadTailSeparator);
}

Expand Down
169 changes: 155 additions & 14 deletions src/parser/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ enum TokenType {
OpenList, // '['
OpenCurly, // '{'
HeadTailSeparator, // '|'
DoubleBar, // '||'
Comma, // ','
Close,
CloseList, // ']'
Expand All @@ -44,6 +45,7 @@ impl TokenType {
matches!(
self,
TokenType::HeadTailSeparator
| TokenType::DoubleBar
| TokenType::OpenCT
| TokenType::Open
| TokenType::Close
Expand Down Expand Up @@ -315,6 +317,7 @@ impl<'a, R: CharRead> Parser<'a, R> {
fn get_term_name(&mut self, td: TokenDesc) -> Option<Atom> {
match td.tt {
TokenType::HeadTailSeparator => Some(atom!("|")),
TokenType::DoubleBar => Some(atom!("||")),
TokenType::Comma => Some(atom!(",")),
TokenType::Term => match self.terms.pop() {
Some(Term::Literal(_, Literal::Atom(atom))) => Some(atom),
Expand All @@ -328,11 +331,57 @@ impl<'a, R: CharRead> Parser<'a, R> {
}
}

// Helper function to replace the tail of a Cons list with a new tail
fn replace_cons_tail(cons: Term, new_tail: Term) -> Term {
match cons {
Term::Cons(cell, head, tail) => {
match *tail {
Term::Literal(_, Literal::Atom(atom)) if atom == atom!("[]") => {
// Found the empty list tail, replace it
Term::Cons(cell, head, Box::new(new_tail))
}
_ => {
// Recurse on the tail
Term::Cons(cell, head, Box::new(Self::replace_cons_tail(*tail, new_tail)))
}
}
}
_ => cons, // Not a Cons, return as-is (shouldn't happen)
}
}

fn push_binary_op(&mut self, td: TokenDesc, spec: Specifier) {
if let Some(arg2) = self.terms.pop() {
if let Some(name) = self.get_term_name(td) {
if let Some(arg1) = self.terms.pop() {
let term = Term::Clause(Cell::default(), name, vec![arg1, arg2]);
let term = if name == atom!("||") {
match arg1 {
Term::CompleteString(_, s) | Term::PartialString(_, s, _) => {
if s.is_empty() {
// Empty string collapses: ""||K => K
arg2
} else {
// Create/extend partial string: "abc"||K => [a,b,c|K]
Term::PartialString(Cell::default(), s, Box::new(arg2))
}
}
Term::Literal(_, Literal::Atom(atom)) if atom == atom!("[]") => {
// Empty string in codes mode: ""||K => K
arg2
}
Term::Cons(_, _, _) => {
// Handle codes mode: "abc" becomes Term::Cons([97,98,99])
// Replace the [] tail with arg2
Self::replace_cons_tail(arg1, arg2)
}
_ => {
// Should never reach here due to validation, but handle gracefully
Term::Clause(Cell::default(), name, vec![arg1, arg2])
}
}
} else {
Term::Clause(Cell::default(), name, vec![arg1, arg2])
};

self.terms.push(term);
self.stack.push(TokenDesc {
Expand Down Expand Up @@ -422,6 +471,7 @@ impl<'a, R: CharRead> Parser<'a, R> {
Token::Close => TokenType::Close,
Token::OpenCT => TokenType::OpenCT,
Token::HeadTailSeparator => TokenType::HeadTailSeparator,
Token::DoubleBar => TokenType::DoubleBar,
Token::OpenList => TokenType::OpenList,
Token::CloseList => TokenType::CloseList,
Token::OpenCurly => TokenType::OpenCurly,
Expand Down Expand Up @@ -735,7 +785,7 @@ impl<'a, R: CharRead> Parser<'a, R> {
self.stack.push(TokenDesc {
tt: TokenType::Term,
priority: 0,
spec: TERM,
spec: LIST_TERM,
unfold_bounds: 0,
});

Expand Down Expand Up @@ -1022,24 +1072,114 @@ impl<'a, R: CharRead> Parser<'a, R> {
}
}
Token::HeadTailSeparator => {
/* '|' as an operator must have priority > 1000 and can only be infix.
* See: http://www.complang.tuwien.ac.at/ulrich/iso-prolog/dtc2#Res_A78
*/
let (priority, spec) = get_op_desc(atom!("|"), op_dir)
.map(|CompositeOpDesc { inf, spec, .. }| (inf, spec))
.unwrap_or((1000, DELIMITER));
// Check if next token is also HeadTailSeparator (i.e., "| |" with space)
// This allows both "||" and "| |" syntax per spec
if matches!(self.tokens.last(), Some(Token::HeadTailSeparator)) {
// Pop the second | and treat as DoubleBar
self.tokens.pop();

// Handle as DoubleBar - check validation constraints
if let Some(last_stack) = self.stack.last() {
if last_stack.tt == TokenType::Term && last_stack.spec == BTERM {
return Err(ParserError::IncompleteReduction(
self.lexer.line_num,
self.lexer.col_num,
));
}
if last_stack.tt == TokenType::Term && last_stack.spec == LIST_TERM {
return Err(ParserError::IncompleteReduction(
self.lexer.line_num,
self.lexer.col_num,
));
}
}

let is_valid = if let Some(last_term) = self.terms.last() {
match last_term {
Term::CompleteString(_, _) => true,
Term::PartialString(_, _, _) => true,
_ => false,
}
} else {
false
};

if !is_valid {
return Err(ParserError::IncompleteReduction(
self.lexer.line_num,
self.lexer.col_num,
));
}

self.reduce_op(1);
self.shift(Token::DoubleBar, 1, XFY as u32);
} else {
// Handle as regular HeadTailSeparator
/* '|' as an operator must have priority > 1000 and can only be infix.
* See: http://www.complang.tuwien.ac.at/ulrich/iso-prolog/dtc2#Res_A78
*/
let (priority, spec) = get_op_desc(atom!("|"), op_dir)
.map(|CompositeOpDesc { inf, spec, .. }| (inf, spec))
.unwrap_or((1000, DELIMITER));

let old_stack_len = self.stack.len();
let old_stack_len = self.stack.len();

self.reduce_op(priority);
self.reduce_op(priority);

let new_stack_len = self.stack.len();
let new_stack_len = self.stack.len();

if let Some(term_desc) = self.stack.last_mut() {
term_desc.unfold_bounds = old_stack_len - new_stack_len;
if let Some(term_desc) = self.stack.last_mut() {
term_desc.unfold_bounds = old_stack_len - new_stack_len;
}

self.shift(Token::HeadTailSeparator, priority, spec);
}
}
Token::DoubleBar => {
// Double bar operator only valid after string literals
// NOT valid after parenthesized expressions or variables

// Check that the last stack element is not from brackets or list syntax
if let Some(last_stack) = self.stack.last() {
if last_stack.tt == TokenType::Term && last_stack.spec == BTERM {
// Term came from parentheses like ("a"), reject it
return Err(ParserError::IncompleteReduction(
self.lexer.line_num,
self.lexer.col_num,
));
}
if last_stack.tt == TokenType::Term && last_stack.spec == LIST_TERM {
// Term came from list syntax like [a,b,c], reject it
return Err(ParserError::IncompleteReduction(
self.lexer.line_num,
self.lexer.col_num,
));
}
}

// Check that the last term is a string literal (CompleteString, PartialString, or Cons from codes mode)
// NOT arbitrary lists like [1,2,3] or variables from list syntax
let is_valid = if let Some(last_term) = self.terms.last() {
match last_term {
Term::CompleteString(_, _) => true,
Term::PartialString(_, _, _) => true,
Term::Cons(_, _, _) => true, // Allows codes mode: "abc" becomes [97,98,99]
Term::Literal(_, Literal::Atom(atom)) if *atom == atom!("[]") => true, // Empty string in codes mode
_ => false,
}
} else {
false
};

if !is_valid {
return Err(ParserError::IncompleteReduction(
self.lexer.line_num,
self.lexer.col_num,
));
}

self.shift(Token::HeadTailSeparator, priority, spec);
self.reduce_op(1);
self.shift(Token::DoubleBar, 1, XFY as u32);
}
Token::Comma => {
self.reduce_op(1000);
Expand All @@ -1051,6 +1191,7 @@ impl<'a, R: CharRead> Parser<'a, R> {
| Some(TokenType::OpenList)
| Some(TokenType::OpenCurly)
| Some(TokenType::HeadTailSeparator)
| Some(TokenType::DoubleBar)
| Some(TokenType::Comma) => {
return Err(ParserError::IncompleteReduction(
self.lexer.line_num,
Expand Down
Loading