/* * Copyright 2022 Arnaud Golfouse * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use super::{next_word, parse_number}; use ielr::{ input::{ConflictSolution, ConflictingAction, Node, ProdIdx, Token}, output::Lookahead, }; use std::collections::HashMap; pub(super) fn parse_conflict( line: &str, nodes: &HashMap<&str, (Node, Vec)>, tokens: &HashMap<&str, Token>, ) -> Result { let line = match line.strip_prefix('(') { Some(line) => line, None => return Err(String::from("expected '(' after '@conflict'")), }; let (mut line, mut action1) = parse_conflict_action(line, nodes, tokens)?; line = line.trim_start(); let (line, prefer1) = if let Some(line) = line.strip_prefix('>') { (line, true) } else if let Some(line) = line.strip_prefix('<') { (line, false) } else { return Err(String::from( "expected '>' or '<' after the first conflicting action", )); }; let (mut line, mut action2) = parse_conflict_action(line, nodes, tokens)?; line = line.trim(); if line != ")" { if let Some(after) = line.strip_prefix(')') { return Err(format!("unexpected '{after}' after conflict")); } else { return Err(String::from("expected ')' after conflict")); } } if !prefer1 { std::mem::swap(&mut action1, &mut action2); } Ok(ConflictSolution { prefer: action1, over: action2, }) } fn parse_conflict_action<'a>( line: &'a str, nodes: &HashMap<&str, (Node, Vec)>, tokens: &HashMap<&str, Token>, ) -> Result<(&'a str, ConflictingAction), String> { let line = line.trim_start(); if let Some(line) = line.strip_prefix("reduce(") { let (productions, line) = match next_word(line) { Some((word, line)) => { if let Some((_, productions)) = nodes.get(word) { (productions, line) } else { return Err(format!("unknown node: {word}")); } } None => return Err(String::from("expected identifier after 'reduce'")), }; let line = if let Some(line) = line.strip_prefix(',') { line.trim_start() } else { return Err(String::from("expected ',' after the node in 'reduce'")); }; let (line, index) = parse_number(line)?; match line.strip_prefix(')') { Some(line) => Ok((line, ConflictingAction::Reduce(productions[index as usize]))), None => Err(String::from("missing closing ')' on 'shift'")), } } else if let Some(after) = line.strip_prefix("shift(") { let (lookahead, after) = match next_word(after) { Some((word, after)) => { if word == "EOF" { (Lookahead::Eof, after) } else if let Some(token) = tokens.get(word) { (Lookahead::Token(*token), after) } else { return Err(format!("unknown token: {word}")); } } None => return Err(String::from("expected identifier after 'shift'")), }; match after.strip_prefix(')') { Some(after) => Ok((after, ConflictingAction::Shift(lookahead))), None => Err(String::from("missing closing ')' on 'shift'")), } } else { Err(String::from("expected 'reduce' or 'shift'")) } }