Skip to content

Commit

Permalink
Showing 21 changed files with 892 additions and 3 deletions.
76 changes: 76 additions & 0 deletions lib/ruby/truffle/truffle/readline.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Copyright (c) 2016 Oracle and/or its affiliates. All rights reserved. This
# code is released under a tri EPL/GPL/LGPL license. You can use it,
# redistribute it and/or modify it under the terms of the:
#
# Eclipse Public License version 1.0
# GNU General Public License version 2
# GNU Lesser General Public License version 2.1

Readline = Truffle::Readline

module Readline

HISTORY = Object.new
VERSION = 'JLine wrapper'

module_function

%i[
basic_quote_characters
basic_quote_characters=
completer_quote_characters
completer_quote_characters=
completer_word_break_characters
completer_word_break_characters=
completion_append_character
completion_append_character=
completion_case_fold
completion_case_fold=
completion_proc
completion_proc=
emacs_editing_mode
emacs_editing_mode?
filename_quote_characters
filename_quote_characters=
point=
pre_input_hook
pre_input_hook=
redisplay
set_screen_size
special_prefixes
special_prefixes=
vi_editing_mode
vi_editing_mode?
set_screen_size
].each do |method_name|
define_method(method_name) do
raise NotImplementedError.new("#{method_name}() function is unimplemented on this machine")
end
end

def input=(input)
# TODO (nirvdrum 20-May-16): This should do something functional.
nil
end

def output=(output)
# TODO (nirvdrum 20-May-16): This should do something functional.
nil
end

end

class << Readline::HISTORY

include Enumerable
include Truffle::ReadlineHistory

def empty?
size == 0
end

def to_s
'HISTORY'
end

end
10 changes: 7 additions & 3 deletions mx.jruby/suite.py
Original file line number Diff line number Diff line change
@@ -145,6 +145,13 @@ def mavenLib(mavenDep, sha1, sourceSha1, license):
"e2c76a19f00128bb1806207e2989139bfb45f49d",
"201985f0f15af95f03494ab9ef0400e849090d6c",
"MIT"),

"JLINE": mavenLib(
"jline:jline:2.11",
"9504d5e2da5d78237239c5226e8200ec21182040",
"ef2539b992e5605be966b6db7cfc83930f0da39b",
"BSD-simplified"
)
},

