package org.eclipse.dltk.tcl.parser.definitions; import java.util.ArrayList; import java.util.List; import org.eclipse.dltk.tcl.ast.ArgumentMatch; import org.eclipse.dltk.tcl.ast.ComplexString; import org.eclipse.dltk.tcl.ast.StringArgument; import org.eclipse.dltk.tcl.ast.Substitution; import org.eclipse.dltk.tcl.ast.TclArgument; import org.eclipse.dltk.tcl.ast.TclCommand; import org.eclipse.dltk.tcl.ast.VariableReference; import org.eclipse.dltk.tcl.definitions.Argument; import org.eclipse.dltk.tcl.definitions.ArgumentType; import org.eclipse.dltk.tcl.definitions.Command; import org.eclipse.dltk.tcl.definitions.ComplexArgument; import org.eclipse.dltk.tcl.definitions.Constant; import org.eclipse.dltk.tcl.definitions.DefinitionsFactory; import org.eclipse.dltk.tcl.definitions.Group; import org.eclipse.dltk.tcl.definitions.Switch; import org.eclipse.dltk.tcl.definitions.TypedArgument; import org.eclipse.dltk.tcl.parser.Messages; import org.eclipse.emf.common.util.EList; public class SynopsisBuilder { private final static String NULL_SYNOPSIS = ""; //$NON-NLS-1$ private final static String NULL_LINE = "..."; //$NON-NLS-1$ private final static String NULL_ARG_DEFINITON_SYNOPSIS = "arg"; //$NON-NLS-1$ private final static String NULL_TCL_ARGUMENT_SYNOPSIS = "none"; //$NON-NLS-1$ private final static String ENDLESS_BOUNDS_END = " ..."; //$NON-NLS-1$ private final static String POSSIBLE_START = "?"; //$NON-NLS-1$ private final static String POSSIBLE_END = "?"; //$NON-NLS-1$ private final static String SWITCH_START = "["; //$NON-NLS-1$ private final static String SWITCH_END = "]"; //$NON-NLS-1$ private final static String SWITCH_SEPARATOR = "|"; //$NON-NLS-1$ private final static String COMPLEX_ARGUMENT_START = "{"; //$NON-NLS-1$ private final static String COMPLEX_ARGUMENT_END = "}"; //$NON-NLS-1$ private final static String BOUNDS_START = "("; //$NON-NLS-1$ private final static String BOUNDS_END = ")"; //$NON-NLS-1$ private final static String BOUNDS_SEPARATOR = "-"; //$NON-NLS-1$ private final static String DEFINITION_SEPARATOR = " "; //$NON-NLS-1$ private final static String LINE_SEPARATOR = "\n"; //$NON-NLS-1$ private final static String HTML_LINE_SEPARATOR = "<br/>"; //$NON-NLS-1$ private final static String LIST_SEPARATOR = ","; //$NON-NLS-1$ private final static String HTML_CONST_START = "<b>"; //$NON-NLS-1$ private final static String HTML_CONST_END = "</b>"; //$NON-NLS-1$ private final static String HTML_ARGUMENT_START = "<i>"; //$NON-NLS-1$ private final static String HTML_ARGUMENT_END = "</i>"; //$NON-NLS-1$ private final static String CONST_START = ""; //$NON-NLS-1$ private final static String CONST_END = ""; //$NON-NLS-1$ private final static String ARGUMENT_START = ""; //$NON-NLS-1$ private final static String ARGUMENT_END = ""; //$NON-NLS-1$ private class SynopsisWord { private String word; private String start; private String end; public SynopsisWord(String word, String start, String end) { this.word = word; this.start = start; this.end = end; } @Override public String toString() { StringBuilder result = new StringBuilder(); result.append(start); result.append(word); result.append(end); return result.toString(); } } private class SynopsisConstant extends SynopsisWord { public SynopsisConstant(String word) { super(word, (asHtml) ? HTML_CONST_START : CONST_START, (asHtml) ? HTML_CONST_END : CONST_END); } } private class SynopsisArgument extends SynopsisWord { public SynopsisArgument(String word) { super(word, (asHtml) ? HTML_ARGUMENT_START : ARGUMENT_START, (asHtml) ? HTML_ARGUMENT_END : ARGUMENT_END); } } private static class Synopsis { protected Argument selector; private List<Synopsis> children = new ArrayList<>(); private String string = null; public Synopsis() { } public void insert(String prefix) { if (prefix == null || prefix.length() == 0) return; if (string == null) string = prefix; else string = prefix + DEFINITION_SEPARATOR + string; } private boolean matchArgument(Argument argument, Argument selector) { if (selector == null) return false; if (selector instanceof Switch) { for (Group group : ((Switch) selector).getGroups()) { if (matchArgument(argument, group)) return true; } } else if (selector instanceof Group) { Group group = ((Group) selector); String cval = group.getConstant(); if (cval != null && cval.length() > 0) { Constant c = DefinitionsFactory.eINSTANCE.createConstant(); c.setName(cval); return DefinitionUtils.equalsArgumentIgnoreName(c, argument); } else if (group.getArguments().size() != 0) { return DefinitionUtils.equalsArgumentIgnoreName( group.getArguments().get(0), argument); } } else if (selector instanceof ComplexArgument) { ComplexArgument complex = ((ComplexArgument) selector); if (complex.getArguments().size() != 0) return DefinitionUtils.equalsArgumentIgnoreName( complex.getArguments().get(0), argument); } else { return DefinitionUtils.equalsArgumentIgnoreName(selector, argument); } return false; } private List<Synopsis> matchPrefix(TclArgument tclArgument) { List<Synopsis> matched = new ArrayList<>(); if (!(tclArgument instanceof StringArgument)) return matched; String value = ((StringArgument) tclArgument).getValue(); for (Synopsis child : children) { if (child.selector instanceof Constant) { String cvalue = ((Constant) child.selector).getName(); if (cvalue == null) continue; matched.add(child); } } if (matched.size() == 0) return matched; List<Synopsis> notMatched = new ArrayList<>(); for (int i = 0; i < value.length(); i++) { if (matched.size() == 1) { String c = ((Constant) matched.get(0).selector).getName(); if (value.equals(c)) return matched; } for (Synopsis child : matched) { String c = ((Constant) child.selector).getName(); if (c.length() <= i || value.charAt(i) != c.charAt(i)) { notMatched.add(child); } } if (notMatched.size() != 0) { matched.removeAll(notMatched); notMatched.clear(); } } return matched; } public boolean match(List<ArgumentMatch> matches) { for (ArgumentMatch match : matches) { if (matchArgument(match.getDefinition(), selector)) { return true; } } return false; } public String getShortHint(TclCommand tclCommand, Synopsis root) { if (children.size() == 0) { return (string == null) ? NULL_SYNOPSIS : string; } List<Synopsis> synopsises = null; StringBuilder result = new StringBuilder(); if (tclCommand != null) { List<ArgumentMatch> matches = tclCommand.getMatches(); if (matches.size() == 0 && this == root && tclCommand.getArguments().size() != 0) { synopsises = matchPrefix(tclCommand.getArguments().get(0)); if (synopsises.size() == 1) { String subResult = synopsises.get(0) .getShortHint(tclCommand, root); if (string != null) result.append(string); if (subResult.length() > 0) result.append(DEFINITION_SEPARATOR) .append(subResult); return result.toString(); } if (synopsises.size() == 0) synopsises = children; } else { synopsises = children; for (Synopsis synopsis : synopsises) { if (synopsis.match(matches)) { String subResult = synopsis.getShortHint(tclCommand, root); if (string != null) result.append(string); if (subResult.length() > 0) result.append(DEFINITION_SEPARATOR) .append(subResult); return result.toString(); } } } } else synopsises = children; result.append(string); boolean first = true; for (Synopsis synopsis : synopsises) { if (first) { result.append(DEFINITION_SEPARATOR).append(SWITCH_START); first = false; } else result.append(SWITCH_SEPARATOR); if (synopsis.string == null) { result.append(NULL_LINE); continue; } String[] words = synopsis.string.split(DEFINITION_SEPARATOR); result.append(words[0]); if (synopsis.children.size() != 0 || words.length > 1) result.append(ENDLESS_BOUNDS_END); } result.append(SWITCH_END); return result.toString(); } public List<String> getSynopsis() { List<String> results = new ArrayList<>(); if (children.size() == 0) { results.add((string == null) ? NULL_SYNOPSIS : string); } for (Synopsis child : children) { List<String> subResults = child.getSynopsis(); if (subResults.size() == 0) { results.add(NULL_SYNOPSIS); } for (String subResult : subResults) { StringBuilder result = new StringBuilder(); if (string != null) result.append(string); if (subResult.length() > 0) result.append(DEFINITION_SEPARATOR).append(subResult); results.add(result.toString()); } } return results; } } private boolean asHtml; public SynopsisBuilder() { this(false); } public SynopsisBuilder(boolean asHtml) { this.asHtml = asHtml; } public String getSynopsis(Command command) { if (command == null) return NULL_LINE; Synopsis root = processCommand(command); return join(root.getSynopsis(), asHtml ? HTML_LINE_SEPARATOR : LINE_SEPARATOR); } public String getShortHint(TclCommand tclCommand) { if (tclCommand == null || tclCommand.getDefinition() == null) return NULL_LINE; Synopsis root = processCommand(tclCommand.getDefinition()); return root.getShortHint(tclCommand, root); } private String join(List<String> strings, String delim) { StringBuilder result = new StringBuilder(); boolean first = true; for (String string : strings) { if (first) first = false; else result.append(delim); result.append(string); } return result.toString(); } private Synopsis processCommand(Command command) { Synopsis root = processArgumentList(command.getArguments(), 0); String commandName = new SynopsisConstant(command.getName()).toString(); root.insert(commandName); return root; } private Synopsis processArgumentList(List<Argument> arguments, int pos) { if (pos >= arguments.size()) { Synopsis leaf = new Synopsis(); if (arguments.size() != 0) leaf.selector = arguments.get(0); return leaf; } Argument argument = arguments.get(pos); if (argument instanceof Switch) { Synopsis synopsis = processSubCommands(arguments, pos); if (synopsis != null) { if (arguments.size() != 0) synopsis.selector = arguments.get(0); return synopsis; } } StringBuilder prefix = new StringBuilder(); prefix.append(definitionToString(argument, argument.getLowerBound(), argument.getUpperBound())); Synopsis synopsis = processArgumentList(arguments, pos + 1); synopsis.insert(prefix.toString()); return synopsis; } private Synopsis processSubCommands(List<Argument> arguments, int pos) { Switch sw = (Switch) arguments.get(pos); if (DefinitionUtils.isOptions(sw) || DefinitionUtils.isMode(sw) || (sw.getName() != null && sw.getName().length() > 0)) return null; Synopsis synopsis = new Synopsis(); if (sw.getLowerBound() == 0) { synopsis.children.add(new Synopsis()); } for (Group group : sw.getGroups()) { Synopsis child; String cval = group.getConstant(); if (cval != null && cval.length() > 0) { Constant c = DefinitionsFactory.eINSTANCE.createConstant(); c.setName(cval); c.setName(cval); List<Argument> list = new ArrayList<>(); list.add(c); list.addAll(group.getArguments()); child = processArgumentList(list, 0); } else { child = processArgumentList(group.getArguments(), 0); } synopsis.children.add(child); } return synopsis; } private String addBounds(String inner, int lower, int upper) { StringBuilder result = new StringBuilder(); if (lower == 0) { if (upper == -1) { result.append(POSSIBLE_START).append(inner) .append(ENDLESS_BOUNDS_END).append(POSSIBLE_END); } else { if (upper <= 2) { boolean first = true; for (int i = 0; i < upper; i++) { if (first) { first = false; } else { result.append(DEFINITION_SEPARATOR); } result.append(POSSIBLE_START).append(inner) .append(POSSIBLE_END); } } else { result.append(inner).append(BOUNDS_START).append(lower) .append(BOUNDS_SEPARATOR).append(upper) .append(BOUNDS_END); } } } else { if (upper <= 2) { boolean first = true; for (int i = 0; i < lower; i++) { if (first) { first = false; } else { result.append(DEFINITION_SEPARATOR); } result.append(inner); } if (upper == -1) { result.append(DEFINITION_SEPARATOR).append(POSSIBLE_START) .append(inner).append(ENDLESS_BOUNDS_END) .append(POSSIBLE_END); } else { for (int i = 0; i < upper - lower; i++) { result.append(DEFINITION_SEPARATOR) .append(POSSIBLE_START).append(inner) .append(POSSIBLE_END); } } } else { result.append(inner).append(BOUNDS_START).append(lower) .append(BOUNDS_SEPARATOR).append(upper) .append(BOUNDS_END); } } return result.toString(); } public String definitionToList(List<Argument> definitions) { if (definitions == null || definitions.size() == 0) return NULL_SYNOPSIS; if (definitions.size() == 1) return definitionToString(definitions.get(0)); List<String> list = new ArrayList<>(); for (Argument definition : definitions) { String value = definitionToString(definition); if (!list.contains(value)) list.add(value); } StringBuilder result = new StringBuilder(); boolean first = true; for (String s : list) { if (first) { first = false; } else { result.append(LIST_SEPARATOR); } result.append(s); } return result.toString(); } public String definitionToString(Argument argument, int lower, int upper) { if (argument == null) return addBounds(new SynopsisArgument(NULL_ARG_DEFINITON_SYNOPSIS) .toString(), lower, upper); StringBuilder out = new StringBuilder(); if (argument instanceof Constant) { out.append(new SynopsisConstant(((Constant) argument).getName()) .toString()); } else if (argument instanceof TypedArgument || (argument.getName() != null && argument.getName().length() > 0)) { out.append(new SynopsisArgument(argument.getName()).toString()); } else if (argument instanceof Group) { Group gr = (Group) argument; String inner = definitionToString(gr.getArguments()); String cval = gr.getConstant(); boolean isConst = cval != null && cval.length() > 0; boolean isInner = inner != null && inner.length() > 0; if (isConst && isInner) out.append(new SynopsisConstant(cval).toString()) .append(DEFINITION_SEPARATOR).append(inner); else if (isConst) out.append(new SynopsisConstant(cval).toString()); else if (isInner) out.append(inner); } else if (argument instanceof ComplexArgument) { ComplexArgument ca = (ComplexArgument) argument; out.append(COMPLEX_ARGUMENT_START) .append(definitionToString(ca.getArguments())) .append(COMPLEX_ARGUMENT_END); } else if (argument instanceof Switch) { Switch sw = (Switch) argument; if (DefinitionUtils.isOptions(sw)) { boolean first = true; for (Group group : sw.getGroups()) { if (first) { first = false; } else { out.append(DEFINITION_SEPARATOR); } out.append(POSSIBLE_START) .append(definitionToString(group, 1, 1)) .append(POSSIBLE_END); } return out.toString(); } else if (DefinitionUtils.isMode(sw)) { boolean first = true; out.append(SWITCH_START); for (Group group : sw.getGroups()) { if (first) { first = false; } else { out.append(SWITCH_SEPARATOR); } out.append(definitionToString(group, 1, 1)); } out.append(SWITCH_END); return addBounds(out.toString(), sw.getLowerBound(), sw.getUpperBound()); } boolean first = true; out.append(SWITCH_START); for (Group group : sw.getGroups()) { if (first) { first = false; } else { out.append(SWITCH_SEPARATOR); } out.append(definitionToString(group, group.getLowerBound(), group.getUpperBound())); } out.append(SWITCH_END); } else throw new IllegalArgumentException(); return addBounds(out.toString(), lower, upper); } public String definitionToString(List<Argument> definitions) { if (definitions == null || definitions.size() == 0) return NULL_SYNOPSIS; StringBuilder out = new StringBuilder(); boolean first = true; for (Argument definition : definitions) { if (first) first = false; else out.append(DEFINITION_SEPARATOR); out.append(definitionToString(definition)); } return out.toString(); } public String definitionToString(Argument definition) { if (definition == null) return NULL_ARG_DEFINITON_SYNOPSIS; return definitionToString(definition, definition.getLowerBound(), definition.getUpperBound()); } public String argumentToString(TclArgument argument) { if (argument == null) return NULL_TCL_ARGUMENT_SYNOPSIS; if (argument instanceof StringArgument) { return ((StringArgument) argument).getValue(); } else if (argument instanceof ComplexString) { ComplexString complexString = (ComplexString) argument; StringBuffer buffer = new StringBuffer(); EList<TclArgument> arguments = complexString.getArguments(); for (TclArgument tclArgument : arguments) { buffer.append(argumentToString(tclArgument)); } return buffer.toString(); } else if (argument instanceof VariableReference) { return ((VariableReference) argument).getName(); } else if (argument instanceof Substitution) { return Messages.TclArgumentMatcher_Tcl_Substitution_Display; } return null; } public String typeToString(ArgumentType type) { switch (type) { case ANY: return Info.TclArgumentType_Any; case BOOLEAN: return Info.TclArgumentType_Boolean; case CMD_NAME: return Info.TclArgumentType_CmdName; case EXPRESSION: return Info.TclArgumentType_Expression; case INDEX: return Info.TclArgumentType_Index; case INTEGER: return Info.TclArgumentType_Integer; case LEVEL: return Info.TclArgumentType_Level; case NAMESPACE: return Info.TclArgumentType_Namespace; case NOT_NEGATIVE: return Info.TclArgumentType_NotNegative; case PACKAGE: return Info.TclArgumentType_Package; case SCRIPT: return Info.TclArgumentType_Script; case VAR_NAME: return Info.TclArgumentType_VarName; } return null; } }