Skip to content

Commit

Permalink
Showing 29 changed files with 930 additions and 156 deletions.
12 changes: 2 additions & 10 deletions ci.hocon
Original file line number Diff line number Diff line change
@@ -6,19 +6,11 @@ common: {
maven: ">=3.3.9"
mercurial: ">=3.2.4"
ruby: ">=2.1.0"
}

downloads: {
JAVA_HOME: {
name: oraclejdk,
version: "8u66",
platformspecific: true
}
"java/jdk": "==1.8.0/60"
}

environment: {
CI: "true",
PATH: "$JAVA_HOME/bin:$PATH"
CI: "true"
}

setup: [
6 changes: 3 additions & 3 deletions core/src/main/java/org/jruby/ext/socket/RubyBasicSocket.java
Original file line number Diff line number Diff line change
@@ -216,11 +216,11 @@ public IRubyObject recv_nonblock(ThreadContext context, IRubyObject[] args) {

switch (argc) {
case 3:
str = args[3];
str = args[2];
case 2:
flags = args[2];
flags = args[1];
case 1:
length = args[1];
length = args[0];
}

boolean exception = ArgsUtil.extractKeywordArg(context, "exception", opts) != runtime.getFalse();
6 changes: 3 additions & 3 deletions core/src/main/java/org/jruby/ext/socket/RubyUDPSocket.java
Original file line number Diff line number Diff line change
@@ -218,11 +218,11 @@ public IRubyObject recvfrom_nonblock(ThreadContext context, IRubyObject[] args)

switch (argc) {
case 3:
str = args[3];
str = args[2];
case 2:
flags = args[2];
flags = args[1];
case 1:
length = args[1];
length = args[0];
}

boolean exception = ArgsUtil.extractKeywordArg(context, "exception", opts) != runtime.getFalse();
Original file line number Diff line number Diff line change
@@ -44,8 +44,7 @@
exclude_tests.rb: |
failures = { KernelTest: [:test_silence_stream,
:test_quietly],
InflectorTest: [:test_titleize_mixture_to_title_case_13,
:test_titleize_mixture_to_title_case_14],
InflectorTest: [:test_titleize_mixture_to_title_case_14],
LoadPathsTest: [:test_uniq_load_paths],
LoggerTest: [:test_buffer_multibyte],
MultibyteCharsExtrasTest: [:test_titleize_should_be_unicode_aware,
@@ -55,8 +54,7 @@
:test_transliterate_should_work_with_custom_i18n_rules_and_uncomposed_utf8],
StringInflectionsTest: [:test_string_parameterized_no_separator,
:test_string_parameterized_normal,
:test_string_parameterized_underscore,
:test_titleize],
:test_string_parameterized_underscore],
TimeZoneTest: :test_map_srednekolymsk_to_tzinfo }
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
9 changes: 9 additions & 0 deletions spec/ruby/core/string/gsub_spec.rb
Original file line number Diff line number Diff line change
@@ -43,6 +43,9 @@

str = "hello homely world. hah!"
str.gsub(/\Ah\S+\s*/, "huh? ").should == "huh? homely world. hah!"

str = "¿por qué?"
str.gsub(/([a-z\d]*)/, "*").should == "*¿** **é*?*"
end

it "ignores a block if supplied" do
@@ -583,6 +586,12 @@ def obj.to_s() "ok" end
a.should == "h*ll*"
end

it "modifies self in place with multi-byte characters and returns self" do
a = "¿por qué?"
a.gsub!(/([a-z\d]*)/, "*").should equal(a)
a.should == "*¿** **é*?*"
end

it "taints self if replacement is tainted" do
a = "hello"
a.gsub!(/./.taint, "foo").tainted?.should == false
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
@@ -124,6 +124,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
146 changes: 32 additions & 114 deletions tool/jt.rb
Original file line number Diff line number Diff line change
@@ -133,6 +133,14 @@ def self.find_jruby_bin_dir
end
end

def self.find_gem(name)
["#{JRUBY_DIR}/lib/ruby/gems/shared/gems", JRUBY_DIR, "#{JRUBY_DIR}/.."].each do |dir|
found = Dir.glob("#{dir}/#{name}*").first
return found if found
end
raise "Can't find the #{name} gem - gem install it in this repository, or put it in the repository directory or its parent"
end

def self.git_branch
@git_branch ||= `GIT_DIR="#{JRUBY_DIR}/.git" git rev-parse --abbrev-ref HEAD`.strip
end
@@ -187,22 +195,6 @@ def self.ensure_igv_running
end
end

def self.find_bench
bench_locations = [
ENV['BENCH_DIR'],
'bench9000',
'../bench9000'
].compact.map { |path| File.expand_path(path, JRUBY_DIR) }

not_found = -> {
raise "couldn't find bench9000 - clone it from https://github.com/jruby/bench9000.git into the JRuby repository or parent directory"
}

bench_locations.find(not_found) do |location|
Dir.exist?(location)
end
end

def self.jruby_version
File.read("#{JRUBY_DIR}/VERSION").strip
end
@@ -309,7 +301,6 @@ def help
puts ' --server run an instrumentation server on port 8080'
puts ' --igv make sure IGV is running and dump Graal graphs after partial escape (implies --graal)'
puts ' --full show all phases, not just up to the Truffle partial escape'
puts ' --bips run with benchmark-ips on the load path (implies --graal)'
puts ' --jdebug run a JDWP debug server on #{JDEBUG_PORT}'
puts ' --jexception[s] print java exceptions'
puts 'jt e 14 + 2 evaluate an expression'
@@ -331,19 +322,14 @@ def help
puts 'jt tag all spec/ruby/language tag all specs in this file, without running them'
puts 'jt untag spec/ruby/language untag passing specs in this directory'
puts 'jt untag spec/ruby/language/while_spec.rb untag passing specs in this file'
puts 'jt bench debug [options] [vm-args] benchmark run a single benchmark with options for compiler debugging'
puts ' --igv make sure IGV is running and dump Graal graphs after partial escape (implies --graal)'
puts ' --full show all phases, not just up to the Truffle partial escape'
puts ' --ruby-backtrace print a Ruby backtrace on any compilation failures'
puts 'jt bench reference [benchmarks] run a set of benchmarks and record a reference point'
puts 'jt bench compare [benchmarks] run a set of benchmarks and compare against a reference point'
puts ' benchmarks can be any benchmarks or group of benchmarks supported'
puts ' by bench9000, eg all, classic, chunky, 3, 5, 10, 15 - default is 5'
puts 'jt metrics [--score name] alloc ... how much memory is allocated running a program (use -X-T to test normal JRuby on this metric and others)'
puts ' --score name report results as scores'
puts 'jt metrics ... minheap ... what is the smallest heap you can use to run an application'
puts 'jt metrics ... time ... how long does it take to run a command, broken down into different phases'
puts 'jt metrics alloc ... how much memory is allocated running a program (use -X-T to test normal JRuby on this metric and others)'
puts 'jt metrics minheap ... what is the smallest heap you can use to run an application'
puts 'jt metrics time ... how long does it take to run a command, broken down into different phases'
puts 'jt tarball build the and test the distribution tarball'
puts 'jt benchmark args... run benchmark-interface (implies --graal)'
puts ' note that to run most MRI benchmarks, you should translate them first with normal Ruby and cache the result, such as'
puts ' benchmark bench/mri/bm_vm1_not.rb --cache'
puts ' jt benchmark bench/mri/bm_vm1_not.rb --use-cache'
puts
puts 'you can also put build or rebuild in front of any command'
puts
@@ -406,8 +392,7 @@ def run(*args)

{
'--asm' => '--graal',
'--igv' => '--graal',
'--bips' => '--graal'
'--igv' => '--graal'
}.each_pair do |arg, dep|
args.unshift dep if args.include?(arg)
end
@@ -430,13 +415,6 @@ def run(*args)
jruby_args += %w[-J-XX:+UnlockDiagnosticVMOptions -J-XX:CompileCommand=print,*::callRoot]
end

if args.delete('--bips')
bips_version = '2.6.1'
bips = "#{JRUBY_DIR}/lib/ruby/gems/shared/gems/benchmark-ips-#{bips_version}/lib"
sh 'bin/jruby', 'bin/gem', 'install', 'benchmark-ips', '-v', bips_version unless Dir.exist?(bips)
jruby_args << "-I#{bips}"
end

if args.delete('--jdebug')
jruby_args << JDEBUG
end
@@ -691,77 +669,22 @@ def untag(path, *args)
test_specs('untag', path, *args)
end

def bench(command, *args)
bench_dir = Utilities.find_bench
env_vars = {
"JRUBY_DEV_DIR" => JRUBY_DIR,
"GRAAL_BIN" => Utilities.find_graal,
}
bench_args = ["#{bench_dir}/bin/bench9000"]
case command
when 'debug'
vm_args = ['-G:+TraceTruffleCompilation', '-G:+DumpOnError']
if args.delete '--igv'
warn "warning: --igv might not work on master - if it does not, use truffle-head instead which builds against latest graal" if Utilities.git_branch == 'master'
Utilities.ensure_igv_running

if args.delete('--full')
vm_args.push '-G:Dump=Truffle'
else
vm_args.push '-G:Dump=TrufflePartialEscape'
end
end
if args.delete '--ruby-backtrace'
vm_args.push '-G:+TruffleCompilationExceptionsAreThrown'
else
vm_args.push '-G:+TruffleCompilationExceptionsAreFatal'
end
remaining_args = []
args.each do |arg|
if arg.start_with? '-'
vm_args.push arg
else
remaining_args.push arg
end
end
env_vars["JRUBY_OPTS"] = vm_args.map{ |a| '-J' + a }.join(' ')
bench_args += ['score', '--config', "#{bench_dir}/benchmarks/default.config.rb", 'jruby-dev-truffle-graal', '--show-commands', '--show-samples']
raise 'specify a single benchmark for run - eg classic-fannkuch-redux' if remaining_args.size != 1
args = remaining_args
when 'reference'
bench_args += ['reference', '--config', "#{bench_dir}/benchmarks/default.config.rb", 'jruby-dev-truffle-graal', '--show-commands']
args << "5" if args.empty?
when 'compare'
bench_args += ['compare-reference', '--config', "#{bench_dir}/benchmarks/default.config.rb", 'jruby-dev-truffle-graal']
args << "5" if args.empty?
else
raise ArgumentError, command
end
raw_sh env_vars, "ruby", *bench_args, *args
end

def metrics(command, *args)
trap(:INT) { puts; exit }
args = args.dup
if args.first == '--score'
args.shift
score_name = args.shift
else
score_name = nil
end
case command
when 'alloc'
metrics_alloc score_name, *args
metrics_alloc *args
when 'minheap'
metrics_minheap score_name, *args
metrics_minheap *args
when 'time'
metrics_time score_name, *args
metrics_time *args
else
raise ArgumentError, command
end
end

def metrics_alloc(score_name, *args)
def metrics_alloc(*args)
samples = []
METRICS_REPS.times do
log '.', 'sampling'
@@ -773,11 +696,7 @@ def metrics_alloc(score_name, *args)
end
log "\n", nil
mean = samples.inject(:+) / samples.size
if score_name
puts "alloc-#{score_name}: #{mean}"
else
puts "#{human_size(mean)}, max #{human_size(samples.max)}"
end
puts "#{human_size(mean)}, max #{human_size(samples.max)}"
end

def memory_allocated(trace)
@@ -796,7 +715,7 @@ def memory_allocated(trace)
allocated
end

def metrics_minheap(score_name, *args)
def metrics_minheap(*args)
heap = 10
log '>', "Trying #{heap} MB"
until can_run_in_heap(heap, *args)
@@ -821,18 +740,14 @@ def metrics_minheap(score_name, *args)
end
end
log "\n", nil
if score_name
puts "minheap-#{score_name}: #{heap*1024*1024}"
else
puts "#{heap} MB"
end
puts "#{heap} MB"
end

def can_run_in_heap(heap, *command)
run("-J-Xmx#{heap}M", *command, {err: '/dev/null', out: '/dev/null'}, :continue_on_failure, :no_print_cmd)
end

def metrics_time(score_name, *args)
def metrics_time(*args)
samples = []
METRICS_REPS.times do
log '.', 'sampling'
@@ -848,11 +763,7 @@ def metrics_time(score_name, *args)
samples[0].each_key do |region|
region_samples = samples.map { |s| s[region] }
mean = region_samples.inject(:+) / samples.size
if score_name
puts "time-#{region.strip}-#{score_name}: #{(mean*1000).round}"
else
puts "#{region} #{mean.round(2)} s"
end
puts "#{region} #{mean.round(2)} s"
end
end

@@ -915,6 +826,13 @@ def log(tty_message, full_message)
end
end

def benchmark(*args)
run '--graal',
'-I', "#{Utilities.find_gem('deep-bench')}/lib",
'-I', "#{Utilities.find_gem('benchmark-ips')}/lib",
"#{Utilities.find_gem('benchmark-interface')}/bin/benchmark", *args
end

def check_ambiguous_arguments
ENV.delete "JRUBY_ECLIPSE" # never run from the Eclipse launcher here
clean
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
@@ -47,6 +47,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;
@@ -91,6 +92,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();

@@ -174,6 +176,8 @@ public RubyContext(Ruby jrubyRuntime, TruffleLanguage.Env env) {
coverageManager = new CoverageManager(this, instrumenter);

coreLibrary.initializePostBoot();

consoleHolder = new ConsoleHolder();
}

public Object send(Object object, String methodName, DynamicObject block, Object... arguments) {
@@ -348,4 +352,8 @@ public CoreExceptions getCoreExceptions() {
return coreExceptions;
}

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
@@ -111,6 +111,8 @@
import org.jruby.truffle.stdlib.psych.PsychEmitterNodesFactory;
import org.jruby.truffle.stdlib.psych.PsychParserNodesFactory;
import org.jruby.truffle.stdlib.psych.YAMLEncoding;
import org.jruby.truffle.stdlib.readline.ReadlineHistoryNodesFactory;
import org.jruby.truffle.stdlib.readline.ReadlineNodesFactory;
import org.jruby.util.cli.OutputStrings;
import java.io.File;
import java.io.IOException;
@@ -594,6 +596,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");
@@ -746,6 +750,8 @@ public void addCoreMethods() {
coreMethodNodeManager.addCoreMethodNodes(EtcNodesFactory.getFactories());
coreMethodNodeManager.addCoreMethodNodes(PsychParserNodesFactory.getFactories());
coreMethodNodeManager.addCoreMethodNodes(PsychEmitterNodesFactory.getFactories());
coreMethodNodeManager.addCoreMethodNodes(ReadlineNodesFactory.getFactories());
coreMethodNodeManager.addCoreMethodNodes(ReadlineHistoryNodesFactory.getFactories());
coreMethodNodeManager.addCoreMethodNodes(AtomicReferenceNodesFactory.getFactories());
coreMethodNodeManager.addCoreMethodNodes(TracePointNodesFactory.getFactories());
coreMethodNodeManager.addCoreMethodNodes(CoverageNodesFactory.getFactories());
@@ -937,6 +943,7 @@ public void initializePostBoot() {
throw new TruffleFatalException("couldn't load the post-boot code", e);
}
}

}

private void initializeRubiniusFFI() {
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(context, sourceSection, 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
@@ -217,6 +217,11 @@ public DynamicObject negativeLengthError(int length, Node currentNode) {
return indexError(String.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
@@ -34,11 +34,6 @@ public static boolean isAsciiCompatible(DynamicObject string) {
return Layouts.STRING.getRope(string).getEncoding().isAsciiCompatible();
}

public static boolean isSingleByte(DynamicObject string) {
assert RubyGuards.isRubyString(string);
return Layouts.STRING.getRope(string).getEncoding().isSingleByte();
}

public static boolean isValidOr7BitEncoding(DynamicObject string) {
assert RubyGuards.isRubyString(string);
final Rope rope = StringOperations.rope(string);
Original file line number Diff line number Diff line change
@@ -3112,33 +3112,30 @@ public Object stringFindCharacterNegativeOffset(DynamicObject string, int offset
return nil();
}

@Specialization(guards = { "offset >= 0", "isSingleByte(string)" })
public Object stringFindCharacterSingleByte(DynamicObject string, int offset,
@Cached("createBinaryProfile()") ConditionProfile offsetTooLargeProfile) {
@Specialization(guards = "offsetTooLarge(string, offset)")
public Object stringFindCharacterOffsetTooLarge(DynamicObject string, int offset) {
return nil();
}

@Specialization(guards = { "offset >= 0", "!offsetTooLarge(string, offset)", "isSingleByteOptimizable(string)" })
public Object stringFindCharacterSingleByte(DynamicObject string, int offset) {
// Taken from Rubinius's String::find_character.

final Rope rope = rope(string);
if (offsetTooLargeProfile.profile(offset >= rope.byteLength())) {
return nil();
}

final DynamicObject ret = allocateObjectNode.allocate(Layouts.BASIC_OBJECT.getLogicalClass(string), makeSubstringNode.executeMake(rope, offset, 1), null);

return propagate(string, ret);
}

@Specialization(guards = { "offset >= 0", "!isSingleByte(string)" })
public Object stringFindCharacter(DynamicObject string, int offset,
@Cached("createBinaryProfile()") ConditionProfile offsetTooLargeProfile) {
@Specialization(guards = { "offset >= 0", "!offsetTooLarge(string, offset)", "!isSingleByteOptimizable(string)" })
public Object stringFindCharacter(DynamicObject string, int offset) {
// Taken from Rubinius's String::find_character.

final Rope rope = rope(string);
if (offsetTooLargeProfile.profile(offset >= rope.byteLength())) {
return nil();
}

final Encoding enc = rope.getEncoding();
final int clen = StringSupport.preciseLength(enc, rope.getBytes(), 0, rope.byteLength());
final int clen = StringSupport.preciseLength(enc, rope.getBytes(), offset, offset + enc.maxLength());

final DynamicObject ret;
if (StringSupport.MBCLEN_CHARFOUND_P(clen)) {
@@ -3164,6 +3161,12 @@ private Object maybeTaint(DynamicObject source, DynamicObject value) {
return taintResultNode.maybeTaint(source, value);
}

protected static boolean offsetTooLarge(DynamicObject string, int offset) {
assert RubyGuards.isRubyString(string);

return offset >= rope(string).byteLength();
}

}

@Primitive(name = "string_from_codepoint", needsSelf = false)
Original file line number Diff line number Diff line change
@@ -44,6 +44,12 @@
@CoreClass("Truffle::POSIX")
public abstract class TrufflePosixNodes {

private static void invalidateENV(String name) {
if (name.equals("TZ")) {
GetTimeZoneNode.invalidateTZ();
}
}

@CoreMethod(names = "access", isModuleFunction = true, required = 2, unsafe = UnsafeGroup.IO)
public abstract static class AccessNode extends CoreMethodArrayArgumentsNode {

@@ -285,9 +291,7 @@ public abstract static class SetenvNode extends CoreMethodArrayArgumentsNode {
@Specialization(guards = { "isRubyString(name)", "isRubyString(value)" })
public int setenv(DynamicObject name, DynamicObject value, int overwrite) {
final String nameString = decodeUTF8(name);
if (nameString.equals("TZ")) {
GetTimeZoneNode.invalidateTZ();
}
invalidateENV(nameString);
return posix().setenv(nameString, decodeUTF8(value), overwrite);
}

@@ -331,7 +335,9 @@ public abstract static class UnsetenvNode extends CoreMethodArrayArgumentsNode {
@CompilerDirectives.TruffleBoundary
@Specialization(guards = "isRubyString(name)")
public int unsetenv(DynamicObject name) {
return posix().unsetenv(decodeUTF8(name));
final String nameString = decodeUTF8(name);
invalidateENV(nameString);
return posix().unsetenv(nameString);
}

}
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;
@@ -86,6 +87,12 @@ protected DynamicObject getSymbol(Rope name) {
return getContext().getSymbolTable().getSymbol(name);
}

protected Encoding getDefaultInternalEncoding() {
return getContext().getJRubyRuntime().getDefaultInternalEncoding() == null ?
UTF8Encoding.INSTANCE :
getContext().getJRubyRuntime().getDefaultInternalEncoding();
}

protected DynamicObject createString(ByteList bytes) {
return StringOperations.createString(getContext(), bytes);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* 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 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;

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,281 @@
/*
* 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.*;
import org.jruby.truffle.core.cast.*;
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;

@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(context, sourceSection, null);
}

@Specialization
public DynamicObject push(VirtualFrame frame, DynamicObject history, Object... lines) {
final ConsoleHolder consoleHolder = getContext().getConsoleHolder();

for (Object line : lines) {
final String asString = toJavaStringNode.executeToJavaString(frame, line);
consoleHolder.getHistory().add(asString);
}

return history;
}

}

@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.createRope(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.createRope(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.createRope(e.value().toString(), getDefaultInternalEncoding()));

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

return history;
}

}

@CoreMethod(names = "[]", needsSelf = false, required = 1, lowerFixnumParameters = 0)
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.createRope(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(null, null, line);
}

@Specialization
public Object setIndex(VirtualFrame frame, 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, lowerFixnumParameters = 0)
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);
}

@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.createRope(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,313 @@
/*
* 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.*;
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.*;
import org.jruby.truffle.core.array.ArrayHelpers;
import org.jruby.truffle.core.cast.*;
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.createRope(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);
}

@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 {

@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(null, null, false, addToHistory);
}

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

DynamicObject line = nil();
String value;
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.createRope(value, getContext().getJRubyRuntime().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(null, null, 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.createRope(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;
}

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

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()]);
}

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 {
@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 a62df65

Please sign in to comment.