branch: elpa/geiser-kawa commit 86ef157ea976bce407e758850a7291aa0b574841 Author: spellcard199 <spellcard...@protonmail.com> Commit: spellcard199 <spellcard...@protonmail.com>
Join projects: kawa-geiser is now part of geiser-kawa --- elisp/geiser-kawa.el | 2 +- example-mvn-run-geiser.sh | 14 + pom.xml | 21 +- src/main/java/kawageiser/Geiser.java | 32 +++ src/main/java/kawageiser/GeiserAutodoc.java | 307 +++++++++++++++++++++ src/main/java/kawageiser/GeiserCompleteModule.java | 86 ++++++ src/main/java/kawageiser/GeiserCompleteSymbol.java | 70 +++++ src/main/java/kawageiser/GeiserEval.java | 109 ++++++++ src/main/java/kawageiser/GeiserLoadFile.java | 39 +++ src/main/java/kawageiser/GeiserNoValues.java | 24 ++ .../kawageiser/StartKawaWithGeiserSupport.java | 53 ++++ .../geiserDoc/ManualEpubUnzipToTmpDir.java | 42 +++ src/main/java/kawageiser/java/Complete.java | 99 +++++++ src/test/java/kawageiser/GeiserAutodocTest.java | 88 ++++++ 14 files changed, 982 insertions(+), 4 deletions(-) diff --git a/elisp/geiser-kawa.el b/elisp/geiser-kawa.el index 5bf5435..be5492f 100644 --- a/elisp/geiser-kawa.el +++ b/elisp/geiser-kawa.el @@ -86,7 +86,7 @@ ;; jar containing all the java dependencies. (defcustom geiser-kawa-kawa-geiser-jar-path (expand-file-name - "./target/kawa-geiser-wrapper-0.1-SNAPSHOT-jar-with-dependencies.jar" + "./target/kawa-geiser-0.1-SNAPSHOT-jar-with-dependencies.jar" geiser-kawa-dir) "Path to the kawa-geiser fat jar." :type 'string diff --git a/example-mvn-run-geiser.sh b/example-mvn-run-geiser.sh new file mode 100644 index 0000000..f0cabc6 --- /dev/null +++ b/example-mvn-run-geiser.sh @@ -0,0 +1,14 @@ +#!/bin/env sh + +# This file is not used by geiser-kawa. It's just to show that you can +# start a Kawa repl with geiser support using the Kawa version that is +# included in the maven dependencies of kawa-geiser. + +# valid exec.args are either: +# - a port number: starts a Kawa telnet server listening on that port +# - --no-server: starts a Kawa repl in current terminal + +mvn compile && + mvn exec:java \ + -D"exec.mainClass"="kawageiser.StartKawaWithGeiserSupport" \ + -D"exec.args"=$@ diff --git a/pom.xml b/pom.xml index 32e215e..71bbc1a 100644 --- a/pom.xml +++ b/pom.xml @@ -1,11 +1,16 @@ <?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2020 spellcard199 <spellcard...@protonmail.com> + ~ This is free software; for terms and warranty disclaimer see ./COPYING. + --> + <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.gitlab.spellcard199</groupId> - <artifactId>kawa-geiser-wrapper</artifactId> + <artifactId>kawa-geiser</artifactId> <version>0.1-SNAPSHOT</version> <build> @@ -52,11 +57,21 @@ </repositories> <dependencies> + <dependency> <groupId>com.gitlab.spellcard199</groupId> - <artifactId>kawa-geiser</artifactId> - <version>3e7d104a58457e9710f57b1a9b79ea8b48467b5c</version> + <artifactId>kawa-devutil</artifactId> + <version>0c12c104505e4965308b9214486f2051af152360</version> + </dependency> + + <!-- https://mvnrepository.com/artifact/org.testng/testng --> + <dependency> + <groupId>org.testng</groupId> + <artifactId>testng</artifactId> + <version>7.0.0</version> + <scope>test</scope> </dependency> + </dependencies> </project> diff --git a/src/main/java/kawageiser/Geiser.java b/src/main/java/kawageiser/Geiser.java new file mode 100644 index 0000000..40b7e18 --- /dev/null +++ b/src/main/java/kawageiser/Geiser.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2020 spellcard199 <spellcard...@protonmail.com> + * This is free software; for terms and warranty disclaimer see ./COPYING. + */ + +package kawageiser; + +import gnu.expr.Language; +import kawageiser.geiserDoc.ManualEpubUnzipToTmpDir; + +public class Geiser implements Runnable { + private static boolean prettyPrintResult = true; + public static boolean isPrettyPrintResult() { return prettyPrintResult; } + public static boolean isPrettyPrintOutput() { return evaluator.isPrettyPrintOutput(); } + public static void setPrettyPrintResult(boolean v) { prettyPrintResult = v; } + public static void setPrettyPrintOutput(boolean v) { evaluator.setPrettyPrintOutput(v); } + + public static kawadevutil.eval.Eval evaluator = new kawadevutil.eval.Eval(); + + @Override + public void run() { + Language lang = Language.getDefaultLanguage(); + lang.defineFunction(new GeiserEval("geiser:eval")); + lang.defineFunction(new GeiserNoValues("geiser:no-values")); + lang.defineFunction(new GeiserLoadFile("geiser:load-file")); + lang.defineFunction(new GeiserCompleteSymbol("geiser:completions")); + lang.defineFunction(new GeiserCompleteModule("geiser:module-completions")); + lang.defineFunction(new GeiserAutodoc("geiser:autodoc", lang)); + lang.defineFunction(new kawageiser.java.Complete("geiser:complete-java")); + lang.defineFunction(new ManualEpubUnzipToTmpDir("geiser:manual-epub-unzip-to-tmp-dir")); + } +} diff --git a/src/main/java/kawageiser/GeiserAutodoc.java b/src/main/java/kawageiser/GeiserAutodoc.java new file mode 100644 index 0000000..747bbd2 --- /dev/null +++ b/src/main/java/kawageiser/GeiserAutodoc.java @@ -0,0 +1,307 @@ +/* + * Copyright (C) 2020 spellcard199 <spellcard...@protonmail.com> + * This is free software; for terms and warranty disclaimer see ./COPYING. + */ + +package kawageiser; + +import gnu.expr.CompiledProc; +import gnu.expr.Language; +import gnu.kawa.functions.Format; +import gnu.lists.LList; +import gnu.mapping.*; +import kawadevutil.data.ParamData; +import kawadevutil.data.ProcDataGeneric; +import kawadevutil.data.ProcDataNonGeneric; +import kawadevutil.kawa.GnuMappingLocation; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class GeiserAutodoc extends Procedure1or2 { + + public static boolean showTypes = true; + Language lang; + + GeiserAutodoc(String name, Language lang) { + super(name); + this.lang = lang; + } + + // TODO: find the "right" way to get modules for symbols. + // TODO: support for procedures defined in java, like `append' + // TODO: support for macros (possible?) + // TODO: support for getting parameter names for java instance + // methods getMethods bytecode with ClassType (not so simple) + // TODO: support names with special characters, like |a[)| + // Maybe we can: + // 1. keep a list of special chars + // 2. when `id' contains one: surround with || (e.g. |id|) + // TODO: consider multiple ids: + // - Examples: to get more add (message (format "(geiser:%s %s)" proc form)) + // in the t clause of the geiser-kawa--geiser-procedure: + // - (display [cursor] -> (geiser:autodoc ’(display)) + // - (display (symbol? (string->symbol [cursor] -> (geiser:autodoc ’(string->symbol symbol? display)) + + + // At the moment arguments are enclosed in double quotes. The + // reason is that geiser's output is `read' by elisp, but java types + // may contain characters that are not valid in elisp symbols + // (e.g. java arrays contain square brackets). So instead of symbols + // we use strings. When type annotations are disabled using + // (geiser:set-autodoc-show-types #f) parameter names displayed as + // symbols (without double quotes around them). + + // List<Object> idsArr = List.getLocalVarsAttr(ids.toArray()); + // idsArr.stream().map( (Object symId, Object env) -> { + // return (new SymToAutodoc()).apply2(symId, env); + // }) + + // @Override + public Object apply1(Object ids) { + return apply2(ids, Language.getDefaultLanguage().getEnvironment()); + } + + // @Override + public Object apply2(Object ids, Object env) { + + if (!LList.class.isAssignableFrom(ids.getClass())) { + throw new IllegalArgumentException(String.format( + "GeiserAutodoc's 1st arg should be a gnu.lists.LList")); + } + if (!Environment.class.isAssignableFrom(env.getClass())) { + throw new IllegalArgumentException(String.format( + "GeiserAutodoc's 2nd arg should be a gnu.mapping.Environment")); + } + try { + ArrayList<Object> autodocList = new ArrayList<>(); + for (Object symId : (LList) ids) { + AutodocDataForSymId autodocDataForSymId = + new AutodocDataForSymId((Symbol) symId, (Environment) env, this.lang); + autodocList.add(autodocDataForSymId.toLList()); + } + String formattedAutodoc = + Format + .format("~S", LList.makeList(autodocList)) + .toString(); + return formattedAutodoc; + } catch (Throwable throwable) { + throwable.printStackTrace(); + return throwable; + } + } + + public static class OperatorArgListData { + ProcDataGeneric procDataGeneric; + + public OperatorArgListData(ProcDataGeneric procDataGeneric) { + this.procDataGeneric = procDataGeneric; + } + + private static String + formatParam(ParamData param, String formatting) { + // This method is just to reduce boilerplate in the other `formatParam' + String paramName = param.getName(); + String formattedParamType = + Format + .format("::~a", + param.getType().getReflectClass().getName()) + .toString(); + return Format.format( + formatting, + paramName, + formattedParamType).toString(); + } + + public static String + formatParam(ParamData param, + boolean isOptionalParam) { + if (isOptionalParam && showTypes) { + return formatParam(param, "(~a~a)"); + } else if (isOptionalParam && !showTypes) { + return formatParam(param, "(~a)"); + } else if (!isOptionalParam && showTypes) { + return formatParam(param, "~a~a"); + } else if (!isOptionalParam && !showTypes) { + return formatParam(param, "~a"); + } else { + throw new Error("No worries, can't happen (2 booleans == 4 possibilities)." + + "Just silencing the \"Missing return statement\" error."); + } + } + + public LList paramListToFormattedParamLList(List<ParamData> params, boolean areOptionalParams) { + List<String> formattedParamList = new ArrayList<>(); + for (ParamData req : params) { + String s = formatParam(req, areOptionalParams); + formattedParamList.add(s); + } + return LList.makeList(formattedParamList); + } + + public LList toLList() { + ArrayList<Object> genericProcArgList = new ArrayList<>(); + + for (ProcDataNonGeneric pd : this.procDataGeneric.getProcDataNonGenericList()) { + ArrayList<Object> nonGenericProcArgList = new ArrayList<>(); + ArrayList<Object> requiredParamList = new ArrayList<>(); + ArrayList<Object> optionalParamList = new ArrayList<>(); + + List<ParamData> requiredParams = pd.getRequiredParams(); + + if (!requiredParams.isEmpty()) { + LList requiredParamLList = paramListToFormattedParamLList(requiredParams, false); + // argList.add(LList.list2("required", requiredParamLList)); + requiredParamList.add("required"); + requiredParamList.addAll(requiredParamLList); + } + + List<ParamData> optionalParams = pd.getOptionalParams(); + Optional<ParamData> restParamMaybe = pd.getRestParam(); + if (optionalParams.size() > 0 || restParamMaybe.isPresent()) { + LList optionalOrRestParamLList = + optionalParams.size() > 0 + ? paramListToFormattedParamLList(optionalParams, true) + : LList.makeList(java.util.Collections.emptyList()); + if (restParamMaybe.isPresent()) { + optionalOrRestParamLList.add( + Format.format( + "(... ~a...)", + formatParam(restParamMaybe.get(), false) + ) + ); + } + optionalParamList.add("optional"); + optionalParamList.addAll(optionalOrRestParamLList); + } + + if (!requiredParamList.isEmpty()) { + nonGenericProcArgList.add(LList.makeList(requiredParamList)); + } + if (!optionalParamList.isEmpty()) { + nonGenericProcArgList.add(LList.makeList(optionalParamList)); + } + genericProcArgList.add(LList.makeList(nonGenericProcArgList)); + } + + return LList.makeList(genericProcArgList); + } + } + + public static class AutodocDataForSymId { + private boolean symExists; + private Symbol symId; + private Object operator; + private Environment environment; + private Optional<OperatorArgListData> operatorArgListMaybe; + // TODO: fix type, write way to get it + private Object module; + + + public AutodocDataForSymId(Symbol symId, Environment env, Language lang) { + this.symId = symId; + this.environment = env; + + Optional<OperatorArgListData> operatorArgListMaybe = Optional.empty(); + Object operator = null; + boolean symExists = false; + try { + operator = lang.eval(symId.toString()); + symExists = true; // If it didn't exist env.get(symId) would have raised UnboundLocationException + if (!Procedure.class.isAssignableFrom(operator.getClass())) { + // Not a procedure + // TODO : is it possible to implement autodoc for macros? + // If not: write a comment why. + operatorArgListMaybe = Optional.empty(); + } else { + ProcDataGeneric procDataGeneric = ProcDataGeneric.makeForProcedure((Procedure) operator); + operatorArgListMaybe = Optional.of(new OperatorArgListData(procDataGeneric)); + } + } catch (Throwable throwable) { + throwable.printStackTrace(); + } + + this.operatorArgListMaybe = operatorArgListMaybe; + this.operator = operator; + this.symExists = symExists; + } + + public LList toLList() { + + ArrayList<Object> operatorArgListAsList = new ArrayList<>(); + operatorArgListAsList.add("args"); + if (operatorArgListMaybe.isPresent()) { + operatorArgListAsList.addAll(operatorArgListMaybe.get().toLList()); + } else { + operatorArgListAsList.add(false); + } + LList operatorArgListAsLList = LList.makeList(operatorArgListAsList); + + // TODO: write a procedure that gets the module getMethods + // which a symbol comes getMethods using "the right way" (is there one?) + // TODO: When we find the correct way to do it, refactor moduleValue inside + // ProcDataNonGeneric or a generic wrapper for Procedure data + LList moduleValue = null; + if (operator.getClass() == CompiledProc.class) { + CompiledProc compProc = (CompiledProc) operator; + moduleValue = LList.makeList( + java.util.Arrays + .asList(compProc + .getModuleClass() + .getName() + .split("\\."))); + } else { + try { + // If it's not a CompiledProc it does not have a + // `getModule' method: fallback to trying to figure + // out getMethods GnuMappingLocation in Environment. + // TODO: generalize to arbitrary environment + moduleValue = (LList) kawa.lib.ports.read( + new gnu.kawa.io.CharArrayInPort( + GnuMappingLocation.baseLocationToModuleName( + environment.lookup(symId).getBase() + ) + ) + ); + } catch (NullPointerException e) { + // If it is not even a sym in the environment, give up. + // TODO: should we consider all java classes as modules? + moduleValue = LList.makeList(new ArrayList()); + } + } + + // If you don't convert your symbol to String it may not match with + // the string seen from the emacs side and when that happens geiser + // does not considers that a valid autodoc response. + // Example: as a Symbol, java.lang.String:format is displayed by + // kawa as: + // java.lang.String{$unknown$}:format + // which does not match: + // java.lang.String:format + // so geiser ignores it. + String symIdAsStr = symId.toString(); + LList returnMe; + if (moduleValue.size() > 0) { + ArrayList<Object> moduleList = new ArrayList<>(); + moduleList.add("module"); + for (Object m : moduleValue) { + moduleList.add(Symbol.valueOf(m.toString())); + } + returnMe = LList.list3( + symIdAsStr, + operatorArgListAsLList, + LList.makeList(moduleList) + ); + } else { + returnMe = LList.list2( + symIdAsStr, + operatorArgListAsLList + ); + } + + return returnMe; + } + } +} + diff --git a/src/main/java/kawageiser/GeiserCompleteModule.java b/src/main/java/kawageiser/GeiserCompleteModule.java new file mode 100644 index 0000000..fe00508 --- /dev/null +++ b/src/main/java/kawageiser/GeiserCompleteModule.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2020 spellcard199 <spellcard...@protonmail.com> + * This is free software; for terms and warranty disclaimer see ./COPYING. + */ + +package kawageiser; + +import gnu.expr.Language; +import gnu.lists.IString; +import gnu.lists.LList; +import gnu.mapping.Environment; +import gnu.mapping.Procedure1or2; +import kawadevutil.kawa.GnuMappingLocation; + +import java.util.ArrayList; + + +public class GeiserCompleteModule extends Procedure1or2 { + + GeiserCompleteModule(String name) { + super(name); + } + + @Override + public Object apply1(Object prefix) throws Throwable { + return apply2( + prefix, + Language.getDefaultLanguage().getEnvironment()); + } + + @Override + public Object apply2(Object prefix, Object env) throws Throwable { + + String prefixStr = null; + if (prefix instanceof String) { + prefixStr = (String) prefix; + } else if (prefix instanceof IString) { + prefixStr = prefix.toString(); + } else { + throw new IllegalArgumentException( + "`prefix' arg should be either a String or an IString"); + } + + Environment castedEnv; + if (Environment.class.isAssignableFrom(env.getClass())) { + castedEnv = (Environment) env; + } else { + throw new IllegalArgumentException( + "`env' arg should be an gnu.mapping.Environment"); + } + + ArrayList<String> moduleCompletions = getCompletions(prefixStr, castedEnv); + // Geiser protocol wants modules in the result to be printed + // between double quotes + // ("(... ... ...)" "(... ...)") + // Kawa repl doesn't show returned strings with surrounding + // quotes, so we have to manually surround completions. + return gnu.kawa.functions.Format.format("~S", LList.makeList(moduleCompletions)); + } + + private ArrayList<String> getCompletions(String prefix, Environment env) { + + ArrayList<String> moduleCompletions = new ArrayList<>(); + + // Since this procedure works iterating over locations in the + // (interaction-environment), if a module does not export any + // symbol it won't appear in the result. + // TODO: this is an hack. If it exists, find a way to list + // modules directly. + env.enumerateAllLocations().forEachRemaining( + loc -> + { + String moduleStrRepr = GnuMappingLocation + .baseLocationToModuleName(loc.getBase()); + if ((!moduleCompletions.contains(moduleStrRepr)) + && (!(moduleStrRepr.equals(""))) + && (moduleStrRepr.startsWith(prefix)) + ) { + moduleCompletions.add(moduleStrRepr); + } + } + ); + + return moduleCompletions; + } +} diff --git a/src/main/java/kawageiser/GeiserCompleteSymbol.java b/src/main/java/kawageiser/GeiserCompleteSymbol.java new file mode 100644 index 0000000..5dda5c7 --- /dev/null +++ b/src/main/java/kawageiser/GeiserCompleteSymbol.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2020 spellcard199 <spellcard...@protonmail.com> + * This is free software; for terms and warranty disclaimer see ./COPYING. + */ + +package kawageiser; + +import gnu.expr.Language; +import gnu.lists.IString; +import gnu.lists.LList; +import gnu.mapping.Environment; +import gnu.mapping.Procedure1or2; +import gnu.mapping.Symbol; + +import java.util.ArrayList; + +public class GeiserCompleteSymbol extends Procedure1or2 { + + GeiserCompleteSymbol(String name) { + super(name); + } + + @Override + public Object apply1(Object prefix) { + return apply2( + prefix, + Language.getDefaultLanguage().getEnvironment()); + } + + @Override + public Object apply2(Object prefix, Object module) { + + String prefixStr = null; + if (prefix instanceof String) { + prefixStr = (String) prefix; + } else if (prefix instanceof IString) { + prefixStr = prefix.toString(); + } else { + throw new IllegalArgumentException( + "prefix arg should be either String or IString"); + } + + Environment env = null; + if (Environment.class.isAssignableFrom(module.getClass())) { + // already an Environment + env = (Environment) module; + } else if (kawa.lib.lists.isList(module)) { + env = kawa.lib.scheme.eval.environment$V(((LList) module)); + } else { + throw new IllegalArgumentException( + "module argument should be either a proper list or an Environment."); + } + + return getCompletions(prefixStr, env); + } + + private LList getCompletions(String prefix, Environment env) { + ArrayList<Symbol> resultArrList = new ArrayList<>(); + env.enumerateAllLocations().forEachRemaining( + loc -> { + Symbol sym = loc.getKeySymbol(); + String symName = sym.getName(); + if (symName.contains(prefix)) { + resultArrList.add(sym); + } + } + ); + return LList.makeList(resultArrList); + } +} diff --git a/src/main/java/kawageiser/GeiserEval.java b/src/main/java/kawageiser/GeiserEval.java new file mode 100644 index 0000000..906b41a --- /dev/null +++ b/src/main/java/kawageiser/GeiserEval.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2020 spellcard199 <spellcard...@protonmail.com> + * This is free software; for terms and warranty disclaimer see ./COPYING. + */ + +package kawageiser; + +import gnu.kawa.functions.Format; +import gnu.lists.IString; +import gnu.lists.LList; +import gnu.lists.Pair; +import gnu.mapping.Environment; +import gnu.mapping.Procedure2; +import gnu.mapping.Symbol; +import kawadevutil.eval.EvalResult; +import kawadevutil.eval.EvalResultAndOutput; +import kawadevutil.redirect.RedirectedOutErr; + +public class GeiserEval extends Procedure2 { + /* + * Actual evaluation happens in kawadevtools.eval.Eval. + * Here we are just sending arguments and converting our own + * types into the geiser protocol. + */ + + GeiserEval(String procName) { + super(procName); + } + + @Override + public String + apply2(Object module, Object codeStr) { + // Today (2019-12-9) Kawa has still that issue when + // quoting (this) followed by a double colon. So, to avoid + // it altogether, geiser:eval default is to accept Strings + // instead of sexprs. + // You can still evaluate expressions instead of strings using + // the other GeiserEval:eval method explicitly. + String code; + if (codeStr instanceof IString) { + code = ((IString) codeStr).toString(); + } else if (codeStr instanceof String) { + code = (String) codeStr; + } else { + throw new IllegalArgumentException( + "`codeStr' arg should be either a String or an IString"); + } + return eval((Environment) module, code); + } + + public static String + eval(Environment module, String codeStr) { + EvalResultAndOutput resOutErr = Geiser.evaluator.evalCatchingOutErr(module, codeStr); + return formatGeiserProtocol(evaluationDataToGeiserProtocol(resOutErr)); + } + + public static String + eval(Environment module, Object sexpr) { + EvalResultAndOutput resOutErr = Geiser.evaluator.evalCatchingOutErr(module, sexpr); + return formatGeiserProtocol(evaluationDataToGeiserProtocol(resOutErr)); + } + + public static LList + evaluationDataToGeiserProtocol(EvalResultAndOutput resOutErr) { + EvalResult evalRes = resOutErr.getResultOfSupplier(); + RedirectedOutErr outErr = resOutErr.getOutErr(); + + // result + String geiserResult = evalRes.isSuccess() + ? evalRes.getResultAsString(Geiser.isPrettyPrintResult()) + : ""; + + // output + String messages = (evalRes.getMessages() != null) + ? evalRes.getMessages().toString(100000) + : ""; + messages = (messages != null) ? messages : ""; + String stackTrace = (evalRes.getThrowed() != null) + ? Geiser.evaluator.formatStackTrace(evalRes.getThrowed()) + : ""; + String output = outErr.getOutAndErrInPrintOrder(); + // If we wanted, we could include stack traces directly in + // the output using Eval.setPrintStackTrace(): that would + // display stack traces of exceptions after the output + // produced by the code instead of before. + // Since the Kawa repl prints in order: messages, stacktrace, + // output, we are doing the same. + String geiserOutput = messages + stackTrace + output; + + return evaluationDataToGeiserProtocol( + evalRes.isSuccess(), geiserResult, geiserOutput); + } + + private static LList + evaluationDataToGeiserProtocol + (boolean isSuccess, String geiserResult, String geiserOutput) { + LList geiserResOrErr = + isSuccess + ? LList.list2(Symbol.valueOf("result"), geiserResult) + : LList.list2(Symbol.valueOf("error"), ""); + Pair geiserOut = Pair.make(Symbol.valueOf("output"), geiserOutput); + return LList.list2(geiserResOrErr, geiserOut); + } + + public static String formatGeiserProtocol(LList geiserAnswer) { + return (String) Format.format("~S", geiserAnswer); + } + +} diff --git a/src/main/java/kawageiser/GeiserLoadFile.java b/src/main/java/kawageiser/GeiserLoadFile.java new file mode 100644 index 0000000..6fa7763 --- /dev/null +++ b/src/main/java/kawageiser/GeiserLoadFile.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2020 spellcard199 <spellcard...@protonmail.com> + * This is free software; for terms and warranty disclaimer see ./COPYING. + */ + +package kawageiser; + +import gnu.expr.Language; +import gnu.lists.IString; +import gnu.lists.LList; +import gnu.mapping.Procedure1; +import kawa.standard.load; + +public class GeiserLoadFile extends Procedure1 { + + GeiserLoadFile(String name) { + super(name); + } + + @Override + public Object apply1(Object o) throws Throwable { + String filepath; + if (o instanceof String) { + filepath = (String) o; + } else if (o instanceof IString) { + filepath = ((IString) o).toString(); + } else { + throw new IllegalArgumentException( + "geiser:load should take a String or an IString as argument"); + } + return load(filepath); + } + + public Object load(String filepath) { + return GeiserEval.eval( + Language.getDefaultLanguage().getEnvironment(), + LList.list2(load.load, filepath)); + } +} diff --git a/src/main/java/kawageiser/GeiserNoValues.java b/src/main/java/kawageiser/GeiserNoValues.java new file mode 100644 index 0000000..b15b29c --- /dev/null +++ b/src/main/java/kawageiser/GeiserNoValues.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2020 spellcard199 <spellcard...@protonmail.com> + * This is free software; for terms and warranty disclaimer see ./COPYING. + */ + +package kawageiser; + +import gnu.mapping.Procedure0; +import gnu.mapping.Values; + +public class GeiserNoValues extends Procedure0 { + + GeiserNoValues(String name) { + super(name); + } + + @Override + public Object apply0() throws Throwable { + gnu.kawa.io.InPort.inDefault().setLineNumber( + gnu.kawa.io.InPort.inDefault().getLineNumber() - 1); + // apply0 signature doesn't allow us to return void + return Values.FromArray.make(); + } +} diff --git a/src/main/java/kawageiser/StartKawaWithGeiserSupport.java b/src/main/java/kawageiser/StartKawaWithGeiserSupport.java new file mode 100644 index 0000000..cc558e8 --- /dev/null +++ b/src/main/java/kawageiser/StartKawaWithGeiserSupport.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2020 spellcard199 <spellcard...@protonmail.com> + * This is free software; for terms and warranty disclaimer see ./COPYING. + */ + +package kawageiser; + +public class StartKawaWithGeiserSupport { + + public static void main(String[] args) { + if (args.length == 0) { + int defaultPort = 37146; + System.out.println( + String.format( + "No port specified. Starting kawa server on default port (%d)...", + defaultPort)); + startKawaServerWithGeiserSupport(defaultPort); + } else if (args.length == 1 && args[0].matches("[0-9]+")) { + int port = Integer.parseInt(args[0]); + startKawaServerWithGeiserSupport(port); + } else if (args.length == 1 && args[0].equals("--no-server")) { + System.out.println("Starting kawa repl in current terminal..."); + startKawaReplWithGeiserSupport(); + } else { + System.out.println( + "You must pass at most 1 argument and it can be only one of:\n" + + "- a port number" + + "- --no-server" + ); + } + } + + public static void startKawaReplWithGeiserSupport() { + String[] interpArgs = new String[]{ + "-e", "(require <kawageiser.Geiser>)", + "--", + }; + runSchemeAsApplication(interpArgs); + } + + public static void startKawaServerWithGeiserSupport(int port) { + String[] interpArgs = new String[]{ + "-e", "(require <kawageiser.Geiser>)", + "--server", String.valueOf(port)}; + runSchemeAsApplication(interpArgs); + } + + public static void runSchemeAsApplication(String[] args) { + kawa.standard.Scheme scheme = kawa.standard.Scheme.getInstance(); + scheme.runAsApplication(args); + } + +} diff --git a/src/main/java/kawageiser/geiserDoc/ManualEpubUnzipToTmpDir.java b/src/main/java/kawageiser/geiserDoc/ManualEpubUnzipToTmpDir.java new file mode 100644 index 0000000..f24b035 --- /dev/null +++ b/src/main/java/kawageiser/geiserDoc/ManualEpubUnzipToTmpDir.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2020 spellcard199 <spellcard...@protonmail.com> + * This is free software; for terms and warranty disclaimer see ./COPYING. + */ + +package kawageiser.geiserDoc; + +import gnu.lists.IString; +import gnu.mapping.Procedure1; + +import java.io.File; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; + +public class ManualEpubUnzipToTmpDir extends Procedure1 { + + public ManualEpubUnzipToTmpDir(String name) { + super(name); + } + + @Override + public Object apply1(Object kawaEpubManualPath) throws Throwable { + if (! (kawaEpubManualPath.getClass() == String.class || + kawaEpubManualPath.getClass() == IString.class)) { + throw new IllegalArgumentException( + "`kawaEpubManualPath' arg must be either String or IString"); + } + String systemTmpDir = System.getProperty("java.io.tmpdir"); + String manualUnzippedTmpDir = String.join( + File.separator, + systemTmpDir, + "geiser-kawa", + "manual-epub-unzipped"); + + File zipArchiveFile = new File(kawaEpubManualPath.toString()); + Path destDirPath = new File(manualUnzippedTmpDir).toPath(); + + kawadevutil.util.ZipExtractor.unzip(zipArchiveFile, destDirPath); + return gnu.kawa.functions.Format.format("~S", manualUnzippedTmpDir); + } +} diff --git a/src/main/java/kawageiser/java/Complete.java b/src/main/java/kawageiser/java/Complete.java new file mode 100644 index 0000000..6be9291 --- /dev/null +++ b/src/main/java/kawageiser/java/Complete.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2020 spellcard199 <spellcard...@protonmail.com> + * This is free software; for terms and warranty disclaimer see ./COPYING. + */ + +package kawageiser.java; + +import gnu.expr.Language; +import gnu.lists.IString; +import gnu.lists.LList; +import gnu.lists.Pair; +import gnu.mapping.Environment; +import gnu.mapping.Procedure1or2; +import gnu.mapping.Procedure3; +import gnu.mapping.Procedure4; +import gnu.math.IntNum; +import kawadevutil.complete.*; + +import java.io.File; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class Complete extends Procedure4 { + + public static boolean showTypes = true; + + public Complete(String name) { + super(name); + } + + @Override + public Object + apply4(Object codeStr, Object cursorIndex, Object lang, Object env) throws Throwable { + + String codeStrChecked = null; + if (codeStr.getClass().equals(IString.class) || codeStr.getClass().equals(String.class)) { + codeStrChecked = codeStr.toString(); + } else { + throw new IllegalArgumentException( + "`codeStr` must be either String or IString: " + codeStr.getClass().toString()); + } + + Integer cursorIndexChecked = null; + if (cursorIndex.getClass().equals(Integer.class)) { + cursorIndexChecked = (Integer) cursorIndex; + } else if (cursorIndex.getClass().equals(IntNum.class)) { + cursorIndexChecked = ((IntNum) cursorIndex).intValue(); + } else { + throw new IllegalArgumentException( + "`cursorIndex` must be either Integer or IntNum: " + cursorIndex.getClass().toString()); + } + + // Get Data + Optional<CompletionDataForJava> complDataMaybe = kawadevutil.complete.Complete.complete( + codeStrChecked, cursorIndexChecked, (Language) lang, (Environment) env, (String name) -> true); + + // Wrap data of interest in Scheme's LList + if (!complDataMaybe.isPresent()) { + return LList.Empty; + } else { + CompletionDataForJava complData = complDataMaybe.get(); + if (complData.getClass().equals(CompletionDataForJavaField.class)) { + CompletionDataForJavaField complDataForField = (CompletionDataForJavaField) complData; + } else if (complData.getClass().equals(CompletionDataForJavaMethod.class)) { + CompletionDataForJavaMethod complDataForMethod = (CompletionDataForJavaMethod) complData; + } else { + throw new Error("Bug spotted."); + } + + String completionsForClass = complData.getForClass().getName(); + CompletionDataForJava.FieldOrMethod fieldOrMethod = complData.getFieldOrMethod(); + List<String> names = (List<String>) complData.getNames().stream().distinct().collect(Collectors.toList()); + String beforeCursor = complData.getCursorMatcher().getCursorMatch().getBeforeCursor(); + String afterCursor = complData.getCursorMatcher().getCursorMatch().getAfterCursor(); + // I don't know why it says "unchecked call" when using complData.getRequiredModifiers().stream() + ArrayList<String> modifiers = new ArrayList<>(); + for (Object modifier : complData.getRequiredModifiers()) { + modifiers.add(modifier.toString()); + } + + java.util.List<LList> res = Arrays.asList( + LList.list2("compl-for-class", completionsForClass), + LList.list2("modifiers", LList.makeList(modifiers)), + LList.list2("field-or-method", fieldOrMethod.toString()), + LList.list2("completions", LList.makeList(names)), + LList.list2("before-cursor", beforeCursor), + LList.list2("after-cursor", afterCursor) + ); + LList resLList = LList.makeList(res); + return gnu.kawa.functions.Format.format("~S", resLList); + } + } + +} diff --git a/src/test/java/kawageiser/GeiserAutodocTest.java b/src/test/java/kawageiser/GeiserAutodocTest.java new file mode 100644 index 0000000..a2c0253 --- /dev/null +++ b/src/test/java/kawageiser/GeiserAutodocTest.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2020 spellcard199 <spellcard...@protonmail.com> + * This is free software; for terms and warranty disclaimer see ./COPYING. + */ + +package kawageiser; + +import gnu.lists.LList; +import gnu.mapping.Environment; +import gnu.mapping.Symbol; +import kawa.lib.ports; +import kawa.standard.Scheme; +import kawadevutil.data.ProcDataGeneric; +import org.testng.annotations.Test; + +import static org.testng.Assert.*; + +public class GeiserAutodocTest { + + @Test + public void testApply2() { + Scheme scheme = new Scheme(); + Environment env = scheme.getEnvironment(); + Symbol displaySym = env.getSymbol("display"); + Symbol cdddrSym = env.getSymbol("cdddr"); + GeiserAutodoc geiserAutodoc = new GeiserAutodoc("geiser-testing-autodoc", scheme); + String autodocDisplay = (String) geiserAutodoc.apply2(LList.list1(displaySym), env); + String autodocCdddr = (String) geiserAutodoc.apply2(LList.list1(cdddrSym), env); + + // System.out.println(autodocDisplay); + + assertTrue(autodocDisplay.startsWith("((\"display\"")); + assertTrue(autodocDisplay.contains(" (\"args\" ")); + assertTrue(autodocDisplay.contains("((\"required\" ")); + assertTrue(autodocDisplay.contains("(\"optional\" ")); + assertTrue(autodocDisplay.contains("(\"module\" ")); + + assertTrue(autodocCdddr.startsWith("((\"cdddr\"")); + assertTrue(autodocCdddr.contains(" (\"args\" ")); + assertTrue(autodocCdddr.contains("((\"required\" ")); + assertTrue(!autodocCdddr.contains("(\"optional\" ")); + assertTrue(autodocCdddr.contains("(\"module\" ")); + } + + public static class OperatorArgListDataTest { + + @Test + public void testToLList() { + ProcDataGeneric procDataNonGenericList = ProcDataGeneric.makeForProcedure(ports.display); + GeiserAutodoc.OperatorArgListData operatorArgListData = new GeiserAutodoc.OperatorArgListData(procDataNonGenericList); + LList llist = operatorArgListData.toLList(); + // System.out.println(llist); + String required = (String) ((LList) ((LList) llist.get(0)).get(0)).get(0); + String optional = (String) ((LList) ((LList) llist.get(0)).get(1)).get(0); + + assertEquals(required, "required"); + assertEquals(optional, "optional"); + + } + } + + public static class AutodocDataForSymIdTest { + + @Test + public void testToLListForProc1() { + Scheme scheme = new Scheme(); + Environment env = scheme.getEnvironment(); + Symbol display = env.getSymbol("display"); + GeiserAutodoc.AutodocDataForSymId autodocDataForDisplay = new GeiserAutodoc.AutodocDataForSymId(display, env, scheme);; + assertEquals(display.toString(), autodocDataForDisplay.toLList().get(0)); + } + + @Test + public void testToLListForProcN() { + Scheme scheme = new Scheme(); + Environment env = scheme.getEnvironment(); + Symbol strFormatSym = null; + try { + strFormatSym = (Symbol) scheme.eval("'java.lang.String:format"); + } catch (Throwable throwable) { + throwable.printStackTrace(); + } + GeiserAutodoc.AutodocDataForSymId autodocDataForDisplay = new GeiserAutodoc.AutodocDataForSymId(strFormatSym, env, scheme);; + LList llist = autodocDataForDisplay.toLList(); + assertEquals("java.lang.String:format", llist.get(0)); + } + } +} \ No newline at end of file