diff --git a/.github/workflows/maven-publish.yml b/.github/workflows/maven-publish.yml index 7e5c3afb..a5336f59 100644 --- a/.github/workflows/maven-publish.yml +++ b/.github/workflows/maven-publish.yml @@ -18,10 +18,10 @@ jobs: contents: read steps: - uses: actions/checkout@v3 - - name: Set up JDK 8 + - name: Set up JDK 11 uses: actions/setup-java@v3 with: - java-version: '8' + java-version: '11' distribution: 'temurin' cache: maven - name: Run install phase @@ -30,4 +30,4 @@ jobs: uses: actions/upload-artifact@v4 with: name: aya.zip - path: target/aya-*.zip \ No newline at end of file + path: target/aya-*.zip diff --git a/.github/workflows/maven-test.yml b/.github/workflows/maven-test.yml index 563b5fc7..54973e4b 100644 --- a/.github/workflows/maven-test.yml +++ b/.github/workflows/maven-test.yml @@ -19,11 +19,11 @@ jobs: contents: read steps: - uses: actions/checkout@v3 - - name: Set up JDK 8 + - name: Set up JDK 11 uses: actions/setup-java@v3 with: - java-version: '8' + java-version: '11' distribution: 'temurin' cache: maven - name: Run test phase - run: mvn test --batch-mode \ No newline at end of file + run: mvn test --batch-mode diff --git a/build-scripts/package-ayastdlib-js-build.xml b/build-scripts/package-ayastdlib-js-build.xml new file mode 100644 index 00000000..97b1e155 --- /dev/null +++ b/build-scripts/package-ayastdlib-js-build.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/package_aya_libs.aya b/examples/package_aya_libs.aya new file mode 100644 index 00000000..ebeb8767 --- /dev/null +++ b/examples/package_aya_libs.aya @@ -0,0 +1,29 @@ +import ::json +import ::sys +import ::io + +.# All files in base/ and std/ +[ + "base" path! $ sys.readdir + ~ + "std" path! $ sys.readdir + ~ +] :files; + +.# Convert to strings +files #P :files; + +{,} :data; + +files :# {file, + file G data.:[file]; +}; + +data json.dumps :json_data; + +"aya-stdlib.js" :outfile; + +"const AYA_STDLIB = $json_data;" outfile 0 .G + +"Created file $outfile" :P + + + diff --git a/index.html b/index.html new file mode 100644 index 00000000..937ba5b4 --- /dev/null +++ b/index.html @@ -0,0 +1,58 @@ + + + + TeaVM example + + + + + + + + + + Press Shift+Enter to run +
+

+  
+
diff --git a/pom.xml b/pom.xml
index 403e18df..aa98819a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -9,11 +9,13 @@
     4.0.0
 
     
-        8
-        8
+        11
+        11
         UTF-8
     
 
+
+
     
         
             local-libs
@@ -23,7 +25,9 @@
 
     
         src
+
         
+
             
                 org.apache.maven.plugins
                 maven-dependency-plugin
@@ -60,49 +64,14 @@
                             
                         
                     
-                    
-                        release-zip
-                        verify
-                        
-                            run
-                        
-                        
-                            
-                                
-                                
-                                
-                            
-                        
-                    
                 
             
 
-            
-                org.apache.maven.plugins
-                maven-shade-plugin
-                3.5.0
-                
-                    
-                        package
-                        
-                            shade
-                        
-                        
-                            false
-                            
-                                
-                                    ui.AyaIDE
-                                
-                            
-                            false
-                        
-                    
-                
-            
         
     
 
     
+
         
             ch.obermuhlner
             big-math
@@ -128,6 +97,149 @@
             json
             20231013
         
+
+        
+            org.teavm
+            teavm-jso
+            0.10.2
+            true
+        
     
 
-
\ No newline at end of file
+
+    
+
+        
+        
+            desktop
+            
+                true
+            
+            
+                
+
+                    
+                    
+                        org.apache.maven.plugins
+                        maven-shade-plugin
+                        3.5.0
+
+                        
+                            
+                                package
+                                
+                                    shade
+                                
+                                
+                                    false
+                                    
+                                        
+                                            ui.AyaIDE
+                                        
+                                    
+                                    false
+
+                                    
+                                    
+                                        
+                                            org.teavm:teavm-classlib
+                                        
+                                    
+                                
+                            
+                        
+                    
+
+                    
+                    
+                        org.apache.maven.plugins
+                        maven-antrun-plugin
+                        3.1.0
+                        
+                            
+                                release-zip
+                                package
+                                
+                                    run
+                                
+                                
+                                    
+                                        
+                                        
+                                        
+                                    
+                                
+                            
+                        
+                    
+
+                
+            
+        
+
+        
+        
+            web
+            
+                
+
+                    
+                    
+                        org.teavm
+                        teavm-maven-plugin
+                        0.10.2
+                        
+                            
+                            
+                                org.teavm
+                                teavm-classlib
+                                0.10.2
+                            
+                        
+                        
+                            
+                                
+                                    compile
+                                
+                                process-classes
+                                
+                                    web.AyaWeb
+                                    true
+                                    true
+                                    true
+                                    aya.js
+                                
+                            
+                        
+                    
+
+                    
+                    
+                        org.apache.maven.plugins
+                        maven-antrun-plugin
+                        3.1.0
+                        
+                            
+                                package-ayastdlib-js
+                                package
+                                
+                                    run
+                                
+                                
+                                    
+                                        
+                                        
+                                    
+                                
+                            
+                        
+                    
+
+                
+            
+
+        
+    
+
+
+
diff --git a/src/aya/AyaPrefs.java b/src/aya/AyaPrefs.java
index d6379776..0f4d81d8 100644
--- a/src/aya/AyaPrefs.java
+++ b/src/aya/AyaPrefs.java
@@ -4,8 +4,10 @@
 import java.io.IOException;
 import java.net.URISyntaxException;
 import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.ArrayList;
 
