diff --git a/alpha-api/src/main/java/at/ac/tuwien/kr/alpha/api/programs/InputProgram.java b/alpha-api/src/main/java/at/ac/tuwien/kr/alpha/api/programs/InputProgram.java index 7c3df5bf8..54f697292 100644 --- a/alpha-api/src/main/java/at/ac/tuwien/kr/alpha/api/programs/InputProgram.java +++ b/alpha-api/src/main/java/at/ac/tuwien/kr/alpha/api/programs/InputProgram.java @@ -1,5 +1,6 @@ package at.ac.tuwien.kr.alpha.api.programs; +import at.ac.tuwien.kr.alpha.api.programs.modules.Module; import at.ac.tuwien.kr.alpha.api.programs.rules.Rule; import at.ac.tuwien.kr.alpha.api.programs.rules.heads.Head; import at.ac.tuwien.kr.alpha.api.programs.tests.TestCase; @@ -13,4 +14,6 @@ public interface InputProgram extends Program> { */ List getTestCases(); + List getModules(); + } diff --git a/alpha-api/src/main/java/at/ac/tuwien/kr/alpha/api/programs/modules/Module.java b/alpha-api/src/main/java/at/ac/tuwien/kr/alpha/api/programs/modules/Module.java new file mode 100644 index 000000000..d43bdce6c --- /dev/null +++ b/alpha-api/src/main/java/at/ac/tuwien/kr/alpha/api/programs/modules/Module.java @@ -0,0 +1,18 @@ +package at.ac.tuwien.kr.alpha.api.programs.modules; + +import at.ac.tuwien.kr.alpha.api.programs.InputProgram; +import at.ac.tuwien.kr.alpha.api.programs.Predicate; + +import java.util.Set; + +public interface Module { + + String getName(); + + Predicate getInputSpec(); + + Set getOutputSpec(); + + InputProgram getImplementation(); + +} diff --git a/alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/programs/InputProgramImpl.java b/alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/programs/InputProgramImpl.java index 64c3c5a3c..fdf4d0021 100644 --- a/alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/programs/InputProgramImpl.java +++ b/alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/programs/InputProgramImpl.java @@ -33,6 +33,7 @@ import at.ac.tuwien.kr.alpha.api.programs.rules.Rule; import at.ac.tuwien.kr.alpha.api.programs.rules.heads.Head; import at.ac.tuwien.kr.alpha.api.programs.tests.TestCase; +import at.ac.tuwien.kr.alpha.api.programs.modules.Module; import at.ac.tuwien.kr.alpha.commons.util.Util; import java.util.Collections; @@ -46,13 +47,15 @@ // TODO rename this to InputProgramImpl or some such class InputProgramImpl extends AbstractProgram> implements InputProgram { - static final InputProgramImpl EMPTY = new InputProgramImpl(Collections.emptyList(), Collections.emptyList(), new InlineDirectivesImpl(), Collections.emptyList()); + static final InputProgramImpl EMPTY = new InputProgramImpl(Collections.emptyList(), Collections.emptyList(), new InlineDirectivesImpl(), Collections.emptyList(), Collections.emptyList()); private final List testCases; + private final List modules; - InputProgramImpl(List> rules, List facts, InlineDirectives inlineDirectives, List testCases) { + InputProgramImpl(List> rules, List facts, InlineDirectives inlineDirectives, List testCases, List modules) { super(rules, facts, inlineDirectives); this.testCases = testCases; + this.modules = modules; } @Override @@ -60,6 +63,10 @@ public List getTestCases() { return testCases; } + public List getModules() { + return modules; + } + @Override public String toString() { String ls = System.lineSeparator(); diff --git a/alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/programs/Programs.java b/alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/programs/Programs.java index 0a10b61d7..1885842e9 100644 --- a/alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/programs/Programs.java +++ b/alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/programs/Programs.java @@ -8,6 +8,7 @@ import at.ac.tuwien.kr.alpha.api.programs.InlineDirectives; import at.ac.tuwien.kr.alpha.api.programs.NormalProgram; import at.ac.tuwien.kr.alpha.api.programs.atoms.Atom; +import at.ac.tuwien.kr.alpha.api.programs.modules.Module; import at.ac.tuwien.kr.alpha.api.programs.rules.NormalRule; import at.ac.tuwien.kr.alpha.api.programs.rules.Rule; import at.ac.tuwien.kr.alpha.api.programs.rules.heads.Head; @@ -24,14 +25,18 @@ public static InputProgram emptyProgram() { return InputProgramImpl.EMPTY; } + public static InputProgram newInputProgram(List> rules, List facts, InlineDirectives inlineDirectives, List testCases, List modules) { + return new InputProgramImpl(rules, facts, inlineDirectives, testCases, modules); + } + // TODO rename method public static InputProgram newInputProgram(List> rules, List facts, InlineDirectives inlineDirectives, List testCases) { - return new InputProgramImpl(rules, facts, inlineDirectives, testCases); + return new InputProgramImpl(rules, facts, inlineDirectives, testCases, Collections.emptyList()); } // TODO rename method public static InputProgram newInputProgram(List> rules, List facts, InlineDirectives inlineDirectives) { - return new InputProgramImpl(rules, facts, inlineDirectives, Collections.emptyList()); + return new InputProgramImpl(rules, facts, inlineDirectives, Collections.emptyList(), Collections.emptyList()); } public static InputProgramBuilder builder() { @@ -69,6 +74,7 @@ public static class InputProgramBuilder { private InlineDirectives inlineDirectives = new InlineDirectivesImpl(); private List testCases = new ArrayList<>(); + private List modules = new ArrayList<>(); public InputProgramBuilder(InputProgram prog) { this.addRules(prog.getRules()); @@ -116,12 +122,22 @@ public InputProgramBuilder addTestCases(List testCases) { return this; } + public InputProgramBuilder addModule(Module module) { + this.modules.add(module); + return this; + } + + public InputProgramBuilder addModules(List modules) { + this.modules.addAll(modules); + return this; + } + public InputProgramBuilder accumulate(InputProgram prog) { - return this.addRules(prog.getRules()).addFacts(prog.getFacts()).addInlineDirectives(prog.getInlineDirectives()).addTestCases(prog.getTestCases()); + return this.addRules(prog.getRules()).addFacts(prog.getFacts()).addInlineDirectives(prog.getInlineDirectives()).addTestCases(prog.getTestCases()).addModules(prog.getModules()); } public InputProgram build() { - return Programs.newInputProgram(this.rules, this.facts, this.inlineDirectives, this.testCases); + return Programs.newInputProgram(this.rules, this.facts, this.inlineDirectives, this.testCases, this.modules); } } diff --git a/alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/programs/literals/ExternalLiteralImpl.java b/alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/programs/literals/ExternalLiteralImpl.java index c2d87b3e6..276ebd5ce 100644 --- a/alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/programs/literals/ExternalLiteralImpl.java +++ b/alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/programs/literals/ExternalLiteralImpl.java @@ -27,12 +27,6 @@ */ package at.ac.tuwien.kr.alpha.commons.programs.literals; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - import at.ac.tuwien.kr.alpha.api.grounder.Substitution; import at.ac.tuwien.kr.alpha.api.programs.atoms.ExternalAtom; import at.ac.tuwien.kr.alpha.api.programs.literals.ExternalLiteral; @@ -40,11 +34,12 @@ import at.ac.tuwien.kr.alpha.api.programs.terms.ConstantTerm; import at.ac.tuwien.kr.alpha.api.programs.terms.Term; import at.ac.tuwien.kr.alpha.api.programs.terms.VariableTerm; -import at.ac.tuwien.kr.alpha.commons.programs.atoms.AbstractAtom; import at.ac.tuwien.kr.alpha.commons.substitutions.BasicSubstitution; +import java.util.*; + /** - * Contains a potentially negated {@link ExternalAtomImpl}. + * Contains a potentially negated {@link ExternalAtom}. */ class ExternalLiteralImpl extends AbstractLiteral implements ExternalLiteral { @@ -67,7 +62,7 @@ public ExternalLiteralImpl negate() { } /** - * @see AbstractAtom#substitute(BasicSubstitution) + * @see Literal#substitute(Substitution) */ @Override public ExternalLiteralImpl substitute(Substitution substitution) { diff --git a/alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/programs/modules/ModuleImpl.java b/alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/programs/modules/ModuleImpl.java new file mode 100644 index 000000000..be4fd2d31 --- /dev/null +++ b/alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/programs/modules/ModuleImpl.java @@ -0,0 +1,43 @@ +package at.ac.tuwien.kr.alpha.commons.programs.modules; + +import at.ac.tuwien.kr.alpha.api.programs.InputProgram; +import at.ac.tuwien.kr.alpha.api.programs.Predicate; +import at.ac.tuwien.kr.alpha.api.programs.modules.Module; + +import java.util.Set; + +class ModuleImpl implements Module { + + private final String name; + private final Predicate inputSpec; + private final Set outputSpec; + private final InputProgram implementation; + + ModuleImpl(String name, Predicate inputSpec, Set outputSpec, InputProgram implementation) { + this.name = name; + this.inputSpec = inputSpec; + this.outputSpec = outputSpec; + this.implementation = implementation; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public Predicate getInputSpec() { + return this.inputSpec; + } + + @Override + public Set getOutputSpec() { + return this.outputSpec; + } + + @Override + public InputProgram getImplementation() { + return this.implementation; + } + +} diff --git a/alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/programs/modules/Modules.java b/alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/programs/modules/Modules.java new file mode 100644 index 000000000..0d8d04e0b --- /dev/null +++ b/alpha-commons/src/main/java/at/ac/tuwien/kr/alpha/commons/programs/modules/Modules.java @@ -0,0 +1,19 @@ +package at.ac.tuwien.kr.alpha.commons.programs.modules; + +import at.ac.tuwien.kr.alpha.api.programs.InputProgram; +import at.ac.tuwien.kr.alpha.api.programs.Predicate; +import at.ac.tuwien.kr.alpha.api.programs.modules.Module; + +import java.util.Set; + +public final class Modules { + + private Modules() { + throw new AssertionError("Cannot instantiate utility class!"); + } + + public static Module newModule(final String name, final Predicate inputSpec, final Set outputSpec, final InputProgram implementation) { + return new ModuleImpl(name, inputSpec, outputSpec, implementation); + } + +} diff --git a/alpha-core/src/main/antlr/at/ac/tuwien/kr/alpha/core/antlr/ASPCore2.g4 b/alpha-core/src/main/antlr/at/ac/tuwien/kr/alpha/core/antlr/ASPCore2.g4 index bbd952cad..caa9147fd 100644 --- a/alpha-core/src/main/antlr/at/ac/tuwien/kr/alpha/core/antlr/ASPCore2.g4 +++ b/alpha-core/src/main/antlr/at/ac/tuwien/kr/alpha/core/antlr/ASPCore2.g4 @@ -56,6 +56,10 @@ classical_literal : MINUS? basic_atom; builtin_atom : term binop term; +predicate_spec: id '/' NUMBER; + +predicate_specs: predicate_spec (COMMA predicate_specs)?; + binop : EQUAL | UNEQUAL | LESS | GREATER | LESS_OR_EQ | GREATER_OR_EQ; terms : term (COMMA terms)?; @@ -83,13 +87,16 @@ interval_bound : numeral | VARIABLE; external_atom : MINUS? AMPERSAND id (SQUARE_OPEN input = terms SQUARE_CLOSE)? (PAREN_OPEN output = terms PAREN_CLOSE)?; // NOT Core2 syntax. -directive : directive_enumeration | directive_test; // NOT Core2 syntax, allows solver specific directives. Further directives shall be added here. +directive : directive_enumeration | directive_test | directive_module; // NOT Core2 syntax, allows solver specific directives. Further directives shall be added here. directive_enumeration : SHARP DIRECTIVE_ENUM id DOT; // NOT Core2 syntax, used for aggregate translation. // Alpha-specific language extension: Unit Tests (-> https://github.com/alpha-asp/Alpha/issues/237) directive_test : SHARP DIRECTIVE_TEST id PAREN_OPEN test_satisfiability_condition PAREN_CLOSE CURLY_OPEN test_input test_assert* CURLY_CLOSE; +// Alpha-specific language extension: Program Modularization (-> https://github.com/madmike200590/evolog-thesis) +directive_module: SHARP DIRECTIVE_MODULE id PAREN_OPEN module_signature PAREN_CLOSE CURLY_OPEN statements CURLY_CLOSE; + basic_terms : basic_term (COMMA basic_terms)? ; basic_term : ground_term | variable_term; @@ -112,3 +119,5 @@ test_assert_all : TEST_ASSERT_ALL CURLY_OPEN statements? CURLY_CLOSE; test_assert_some : TEST_ASSERT_SOME CURLY_OPEN statements? CURLY_CLOSE; +module_signature : predicate_spec ARROW CURLY_OPEN ('*' | predicate_specs) CURLY_CLOSE; + diff --git a/alpha-core/src/main/antlr/at/ac/tuwien/kr/alpha/core/antlr/ASPLexer.g4 b/alpha-core/src/main/antlr/at/ac/tuwien/kr/alpha/core/antlr/ASPLexer.g4 index 1e5582d82..b0641ab1d 100644 --- a/alpha-core/src/main/antlr/at/ac/tuwien/kr/alpha/core/antlr/ASPLexer.g4 +++ b/alpha-core/src/main/antlr/at/ac/tuwien/kr/alpha/core/antlr/ASPLexer.g4 @@ -28,6 +28,7 @@ SQUARE_OPEN : '['; SQUARE_CLOSE : ']'; CURLY_OPEN : '{'; CURLY_CLOSE : '}'; +ARROW : '=>'; EQUAL : '='; UNEQUAL : '<>' | '!='; LESS : '<'; @@ -49,6 +50,8 @@ TEST_GIVEN : 'given'; TEST_ASSERT_ALL : 'assertForAll'; TEST_ASSERT_SOME : 'assertForSome'; +DIRECTIVE_MODULE : 'module'; + ID : ('a'..'z') ( 'A'..'Z' | 'a'..'z' | '0'..'9' | '_' )*; VARIABLE : ('A'..'Z') ( 'A'..'Z' | 'a'..'z' | '0'..'9' | '_' )*; diff --git a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/grounder/instantiation/LiteralInstantiator.java b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/grounder/instantiation/LiteralInstantiator.java index 68d3839fc..ef9b4d5a4 100644 --- a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/grounder/instantiation/LiteralInstantiator.java +++ b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/grounder/instantiation/LiteralInstantiator.java @@ -112,7 +112,7 @@ private LiteralInstantiationResult instantiateFixedInterpretationLiteral(FixedIn } /** - * Calculates a substitution that adds an enumeration index (see {@link EnumerationLiteral#addEnumerationIndexToSubstitution(BasicSubstitution)}) + * Calculates a substitution that adds an enumeration index (see {@link EnumerationLiteral#addEnumerationIndexToSubstitution(Substitution)}) * to the given partial substitution. Due to the special nature of enumeration literals, this method will always return * {@link LiteralInstantiationResult.Type#CONTINUE} as its result type. This method assumes that the partial substitution has * not been applied to the passed literal. diff --git a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/parser/ParseTreeVisitor.java b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/parser/ParseTreeVisitor.java index 6bdb979cf..6d90d99eb 100644 --- a/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/parser/ParseTreeVisitor.java +++ b/alpha-core/src/main/java/at/ac/tuwien/kr/alpha/core/parser/ParseTreeVisitor.java @@ -50,6 +50,7 @@ import at.ac.tuwien.kr.alpha.commons.programs.Programs.InputProgramBuilder; import at.ac.tuwien.kr.alpha.commons.programs.atoms.Atoms; import at.ac.tuwien.kr.alpha.commons.programs.literals.Literals; +import at.ac.tuwien.kr.alpha.commons.programs.modules.Modules; import at.ac.tuwien.kr.alpha.commons.programs.rules.Rules; import at.ac.tuwien.kr.alpha.commons.programs.rules.heads.Heads; import at.ac.tuwien.kr.alpha.commons.programs.terms.Terms; @@ -58,6 +59,7 @@ import at.ac.tuwien.kr.alpha.core.antlr.ASPCore2Parser; import org.antlr.v4.runtime.RuleContext; import org.antlr.v4.runtime.tree.TerminalNode; +import org.apache.commons.lang3.tuple.ImmutablePair; import java.util.*; import java.util.function.IntPredicate; @@ -306,6 +308,9 @@ public Object visitDirective_enumeration(ASPCore2Parser.Directive_enumerationCon @Override public Object visitDirective_test(ASPCore2Parser.Directive_testContext ctx) { + if (!programBuilders.empty()) { + throw new IllegalStateException("Test directives are not allowed in nested programs!"); + } // directive_test : DIRECTIVE_TEST id PAREN_OPEN test_satisfiability_condition PAREN_CLOSE CURLY_OPEN test_input test_assert* CURLY_CLOSE; String name = visitId(ctx.id()); IntPredicate answerSetCountVerifier = visitTest_satisfiability_condition(ctx.test_satisfiability_condition()); @@ -324,6 +329,44 @@ public Object visitDirective_test(ASPCore2Parser.Directive_testContext ctx) { return null; } + public Object visitDirective_module(ASPCore2Parser.Directive_moduleContext ctx) { + if (!programBuilders.empty()) { + throw new IllegalStateException("Module directives are not allowed in nested programs!"); + } + // directive_module: SHARP DIRECTIVE_MODULE id PAREN_OPEN module_signature PAREN_CLOSE CURLY_OPEN statements CURLY_CLOSE; + String name = visitId(ctx.id()); + ImmutablePair> moduleSignature = visitModule_signature(ctx.module_signature()); + startNestedProgram(); + visitStatements(ctx.statements()); + InputProgram moduleImplementation = endNestedProgram(); + currentLevelProgramBuilder.addModule(Modules.newModule(name, moduleSignature.getLeft(), moduleSignature.getRight(), moduleImplementation)); + return null; + } + + public ImmutablePair> visitModule_signature(ASPCore2Parser.Module_signatureContext ctx) { + Predicate inputPredicate = visitPredicate_spec(ctx.predicate_spec()); + Set outputPredicates = ctx.predicate_specs() != null ? visitPredicate_specs(ctx.predicate_specs()) : Collections.emptySet(); + return ImmutablePair.of(inputPredicate, outputPredicates); + } + + @Override + public Set visitPredicate_specs(ASPCore2Parser.Predicate_specsContext ctx) { + // predicate_specs : predicate_spec (COMMA predicate_specs)?; + Set result = new LinkedHashSet<>(); + result.add(visitPredicate_spec(ctx.predicate_spec())); + if (ctx.predicate_specs() != null) { + result.addAll(visitPredicate_specs(ctx.predicate_specs())); + } + return result; + } + + @Override + public Predicate visitPredicate_spec(ASPCore2Parser.Predicate_specContext ctx) { + String symbol = visitId(ctx.id()); + int arity = Integer.parseInt(ctx.NUMBER().getText()); + return Predicates.getPredicate(symbol, arity); + } + @Override public Set visitBody(ASPCore2Parser.BodyContext ctx) { // body : ( naf_literal | aggregate ) (COMMA body)?; @@ -710,14 +753,23 @@ public Assertion visitTestVerifier(Assertion.Mode assertionMode, ASPCore2Parser. return Tests.newAssertion(assertionMode, Programs.emptyProgram()); } List stmts = ctx.statement(); - programBuilders.push(currentLevelProgramBuilder); - currentLevelProgramBuilder = new InputProgramBuilder(); + startNestedProgram(); for (ASPCore2Parser.StatementContext stmtCtx : stmts) { visit(stmtCtx); } - InputProgram verifier = currentLevelProgramBuilder.build(); - currentLevelProgramBuilder = programBuilders.pop(); + InputProgram verifier = endNestedProgram(); return Tests.newAssertion(assertionMode, verifier); } + private void startNestedProgram() { + programBuilders.push(currentLevelProgramBuilder); + currentLevelProgramBuilder = new InputProgramBuilder(); + } + + private InputProgram endNestedProgram() { + InputProgram result = currentLevelProgramBuilder.build(); + currentLevelProgramBuilder = programBuilders.pop(); + return result; + } + } diff --git a/alpha-core/src/test/java/at/ac/tuwien/kr/alpha/core/parser/ParserTest.java b/alpha-core/src/test/java/at/ac/tuwien/kr/alpha/core/parser/ParserTest.java index 3557a6fa0..8feb5bc04 100644 --- a/alpha-core/src/test/java/at/ac/tuwien/kr/alpha/core/parser/ParserTest.java +++ b/alpha-core/src/test/java/at/ac/tuwien/kr/alpha/core/parser/ParserTest.java @@ -34,12 +34,11 @@ import java.io.IOException; import java.nio.channels.ReadableByteChannel; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Optional; +import java.util.*; import java.util.stream.Stream; +import at.ac.tuwien.kr.alpha.api.programs.Predicate; +import at.ac.tuwien.kr.alpha.api.programs.modules.Module; import at.ac.tuwien.kr.alpha.api.programs.tests.Assertion; import at.ac.tuwien.kr.alpha.api.programs.tests.TestCase; import org.antlr.v4.runtime.CharStream; @@ -87,6 +86,12 @@ public class ParserTest { private static final String UNIT_TEST_KEYWORDS_AS_IDS = "assert(a) :- given(b). # test test(expect: 1) { given { given(b). } assertForAll { :- not assert(a). :- assertForSome(b).}}"; + private static final String MODULE_SIMPLE = "#module aSimpleModule(input/1 => {out1/2, out2/3}) { p(a). p(b). q(X) :- p(X). }"; + + private static final String MODULE_OUTPUT_ALL = "#module mod(in/1 => {*}) { a(X). b(X) :- a(X).}"; + + private static final String MODULE_WITH_REGULAR_STMTS = "p(a). p(b). q(X) :- p(X). #module aSimpleModule(input/1 => {out1/2, out2/3}) { p(a). p(b). q(X) :- p(X). }"; + private final ProgramParserImpl parser = new ProgramParserImpl(); @Test @@ -305,4 +310,63 @@ public void unitTestKeywordsAsIds() { assertEquals(Assertion.Mode.FOR_ALL, tc.getAssertions().get(0).getMode()); } + @Test + public void simpleModule() { + InputProgram prog = parser.parse(MODULE_SIMPLE); + List modules = prog.getModules(); + assertFalse(modules.isEmpty()); + assertEquals(1, modules.size()); + Module module = modules.get(0); + assertEquals("aSimpleModule", module.getName()); + Predicate inputSpec = module.getInputSpec(); + assertEquals("input", inputSpec.getName()); + assertEquals(1, inputSpec.getArity()); + Set outputSpec = module.getOutputSpec(); + assertEquals(2, outputSpec.size()); + assertTrue(outputSpec.contains(Predicates.getPredicate("out1", 2))); + assertTrue(outputSpec.contains(Predicates.getPredicate("out2", 3))); + InputProgram implementation = module.getImplementation(); + assertEquals(2, implementation.getFacts().size()); + assertEquals(1, implementation.getRules().size()); + } + + @Test + public void moduleOutputAll() { + InputProgram prog = parser.parse(MODULE_OUTPUT_ALL); + List modules = prog.getModules(); + assertFalse(modules.isEmpty()); + assertEquals(1, modules.size()); + Module module = modules.get(0); + assertEquals("mod", module.getName()); + Predicate inputSpec = module.getInputSpec(); + assertEquals("in", inputSpec.getName()); + assertEquals(1, inputSpec.getArity()); + assertTrue(module.getOutputSpec().isEmpty()); + InputProgram implementation = module.getImplementation(); + assertEquals(1, implementation.getFacts().size()); + assertEquals(1, implementation.getRules().size()); + } + + @Test + public void moduleAndRegularStmts() { + InputProgram prog = parser.parse(MODULE_WITH_REGULAR_STMTS); + assertEquals(2, prog.getFacts().size()); + assertEquals(1, prog.getRules().size()); + List modules = prog.getModules(); + assertFalse(modules.isEmpty()); + assertEquals(1, modules.size()); + Module module = modules.get(0); + assertEquals("aSimpleModule", module.getName()); + Predicate inputSpec = module.getInputSpec(); + assertEquals("input", inputSpec.getName()); + assertEquals(1, inputSpec.getArity()); + Set outputSpec = module.getOutputSpec(); + assertEquals(2, outputSpec.size()); + assertTrue(outputSpec.contains(Predicates.getPredicate("out1", 2))); + assertTrue(outputSpec.contains(Predicates.getPredicate("out2", 3))); + InputProgram implementation = module.getImplementation(); + assertEquals(2, implementation.getFacts().size()); + assertEquals(1, implementation.getRules().size()); + } + }