/** * Copyright (c) 2012-2016 André Bargull * Alle Rechte vorbehalten / All Rights Reserved. Use is subject to license terms. * * <https://github.com/anba/es6draft> */ package com.github.anba.es6draft.repl; import static com.github.anba.es6draft.runtime.AbstractOperations.Get; import static com.github.anba.es6draft.runtime.AbstractOperations.HasProperty; import static com.github.anba.es6draft.runtime.AbstractOperations.ToObject; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.github.anba.es6draft.repl.console.ShellConsole; import com.github.anba.es6draft.repl.console.ShellConsole.Completion; import com.github.anba.es6draft.runtime.ExecutionContext; import com.github.anba.es6draft.runtime.Realm; import com.github.anba.es6draft.runtime.types.ScriptObject; import com.github.anba.es6draft.runtime.types.Type; /** * */ final class ShellCompleter implements ShellConsole.Completer { private static final Pattern hierarchyPattern, namePattern; static { final String space = "\\s*"; final String name = "\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*"; final String spacedName = space + name + space; final String hierarchy = "(?:" + spacedName + "\\.)*" + spacedName + "\\.?"; hierarchyPattern = Pattern.compile(space + "(" + hierarchy + ")" + space + "$"); namePattern = Pattern.compile(name); } private final Realm realm; public ShellCompleter(Realm realm) { this.realm = realm; } @Override public Optional<Completion> complete(String line, int cursor) { ExecutionContext cx = realm.defaultContext(); ScriptObject object = realm.getGlobalThis(); String leftContext = line.substring(0, cursor); if (leftContext.isEmpty()) { ArrayList<String> candidates = createCandidates(getPropertyNames(cx, object), "", ""); return Optional.of(new Completion(line, 0, cursor, candidates)); } Matcher m = hierarchyPattern.matcher(leftContext); if (!m.find()) { return Optional.empty(); } ArrayList<String> segments = segments(m.group(1)); StringBuilder prefix = new StringBuilder(); List<String> properties = segments.subList(0, segments.size() - 1); if (!properties.isEmpty() && "this".equals(properties.get(0))) { // skip leading `this` segment in property traversal properties = properties.subList(1, properties.size()); prefix.append("this."); } for (String property : properties) { if (!HasProperty(cx, object, property)) { return Optional.empty(); } Object value = Get(cx, object, property); if (Type.isObject(value)) { object = Type.objectValue(value); } else if (!Type.isUndefinedOrNull(value)) { object = ToObject(cx, value); } else { return Optional.empty(); } prefix.append(property).append('.'); } String partial = segments.get(segments.size() - 1); ArrayList<String> candidates = createCandidates(getPropertyNames(cx, object), partial, prefix.toString()); return Optional.of(new Completion(line, m.start(1), cursor, candidates)); } private ArrayList<String> createCandidates(Iterable<String> names, String partial, String prefix) { ArrayList<String> candidates = new ArrayList<>(); for (String name : names) { if (name.startsWith(partial) && namePattern.matcher(name).matches()) { candidates.add(prefix + name); } } return candidates; } private LinkedHashSet<String> getPropertyNames(ExecutionContext cx, ScriptObject object) { LinkedHashSet<String> names = new LinkedHashSet<>(); for (; object != null; object = object.getPrototypeOf(cx)) { for (Object key : object.ownPropertyKeys(cx)) { if (key instanceof String) { names.add((String) key); } } } return names; } private ArrayList<String> segments(String hierarchy) { ArrayList<String> segments = new ArrayList<>(); Matcher m = namePattern.matcher(hierarchy); while (m.find()) { segments.add(m.group()); } if (hierarchy.charAt(hierarchy.length() - 1) == '.') { // add empty segment for trailing dot segments.add(""); } assert !segments.isEmpty(); return segments; } }