mirror of
https://github.com/aNNiMON/Own-Programming-Language-Tutorial.git
synced 2024-09-20 00:34:20 +03:00
Add input sources and source loading stage
This commit is contained in:
parent
5dcf31c106
commit
c5810f0deb
@ -3,20 +3,21 @@ package com.annimon.ownlang.util;
|
||||
import com.annimon.ownlang.Console;
|
||||
import com.annimon.ownlang.stages.Stage;
|
||||
import com.annimon.ownlang.stages.StagesData;
|
||||
import com.annimon.ownlang.util.input.SourceLoaderStage;
|
||||
|
||||
public class ErrorsLocationFormatterStage implements Stage<Iterable<? extends SourceLocatedError>, String> {
|
||||
|
||||
@Override
|
||||
public String perform(StagesData stagesData, Iterable<? extends SourceLocatedError> input) {
|
||||
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");
|
||||
for (SourceLocatedError error : input) {
|
||||
sb.append(Console.newline());
|
||||
sb.append(error);
|
||||
sb.append(Console.newline());
|
||||
final Range range = error.getRange();
|
||||
if (range != null) {
|
||||
if (range != null && lines.length > 0) {
|
||||
printPosition(sb, range.normalize(), lines);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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";
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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.stages.Stage;
|
||||
import com.annimon.ownlang.stages.StagesData;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
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";
|
||||
|
||||
@Override
|
||||
public String perform(StagesData stagesData, String name) {
|
||||
public String perform(StagesData stagesData, InputSource inputSource) {
|
||||
try {
|
||||
String result = readSource(name);
|
||||
String result = inputSource.load();
|
||||
stagesData.put(TAG_SOURCE, result);
|
||||
return result;
|
||||
} catch (IOException e) {
|
||||
throw new OwnLangRuntimeException("Unable to read input " + name, 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);
|
||||
throw new OwnLangRuntimeException("Unable to read input " + inputSource, e);
|
||||
}
|
||||
}
|
||||
|
@ -56,4 +56,3 @@ tasks.register('runOptimizationDumper', JavaExec) {
|
||||
classpath = sourceSets.main.runtimeClasspath
|
||||
args '../program.own'
|
||||
}
|
||||
//
|
||||
|
@ -3,13 +3,13 @@ package com.annimon.ownlang;
|
||||
import com.annimon.ownlang.exceptions.OwnLangParserException;
|
||||
import com.annimon.ownlang.exceptions.StoppedException;
|
||||
import com.annimon.ownlang.parser.Beautifier;
|
||||
import com.annimon.ownlang.parser.SourceLoader;
|
||||
import com.annimon.ownlang.parser.Token;
|
||||
import com.annimon.ownlang.parser.ast.Statement;
|
||||
import com.annimon.ownlang.parser.error.ParseErrorsFormatterStage;
|
||||
import com.annimon.ownlang.parser.linters.LinterStage;
|
||||
import com.annimon.ownlang.parser.optimization.OptimizationStage;
|
||||
import com.annimon.ownlang.stages.*;
|
||||
import com.annimon.ownlang.util.input.SourceLoaderStage;
|
||||
import com.annimon.ownlang.utils.Repl;
|
||||
import com.annimon.ownlang.utils.Sandbox;
|
||||
import com.annimon.ownlang.utils.TimeMeasurement;
|
||||
@ -23,17 +23,12 @@ import java.util.concurrent.TimeUnit;
|
||||
public final class Main {
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
if (args.length == 0) {
|
||||
try {
|
||||
runDefault();
|
||||
} catch (IOException ioe) {
|
||||
printUsage();
|
||||
}
|
||||
final RunOptions options = new RunOptions();
|
||||
if (args.length == 0 && (options.detectDefaultProgramPath() == null)) {
|
||||
printUsage();
|
||||
return;
|
||||
}
|
||||
|
||||
final RunOptions options = new RunOptions();
|
||||
String input = null;
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
switch (args[i]) {
|
||||
case "-a":
|
||||
@ -82,73 +77,69 @@ public final class Main {
|
||||
|
||||
case "-f":
|
||||
case "--file":
|
||||
if (i + 1 < args.length) {
|
||||
input = SourceLoader.readSource(args[i + 1]);
|
||||
if (i + 1 < args.length) {
|
||||
options.programPath = args[i + 1];
|
||||
createOwnLangArgs(args, i + 2);
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
|
||||
case "--sandbox":
|
||||
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);
|
||||
Sandbox.main(createOwnLangArgs(args, i + 1));
|
||||
return;
|
||||
|
||||
default:
|
||||
if (input == null) {
|
||||
input = args[i];
|
||||
if (options.programSource == null) {
|
||||
options.programSource = args[i];
|
||||
createOwnLangArgs(args, i + 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (input == null) {
|
||||
throw new IllegalArgumentException("Empty input");
|
||||
}
|
||||
if (options.beautifyMode) {
|
||||
String input = new SourceLoaderStage()
|
||||
.perform(new StagesDataMap(), options.toInputSource());
|
||||
System.out.println(Beautifier.beautify(input));
|
||||
return;
|
||||
}
|
||||
run(input, options);
|
||||
}
|
||||
|
||||
private static void runDefault() throws IOException {
|
||||
final RunOptions options = new RunOptions();
|
||||
run(SourceLoader.readSource("program.own"), options);
|
||||
run(options);
|
||||
}
|
||||
|
||||
private static void printUsage() {
|
||||
System.out.println("OwnLang version " + Version.VERSION + "\n\n" +
|
||||
"Usage: ownlang [options]\n" +
|
||||
" options:\n" +
|
||||
" -f, --file [input] Run program file. Required.\n" +
|
||||
" -r, --repl Enter to a REPL mode\n" +
|
||||
" -l, --lint Find bugs in code\n" +
|
||||
" -o N, --optimize N Perform optimization with N passes\n" +
|
||||
" -b, --beautify Beautify source code\n" +
|
||||
" -a, --showast Show AST of program\n" +
|
||||
" -t, --showtokens Show lexical tokens\n" +
|
||||
" -m, --showtime Show elapsed time of parsing and execution");
|
||||
System.out.println("OwnLang version %s\n\n".formatted(Version.VERSION) + """
|
||||
Usage: ownlang [options]
|
||||
options:
|
||||
-f, --file [input] Run program file. Required.
|
||||
-r, --repl Enter to a REPL mode
|
||||
-l, --lint Find bugs in code
|
||||
-o N, --optimize N Perform optimization with N (0...9) passes
|
||||
-b, --beautify Beautify source code
|
||||
-a, --showast Show AST of program
|
||||
-t, --showtokens Show lexical tokens
|
||||
-m, --showtime Show elapsed time of parsing and execution
|
||||
""");
|
||||
}
|
||||
|
||||
private static void createOwnLangArgs(String[] javaArgs, int index) {
|
||||
if (index >= javaArgs.length) return;
|
||||
final String[] ownlangArgs = new String[javaArgs.length - index];
|
||||
System.arraycopy(javaArgs, index, ownlangArgs, 0, ownlangArgs.length);
|
||||
private static String[] createOwnLangArgs(String[] javaArgs, int index) {
|
||||
final String[] ownlangArgs;
|
||||
if (index >= javaArgs.length) {
|
||||
ownlangArgs = new String[0];
|
||||
} else {
|
||||
ownlangArgs = new String[javaArgs.length - index];
|
||||
System.arraycopy(javaArgs, index, ownlangArgs, 0, ownlangArgs.length);
|
||||
}
|
||||
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 scopedStages = new ScopedStageFactory(measurement::start, measurement::stop);
|
||||
|
||||
final var stagesData = new StagesDataMap();
|
||||
stagesData.put(SourceLoaderStage.TAG_SOURCE, input);
|
||||
try {
|
||||
scopedStages.create("Lexer", new LexerStage())
|
||||
scopedStages.create("Source loader", new SourceLoaderStage())
|
||||
.then(scopedStages.create("Lexer", new LexerStage()))
|
||||
.then(scopedStages.create("Parser", new ParserStage()))
|
||||
.thenConditional(options.optimizationLevel > 0,
|
||||
scopedStages.create("Optimization",
|
||||
@ -157,7 +148,7 @@ public final class Main {
|
||||
scopedStages.create("Linter", new LinterStage()))
|
||||
.then(scopedStages.create("Function adding", new FunctionAddingStage()))
|
||||
.then(scopedStages.create("Execution", new ExecutionStage()))
|
||||
.perform(stagesData, input);
|
||||
.perform(stagesData, options.toInputSource());
|
||||
} catch (OwnLangParserException ex) {
|
||||
final var error = new ParseErrorsFormatterStage()
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -12,7 +12,9 @@ import com.annimon.ownlang.parser.error.ParseErrorsFormatterStage;
|
||||
import com.annimon.ownlang.parser.optimization.OptimizationStage;
|
||||
import com.annimon.ownlang.parser.visitors.AbstractVisitor;
|
||||
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.BeforeEach;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
@ -24,11 +26,12 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class ProgramsTest {
|
||||
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)
|
||||
.map(File::getPath);
|
||||
.map(File::getPath)
|
||||
.map(InputSourceFile::new);
|
||||
}
|
||||
|
||||
@BeforeAll
|
||||
@ -85,16 +88,16 @@ public class ProgramsTest {
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("data")
|
||||
public void testProgram(String programPath) {
|
||||
public void testProgram(InputSource inputSource) {
|
||||
final StagesDataMap stagesData = new StagesDataMap();
|
||||
try {
|
||||
testPipeline.perform(stagesData, programPath);
|
||||
testPipeline.perform(stagesData, inputSource);
|
||||
} catch (OwnLangParserException ex) {
|
||||
final var error = new ParseErrorsFormatterStage()
|
||||
.perform(stagesData, ex.getParseErrors());
|
||||
fail(programPath + "\n" + error, ex);
|
||||
fail(inputSource + "\n" + error, ex);
|
||||
} catch (Exception oae) {
|
||||
fail(programPath, oae);
|
||||
fail(inputSource.toString(), oae);
|
||||
Console.handleException(stagesData, Thread.currentThread(), oae);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user