Add input sources and source loading stage

This commit is contained in:
aNNiMON 2023-10-05 13:41:47 +03:00 committed by Victor Melnik
parent 5dcf31c106
commit c5810f0deb
10 changed files with 202 additions and 90 deletions

View File

@ -3,20 +3,21 @@ package com.annimon.ownlang.util;
import com.annimon.ownlang.Console; import com.annimon.ownlang.Console;
import com.annimon.ownlang.stages.Stage; import com.annimon.ownlang.stages.Stage;
import com.annimon.ownlang.stages.StagesData; import com.annimon.ownlang.stages.StagesData;
import com.annimon.ownlang.util.input.SourceLoaderStage;
public class ErrorsLocationFormatterStage implements Stage<Iterable<? extends SourceLocatedError>, String> { public class ErrorsLocationFormatterStage implements Stage<Iterable<? extends SourceLocatedError>, String> {
@Override @Override
public String perform(StagesData stagesData, Iterable<? extends SourceLocatedError> input) { public String perform(StagesData stagesData, Iterable<? extends SourceLocatedError> input) {
final var sb = new StringBuilder(); final var sb = new StringBuilder();
final String source = stagesData.get(SourceLoaderStage.TAG_SOURCE); final String source = stagesData.getOrDefault(SourceLoaderStage.TAG_SOURCE, "");
final var lines = source.split("\r?\n"); final var lines = source.split("\r?\n");
for (SourceLocatedError error : input) { for (SourceLocatedError error : input) {
sb.append(Console.newline()); sb.append(Console.newline());
sb.append(error); sb.append(error);
sb.append(Console.newline()); sb.append(Console.newline());
final Range range = error.getRange(); final Range range = error.getRange();
if (range != null) { if (range != null && lines.length > 0) {
printPosition(sb, range.normalize(), lines); printPosition(sb, range.normalize(), lines);
} }
} }

View File

@ -0,0 +1,15 @@
package com.annimon.ownlang.util.input;
import java.io.IOException;
public interface InputSource {
String getPath();
String load() throws IOException;
default String getBasePath() {
int i = getPath().lastIndexOf("/");
if (i == -1) return "";
return getPath().substring(0, i + 1);
}
}

View File

@ -0,0 +1,30 @@
package com.annimon.ownlang.util.input;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
public record InputSourceFile(String path) implements InputSource {
@Override
public String getPath() {
return path;
}
@Override
public String load() throws IOException {
if (Files.isReadable(Path.of(path))) {
try (InputStream is = new FileInputStream(path)) {
return SourceLoaderStage.readStream(is);
}
}
throw new IOException(path + " not found");
}
@Override
public String toString() {
return "File " + path;
}
}

View File

@ -0,0 +1,19 @@
package com.annimon.ownlang.util.input;
public record InputSourceProgram(String program) implements InputSource {
@Override
public String getPath() {
return ".";
}
@Override
public String load() {
return program;
}
@Override
public String toString() {
return "Program";
}
}

View File

@ -0,0 +1,27 @@
package com.annimon.ownlang.util.input;
import java.io.IOException;
import java.io.InputStream;
public record InputSourceResource(String path) implements InputSource {
@Override
public String getPath() {
return path;
}
@Override
public String load() throws IOException {
try (InputStream is = getClass().getResourceAsStream(path)) {
if (is != null) {
return SourceLoaderStage.readStream(is);
}
}
throw new IOException(path + " not found");
}
@Override
public String toString() {
return "Resource " + path;
}
}

View File

@ -1,37 +1,25 @@
package com.annimon.ownlang.util; package com.annimon.ownlang.util.input;
import com.annimon.ownlang.exceptions.OwnLangRuntimeException; import com.annimon.ownlang.exceptions.OwnLangRuntimeException;
import com.annimon.ownlang.stages.Stage; import com.annimon.ownlang.stages.Stage;
import com.annimon.ownlang.stages.StagesData; import com.annimon.ownlang.stages.StagesData;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
public class SourceLoaderStage implements Stage<String, String> { public class SourceLoaderStage implements Stage<InputSource, String> {
public static final String TAG_SOURCE = "source"; public static final String TAG_SOURCE = "source";
@Override @Override
public String perform(StagesData stagesData, String name) { public String perform(StagesData stagesData, InputSource inputSource) {
try { try {
String result = readSource(name); String result = inputSource.load();
stagesData.put(TAG_SOURCE, result); stagesData.put(TAG_SOURCE, result);
return result; return result;
} catch (IOException e) { } catch (IOException e) {
throw new OwnLangRuntimeException("Unable to read input " + name, e); throw new OwnLangRuntimeException("Unable to read input " + inputSource, e);
}
}
private String readSource(String name) throws IOException {
try (InputStream is = getClass().getResourceAsStream("/" + name)) {
if (is != null) {
return readStream(is);
}
}
try (InputStream is = new FileInputStream(name)) {
return readStream(is);
} }
} }

View File

@ -56,4 +56,3 @@ tasks.register('runOptimizationDumper', JavaExec) {
classpath = sourceSets.main.runtimeClasspath classpath = sourceSets.main.runtimeClasspath
args '../program.own' args '../program.own'
} }
//

View File

@ -3,13 +3,13 @@ package com.annimon.ownlang;
import com.annimon.ownlang.exceptions.OwnLangParserException; import com.annimon.ownlang.exceptions.OwnLangParserException;
import com.annimon.ownlang.exceptions.StoppedException; import com.annimon.ownlang.exceptions.StoppedException;
import com.annimon.ownlang.parser.Beautifier; import com.annimon.ownlang.parser.Beautifier;
import com.annimon.ownlang.parser.SourceLoader;
import com.annimon.ownlang.parser.Token; import com.annimon.ownlang.parser.Token;
import com.annimon.ownlang.parser.ast.Statement; import com.annimon.ownlang.parser.ast.Statement;
import com.annimon.ownlang.parser.error.ParseErrorsFormatterStage; import com.annimon.ownlang.parser.error.ParseErrorsFormatterStage;
import com.annimon.ownlang.parser.linters.LinterStage; import com.annimon.ownlang.parser.linters.LinterStage;
import com.annimon.ownlang.parser.optimization.OptimizationStage; import com.annimon.ownlang.parser.optimization.OptimizationStage;
import com.annimon.ownlang.stages.*; import com.annimon.ownlang.stages.*;
import com.annimon.ownlang.util.input.SourceLoaderStage;
import com.annimon.ownlang.utils.Repl; import com.annimon.ownlang.utils.Repl;
import com.annimon.ownlang.utils.Sandbox; import com.annimon.ownlang.utils.Sandbox;
import com.annimon.ownlang.utils.TimeMeasurement; import com.annimon.ownlang.utils.TimeMeasurement;
@ -23,17 +23,12 @@ import java.util.concurrent.TimeUnit;
public final class Main { public final class Main {
public static void main(String[] args) throws IOException { public static void main(String[] args) throws IOException {
if (args.length == 0) { final RunOptions options = new RunOptions();
try { if (args.length == 0 && (options.detectDefaultProgramPath() == null)) {
runDefault(); printUsage();
} catch (IOException ioe) {
printUsage();
}
return; return;
} }
final RunOptions options = new RunOptions();
String input = null;
for (int i = 0; i < args.length; i++) { for (int i = 0; i < args.length; i++) {
switch (args[i]) { switch (args[i]) {
case "-a": case "-a":
@ -82,73 +77,69 @@ public final class Main {
case "-f": case "-f":
case "--file": case "--file":
if (i + 1 < args.length) { if (i + 1 < args.length) {
input = SourceLoader.readSource(args[i + 1]); options.programPath = args[i + 1];
createOwnLangArgs(args, i + 2); createOwnLangArgs(args, i + 2);
i++; i++;
} }
break; break;
case "--sandbox": case "--sandbox":
createOwnLangArgs(args, i + 1); Sandbox.main(createOwnLangArgs(args, i + 1));
final String[] ownlangArgs = Shared.getOwnlangArgs();
String[] newArgs = new String[ownlangArgs.length];
System.arraycopy(ownlangArgs, 0, newArgs, 0, ownlangArgs.length);
Sandbox.main(newArgs);
return; return;
default: default:
if (input == null) { if (options.programSource == null) {
input = args[i]; options.programSource = args[i];
createOwnLangArgs(args, i + 1); createOwnLangArgs(args, i + 1);
} }
break; break;
} }
} }
if (input == null) {
throw new IllegalArgumentException("Empty input");
}
if (options.beautifyMode) { if (options.beautifyMode) {
String input = new SourceLoaderStage()
.perform(new StagesDataMap(), options.toInputSource());
System.out.println(Beautifier.beautify(input)); System.out.println(Beautifier.beautify(input));
return; return;
} }
run(input, options); run(options);
}
private static void runDefault() throws IOException {
final RunOptions options = new RunOptions();
run(SourceLoader.readSource("program.own"), options);
} }
private static void printUsage() { private static void printUsage() {
System.out.println("OwnLang version " + Version.VERSION + "\n\n" + System.out.println("OwnLang version %s\n\n".formatted(Version.VERSION) + """
"Usage: ownlang [options]\n" + Usage: ownlang [options]
" options:\n" + options:
" -f, --file [input] Run program file. Required.\n" + -f, --file [input] Run program file. Required.
" -r, --repl Enter to a REPL mode\n" + -r, --repl Enter to a REPL mode
" -l, --lint Find bugs in code\n" + -l, --lint Find bugs in code
" -o N, --optimize N Perform optimization with N passes\n" + -o N, --optimize N Perform optimization with N (0...9) passes
" -b, --beautify Beautify source code\n" + -b, --beautify Beautify source code
" -a, --showast Show AST of program\n" + -a, --showast Show AST of program
" -t, --showtokens Show lexical tokens\n" + -t, --showtokens Show lexical tokens
" -m, --showtime Show elapsed time of parsing and execution"); -m, --showtime Show elapsed time of parsing and execution
""");
} }
private static void createOwnLangArgs(String[] javaArgs, int index) { private static String[] createOwnLangArgs(String[] javaArgs, int index) {
if (index >= javaArgs.length) return; final String[] ownlangArgs;
final String[] ownlangArgs = new String[javaArgs.length - index]; if (index >= javaArgs.length) {
System.arraycopy(javaArgs, index, ownlangArgs, 0, ownlangArgs.length); ownlangArgs = new String[0];
} else {
ownlangArgs = new String[javaArgs.length - index];
System.arraycopy(javaArgs, index, ownlangArgs, 0, ownlangArgs.length);
}
Shared.setOwnlangArgs(ownlangArgs); Shared.setOwnlangArgs(ownlangArgs);
return ownlangArgs;
} }
private static void run(String input, RunOptions options) { private static void run(RunOptions options) {
final var measurement = new TimeMeasurement(); final var measurement = new TimeMeasurement();
final var scopedStages = new ScopedStageFactory(measurement::start, measurement::stop); final var scopedStages = new ScopedStageFactory(measurement::start, measurement::stop);
final var stagesData = new StagesDataMap(); final var stagesData = new StagesDataMap();
stagesData.put(SourceLoaderStage.TAG_SOURCE, input);
try { try {
scopedStages.create("Lexer", new LexerStage()) scopedStages.create("Source loader", new SourceLoaderStage())
.then(scopedStages.create("Lexer", new LexerStage()))
.then(scopedStages.create("Parser", new ParserStage())) .then(scopedStages.create("Parser", new ParserStage()))
.thenConditional(options.optimizationLevel > 0, .thenConditional(options.optimizationLevel > 0,
scopedStages.create("Optimization", scopedStages.create("Optimization",
@ -157,7 +148,7 @@ public final class Main {
scopedStages.create("Linter", new LinterStage())) scopedStages.create("Linter", new LinterStage()))
.then(scopedStages.create("Function adding", new FunctionAddingStage())) .then(scopedStages.create("Function adding", new FunctionAddingStage()))
.then(scopedStages.create("Execution", new ExecutionStage())) .then(scopedStages.create("Execution", new ExecutionStage()))
.perform(stagesData, input); .perform(stagesData, options.toInputSource());
} catch (OwnLangParserException ex) { } catch (OwnLangParserException ex) {
final var error = new ParseErrorsFormatterStage() final var error = new ParseErrorsFormatterStage()
.perform(stagesData, ex.getParseErrors()); .perform(stagesData, ex.getParseErrors());
@ -185,20 +176,4 @@ public final class Main {
} }
} }
} }
private static class RunOptions {
boolean showTokens, showAst, showMeasurements;
boolean lintMode;
boolean beautifyMode;
int optimizationLevel;
RunOptions() {
showTokens = false;
showAst = false;
showMeasurements = false;
lintMode = false;
beautifyMode = false;
optimizationLevel = 0;
}
}
} }

View File

@ -0,0 +1,55 @@
package com.annimon.ownlang;
import com.annimon.ownlang.util.input.InputSource;
import com.annimon.ownlang.util.input.InputSourceFile;
import com.annimon.ownlang.util.input.InputSourceProgram;
import com.annimon.ownlang.util.input.InputSourceResource;
import java.nio.file.Files;
import java.nio.file.Path;
public class RunOptions {
private static final String RESOURCE_PREFIX = "resource:";
private static final String DEFAULT_PROGRAM = "program.own";
// input
String programPath;
String programSource;
// modes
boolean lintMode;
boolean beautifyMode;
int optimizationLevel;
// flags
boolean showTokens;
boolean showAst;
boolean showMeasurements;
String detectDefaultProgramPath() {
if (getClass().getResource("/" + DEFAULT_PROGRAM) != null) {
return RESOURCE_PREFIX + "/" + DEFAULT_PROGRAM;
}
if (Files.isReadable(Path.of(DEFAULT_PROGRAM))) {
return DEFAULT_PROGRAM;
}
return null;
}
InputSource toInputSource() {
if (programSource != null) {
return new InputSourceProgram(programSource);
}
if (programPath == null) {
// No arguments. Default to program.own
programPath = detectDefaultProgramPath();
if (programPath == null) {
throw new IllegalArgumentException("Empty input");
}
}
if (programPath.startsWith(RESOURCE_PREFIX)) {
String path = programPath.substring(RESOURCE_PREFIX.length());
return new InputSourceResource(path);
} else {
return new InputSourceFile(programPath);
}
}
}

View File

@ -12,7 +12,9 @@ import com.annimon.ownlang.parser.error.ParseErrorsFormatterStage;
import com.annimon.ownlang.parser.optimization.OptimizationStage; import com.annimon.ownlang.parser.optimization.OptimizationStage;
import com.annimon.ownlang.parser.visitors.AbstractVisitor; import com.annimon.ownlang.parser.visitors.AbstractVisitor;
import com.annimon.ownlang.stages.*; import com.annimon.ownlang.stages.*;
import com.annimon.ownlang.util.SourceLoaderStage; import com.annimon.ownlang.util.input.InputSource;
import com.annimon.ownlang.util.input.InputSourceFile;
import com.annimon.ownlang.util.input.SourceLoaderStage;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
@ -24,11 +26,12 @@ import static org.junit.jupiter.api.Assertions.*;
public class ProgramsTest { public class ProgramsTest {
private static final String RES_DIR = "src/test/resources"; private static final String RES_DIR = "src/test/resources";
private static Stage<String, Statement> testPipeline; private static Stage<InputSource, Statement> testPipeline;
public static Stream<String> data() { public static Stream<InputSource> data() {
return scanDirectory(RES_DIR) return scanDirectory(RES_DIR)
.map(File::getPath); .map(File::getPath)
.map(InputSourceFile::new);
} }
@BeforeAll @BeforeAll
@ -85,16 +88,16 @@ public class ProgramsTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("data") @MethodSource("data")
public void testProgram(String programPath) { public void testProgram(InputSource inputSource) {
final StagesDataMap stagesData = new StagesDataMap(); final StagesDataMap stagesData = new StagesDataMap();
try { try {
testPipeline.perform(stagesData, programPath); testPipeline.perform(stagesData, inputSource);
} catch (OwnLangParserException ex) { } catch (OwnLangParserException ex) {
final var error = new ParseErrorsFormatterStage() final var error = new ParseErrorsFormatterStage()
.perform(stagesData, ex.getParseErrors()); .perform(stagesData, ex.getParseErrors());
fail(programPath + "\n" + error, ex); fail(inputSource + "\n" + error, ex);
} catch (Exception oae) { } catch (Exception oae) {
fail(programPath, oae); fail(inputSource.toString(), oae);
Console.handleException(stagesData, Thread.currentThread(), oae); Console.handleException(stagesData, Thread.currentThread(), oae);
} }
} }