+import aya.exceptions.runtime.IOError;
 import aya.obj.Obj;
 import aya.obj.character.Char;
 import aya.obj.list.Str;
@@ -100,15 +102,19 @@ public static boolean setAyaDir(String dir) {
 		}
 	}
 
-	public static ArrayList listFilesAndDirsForFolder(final File folder) {
-		File[] listOfFiles = folder.listFiles();
+	public static ArrayList listFilesAndDirsForFolder(final Path path) {
+		File[] listOfFiles = path.toFile().listFiles();
 		ArrayList fileList = new ArrayList();
-		for (File file : listOfFiles) {
-		    if (file.isFile()) {
-		        fileList.add(file.getName());
-		    } else if (file.isDirectory()) {
-		    	fileList.add(file.getName() + File.separator);
-		    }
+		if (listOfFiles == null) {
+			throw new IOError("AyaPrefs.listFilesAndDirsForFolder", path.toString(), "Unable to list files, path is invalid");
+		} else {
+			for (File file : listOfFiles) {
+			    if (file.isFile()) {
+			        fileList.add(file.getName());
+			    } else if (file.isDirectory()) {
+			    	fileList.add(file.getName() + File.separator);
+			    }
+			}
 		}
 		return fileList;
 	}
diff --git a/src/aya/AyaStdIO.java b/src/aya/AyaStdIO.java
index de650410..c6c52b7f 100644
--- a/src/aya/AyaStdIO.java
+++ b/src/aya/AyaStdIO.java
@@ -5,19 +5,19 @@
 import java.io.OutputStream;
 import java.io.PrintStream;
 import java.io.UnsupportedEncodingException;
-import java.util.Scanner;
 
-public class AyaStdIO {
+import aya.io.stdin.InputWrapper;
 
+public class AyaStdIO {
 	private PrintStream _out;
 	private PrintStream _err;
 	private InputStream _in;
-	private Scanner _scanner; 
+	private InputWrapper _input_wrapper;
 	
-	public AyaStdIO(PrintStream out, PrintStream err, InputStream in) {
+	public AyaStdIO(PrintStream out, PrintStream err, InputStream in, InputWrapper iw) {
 		_out = out;
 		_err = err;
-		setIn(in);
+		setIn(in, iw);
 	}
 	
 	public PrintStream out() {
@@ -32,12 +32,8 @@ public InputStream in() {
 		return _in;
 	}
 	
-	public Scanner scanner() {
-		return _scanner;
-	}
-	
 	public String nextLine() {
-		return _scanner.nextLine();
+		return _input_wrapper.nextLine();
 	}
 
 	/** Return true if input is ready to be read */
@@ -71,9 +67,13 @@ public void setErr(OutputStream os) {
 		}
 	}
 	
-	public void setIn(InputStream is) {
+	public void setIn(InputStream is, InputWrapper input_wrapper) {
 		_in = is;
-		_scanner = new Scanner(_in, "UTF-8");
+		_input_wrapper = input_wrapper;
+	}
+
+	public InputWrapper inputWrapper() {
+		return _input_wrapper;
 	}
 
 	
diff --git a/src/aya/InteractiveAya.java b/src/aya/InteractiveAya.java
index 750abd18..9e4db1b4 100644
--- a/src/aya/InteractiveAya.java
+++ b/src/aya/InteractiveAya.java
@@ -8,6 +8,9 @@
 
 import aya.eval.ExecutionContext;
 import aya.exceptions.parser.ParserException;
+import aya.io.fs.FilesystemIO;
+import aya.io.http.HTTPDownloader;
+import aya.io.stdin.ScannerInputWrapper;
 import aya.obj.Obj;
 import aya.obj.block.StaticBlock;
 import aya.parser.Parser;
@@ -165,7 +168,7 @@ public int loop() {
 		// Get Aya I/O
 		PrintStream out = _io().out();
 		PrintStream err = _io().err();		
-		Scanner scanner = _io().scanner();
+		Scanner scanner = ((ScannerInputWrapper)(_io().inputWrapper())).getScanner();
 		
  		_aya.start();
 		
@@ -285,8 +288,12 @@ public static InteractiveAya createInteractiveSession(String[] args) {
 	}
 	
 	public static void main(String[] args) {
-		InteractiveAya iaya = createInteractiveSession(args);
+		StaticData.IO = new AyaStdIO(System.out, System.err, System.in, new ScannerInputWrapper(System.in));
+		StaticData.HTTP_DOWNLOADER = new HTTPDownloader();
+		StaticData.FILESYSTEM = new FilesystemIO();
 
+		InteractiveAya iaya = createInteractiveSession(args);
+		
 		// argument[0] is always the working directory, check for args 1+
 		if (args.length > 1) {
 			if (args[1].equals("-i")) {
diff --git a/src/aya/StandaloneAya.java b/src/aya/StandaloneAya.java
new file mode 100644
index 00000000..f2ed253d
--- /dev/null
+++ b/src/aya/StandaloneAya.java
@@ -0,0 +1,119 @@
+package aya;
+
+import java.io.PrintStream;
+import java.util.ArrayList;
+
+import aya.eval.BlockEvaluator;
+import aya.eval.ExecutionContext;
+import aya.exceptions.parser.ParserException;
+import aya.exceptions.runtime.AyaRuntimeException;
+import aya.exceptions.runtime.ValueError;
+import aya.io.fs.FilesystemIO;
+import aya.io.http.HTTPDownloader;
+import aya.io.stdin.ScannerInputWrapper;
+import aya.obj.Obj;
+import aya.obj.block.StaticBlock;
+import aya.parser.Parser;
+import aya.parser.SourceString;
+
+public class StandaloneAya {
+	/**
+	 * A Bare-bones aya entry point that does not provide interactivity or preserve state
+	 */
+
+    public static void main(String[] args) {
+		StaticData.IO = new AyaStdIO(System.out, System.err, System.in, new ScannerInputWrapper(System.in));
+		StaticData.HTTP_DOWNLOADER = new HTTPDownloader();
+		StaticData.FILESYSTEM = new FilesystemIO();
+		
+		if (args.length == 1) {
+			runIsolated(args[0], StaticData.IO);
+		} else {
+			StaticData.IO.err().println("Please specify code to run as command line argument");
+		}
+    }
+    
+    // An extremely basic linter that catches syntax errors only
+    // Currently the parser will stop after the first error, may need to update later
+    public static ArrayList lint(String input) {
+    	ArrayList errors = new ArrayList();
+        try {
+        	Parser.compile(new SourceString(input, "
")); + } catch (ParserException err) { + errors.add(err); + } + return errors; + } + + public static void runIsolated(String input, AyaStdIO io) { + ExecutionContext ctx = ExecutionContext.createIsolatedContext(); + ctx.getVars().initGlobals(); + + // Create request + StaticBlock blk = Parser.compileSafeOrNull(new SourceString(input, "
"), io); + + if (blk != null) { + ExecutionRequest request = new ExecutionRequest(0, blk); + + // Create evaluator and run + BlockEvaluator b = ctx.createEvaluator(); + b.dump(request.getBlock()); + + ExecutionResult result = null; + try { + b.eval(); + result = new ExecutionResultSuccess(request.id(), b.getStack()); + } catch (AyaRuntimeException ex) { + result = new ExecutionResultException(request.id(), ex, ctx.getCallStack()); + } catch (Exception e) { + PrintStream err = StaticData.IO.err(); + err.println(DebugUtils.exToString(e)); + try { + if (b.hasOutputState()) + err.println("stack:\n\t" + b.getPrintOutputState()); + if (b.getInstructions().size() > 0) + err.println("just before:\n\t" + b.getInstructions().toString()); + if (!ctx.getCallStack().isEmpty()) + err.print(ctx.getCallStack().toString()); + } catch (Exception e2) { + err.println("An additional error was thrown when attempting to print the stack state:"); + err.println(DebugUtils.exToString(e2)); + err.println("This is likely caused by an error in an overloaded __str__ or __repr__ blockEvaluator."); + } + result = new ExecutionResultException(request.id(), new ValueError("TODO"), ctx.getCallStack()); + } finally { + ctx.getVars().reset(); + ctx.getCallStack().reset(); + } + + if (result != null) { + switch (result.getType()) { + case ExecutionResult.TYPE_SUCCESS: + { + ExecutionResultSuccess res = (ExecutionResultSuccess)result; + ArrayList data = res.getData(); + if (data.size() > 0) { + for (int i = 0; i < data.size(); i++) { + io.out().print(data.get(i)); + if (i < data.size() - 1) io.out().print(' '); + } + io.out().println(); + } + } + break; + case ExecutionResult.TYPE_EXCEPTION: + { + ExecutionResultException res = (ExecutionResultException)result; + res.ex().print(io.err()); + if (!res.callstack().equals("")) { + io.err().print(res.callstack()); + } + } + break; + } + } + } + + } +} + diff --git a/src/aya/StaticData.java b/src/aya/StaticData.java index 8ed0e323..d5dfe207 100644 --- a/src/aya/StaticData.java +++ b/src/aya/StaticData.java @@ -23,6 +23,10 @@ import aya.instruction.op.OpDocReader; import aya.instruction.op.Operator; import aya.instruction.op.Ops; +import aya.io.fs.AbstractFilesystemIO; +import aya.io.fs.UnimplementedFilesystemIO; +import aya.io.http.AbstractHTTPDownloader; +import aya.io.http.UnimplementedHTTPDownloader; import aya.parser.SpecialNumberParser; import aya.util.StringSearch; @@ -35,15 +39,16 @@ public class StaticData { public static final String QUIT = "\\Q"; - public static final AyaStdIO IO = new AyaStdIO(System.out, System.err, System.in); + // Must me initialized in main + public static AyaStdIO IO = null; + public static AbstractHTTPDownloader HTTP_DOWNLOADER = new UnimplementedHTTPDownloader(); + public static AbstractFilesystemIO FILESYSTEM = new UnimplementedFilesystemIO(); // // All calls to modify this data will need to be thread safe // private static StaticData _instance; - - // // Data loaded in the parser // @@ -73,6 +78,11 @@ public void init() { initNamedInstructions(); } + public void addNamedInstructionStore(NamedInstructionStore is) { + _namedInstructionStores.add(is); + is.initHelpData(this); + } + /////////////// // Help Data // @@ -148,8 +158,9 @@ private void initNamedInstructions() { for (NamedInstructionStore x : _namedInstructionStores) { x.initHelpData(this); } - } + + public NamedOperator getNamedInstruction(String name) { for (NamedInstructionStore x : _namedInstructionStores) { NamedOperator i = x.getInstruction(name); diff --git a/src/aya/eval/ExecutionContext.java b/src/aya/eval/ExecutionContext.java index 6d2b70c4..2e5fd707 100644 --- a/src/aya/eval/ExecutionContext.java +++ b/src/aya/eval/ExecutionContext.java @@ -2,6 +2,7 @@ import aya.AyaStdIO; import aya.CallStack; +import aya.StaticData; import aya.obj.dict.Dict; import aya.variable.VariableData; @@ -18,7 +19,8 @@ private ExecutionContext(AyaStdIO io) { } public static ExecutionContext createIsolatedContext() { - final ExecutionContext at = new ExecutionContext(new AyaStdIO(System.out, System.err, System.in)); + final ExecutionContext at = new ExecutionContext(StaticData.IO); + at.getVars().add(new Dict()); // Add empty globals return at; } diff --git a/src/aya/ext/fstream/FStreamManager.java b/src/aya/ext/fstream/FStreamManager.java index a54b516b..61290361 100644 --- a/src/aya/ext/fstream/FStreamManager.java +++ b/src/aya/ext/fstream/FStreamManager.java @@ -141,7 +141,23 @@ public static int read(int fileid) { return -1; } } - + + public static String readAll(String path) throws IOException { + File file = new File(path); + String output = null; + BufferedReader br = null; + try { + br = new BufferedReader(new FileReader(file)); + output = br.lines().collect(Collectors.joining("\n")); + br.close(); + } catch (IOException e) { + br.close(); + output = null; + } + + return output; + } + public static String readAll(int fileid) { BufferedReader f = _input_streams.get(fileid); if (f == null) return null; diff --git a/src/aya/ext/json/JSONUtils.java b/src/aya/ext/json/JSONUtils.java index 0223f8ef..3bce1c54 100644 --- a/src/aya/ext/json/JSONUtils.java +++ b/src/aya/ext/json/JSONUtils.java @@ -161,7 +161,7 @@ private static Object toJSON(Obj o, JSONParams params, VisitedChecker vc) { if (vc.hasVisited(e.second())) { throw new ValueError("JSON: Circular reference detected when serializing json object. key: " + e.first()); } else { - out.put(e.first().name(), toJSON(e.second(), params, vc)); + out.put(e.first().unquotedName(), toJSON(e.second(), params, vc)); } } vc.pop(d); diff --git a/src/aya/ext/sys/FileExistsSystemInstruction.java b/src/aya/ext/sys/FileExistsSystemInstruction.java new file mode 100644 index 00000000..9ecbdf17 --- /dev/null +++ b/src/aya/ext/sys/FileExistsSystemInstruction.java @@ -0,0 +1,22 @@ +package aya.ext.sys; + +import aya.eval.BlockEvaluator; +import aya.instruction.named.NamedOperator; +import aya.obj.Obj; +import aya.obj.number.Num; +import aya.util.FileUtils; + +public class FileExistsSystemInstruction extends NamedOperator { + + public FileExistsSystemInstruction() { + super("sys.file_exists"); + _doc = "test if the file exists"; + } + + @Override + public void execute(BlockEvaluator blockEvaluator) { + final Obj arg = blockEvaluator.pop(); + blockEvaluator.push(FileUtils.isFile(arg.str()) ? Num.ONE : Num.ZERO); + } + +} diff --git a/src/aya/ext/sys/SystemInstructionStore.java b/src/aya/ext/sys/SystemInstructionStore.java index 42fca420..b0728283 100644 --- a/src/aya/ext/sys/SystemInstructionStore.java +++ b/src/aya/ext/sys/SystemInstructionStore.java @@ -1,16 +1,16 @@ package aya.ext.sys; import java.io.File; +import java.nio.file.Path; import java.util.ArrayList; import aya.AyaPrefs; import aya.eval.BlockEvaluator; import aya.exceptions.runtime.ValueError; -import aya.instruction.named.NamedOperator; import aya.instruction.named.NamedInstructionStore; +import aya.instruction.named.NamedOperator; import aya.obj.Obj; import aya.obj.list.List; -import aya.obj.number.Num; import aya.util.FileUtils; public class SystemInstructionStore extends NamedInstructionStore { @@ -28,16 +28,13 @@ public void execute(BlockEvaluator blockEvaluator) { if (arg.isa(Obj.STR)) { String fstr = arg.str(); - try { - ArrayList dirs = AyaPrefs.listFilesAndDirsForFolder(FileUtils.resolveFile(fstr)); - ArrayList obj_dirs = new ArrayList(dirs.size()); - for (String s : dirs) { - obj_dirs.add(List.fromString(s)); - } - blockEvaluator.push(new List(obj_dirs)); - } catch (NullPointerException e) { - throw new ValueError(":{sys.readdir} : arg is not a valid location. Received:\n'" + fstr + "'"); + Path path = FileUtils.resolvePath(fstr); + ArrayList dirs = AyaPrefs.listFilesAndDirsForFolder(path); + ArrayList obj_dirs = new ArrayList(dirs.size()); + for (String s : dirs) { + obj_dirs.add(List.fromString(s)); } + blockEvaluator.push(new List(obj_dirs)); } else { throw new ValueError(":{sys.readdir} : arg must be a string. Received:\n" + arg.repr()); } @@ -143,14 +140,7 @@ public void execute(BlockEvaluator blockEvaluator) { } }); - // Test if file exists - addInstruction(new NamedOperator("sys.file_exists", "test if the file exists") { - @Override - public void execute(BlockEvaluator blockEvaluator) { - final Obj arg = blockEvaluator.pop(); - blockEvaluator.push(FileUtils.isFile(arg.str()) ? Num.ONE : Num.ZERO); - } - }); + addInstruction(new FileExistsSystemInstruction()); // Resolve home (replace ~/ with /path/to/home) addInstruction(new NamedOperator("sys.resolvehome", "replace ~/.. with /path/to/home/..") { diff --git a/src/aya/instruction/op/DotOps.java b/src/aya/instruction/op/DotOps.java index 1166e5b6..375b9396 100644 --- a/src/aya/instruction/op/DotOps.java +++ b/src/aya/instruction/op/DotOps.java @@ -19,7 +19,6 @@ import java.io.File; import java.io.IOException; -import java.nio.file.Files; import java.nio.file.InvalidPathException; import java.nio.file.StandardOpenOption; import java.util.ArrayList; @@ -40,7 +39,6 @@ import aya.exceptions.runtime.UnimplementedError; import aya.exceptions.runtime.UserObjRuntimeException; import aya.exceptions.runtime.ValueError; -import aya.ext.dialog.QuickDialog; import aya.instruction.DataInstruction; import aya.instruction.ListBuilderInstruction; import aya.obj.Obj; @@ -132,7 +130,7 @@ public class DotOps { /* 82 R */ new OP_Dot_R(), /* 83 S */ new OP_Dot_S(), /* 84 T */ new OP_Dot_T(), - /* 85 U */ new OP_RequestString(), + /* 85 U */ null, /* 86 V */ new OP_Dot_AppendBack(), /* 87 W */ null, /* 88 X */ null, @@ -1020,7 +1018,7 @@ public void execute(BlockEvaluator blockEvaluator) { if(option == 0) { try { - Files.write(file.toPath(), write.getBytes(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + StaticData.FILESYSTEM.write(file, write.getBytes(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); }catch (IOException e) { throw new IOError(".G", absFilePath, e); } catch (InvalidPathException ipe) { @@ -1030,7 +1028,7 @@ public void execute(BlockEvaluator blockEvaluator) { else if (option == 1) { try { - Files.write(file.toPath(), write.getBytes(), StandardOpenOption.CREATE, StandardOpenOption.APPEND); + StaticData.FILESYSTEM.write(file, write.getBytes(), StandardOpenOption.CREATE, StandardOpenOption.APPEND); }catch (IOException e) { throw new IOError(".G", absFilePath, e); } catch (InvalidPathException ipe) { @@ -1378,20 +1376,6 @@ public void execute (BlockEvaluator blockEvaluator) { } } -// U - 85 -class OP_RequestString extends Operator { - - public OP_RequestString() { - init(".U"); - arg("S", "requests a string using a ui dialog, S is the prompt text"); - } - - @Override - public void execute(BlockEvaluator blockEvaluator) { - blockEvaluator.push(List.fromString(QuickDialog.requestString(blockEvaluator.pop().str()))); - } -} - // V - 86 class OP_Dot_AppendBack extends Operator { diff --git a/src/aya/instruction/op/Ops.java b/src/aya/instruction/op/Ops.java index 902500ac..a5c78cbf 100644 --- a/src/aya/instruction/op/Ops.java +++ b/src/aya/instruction/op/Ops.java @@ -20,18 +20,17 @@ import java.io.File; import java.io.IOException; import java.math.BigDecimal; -import java.net.URL; import java.util.ArrayList; import java.util.Date; import java.util.Map.Entry; import java.util.Random; -import java.util.Scanner; import java.util.Stack; import java.util.regex.Matcher; import java.util.regex.Pattern; -import aya.eval.ExecutionContext; +import aya.StaticData; import aya.eval.BlockEvaluator; +import aya.eval.ExecutionContext; import aya.exceptions.parser.NotAnOperatorError; import aya.exceptions.parser.ParserException; import aya.exceptions.runtime.IOError; @@ -933,22 +932,12 @@ public void execute (final BlockEvaluator blockEvaluator) { String name = a.str(); if(Ops.PATTERN_URL.matcher(name).matches()) { - Scanner scnr = null; try { - URL url = new URL(name); - scnr = new Scanner(url.openStream()); - StringBuilder sb = new StringBuilder(); - - while(scnr.hasNext()) { - sb.append(scnr.nextLine()).append('\n'); - } - blockEvaluator.push(List.fromString(sb.toString())); + String downloaded = StaticData.HTTP_DOWNLOADER.downloadFile(name); + blockEvaluator.push(List.fromString(downloaded)); } catch(IOException ex) { throw new IOError("G", name, ex); - } finally { - if(scnr != null) - scnr.close(); } } else { File readFile = FileUtils.resolveFile(name); diff --git a/src/aya/io/StringOut.java b/src/aya/io/StringOut.java new file mode 100644 index 00000000..ad535e55 --- /dev/null +++ b/src/aya/io/StringOut.java @@ -0,0 +1,48 @@ +package aya.io; + +import java.io.ByteArrayOutputStream; +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; + +public class StringOut { + + public String ENCODING = StandardCharsets.UTF_8.name(); + + private ByteArrayOutputStream _out; + private ByteArrayOutputStream _err; + + public StringOut() { + _out = new ByteArrayOutputStream(); + _err = new ByteArrayOutputStream(); + } + + public ByteArrayOutputStream getOutStream() { + return _out; + } + + public ByteArrayOutputStream getErrStream() { + return _out; + } + + public String flushOut() { + try { + String s = _out.toString(ENCODING); + _out.reset(); + return s; + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + return e.getLocalizedMessage(); + } + } + + public String flushErr() { + try { + String s = _err.toString(ENCODING); + _err.reset(); + return s; + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + return e.getLocalizedMessage(); + } + } +} diff --git a/src/aya/io/fs/AbstractFilesystemIO.java b/src/aya/io/fs/AbstractFilesystemIO.java new file mode 100644 index 00000000..bc4b12d2 --- /dev/null +++ b/src/aya/io/fs/AbstractFilesystemIO.java @@ -0,0 +1,14 @@ +package aya.io.fs; + +import java.io.File; +import java.io.IOException; +import java.nio.file.StandardOpenOption; + +public abstract class AbstractFilesystemIO { + public abstract byte[] readAllBytes(File file) throws IOException; + + public abstract void write(File file, byte[] bytes, StandardOpenOption create, + StandardOpenOption truncateExisting) throws IOException; + + public abstract boolean isFile(File file); +} diff --git a/src/aya/io/fs/FilesystemIO.java b/src/aya/io/fs/FilesystemIO.java new file mode 100644 index 00000000..bc59d3a5 --- /dev/null +++ b/src/aya/io/fs/FilesystemIO.java @@ -0,0 +1,25 @@ +package aya.io.fs; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.StandardOpenOption; + +public class FilesystemIO extends AbstractFilesystemIO { + + @Override + public byte[] readAllBytes(File file) throws IOException { + return Files.readAllBytes(file.toPath()); + } + + @Override + public void write(File file, byte[] bytes, StandardOpenOption create, StandardOpenOption truncateExisting) throws IOException { + Files.write(file.toPath(), bytes, create, truncateExisting); + } + + @Override + public boolean isFile(File file) { + return file.isFile(); + } + +} diff --git a/src/aya/io/fs/UnimplementedFilesystemIO.java b/src/aya/io/fs/UnimplementedFilesystemIO.java new file mode 100644 index 00000000..966c5c28 --- /dev/null +++ b/src/aya/io/fs/UnimplementedFilesystemIO.java @@ -0,0 +1,27 @@ +package aya.io.fs; + +import java.io.File; +import java.io.IOException; +import java.nio.file.StandardOpenOption; + +import aya.exceptions.runtime.IOError; + +public class UnimplementedFilesystemIO extends AbstractFilesystemIO { + + @Override + public byte[] readAllBytes(File file) throws IOException { + throw new IOError("", "UnimplementedFilesystemReader.readAllBytes", "filesystem unavailable"); + } + + @Override + public void write(File file, byte[] bytes, StandardOpenOption create, StandardOpenOption truncateExisting) + throws IOException { + throw new IOError("", "UnimplementedFilesystemReader.write", "filesystem unavailable"); + } + + @Override + public boolean isFile(File file) { + throw new IOError("", "UnimplementedFilesystemReader.write", "filesystem unavailable"); + } + +} diff --git a/src/aya/io/http/AbstractHTTPDownloader.java b/src/aya/io/http/AbstractHTTPDownloader.java new file mode 100644 index 00000000..697652ab --- /dev/null +++ b/src/aya/io/http/AbstractHTTPDownloader.java @@ -0,0 +1,7 @@ +package aya.io.http; + +import java.io.IOException; + +public abstract class AbstractHTTPDownloader { + public abstract String downloadFile(String url) throws IOException; +} diff --git a/src/aya/io/http/HTTPDownloader.java b/src/aya/io/http/HTTPDownloader.java new file mode 100644 index 00000000..5fe3230a --- /dev/null +++ b/src/aya/io/http/HTTPDownloader.java @@ -0,0 +1,27 @@ +package aya.io.http; + +import java.io.IOException; +import java.net.URL; +import java.util.Scanner; + +public class HTTPDownloader extends AbstractHTTPDownloader { + + @Override + public String downloadFile(String url_str) throws IOException { + Scanner scanner = null; + try { + URL url = new URL(url_str); + scanner = new Scanner(url.openStream()); + StringBuilder sb = new StringBuilder(); + while(scanner.hasNext()) { + sb.append(scanner.nextLine()).append('\n'); + } + return sb.toString(); + } catch (IOException e) { + throw e; + } finally { + if (scanner != null) scanner.close(); + } + } + +} diff --git a/src/aya/io/http/UnimplementedHTTPDownloader.java b/src/aya/io/http/UnimplementedHTTPDownloader.java new file mode 100644 index 00000000..179c7973 --- /dev/null +++ b/src/aya/io/http/UnimplementedHTTPDownloader.java @@ -0,0 +1,14 @@ +package aya.io.http; + +import java.io.IOException; + +import aya.exceptions.runtime.IOError; + +public class UnimplementedHTTPDownloader extends AbstractHTTPDownloader { + + @Override + public String downloadFile(String url) throws IOException { + throw new IOError("", "UnimplementedHTTPDownloader.downloadFile", "downloadFile unimplemented"); + } + +} diff --git a/src/aya/io/stdin/EmptyInputWrapper.java b/src/aya/io/stdin/EmptyInputWrapper.java new file mode 100644 index 00000000..f33e70f4 --- /dev/null +++ b/src/aya/io/stdin/EmptyInputWrapper.java @@ -0,0 +1,12 @@ +package aya.io.stdin; + +import aya.exceptions.runtime.IOError; + +public class EmptyInputWrapper extends InputWrapper { + + @Override + public String nextLine() { + throw new IOError("", "EmptyInputWrapper.nextLine()", "Next line called!"); + } + +} diff --git a/src/aya/io/stdin/InputWrapper.java b/src/aya/io/stdin/InputWrapper.java new file mode 100644 index 00000000..24fdf352 --- /dev/null +++ b/src/aya/io/stdin/InputWrapper.java @@ -0,0 +1,5 @@ +package aya.io.stdin; + +public abstract class InputWrapper { + public abstract String nextLine(); +} diff --git a/src/aya/io/stdin/ScannerInputWrapper.java b/src/aya/io/stdin/ScannerInputWrapper.java new file mode 100644 index 00000000..3a707234 --- /dev/null +++ b/src/aya/io/stdin/ScannerInputWrapper.java @@ -0,0 +1,25 @@ +package aya.io.stdin; + +import java.io.InputStream; +import java.util.Scanner; + +public class ScannerInputWrapper extends InputWrapper { + private Scanner _scanner; + + public ScannerInputWrapper(Scanner scanner) { + _scanner = scanner; + } + + public ScannerInputWrapper(InputStream in) { + _scanner = new Scanner(in, "UTF-8"); + } + + @Override + public String nextLine() { + return _scanner.nextLine(); + } + + public Scanner getScanner() { + return _scanner; + } +} diff --git a/src/aya/obj/symbol/Symbol.java b/src/aya/obj/symbol/Symbol.java index 2f031cf4..8149867f 100644 --- a/src/aya/obj/symbol/Symbol.java +++ b/src/aya/obj/symbol/Symbol.java @@ -25,6 +25,10 @@ public String name() { } } + public String unquotedName() { + return SymbolTable.getName(this); + } + @Override public int hashCode() { return _id; diff --git a/src/aya/util/FileUtils.java b/src/aya/util/FileUtils.java index ee8255ad..222b9d53 100644 --- a/src/aya/util/FileUtils.java +++ b/src/aya/util/FileUtils.java @@ -3,18 +3,19 @@ import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; +import java.nio.file.Path; import aya.AyaPrefs; +import aya.StaticData; public class FileUtils { public static String readAllText(File file) throws IOException { - return new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8); // in Java 11 you can also do Files.readString(Path) + return new String(readAllBytes(file), StandardCharsets.UTF_8); // in Java 11 you can also do Files.readString(Path) } - - public static byte[] readAllBytes(File file) throws IOException { - return Files.readAllBytes(file.toPath()); + + public static byte[] readAllBytes(File file) throws IOException { + return StaticData.FILESYSTEM.readAllBytes(file); } /** @@ -25,9 +26,16 @@ public static File resolveFile(String pathName) { File file = new File(pathName); return file.isAbsolute() ? file : new File(AyaPrefs.getWorkingDir(), pathName); } + + /** See resolveFile(String) */ + public static Path resolvePath(String pathName) { + Path path = Path.of(pathName); + return path.isAbsolute() ? path : Path.of(AyaPrefs.getWorkingDir(), pathName); + + } public static boolean isFile(String str) { - return resolveFile(str).isFile(); + return StaticData.FILESYSTEM.isFile(resolveFile(str)); } public static String resolveHome(String path) { diff --git a/src/ui/AyaIDE.java b/src/ui/AyaIDE.java index 1aaccd58..dbb3163d 100644 --- a/src/ui/AyaIDE.java +++ b/src/ui/AyaIDE.java @@ -22,10 +22,14 @@ import javax.swing.JOptionPane; import javax.swing.JPanel; +import aya.AyaStdIO; import aya.AyaThread; import aya.ExecutionRequest; import aya.InteractiveAya; import aya.StaticData; +import aya.io.fs.FilesystemIO; +import aya.io.http.HTTPDownloader; +import aya.io.stdin.ScannerInputWrapper; import aya.obj.block.StaticBlock; import aya.parser.Parser; import aya.parser.SourceString; @@ -347,6 +351,11 @@ public InputStream getInputStream() { } public static void main(String[] args) { + // Set to System.in/out/err. If using the GUI, these will be changed to IDE GUI later + StaticData.IO = new AyaStdIO(System.out, System.err, System.in, new ScannerInputWrapper(System.in)); + StaticData.HTTP_DOWNLOADER = new HTTPDownloader(); + StaticData.FILESYSTEM = new FilesystemIO(); + InteractiveAya iaya = InteractiveAya.createInteractiveSession(args); boolean readstdin = StaticData.IO.isInputAvaiable(); @@ -363,7 +372,7 @@ public static void main(String[] args) { // Aya Prefs StaticData.IO.setOut(ide.getOutputStream()); StaticData.IO.setErr(ide.getOutputStream()); - StaticData.IO.setIn(ide.getInputStream()); + StaticData.IO.setIn(ide.getInputStream(), new ScannerInputWrapper(ide.getInputStream())); // InteractiveAya Prefs iaya.setPromptText(false); @@ -374,8 +383,8 @@ public static void main(String[] args) { } - int resultCode = iaya.loop(); - System.exit( resultCode ); + int resultCode = iaya.loop(); + System.exit(resultCode); } } diff --git a/src/web/AyaWeb.java b/src/web/AyaWeb.java new file mode 100644 index 00000000..c16bb18d --- /dev/null +++ b/src/web/AyaWeb.java @@ -0,0 +1,114 @@ +package web; + +import java.io.PrintStream; +import java.util.ArrayList; + +import org.teavm.jso.JSBody; +import org.teavm.jso.JSFunctor; +import org.teavm.jso.JSObject; + +import aya.AyaStdIO; +import aya.StandaloneAya; +import aya.StaticData; +import aya.exceptions.parser.ParserException; +import aya.ext.color.ColorInstructionStore; +import aya.ext.date.DateInstructionStore; +import aya.ext.json.JSONInstructionStore; +import aya.ext.la.LinearAlgebraInstructionStore; +import aya.io.StringOut; +import aya.io.stdin.EmptyInputWrapper; + +public class AyaWeb { + + private static final StringOut output = new StringOut(); + + public static void main(String[] args) { + + // + // Set up StaticData + // + StaticData sd = StaticData.getInstance(); + + StaticData.IO = new AyaStdIO( + new PrintStream(output.getOutStream()), + new PrintStream(output.getErrStream()), + null, + new EmptyInputWrapper()); + + WebFilesystemIO fs = new WebFilesystemIO(); + StaticData.FILESYSTEM = fs; + + // + // Named Instructions + // Web build only supports a limited set of named instructions + // + WebAvailableNamedInstructionStore wsi = new WebAvailableNamedInstructionStore(); + sd.addNamedInstructionStore(wsi); + sd.addNamedInstructionStore(new JSONInstructionStore()); + sd.addNamedInstructionStore(new DateInstructionStore()); + sd.addNamedInstructionStore(new ColorInstructionStore()); + sd.addNamedInstructionStore(new LinearAlgebraInstructionStore()); + + // + // Exported Functions Implementation + // + exportRunIsolated(s -> { + StandaloneAya.runIsolated(s, StaticData.IO); + return output.flushOut() + output.flushErr(); + }); + + exportAddFile((path, content) -> ((WebFilesystemIO)(StaticData.FILESYSTEM)).addFile(path, content)); + + exportListFiles(() -> String.join(",", fs.listFiles())); + + exportLint(source -> { + ArrayList errors = StandaloneAya.lint(source); + if (errors.size() > 0) { + // TODO: The compile function stops after the first error + // if we update the parser to catch multiple errors, we will need to update this + ParserException err = errors.get(0); + return err.getSource().getIndex() + ":" + err.getSimpleMessage(); + } else { + return ""; + } + }); + + } + + // + // Exported Functions + // + + @JSBody(params = "runIsolated", script = "main.runIsolated = runIsolated;") + private static native void exportRunIsolated(ExportFunctionRunIsolated fn); + + @JSBody(params = "addFile", script = "main.addFile = addFile;") + private static native void exportAddFile(ExportFunctionAddFile fn); + + @JSBody(params = "listFiles", script = "main.listFiles = listFiles;") + private static native void exportListFiles(ExportFunctionListFiles fn); + + @JSBody(params = "lint", script = "main.lint = lint;") + private static native void exportLint(ExportFunctionLint fn); + +} + +@JSFunctor +interface ExportFunctionRunIsolated extends JSObject { + String call(String s); +} + +@JSFunctor +interface ExportFunctionAddFile extends JSObject { + void call(String path, String content); +} + +@JSFunctor +interface ExportFunctionListFiles extends JSObject { + String call(); +} + +@JSFunctor +interface ExportFunctionLint extends JSObject { + String call(String source); +} \ No newline at end of file diff --git a/src/web/WebAvailableNamedInstructionStore.java b/src/web/WebAvailableNamedInstructionStore.java new file mode 100644 index 00000000..c5a1eb77 --- /dev/null +++ b/src/web/WebAvailableNamedInstructionStore.java @@ -0,0 +1,47 @@ +package web; + +import aya.eval.BlockEvaluator; +import aya.exceptions.runtime.UnimplementedError; +import aya.ext.sys.FileExistsSystemInstruction; +import aya.instruction.named.NamedInstructionStore; +import aya.instruction.named.NamedOperator; +import aya.obj.list.List; +import aya.obj.list.Str; + +public class WebAvailableNamedInstructionStore extends NamedInstructionStore { + /** + * This class provides some overrides for aya instructions so they work in the web implementation + * + * Only a small subset of instructions are supported + */ + + @Override + protected void init() { + + addInstruction(new FileExistsSystemInstruction()); + + addInstruction(new NamedOperator("sys.ad", "get absolute path of aya dir") { + @Override + public void execute(BlockEvaluator blockEvaluator) { + blockEvaluator.push(List.fromStr(Str.EMPTY)); + } + }); + + addInstruction(new NamedOperator("sys.wd", "get absolute path of working dir") { + @Override + public void execute(BlockEvaluator blockEvaluator) { + blockEvaluator.push(List.fromStr(Str.EMPTY)); + } + }); + + addInstruction(new NamedOperator("debug.pause", "pause execution and open a repl") { + @Override + public void execute(BlockEvaluator blockEvaluator) { + // Unimplemented + throw new UnimplementedError(); + } + }); + + } + +} diff --git a/src/web/WebFilesystemIO.java b/src/web/WebFilesystemIO.java new file mode 100644 index 00000000..305b6514 --- /dev/null +++ b/src/web/WebFilesystemIO.java @@ -0,0 +1,83 @@ +package web; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.HashMap; + +import aya.io.fs.AbstractFilesystemIO; + +public class WebFilesystemIO extends AbstractFilesystemIO { + /** + * A very basic virtual filesystem implementation + * This is primarily used to load the standard library in the web implementation + * + * Very few features outside of loading the standard library are supported + */ + + private static class FileData { + public byte[] data; + + public FileData(byte[] bytes) { + data = bytes; + } + + public static FileData fromString(String s) { + return new FileData(s.getBytes(StandardCharsets.UTF_8)); + } + } + + private HashMap _files; + + public WebFilesystemIO() { + _files = new HashMap(); + } + + public void addFile(String path, String content) { + _files.put(toPath(new File(path)), FileData.fromString(content)); + } + + private static String toPath(File f) { + String p = f.getAbsolutePath(); + if (!p.startsWith("/")) { + p = "/" + p; + } + return p; + } + + @Override + public byte[] readAllBytes(File file) throws IOException { + FileData data = _files.get(toPath(file)); + if (data == null) { + throw new IOException(); + } else { + return data.data; + } + } + + @Override + public void write(File file, byte[] bytes, StandardOpenOption create, StandardOpenOption truncateExisting) + throws IOException { + // TODO: open option and truncate option + _files.put(toPath(file), new FileData(bytes)); + } + + public ArrayList listFiles() { + ArrayList out = new ArrayList(); + for (String s : _files.keySet()) { + out.add(s); + } + return out; + } + + @Override + public boolean isFile(File file) { + String path = toPath(file); + String[] sections = path.split("/"); + String basename = sections[sections.length-1]; + return basename.contains(".") && _files.get(path) != null; + } + +}