// Souffle - A Datalog Compiler
// Copyright (c) 2013-14, Oracle and/or its affiliates. All rights reserved
// Licensed under the Universal Permissive License v 1.0 as shown at:
// - https://opensource.org/licenses/UPL
// - <souffle root>/licenses/SOUFFLE-UPL.txt

/* Conversion of non-deterministic FSA to deterministic FSA.
 * Complex souffle example that demonstrates: components,
 * aggregates, and string concatenation. */

/* The inputs: alphabet, set of states and transition relation */
/* By convention: state 1 is initial state, last state is the final accepting state */

.type N <: number
.type Letter <: symbol

.decl state(n : N)
.input state()
.decl alphabet(x : Letter)
.input alphabet()
.decl tr(s1 : N, s2 : N, x : Letter)
.input tr()

// --------------------------------------------------------
// --------------------------------------------------------
// Generic common code

.type NumberList = [head: N, tail: NumberList]
.comp Vector<T, TList> {
	// note: TList must be list of T

	// input relations:
	.decl size(l: number)	// must be one number that determines the size
	.decl element(x: T)	// contains all valid elements for this vector

	// output relations:
	.decl len(list: TList, result: N)
	.decl sublist(list: TList)	// is 'list' a list of given size, or any smaller?
	.decl vector(list: TList)	// is 'list' our vector, i.e. list of given size
	.decl member_at(list: TList, index: N, member: T)	// is 'member' at given index position, 1-based index

	len(nil, 0).
	len([h, t], l+1) :-  sublist([h, t]), len(t, l).

	sublist(nil).
	sublist([h, t]) :- element(h), sublist(t), len(t, l), size(max_size), l < max_size.

	vector(list) :- sublist(list), size(max_size), len(list, max_size).


	member_at([h, t], idx, member) :-
		element(h), sublist(t), element(member),
		member_at(t, idx, member).

	member_at([h, t], idx, member) :-
		element(h), sublist(t), element(member),
		size(max_size), idx <= max_size,
		h = member, len([h, t], idx).
}

.comp Automaton<TState, TAlpha, TWord> {
	// note: TWord must be list of TAlpha

	// input relations:
	.decl state(s: TState)
	.decl start_state(s: TState)
	.decl final(s: TState)
	.decl alphabet(x: TAlpha)
	.decl tr(s: TState, q: TState, a: TAlpha)

	.decl max_acceptance_size(n: number)

	// private
	.init Word = Vector<TAlpha, TWord>
	Word.size(x) :- max_acceptance_size(x).
	Word.element(x) :- alphabet(x).

	// output
	.decl accepts(w: TWord)
	.decl accepts_from_state(w: TWord, s: TState)

	accepts(w) :- Word.sublist(w), start_state(s), accepts_from_state(w, s).

	accepts_from_state(nil, s) :- final(s).
	accepts_from_state([h, t], s) :-
		alphabet(h), state(s), Word.sublist(t),
		tr(s, q, h), accepts_from_state(t, q).
}


// -------------------------------------------------------
// -------------------------------------------------------
// Conversion NFSA -> FSA


.type Word = [ c : Letter, r : Word ]
.init Nfsa = Automaton<N, Letter, Word>
Nfsa.start_state(1).
Nfsa.state(s) :- state(s).
Nfsa.final(fs) :- fs = max s : { state(s) }.
Nfsa.alphabet(c) :- alphabet(c).
Nfsa.max_acceptance_size(5).
Nfsa.tr(s, q, c) :- tr(s, q, c).

// FSA state is bitvector that represents the set of NFSA states
.init FsaState = Vector<number, NumberList>
FsaState.size(sz) :- Nfsa.final(sz).
FsaState.element(0).
FsaState.element(1).


.decl not_fsa_start_state(s: NumberList)
not_fsa_start_state(s) :- FsaState.member_at(s, x, 1), !Nfsa.start_state(x).

.init Fsa = Automaton<NumberList, Letter, Word>
Fsa.state(s) :- FsaState.vector(s).
Fsa.start_state(s) :- FsaState.member_at(s, nfsa_init, 1), Nfsa.start_state(nfsa_init), !not_fsa_start_state(s).
Fsa.final(s) :- Nfsa.final(fin), FsaState.member_at(s, fin, 1).
Fsa.alphabet(c) :- alphabet(c).
Fsa.max_acceptance_size(5).

// the result of the conversion is this relation that
// defines the transitions of the resulting FSA
Fsa.tr(s, q, a) :-
	FsaState.vector(s), FsaState.vector(q), Nfsa.alphabet(a),
	!not_fsa_tr(s, q, a), // negation of negation helps us to get rid of quantification (for all)
	Nfsa.tr(x, y, a),		// this is so that there is at least one transition between the two states
	FsaState.member_at(s, x, 1), FsaState.member_at(q, y, 1). // this is so that s,q (the states) are not empty sets


.decl has_x(s: NumberList, y: N, a: Letter) // does the list have some 'x' for which tr(x, y, a)?
has_x(S, y, a) :- FsaState.member_at(S, x, 1), Nfsa.tr(x,y,a).

.decl not_fsa_tr(s: NumberList, q: NumberList, a: Letter)

// it is not fsa transition if have some x from S and tr(x,y,a) for such y that is not member of Q
// i.e. Q is missing some target states
not_fsa_tr(s, q, a) :-
	FsaState.vector(s), FsaState.vector(q), Nfsa.alphabet(a),
	Nfsa.tr(x, y, a), FsaState.member_at(s, x, 1), !FsaState.member_at(q, y, 1).

// it is not fsa transition if for y from Q there is no x from S such that tr(x,y,a):
// i.e. Q has got some more states than it should
not_fsa_tr(s, q, a) :-
	FsaState.vector(s), FsaState.vector(q), Nfsa.alphabet(a),
	FsaState.member_at(q, y, 1), !has_x(s, y, a).


// -------------------------------------------------------
// -------------------------------------------------------
// Debug output: accepted word of length <= 5 and deterministric transition relation

// note: the numbering of recursive structures may differ when we
// run the program in parallel, so relations that output recursive structures
// cannot be used for testing purposes. For that reason, we convert the states
// into strings, e.g. "01011" and words too.

.type String <: symbol
.decl fsa_state_to_str(state: NumberList, str: String)
fsa_state_to_str(nil, " ").
fsa_state_to_str([0,t], cat("0", str)) :- FsaState.sublist(t), fsa_state_to_str(t, str).
fsa_state_to_str([1,t], cat("1", str)) :- FsaState.sublist(t), fsa_state_to_str(t, str).

.decl word_to_str(word: Word, str: String)
word_to_str(nil, " ").
word_to_str([h,t], cat(h, str)) :- Fsa.alphabet(h), Fsa.Word.sublist(t), word_to_str(t, str).

.decl fsa_accepted(w: String)
.output fsa_accepted()
fsa_accepted(w) :- Fsa.accepts(list), word_to_str(list, w).

.decl nfsa_accepted(w: String)
.output nfsa_accepted()
nfsa_accepted(w) :- Nfsa.accepts(list), word_to_str(list, w).

.decl fsa_tr(s: String, q: String, c: Letter)
.output fsa_tr()
fsa_tr(s_str, q_str, c) :- Fsa.tr(s, q, c), fsa_state_to_str(s, s_str), fsa_state_to_str(q, q_str).

