diff --git a/src/rexen/nfa/machine.gleam b/src/rexen/nfa/machine.gleam index d529130..b175003 100644 --- a/src/rexen/nfa/machine.gleam +++ b/src/rexen/nfa/machine.gleam @@ -1,5 +1,6 @@ import gleam/dict import gleam/list +import gleam/set.{type Set} import gleam/string import rexen/nfa/state @@ -65,42 +66,63 @@ pub fn add_transition( ) } -pub type StackValue { - StackValue(i: Int, state: state.State) +pub type Node { + Node(index: Int, state: state.State) } pub fn evaluate(nfa: NFA, input: String) -> Bool { let assert Ok(s) = dict.get(nfa.states, nfa.initial_state) - let stack = [StackValue(0, s)] - evaluate_loop(nfa, input, stack) + + let nodes = [Node(0, s)] + let visited_nodes: Set(Node) = set.new() + + evaluate_loop(nfa, input, nodes, visited_nodes) } -fn evaluate_loop(nfa: NFA, input: String, stack: List(StackValue)) -> Bool { - case stack { +fn evaluate_loop( + nfa: NFA, + input: String, + nodes: List(Node), + visited_nodes: Set(Node), +) -> Bool { + case nodes { [] -> False - [value, ..rest] -> { - case list.contains(nfa.ending_states, value.state.name) { + [node, ..rest] -> { + let visited_nodes = + set.insert(visited_nodes, Node(index: node.index, state: node.state)) + + case list.contains(nfa.ending_states, node.state.name) { True -> - case string.length(input) == value.i { + case string.length(input) == node.index { True -> True False -> { - let char = string.slice(input, value.i, 1) - let new_stack = + let char = string.slice(input, node.index, 1) + let updated_nodes = process_transitions( nfa, - value.state.transitions, + node.state.transitions, + node.index, + visited_nodes, rest, - value, char, ) - evaluate_loop(nfa, input, new_stack) + + evaluate_loop(nfa, input, updated_nodes, visited_nodes) } } False -> { - let char = string.slice(input, value.i, 1) - let new_stack = - process_transitions(nfa, value.state.transitions, rest, value, char) - evaluate_loop(nfa, input, new_stack) + let char = string.slice(input, node.index, 1) + let updated_nodes = + process_transitions( + nfa, + node.state.transitions, + node.index, + visited_nodes, + rest, + char, + ) + + evaluate_loop(nfa, input, updated_nodes, visited_nodes) } } } @@ -110,25 +132,39 @@ fn evaluate_loop(nfa: NFA, input: String, stack: List(StackValue)) -> Bool { fn process_transitions( nfa: NFA, transitions: List(state.Transition), - stack: List(StackValue), - stkv: StackValue, + index: Int, + visited_nodes: Set(Node), + pending_nodes: List(Node), char: String, -) -> List(StackValue) { +) -> List(Node) { case transitions { - [] -> stack + [] -> pending_nodes [#(matcher, name), ..rest] -> { case state.matches(matcher, char) { False -> { - process_transitions(nfa, rest, stack, stkv, char) + process_transitions( + nfa, + rest, + index, + visited_nodes, + pending_nodes, + char, + ) } True -> { - let index = case state.is_epsilon(matcher) { - True -> stkv.i - False -> stkv.i + 1 - } let assert Ok(to) = dict.get(nfa.states, name) - let new_stack = list.append(stack, [StackValue(i: index, state: to)]) - process_transitions(nfa, rest, new_stack, stkv, char) + + let new_node = case state.is_epsilon(matcher) { + True -> Node(index: index, state: to) + False -> Node(index: index + 1, state: to) + } + + let nodes = case set.contains(visited_nodes, new_node) { + True -> pending_nodes + False -> list.append(pending_nodes, [new_node]) + } + + process_transitions(nfa, rest, index, visited_nodes, nodes, char) } } } diff --git a/test/grammar_test.gleam b/test/grammar_test.gleam deleted file mode 100644 index 48255b5..0000000 --- a/test/grammar_test.gleam +++ /dev/null @@ -1,110 +0,0 @@ -import gleam/list -import gleeunit/should -import rexen/grammar.{ - type Token, Asterix, Bar, CParen, Dot, Letter, OParen, Operator, QMark, -} - -pub fn to_string_test() { - let tokens = [ - [ - Operator(OParen), - Letter("a"), - Operator(Dot(2)), - Letter("b"), - CParen, - Operator(Asterix(3)), - ], - [ - Letter("a"), - Operator(Asterix(3)), - Letter("b"), - Operator(Asterix(3)), - Operator(Dot(2)), - Letter("c"), - Operator(Dot(2)), - Letter("d"), - Letter("e"), - Operator(Bar(1)), - Operator(Dot(2)), - ], - [ - Operator(OParen), - Letter("a"), - Operator(Dot(2)), - Letter("b"), - CParen, - Operator(QMark(3)), - ], - ] - to_string_loop(tokens, []) - |> should.equal(["(ab)*", "a*b*cde|", "(ab)?"]) -} - -fn to_string_loop( - tokens: List(List(Token)), - output: List(String), -) -> List(String) { - case tokens { - [] -> output - [toks, ..rest] -> { - to_string_loop(rest, list.append(output, [grammar.to_string(toks, "")])) - } - } -} - -pub fn shunt_ok_test() { - let input = ["(a*b*)c(d|e)", "a|b", "a*b"] - shunt_ok_loop(input, []) - |> should.equal([ - [ - Letter("a"), - Operator(Asterix(3)), - Letter("b"), - Operator(Asterix(3)), - Operator(Dot(2)), - Letter("c"), - Operator(Dot(2)), - Letter("d"), - Letter("e"), - Operator(Bar(1)), - Operator(Dot(2)), - ], - [Letter("a"), Letter("b"), Operator(Bar(1))], - [Letter("a"), Operator(Asterix(3)), Letter("b"), Operator(Dot(2))], - ]) -} - -fn shunt_ok_loop( - input: List(String), - output: List(List(Token)), -) -> List(List(Token)) { - case input { - [] -> output - [str, ..rest] -> { - let assert Ok(out) = grammar.shunt(str) - shunt_ok_loop(rest, list.append(output, [out])) - } - } -} - -// the shunt function returns an error only when a closing bracket does not -// have a corresponding opening bracket. -pub fn shunt_test() { - let input = ["(a*|)", "abab|)"] - shunt_loop(input, []) - |> should.equal([True, False]) -} - -// This append False to the output list when we have an error and True on okay -fn shunt_loop(input: List(String), output: List(Bool)) -> List(Bool) { - case input { - [] -> output - [str, ..rest] -> { - let out = case grammar.shunt(str) { - Ok(_) -> True - Error(_) -> False - } - shunt_loop(rest, list.append(output, [out])) - } - } -} diff --git a/test/rexen_test.gleam b/test/rexen_test.gleam index d63f7ec..ecd12ad 100644 --- a/test/rexen_test.gleam +++ b/test/rexen_test.gleam @@ -1,32 +1,5 @@ -import gleam/list import gleeunit -import gleeunit/should -import rexen -import rexen/nfa/machine pub fn main() { gleeunit.main() } - -pub fn compute_test() { - let assert Ok(nfa) = rexen.new("(a*b*)c(d|e)") - compute_loop( - nfa, - ["abcd", "abce", "aabbbcd", "aaaabbbbce", "bc", "bceeee"], - [], - ) - |> should.equal([True, True, True, True, False, False]) -} - -fn compute_loop( - nfa: machine.NFA, - input: List(String), - output: List(Bool), -) -> List(Bool) { - case input { - [] -> output - [str, ..rest] -> { - compute_loop(nfa, rest, list.append(output, [rexen.compute(nfa, str)])) - } - } -}