-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathReadline.java
More file actions
370 lines (308 loc) · 13 KB
/
Readline.java
File metadata and controls
370 lines (308 loc) · 13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
package org.perlonjava.runtime.operators;
import org.perlonjava.runtime.runtimetypes.*;
import static org.perlonjava.runtime.runtimetypes.GlobalVariable.getGlobalVariable;
import static org.perlonjava.runtime.runtimetypes.RuntimeScalarCache.scalarFalse;
import static org.perlonjava.runtime.runtimetypes.RuntimeScalarCache.scalarUndef;
public class Readline {
/**
* Reads a line from a file handle.
*
* @param fileHandle The file handle.
* @param ctx The context (SCALAR or LIST).
* @return A RuntimeBase with the line(s).
*/
public static RuntimeBase readline(RuntimeScalar fileHandle, int ctx) {
RuntimeIO fh = fileHandle.getRuntimeIO();
if (fh == null) {
throw new PerlCompilerException("Cannot readline from undefined filehandle");
}
if (fh instanceof TieHandle tieHandle) {
return TieHandle.tiedReadline(tieHandle, ctx);
}
if (ctx == RuntimeContextType.LIST) {
// Handle LIST context
RuntimeList lines = new RuntimeList();
RuntimeScalar line;
while ((line = readline(fh)).type != RuntimeScalarType.UNDEF) {
lines.elements.add(line);
}
return lines;
} else {
// Handle SCALAR context (original behavior)
return readline(fh);
}
}
public static RuntimeScalar readline(RuntimeIO runtimeIO) {
// Flush stdout and stderr before reading, in case we are displaying a prompt
RuntimeIO.flushFileHandles();
// Check if the IO object is set up for reading
if (runtimeIO.ioHandle == null) {
throw new PerlCompilerException("readline is not supported for output streams");
}
// Set this as the last accessed handle for $. (INPUT_LINE_NUMBER) special variable
RuntimeIO.lastAccesseddHandle = runtimeIO;
// Get the input record separator (equivalent to Perl's $/)
RuntimeScalar rsScalar = getGlobalVariable("main::/");
// Check if we're dealing with an InputRecordSeparator instance
InputRecordSeparator rs = null;
if (rsScalar instanceof InputRecordSeparator) {
rs = (InputRecordSeparator) rsScalar;
}
// Handle different modes of $/
if (rs != null && rs.isSlurpMode()) {
// Handle slurp mode when $/ = undef
StringBuilder content = new StringBuilder();
String readChar;
while (!(readChar = runtimeIO.ioHandle.read(1).toString()).isEmpty()) {
content.append(readChar.charAt(0));
}
if (content.length() > 0) {
// Count newlines for line number tracking
String contentStr = content.toString();
for (int i = 0; i < contentStr.length(); i++) {
if (contentStr.charAt(i) == '\n') {
runtimeIO.currentLineNumber++;
}
}
return new RuntimeScalar(contentStr);
} else if (runtimeIO.eof().getBoolean()) {
return scalarUndef;
}
return new RuntimeScalar(content.toString());
}
if (rs != null && rs.isParagraphMode()) {
// Handle paragraph mode when $/ = ''
return readParagraphMode(runtimeIO);
}
if (rs != null && rs.isRecordLengthMode()) {
// Handle record length mode when $/ = \N
int recordLength = rs.getRecordLength();
return readFixedLength(runtimeIO, recordLength);
}
// Handle normal string separator mode
String sep = rsScalar.toString();
if (sep.isEmpty()) {
// Handle paragraph mode when $/ = '' (fallback if not InputRecordSeparator)
return readParagraphMode(runtimeIO);
}
// Handle multi-character or single character separators
if (sep.length() == 1) {
// Single character separator (optimized path)
return readUntilCharacter(runtimeIO, sep.charAt(0));
} else {
// Multi-character separator
return readUntilString(runtimeIO, sep);
}
}
private static RuntimeScalar readParagraphMode(RuntimeIO runtimeIO) {
StringBuilder paragraph = new StringBuilder();
boolean inParagraph = false;
boolean lastWasNewline = false;
String readChar;
while (!(readChar = runtimeIO.ioHandle.read(1).toString()).isEmpty()) {
char c = readChar.charAt(0);
if (c == '\n') {
if (!inParagraph) {
// Skip leading newlines
continue;
}
paragraph.append(c);
if (lastWasNewline) {
// Found blank line (two consecutive newlines) - end of paragraph
break;
}
lastWasNewline = true;
} else {
inParagraph = true;
lastWasNewline = false;
paragraph.append(c);
}
}
// Return undef if we've reached EOF and no characters were read (excluding skipped newlines)
if (!inParagraph && runtimeIO.eof().getBoolean()) {
return scalarUndef;
}
// Increment the line number counter if a paragraph was read
if (inParagraph) {
// Count the number of lines in the paragraph
String paragraphStr = paragraph.toString();
for (int i = 0; i < paragraphStr.length(); i++) {
if (paragraphStr.charAt(i) == '\n') {
runtimeIO.currentLineNumber++;
}
}
}
return new RuntimeScalar(paragraph.toString());
}
private static RuntimeScalar readFixedLength(RuntimeIO runtimeIO, int length) {
StringBuilder result = new StringBuilder();
for (int i = 0; i < length; i++) {
String readChar = runtimeIO.ioHandle.read(1).toString();
if (readChar.isEmpty()) {
break; // EOF reached
}
result.append(readChar.charAt(0));
}
// Return undef if we've reached EOF and no characters were read
if (result.length() == 0 && runtimeIO.eof().getBoolean()) {
return scalarUndef;
}
// Don't increment line numbers for fixed-length reads
// (this matches Perl behavior for record-length mode)
return new RuntimeScalar(result.toString());
}
private static RuntimeScalar readUntilCharacter(RuntimeIO runtimeIO, char separator) {
StringBuilder line = new StringBuilder();
String readChar;
while (!(readChar = runtimeIO.ioHandle.read(1).toString()).isEmpty()) {
char c = readChar.charAt(0);
line.append(c);
// Break if we've reached the separator
if (c == separator) {
break;
}
}
// Increment the line number counter if a line was read
if (!line.isEmpty()) {
runtimeIO.currentLineNumber++;
}
// Return undef if we've reached EOF and no characters were read
if (line.isEmpty() && runtimeIO.eof().getBoolean()) {
return scalarUndef;
}
return new RuntimeScalar(line.toString());
}
private static RuntimeScalar readUntilString(RuntimeIO runtimeIO, String separator) {
StringBuilder line = new StringBuilder();
StringBuilder buffer = new StringBuilder();
String readChar;
while (!(readChar = runtimeIO.ioHandle.read(1).toString()).isEmpty()) {
char c = readChar.charAt(0);
line.append(c);
buffer.append(c);
// Keep only the last separator.length() characters in buffer
if (buffer.length() > separator.length()) {
buffer.deleteCharAt(0);
}
// Check if buffer ends with separator
if (buffer.toString().equals(separator)) {
break;
}
}
// Increment the line number counter if a line was read and contains newlines
if (!line.isEmpty()) {
String lineStr = line.toString();
for (int i = 0; i < lineStr.length(); i++) {
if (lineStr.charAt(i) == '\n') {
runtimeIO.currentLineNumber++;
}
}
}
// Return undef if we've reached EOF and no characters were read
if (line.isEmpty() && runtimeIO.eof().getBoolean()) {
return scalarUndef;
}
return new RuntimeScalar(line.toString());
}
/**
* Reads a specified number of characters from a file handle into a scalar.
*
* @param args A RuntimeList containing fileHandle, scalar, length, and offset.
* @return The number of characters read, or 0 at EOF, or undef on error.
*/
public static RuntimeScalar read(RuntimeList args) {
// Extract arguments from the list
RuntimeScalar fileHandle = (RuntimeScalar) args.elements.getFirst();
RuntimeIO fh = fileHandle.getRuntimeIO();
RuntimeScalar scalar = ((RuntimeScalar) args.elements.get(1)).scalarDeref();
RuntimeScalar length = (RuntimeScalar) args.elements.get(2);
RuntimeScalar offset = args.elements.size() > 3
? (RuntimeScalar) args.elements.get(3)
: new RuntimeScalar(0);
if (fh instanceof TieHandle tieHandle) {
args = args.elements.size() > 3
? new RuntimeList(scalar, length, offset)
: new RuntimeList(scalar, length);
return TieHandle.tiedRead(tieHandle, args);
}
if (fh == null) {
getGlobalVariable("main::!").set("read file handle is closed");
return scalarFalse;
}
// Check if the IO object is set up for reading
if (fh.ioHandle == null) {
getGlobalVariable("main::!").set("read is not open for input");
return scalarFalse;
}
// Convert length and offset to integers
int lengthValue = length.getInt();
int offsetValue = offset.getInt();
// Handle zero-length read
if (lengthValue == 0) {
String currentValue = scalar.toString();
StringBuilder scalarValue = new StringBuilder(currentValue);
// Truncate the buffer at the offset
if (offsetValue < 0) {
offsetValue = scalarValue.length() + offsetValue;
if (offsetValue < 0) {
offsetValue = 0;
}
}
scalarValue.setLength(offsetValue);
scalar.set(scalarValue.toString());
return new RuntimeScalar(0);
}
// Read data using the new API - read characters, not bytes
String readData = fh.ioHandle.read(lengthValue).toString();
int charsRead = readData.length();
if (charsRead == 0) {
// EOF or error - handle based on offset
if (offsetValue != 0) {
// Handle offset (both positive and negative) when reading 0 bytes
StringBuilder scalarValue = new StringBuilder(scalar.toString());
// Convert negative offset to positive
if (offsetValue < 0) {
offsetValue = scalarValue.length() + offsetValue;
if (offsetValue < 0) {
offsetValue = 0;
}
}
// Ensure buffer is large enough for offset
while (scalarValue.length() < offsetValue) {
scalarValue.append('\0');
}
// Truncate to offset
scalarValue.setLength(offsetValue);
scalar.set(scalarValue.toString());
} else {
// No offset - just clear the scalar
scalar.set("");
}
return new RuntimeScalar(0);
}
// Handle offset
StringBuilder scalarValue = new StringBuilder(scalar.toString());
if (offsetValue < 0) {
offsetValue = scalarValue.length() + offsetValue;
if (offsetValue < 0) {
offsetValue = 0;
}
}
int newLength = offsetValue + charsRead;
// Ensure the buffer is large enough for the offset
while (scalarValue.length() < offsetValue) {
scalarValue.append('\0');
}
// Replace the data from offsetValue onwards with the new data
scalarValue.replace(offsetValue, scalarValue.length(), readData);
// Truncate to the correct final length
scalarValue.setLength(newLength);
// Update the scalar with the new value
scalar.set(scalarValue.toString());
if (!IOOperator.hasUtf8Layer(fh)) {
scalar.type = RuntimeScalarType.BYTE_STRING;
}
// Return the number of characters read
return new RuntimeScalar(charsRead);
}
}