Skip to content

Commit b1a08c9

Browse files
committed
add #line preprocessor directive
1 parent 5c60585 commit b1a08c9

File tree

6 files changed

+76
-8
lines changed

6 files changed

+76
-8
lines changed

MILESTONES.md

+1
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ The following areas are currently under active development to enhance the functi
235235
## Upcoming Milestones
236236

237237
- **v3.0.1**: Next minor version
238+
- Added `# line` preprocessor directive.
238239
- Planned release date: 2025-05-10
239240

240241
### v4.0.0 Milestone (Planned Release Date: 2026-05-10)

docs/FEATURE_MATRIX.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ This list is under review.
293293
-**`UNITCHECK`**: special block is implemented.
294294
-**Labels**: Labels are implemented.
295295
-**Here-docs**: Here-docs for multiline string literals are implemented.
296-
- **Preprocessor**: `# line` directive is not yet implemented.
296+
- **Preprocessor**: `# line` directive is implemented.
297297
-**`glob`**: `glob` operator is implemented.
298298
-**`<>`**: `<>` operator is implemented.
299299
-**`<$fh>`**: `<$fh>` and `<STDIN>` operators are implemented.

src/main/java/org/perlonjava/parser/CoreOperatorResolver.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public static Node parseCoreOperator(Parser parser, LexerToken token, int startI
3737
return new NumberNode(Integer.toString(parser.ctx.errorUtil.getLineNumber(parser.tokenIndex)), parser.tokenIndex);
3838
case "__FILE__":
3939
// Returns the current file name as a StringNode
40-
return new StringNode(parser.ctx.compilerOptions.fileName, parser.tokenIndex);
40+
return new StringNode(parser.ctx.errorUtil.getFileName(), parser.tokenIndex);
4141
case "__PACKAGE__":
4242
// Returns the current package name as a StringNode
4343
return new StringNode(parser.ctx.symbolTable.getCurrentPackage(), parser.tokenIndex);

src/main/java/org/perlonjava/parser/Whitespace.java

+14-5
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,17 @@ public static int skipWhitespace(Parser parser, int tokenIndex, List<LexerToken>
6565

6666
case OPERATOR:
6767
if (token.text.equals("#")) {
68+
// # line directive must appear at the beginning of the line
69+
boolean maybeLineDirective = tokenIndex == 0 || tokens.get(tokenIndex - 1).type == LexerTokenType.NEWLINE;
70+
6871
// Skip optional whitespace after '#'
6972
tokenIndex++;
7073
while (tokenIndex < tokens.size() && tokens.get(tokenIndex).type == LexerTokenType.WHITESPACE) {
7174
tokenIndex++;
7275
}
7376
// Check if it's a "# line" directive
74-
if (tokenIndex < tokens.size() && tokens.get(tokenIndex).text.equals("line")) {
75-
tokenIndex = parseLineDirective(tokenIndex, tokens);
77+
if (maybeLineDirective && tokenIndex < tokens.size() && tokens.get(tokenIndex).text.equals("line")) {
78+
tokenIndex = parseLineDirective(parser, tokenIndex, tokens);
7679
}
7780
// Skip comment until end of line
7881
while (tokenIndex < tokens.size() && tokens.get(tokenIndex).type != LexerTokenType.NEWLINE) {
@@ -103,7 +106,7 @@ public static int skipWhitespace(Parser parser, int tokenIndex, List<LexerToken>
103106
return tokenIndex;
104107
}
105108

106-
private static int parseLineDirective(int tokenIndex, List<LexerToken> tokens) {
109+
private static int parseLineDirective(Parser parser, int tokenIndex, List<LexerToken> tokens) {
107110
tokenIndex++; // Skip 'line'
108111
// Skip optional whitespace after 'line'
109112
while (tokenIndex < tokens.size() && tokens.get(tokenIndex).type == LexerTokenType.WHITESPACE) {
@@ -115,6 +118,11 @@ private static int parseLineDirective(int tokenIndex, List<LexerToken> tokens) {
115118
try {
116119
int lineNumber = Integer.parseInt(lineNumberStr);
117120
tokenIndex++;
121+
122+
// Update the context (ErrorMessageUtil instance) with the new line number
123+
parser.ctx.errorUtil.setLineNumber(lineNumber - 1);
124+
parser.ctx.errorUtil.setTokenIndex(tokenIndex - 1);
125+
118126
// Skip optional whitespace before filename
119127
while (tokenIndex < tokens.size() && tokens.get(tokenIndex).type == LexerTokenType.WHITESPACE) {
120128
tokenIndex++;
@@ -130,8 +138,9 @@ private static int parseLineDirective(int tokenIndex, List<LexerToken> tokens) {
130138
if (tokenIndex < tokens.size() && tokens.get(tokenIndex).type == LexerTokenType.OPERATOR && tokens.get(tokenIndex).text.equals("\"")) {
131139
tokenIndex++; // Skip closing quote
132140
String filename = filenameBuilder.toString();
133-
// Handle the line number and filename as needed
134-
// For example, update a state or context object
141+
142+
// Update the context (ErrorMessageUtil instance) with the new file name
143+
parser.ctx.errorUtil.setFileName(filename);
135144
}
136145
}
137146
} catch (NumberFormatException e) {

src/main/java/org/perlonjava/runtime/ErrorMessageUtil.java

+17-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* Utility class for generating error messages with context from a list of tokens.
1414
*/
1515
public class ErrorMessageUtil {
16-
private final String fileName;
16+
private String fileName;
1717
private final List<LexerToken> tokens;
1818
private int tokenIndex;
1919
private int lastLineNumber;
@@ -31,6 +31,22 @@ public ErrorMessageUtil(String fileName, List<LexerToken> tokens) {
3131
this.lastLineNumber = 1;
3232
}
3333

34+
public String getFileName() {
35+
return fileName;
36+
}
37+
38+
public void setFileName(String fileName) {
39+
this.fileName = fileName;
40+
}
41+
42+
public void setLineNumber(int lineNumber) {
43+
this.lastLineNumber = lineNumber;
44+
}
45+
46+
public void setTokenIndex(int index) {
47+
this.tokenIndex = index;
48+
}
49+
3450
/**
3551
* Quotes the specified string for inclusion in an error message.
3652
* Escapes special characters such as newlines, tabs, and backslashes.

src/test/resources/line.t

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
use 5.38.0;
2+
use strict;
3+
use warnings;
4+
use Test::More;
5+
6+
# Test #line directive
7+
8+
my $original_file = __FILE__;
9+
10+
my $line;
11+
my $file;
12+
13+
# line 50
14+
$line = __LINE__;
15+
$file = __FILE__;
16+
is( $line, 50, "Line number is $line" );
17+
is( $file, $original_file, "File is original file" );
18+
19+
{
20+
# This is an invalid directive because it is indented
21+
# line 200
22+
;
23+
}
24+
$line = __LINE__;
25+
$file = __FILE__;
26+
isnt( $line, 200, "This is a comment; Line number is $line" );
27+
is( $file, $original_file, "File is original file" );
28+
29+
# line 100 "testfile.pl"
30+
$line = __LINE__;
31+
$file = __FILE__;
32+
is( $line, 100, "Line number is $line" );
33+
is( $file, 'testfile.pl', "File is '$file'" );
34+
35+
## # line 150 "testfile2.pl" other comments
36+
## $line = __LINE__;
37+
## $file = __FILE__;
38+
## is($line, 150, "Line number is $line");
39+
## is($file, 'testfile2.pl', "File is '$file'");
40+
41+
done_testing();
42+

0 commit comments

Comments
 (0)