Java tutorial
/** * The GOAL Grammar Tools. Copyright (C) 2014 Koen Hindriks. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see <http://www.gnu.org/licenses/>. */ package languageTools.analyzer.agent; import java.io.File; import java.io.PrintWriter; import java.io.StringReader; import java.io.StringWriter; import java.nio.file.Files; import java.nio.file.Paths; import java.util.AbstractMap; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Pattern; import krTools.KRInterface; import krTools.errors.exceptions.ParserException; import krTools.language.DatabaseFormula; import krTools.language.Query; import krTools.language.Term; import krTools.language.Update; import krTools.language.Var; import krTools.parser.Parser; import krTools.parser.SourceInfo; import languageTools.analyzer.Validator; import languageTools.errors.ParserError.SyntaxError; import languageTools.errors.agent.AgentError; import languageTools.errors.agent.AgentErrorStrategy; import languageTools.errors.agent.AgentWarning; import languageTools.parser.GOAL; import languageTools.parser.GOAL.ActionContext; import languageTools.parser.GOAL.ActionOperatorContext; import languageTools.parser.GOAL.ActionSpecContext; import languageTools.parser.GOAL.ActionSpecsContext; import languageTools.parser.GOAL.ActionsContext; import languageTools.parser.GOAL.BasicConditionContext; import languageTools.parser.GOAL.BeliefsContext; import languageTools.parser.GOAL.DeclarationContext; import languageTools.parser.GOAL.DeclarationOrCallWithTermsContext; import languageTools.parser.GOAL.GoalsContext; import languageTools.parser.GOAL.KnowledgeContext; import languageTools.parser.GOAL.KrImportContext; import languageTools.parser.GOAL.MacroDefContext; import languageTools.parser.GOAL.MentalAtomContext; import languageTools.parser.GOAL.MentalOperatorContext; import languageTools.parser.GOAL.MentalStateConditionContext; import languageTools.parser.GOAL.ModuleContext; import languageTools.parser.GOAL.ModuleDefContext; import languageTools.parser.GOAL.ModuleImportContext; import languageTools.parser.GOAL.ModuleOptionContext; import languageTools.parser.GOAL.ModulesContext; import languageTools.parser.GOAL.NestedRulesContext; import languageTools.parser.GOAL.PostconditionContext; import languageTools.parser.GOAL.PreconditionContext; import languageTools.parser.GOAL.ProgramContext; import languageTools.parser.GOAL.ProgramRuleContext; import languageTools.parser.GOAL.RuleEvaluationOrderContext; import languageTools.parser.GOAL.SelectorContext; import languageTools.parser.GOALVisitor; import languageTools.parser.InputStreamPosition; import languageTools.parser.agent.MyGOALLexer; import languageTools.program.agent.ActionSpecification; import languageTools.program.agent.AgentProgram; import languageTools.program.agent.Module; import languageTools.program.agent.Module.ExitCondition; import languageTools.program.agent.Module.FocusMethod; import languageTools.program.agent.Module.RuleEvaluationOrder; import languageTools.program.agent.Module.TYPE; import languageTools.program.agent.actions.Action; import languageTools.program.agent.actions.ActionCombo; import languageTools.program.agent.actions.AdoptAction; import languageTools.program.agent.actions.DeleteAction; import languageTools.program.agent.actions.DropAction; import languageTools.program.agent.actions.ExitModuleAction; import languageTools.program.agent.actions.InsertAction; import languageTools.program.agent.actions.LogAction; import languageTools.program.agent.actions.ModuleCallAction; import languageTools.program.agent.actions.PrintAction; import languageTools.program.agent.actions.SendAction; import languageTools.program.agent.actions.SendOnceAction; import languageTools.program.agent.actions.UserSpecAction; import languageTools.program.agent.actions.UserSpecOrModuleCall; import languageTools.program.agent.msc.AGoalLiteral; import languageTools.program.agent.msc.BelLiteral; import languageTools.program.agent.msc.GoalALiteral; import languageTools.program.agent.msc.GoalLiteral; import languageTools.program.agent.msc.Macro; import languageTools.program.agent.msc.MentalFormula; import languageTools.program.agent.msc.MentalLiteral; import languageTools.program.agent.msc.MentalStateCondition; import languageTools.program.agent.msg.SentenceMood; import languageTools.program.agent.rules.ForallDoRule; import languageTools.program.agent.rules.IfThenRule; import languageTools.program.agent.rules.ListallDoRule; import languageTools.program.agent.rules.Rule; import languageTools.program.agent.selector.Selector; import languageTools.symbolTable.Scope; import languageTools.symbolTable.Symbol; import languageTools.symbolTable.SymbolTable; import languageTools.symbolTable.agent.ActionSymbol; import languageTools.symbolTable.agent.MacroSymbol; import languageTools.symbolTable.agent.ModuleSymbol; import languageTools.symbolTable.agent.VarSymbol; import org.antlr.v4.runtime.ANTLRErrorListener; import org.antlr.v4.runtime.CharStream; import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.TokenStream; import org.antlr.v4.runtime.misc.NotNull; import org.antlr.v4.runtime.tree.ErrorNodeImpl; import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.TerminalNode; /** * Validates an agent or module file and constructs an agent program or module. * DOC explain what is validated. How to use. What happens if filename does not * validate. How to validate just one term instead of a whole program. * Apparently it does not validate everything as there also is * {@link AgentValidatorSecondPass}. */ @SuppressWarnings("rawtypes") public class AgentValidator extends Validator<MyGOALLexer, GOAL, AgentErrorStrategy, AgentProgram> implements GOALVisitor { private GOAL parser; private static AgentErrorStrategy strategy = null; /** * Knowledge representation interface used for parsing the contents of * beliefs, etc. */ private KRInterface kri; /** * For agent validation, we use three symbol tables. The first is used for * actions and modules. Action labels and module names cannot have the same * signature because a call cannot be resolved in that case; this motivates * introducing a single table for both. The second is used for predicate * symbols and the third for macros. */ private final SymbolTable actionSymbols = new SymbolTable(); private Scope varSymbols = new SymbolTable(); public AgentValidator(String filename) { super(filename); } public AgentValidator(String filename, AgentProgram program) { super(filename, program); } @Override protected ParseTree startParser() { return this.parser.modules(); } @Override protected AgentErrorStrategy getTheErrorStrategy() { if (strategy == null) { strategy = new AgentErrorStrategy(); } return strategy; } /** * Sets the KR interface that should be used for parsing KR fragments. * * @param kri * The KR interface that should be used. */ public void setKRInterface(KRInterface kri) { this.kri = kri; } /** * @return Symbol table with action and module symbols. */ public SymbolTable getActionSymbols() { return this.actionSymbols; } /** * @return Symbol table with variable symbols. */ public Scope getVarSymbols() { return this.varSymbols; } /** * Validation of agent program resolves references to action, macro, and * module symbols, and checks whether all predicates used have been defined. */ @Override protected void secondPass(ParseTree tree) { AgentValidatorSecondPass pass = new AgentValidatorSecondPass(this); pass.validate(); } @Override protected MyGOALLexer getNewLexer(CharStream stream, ANTLRErrorListener errorlistener) { return new MyGOALLexer(stream, errorlistener); } @Override protected GOAL getNewParser(TokenStream stream) { this.parser = new GOAL(stream); return this.parser; } @Override protected AgentProgram getNewProgram(File file) { return new AgentProgram(new InputStreamPosition(0, 0, 0, 0, file)); } /** * Calls {@link ParseTree#accept} on the specified tree. */ @Override @SuppressWarnings("unchecked") public Void visit(@NotNull ParseTree tree) { tree.accept(this); return null; // Java says must return something even when Void } // ------------------------------------------------------------- // Modules // ------------------------------------------------------------- @Override public Void visitModules(ModulesContext ctx) { // Set KR interface getProgram().setKRInterface(this.kri); // Get imported module files for (ModuleImportContext imported : ctx.moduleImport()) { visitModuleImport(imported); } // Get modules defined in agent file for (ModuleContext module : ctx.module()) { visitModule(module); } // Check for main or event module Module mainOrEvent = null; for (Module module : getProgram().getModules()) { if (module.getType() == TYPE.MAIN || module.getType() == TYPE.EVENT) { mainOrEvent = module; break; } } // Report error if no main or event module if (mainOrEvent == null) { reportError(AgentError.PROGRAM_NO_MAIN_NOR_EVENT, getProgram().getSourceInfo()); } return null; // Java says must return something even when Void } @Override public Void visitModuleImport(ModuleImportContext ctx) { if (ctx.MODULEFILE() != null) { String path = removeLeadTrailCharacters(ctx.MODULEFILE().getText()); File file = new File(getPathRelativeToSourceFile(path)); // Check existence of file. Extension check handled in grammar. if (file.exists()) { getProgram().addImportedModule(file); } else { reportError(AgentError.IMPORT_MISSING_FILE, ctx, file.getPath()); } } return null; // Java says must return something even when Void } @Override public Void visitModule(ModuleContext ctx) { Module module = null; // Process module declaration module = visitModuleDef(ctx.moduleDef()); // Process module options visitModuleOptions(ctx, module); List<DatabaseFormula> knowledge = new LinkedList<>(); // Imported knowledge if (ctx.krImport() != null) { boolean hadImport = false; for (KrImportContext kriCtx : ctx.krImport()) { if (hadImport) { reportWarning(AgentWarning.MODULE_DUPLICATE_SECTION, kriCtx); } else { knowledge.addAll(visitKrImport(kriCtx)); hadImport = true; } } } // Knowledge if (ctx.knowledge() != null) { boolean hadKnowledge = false; for (KnowledgeContext knowCtx : ctx.knowledge()) { if (hadKnowledge) { reportWarning(AgentWarning.MODULE_DUPLICATE_SECTION, knowCtx); } else { knowledge.addAll(visitKnowledge(knowCtx)); hadKnowledge = true; } } } module.setKnowledge(knowledge); // Beliefs if (ctx.beliefs() != null) { boolean hadBeliefs = false; for (BeliefsContext belCtx : ctx.beliefs()) { if (hadBeliefs) { reportWarning(AgentWarning.MODULE_DUPLICATE_SECTION, belCtx); } else { module.setBeliefs(visitBeliefs(belCtx)); hadBeliefs = true; } } } // Goals if (ctx.goals() != null) { boolean hadGoals = false; for (GoalsContext goalCtx : ctx.goals()) { if (hadGoals) { reportWarning(AgentWarning.MODULE_DUPLICATE_SECTION, goalCtx); } else { module.setGoals(visitGoals(goalCtx)); hadGoals = true; } } } // Program boolean hadProgram = false; if (ctx.program() != null) { // Process rule evaluation order for (ProgramContext progCtx : ctx.program()) { if (hadProgram) { reportWarning(AgentWarning.MODULE_DUPLICATE_SECTION, progCtx); } else { RuleEvaluationOrder order = visitRuleEvaluationOrder(progCtx.ruleEvaluationOrder()); if (order == null) { order = getDefaultRuleEvaluationOrder(module.getType()); } module.setRuleEvaluationOrder(order); // Process content of program section Map.Entry<List<Macro>, List<Rule>> program = visitProgram(progCtx); // Add macro to symbol table for (final Macro macro : program.getKey()) { if (!module.getResolvedMacros() .define(new MacroSymbol(macro.getSignature(), macro, macro.getSourceInfo()))) { // report duplicate use of macro symbol reportError(AgentError.MACRO_DUPLICATE_NAME, ctx, macro.getSignature()); } } module.setMacros(program.getKey()); module.setRules(program.getValue()); // Check if program section is empty if (module.getRules().isEmpty()) { reportWarning(AgentWarning.MODULE_EMPTY_PROGRAMSECTION, progCtx, module.getNamePhrase()); } hadProgram = true; } } } if (!hadProgram && module.getType() != TYPE.INIT) { reportError(AgentError.MODULE_MISSING_PROGRAM_SECTION, ctx, module.getNamePhrase()); } // Action specifications if (ctx.actionSpecs() != null) { boolean hadSpecs = false; for (ActionSpecsContext specCtx : ctx.actionSpecs()) { if (hadSpecs) { reportWarning(AgentWarning.MODULE_DUPLICATE_SECTION, specCtx); } else { module.setActionSpecifications(visitActionSpecs(specCtx)); hadSpecs = true; } } } // Add module to program getProgram().addModule(module); // Define symbol ModuleSymbol msym = new ModuleSymbol(module.getSignature(), module, getSourceInfo(ctx)); if (!this.actionSymbols.define(msym)) { // Report duplicate action label Symbol symbol = this.actionSymbols.resolve(module.getSignature()); String specifiedAs = null; if (symbol instanceof ActionSymbol) { specifiedAs = "action"; } else if (symbol instanceof ModuleSymbol) { specifiedAs = "module"; } if (specifiedAs != null) { reportError(AgentError.ACTION_LABEL_ALREADY_DEFINED, ctx, "Module " + module.getSignature(), specifiedAs); } } // Remove variable scope for this module again this.varSymbols = this.varSymbols.getEnclosingScope(); return null; } @Override public Module visitModuleDef(ModuleDefContext ctx) { Module module = null; if (ctx.declaration() != null) { Map.Entry<String, List<Term>> function = visitDeclaration(ctx.declaration()); module = new Module(function.getKey(), TYPE.USERDEF, this.kri, getSourceInfo(ctx)); module.setParameters(function.getValue()); } else if (ctx.INIT() != null) { module = new Module(TYPE.INIT.getDisplayName(), TYPE.INIT, this.kri, getSourceInfo(ctx)); } else if (ctx.EVENT() != null) { module = new Module(TYPE.EVENT.getDisplayName(), TYPE.EVENT, this.kri, getSourceInfo(ctx)); } else if (ctx.MAIN() != null) { module = new Module(TYPE.MAIN.getDisplayName(), TYPE.MAIN, this.kri, getSourceInfo(ctx)); } // Add variable parameters of module to new scope this.varSymbols = this.varSymbols.getNewScope(module.getName()); for (Term term : module.getParameters()) { this.varSymbols.define(new VarSymbol(term.getSignature(), getSourceInfo(ctx))); } return module; } public void visitModuleOptions(ModuleContext ctx, Module module) { // Set default exit option, overwrite below if option is explicitly set module.setExitCondition(getDefaultExitCondition(module.getType())); Map<String, String> keyValuePairs = new HashMap<String, String>(); for (ModuleOptionContext option : ctx.moduleOption()) { Map.Entry<String, String> keyValuePair = visitModuleOption(option); String key = keyValuePair.getKey(); String value = keyValuePair.getValue(); // Check for duplicates if (keyValuePairs.containsKey(keyValuePair.getKey())) { reportWarning(AgentWarning.MODULE_DUPLICATE_OPTION, option, keyValuePair.getKey()); continue; } else { keyValuePairs.put(keyValuePair.getKey(), keyValuePair.getValue()); } // Process option try { if (key.equals(getTokenName(GOAL.EXIT))) { if (module.getType() == TYPE.INIT || module.getType() == TYPE.EVENT) { reportWarning(AgentWarning.MODULE_USELESS_EXIT, option, module.getType().toString()); } else { module.setExitCondition(ExitCondition.valueOf(value.toUpperCase())); } continue; } else if (key.equals(getTokenName(GOAL.FOCUS))) { if (module.getType() != TYPE.USERDEF) { reportWarning(AgentWarning.MODULE_ILLEGAL_FOCUS, option); } else { module.setFocusMethod(FocusMethod.valueOf(value.toUpperCase())); } continue; } else { reportWarning(AgentWarning.MODULE_UNKNOWN_OPTION, option, key); } } catch (IllegalArgumentException e) { reportWarning(AgentWarning.MODULE_UNKNOWN_OPTION, option, value); } } } @Override public Map.Entry<String, String> visitModuleOption(ModuleOptionContext ctx) { return new AbstractMap.SimpleEntry<String, String>(ctx.key.getText(), ctx.value.getText()); } // ------------------------------------------------------------- // Module sections // ------------------------------------------------------------- @Override public List<DatabaseFormula> visitKrImport(KrImportContext ctx) { List<DatabaseFormula> imported = new ArrayList<>(0); String path = null; if (ctx.StringLiteral() != null) { // TODO: what is the logic here? String text = ctx.StringLiteral().getText(); String[] parts = text.split("(?<!\\\\)\"", 0); path = parts[1].replace("\\\"", "\""); } if (ctx.SingleQuotedStringLiteral() != null) { // TODO: what is the logic here? String text = ctx.SingleQuotedStringLiteral().getText(); String[] parts = text.split("(?<!\\\\)'", 0); path = parts[1].replace("\\'", "'"); } File file = (path == null) ? null : new File(getPathRelativeToSourceFile(path)); // Check existence of file. Extension check handled in grammar. if (file != null && file.exists()) { try { String content = new String(Files.readAllBytes(Paths.get(file.getPath()))); imported = visit_KR_DBFs(content, new InputStreamPosition(1, 0, 0, 0, file)); } catch (Exception e) { // Convert stack trace to string StringWriter sw = new StringWriter(); e.printStackTrace(new PrintWriter(sw)); reportError(SyntaxError.FATAL, getSourceInfo(ctx), e.getMessage() + "\n" + sw.toString()); } } else { reportError(AgentError.IMPORT_MISSING_FILE, ctx, file.getPath()); } return imported; } @Override public List<DatabaseFormula> visitKnowledge(KnowledgeContext ctx) { return visit_KR_DBFs(removeLeadTrailCharacters(ctx.KR_BLOCK().getText()), getSourceInfo(ctx.KR_BLOCK())); } @Override public List<DatabaseFormula> visitBeliefs(BeliefsContext ctx) { return visit_KR_DBFs(removeLeadTrailCharacters(ctx.KR_BLOCK().getText()), getSourceInfo(ctx.KR_BLOCK())); } @Override public List<Query> visitGoals(GoalsContext ctx) { List<Query> dbfs = visit_KR_Queries(removeLeadTrailCharacters(ctx.KR_BLOCK().getText()), getSourceInfo(ctx.KR_BLOCK())); // Check that goals (queries) are closed and can be used as updates, if // not remove them List<Query> errors = new LinkedList<Query>(); for (Query dbf : dbfs) { if (!dbf.isUpdate()) { reportError(AgentError.GOALSECTION_NOT_AN_UPDATE, dbf.getSourceInfo(), dbf.toString()); errors.add(dbf); } } dbfs.removeAll(errors); return dbfs; } @Override public Map.Entry<List<Macro>, List<Rule>> visitProgram(ProgramContext ctx) { // Process macro definitions List<Macro> macros = new ArrayList<Macro>(ctx.macroDef().size()); for (MacroDefContext macrodf : ctx.macroDef()) { Macro macro = visitMacroDef(macrodf); if (macro != null) { macros.add(macro); } } // Process program rules List<Rule> rules = new ArrayList<Rule>(ctx.programRule().size()); for (ProgramRuleContext programRule : ctx.programRule()) { Rule rule = visitProgramRule(programRule); if (rule != null) { rules.add(rule); } } return new AbstractMap.SimpleEntry<List<Macro>, List<Rule>>(macros, rules); } @Override public RuleEvaluationOrder visitRuleEvaluationOrder(RuleEvaluationOrderContext ctx) { if (ctx != null && ctx.value != null) { try { return RuleEvaluationOrder.valueOf(ctx.value.getText().toUpperCase()); } catch (IllegalArgumentException e) { // simply ignore, parser will report problem } } return null; } @Override public Macro visitMacroDef(MacroDefContext ctx) { Map.Entry<String, List<Term>> declaration = visitDeclarationOrCallWithTerms( ctx.declarationOrCallWithTerms()); MentalStateCondition msc = visitMentalStateCondition(ctx.mentalStateCondition()); Macro macro = new Macro(declaration.getKey(), declaration.getValue(), msc, getSourceInfo(ctx)); // Check whether macro parameters have been used in definitions if (!msc.getFreeVar().containsAll(declaration.getValue())) { reportError(AgentError.MACRO_PARAMETERS_NOT_IN_DEFINITION, macro.getSourceInfo(), prettyPrintSet(new HashSet<>(declaration.getValue())), msc.toString()); } return macro; } @Override public Rule visitProgramRule(ProgramRuleContext ctx) { Rule rule = null; // Get mental state condition MentalStateCondition msc = visitMentalStateCondition(ctx.mentalStateCondition()); // Check that variables used in selectors are bound isSelectorVarBound(msc.getSubFormulas(), new HashSet<Var>()); // Get action part of rule ActionCombo actions = null; if (ctx.actions() != null) { actions = visitActions(ctx.actions()); } // Check for type of rule if (ctx.nestedRules() != null) { // Add variable parameters of anonymous module to a new scope this.varSymbols = this.varSymbols.getNewScope("anonymous"); for (Term term : msc.getFreeVar()) { this.varSymbols.define(new VarSymbol(term.getSignature(), getSourceInfo(ctx))); } Module module = visitNestedRules(ctx.nestedRules()); ModuleCallAction action = new ModuleCallAction(module, new ArrayList<Term>(0), getSourceInfo(ctx), this.kri); actions = new ActionCombo(); actions.addAction(action); } // Create rule of right type if (ctx.IF() != null) { rule = new IfThenRule(msc, actions); } if (ctx.FORALL() != null) { rule = new ForallDoRule(msc, actions); } if (ctx.LISTALL() != null) { Var var = null; try { // Check if there is a parser problem with variable; if so, // don't do anything (parser will report error) if (!(ctx.VAR() instanceof ErrorNodeImpl)) { String name = ctx.VAR().getText(); var = visit_KR_Var(name, getSourceInfo(ctx)); // Check for Prolog anonymous variable /* * if (this.kri.getName().equals(KRFactory.SWI_PROLOG) && * ((PrologVar) var).isAnonymous()) { FIXME CANNOT USE * PROLOGVAR HERE reportError( * AgentError.PROLOG_LISTALL_ANONYMOUS_VARIABLE, ctx.VAR(), * name); var = null; } */ } rule = new ListallDoRule(msc, var, actions); } catch (ParserException e) { // Report problem, return null, and try to continue with parsing // the rest of the source. reportParsingException(e); } } // Report issue if no actions were found if (rule != null && actions == null) { reportError(AgentError.RULE_MISSING_BODY, ctx, rule.prettyPrint()); rule = null; } return rule; } @Override public MentalStateCondition visitMentalStateCondition(MentalStateConditionContext ctx) { List<MentalFormula> formulas = new LinkedList<MentalFormula>(); // Check if something bad happened, and if so, whether we still can // report anything sensible if (ctx == null) { // can't do anything sensible here, job of parser to report back } else if (ctx.exception != null && ctx.basicCondition() == null) { if (ctx.getText().replaceAll("[^\\(]", "").length() != ctx.getText().replaceAll("[^\\)]", "") .length()) { // bracket imbalance reportError(AgentError.MSC_BRACKET_DO_NOT_MATCH, ctx, ctx.getText()); } else if (!ctx.getText().startsWith(getTokenName(GOAL.GOAL_OP)) && !ctx.getText().startsWith(getTokenName(GOAL.GOALA_OP)) && !ctx.getText().startsWith(getTokenName(GOAL.AGOAL_OP)) && !ctx.getText().startsWith(getTokenName(GOAL.BELIEF_OP)) && !ctx.getText().startsWith(getTokenName(GOAL.NOT))) { String found = "no operator"; if (ctx.children != null) { found = ctx.getChild(0).toString(); } reportError(AgentError.MSC_INVALID_OPERATOR, ctx, found); } else if (ctx.getText().startsWith(getTokenName(GOAL.NOT))) { // starts with negation, perhaps not applied to mental atom? reportError(AgentError.MSC_INVALID_NOT, ctx, ctx.getText()); } } else { MentalFormula formula = visitBasicCondition(ctx.basicCondition()); // do not add atoms that could not be validated if (formula != null) { formulas.add(formula); } if (ctx.mentalStateCondition() != null) { formulas.addAll(visitMentalStateCondition(ctx.mentalStateCondition()).getSubFormulas()); } } return new MentalStateCondition(formulas); } @Override public MentalFormula visitBasicCondition(BasicConditionContext ctx) { if (ctx.declarationOrCallWithTerms() != null) { Map.Entry<String, List<Term>> macro = visitDeclarationOrCallWithTerms(ctx.declarationOrCallWithTerms()); return new Macro(macro.getKey(), macro.getValue(), null, getSourceInfo(ctx)); } if (ctx.NOT() != null) { MentalLiteral atom = visitMentalAtom(ctx.mentalAtom()); atom.setPolarity(false); return atom; } if (ctx.mentalAtom() != null) { return visitMentalAtom(ctx.mentalAtom()); } if (ctx.TRUE() != null) { // nothing to do return null; } // Report issue reportError(AgentError.RULE_MISSING_CONDITION, ctx, ctx.getText()); return null; } @Override public MentalLiteral visitMentalAtom(MentalAtomContext ctx) { // Get selector and operator Selector selector = visitSelector(ctx.selector()); String op = visitMentalOperator(ctx.mentalOperator()); // Get condition String krFragment = removeLeadTrailCharacters(ctx.PARLIST().getText()); Query query = visit_KR_Query(krFragment, getSourceInfo(ctx)); // If no query was returned, we cannot return a literal that we can use // later for // validation purposes; return null if (query == null) { return null; } if (op.equals(getTokenName(GOAL.BELIEF_OP))) { return new BelLiteral(true, selector, query, getSourceInfo(ctx)); } else if (op.equals(getTokenName(GOAL.GOAL_OP))) { return new GoalLiteral(true, selector, query, getSourceInfo(ctx)); } else { // Check for Prolog anonymous variable /* * for (Var var : query.getFreeVar()) { FIXME CANNOT USE PROLOGVAR * HERE if (this.kri.getName().equals(KRFactory.SWI_PROLOG) && * ((PrologVar) var).isAnonymous()) { reportError( * AgentError.PROLOG_MENTAL_LITERAL_ANONYMOUS_VARIABLE, ctx, * var.toString(), ctx.toString()); } } */ if (op.equals(getTokenName(GOAL.AGOAL_OP))) { return new AGoalLiteral(true, selector, query, getSourceInfo(ctx)); } else if (op.equals(getTokenName(GOAL.GOALA_OP))) { return new GoalALiteral(true, selector, query, getSourceInfo(ctx)); } } return null; } @Override public String visitMentalOperator(MentalOperatorContext ctx) { return ctx.op.getText(); } @Override public ActionCombo visitActions(ActionsContext ctx) { ActionCombo actions = new ActionCombo(); for (ActionContext actionCtx : ctx.action()) { Action<?> action = visitAction(actionCtx); if (action == null) { continue; } if (actions.size() > 0 && actions.getActions().get(actions.size() - 1) instanceof ExitModuleAction) { reportWarning(AgentWarning.EXITMODULE_CANNOT_REACH, actionCtx, action.toString()); } actions.addAction(action); } return actions; } @Override public Action visitAction(ActionContext ctx) { if (ctx.actionOperator() != null) { // Must be action that has KR // content // Get selector Selector selector = visitSelector(ctx.selector()); // Get action operator String op = visitActionOperator(ctx.actionOperator()); if (op == null) { // Can't figure out which action but don't return null. return new UserSpecOrModuleCall("<missing name>", new ArrayList<Term>(0), getSourceInfo(ctx), this.kri); } TerminalNode parlistctx = ctx.PARLIST(); String argument = removeLeadTrailCharacters(parlistctx.getText()); // Handle cases if (op.equals(AgentProgram.getTokenName(GOAL.PRINT))) { Term parameter = visit_KR_Term(argument, getSourceInfo(parlistctx)); return new PrintAction(parameter, getSourceInfo(parlistctx), this.kri); } else if (op.equals(AgentProgram.getTokenName(GOAL.LOG))) { return new LogAction(removeLeadTrailCharacters(parlistctx.getText()), getSourceInfo(parlistctx), this.kri); } else { // send actions may have initial mood operator; check SentenceMood mood = getMood(argument); if (mood == null) { // set default mood mood = SentenceMood.INDICATIVE; } else { // remove mood operator from content argument = argument.replaceFirst(// keep indexes Pattern.quote(mood.toString()), " "); } // Parse content using KR parser Update content = visit_KR_Update(argument, getSourceInfo(parlistctx)); if (content != null) { if (op.equals(AgentProgram.getTokenName(GOAL.ADOPT))) { checkEmpty(content, getSourceInfo(parlistctx), false); return new AdoptAction(selector, content, getSourceInfo(parlistctx), this.kri); } else if (op.equals(AgentProgram.getTokenName(GOAL.DROP))) { checkEmpty(content, getSourceInfo(parlistctx), false); return new DropAction(selector, content, getSourceInfo(parlistctx), this.kri); } else if (op.equals(AgentProgram.getTokenName(GOAL.INSERT))) { checkEmpty(content, getSourceInfo(parlistctx), true); return new InsertAction(selector, content, getSourceInfo(parlistctx), this.kri); } else if (op.equals(AgentProgram.getTokenName(GOAL.DELETE))) { checkEmpty(content, getSourceInfo(parlistctx), false); return new DeleteAction(selector, content, getSourceInfo(parlistctx), this.kri); } else if (op.equals(AgentProgram.getTokenName(GOAL.SEND))) { checkEmpty(content, getSourceInfo(parlistctx), true); checkSendSelector(selector, ctx); return new SendAction(selector, mood, content, getSourceInfo(parlistctx), this.kri); } else if (op.equals(AgentProgram.getTokenName(GOAL.SENDONCE))) { checkEmpty(content, getSourceInfo(parlistctx), true); checkSendSelector(selector, ctx); return new SendOnceAction(selector, mood, content, getSourceInfo(parlistctx), this.kri); } } return null; } } else if (ctx.declarationOrCallWithTerms() != null) { Map.Entry<String, List<Term>> action = visitDeclarationOrCallWithTerms( ctx.declarationOrCallWithTerms()); return new UserSpecOrModuleCall(action.getKey(), action.getValue(), getSourceInfo(ctx), this.kri); } else if (ctx.op.getType() == GOAL.EXITMODULE) { return new ExitModuleAction(getSourceInfo(ctx), this.kri); } else { return new UserSpecOrModuleCall(ctx.op.getText(), new ArrayList<Term>(0), getSourceInfo(ctx), this.kri); } } private void checkEmpty(Update content, SourceInfo info, boolean allowNegative) { boolean addEmpty = content.getAddList().isEmpty(); boolean deleteEmpty = content.getDeleteList().isEmpty(); if (addEmpty && deleteEmpty) { reportError(AgentError.UPDATE_EMPTY, info); } else if (!allowNegative && !deleteEmpty) { reportError(AgentError.UPDATE_NEGATIVE, info); } } @Override public String visitActionOperator(ActionOperatorContext ctx) { if (ctx.op != null) { return ctx.op.getText(); } else { return null; } } @Override public Selector visitSelector(SelectorContext ctx) { if (ctx == null) { // return default return Selector.getDefault(); } else if (ctx.PARLIST() != null) { List<Term> terms = visitPARLIST(ctx.PARLIST().getText(), ctx); return new Selector(terms); } else { String op = ctx.op.getText(); return new Selector(Selector.SelectorType.valueOf(op.toUpperCase())); } } @Override public Module visitNestedRules(NestedRulesContext ctx) { List<Rule> rules = new ArrayList<Rule>(ctx.programRule().size()); for (ProgramRuleContext programRule : ctx.programRule()) { Rule rule = visitProgramRule(programRule); if (rule != null) { rules.add(rule); } } Module module = new Module("", TYPE.ANONYMOUS, this.kri, getSourceInfo(ctx)); module.setParameters(new ArrayList<Term>(0)); module.setRuleEvaluationOrder(getDefaultRuleEvaluationOrder(module.getType())); module.setRules(rules); // Remove variable scope for this module again. this.varSymbols = this.varSymbols.getEnclosingScope(); return module; } @Override public List<ActionSpecification> visitActionSpecs(ActionSpecsContext ctx) { List<ActionSpecification> specs = new ArrayList<ActionSpecification>(ctx.actionSpec().size()); for (ActionSpecContext context : ctx.actionSpec()) { ActionSpecification spec = visitActionSpec(context); if (spec != null) { // ignore if not OK specs.add(spec); } } return specs; } @Override public ActionSpecification visitActionSpec(ActionSpecContext ctx) { boolean problem = false; // Get internal/external annotation (external by default) boolean external = true; if (ctx.INTERNAL() != null) { external = false; } // Get action Map.Entry<String, List<Term>> declaration = visitDeclarationOrCallWithTerms( ctx.declarationOrCallWithTerms()); // Check for duplicate parameters Set<Term> checkDuplicates = new HashSet<Term>(); for (Term term : declaration.getValue()) { if (!checkDuplicates.add(term)) { reportError(AgentError.ACTIONSPEC_DUPLICATE_PARAMETER, ctx.declarationOrCallWithTerms(), term.toString()); } } // Get precondition Query precondition = null; if (ctx.precondition() != null) { precondition = visitPrecondition(ctx.precondition()); } problem = (precondition == null); // Get postcondition Update postcondition = null; if (ctx.postcondition() != null) { postcondition = visitPostcondition(ctx.postcondition()); } problem |= (postcondition == null); if (!problem) { // Create action UserSpecAction action = new UserSpecAction(declaration.getKey(), declaration.getValue(), external, precondition, postcondition, getSourceInfo(ctx), this.kri); // Check use of action parameters and variables in postcondition Set<Var> actionParsNotUsed = action.getFreeVar(); actionParsNotUsed.removeAll(postcondition.getFreeVar()); actionParsNotUsed.removeAll(precondition.getFreeVar()); if (!actionParsNotUsed.isEmpty()) { reportWarning(AgentWarning.ACTIONSPEC_PARAMETER_NOT_USED, ctx.declarationOrCallWithTerms(), prettyPrintSet(actionParsNotUsed)); } Set<Var> postVarNotBound = postcondition.getFreeVar(); postVarNotBound.removeAll(action.getFreeVar()); postVarNotBound.removeAll(precondition.getFreeVar()); if (!postVarNotBound.isEmpty()) { reportError(AgentError.POSTCONDITION_UNBOUND_VARIABLE, ctx.postcondition(), prettyPrintSet(postVarNotBound)); } // Create action specification ActionSpecification spec = new ActionSpecification(action); // Define symbol if (!this.actionSymbols.define(new ActionSymbol(action.getSignature(), spec, getSourceInfo(ctx)))) { // Report duplicate action label Symbol symbol = this.actionSymbols.resolve(action.getSignature()); String specifiedAs = null; if (symbol instanceof ActionSymbol) { specifiedAs = "action"; } else if (symbol instanceof ModuleSymbol) { specifiedAs = "module"; } if (specifiedAs != null) { reportError(AgentError.ACTION_LABEL_ALREADY_DEFINED, ctx, "Action " + action.getSignature(), specifiedAs); } } return spec; } else { return null; } } @Override public Query visitPrecondition(PreconditionContext ctx) { String krFragment = removeLeadTrailCharacters(ctx.KR_BLOCK().getText()); if (krFragment.isEmpty()) { reportWarning(AgentWarning.ACTIONSPEC_MISSING_PRE, ctx); } return visit_KR_Query(krFragment, getSourceInfo(ctx)); } @Override public Update visitPostcondition(PostconditionContext ctx) { String krFragment = removeLeadTrailCharacters(ctx.KR_BLOCK().getText()); if (krFragment.isEmpty()) { reportWarning(AgentWarning.ACTIONSPEC_MISSING_POST, ctx); } return visit_KR_Update(krFragment, getSourceInfo(ctx)); } @Override public Map.Entry<String, List<Term>> visitDeclaration(DeclarationContext ctx) { String name = null; List<Term> parameters; // Get functor name if (ctx.ID() != null) { name = ctx.ID().getText(); } // Get parameters if (ctx.PARLIST() != null) { parameters = visitVARLIST(ctx.PARLIST().getText(), ctx); } else { parameters = new ArrayList<Term>(0); } return new AbstractMap.SimpleEntry<String, List<Term>>(name, parameters); } @Override public Map.Entry<String, List<Term>> visitDeclarationOrCallWithTerms(DeclarationOrCallWithTermsContext ctx) { String name = null; List<Term> parameters = new ArrayList<Term>(0); // Get functor name if (ctx.ID() != null) { name = ctx.ID().getText(); } if (ctx.PARLIST() != null) { parameters = visitPARLIST(ctx.PARLIST().getText(), ctx); } return new AbstractMap.SimpleEntry<String, List<Term>>(name, parameters); } /** * Delegate parsing of PARLIST terminal node to KR parser and checks whether * terms are variables and reports errors if this is not the case. * * @param pars * String text from PARLIST terminal. * @param ctx * Parser context where PARLIST was found. * @return List of terms. */ public List<Term> visitVARLIST(String pars, ParserRuleContext ctx) { List<Term> parameters = visitPARLIST(pars, ctx); for (Term term : parameters) { if (!term.isVar()) { reportError(AgentError.PARAMETER_NOT_A_VARIABLE, ctx, strategy.prettyPrintRuleContext(ctx.getParent().getRuleIndex()), term.toString()); } } return parameters; } /** * Delegate parsing of PARLIST terminal node to KR parser. * * @param pars * String text from PARLIST terminal. * @param ctx * Parser context where PARLIST was found. * @return List of terms. */ public List<Term> visitPARLIST(String pars, ParserRuleContext ctx) { List<Term> parameters = visit_KR_Terms(removeLeadTrailCharacters(pars), getSourceInfo(ctx)); // If no parameters were returned, return the empty list to avoid a // cascade of errors. if (parameters == null) { parameters = new ArrayList<Term>(0); } /* * for (Term node : parameters) { FIXME cannot use PrologTerm here // KR * specific check: cannot use Prolog anonymous variable as // parameter * if (this.kri.getName().equals(KRFactory.SWI_PROLOG) && ((PrologTerm) * node).isAnonymousVar()) { * reportError(AgentError.PROLOG_ANONYMOUS_VARIABLE, ctx, * node.toString()); } } */ return parameters; } // ------------------------------------------------------------- // Helper methods // ------------------------------------------------------------- /** * Check whether send(once) action has valid selector type. * * @param selector * Selector found. * @param ctx * Parser context. */ private void checkSendSelector(Selector selector, ParserRuleContext ctx) { switch (selector.getType()) { case ALL: case ALLOTHER: case PARAMETERLIST: case SELF: break; case SOME: case SOMEOTHER: case THIS: reportError(AgentError.SEND_INVALID_SELECTOR, ctx, selector.toString()); break; } } /** * @param type * Type of module. * @return The default exit condition associated with the module type. */ public ExitCondition getDefaultExitCondition(TYPE type) { if (type == TYPE.MAIN) { return ExitCondition.NEVER; } else { return ExitCondition.ALWAYS; } } /** * @param type * Type of module. * @return The default rule evaluation order associated with the module * type. */ public RuleEvaluationOrder getDefaultRuleEvaluationOrder(TYPE type) { if (type == TYPE.ANONYMOUS || type == TYPE.EVENT || type == TYPE.INIT) { return RuleEvaluationOrder.LINEARALL; } else { return RuleEvaluationOrder.LINEAR; } } /** * Extracts sentence mood from message string, if any. * * @param msg * Message that is part of send action. * @return Mood operator, if message starts with operator, {@code null} * otherwise. */ public SentenceMood getMood(String msg) { String trimmed = msg.trim(); if (trimmed.startsWith("!")) { return SentenceMood.IMPERATIVE; } else if (trimmed.startsWith("?")) { return SentenceMood.INTERROGATIVE; } else if (trimmed.startsWith(":")) { return SentenceMood.INDICATIVE; } else { return null; } } /** * @param token * A token index (can be found in GOAL grammar) * @return The name of the token. */ private String getTokenName(int token) { return GOAL.tokenNames[token].replaceAll("'", ""); } /** * Checks whether all variables used in selectors in the list of mental * formulas are bound. Variables in the given set of variables are * considered bound. Reports variable(s) that are not bound as a validation * error. * * @param formulas * A list of formulas to check. * @param boundVars * A set of variables that may bound variables in selectors in * the formulas. * @return {@code true} if all variables that occur in selectors are bound, * {@code false} otherwise. */ private boolean isSelectorVarBound(List<MentalFormula> formulas, Set<Var> boundVars) { boolean bound = true; Set<Var> selectorVars; for (MentalFormula formula : formulas) { if (formula instanceof MentalLiteral) { selectorVars = ((MentalLiteral) formula).getSelector().getFreeVar(); selectorVars.removeAll(boundVars); selectorVars = outOfScope(selectorVars); bound &= selectorVars.isEmpty(); if (!bound) { // report that some variables are not bound reportError(AgentError.SELECTOR_VAR_NOT_BOUND, formula.getSourceInfo(), prettyPrintSet(selectorVars)); } // add variables bound by this formula to boundVars boundVars.addAll(formula.getFreeVar()); } else if (formula instanceof Macro) { Macro macro = (Macro) formula; if (macro.getDefinition() != null) { bound &= isSelectorVarBound(macro.getDefinition().getSubFormulas(), boundVars); } } } return bound; } /** * Checks whether all variables in a set are accessible within the current * variable scope. * * @param vars * Set of variables to check. * @return Set of variables that are out of scope. */ private Set<Var> outOfScope(Set<Var> vars) { Set<Var> outOfScope = new HashSet<Var>(); for (Var var : vars) { if (this.varSymbols.resolve(var.getSignature()) == null) { outOfScope.add(var); } } return outOfScope; } /** * Reports a parsing exception that occurred while parsing embedded language * fragments. * * @param e * The exception generated by the embedded language parser. * @param ctx * The context of the agent parser where the embedded language * fragment is located. */ private void reportParsingException(ParserException e) { String msg = e.getMessage(); if (e.getCause() != null) { msg += " because " + e.getCause().getMessage(); } reportError(SyntaxError.EMBEDDED_LANGUAGE_ERROR, e, this.kri.getName(), msg); } /** * Reports parsing errors that occurred while parsing embedded language * fragments. * * @param parser * The parser that generated the errors. */ private void reportEmbeddedLanguageErrors(Parser parser) { for (SourceInfo error : parser.getErrors()) { reportError(SyntaxError.EMBEDDED_LANGUAGE_ERROR, error, this.kri.getName(), error.getMessage()); } } // ------------------------------------------------------------- // Helper methods - embedded KR language fragments // ------------------------------------------------------------- /** * Processes embedded KR language fragments (used for knowledge and belief * sections). Assumes that these fragments represent {@link DatabaseFormula} * s. * * @param krFragments * List of KR fragments. * @param info * the source info where in the source is the first char of this * text fragment * @return List of {@link DatabaseFormula}s. */ public List<DatabaseFormula> visit_KR_DBFs(String krFragment, SourceInfo info) { List<DatabaseFormula> formulas = new ArrayList<DatabaseFormula>(0); // Get the formulas try { Parser parser = this.kri.getParser(new StringReader(krFragment), info); formulas = parser.parseDBFs(); // Add errors from parser for embedded language to our own reportEmbeddedLanguageErrors(parser); } catch (ParserException e) { // Report problem, and try to continue with parsing the rest of the // source. reportParsingException(e); } if (formulas == null) { return new ArrayList<DatabaseFormula>(0); } return formulas; } /** * Processes embedded KR language fragments (used for built-in actions). * Assumes that these fragments represent an {@link Update}. * * @param krFragment * String with KR fragment. * @param info * the source info where in the source is the first char of this * text fragment * @return {@link Update}. */ public Update visit_KR_Update(String krFragment, SourceInfo info) { Update update = null; // Get the update try { Parser parser = this.kri.getParser(new StringReader(krFragment), info); update = parser.parseUpdate(); // Add errors from parser for embedded language to our own reportEmbeddedLanguageErrors(parser); } catch (ParserException e) { // Report problem, and try to continue with parsing the rest of the // source. reportParsingException(e); } return update; } /** * Processes embedded KR language fragments (used for goals section). * Assumes that these fragments represent a {@link List<Query>}. * * @param krFragments * List of KR fragments. * @param info * the source info where in the source is the first char of this * text fragment * @return A {@link List<Query>}. */ public List<Query> visit_KR_Queries(String krFragment, SourceInfo info) { List<Query> queries = new ArrayList<Query>(0); if (krFragment.isEmpty()) { return queries; } // Get the queries try { Parser parser = this.kri.getParser(new StringReader(krFragment), info); queries = parser.parseQueries(); // Add errors from parser for embedded language to our own reportEmbeddedLanguageErrors(parser); } catch (ParserException e) { // Report problem, return, and try to continue with parsing the rest // of the source. reportParsingException(e); } return queries; } /** * Processes embedded KR language fragments (used for mental literals and * preconditions). Assumes that these fragments represent a {@link Query}. * * @param krFragments * List of KR fragments. * @param info * the source info where in the source is the first char of this * text fragment * @return A {@link Query}. */ public Query visit_KR_Query(String krFragment, SourceInfo info) { Query query = null; // Get the query Parser parser; try { parser = this.kri.getParser(new StringReader(krFragment), info); query = parser.parseQuery(); // Add errors from parser for embedded language to our own reportEmbeddedLanguageErrors(parser); } catch (ParserException e) { // Report problem, return, and try to continue with parsing the rest // of the source. reportParsingException(e); } return query; } /** * Processes embedded KR language fragments (used for print action and * selector expressions). Assumes that these fragments represent a * {@link Term}. * * @param krFragment * KR fragment string. * @param info * the source info where in the source is the first char of this * text fragment * @return A {@link Term}. */ public Term visit_KR_Term(String krFragment, SourceInfo info) { Term term = null; // Get the term try { Parser parser = this.kri.getParser(new StringReader(krFragment), info); term = parser.parseTerm(); // Add errors from parser for embedded language to our own reportEmbeddedLanguageErrors(parser); } catch (ParserException e) { // Report problem, return, and try to continue with parsing the rest // of the source. reportParsingException(e); } return term; } /** * Processes embedded KR language fragment (used for all parameter lists of * actions, macros, modules, and also for content of mental literals and * mental actions). Assumes that these fragments represent a {@link Term}. * * @param krFragment * KR fragment string. * @param info * the source info where in the source is the first char of this * text fragment * @return A {@link Term}. */ public List<Term> visit_KR_Terms(String krFragment, SourceInfo info) { List<Term> parameters = null; try { Parser parser = this.kri.getParser(new StringReader(krFragment), info); parameters = parser.parseTerms(); // Add errors from parser for embedded language to our own reportEmbeddedLanguageErrors(parser); } catch (ParserException e) { // Report problem, return, and try to continue with parsing the rest // of the source. reportParsingException(e); } return parameters; } /** * Parses a terminal node that should contain the text (name) of a variable. * In other words, assumes that the text associated with the node represents * a {@link Var}. * * @param name * the variable text to be parsed. * @param info * the source info where in the source is the first char of this * text fragment * @return The variable we got from parsing the node. * @throws ParserException * See {@link ParserException}. */ public Var visit_KR_Var(String name, SourceInfo info) throws ParserException { Parser parser = this.kri.getParser(new StringReader(name), info); Var var = parser.parseVar(); // Add errors from parser for embedded language to our own reportEmbeddedLanguageErrors(parser); return var; } // TODO: this code implements a rather naive approach to getting the // action specification(s) of the action. In particular, it does not take // scope into account. If an action is specified e.g. at top level and again // within a module, then this approach is not able to determine which action // specification should be associated with the action. // If the user intends to execute the action in the module, we maybe should // allow for expressions of the form: module.action(parameters) so that the // user is able to refer to the right scope. public static Action<?> resolve(UserSpecOrModuleCall call, AgentProgram program) { if (call == null || program == null) { return null; } for (Module module : program.getModules()) { if (call.getName().equals(module.getName()) && call.getParameters().size() == module.getParameters().size()) { return new ModuleCallAction(module, call.getParameters(), call.getSourceInfo(), program.getKRInterface()); } for (ActionSpecification specification : module.getActionSpecifications()) { UserSpecAction spec = specification.getAction(); if (call.getName().equals(spec.getName()) && call.getParameters().size() == spec.getParameters().size()) { return new UserSpecAction(call.getName(), call.getParameters(), spec.isExternal(), ((MentalLiteral) spec.getPrecondition().getSubFormulas().get(0)).getFormula(), spec.getPostcondition(), call.getSourceInfo(), program.getKRInterface()); } } } return null; } }