Skip to content

Commit 185cdf9

Browse files
committed
Implement negative line numbers for eval
1 parent 5cfbbd0 commit 185cdf9

File tree

14 files changed

+50
-62
lines changed

14 files changed

+50
-62
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ Compatibility:
3838
* Pass the final `super` specs (#2104, @chrisseaton).
3939
* Fix arity for arguments with optional kwargs (#1669, @ssnickolay)
4040
* Fix arity for `Proc` (#2098, @ssnickolay)
41+
* Implement negative line numbers for eval (#1482).
4142

4243
Performance:
4344

doc/user/compatibility.md

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -147,14 +147,6 @@ be a large effort to support every Ruby String operation on native strings.
147147
Setting the process title (via `$0` or `Process.setproctitle` in Ruby) is done
148148
as best-effort. It may not work, or the title you try to set may be truncated.
149149

150-
#### Line numbers other than 1 work differently
151-
152-
In an `eval` where a custom line number can be specified, line numbers below 1
153-
are treated as 1, and line numbers above 1 are implemented by inserting blank
154-
lines in front of the source before parsing it.
155-
156-
The `erb` standard library has been modified to not use negative line numbers.
157-
158150
#### Polyglot standard IO streams
159151

160152
If you use standard IO streams provided by the Polyglot engine, via the

lib/mri/erb.rb

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -938,11 +938,7 @@ def new_toplevel(vars = nil)
938938
def def_method(mod, methodname, fname='(ERB)')
939939
src = self.src.sub(/^(?!#|$)/) {"def #{methodname}\n"} << "\nend\n"
940940
mod.module_eval do
941-
if defined?(::TruffleRuby)
942-
eval(src, binding, fname)
943-
else
944-
eval(src, binding, fname, -1)
945-
end
941+
eval(src, binding, fname, -1)
946942
end
947943
end
948944

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
fails:BasicObject#instance_eval gets constants in the receiver if a string given
2-
fails:BasicObject#instance_eval evaluates string with given filename and negative linenumber

spec/tags/core/kernel/eval_tags.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,4 @@ fails:Kernel#eval returns from the scope calling #eval when evaluating 'return'
33
fails:Kernel#eval unwinds through a Proc-style closure and returns from a lambda-style closure in the closure chain
44
slow:Kernel#eval raises a LocalJumpError if there is no lambda-style closure in the chain
55
slow:Kernel#eval does not share locals across eval scopes
6-
fails:Kernel#eval evaluates string with given filename and negative linenumber
76
fails(cannot store constant with name in binary encoding):Kernel#eval with a magic encoding comment ignores the magic encoding comment if it is after a frozen_string_literal magic comment

src/main/java/org/truffleruby/RubyContext.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@
1313
import java.nio.ByteBuffer;
1414
import java.security.NoSuchAlgorithmException;
1515
import java.security.SecureRandom;
16+
import java.util.Collections;
1617
import java.util.Objects;
18+
import java.util.Map;
19+
import java.util.WeakHashMap;
1720
import java.util.concurrent.locks.ReentrantLock;
1821
import java.util.logging.Level;
1922

@@ -120,6 +123,7 @@ public class RubyContext {
120123
private final PreInitializationManager preInitializationManager;
121124
private final NativeConfiguration nativeConfiguration;
122125
private final ValueWrapperManager valueWrapperManager;
126+
private final Map<Source, Integer> sourceLineOffsets = Collections.synchronizedMap(new WeakHashMap<>());
123127

124128
@CompilationFinal private SecureRandom random;
125129
private final Hashing hashing;
@@ -758,6 +762,10 @@ public ValueWrapperManager getValueWrapperManager() {
758762
return valueWrapperManager;
759763
}
760764

765+
public Map<Source, Integer> getSourceLineOffsets() {
766+
return sourceLineOffsets;
767+
}
768+
761769
private static SecureRandom createRandomInstance() {
762770
try {
763771
/* We want to use a non-blocking source because this is what MRI does (via /dev/urandom) and it's been found

src/main/java/org/truffleruby/language/backtrace/BacktraceFormatter.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.truffleruby.core.string.StringUtils;
2929
import org.truffleruby.language.RubyRootNode;
3030
import org.truffleruby.language.methods.TranslateExceptionNode;
31+
import org.truffleruby.parser.RubySource;
3132

3233
import java.io.OutputStream;
3334
import java.io.PrintStream;
@@ -244,7 +245,7 @@ private String formatLineInternal(TruffleStackTraceElement[] stackTrace, int n,
244245
builder.append(RubyContext.getPath(reportedSourceSection.getSource()));
245246
}
246247
builder.append(":");
247-
builder.append(reportedSourceSection.getStartLine());
248+
builder.append(RubySource.getStartLineAdjusted(context, reportedSourceSection));
248249
}
249250
builder.append(":in `");
250251
builder.append(reportedName);

src/main/java/org/truffleruby/language/eval/CreateEvalSourceNode.java

Lines changed: 5 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,8 @@
99
*/
1010
package org.truffleruby.language.eval;
1111

12-
import java.util.Arrays;
1312

1413
import org.jcodings.Encoding;
15-
import org.truffleruby.RubyLanguage;
1614
import org.truffleruby.core.encoding.EncodingManager;
1715
import org.truffleruby.core.rope.CannotConvertBinaryRubyStringToJavaString;
1816
import org.truffleruby.core.rope.Rope;
@@ -48,7 +46,10 @@ public RubySource createEvalSource(Rope code, String method, String file, int li
4846

4947
final Source source = Source.newBuilder(TruffleRuby.LANGUAGE_ID, sourceString, file).build();
5048

51-
return new RubySource(source, file, sourceRope, true);
49+
final RubySource rubySource = new RubySource(source, file, sourceRope, true, line - 1);
50+
51+
getContext().getSourceLineOffsets().put(source, line - 1);
52+
return rubySource;
5253
}
5354

5455
private static Rope createEvalRope(Rope source, String method, String file, int line) {
@@ -64,46 +65,7 @@ private static Rope createEvalRope(Rope source, String method, String file, int
6465
source = RopeOperations.withEncoding(source, encoding[0]);
6566
}
6667

67-
// Do padding after magic comment detection
68-
return offsetSource(method, source, file, line);
69-
}
70-
71-
private static Rope offsetSource(String method, Rope source, String file, int line) {
72-
// TODO CS 23-Apr-18 Truffle doesn't support line numbers starting at anything but 1
73-
if (line == 0) {
74-
// fine instead of warning because these seem common
75-
RubyLanguage.LOGGER.fine(() -> String.format(
76-
"zero line number %s:%d not supported in #%s - will be reported as starting at 1",
77-
file,
78-
line,
79-
method));
80-
return source;
81-
} else if (line < 1) {
82-
RubyLanguage.LOGGER.warning(
83-
String.format(
84-
"negative line number %s:%d not supported in #%s - will be reported as starting at 1",
85-
file,
86-
line,
87-
method));
88-
return source;
89-
} else if (line > 1) {
90-
// fine instead of warning because we can simulate these
91-
RubyLanguage.LOGGER.fine(() -> String.format(
92-
"offset line number %s:%d are simulated in #%s by adding blank lines",
93-
file,
94-
line,
95-
method));
96-
if (!source.getEncoding().isAsciiCompatible()) {
97-
throw new UnsupportedOperationException("Cannot prepend newlines in an ASCII incompatible encoding");
98-
}
99-
final int n = line - 1;
100-
final byte[] bytes = new byte[n + source.byteLength()];
101-
Arrays.fill(bytes, 0, n, (byte) '\n');
102-
System.arraycopy(source.getBytes(), 0, bytes, n, source.byteLength());
103-
return RopeOperations.create(bytes, source.getEncoding(), source.getCodeRange());
104-
} else {
105-
return source;
106-
}
68+
return source;
10769
}
10870

10971
}

src/main/java/org/truffleruby/parser/RubySource.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import java.util.Objects;
1313

14+
import com.oracle.truffle.api.source.SourceSection;
1415
import org.truffleruby.RubyContext;
1516
import org.truffleruby.core.rope.Rope;
1617

@@ -25,6 +26,7 @@ public class RubySource {
2526
private final String sourcePath;
2627
private final Rope sourceRope;
2728
private final boolean isEval;
29+
private final int lineOffset;
2830

2931
public RubySource(Source source, String sourcePath) {
3032
this(source, sourcePath, null, false);
@@ -35,12 +37,17 @@ public RubySource(Source source, String sourcePath, Rope sourceRope) {
3537
}
3638

3739
public RubySource(Source source, String sourcePath, Rope sourceRope, boolean isEval) {
40+
this(source, sourcePath, sourceRope, isEval, 0);
41+
}
42+
43+
public RubySource(Source source, String sourcePath, Rope sourceRope, boolean isEval, int lineOffset) {
3844
assert RubyContext.getPath(source).equals(sourcePath) : RubyContext.getPath(source) + " vs " + sourcePath;
3945
this.source = Objects.requireNonNull(source);
4046
//intern() to improve footprint
4147
this.sourcePath = Objects.requireNonNull(sourcePath).intern();
4248
this.sourceRope = sourceRope;
4349
this.isEval = isEval;
50+
this.lineOffset = lineOffset;
4451
}
4552

4653
public Source getSource() {
@@ -58,4 +65,18 @@ public Rope getRope() {
5865
public boolean isEval() {
5966
return isEval;
6067
}
68+
69+
public int getLineOffset() {
70+
return lineOffset;
71+
}
72+
73+
public static int getStartLineAdjusted(RubyContext context, SourceSection sourceSection) {
74+
final Integer lineOffset = context.getSourceLineOffsets().get(sourceSection.getSource());
75+
if (lineOffset != null) {
76+
return sourceSection.getStartLine() + lineOffset;
77+
} else {
78+
return sourceSection.getStartLine();
79+
}
80+
}
81+
6182
}

src/main/java/org/truffleruby/parser/TranslatorDriver.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ public RootParseNode parseToJRubyAST(RubySource rubySource, StaticScope blockSco
403403
default:
404404
StringBuilder buffer = new StringBuilder(100);
405405
buffer.append(e.getFile()).append(':');
406-
buffer.append(e.getLine()).append(": ");
406+
buffer.append(e.getLine() + rubySource.getLineOffset()).append(": ");
407407
buffer.append(e.getMessage());
408408

409409
if (context != null) {

0 commit comments

Comments
 (0)