"projects": {
@@ -225,9 +232,6 @@ def mavenLib(mavenDep, sha1, sourceSha1, license):
"jruby-truffle",
"jruby-truffle-ruby",
],
"exclude": [
"truffle:JLINE",
],
"distDependencies": [
"truffle:TRUFFLE_API",
"truffle:TRUFFLE_DEBUG",
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
fails:Readline.basic_quote_characters returns not nil
fails:Readline.basic_quote_characters= returns the passed string
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
fails:Readline.completer_quote_characters returns nil
fails:Readline.completer_quote_characters= returns the passed string
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
fails:Readline.completer_word_break_characters returns nil
fails:Readline.completer_word_break_characters= returns the passed string
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
fails:Readline.completion_append_character returns not nil
fails:Readline.completion_append_character= returns the first character of the passed string
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
fails:Readline.completion_case_fold returns nil
fails:Readline.completion_case_fold= returns the passed boolean
3 changes: 3 additions & 0 deletions spec/truffle/tags/library/readline/completion_proc_tags.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fails:Readline.completion_proc returns nil
fails:Readline.completion_proc= returns the passed Proc
fails:Readline.completion_proc= returns an ArgumentError if not given an Proc or #call
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fails:Readline.emacs_editing_mode returns nil
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
fails:Readline.filename_quote_characters returns nil
fails:Readline.filename_quote_characters= returns the passed string
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fails:Readline.vi_editing_mode returns nil
1 change: 1 addition & 0 deletions spec/truffle/truffle.mspec
Original file line number Diff line number Diff line change
@@ -159,6 +159,7 @@ class MSpecScript
MSpec.disable_feature :continuation_library
MSpec.disable_feature :fork
MSpec.enable_feature :encoding
MSpec.enable_feature :readline

set :files, get(:language) + get(:core) + get(:library) + get(:truffle)
end
8 changes: 8 additions & 0 deletions truffle/src/main/java/org/jruby/truffle/RubyContext.java
Original file line number Diff line number Diff line change
@@ -49,6 +49,7 @@
import org.jruby.truffle.platform.NativePlatform;
import org.jruby.truffle.platform.NativePlatformFactory;
import org.jruby.truffle.stdlib.CoverageManager;
import org.jruby.truffle.stdlib.readline.ConsoleHolder;
import org.jruby.truffle.tools.InstrumentationServerManager;
import org.jruby.truffle.tools.callgraph.CallGraph;
import org.jruby.truffle.tools.callgraph.SimpleWriter;
@@ -103,6 +104,7 @@ public class RubyContext extends ExecutionContext {
private final CallGraph callGraph;
private final PrintStream debugStandardOut;
private final CoverageManager coverageManager;
private final ConsoleHolder consoleHolder;

private final Object classVariableDefinitionLock = new Object();

@@ -201,6 +203,8 @@ public RubyContext(TruffleLanguage.Env env) {

coreLibrary.initializePostBoot();

consoleHolder = new ConsoleHolder();

// Share once everything is loaded
if (options.SHARED_OBJECTS_ENABLED && options.SHARED_OBJECTS_FORCE) {
sharedObjects.startSharing();
@@ -481,4 +485,8 @@ public void setSyntaxCheckInputStream(InputStream syntaxCheckInputStream) {
this.syntaxCheckInputStream = syntaxCheckInputStream;
}

public ConsoleHolder getConsoleHolder() {
return consoleHolder;
}

}
7 changes: 7 additions & 0 deletions truffle/src/main/java/org/jruby/truffle/core/CoreLibrary.java
Original file line number Diff line number Diff line change
@@ -136,6 +136,8 @@
import org.jruby.truffle.stdlib.psych.PsychParserNodesFactory;
import org.jruby.truffle.stdlib.psych.YAMLEncoding;
import org.jruby.truffle.platform.Platform;
import org.jruby.truffle.stdlib.readline.ReadlineHistoryNodesFactory;
import org.jruby.truffle.stdlib.readline.ReadlineNodesFactory;

import java.io.File;
import java.io.IOException;
@@ -600,6 +602,8 @@ public CoreLibrary(RubyContext context) {
defineModule(truffleModule, "Process");
defineModule(truffleModule, "Binding");
defineModule(truffleModule, "POSIX");
defineModule(truffleModule, "Readline");
defineModule(truffleModule, "ReadlineHistory");
psychModule = defineModule("Psych");
psychParserClass = defineClass(psychModule, objectClass, "Parser");
final DynamicObject psychHandlerClass = defineClass(psychModule, objectClass, "Handler");
@@ -744,6 +748,8 @@ public void addCoreMethods(PrimitiveManager primitiveManager) {
QueueNodesFactory.getFactories(),
RandomizerPrimitiveNodesFactory.getFactories(),
RangeNodesFactory.getFactories(),
ReadlineNodesFactory.getFactories(),
ReadlineHistoryNodesFactory.getFactories(),
RegexpNodesFactory.getFactories(),
RubiniusTypeNodesFactory.getFactories(),
SizedQueueNodesFactory.getFactories(),
@@ -1099,6 +1105,7 @@ public void initializePostBoot() {
throw new TruffleFatalException("couldn't load the post-boot code", e);
}
}

}

private void initializeEncodings() {
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright (c) 2016 Oracle and/or its affiliates. All rights reserved. This
* code is released under a tri EPL/GPL/LGPL license. You can use it,
* redistribute it and/or modify it under the terms of the:
*
* Eclipse Public License version 1.0
* GNU General Public License version 2
* GNU Lesser General Public License version 2.1
*/
package org.jruby.truffle.core.cast;

import com.oracle.truffle.api.dsl.NodeChild;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.source.SourceSection;
import org.jruby.truffle.RubyContext;
import org.jruby.truffle.language.NotProvided;
import org.jruby.truffle.language.RubyNode;

/**
* Take a Symbol or some object accepting #to_str
* and convert it to a Java String and defaults to
* the given value if not provided.
*/
@NodeChild(value = "value", type = RubyNode.class)
public abstract class NameToJavaStringWithDefaultNode extends RubyNode {

private final String defaultValue;
@Child private NameToJavaStringNode toJavaStringNode;

public NameToJavaStringWithDefaultNode(RubyContext context, SourceSection sourceSection, String defaultValue) {
super(context, sourceSection);
this.defaultValue = defaultValue;
toJavaStringNode = NameToJavaStringNodeGen.create(null);
}

public abstract String executeString(VirtualFrame frame, Object value);

@Specialization
public String doDefault(VirtualFrame frame, NotProvided value) {
return toJavaStringNode.executeToJavaString(frame, defaultValue);
}

@Specialization(guards = "wasProvided(value)")
public String doProvided(VirtualFrame frame, Object value) {
return toJavaStringNode.executeToJavaString(frame, value);
}


}
Original file line number Diff line number Diff line change
@@ -280,6 +280,11 @@ public DynamicObject negativeLengthError(int length, Node currentNode) {
return indexError(StringUtils.format("negative length (%d)", length), currentNode);
}

@TruffleBoundary
public DynamicObject indexErrorInvalidIndex(Node currentNode) {
return indexError("invalid index", currentNode);
}

// LocalJumpError

@TruffleBoundary
Original file line number Diff line number Diff line change
@@ -20,6 +20,7 @@ public class CoreStrings {
public final CoreString CANT_COMPRESS_NEGATIVE;
public final CoreString CLASS;
public final CoreString CLASS_VARIABLE;
public final CoreString EMPTY_STRING;
public final CoreString EXPRESSION;
public final CoreString FALSE;
public final CoreString GLOBAL_VARIABLE;
@@ -50,6 +51,7 @@ public CoreStrings(RubyContext context) {
CANT_COMPRESS_NEGATIVE = new CoreString(context, "can't compress negative numbers");
CLASS = new CoreString(context, "class");
CLASS_VARIABLE = new CoreString(context, "class variable");
EMPTY_STRING = new CoreString(context, "");
EXPRESSION = new CoreString(context, "expression");
FALSE = new CoreString(context, "false");
GLOBAL_VARIABLE = new CoreString(context, "global-variable");
Original file line number Diff line number Diff line change
@@ -20,6 +20,7 @@
import com.oracle.truffle.api.source.SourceSection;
import jnr.ffi.provider.MemoryManager;
import org.jcodings.Encoding;
import org.jcodings.specific.UTF8Encoding;
import org.jruby.truffle.Layouts;
import org.jruby.truffle.RubyContext;
import org.jruby.truffle.core.CoreLibrary;
@@ -114,6 +115,10 @@ protected DynamicObject getSymbol(Rope name) {
return getContext().getSymbolTable().getSymbol(name);
}

protected Encoding getDefaultInternalEncoding() {
return getContext().getEncodingManager().getDefaultInternalEncoding();
}

protected DynamicObject createString(ByteList bytes) {
return StringOperations.createString(getContext(), bytes);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Copyright (c) 2016 Oracle and/or its affiliates. All rights reserved. This
* code is released under a tri EPL/GPL/LGPL license. You can use it,
* redistribute it and/or modify it under the terms of the:
*
* Eclipse Public License version 1.0
* GNU General Public License version 2
* GNU Lesser General Public License version 2.1
*
* This code is modified from the Readline JRuby extension module
* implementation with the following header:
*
* Version: EPL 1.0/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Eclipse Public
* License Version 1.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.eclipse.org/legal/epl-v10.html
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* Copyright (C) 2006 Ola Bini <ola@ologix.com>
* Copyright (C) 2006 Damian Steer <pldms@mac.com>
* Copyright (C) 2008 Joseph LaFata <joe@quibb.org>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the EPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the EPL, the GPL or the LGPL.
*/
package org.jruby.truffle.stdlib.readline;

import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import jline.console.ConsoleReader;
import jline.console.completer.Completer;
import jline.console.history.History;
import jline.console.history.MemoryHistory;

import java.io.IOException;

public class ConsoleHolder {

private final ConsoleReader readline;
private final Completer currentCompleter;
private final History history;

@TruffleBoundary
public ConsoleHolder() {
try {
readline = new ConsoleReader();
} catch (IOException e) {
throw new UnsupportedOperationException("Couldn't initialize readline", e);
}

readline.setHistoryEnabled(false);
readline.setPaginationEnabled(true);
readline.setBellEnabled(true);

currentCompleter = new ReadlineNodes.RubyFileNameCompleter();
readline.addCompleter(currentCompleter);

history = new MemoryHistory();
readline.setHistory(history);
}

public ConsoleReader getReadline() {
return readline;
}

public Completer getCurrentCompleter() {
return currentCompleter;
}

public History getHistory() {
return history;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,296 @@
/*
* Copyright (c) 2016 Oracle and/or its affiliates. All rights reserved. This
* code is released under a tri EPL/GPL/LGPL license. You can use it,
* redistribute it and/or modify it under the terms of the:
*
* Eclipse Public License version 1.0
* GNU General Public License version 2
* GNU Lesser General Public License version 2.1
*
* This code is modified from the Readline JRuby extension module
* implementation with the following header:
*
* Version: EPL 1.0/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Eclipse Public
* License Version 1.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.eclipse.org/legal/epl-v10.html
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* Copyright (C) 2006 Ola Bini <ola@ologix.com>
* Copyright (C) 2006 Damian Steer <pldms@mac.com>
* Copyright (C) 2008 Joseph LaFata <joe@quibb.org>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the EPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the EPL, the GPL or the LGPL.
*/
package org.jruby.truffle.stdlib.readline;

import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.CreateCast;
import com.oracle.truffle.api.dsl.NodeChild;
import com.oracle.truffle.api.dsl.NodeChildren;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.source.SourceSection;
import jline.console.history.History;
import org.jruby.truffle.RubyContext;
import org.jruby.truffle.builtins.CoreClass;
import org.jruby.truffle.builtins.CoreMethod;
import org.jruby.truffle.builtins.CoreMethodArrayArgumentsNode;
import org.jruby.truffle.builtins.CoreMethodNode;
import org.jruby.truffle.builtins.YieldingCoreMethodNode;
import org.jruby.truffle.core.cast.NameToJavaStringNode;
import org.jruby.truffle.core.cast.NameToJavaStringNodeGen;
import org.jruby.truffle.core.cast.ToIntNodeGen;
import org.jruby.truffle.core.string.StringOperations;
import org.jruby.truffle.language.RubyNode;
import org.jruby.truffle.language.control.RaiseException;
import org.jruby.truffle.language.objects.TaintNode;
import org.jruby.truffle.language.objects.TaintNodeGen;

@CoreClass("Truffle::ReadlineHistory")
public abstract class ReadlineHistoryNodes {

@CoreMethod(names = { "push", "<<" }, rest = true)
public abstract static class PushNode extends CoreMethodArrayArgumentsNode {

@Child private NameToJavaStringNode toJavaStringNode;

public PushNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
toJavaStringNode = NameToJavaStringNodeGen.create();
}

@Specialization
public DynamicObject push(VirtualFrame frame, DynamicObject history, Object... lines) {
for (Object line : lines) {
final String asString = toJavaStringNode.executeToJavaString(frame, line);
addToHistory(asString);
}

return history;
}

@TruffleBoundary
private void addToHistory(String item) {
getContext().getConsoleHolder().getHistory().add(item);
}

}

@CoreMethod(names = "pop", needsSelf = false)
public abstract static class PopNode extends CoreMethodArrayArgumentsNode {

@Child private TaintNode taintNode;

public PopNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
taintNode = TaintNodeGen.create(context, sourceSection, null);
}

@TruffleBoundary
@Specialization
public Object pop() {
final ConsoleHolder consoleHolder = getContext().getConsoleHolder();

if (consoleHolder.getHistory().isEmpty()) {
return nil();
}

final String lastLine = consoleHolder.getHistory().removeLast().toString();
final DynamicObject ret = createString(StringOperations.encodeRope(lastLine, getDefaultInternalEncoding()));

return taintNode.executeTaint(ret);
}

}

@CoreMethod(names = "shift", needsSelf = false)
public abstract static class ShiftNode extends CoreMethodArrayArgumentsNode {

@Child private TaintNode taintNode;

public ShiftNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
taintNode = TaintNodeGen.create(context, sourceSection, null);
}

@TruffleBoundary
@Specialization
public Object shift() {
final ConsoleHolder consoleHolder = getContext().getConsoleHolder();

if (consoleHolder.getHistory().isEmpty()) {
return nil();
}

final String lastLine = consoleHolder.getHistory().removeFirst().toString();
final DynamicObject ret = createString(StringOperations.encodeRope(lastLine, getDefaultInternalEncoding()));

return taintNode.executeTaint(ret);
}

}

@CoreMethod(names = { "length", "size" }, needsSelf = false)
public abstract static class LengthNode extends CoreMethodArrayArgumentsNode {

@TruffleBoundary
@Specialization
public int length() {
return getContext().getConsoleHolder().getHistory().size();
}

}

@CoreMethod(names = "clear", needsSelf = false)
public abstract static class ClearNode extends CoreMethodArrayArgumentsNode {

@TruffleBoundary
@Specialization
public DynamicObject clear() {
getContext().getConsoleHolder().getHistory().clear();

return nil();
}

}

@CoreMethod(names = "each", needsBlock = true)
public abstract static class EachNode extends YieldingCoreMethodNode {

@Child private TaintNode taintNode;

public EachNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
taintNode = TaintNodeGen.create(context, sourceSection, null);
}

@Specialization
public DynamicObject each(VirtualFrame frame, DynamicObject history, DynamicObject block) {
final ConsoleHolder consoleHolder = getContext().getConsoleHolder();

for (final History.Entry e : consoleHolder.getHistory()) {
final DynamicObject line = createString(StringOperations.encodeRope(historyEntryToString(e), getDefaultInternalEncoding()));

yield(frame, block, taintNode.executeTaint(line));
}

return history;
}

@TruffleBoundary
private String historyEntryToString(History.Entry entry) {
return entry.value().toString();
}

}

@CoreMethod(names = "[]", needsSelf = false, required = 1, lowerFixnum = 1)
public abstract static class GetIndexNode extends CoreMethodArrayArgumentsNode {

@Child private TaintNode taintNode;

public GetIndexNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
taintNode = TaintNodeGen.create(context, sourceSection, null);
}

@TruffleBoundary
@Specialization
public Object getIndex(int index) {
final ConsoleHolder consoleHolder = getContext().getConsoleHolder();

final int normalizedIndex = index < 0 ? index + consoleHolder.getHistory().size() : index;

try {
final String line = consoleHolder.getHistory().get(normalizedIndex).toString();
final DynamicObject ret = createString(StringOperations.encodeRope(line, getDefaultInternalEncoding()));

return taintNode.executeTaint(ret);
} catch (IndexOutOfBoundsException e) {
throw new RaiseException(coreExceptions().indexErrorInvalidIndex(this));
}
}

}

@CoreMethod(names = "[]=", needsSelf = false, required = 2)
@NodeChildren({
@NodeChild(type = RubyNode.class, value = "index"),
@NodeChild(type = RubyNode.class, value = "line")
})
public abstract static class SetIndexNode extends CoreMethodNode {

@CreateCast("index") public RubyNode coerceIndexToInt(RubyNode index) {
return ToIntNodeGen.create(index);
}

@CreateCast("line") public RubyNode coerceLineToJavaString(RubyNode line) {
return NameToJavaStringNodeGen.create(line);
}

@TruffleBoundary
@Specialization
public Object setIndex(int index, String line) {
final ConsoleHolder consoleHolder = getContext().getConsoleHolder();

final int normalizedIndex = index < 0 ? index + consoleHolder.getHistory().size() : index;

try {
consoleHolder.getHistory().set(normalizedIndex, line);

return nil();
} catch (IndexOutOfBoundsException e) {
throw new RaiseException(coreExceptions().indexErrorInvalidIndex(this));
}
}

}

@CoreMethod(names = "delete_at", needsSelf = false, required = 1, lowerFixnum = 1)
public abstract static class DeleteAtNode extends CoreMethodArrayArgumentsNode {

@Child private TaintNode taintNode;

public DeleteAtNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
taintNode = TaintNodeGen.create(context, sourceSection, null);
}

@TruffleBoundary
@Specialization
public Object deleteAt(int index) {
final ConsoleHolder consoleHolder = getContext().getConsoleHolder();

final int normalizedIndex = index < 0 ? index + consoleHolder.getHistory().size() : index;

try {
final String line = consoleHolder.getHistory().remove(normalizedIndex).toString();
final DynamicObject ret = createString(StringOperations.encodeRope(line, getDefaultInternalEncoding()));

return taintNode.executeTaint(ret);
} catch (IndexOutOfBoundsException e) {
throw new RaiseException(coreExceptions().indexErrorInvalidIndex(this));
}
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,330 @@
/*
* Copyright (c) 2016 Oracle and/or its affiliates. All rights reserved. This
* code is released under a tri EPL/GPL/LGPL license. You can use it,
* redistribute it and/or modify it under the terms of the:
*
* Eclipse Public License version 1.0
* GNU General Public License version 2
* GNU Lesser General Public License version 2.1
*
* This code is modified from the Readline JRuby extension module
* implementation with the following header:
*
* Version: EPL 1.0/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Eclipse Public
* License Version 1.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.eclipse.org/legal/epl-v10.html
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* Copyright (C) 2006 Ola Bini <ola@ologix.com>
* Copyright (C) 2006 Damian Steer <pldms@mac.com>
* Copyright (C) 2008 Joseph LaFata <joe@quibb.org>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the EPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the EPL, the GPL or the LGPL.
*/
package org.jruby.truffle.stdlib.readline;

import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.CreateCast;
import com.oracle.truffle.api.dsl.NodeChild;
import com.oracle.truffle.api.dsl.NodeChildren;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.source.SourceSection;
import jline.console.CursorBuffer;
import jline.console.completer.Completer;
import jline.console.completer.FileNameCompleter;
import org.jcodings.specific.UTF8Encoding;
import org.jruby.truffle.RubyContext;
import org.jruby.truffle.builtins.CoreClass;
import org.jruby.truffle.builtins.CoreMethod;
import org.jruby.truffle.builtins.CoreMethodArrayArgumentsNode;
import org.jruby.truffle.builtins.CoreMethodNode;
import org.jruby.truffle.core.array.ArrayHelpers;
import org.jruby.truffle.core.cast.BooleanCastWithDefaultNodeGen;
import org.jruby.truffle.core.cast.NameToJavaStringNodeGen;
import org.jruby.truffle.core.cast.NameToJavaStringWithDefaultNodeGen;
import org.jruby.truffle.core.cast.ToStrNodeGen;
import org.jruby.truffle.core.rope.RopeOperations;
import org.jruby.truffle.core.string.StringOperations;
import org.jruby.truffle.language.RubyNode;
import org.jruby.truffle.language.control.RaiseException;
import org.jruby.truffle.language.objects.TaintNode;
import org.jruby.truffle.language.objects.TaintNodeGen;

import java.io.IOException;
import java.nio.CharBuffer;
import java.util.ArrayList;
import java.util.List;

@CoreClass("Truffle::Readline")
public abstract class ReadlineNodes {

@CoreMethod(names = "basic_word_break_characters", onSingleton = true)
public abstract static class BasicWordBreakCharactersNode extends CoreMethodArrayArgumentsNode {

@Specialization
public DynamicObject basicWordBreakCharacters() {
return createString(StringOperations.encodeRope(ProcCompleter.getDelimiter(), UTF8Encoding.INSTANCE));
}

}

@CoreMethod(names = "basic_word_break_characters=", onSingleton = true, required = 1)
@NodeChild(type = RubyNode.class, value = "characters")
public abstract static class SetBasicWordBreakCharactersNode extends CoreMethodNode {

@CreateCast("characters") public RubyNode coerceCharactersToString(RubyNode characters) {
return ToStrNodeGen.create(null, null, characters);
}

@TruffleBoundary
@Specialization
public DynamicObject setBasicWordBreakCharacters(DynamicObject characters) {
ProcCompleter.setDelimiter(RopeOperations.decodeUTF8(StringOperations.rope(characters)));

return characters;
}

}

@CoreMethod(names = "get_screen_size", onSingleton = true)
public abstract static class GetScreenSizeNode extends CoreMethodArrayArgumentsNode {

@TruffleBoundary
@Specialization
public DynamicObject getScreenSize() {
final int[] store = {
getContext().getConsoleHolder().getReadline().getTerminal().getHeight(),
getContext().getConsoleHolder().getReadline().getTerminal().getWidth()
};

return ArrayHelpers.createArray(getContext(), store, 2);
}

}

@CoreMethod(names = "readline", isModuleFunction = true, optional = 2)
@NodeChildren({
@NodeChild(type = RubyNode.class, value = "prompt"),
@NodeChild(type = RubyNode.class, value = "addToHistory")
})
public abstract static class ReadlineNode extends CoreMethodNode {

@Child private TaintNode taintNode;

public ReadlineNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
taintNode = TaintNodeGen.create(context, sourceSection, null);
}

@CreateCast("prompt") public RubyNode coercePromptToJavaString(RubyNode prompt) {
return NameToJavaStringWithDefaultNodeGen.create(null, null, coreStrings().EMPTY_STRING.toString(), prompt);
}

@CreateCast("addToHistory") public RubyNode coerceToBoolean(RubyNode addToHistory) {
return BooleanCastWithDefaultNodeGen.create(false, addToHistory);
}

@TruffleBoundary
@Specialization
public Object readline(String prompt, boolean addToHistory) {
getContext().getConsoleHolder().getReadline().setExpandEvents(false);

DynamicObject line = nil();
String value = null;
while (true) {
try {
getContext().getConsoleHolder().getReadline().getTerminal().setEchoEnabled(false);
value = getContext().getConsoleHolder().getReadline().readLine(prompt);
break;
} catch (IOException e) {
throw new RaiseException(coreExceptions().ioError(e.getMessage(), this));
} finally {
getContext().getConsoleHolder().getReadline().getTerminal().setEchoEnabled(true);
}
}

if (value != null) {
if (addToHistory) {
getContext().getConsoleHolder().getReadline().getHistory().add(value);
}

// Enebo: This is a little weird and a little broken. We just ask
// for the bytes and hope they match default_external. This will
// work for common cases, but not ones in which the user explicitly
// sets the default_external to something else. The second problem
// is that no al M17n encodings are valid encodings in java.lang.String.
// We clearly need a byte[]-version of JLine since we cannot totally
// behave properly using Java Strings.
line = createString(StringOperations.encodeRope(value, getContext().getEncodingManager().getDefaultExternalEncoding()));
}

return taintNode.executeTaint(line);
}

}

@CoreMethod(names = "point", onSingleton = true)
public abstract static class PointNode extends CoreMethodArrayArgumentsNode {

@TruffleBoundary
@Specialization
public int point() {
return getContext().getConsoleHolder().getReadline().getCursorBuffer().cursor;
}

}

@CoreMethod(names = "insert_text", constructor = true, required = 1)
@NodeChildren({
@NodeChild(type = RubyNode.class, value = "self"),
@NodeChild(type = RubyNode.class, value = "text")
})
public abstract static class InsertTextNode extends CoreMethodNode {

@CreateCast("text") public RubyNode coerceTextToString(RubyNode text) {
return NameToJavaStringNodeGen.create(text);
}

@TruffleBoundary
@Specialization
public DynamicObject insertText(DynamicObject readline, String text) {
getContext().getConsoleHolder().getReadline().getCursorBuffer().write(text);

return readline;
}

}

@CoreMethod(names = "delete_text", constructor = true)
public abstract static class DeleteTextNode extends CoreMethodArrayArgumentsNode {

@TruffleBoundary
@Specialization
public DynamicObject deleteText(DynamicObject readline) {
getContext().getConsoleHolder().getReadline().getCursorBuffer().clear();

return readline;
}

}

@CoreMethod(names = "line_buffer", onSingleton = true)
public abstract static class LineBufferNode extends CoreMethodArrayArgumentsNode {

@Child private TaintNode taintNode;

public LineBufferNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
taintNode = TaintNodeGen.create(context, sourceSection, null);
}

@TruffleBoundary
@Specialization
public Object lineBuffer() {
final CursorBuffer cb = getContext().getConsoleHolder().getReadline().getCursorBuffer();

final DynamicObject ret = createString(StringOperations.encodeRope(cb.toString(), getDefaultInternalEncoding()));
return taintNode.executeTaint(ret);
}

}

@CoreMethod(names = "refresh_line", onSingleton = true)
public abstract static class RefreshLineNode extends CoreMethodArrayArgumentsNode {

@TruffleBoundary
@Specialization
public DynamicObject refreshLine() {
try {
getContext().getConsoleHolder().getReadline().redrawLine();
} catch (IOException e) {
throw new RaiseException(coreExceptions().ioError(e.getMessage(), this));
}

return nil();
}

}

// Taken from org.jruby.ext.readline.Readline.ProcCompleter.
// Complete using a Proc object
public static class ProcCompleter implements Completer {

private final DynamicObject procCompleter;

//\t\n\"\\'`@$><=;|&{(
static private String[] delimiters = {" ", "\t", "\n", "\"", "\\", "'", "`", "@", "$", ">", "<", "=", ";", "|", "&", "{", "("};

public ProcCompleter(DynamicObject procCompleter) {
this.procCompleter = procCompleter;
}

@TruffleBoundary
public static String getDelimiter() {
StringBuilder result = new StringBuilder(delimiters.length);
for (String delimiter : delimiters) {
result.append(delimiter);
}
return result.toString();
}

@TruffleBoundary
public static void setDelimiter(String delimiter) {
List<String> l = new ArrayList<String>();
CharBuffer buf = CharBuffer.wrap(delimiter);
while (buf.hasRemaining()) {
l.add(String.valueOf(buf.get()));
}
delimiters = l.toArray(new String[l.size()]);
}

@TruffleBoundary
private int wordIndexOf(String buffer) {
int index = 0;
for (String c : delimiters) {
index = buffer.lastIndexOf(c);
if (index != -1) return index;
}
return index;
}

public int complete(String buffer, int cursor, List candidates) {
throw new UnsupportedOperationException("auto-completion via proc not yet supported");
}
}

// Taken from org.jruby.ext.readline.Readline.RubyFileNameCompleter.
// Fix FileNameCompletor to work mid-line
public static class RubyFileNameCompleter extends FileNameCompleter {

@TruffleBoundary
@Override
public int complete(String buffer, int cursor, List candidates) {
buffer = buffer.substring(0, cursor);
int index = buffer.lastIndexOf(" ");
if (index != -1) {
buffer = buffer.substring(index + 1);
}
return index + 1 + super.complete(buffer, cursor, candidates);
}

}
}

0 comments on commit 11b8b9a

Please sign in to comment.