From cdc4a0461990dd7cd13c85bda5f4de235a1285dc Mon Sep 17 00:00:00 2001 From: Jonathan King Date: Sun, 29 Apr 2018 18:45:46 +0100 Subject: [PATCH 01/20] Inital seam --- Arithmetic.scr | 21 ++++++++++++++++++ Dockerfile | 13 +++++++++++ .../main/java/org/scribble/cli/CLArgFlag.java | 1 + .../java/org/scribble/cli/CLArgParser.java | 5 ++++- .../java/org/scribble/cli/CommandLine.java | 22 +++++++++++++++++++ scribble-dist/src/main/resources/scribblec.sh | 2 ++ 6 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 Arithmetic.scr create mode 100644 Dockerfile mode change 100644 => 100755 scribble-dist/src/main/resources/scribblec.sh diff --git a/Arithmetic.scr b/Arithmetic.scr new file mode 100644 index 000000000..7ab936555 --- /dev/null +++ b/Arithmetic.scr @@ -0,0 +1,21 @@ +module Arithmetic; + +type "java.lang.Integer" from "rt.jar" as Integer; + +global protocol MathServer(role Client, role Server) { + choice at Client { + Add(Integer, Integer) from Client to Server; + Sum(Integer) from Server to Client; + } or { + Multiply(Integer, Integer) from Client to Server; + Product(Integer) from Server to Client; + } or { + Quit() from Client to Server; + } +} + +global protocol Ping(role Client, role Server) { + Ping() from Client to Server; + Pong() from Server to Client; + do Ping(Client, Server); +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..0a2e2d652 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM maven:latest +RUN apt-get update && apt-get install vim tree less -y + +# RUN git clone https://github.com/scribble/scribble-java.git \ +COPY . /scribble-java +RUN cd scribble-java \ + && mvn install -Dlicense.skip=true \ + && mv scribble-dist/target/scribble-dist* / +RUN unzip scribble-dist* +RUN cp scribble-java/scribble-core/src/test/scrib/tmp/Test.scr . +RUN chmod 755 scribblec.sh + +ENTRYPOINT ["/bin/bash"] diff --git a/scribble-cli/src/main/java/org/scribble/cli/CLArgFlag.java b/scribble-cli/src/main/java/org/scribble/cli/CLArgFlag.java index e14cf7ded..a251a528c 100644 --- a/scribble-cli/src/main/java/org/scribble/cli/CLArgFlag.java +++ b/scribble-cli/src/main/java/org/scribble/cli/CLArgFlag.java @@ -46,6 +46,7 @@ public enum CLArgFlag SGRAPH_PNG, UNFAIR_SGRAPH_PNG, API_GEN, + API_GEN_PS, SESS_API_GEN, SCHAN_API_GEN, } diff --git a/scribble-cli/src/main/java/org/scribble/cli/CLArgParser.java b/scribble-cli/src/main/java/org/scribble/cli/CLArgParser.java index e14bd49db..753c7f8cb 100644 --- a/scribble-cli/src/main/java/org/scribble/cli/CLArgParser.java +++ b/scribble-cli/src/main/java/org/scribble/cli/CLArgParser.java @@ -51,6 +51,7 @@ public class CLArgParser public static final String SGRAPH_PNG_FLAG = "-modelpng"; public static final String UNFAIR_SGRAPH_PNG_FLAG = "-umodelpng"; public static final String API_GEN_FLAG = "-api"; + public static final String API_GEN_PS_FLAG = "-api-ps"; public static final String SESSION_API_GEN_FLAG = "-sessapi"; public static final String STATECHAN_API_GEN_FLAG = "-chanapi"; @@ -86,6 +87,7 @@ public class CLArgParser CLArgParser.NON_UNIQUE_FLAGS.put(CLArgParser.SGRAPH_PNG_FLAG, CLArgFlag.SGRAPH_PNG); CLArgParser.NON_UNIQUE_FLAGS.put(CLArgParser.UNFAIR_SGRAPH_PNG_FLAG, CLArgFlag.UNFAIR_SGRAPH_PNG); CLArgParser.NON_UNIQUE_FLAGS.put(CLArgParser.API_GEN_FLAG, CLArgFlag.API_GEN); + CLArgParser.NON_UNIQUE_FLAGS.put(CLArgParser.API_GEN_PS_FLAG, CLArgFlag.API_GEN_PS); CLArgParser.NON_UNIQUE_FLAGS.put(CLArgParser.SESSION_API_GEN_FLAG, CLArgFlag.SESS_API_GEN); CLArgParser.NON_UNIQUE_FLAGS.put(CLArgParser.STATECHAN_API_GEN_FLAG, CLArgFlag.SCHAN_API_GEN); } @@ -225,6 +227,7 @@ protected int parseFlag(int i) throws CommandLineException case CLArgParser.VALIDATION_EFSM_FLAG: case CLArgParser.UNFAIR_EFSM_FLAG: case CLArgParser.API_GEN_FLAG: + case CLArgParser.API_GEN_PS_FLAG: case CLArgParser.STATECHAN_API_GEN_FLAG: { return parseProtoAndRoleArgs(flag, i); @@ -322,7 +325,7 @@ private int parseProject(int i) throws CommandLineException // Similar to parse { if ((i + 2) >= this.args.length) { - throw new CommandLineException("Missing protocol/role arguments"); +// throw new CommandLineException("Missing protocol/role arguments"); } String proto = this.args[++i]; String role = this.args[++i]; diff --git a/scribble-cli/src/main/java/org/scribble/cli/CommandLine.java b/scribble-cli/src/main/java/org/scribble/cli/CommandLine.java index 20f9ebfca..644055a26 100644 --- a/scribble-cli/src/main/java/org/scribble/cli/CommandLine.java +++ b/scribble-cli/src/main/java/org/scribble/cli/CommandLine.java @@ -261,6 +261,10 @@ protected void doNonAttemptableOutputTasks(Job job) throws ScribbleException, Co { outputEndpointApi(job); } + if (this.args.containsKey(CLArgFlag.API_GEN_PS)) + { + outputEndpointApiPureScript(job); + } } // FIXME: option to write to file, like classes @@ -390,6 +394,24 @@ private void outputEndpointApi(Job job) throws ScribbleException, CommandLineExc } } + private void outputEndpointApiPureScript(Job job) throws ScribbleException, CommandLineException + { + System.out.println("PureScript code generation:"); + JobContext jcontext = job.getContext(); + String[] args = this.args.get(CLArgFlag.API_GEN_PS); + JEndpointApiGenerator jgen = new JEndpointApiGenerator(job); // FIXME: refactor (generalise -- use new API) + for (int i = 0; i < args.length; i += 2) + { + GProtocolName fullname = checkGlobalProtocolArg(jcontext, args[i]); + System.out.println("[JONATHAN] fullname: " + fullname); + Map sessClasses = jgen.generateSessionApi(fullname); + outputClasses(sessClasses); + Role role = checkRoleArg(jcontext, fullname, args[i+1]); + Map scClasses = jgen.generateStateChannelApi(fullname, role, this.args.containsKey(CLArgFlag.SCHAN_API_SUBTYPES)); + outputClasses(scClasses); + } + } + private void outputSessionApi(Job job) throws ScribbleException, CommandLineException { JobContext jcontext = job.getContext(); diff --git a/scribble-dist/src/main/resources/scribblec.sh b/scribble-dist/src/main/resources/scribblec.sh old mode 100644 new mode 100755 index 5f2df50b3..f5ff96e23 --- a/scribble-dist/src/main/resources/scribblec.sh +++ b/scribble-dist/src/main/resources/scribblec.sh @@ -116,6 +116,8 @@ CLASSPATH=$CLASSPATH':'$DIR'/'$LIB'/scribble-codegen.jar' CLASSPATH=$CLASSPATH':'$DIR'/'$LIB'/stringtemplate.jar' CLASSPATH="'"`fixpath "$CLASSPATH"`"'" +echo $DIR + usage=0 verbose=0 dot=0 From afa858f7588dadf078c31c8350ccbfe86d680223 Mon Sep 17 00:00:00 2001 From: Jonathan King Date: Sun, 29 Apr 2018 21:41:25 +0100 Subject: [PATCH 02/20] Add demo files --- .gitignore | 1 + Arithmetic.scr | 6 ----- ArithmeticPS.scr | 17 +++++++++++++ MathServer.purs | 64 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 82 insertions(+), 6 deletions(-) create mode 100644 ArithmeticPS.scr create mode 100644 MathServer.purs diff --git a/.gitignore b/.gitignore index c6aa4d053..bef513c3a 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,4 @@ scribble-core/src/test/scrib/test/test9/ bin/scribblec.sh permissions-fix.sh +.idea diff --git a/Arithmetic.scr b/Arithmetic.scr index 7ab936555..1c017c222 100644 --- a/Arithmetic.scr +++ b/Arithmetic.scr @@ -13,9 +13,3 @@ global protocol MathServer(role Client, role Server) { Quit() from Client to Server; } } - -global protocol Ping(role Client, role Server) { - Ping() from Client to Server; - Pong() from Server to Client; - do Ping(Client, Server); -} diff --git a/ArithmeticPS.scr b/ArithmeticPS.scr new file mode 100644 index 000000000..2bbc8114a --- /dev/null +++ b/ArithmeticPS.scr @@ -0,0 +1,17 @@ +module Arithmetic; + +type "Int" from "Prim" as Int; + +global protocol MathServer(role Client, role Server) { + choice at Client { + Add(Integer, Integer) from Client to Server; + Sum(Integer) from Server to Client; + do MathServer(Client, Server); + } or { + Multiply(Integer, Integer) from Client to Server; + Product(Integer) from Server to Client; + do MathServer(Client, Server); + } or { + Quit() from Client to Server; + } +} diff --git a/MathServer.purs b/MathServer.purs new file mode 100644 index 000000000..798c00969 --- /dev/null +++ b/MathServer.purs @@ -0,0 +1,64 @@ +-- module Scribble.Protocol.{module name}.{protocol name} where +module Scribble.Protocol.Arithmetic.MathServer where + +-- Hard coded import list +import Scribble.FSM (class Branch, class Initial, class Terminal, class Receive, class Select, class Send, kind Role) +import Type.Row (Cons, Nil) +import Data.Void (Void) + +-- Type declaration imports +-- e.g. type "Int" from "Prim" as Int; +import Prim (Int) + +-- Message data types +-- TODO: Derive JSON encodings for them! +data Add = Add Int Int +data Sum = Sum Int +data Multiply = Add Int Int +data Product = Product Int +data Quit = Quit + +-- Client role definition +foreign import data Client :: Role + +-- Client states +foreign import data S6 :: Type +foreign import data S6Add :: Type +foreign import data S6Quit :: Type +foreign import data S6Multiply :: Type +foreign import data S7 :: Type +foreign import data S8 :: Type +foreign import data S9 :: Type + +instance initialClient :: Initial Client S6 +instance terminalClient :: Terminal Client S7 + +-- Client state transitions +-- Branch names are the lowercase of the message label expected to receive +instance selectS6 :: Select Server S6 (Cons "quit" S6Quit (Cons "add" S6Add (Cons "multiply" S6Multiply Nil))) +instance sendS6Quit :: Send Server S6Quit S7 Quit +instance sendS6Add :: Send Server S6Add S8 Add +instance sendS6Multiply :: Send Server S6Multiply S9 Multiply +instance receiveS8 :: Receive Server S8 S6 Sum +instance receiveS9 :: Receive Server S9 S6 Product + +foreign import data Server :: Role + +foreign import data S16 :: Type +foreign import data S16Add :: Type +foreign import data S16Quit :: Type +foreign import data S16Multiply :: Type +foreign import data S17 :: Type +foreign import data S18 :: Type +foreign import data S19 :: Type + +instance initialServer :: Initial Server S16 +instance terminalServer :: Terminal Server S17 + +-- This isn't a typo, it should be Branch Server (see the typeclass for more details) +instance branchS16 :: Branch Server S16 (Cons "quit" S16Quit (Cons "add" S16Add (Cons "multiply" S16Multiply Nil))) +instance receiveS16Quit :: Receive Client S16Quit S17 Quit +instance receiveS16Add :: Receive Client S16Add S18 Add +instance receiveS16Multiply :: Receive Client S16Multiply S19 Multiply +instance sendS8 :: Send Server S18 S16 Sum +instance sendS9 :: Send Server S19 S16 Product From f565d486c2ce82428d9549c57df4a9354db32456 Mon Sep 17 00:00:00 2001 From: Jonathan King Date: Sun, 29 Apr 2018 22:37:13 +0100 Subject: [PATCH 03/20] Create code file --- .../java/org/scribble/cli/CommandLine.java | 10 +- .../purescript/PSEndpointApiGenerator.java | 105 ++++++++++++++++++ 2 files changed, 109 insertions(+), 6 deletions(-) create mode 100644 scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java diff --git a/scribble-cli/src/main/java/org/scribble/cli/CommandLine.java b/scribble-cli/src/main/java/org/scribble/cli/CommandLine.java index 644055a26..763cf345d 100644 --- a/scribble-cli/src/main/java/org/scribble/cli/CommandLine.java +++ b/scribble-cli/src/main/java/org/scribble/cli/CommandLine.java @@ -27,6 +27,7 @@ import org.scribble.ast.ProtocolDecl; import org.scribble.ast.global.GProtocolDecl; import org.scribble.codegen.java.JEndpointApiGenerator; +import org.scribble.codegen.purescript.PSEndpointApiGenerator; import org.scribble.main.AntlrSourceException; import org.scribble.main.Job; import org.scribble.main.JobContext; @@ -399,16 +400,13 @@ private void outputEndpointApiPureScript(Job job) throws ScribbleException, Comm System.out.println("PureScript code generation:"); JobContext jcontext = job.getContext(); String[] args = this.args.get(CLArgFlag.API_GEN_PS); - JEndpointApiGenerator jgen = new JEndpointApiGenerator(job); // FIXME: refactor (generalise -- use new API) + PSEndpointApiGenerator psgen = new PSEndpointApiGenerator(job); for (int i = 0; i < args.length; i += 2) { GProtocolName fullname = checkGlobalProtocolArg(jcontext, args[i]); System.out.println("[JONATHAN] fullname: " + fullname); - Map sessClasses = jgen.generateSessionApi(fullname); - outputClasses(sessClasses); - Role role = checkRoleArg(jcontext, fullname, args[i+1]); - Map scClasses = jgen.generateStateChannelApi(fullname, role, this.args.containsKey(CLArgFlag.SCHAN_API_SUBTYPES)); - outputClasses(scClasses); + Map classes = psgen.generateApi(fullname); + outputClasses(classes); } } diff --git a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java new file mode 100644 index 000000000..f9d02cc85 --- /dev/null +++ b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java @@ -0,0 +1,105 @@ +/** + * Copyright 2008 The Scribble Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.scribble.codegen.purescript; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.scribble.ast.Module; +import org.scribble.ast.global.GProtocolDecl; + +// import org.scribble.codegen.java.endpointapi.SessionApiGenerator; +// import org.scribble.codegen.java.endpointapi.StateChannelApiGenerator; +// import org.scribble.codegen.java.endpointapi.ioifaces.IOInterfacesGenerator; +import org.scribble.main.Job; +import org.scribble.main.RuntimeScribbleException; +import org.scribble.main.ScribbleException; +import org.scribble.type.name.GProtocolName; +import org.scribble.type.name.Role; + +public class PSEndpointApiGenerator +{ + public final Job job; + + public PSEndpointApiGenerator(Job job) + { + this.job = job; + } + + public Map generateApi(GProtocolName protocol) throws ScribbleException { + this.job.debugPrintln("\n[Purescript API gen] Running for " + protocol); + + Module mod = this.job.getContext().getModule(protocol.getPrefix()); + GProtocolName simpname = protocol.getSimpleName(); + GProtocolDecl gpd = (GProtocolDecl) mod.getProtocolDecl(simpname); + Set roles = new HashSet<>(); + for (Role r : gpd.header.roledecls.getRoles()) { + System.out.println(r); + roles.add(r); + } + + StringBuilder sb = new StringBuilder(); + + String moduleName = protocol.getPrefix().toString(); + String protocolName = protocol.getSimpleName().toString(); + + sb.append("module Scribble.Protocol." + protocol + " where\n"); + sb.append("import Scribble.FSM (class Branch, class Initial, class Terminal, class Receive, class Select, class Send, kind Role)\n"); + sb.append("import Type.Row (Cons, Nil)\n"); + sb.append("import Data.Void (Void)\n"); + + Map map = new HashMap(); + map.put(makePath(moduleName, protocolName), sb.toString()); + + return map; + } + + private static String makePath(String module, String protocol) + { + return "Scribble/Protocol/" + module.replace('.', '/') + "/" + protocol + ".purs"; + } + + +// // FIXME: refactor an EndpointApiGenerator -- ? +// public Map generateStateChannelApi(GProtocolName fullname, Role self, boolean subtypes) throws ScribbleException +// { +// /*JobContext jc = this.job.getContext(); +// if (jc.getEndpointGraph(fullname, self) == null) +// { +// buildGraph(fullname, self); +// }*/ +// job.debugPrintln("\n[Java API gen] Running " + StateChannelApiGenerator.class + " for " + fullname + "@" + self); +// StateChannelApiGenerator apigen = new StateChannelApiGenerator(this.job, fullname, self); +// IOInterfacesGenerator iogen = null; +// try +// { +// iogen = new IOInterfacesGenerator(apigen, subtypes); +// } +// catch (RuntimeScribbleException e) // FIXME: use IOInterfacesGenerator.skipIOInterfacesGeneration +// { +// //System.err.println("[Warning] Skipping I/O Interface generation for protocol featuring: " + fullname); +// this.job.warningPrintln("Skipping I/O Interface generation for: " + fullname + "\n Cause: " + e.getMessage()); +// } +// // Construct the Generators first, to build all the types -- then call generate to "compile" all Builders to text (further building changes will not be output) +// Map api = new HashMap<>(); // filepath -> class source // Store results? +// api.putAll(apigen.generateApi()); +// if (iogen != null) +// { +// api.putAll(iogen.generateApi()); +// } +// return api; +// } +} From 4ccf2b372120adb94cd924fc8a7a2209e29d3f31 Mon Sep 17 00:00:00 2001 From: Jonathan King Date: Mon, 30 Apr 2018 01:25:37 +0100 Subject: [PATCH 04/20] Start work on efsms --- Arithmetic.scr | 12 +-- ArithmeticPS.scr | 17 ---- .../purescript/PSEndpointApiGenerator.java | 85 ++++++++++++++++--- 3 files changed, 81 insertions(+), 33 deletions(-) delete mode 100644 ArithmeticPS.scr diff --git a/Arithmetic.scr b/Arithmetic.scr index 1c017c222..eff1657ad 100644 --- a/Arithmetic.scr +++ b/Arithmetic.scr @@ -1,14 +1,16 @@ module Arithmetic; -type "java.lang.Integer" from "rt.jar" as Integer; +type "Int" from "Prim" as Int; global protocol MathServer(role Client, role Server) { choice at Client { - Add(Integer, Integer) from Client to Server; - Sum(Integer) from Server to Client; + Add(Int, Int) from Client to Server; + Sum(Int) from Server to Client; + do MathServer(Client, Server); } or { - Multiply(Integer, Integer) from Client to Server; - Product(Integer) from Server to Client; + Multiply(Int, Int) from Client to Server; + Product(Int) from Server to Client; + do MathServer(Client, Server); } or { Quit() from Client to Server; } diff --git a/ArithmeticPS.scr b/ArithmeticPS.scr deleted file mode 100644 index 2bbc8114a..000000000 --- a/ArithmeticPS.scr +++ /dev/null @@ -1,17 +0,0 @@ -module Arithmetic; - -type "Int" from "Prim" as Int; - -global protocol MathServer(role Client, role Server) { - choice at Client { - Add(Integer, Integer) from Client to Server; - Sum(Integer) from Server to Client; - do MathServer(Client, Server); - } or { - Multiply(Integer, Integer) from Client to Server; - Product(Integer) from Server to Client; - do MathServer(Client, Server); - } or { - Quit() from Client to Server; - } -} diff --git a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java index f9d02cc85..5a4356088 100644 --- a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java +++ b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java @@ -13,22 +13,27 @@ */ package org.scribble.codegen.purescript; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; +import java.util.*; +import org.scribble.ast.ImportDecl; import org.scribble.ast.Module; +import org.scribble.ast.NonProtocolDecl; import org.scribble.ast.global.GProtocolDecl; // import org.scribble.codegen.java.endpointapi.SessionApiGenerator; // import org.scribble.codegen.java.endpointapi.StateChannelApiGenerator; // import org.scribble.codegen.java.endpointapi.ioifaces.IOInterfacesGenerator; +import org.scribble.del.ModuleDel; import org.scribble.main.Job; +import org.scribble.main.JobContext; import org.scribble.main.RuntimeScribbleException; import org.scribble.main.ScribbleException; +import org.scribble.model.endpoint.EGraph; +import org.scribble.model.endpoint.EState; import org.scribble.type.name.GProtocolName; +import org.scribble.type.name.MessageId; import org.scribble.type.name.Role; +import org.scribble.visit.util.MessageIdCollector; public class PSEndpointApiGenerator { @@ -39,11 +44,11 @@ public PSEndpointApiGenerator(Job job) this.job = job; } - public Map generateApi(GProtocolName protocol) throws ScribbleException { - this.job.debugPrintln("\n[Purescript API gen] Running for " + protocol); + public Map generateApi(GProtocolName fullname) throws ScribbleException { + this.job.debugPrintln("\n[Purescript API gen] Running for " + fullname); - Module mod = this.job.getContext().getModule(protocol.getPrefix()); - GProtocolName simpname = protocol.getSimpleName(); + Module mod = this.job.getContext().getModule(fullname.getPrefix()); + GProtocolName simpname = fullname.getSimpleName(); GProtocolDecl gpd = (GProtocolDecl) mod.getProtocolDecl(simpname); Set roles = new HashSet<>(); for (Role r : gpd.header.roledecls.getRoles()) { @@ -53,14 +58,59 @@ public Map generateApi(GProtocolName protocol) throws ScribbleEx StringBuilder sb = new StringBuilder(); - String moduleName = protocol.getPrefix().toString(); - String protocolName = protocol.getSimpleName().toString(); + String moduleName = fullname.getPrefix().toString(); + String protocolName = fullname.getSimpleName().toString(); - sb.append("module Scribble.Protocol." + protocol + " where\n"); + sb.append("module Scribble.Protocol." + fullname + " where\n"); sb.append("import Scribble.FSM (class Branch, class Initial, class Terminal, class Receive, class Select, class Send, kind Role)\n"); sb.append("import Type.Row (Cons, Nil)\n"); sb.append("import Data.Void (Void)\n"); + // Get the foreign type declarations + // TODO: Only include imports that are used + // TODO: Make it clear that JSON encoding/decoding instances are required + // TODO: Group imports by module? + for (NonProtocolDecl foreignType : mod.getNonProtocolDecls()) { + if (!foreignType.schema.equals("purescript")) { + throw new ScribbleException(foreignType.getSource(), "Unsupported data type schema: " + foreignType.schema); + } + System.out.println(foreignType); // There is a bug in the parser "type Int from Int as Int;" + sb.append("import " + foreignType.extSource + " (" + foreignType.extName + ")\n"); + } + + // Go through the global protocol and pull out all the message 'data types' + // TODO: Annotate MessageId with its arguments + // TODO: Handle polymorphic messages (probably just throw an error) + Set> mids = new HashSet<>(); + + MessageIdCollector coll = new MessageIdCollector(this.job, ((ModuleDel) mod.del()).getModuleContext()); + gpd.accept(coll); + for (MessageId mid : coll.getNames()) + { + System.out.println(mid); + mids.add(mid); + } + + System.out.println(mids); + for (MessageId mid : mids) { + String name = getMessageDataTypeName(mid); + sb.append("data " + name + " = " + name + " -- TODO: Add arguments + derive JSON enc/dec\n"); + } + + // TODO: For each role make a projection, then traverse the graph getting the states + transitions + for (Role role : roles) { + String name = getRoleDataTypeName(role); + sb.append("foreign import data " + role + " :: Role\n"); + + JobContext jc = job.getContext(); + EGraph efsm = job.minEfsm ? jc.getMinimisedEGraph(fullname, role) : jc.getEGraph(fullname, role); + EState init = efsm.init; + EState term = EState.getTerminal(init); + System.out.println("Init: " + init); + System.out.println("Term: " + term); + } + + Map map = new HashMap(); map.put(makePath(moduleName, protocolName), sb.toString()); @@ -72,6 +122,19 @@ private static String makePath(String module, String protocol) return "Scribble/Protocol/" + module.replace('.', '/') + "/" + protocol + ".purs"; } + // Returns the data type name for a message - if it is not a capital letter it will be prefixed by 'M' + public static String getMessageDataTypeName(MessageId mid) + { + String s = mid.toString(); + return (s.isEmpty() || s.charAt(0) < 65 || s.charAt(0) > 90) ? "M" + s : s; // Hacky? (Yes) + } + + // Returns the data type name for a role - if it is not a capital letter it will be prefixed by 'R' + public static String getRoleDataTypeName(Role r) + { + String s = r.toString(); + return (s.isEmpty() || s.charAt(0) < 65 || s.charAt(0) > 90) ? "R" + s : s; // Hacky? (Yes) + } // // FIXME: refactor an EndpointApiGenerator -- ? // public Map generateStateChannelApi(GProtocolName fullname, Role self, boolean subtypes) throws ScribbleException From 9a0db4d3c87ec596e5b6bc30bb4f96314423d5da Mon Sep 17 00:00:00 2001 From: Jonathan King Date: Tue, 1 May 2018 15:40:18 +0100 Subject: [PATCH 05/20] Naive code gen without support for data types --- .../purescript/PSEndpointApiGenerator.java | 133 +++++++++++++++++- 1 file changed, 129 insertions(+), 4 deletions(-) diff --git a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java index 5a4356088..722f31650 100644 --- a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java +++ b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java @@ -30,9 +30,11 @@ import org.scribble.main.ScribbleException; import org.scribble.model.endpoint.EGraph; import org.scribble.model.endpoint.EState; +import org.scribble.model.endpoint.actions.EAction; import org.scribble.type.name.GProtocolName; import org.scribble.type.name.MessageId; import org.scribble.type.name.Role; +import org.scribble.util.Pair; import org.scribble.visit.util.MessageIdCollector; public class PSEndpointApiGenerator @@ -106,10 +108,123 @@ public Map generateApi(GProtocolName fullname) throws ScribbleEx EGraph efsm = job.minEfsm ? jc.getMinimisedEGraph(fullname, role) : jc.getEGraph(fullname, role); EState init = efsm.init; EState term = EState.getTerminal(init); - System.out.println("Init: " + init); - System.out.println("Term: " + term); - } + sb.append("instance initial" + role + " " + getRoleDataTypeName(role) + " :: Initial " + getRoleDataTypeName(role) + " " + getStateTypeName(init) + "\n"); + if (term != null) { + sb.append("instance terminal" + role + " " + getRoleDataTypeName(role) + " :: Terminal " + getRoleDataTypeName(role) + " " + getStateTypeName(term) + "\n"); + } else { + sb.append("instance terminal" + role + " " + getRoleDataTypeName(role) + " :: Terminal " + getRoleDataTypeName(role) + "Void \n"); + } + + Set seen = new HashSet<>(); + Set level = new HashSet<>(); + + // Perform a breadth first traversal over the graph + level.add(init); + + while (!level.isEmpty()) { + Set nextlevel = new HashSet<>(); + for (EState s : level) { + // Don't generate more than once + if (seen.contains(s)) break; + seen.add(s); + + sb.append("foreign import data " + getStateTypeName(s) + " :: Type\n"); + + // View the successors during the next level + nextlevel.addAll(s.getAllSuccessors()); + + switch (s.getStateKind()) { + case OUTPUT: { + assert (s.getAllActions().size() > 0); + if (s.getAllActions().size() == 1) { + EState to = s.getAllSuccessors().get(0); + EAction action = s.getAllActions().get(0); + String type = getMessageDataTypeName(action.mid); + String toR = getRoleDataTypeName(action.obj); + sb.append("instance send" + getStateTypeName(s) + " :: Send " + toR + " " + getStateTypeName(s) + " " + getStateTypeName(to) + " " + type + "\n"); + } else { + Set> options = new HashSet<>(); + for (int i = 0; i < s.getAllActions().size(); i++) { + // If there are multiple output actions, we should treat it as a branch and create some dummy states + // where we send the label + EAction action = s.getAllActions().get(i); + EState to = s.getAllSuccessors().get(i); + String labelState = getStateTypeName(s) + action.mid; + String label = getLabelFromMessage(action.mid); + String type = getMessageDataTypeName(action.mid); + String toR = getRoleDataTypeName(action.obj); + sb.append("foreign import data " + labelState + " :: Type\n"); + sb.append("instance send" + labelState + " :: Send " + toR + " " + labelState + " " + getStateTypeName(to) + " " + type + "\n"); + + options.add(new Pair(label,labelState)); + } + + // All messages must be to the same role + EAction action = s.getAllActions().get(0); + String toR = getRoleDataTypeName(action.obj); + sb.append("instance select" + getStateTypeName(s) + " :: Select " + toR + " " + getStateTypeName(s) + " "); + String end = "Nil"; + for (Pair option : options) { + sb.append("(Cons \"" + option.left + "\" " + option.right + " "); + end += ")"; + } + sb.append(end + "\n"); + } + } + break; + case UNARY_INPUT: { + EState from = s.getAllSuccessors().get(0); + EAction action = s.getAllActions().get(0); + String type = getMessageDataTypeName(action.mid); + String fromR = getRoleDataTypeName(action.obj); + sb.append("instance receive" + getStateTypeName(s) + " :: Receive " + fromR + " " + getStateTypeName(s) + " " + getStateTypeName(from) + " " + type + "\n"); + } + break; + case POLY_INPUT: { + Set> options = new HashSet<>(); + for (int i = 0; i < s.getAllActions().size(); i++) { + // If there are multiple output actions, we should treat it as a branch and create some dummy states + // where we send the label + EAction action = s.getAllActions().get(i); + EState to = s.getAllSuccessors().get(i); + String labelState = getStateTypeName(s) + action.mid; + String label = getLabelFromMessage(action.mid); + String type = getMessageDataTypeName(action.mid); + String fromR = getRoleDataTypeName(action.obj); + sb.append("foreign import data " + labelState + " :: Type\n"); + sb.append("instance receive" + labelState + " :: Receive " + fromR + " " + labelState + " " + getStateTypeName(to) + " " + type + "\n"); + + options.add(new Pair(label,labelState)); + } + + // All messages must be to the same role + EAction action = s.getAllActions().get(0); + String atR = getRoleDataTypeName(role); + sb.append("instance branch" + getStateTypeName(s) + " :: Branch " + atR + " " + getStateTypeName(s) + " "); + String end = "Nil"; + for (Pair option : options) { + sb.append("(Cons \"" + option.left + "\" " + option.right + " "); + end += ")"; + } + sb.append(end + "\n"); + } + break; + case TERMINAL: + break; + case ACCEPT: + throw new ScribbleException(null, "Unsupported action " + s.getStateKind()); + case WRAP_SERVER: + throw new ScribbleException(null, "Unsupported action " + s.getStateKind()); + } + + } + + + level = nextlevel; + } + + } Map map = new HashMap(); map.put(makePath(moduleName, protocolName), sb.toString()); @@ -117,7 +232,12 @@ public Map generateApi(GProtocolName fullname) throws ScribbleEx return map; } - private static String makePath(String module, String protocol) + private String getLabelFromMessage(MessageId mid) { + String s = mid.toString().toLowerCase(); + return (s.isEmpty() || s.charAt(0) < 97 || s.charAt(0) > 122) ? "l" + s : s; // Hacky? (Yes) + } + + private static String makePath(String module, String protocol) { return "Scribble/Protocol/" + module.replace('.', '/') + "/" + protocol + ".purs"; } @@ -135,6 +255,11 @@ public static String getRoleDataTypeName(Role r) String s = r.toString(); return (s.isEmpty() || s.charAt(0) < 65 || s.charAt(0) > 90) ? "R" + s : s; // Hacky? (Yes) } + + public static String getStateTypeName(EState s) + { + return "S" + s; + } // // FIXME: refactor an EndpointApiGenerator -- ? // public Map generateStateChannelApi(GProtocolName fullname, Role self, boolean subtypes) throws ScribbleException From 52ed96a4422fde9df26e7619e4b6c5c67e4e5a2d Mon Sep 17 00:00:00 2001 From: Jonathan King Date: Tue, 1 May 2018 17:13:18 +0100 Subject: [PATCH 06/20] Output data types, but there is a bug --- .../purescript/PSEndpointApiGenerator.java | 43 ++++++++++++++++--- 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java index 722f31650..2aa4d6db6 100644 --- a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java +++ b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java @@ -31,8 +31,11 @@ import org.scribble.model.endpoint.EGraph; import org.scribble.model.endpoint.EState; import org.scribble.model.endpoint.actions.EAction; +import org.scribble.type.Payload; +import org.scribble.type.kind.PayloadTypeKind; import org.scribble.type.name.GProtocolName; import org.scribble.type.name.MessageId; +import org.scribble.type.name.PayloadElemType; import org.scribble.type.name.Role; import org.scribble.util.Pair; import org.scribble.visit.util.MessageIdCollector; @@ -84,6 +87,7 @@ public Map generateApi(GProtocolName fullname) throws ScribbleEx // TODO: Annotate MessageId with its arguments // TODO: Handle polymorphic messages (probably just throw an error) Set> mids = new HashSet<>(); + Map, Payload> datatypes = new HashMap<>(); MessageIdCollector coll = new MessageIdCollector(this.job, ((ModuleDel) mod.del()).getModuleContext()); gpd.accept(coll); @@ -93,12 +97,6 @@ public Map generateApi(GProtocolName fullname) throws ScribbleEx mids.add(mid); } - System.out.println(mids); - for (MessageId mid : mids) { - String name = getMessageDataTypeName(mid); - sb.append("data " + name + " = " + name + " -- TODO: Add arguments + derive JSON enc/dec\n"); - } - // TODO: For each role make a projection, then traverse the graph getting the states + transitions for (Role role : roles) { String name = getRoleDataTypeName(role); @@ -143,12 +141,14 @@ public Map generateApi(GProtocolName fullname) throws ScribbleEx String type = getMessageDataTypeName(action.mid); String toR = getRoleDataTypeName(action.obj); sb.append("instance send" + getStateTypeName(s) + " :: Send " + toR + " " + getStateTypeName(s) + " " + getStateTypeName(to) + " " + type + "\n"); + addDatatype(datatypes, action); } else { Set> options = new HashSet<>(); for (int i = 0; i < s.getAllActions().size(); i++) { // If there are multiple output actions, we should treat it as a branch and create some dummy states // where we send the label EAction action = s.getAllActions().get(i); + addDatatype(datatypes, action); EState to = s.getAllSuccessors().get(i); String labelState = getStateTypeName(s) + action.mid; String label = getLabelFromMessage(action.mid); @@ -176,6 +176,7 @@ public Map generateApi(GProtocolName fullname) throws ScribbleEx case UNARY_INPUT: { EState from = s.getAllSuccessors().get(0); EAction action = s.getAllActions().get(0); + addDatatype(datatypes, action); String type = getMessageDataTypeName(action.mid); String fromR = getRoleDataTypeName(action.obj); sb.append("instance receive" + getStateTypeName(s) + " :: Receive " + fromR + " " + getStateTypeName(s) + " " + getStateTypeName(from) + " " + type + "\n"); @@ -187,6 +188,7 @@ public Map generateApi(GProtocolName fullname) throws ScribbleEx // If there are multiple output actions, we should treat it as a branch and create some dummy states // where we send the label EAction action = s.getAllActions().get(i); + addDatatype(datatypes, action); EState to = s.getAllSuccessors().get(i); String labelState = getStateTypeName(s) + action.mid; String label = getLabelFromMessage(action.mid); @@ -226,12 +228,41 @@ public Map generateApi(GProtocolName fullname) throws ScribbleEx } + for (MessageId mid : datatypes.keySet()) { + String name = getMessageDataTypeName(mid); + sb.append("data " + name + " = " + name); + for (PayloadElemType type : datatypes.get(mid).elems) { + sb.append(" " + type); + } + sb.append("-- TODO: Derive JSON enc/dec\n"); + } + + System.out.println(mids); + for (MessageId mid : mids) { + String name = getMessageDataTypeName(mid); + sb.append("data " + name + " = " + name + " -- TODO: Add arguments + derive JSON enc/dec\n"); + } + Map map = new HashMap(); map.put(makePath(moduleName, protocolName), sb.toString()); return map; } + private void addDatatype(Map, Payload> datatypes, EAction action) throws ScribbleException { + if (datatypes.containsKey(action.mid)) { + System.out.println(action.mid + " already there"); + System.out.println(datatypes.keySet().toString()); + System.out.println(datatypes.get(action.mid) + " - " + action.payload); + if (!datatypes.get(action.mid).equals(action.payload)) { + System.out.println("and different!"); + throw new ScribbleException(null, "Messages with the same name `" + action.mid + "` must have the same payload"); + } + } else { + datatypes.put(action.mid, action.payload); + } + } + private String getLabelFromMessage(MessageId mid) { String s = mid.toString().toLowerCase(); return (s.isEmpty() || s.charAt(0) < 97 || s.charAt(0) > 122) ? "l" + s : s; // Hacky? (Yes) From 6224733460205c427f3d9dc0be11b7557ba6eb55 Mon Sep 17 00:00:00 2001 From: Jonathan King Date: Wed, 2 May 2018 02:25:12 +0100 Subject: [PATCH 07/20] Before major refactor --- .../purescript/PSEndpointApiGenerator.java | 20 ++++-- .../purescript/endpointapi/DataType.java | 64 +++++++++++++++++ .../purescript/endpointapi/ForeignType.java | 56 +++++++++++++++ .../purescript/endpointapi/ModuleGen.java | 40 +++++++++++ .../endpointapi/PolyTransition.java | 71 +++++++++++++++++++ .../endpointapi/UnaryTransition.java | 34 +++++++++ 6 files changed, 280 insertions(+), 5 deletions(-) create mode 100644 scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/DataType.java create mode 100644 scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/ForeignType.java create mode 100644 scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/ModuleGen.java create mode 100644 scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/PolyTransition.java create mode 100644 scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/UnaryTransition.java diff --git a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java index 2aa4d6db6..2f54915f5 100644 --- a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java +++ b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java @@ -15,7 +15,6 @@ import java.util.*; -import org.scribble.ast.ImportDecl; import org.scribble.ast.Module; import org.scribble.ast.NonProtocolDecl; import org.scribble.ast.global.GProtocolDecl; @@ -23,10 +22,11 @@ // import org.scribble.codegen.java.endpointapi.SessionApiGenerator; // import org.scribble.codegen.java.endpointapi.StateChannelApiGenerator; // import org.scribble.codegen.java.endpointapi.ioifaces.IOInterfacesGenerator; +import org.scribble.codegen.purescript.endpointapi.ForeignType; +import org.scribble.codegen.purescript.endpointapi.ModuleGen; import org.scribble.del.ModuleDel; import org.scribble.main.Job; import org.scribble.main.JobContext; -import org.scribble.main.RuntimeScribbleException; import org.scribble.main.ScribbleException; import org.scribble.model.endpoint.EGraph; import org.scribble.model.endpoint.EState; @@ -42,7 +42,8 @@ public class PSEndpointApiGenerator { - public final Job job; + public static final String PURESCRIPT_SCHEMA = "purescript"; + public final Job job; public PSEndpointApiGenerator(Job job) { @@ -71,18 +72,27 @@ public Map generateApi(GProtocolName fullname) throws ScribbleEx sb.append("import Type.Row (Cons, Nil)\n"); sb.append("import Data.Void (Void)\n"); + Map foreignTypeContext = new HashMap<>(); + // Get the foreign type declarations // TODO: Only include imports that are used // TODO: Make it clear that JSON encoding/decoding instances are required - // TODO: Group imports by module? for (NonProtocolDecl foreignType : mod.getNonProtocolDecls()) { - if (!foreignType.schema.equals("purescript")) { + if (!foreignType.schema.equals(PURESCRIPT_SCHEMA)) { throw new ScribbleException(foreignType.getSource(), "Unsupported data type schema: " + foreignType.schema); } System.out.println(foreignType); // There is a bug in the parser "type Int from Int as Int;" +// System.out.println("name- " + foreignType.name); +// System.out.println("extSource- " + foreignType.extSource); +// System.out.println("extName- " + foreignType.extName); sb.append("import " + foreignType.extSource + " (" + foreignType.extName + ")\n"); + foreignTypeContext.put(foreignType.name.toString(), new ForeignType(foreignType.extName, foreignType.extSource)); } + ModuleGen moduleGen = new ModuleGen(fullname.getPrefix().toString(), + fullname.getSimpleName().toString(), + new HashSet<>(foreignTypeContext.values())); + // Go through the global protocol and pull out all the message 'data types' // TODO: Annotate MessageId with its arguments // TODO: Handle polymorphic messages (probably just throw an error) diff --git a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/DataType.java b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/DataType.java new file mode 100644 index 000000000..01f67e31c --- /dev/null +++ b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/DataType.java @@ -0,0 +1,64 @@ +package org.scribble.codegen.purescript.endpointapi; + +import org.scribble.type.kind.PayloadTypeKind; +import org.scribble.type.name.PayloadElemType; + +import java.util.List; +import java.util.Objects; + +public class DataType { + public static final String KIND_TYPE = "Type"; + public final String name; + private final List params; + private final String kind; + private boolean isForeign; + + public static boolean isValidName(String name) { + // Names must begin with capital letters and contain only letters and digits + return !(name.isEmpty() || name.charAt(0) < 65 || name.charAt(0) > 90); + } + + public DataType(String name, List args, String kind, boolean isForeign) { + this.isForeign = isForeign; + if (!isValidName(name)) { + throw new RuntimeException("`" + name + "' is an invalid data type name"); + } + if (!isValidName(kind)) { + throw new RuntimeException("`" + kind + "' is an invalid data type kind"); + } + this.name = name; + this.params = args; + this.kind = kind; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + DataType dataType = (DataType) o; + return Objects.equals(params, dataType.params) && + Objects.equals(name, dataType.name) && + Objects.equals(kind, dataType.kind); + } + + @Override + public int hashCode() { + return Objects.hash(params, name, kind); + } + + public String generateDataType() { + if (isForeign) { + return "foreign import data " + name + (kind.equals(KIND_TYPE) ? "" : (" :: " + kind)) + "\n"; + } else { + // TODO: Potential newtype optimisation for constructors with exactly one value + // TODO: Derive JSON encoding/decoding + StringBuilder sb = new StringBuilder(); + sb.append("data " + name + " = " + name); + for (ForeignType type : params) { + sb.append(" " + type.name); + } + sb.append("-- TODO: Derive JSON enc/dec\n"); + return sb.toString(); + } + } +} diff --git a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/ForeignType.java b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/ForeignType.java new file mode 100644 index 000000000..0cf32e4d1 --- /dev/null +++ b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/ForeignType.java @@ -0,0 +1,56 @@ +package org.scribble.codegen.purescript.endpointapi; + +import java.util.*; + +public class ForeignType { + public final String name; + public final String source; + // Primitive types in the language - no need to explicitly import + // See https://pursuit.purescript.org/builtins/docs/Prim + // TODO: Don't generate import statements for them + public static final String[] PRIMS = new String[]{"Number", "Int", "String", "Char", "Boolean"}; + + public ForeignType(String name, String source) { + this.name = name; + this.source = source; + } + + public String generateImport() { + return "import " + source + " (" + name + ")\n"; + } + + // Group by package source + public static String generateImports(Set types) { + HashMap> imports = new HashMap(); + for (ForeignType type : types) { + if (imports.containsKey(type.source)) { + imports.get(type.source).add(type.name); + } else { + Set mod = new HashSet<>(); + mod.add(type.name); + imports.put(type.source, mod); + } + } + for (String source : imports.keySet()) { + String i = "import " + source + " ("; + for (String s : imports.get(source)) { + i += s + ", "; + } + } + return ")\n"; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ForeignType that = (ForeignType) o; + return Objects.equals(name, that.name) && + Objects.equals(source, that.source); + } + + @Override + public int hashCode() { + return Objects.hash(name, source); + } +} diff --git a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/ModuleGen.java b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/ModuleGen.java new file mode 100644 index 000000000..96f30ed8a --- /dev/null +++ b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/ModuleGen.java @@ -0,0 +1,40 @@ +package org.scribble.codegen.purescript.endpointapi; + +import java.util.Map; +import java.util.Set; + +public class ModuleGen { + public final String moduleName; + public final String protocolName; + public final Set foreignTypes; +// public final Map> + public ModuleGen(String moduleName, String protocolName, Set foreignTypes) { + this.moduleName = moduleName; + this.protocolName = protocolName; + this.foreignTypes = foreignTypes; + } + + private String moduleDeclaration() { + return "module Scribble.Protocol." + moduleName + "." + protocolName + " where\n"; + } + + private String staticImports() { + StringBuilder sb = new StringBuilder(); + sb.append("import Scribble.FSM (class Branch, class Initial, class Terminal, class Receive, class Select, class Send, kind Role)\n"); + sb.append("import Type.Row (Cons, Nil)\n"); + sb.append("import Data.Void (Void)\n"); + return sb.toString(); + } + + public String filename() { + return "Scribble/Protocol/" + moduleName.replace('.', '/') + "/" + protocolName + ".purs"; + } + + public String generateApi() { + StringBuilder sb = new StringBuilder(); + sb.append(moduleDeclaration()); + sb.append(ForeignType.generateImports(foreignTypes)); + + return ""; + } +} diff --git a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/PolyTransition.java b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/PolyTransition.java new file mode 100644 index 000000000..fcda210b7 --- /dev/null +++ b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/PolyTransition.java @@ -0,0 +1,71 @@ +package org.scribble.codegen.purescript.endpointapi; + +import org.scribble.util.Pair; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class PolyTransition { + + private final String state; + private final String thisRole; + private final String thatRole; + private final Transition kind; + private final Map> actions; + + public enum Transition { + BRANCH, + SELECT + } + + public static String toTypeClass(Transition kind) { + if (kind == Transition.BRANCH) return "Branch"; + else if (kind == Transition.SELECT) return "Select"; + return null; + } + + public PolyTransition(String state, String thisRole, String thatRole, Transition kind, Map> actions) { + this.state = state; + this.thisRole = thisRole; + this.thatRole = thatRole; + this.kind = kind; + this.actions = actions; + } + + public Pair> generateTransitions() { + StringBuilder t = new StringBuilder(); + + t.append("instance " + toTypeClass(kind).toLowerCase() + state + " :: "); + t.append(toTypeClass(kind) + " "); + String role = kind == Transition.BRANCH ? thisRole : thatRole; + t.append(role + " " + state + " "); + + // The transitions from the intermediate states + StringBuilder ts = new StringBuilder(); + Set intermediates = new HashSet<>(); + String end = "Nil"; + for (String label : actions.keySet()) { + Pair action = actions.get(label); + String iname = state + label; + DataType intermediate = new DataType(iname, null, DataType.KIND_TYPE, true); + intermediates.add(intermediate); + + t.append("(Cons \"" + action.left + "\" " + iname + " "); + end += ")"; + + ts.append("instance "); + if (kind == Transition.BRANCH) t.append("receive"); + else if (kind == Transition.SELECT) t.append("send"); + ts.append(state + " :: "); + if (kind == Transition.BRANCH) t.append("Receive"); + else if (kind == Transition.SELECT) t.append("Send"); + ts.append(" " + thatRole + " " + state + " " + action.left + " " + action.right.name + "\n"); + } + t.append(end + "\n"); + t.append(ts.toString()); + + return new Pair<>(t.toString(), intermediates); + } + +} diff --git a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/UnaryTransition.java b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/UnaryTransition.java new file mode 100644 index 000000000..ec75dcb1d --- /dev/null +++ b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/UnaryTransition.java @@ -0,0 +1,34 @@ +package org.scribble.codegen.purescript.endpointapi; + +import org.scribble.util.Pair; + +public class UnaryTransition { + private final String state; + private final String role; + private final Transition kind; + private final Pair action; + + public enum Transition { + SEND, + RECEIVE + } + + public static String toTypeClass(Transition kind) { + if (kind == Transition.SEND) return "Send"; + else if (kind == Transition.RECEIVE) return "Receive"; + return null; + } + + public UnaryTransition(String state, String role, Transition kind, Pair action) { + this.state = state; + this.role = role; + this.kind = kind; + this.action = action; + } + + public String generateTransition() { + return "instance " + toTypeClass(kind).toLowerCase() + state + + " :: " + toTypeClass(kind) + " " + role + " " + state + + " " + action.left + " " + action.right.name + "\n"; + } +} From a66ee58d90b30f75592eb561d307b382f1cefb16 Mon Sep 17 00:00:00 2001 From: Jonathan King Date: Wed, 2 May 2018 21:29:59 +0100 Subject: [PATCH 08/20] Mid refactor --- .../purescript/PSEndpointApiGenerator.java | 325 +++++++++--------- .../purescript/endpointapi/ForeignType.java | 1 + .../purescript/endpointapi/ModuleGen.java | 21 +- .../endpointapi/PolyTransition.java | 10 +- .../endpointapi/TypeClassInstance.java | 25 ++ .../endpointapi/UnaryTransition.java | 6 +- 6 files changed, 223 insertions(+), 165 deletions(-) create mode 100644 scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/TypeClassInstance.java diff --git a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java index 2f54915f5..0b385c73a 100644 --- a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java +++ b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java @@ -22,8 +22,10 @@ // import org.scribble.codegen.java.endpointapi.SessionApiGenerator; // import org.scribble.codegen.java.endpointapi.StateChannelApiGenerator; // import org.scribble.codegen.java.endpointapi.ioifaces.IOInterfacesGenerator; +import org.scribble.codegen.purescript.endpointapi.DataType; import org.scribble.codegen.purescript.endpointapi.ForeignType; import org.scribble.codegen.purescript.endpointapi.ModuleGen; +import org.scribble.codegen.purescript.endpointapi.TypeClassInstance; import org.scribble.del.ModuleDel; import org.scribble.main.Job; import org.scribble.main.JobContext; @@ -40,6 +42,8 @@ import org.scribble.util.Pair; import org.scribble.visit.util.MessageIdCollector; +import javax.xml.crypto.Data; + public class PSEndpointApiGenerator { public static final String PURESCRIPT_SCHEMA = "purescript"; @@ -56,24 +60,14 @@ public Map generateApi(GProtocolName fullname) throws ScribbleEx Module mod = this.job.getContext().getModule(fullname.getPrefix()); GProtocolName simpname = fullname.getSimpleName(); GProtocolDecl gpd = (GProtocolDecl) mod.getProtocolDecl(simpname); - Set roles = new HashSet<>(); - for (Role r : gpd.header.roledecls.getRoles()) { - System.out.println(r); - roles.add(r); - } - - StringBuilder sb = new StringBuilder(); String moduleName = fullname.getPrefix().toString(); - String protocolName = fullname.getSimpleName().toString(); + String protocolName = fullname.getSimpleName().toString(); - sb.append("module Scribble.Protocol." + fullname + " where\n"); - sb.append("import Scribble.FSM (class Branch, class Initial, class Terminal, class Receive, class Select, class Send, kind Role)\n"); - sb.append("import Type.Row (Cons, Nil)\n"); - sb.append("import Data.Void (Void)\n"); - - Map foreignTypeContext = new HashMap<>(); + // Message actions and their corresponding datatype + Map datatypes = new HashMap<>(); + Map foreignImports = new HashMap<>(); // Get the foreign type declarations // TODO: Only include imports that are used // TODO: Make it clear that JSON encoding/decoding instances are required @@ -82,147 +76,139 @@ public Map generateApi(GProtocolName fullname) throws ScribbleEx throw new ScribbleException(foreignType.getSource(), "Unsupported data type schema: " + foreignType.schema); } System.out.println(foreignType); // There is a bug in the parser "type Int from Int as Int;" -// System.out.println("name- " + foreignType.name); -// System.out.println("extSource- " + foreignType.extSource); -// System.out.println("extName- " + foreignType.extName); - sb.append("import " + foreignType.extSource + " (" + foreignType.extName + ")\n"); - foreignTypeContext.put(foreignType.name.toString(), new ForeignType(foreignType.extName, foreignType.extSource)); + foreignImports.put(foreignType.name.toString(), new ForeignType(foreignType.extName, foreignType.extSource)); } - ModuleGen moduleGen = new ModuleGen(fullname.getPrefix().toString(), - fullname.getSimpleName().toString(), - new HashSet<>(foreignTypeContext.values())); - - // Go through the global protocol and pull out all the message 'data types' - // TODO: Annotate MessageId with its arguments - // TODO: Handle polymorphic messages (probably just throw an error) - Set> mids = new HashSet<>(); - Map, Payload> datatypes = new HashMap<>(); - - MessageIdCollector coll = new MessageIdCollector(this.job, ((ModuleDel) mod.del()).getModuleContext()); - gpd.accept(coll); - for (MessageId mid : coll.getNames()) - { - System.out.println(mid); - mids.add(mid); - } - - // TODO: For each role make a projection, then traverse the graph getting the states + transitions - for (Role role : roles) { - String name = getRoleDataTypeName(role); - sb.append("foreign import data " + role + " :: Role\n"); + Map, List>> efsms = new HashMap(); + // For each role make a projection, then traverse the graph getting the states + transitions + for (Role r : gpd.header.roledecls.getRoles()) { JobContext jc = job.getContext(); - EGraph efsm = job.minEfsm ? jc.getMinimisedEGraph(fullname, role) : jc.getEGraph(fullname, role); + EGraph efsm = job.minEfsm ? jc.getMinimisedEGraph(fullname, r) : jc.getEGraph(fullname, r); + EState init = efsm.init; EState term = EState.getTerminal(init); - sb.append("instance initial" + role + " " + getRoleDataTypeName(role) + " :: Initial " + getRoleDataTypeName(role) + " " + getStateTypeName(init) + "\n"); - if (term != null) { - sb.append("instance terminal" + role + " " + getRoleDataTypeName(role) + " :: Terminal " + getRoleDataTypeName(role) + " " + getStateTypeName(term) + "\n"); - } else { - sb.append("instance terminal" + role + " " + getRoleDataTypeName(role) + " :: Terminal " + getRoleDataTypeName(role) + "Void \n"); - } + DataType role = new DataType(r.toString(), null, "Role", true); + + List instances = new ArrayList<>(); + List states = new ArrayList<>(); - Set seen = new HashSet<>(); + // Add the instances for initial/terminal nodes + instances.add(new TypeClassInstance(("initial" + role.name), "Initial", new String[] {role.name, getStateTypeName(init)})); + String t = term == null ? "Void" : getStateTypeName(term); + instances.add(new TypeClassInstance(("terminal" + role.name), "Terminal", new String[] {role.name, t})); + + Set seen = new HashSet<>(); Set level = new HashSet<>(); // Perform a breadth first traversal over the graph level.add(init); - while (!level.isEmpty()) { Set nextlevel = new HashSet<>(); for (EState s : level) { // Don't generate more than once - if (seen.contains(s)) break; - seen.add(s); - - sb.append("foreign import data " + getStateTypeName(s) + " :: Type\n"); - + if (seen.contains(s.toString())) { + System.out.println(s + "foo"); + continue; + } + seen.add(s.toString()); // View the successors during the next level nextlevel.addAll(s.getAllSuccessors()); + // Generate the state type + String curr = getStateTypeName(s); + states.add(new DataType(curr, null, DataType.KIND_TYPE, true)); + switch (s.getStateKind()) { case OUTPUT: { - assert (s.getAllActions().size() > 0); if (s.getAllActions().size() == 1) { - EState to = s.getAllSuccessors().get(0); + String next = getStateTypeName(s.getAllSuccessors().get(0)); EAction action = s.getAllActions().get(0); - String type = getMessageDataTypeName(action.mid); - String toR = getRoleDataTypeName(action.obj); - sb.append("instance send" + getStateTypeName(s) + " :: Send " + toR + " " + getStateTypeName(s) + " " + getStateTypeName(to) + " " + type + "\n"); - addDatatype(datatypes, action); + String type = action.mid.toString(); + String to = action.obj.toString(); + + // Add the instance and message data type + instances.add(new TypeClassInstance(("send" + curr), "Send", new String[] {to, curr, next, type})); + addDatatype(datatypes, action, foreignImports); } else { - Set> options = new HashSet<>(); + // If there are multiple output actions, we should treat it as a branch and create some dummy states where we send the label + Set> choices = new HashSet<>(); for (int i = 0; i < s.getAllActions().size(); i++) { - // If there are multiple output actions, we should treat it as a branch and create some dummy states - // where we send the label EAction action = s.getAllActions().get(i); - addDatatype(datatypes, action); - EState to = s.getAllSuccessors().get(i); + String next = getStateTypeName(s.getAllSuccessors().get(i)); String labelState = getStateTypeName(s) + action.mid; - String label = getLabelFromMessage(action.mid); - String type = getMessageDataTypeName(action.mid); - String toR = getRoleDataTypeName(action.obj); - sb.append("foreign import data " + labelState + " :: Type\n"); - sb.append("instance send" + labelState + " :: Send " + toR + " " + labelState + " " + getStateTypeName(to) + " " + type + "\n"); - - options.add(new Pair(label,labelState)); + String label = action.mid.toString().toLowerCase(); + String type = action.mid.toString(); + String to = action.obj.toString(); + choices.add(new Pair(label, labelState)); + + // Add the instance and message data type and dummy state + states.add(new DataType(labelState, null, DataType.KIND_TYPE, true)); + instances.add(new TypeClassInstance("send" + labelState, "Send", new String[] {to, labelState, next, type})); + addDatatype(datatypes, action, foreignImports); } - // All messages must be to the same role + // All messages must be to the same role, so we can pick the first one EAction action = s.getAllActions().get(0); - String toR = getRoleDataTypeName(action.obj); - sb.append("instance select" + getStateTypeName(s) + " :: Select " + toR + " " + getStateTypeName(s) + " "); + String to = action.obj.toString(); + String end = "Nil"; - for (Pair option : options) { - sb.append("(Cons \"" + option.left + "\" " + option.right + " "); + StringBuilder labels = new StringBuilder(); + for (Pair option : choices) { + labels.append("(Cons \"" + option.left + "\" " + option.right + " "); end += ")"; } - sb.append(end + "\n"); + labels.append(end); + + instances.add(new TypeClassInstance("select" + curr, "Select", new String[] {to, curr, labels.toString()})); } } break; case UNARY_INPUT: { - EState from = s.getAllSuccessors().get(0); + String next = getStateTypeName(s.getAllSuccessors().get(0)); EAction action = s.getAllActions().get(0); - addDatatype(datatypes, action); - String type = getMessageDataTypeName(action.mid); - String fromR = getRoleDataTypeName(action.obj); - sb.append("instance receive" + getStateTypeName(s) + " :: Receive " + fromR + " " + getStateTypeName(s) + " " + getStateTypeName(from) + " " + type + "\n"); + String type = action.mid.toString(); + String from = action.obj.toString(); + + // Add the instance and message data type + instances.add(new TypeClassInstance("receive" + curr, "Receive", new String[] {from, curr, next, type})); + addDatatype(datatypes, action, foreignImports); } break; case POLY_INPUT: { - Set> options = new HashSet<>(); + Set> choices = new HashSet<>(); for (int i = 0; i < s.getAllActions().size(); i++) { - // If there are multiple output actions, we should treat it as a branch and create some dummy states - // where we send the label EAction action = s.getAllActions().get(i); - addDatatype(datatypes, action); - EState to = s.getAllSuccessors().get(i); + String next = getStateTypeName(s.getAllSuccessors().get(i)); String labelState = getStateTypeName(s) + action.mid; - String label = getLabelFromMessage(action.mid); - String type = getMessageDataTypeName(action.mid); - String fromR = getRoleDataTypeName(action.obj); - sb.append("foreign import data " + labelState + " :: Type\n"); - sb.append("instance receive" + labelState + " :: Receive " + fromR + " " + labelState + " " + getStateTypeName(to) + " " + type + "\n"); - - options.add(new Pair(label,labelState)); + String label = action.mid.toString().toLowerCase(); + String type = action.mid.toString(); + String to = action.obj.toString(); + choices.add(new Pair(label, labelState)); + + // Add the instance and message data type and dummy state + states.add(new DataType(labelState, null, DataType.KIND_TYPE, true)); + instances.add(new TypeClassInstance("receive" + labelState, "Receive", new String[] {to, labelState, next, type})); + addDatatype(datatypes, action, foreignImports); } - // All messages must be to the same role + // All messages must be to the same role, so we can pick the first one EAction action = s.getAllActions().get(0); - String atR = getRoleDataTypeName(role); - sb.append("instance branch" + getStateTypeName(s) + " :: Branch " + atR + " " + getStateTypeName(s) + " "); + String end = "Nil"; - for (Pair option : options) { - sb.append("(Cons \"" + option.left + "\" " + option.right + " "); + StringBuilder labels = new StringBuilder(); + for (Pair option : choices) { + labels.append("(Cons \"" + option.left + "\" " + option.right + " "); end += ")"; } - sb.append(end + "\n"); + labels.append(end); + + instances.add(new TypeClassInstance("branch" + curr, "Branch", new String[] {role.name, curr, labels.toString()})); } break; case TERMINAL: + System.out.println("Terminal"); break; case ACCEPT: throw new ScribbleException(null, "Unsupported action " + s.getStateKind()); @@ -236,40 +222,102 @@ public Map generateApi(GProtocolName fullname) throws ScribbleEx level = nextlevel; } + efsms.put(role, new Pair<>(states, instances)); } - for (MessageId mid : datatypes.keySet()) { - String name = getMessageDataTypeName(mid); - sb.append("data " + name + " = " + name); - for (PayloadElemType type : datatypes.get(mid).elems) { - sb.append(" " + type); +// for (MessageId mid : datatypes.keySet()) { +// String name = getMessageDataTypeName(mid); +// sb.append("data " + name + " = " + name); +// for (PayloadElemType type : datatypes.get(mid).elems) { +// sb.append(" " + type); +// } +// sb.append("-- TODO: Derive JSON enc/dec\n"); +// } + +// System.out.println(mids); +// for (MessageId mid : mids) { +// String name = getMessageDataTypeName(mid); +// sb.append("data " + name + " = " + name + " -- TODO: Add arguments + derive JSON enc/dec\n"); +// } + + +// List arguments = new ArrayList<>(); +// for (PayloadElemType elem : action.payload.elems) { +// arguments.add(foreignImports.get(elem.toString())); +// } +// +// datatypes.put(action.mid.toString(), new DataType(action.mid.toString(), arguments, DataType.KIND_TYPE, false)); + + + // Perform the code generation + List sections = new ArrayList<>(); + sections.add(moduleDeclaration(moduleName, protocolName)); + sections.add(staticImports()); + + // Foreign imports + sections.add(ForeignType.generateImports(new HashSet<>(foreignImports.values()))); + + // Message data types + StringBuilder dt = new StringBuilder(); + for (String type : datatypes.keySet()) { + List arguments = new ArrayList<>(); + for (PayloadElemType elem : datatypes.get(type).elems) { + arguments.add(foreignImports.get(elem.toString())); + } + dt.append(new DataType(type, arguments, DataType.KIND_TYPE, false).generateDataType()); + } + sections.add(dt.toString()); + + // EFSMs + for (DataType role : efsms.keySet()) { + sections.add(role.generateDataType()); + + StringBuilder states = new StringBuilder(); + for (DataType state : efsms.get(role).left) { + states.append(state.generateDataType()); } - sb.append("-- TODO: Derive JSON enc/dec\n"); + sections.add(states.toString()); + + StringBuilder instances = new StringBuilder(); + for (TypeClassInstance instance : efsms.get(role).right) { + instances.append(instance.generateInstance()); + } + sections.add(instances.toString()); } - System.out.println(mids); - for (MessageId mid : mids) { - String name = getMessageDataTypeName(mid); - sb.append("data " + name + " = " + name + " -- TODO: Add arguments + derive JSON enc/dec\n"); - } + // Add newlines between sections + StringBuilder module = new StringBuilder(); + for (String section : sections) { + module.append(section); + module.append("\n"); + } - Map map = new HashMap(); - map.put(makePath(moduleName, protocolName), sb.toString()); + Map map = new HashMap(); + map.put(makePath(moduleName, protocolName), module.toString()); return map; } - private void addDatatype(Map, Payload> datatypes, EAction action) throws ScribbleException { - if (datatypes.containsKey(action.mid)) { - System.out.println(action.mid + " already there"); - System.out.println(datatypes.keySet().toString()); - System.out.println(datatypes.get(action.mid) + " - " + action.payload); - if (!datatypes.get(action.mid).equals(action.payload)) { - System.out.println("and different!"); - throw new ScribbleException(null, "Messages with the same name `" + action.mid + "` must have the same payload"); + private static String moduleDeclaration(String moduleName, String protocolName) { + return "module Scribble.Protocol." + moduleName + "." + protocolName + " where\n"; + } + + private static String staticImports() { + StringBuilder sb = new StringBuilder(); + sb.append("import Scribble.FSM (class Branch, class Initial, class Terminal, class Receive, class Select, class Send, kind Role)\n"); + sb.append("import Type.Row (Cons, Nil)\n"); + sb.append("import Data.Void (Void)\n"); + return sb.toString(); + } + + private void addDatatype(Map datatypes, EAction action, Map foreignImports) throws RuntimeException { + String datatype = action.mid.toString(); + if (datatypes.containsKey(datatype)) { + if (!datatypes.get(datatype).equals(action.payload)) { + throw new RuntimeException("Messages with the same name `" + action.mid + "` must have the same payload"); } } else { - datatypes.put(action.mid, action.payload); + datatypes.put(datatype, action.payload); } } @@ -302,33 +350,4 @@ public static String getStateTypeName(EState s) return "S" + s; } -// // FIXME: refactor an EndpointApiGenerator -- ? -// public Map generateStateChannelApi(GProtocolName fullname, Role self, boolean subtypes) throws ScribbleException -// { -// /*JobContext jc = this.job.getContext(); -// if (jc.getEndpointGraph(fullname, self) == null) -// { -// buildGraph(fullname, self); -// }*/ -// job.debugPrintln("\n[Java API gen] Running " + StateChannelApiGenerator.class + " for " + fullname + "@" + self); -// StateChannelApiGenerator apigen = new StateChannelApiGenerator(this.job, fullname, self); -// IOInterfacesGenerator iogen = null; -// try -// { -// iogen = new IOInterfacesGenerator(apigen, subtypes); -// } -// catch (RuntimeScribbleException e) // FIXME: use IOInterfacesGenerator.skipIOInterfacesGeneration -// { -// //System.err.println("[Warning] Skipping I/O Interface generation for protocol featuring: " + fullname); -// this.job.warningPrintln("Skipping I/O Interface generation for: " + fullname + "\n Cause: " + e.getMessage()); -// } -// // Construct the Generators first, to build all the types -- then call generate to "compile" all Builders to text (further building changes will not be output) -// Map api = new HashMap<>(); // filepath -> class source // Store results? -// api.putAll(apigen.generateApi()); -// if (iogen != null) -// { -// api.putAll(iogen.generateApi()); -// } -// return api; -// } } diff --git a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/ForeignType.java b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/ForeignType.java index 0cf32e4d1..d94b58b2f 100644 --- a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/ForeignType.java +++ b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/ForeignType.java @@ -21,6 +21,7 @@ public String generateImport() { // Group by package source public static String generateImports(Set types) { + System.out.println(types); HashMap> imports = new HashMap(); for (ForeignType type : types) { if (imports.containsKey(type.source)) { diff --git a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/ModuleGen.java b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/ModuleGen.java index 96f30ed8a..ab6709f22 100644 --- a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/ModuleGen.java +++ b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/ModuleGen.java @@ -1,5 +1,8 @@ package org.scribble.codegen.purescript.endpointapi; +import org.scribble.util.Pair; + +import java.util.List; import java.util.Map; import java.util.Set; @@ -7,11 +10,13 @@ public class ModuleGen { public final String moduleName; public final String protocolName; public final Set foreignTypes; -// public final Map> - public ModuleGen(String moduleName, String protocolName, Set foreignTypes) { + private Map, List>> efsms; + + public ModuleGen(String moduleName, String protocolName, Set foreignTypes, Map, List>> efsms) { this.moduleName = moduleName; this.protocolName = protocolName; this.foreignTypes = foreignTypes; + this.efsms = efsms; } private String moduleDeclaration() { @@ -34,7 +39,15 @@ public String generateApi() { StringBuilder sb = new StringBuilder(); sb.append(moduleDeclaration()); sb.append(ForeignType.generateImports(foreignTypes)); - - return ""; + for (DataType role : efsms.keySet()) { + sb.append(role.generateDataType()); + for (DataType state : efsms.get(role).right) { + sb.append(state.generateDataType()); + } + for (String transition : efsms.get(role).left) { + sb.append(transition); + } + } + return sb.toString(); } } diff --git a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/PolyTransition.java b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/PolyTransition.java index fcda210b7..ccaf44e34 100644 --- a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/PolyTransition.java +++ b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/PolyTransition.java @@ -9,8 +9,8 @@ public class PolyTransition { private final String state; - private final String thisRole; - private final String thatRole; + private final DataType thisRole; + private final DataType thatRole; private final Transition kind; private final Map> actions; @@ -25,7 +25,7 @@ public static String toTypeClass(Transition kind) { return null; } - public PolyTransition(String state, String thisRole, String thatRole, Transition kind, Map> actions) { + public PolyTransition(String state, DataType thisRole, DataType thatRole, Transition kind, Map> actions) { this.state = state; this.thisRole = thisRole; this.thatRole = thatRole; @@ -38,7 +38,7 @@ public Pair> generateTransitions() { t.append("instance " + toTypeClass(kind).toLowerCase() + state + " :: "); t.append(toTypeClass(kind) + " "); - String role = kind == Transition.BRANCH ? thisRole : thatRole; + String role = kind == Transition.BRANCH ? thisRole.name : thatRole.name; t.append(role + " " + state + " "); // The transitions from the intermediate states @@ -60,7 +60,7 @@ public Pair> generateTransitions() { ts.append(state + " :: "); if (kind == Transition.BRANCH) t.append("Receive"); else if (kind == Transition.SELECT) t.append("Send"); - ts.append(" " + thatRole + " " + state + " " + action.left + " " + action.right.name + "\n"); + ts.append(" " + thatRole.name + " " + state + " " + action.left + " " + action.right.name + "\n"); } t.append(end + "\n"); t.append(ts.toString()); diff --git a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/TypeClassInstance.java b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/TypeClassInstance.java new file mode 100644 index 000000000..886c875c0 --- /dev/null +++ b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/TypeClassInstance.java @@ -0,0 +1,25 @@ +package org.scribble.codegen.purescript.endpointapi; + +import java.util.List; + +public class TypeClassInstance { + private final String instance; + private final String typeclass; + private final String[] parameters; + + public TypeClassInstance(String instance, String typeclass, String[] parameters) { + this.instance = instance; + this.typeclass = typeclass; + this.parameters = parameters; + } + + public String generateInstance() { + StringBuilder sb = new StringBuilder(); + sb.append("class " + instance + " :: " + typeclass); + for (String parameter : parameters) { + sb.append(" " + parameter); + } + sb.append("\n"); + return sb.toString(); + } +} diff --git a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/UnaryTransition.java b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/UnaryTransition.java index ec75dcb1d..212d45af4 100644 --- a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/UnaryTransition.java +++ b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/UnaryTransition.java @@ -4,7 +4,7 @@ public class UnaryTransition { private final String state; - private final String role; + private final DataType role; private final Transition kind; private final Pair action; @@ -19,7 +19,7 @@ public static String toTypeClass(Transition kind) { return null; } - public UnaryTransition(String state, String role, Transition kind, Pair action) { + public UnaryTransition(String state, DataType role, Transition kind, Pair action) { this.state = state; this.role = role; this.kind = kind; @@ -28,7 +28,7 @@ public UnaryTransition(String state, String role, Transition kind, Pair Date: Wed, 2 May 2018 22:57:58 +0100 Subject: [PATCH 09/20] Fix some bugs + tidy code --- .../java/org/scribble/cli/CommandLine.java | 2 - .../purescript/PSEndpointApiGenerator.java | 55 -------------- .../purescript/endpointapi/DataType.java | 2 +- .../purescript/endpointapi/ForeignType.java | 18 ++--- .../endpointapi/PolyTransition.java | 71 ------------------- .../endpointapi/TypeClassInstance.java | 2 +- .../endpointapi/UnaryTransition.java | 34 --------- 7 files changed, 11 insertions(+), 173 deletions(-) delete mode 100644 scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/PolyTransition.java delete mode 100644 scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/UnaryTransition.java diff --git a/scribble-cli/src/main/java/org/scribble/cli/CommandLine.java b/scribble-cli/src/main/java/org/scribble/cli/CommandLine.java index 763cf345d..b6ee490a6 100644 --- a/scribble-cli/src/main/java/org/scribble/cli/CommandLine.java +++ b/scribble-cli/src/main/java/org/scribble/cli/CommandLine.java @@ -397,14 +397,12 @@ private void outputEndpointApi(Job job) throws ScribbleException, CommandLineExc private void outputEndpointApiPureScript(Job job) throws ScribbleException, CommandLineException { - System.out.println("PureScript code generation:"); JobContext jcontext = job.getContext(); String[] args = this.args.get(CLArgFlag.API_GEN_PS); PSEndpointApiGenerator psgen = new PSEndpointApiGenerator(job); for (int i = 0; i < args.length; i += 2) { GProtocolName fullname = checkGlobalProtocolArg(jcontext, args[i]); - System.out.println("[JONATHAN] fullname: " + fullname); Map classes = psgen.generateApi(fullname); outputClasses(classes); } diff --git a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java index 0b385c73a..4b97b4f0b 100644 --- a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java +++ b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java @@ -18,15 +18,9 @@ import org.scribble.ast.Module; import org.scribble.ast.NonProtocolDecl; import org.scribble.ast.global.GProtocolDecl; - -// import org.scribble.codegen.java.endpointapi.SessionApiGenerator; -// import org.scribble.codegen.java.endpointapi.StateChannelApiGenerator; -// import org.scribble.codegen.java.endpointapi.ioifaces.IOInterfacesGenerator; import org.scribble.codegen.purescript.endpointapi.DataType; import org.scribble.codegen.purescript.endpointapi.ForeignType; -import org.scribble.codegen.purescript.endpointapi.ModuleGen; import org.scribble.codegen.purescript.endpointapi.TypeClassInstance; -import org.scribble.del.ModuleDel; import org.scribble.main.Job; import org.scribble.main.JobContext; import org.scribble.main.ScribbleException; @@ -36,13 +30,9 @@ import org.scribble.type.Payload; import org.scribble.type.kind.PayloadTypeKind; import org.scribble.type.name.GProtocolName; -import org.scribble.type.name.MessageId; import org.scribble.type.name.PayloadElemType; import org.scribble.type.name.Role; import org.scribble.util.Pair; -import org.scribble.visit.util.MessageIdCollector; - -import javax.xml.crypto.Data; public class PSEndpointApiGenerator { @@ -109,7 +99,6 @@ public Map generateApi(GProtocolName fullname) throws ScribbleEx for (EState s : level) { // Don't generate more than once if (seen.contains(s.toString())) { - System.out.println(s + "foo"); continue; } seen.add(s.toString()); @@ -208,7 +197,6 @@ public Map generateApi(GProtocolName fullname) throws ScribbleEx } break; case TERMINAL: - System.out.println("Terminal"); break; case ACCEPT: throw new ScribbleException(null, "Unsupported action " + s.getStateKind()); @@ -225,30 +213,6 @@ public Map generateApi(GProtocolName fullname) throws ScribbleEx efsms.put(role, new Pair<>(states, instances)); } -// for (MessageId mid : datatypes.keySet()) { -// String name = getMessageDataTypeName(mid); -// sb.append("data " + name + " = " + name); -// for (PayloadElemType type : datatypes.get(mid).elems) { -// sb.append(" " + type); -// } -// sb.append("-- TODO: Derive JSON enc/dec\n"); -// } - -// System.out.println(mids); -// for (MessageId mid : mids) { -// String name = getMessageDataTypeName(mid); -// sb.append("data " + name + " = " + name + " -- TODO: Add arguments + derive JSON enc/dec\n"); -// } - - -// List arguments = new ArrayList<>(); -// for (PayloadElemType elem : action.payload.elems) { -// arguments.add(foreignImports.get(elem.toString())); -// } -// -// datatypes.put(action.mid.toString(), new DataType(action.mid.toString(), arguments, DataType.KIND_TYPE, false)); - - // Perform the code generation List sections = new ArrayList<>(); sections.add(moduleDeclaration(moduleName, protocolName)); @@ -321,30 +285,11 @@ private void addDatatype(Map datatypes, EAction action, Map mid) { - String s = mid.toString().toLowerCase(); - return (s.isEmpty() || s.charAt(0) < 97 || s.charAt(0) > 122) ? "l" + s : s; // Hacky? (Yes) - } - private static String makePath(String module, String protocol) { return "Scribble/Protocol/" + module.replace('.', '/') + "/" + protocol + ".purs"; } - // Returns the data type name for a message - if it is not a capital letter it will be prefixed by 'M' - public static String getMessageDataTypeName(MessageId mid) - { - String s = mid.toString(); - return (s.isEmpty() || s.charAt(0) < 65 || s.charAt(0) > 90) ? "M" + s : s; // Hacky? (Yes) - } - - // Returns the data type name for a role - if it is not a capital letter it will be prefixed by 'R' - public static String getRoleDataTypeName(Role r) - { - String s = r.toString(); - return (s.isEmpty() || s.charAt(0) < 65 || s.charAt(0) > 90) ? "R" + s : s; // Hacky? (Yes) - } - public static String getStateTypeName(EState s) { return "S" + s; diff --git a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/DataType.java b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/DataType.java index 01f67e31c..8d1159af5 100644 --- a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/DataType.java +++ b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/DataType.java @@ -57,7 +57,7 @@ public String generateDataType() { for (ForeignType type : params) { sb.append(" " + type.name); } - sb.append("-- TODO: Derive JSON enc/dec\n"); + sb.append(" -- TODO: Derive JSON enc/dec\n"); return sb.toString(); } } diff --git a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/ForeignType.java b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/ForeignType.java index d94b58b2f..729da0a6c 100644 --- a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/ForeignType.java +++ b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/ForeignType.java @@ -15,13 +15,9 @@ public ForeignType(String name, String source) { this.source = source; } - public String generateImport() { - return "import " + source + " (" + name + ")\n"; - } - // Group by package source public static String generateImports(Set types) { - System.out.println(types); + StringBuilder is = new StringBuilder(); HashMap> imports = new HashMap(); for (ForeignType type : types) { if (imports.containsKey(type.source)) { @@ -33,12 +29,16 @@ public static String generateImports(Set types) { } } for (String source : imports.keySet()) { - String i = "import " + source + " ("; - for (String s : imports.get(source)) { - i += s + ", "; + is.append("import " + source + " ("); + List ts = new ArrayList<>(imports.get(source)); + is.append(ts.get(0)); + ts.remove(0); + for (String type : ts) { + is.append(", " + type); } + is.append(")\n"); } - return ")\n"; + return is.toString(); } @Override diff --git a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/PolyTransition.java b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/PolyTransition.java deleted file mode 100644 index ccaf44e34..000000000 --- a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/PolyTransition.java +++ /dev/null @@ -1,71 +0,0 @@ -package org.scribble.codegen.purescript.endpointapi; - -import org.scribble.util.Pair; - -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -public class PolyTransition { - - private final String state; - private final DataType thisRole; - private final DataType thatRole; - private final Transition kind; - private final Map> actions; - - public enum Transition { - BRANCH, - SELECT - } - - public static String toTypeClass(Transition kind) { - if (kind == Transition.BRANCH) return "Branch"; - else if (kind == Transition.SELECT) return "Select"; - return null; - } - - public PolyTransition(String state, DataType thisRole, DataType thatRole, Transition kind, Map> actions) { - this.state = state; - this.thisRole = thisRole; - this.thatRole = thatRole; - this.kind = kind; - this.actions = actions; - } - - public Pair> generateTransitions() { - StringBuilder t = new StringBuilder(); - - t.append("instance " + toTypeClass(kind).toLowerCase() + state + " :: "); - t.append(toTypeClass(kind) + " "); - String role = kind == Transition.BRANCH ? thisRole.name : thatRole.name; - t.append(role + " " + state + " "); - - // The transitions from the intermediate states - StringBuilder ts = new StringBuilder(); - Set intermediates = new HashSet<>(); - String end = "Nil"; - for (String label : actions.keySet()) { - Pair action = actions.get(label); - String iname = state + label; - DataType intermediate = new DataType(iname, null, DataType.KIND_TYPE, true); - intermediates.add(intermediate); - - t.append("(Cons \"" + action.left + "\" " + iname + " "); - end += ")"; - - ts.append("instance "); - if (kind == Transition.BRANCH) t.append("receive"); - else if (kind == Transition.SELECT) t.append("send"); - ts.append(state + " :: "); - if (kind == Transition.BRANCH) t.append("Receive"); - else if (kind == Transition.SELECT) t.append("Send"); - ts.append(" " + thatRole.name + " " + state + " " + action.left + " " + action.right.name + "\n"); - } - t.append(end + "\n"); - t.append(ts.toString()); - - return new Pair<>(t.toString(), intermediates); - } - -} diff --git a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/TypeClassInstance.java b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/TypeClassInstance.java index 886c875c0..7cb791548 100644 --- a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/TypeClassInstance.java +++ b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/TypeClassInstance.java @@ -15,7 +15,7 @@ public TypeClassInstance(String instance, String typeclass, String[] parameters) public String generateInstance() { StringBuilder sb = new StringBuilder(); - sb.append("class " + instance + " :: " + typeclass); + sb.append("instance " + instance + " :: " + typeclass); for (String parameter : parameters) { sb.append(" " + parameter); } diff --git a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/UnaryTransition.java b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/UnaryTransition.java deleted file mode 100644 index 212d45af4..000000000 --- a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/UnaryTransition.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.scribble.codegen.purescript.endpointapi; - -import org.scribble.util.Pair; - -public class UnaryTransition { - private final String state; - private final DataType role; - private final Transition kind; - private final Pair action; - - public enum Transition { - SEND, - RECEIVE - } - - public static String toTypeClass(Transition kind) { - if (kind == Transition.SEND) return "Send"; - else if (kind == Transition.RECEIVE) return "Receive"; - return null; - } - - public UnaryTransition(String state, DataType role, Transition kind, Pair action) { - this.state = state; - this.role = role; - this.kind = kind; - this.action = action; - } - - public String generateTransition() { - return "instance " + toTypeClass(kind).toLowerCase() + state - + " :: " + toTypeClass(kind) + " " + role.name + " " + state - + " " + action.left + " " + action.right.name + "\n"; - } -} From 14784e8216c29b7b6abdf82209c6bef17e1ad473 Mon Sep 17 00:00:00 2001 From: Jonathan King Date: Thu, 3 May 2018 00:31:47 +0100 Subject: [PATCH 10/20] Derive JSON encoding/decoding --- .../{endpointapi => }/DataType.java | 14 +++-- .../{endpointapi => }/ForeignType.java | 8 +-- .../purescript/PSEndpointApiGenerator.java | 19 +++++-- .../{endpointapi => }/TypeClassInstance.java | 2 +- .../purescript/endpointapi/ModuleGen.java | 53 ------------------- 5 files changed, 30 insertions(+), 66 deletions(-) rename scribble-codegen/src/main/java/org/scribble/codegen/purescript/{endpointapi => }/DataType.java (78%) rename scribble-codegen/src/main/java/org/scribble/codegen/purescript/{endpointapi => }/ForeignType.java (90%) rename scribble-codegen/src/main/java/org/scribble/codegen/purescript/{endpointapi => }/TypeClassInstance.java (92%) delete mode 100644 scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/ModuleGen.java diff --git a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/DataType.java b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/DataType.java similarity index 78% rename from scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/DataType.java rename to scribble-codegen/src/main/java/org/scribble/codegen/purescript/DataType.java index 8d1159af5..ead78b1f6 100644 --- a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/DataType.java +++ b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/DataType.java @@ -1,7 +1,4 @@ -package org.scribble.codegen.purescript.endpointapi; - -import org.scribble.type.kind.PayloadTypeKind; -import org.scribble.type.name.PayloadElemType; +package org.scribble.codegen.purescript; import java.util.List; import java.util.Objects; @@ -57,7 +54,14 @@ public String generateDataType() { for (ForeignType type : params) { sb.append(" " + type.name); } - sb.append(" -- TODO: Derive JSON enc/dec\n"); + sb.append("\n"); + if (kind.equals(KIND_TYPE)) { + sb.append("derive instance generic" + name + " :: Generic " + name + " _\n"); + sb.append("instance encodeJson" + name + " :: EncodeJson " + name + " where\n"); + sb.append(" encodeJson = genericEncodeJson\n"); + sb.append("instance decodeJson" + name + " :: DecodeJson " + name + " where\n"); + sb.append(" decodeJson = genericDecodeJson\n"); + } return sb.toString(); } } diff --git a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/ForeignType.java b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/ForeignType.java similarity index 90% rename from scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/ForeignType.java rename to scribble-codegen/src/main/java/org/scribble/codegen/purescript/ForeignType.java index 729da0a6c..979dd47ee 100644 --- a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/ForeignType.java +++ b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/ForeignType.java @@ -1,14 +1,14 @@ -package org.scribble.codegen.purescript.endpointapi; +package org.scribble.codegen.purescript; import java.util.*; public class ForeignType { + public static final String PRIM_MODULE = "Prim"; + public static final String[] PRIMS = new String[]{"Number", "Int", "String", "Char", "Boolean"}; public final String name; public final String source; // Primitive types in the language - no need to explicitly import // See https://pursuit.purescript.org/builtins/docs/Prim - // TODO: Don't generate import statements for them - public static final String[] PRIMS = new String[]{"Number", "Int", "String", "Char", "Boolean"}; public ForeignType(String name, String source) { this.name = name; @@ -29,6 +29,8 @@ public static String generateImports(Set types) { } } for (String source : imports.keySet()) { + // No need to explicitly import + if (source.equals(PRIM_MODULE)) continue; is.append("import " + source + " ("); List ts = new ArrayList<>(imports.get(source)); is.append(ts.get(0)); diff --git a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java index 4b97b4f0b..c52c4111c 100644 --- a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java +++ b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java @@ -18,9 +18,6 @@ import org.scribble.ast.Module; import org.scribble.ast.NonProtocolDecl; import org.scribble.ast.global.GProtocolDecl; -import org.scribble.codegen.purescript.endpointapi.DataType; -import org.scribble.codegen.purescript.endpointapi.ForeignType; -import org.scribble.codegen.purescript.endpointapi.TypeClassInstance; import org.scribble.main.Job; import org.scribble.main.JobContext; import org.scribble.main.ScribbleException; @@ -65,7 +62,6 @@ public Map generateApi(GProtocolName fullname) throws ScribbleEx if (!foreignType.schema.equals(PURESCRIPT_SCHEMA)) { throw new ScribbleException(foreignType.getSource(), "Unsupported data type schema: " + foreignType.schema); } - System.out.println(foreignType); // There is a bug in the parser "type Int from Int as Int;" foreignImports.put(foreignType.name.toString(), new ForeignType(foreignType.extName, foreignType.extSource)); } @@ -218,6 +214,8 @@ public Map generateApi(GProtocolName fullname) throws ScribbleEx sections.add(moduleDeclaration(moduleName, protocolName)); sections.add(staticImports()); + sections.add(jsonImports()); + // Foreign imports sections.add(ForeignType.generateImports(new HashSet<>(foreignImports.values()))); @@ -274,6 +272,19 @@ private static String staticImports() { return sb.toString(); } + private static String jsonImports() { + StringBuilder sb = new StringBuilder(); + sb.append("-- From purescript-argonaut-codecs\n"); + sb.append("import Data.Argonaut.Decode\n"); + sb.append("import Data.Argonaut.Encode\n"); + sb.append("import Data.Argonaut.Core (Json) -- From purescript-argonaut-core\n"); + sb.append("import Data.Generic.Rep (class Generic) -- From purescript-generics-rep\n"); + sb.append("-- From purescript-argonaut-generic\n"); + sb.append("import Data.Argonaut.Decode.Generic.Rep (genericDecodeJson)\n"); + sb.append("import Data.Argonaut.Encode.Generic.Rep (genericEncodeJson)\n"); + return sb.toString(); + } + private void addDatatype(Map datatypes, EAction action, Map foreignImports) throws RuntimeException { String datatype = action.mid.toString(); if (datatypes.containsKey(datatype)) { diff --git a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/TypeClassInstance.java b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/TypeClassInstance.java similarity index 92% rename from scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/TypeClassInstance.java rename to scribble-codegen/src/main/java/org/scribble/codegen/purescript/TypeClassInstance.java index 7cb791548..d80b0112f 100644 --- a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/TypeClassInstance.java +++ b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/TypeClassInstance.java @@ -1,4 +1,4 @@ -package org.scribble.codegen.purescript.endpointapi; +package org.scribble.codegen.purescript; import java.util.List; diff --git a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/ModuleGen.java b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/ModuleGen.java deleted file mode 100644 index ab6709f22..000000000 --- a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/endpointapi/ModuleGen.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.scribble.codegen.purescript.endpointapi; - -import org.scribble.util.Pair; - -import java.util.List; -import java.util.Map; -import java.util.Set; - -public class ModuleGen { - public final String moduleName; - public final String protocolName; - public final Set foreignTypes; - private Map, List>> efsms; - - public ModuleGen(String moduleName, String protocolName, Set foreignTypes, Map, List>> efsms) { - this.moduleName = moduleName; - this.protocolName = protocolName; - this.foreignTypes = foreignTypes; - this.efsms = efsms; - } - - private String moduleDeclaration() { - return "module Scribble.Protocol." + moduleName + "." + protocolName + " where\n"; - } - - private String staticImports() { - StringBuilder sb = new StringBuilder(); - sb.append("import Scribble.FSM (class Branch, class Initial, class Terminal, class Receive, class Select, class Send, kind Role)\n"); - sb.append("import Type.Row (Cons, Nil)\n"); - sb.append("import Data.Void (Void)\n"); - return sb.toString(); - } - - public String filename() { - return "Scribble/Protocol/" + moduleName.replace('.', '/') + "/" + protocolName + ".purs"; - } - - public String generateApi() { - StringBuilder sb = new StringBuilder(); - sb.append(moduleDeclaration()); - sb.append(ForeignType.generateImports(foreignTypes)); - for (DataType role : efsms.keySet()) { - sb.append(role.generateDataType()); - for (DataType state : efsms.get(role).right) { - sb.append(state.generateDataType()); - } - for (String transition : efsms.get(role).left) { - sb.append(transition); - } - } - return sb.toString(); - } -} From a2f9715ae01f68f3ca46373a519cbf000d0c96be Mon Sep 17 00:00:00 2001 From: Jonathan King Date: Tue, 8 May 2018 19:59:43 +0100 Subject: [PATCH 11/20] Generate ProtocolName, ProtocolRoleNames and RoleName instances --- .../purescript/PSEndpointApiGenerator.java | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java index c52c4111c..47788d424 100644 --- a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java +++ b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java @@ -51,6 +51,18 @@ public Map generateApi(GProtocolName fullname) throws ScribbleEx String moduleName = fullname.getPrefix().toString(); String protocolName = fullname.getSimpleName().toString(); + // Generate protocol type-level information + DataType protocolType = new DataType(protocolName, null, "Protocol", true); + TypeClassInstance protocolNameInst = new TypeClassInstance("protocolName" + protocolType.name, "RoleName", new String[] {protocolType.name, ("\"" + protocolType.name + "\"")}); + + StringBuilder roleNames = new StringBuilder("("); + // For each role make a projection, then traverse the graph getting the states + transitions + for (Role r : gpd.header.roledecls.getRoles()) { + roleNames.append("\"" + r.toString() + "\" ::: "); + } + roleNames.append("SNil)"); + TypeClassInstance protocolRoleNames = new TypeClassInstance("protocolRoleNames" + protocolType.name, "ProtocolRoleNames", new String[] {protocolType.name, roleNames.toString()}); + // Message actions and their corresponding datatype Map datatypes = new HashMap<>(); @@ -230,9 +242,19 @@ public Map generateApi(GProtocolName fullname) throws ScribbleEx } sections.add(dt.toString()); + + // Protocol + sections.add(protocolType.generateDataType()); + sections.add(protocolNameInst.generateInstance()); + sections.add(protocolRoleNames.generateInstance()); + + // ProtocolRoleNames + // EFSMs for (DataType role : efsms.keySet()) { sections.add(role.generateDataType()); + TypeClassInstance roleName = new TypeClassInstance("roleName" + role.name, "RoleName", new String[] {role.name, ("\"" + role.name + "\"")}); + sections.add(roleName.generateInstance()); StringBuilder states = new StringBuilder(); for (DataType state : efsms.get(role).left) { @@ -266,7 +288,8 @@ private static String moduleDeclaration(String moduleName, String protocolName) private static String staticImports() { StringBuilder sb = new StringBuilder(); - sb.append("import Scribble.FSM (class Branch, class Initial, class Terminal, class Receive, class Select, class Send, kind Role)\n"); + sb.append("import Scribble.FSM\n"); + sb.append("import Scribble.Type.SList\n"); sb.append("import Type.Row (Cons, Nil)\n"); sb.append("import Data.Void (Void)\n"); return sb.toString(); From 60bfcdf217f12ae372b4beb8195f33e126c47c90 Mon Sep 17 00:00:00 2001 From: Jonathan King Date: Thu, 10 May 2018 04:05:07 +0100 Subject: [PATCH 12/20] Fix bugs in codegen --- .../java/org/scribble/codegen/purescript/DataType.java | 2 +- .../codegen/purescript/PSEndpointApiGenerator.java | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/DataType.java b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/DataType.java index ead78b1f6..e75c23fab 100644 --- a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/DataType.java +++ b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/DataType.java @@ -45,7 +45,7 @@ public int hashCode() { public String generateDataType() { if (isForeign) { - return "foreign import data " + name + (kind.equals(KIND_TYPE) ? "" : (" :: " + kind)) + "\n"; + return "foreign import data " + name + " :: " + kind + "\n"; } else { // TODO: Potential newtype optimisation for constructors with exactly one value // TODO: Derive JSON encoding/decoding diff --git a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java index 47788d424..62d32d859 100644 --- a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java +++ b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java @@ -53,7 +53,7 @@ public Map generateApi(GProtocolName fullname) throws ScribbleEx // Generate protocol type-level information DataType protocolType = new DataType(protocolName, null, "Protocol", true); - TypeClassInstance protocolNameInst = new TypeClassInstance("protocolName" + protocolType.name, "RoleName", new String[] {protocolType.name, ("\"" + protocolType.name + "\"")}); + TypeClassInstance protocolNameInst = new TypeClassInstance("protocolName" + protocolType.name, "ProtocolName", new String[] {protocolType.name, ("\"" + protocolType.name + "\"")}); StringBuilder roleNames = new StringBuilder("("); // For each role make a projection, then traverse the graph getting the states + transitions @@ -289,7 +289,7 @@ private static String moduleDeclaration(String moduleName, String protocolName) private static String staticImports() { StringBuilder sb = new StringBuilder(); sb.append("import Scribble.FSM\n"); - sb.append("import Scribble.Type.SList\n"); + sb.append("import Scribble.Type.SList (type (:::), SLProxy(..), SNil, symbols)\n"); sb.append("import Type.Row (Cons, Nil)\n"); sb.append("import Data.Void (Void)\n"); return sb.toString(); @@ -298,8 +298,8 @@ private static String staticImports() { private static String jsonImports() { StringBuilder sb = new StringBuilder(); sb.append("-- From purescript-argonaut-codecs\n"); - sb.append("import Data.Argonaut.Decode\n"); - sb.append("import Data.Argonaut.Encode\n"); + sb.append("import Data.Argonaut.Decode (class DecodeJson)\n"); + sb.append("import Data.Argonaut.Encode (class EncodeJson)\n"); sb.append("import Data.Argonaut.Core (Json) -- From purescript-argonaut-core\n"); sb.append("import Data.Generic.Rep (class Generic) -- From purescript-generics-rep\n"); sb.append("-- From purescript-argonaut-generic\n"); From 2c9f8b9e86cb6d86421ec2b6bfcdb5ec0c97a895 Mon Sep 17 00:00:00 2001 From: Jonathan King Date: Fri, 1 Jun 2018 18:10:08 +0100 Subject: [PATCH 13/20] Import tuple --- .../org/scribble/codegen/purescript/PSEndpointApiGenerator.java | 1 + 1 file changed, 1 insertion(+) diff --git a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java index 62d32d859..755880a42 100644 --- a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java +++ b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java @@ -292,6 +292,7 @@ private static String staticImports() { sb.append("import Scribble.Type.SList (type (:::), SLProxy(..), SNil, symbols)\n"); sb.append("import Type.Row (Cons, Nil)\n"); sb.append("import Data.Void (Void)\n"); + sb.append("import Data.Tuple (Tuple)\n"); return sb.toString(); } From bb3097afd99004d90a3c710047f342826b4c427f Mon Sep 17 00:00:00 2001 From: Jonathan King Date: Mon, 5 Nov 2018 00:51:31 +0000 Subject: [PATCH 14/20] Generate code for explicit connections --- .../purescript/PSEndpointApiGenerator.java | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java index 755880a42..4da8a22d1 100644 --- a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java +++ b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java @@ -116,20 +116,29 @@ public Map generateApi(GProtocolName fullname) throws ScribbleEx // Generate the state type String curr = getStateTypeName(s); states.add(new DataType(curr, null, DataType.KIND_TYPE, true)); - switch (s.getStateKind()) { case OUTPUT: { if (s.getAllActions().size() == 1) { String next = getStateTypeName(s.getAllSuccessors().get(0)); EAction action = s.getAllActions().get(0); - String type = action.mid.toString(); String to = action.obj.toString(); - - // Add the instance and message data type - instances.add(new TypeClassInstance(("send" + curr), "Send", new String[] {to, curr, next, type})); - addDatatype(datatypes, action, foreignImports); + if (action.isSend()) { + String type = action.mid.toString(); + // Add the instance and message data type + instances.add(new TypeClassInstance(("send" + curr), "Send", new String[]{to, curr, next, type})); + addDatatype(datatypes, action, foreignImports); + } else if (action.isDisconnect()) { + instances.add(new TypeClassInstance(("disconnect" + curr), "Disconnect", new String[]{r.toString(), to, curr, next})); + + } else if (action.isRequest()) { + instances.add(new TypeClassInstance(("request" + curr), "Request", new String[]{r.toString(), to, curr, next})); + } else { + // TODO: What is wrap-client + do we need to handle it? + throw new ScribbleException(null, "Unsupported action " + s.getStateKind()); + } } else { // If there are multiple output actions, we should treat it as a branch and create some dummy states where we send the label + // TODO: I'm making the assumption that if there are multiple outputs, they are all send -- probably should test this Set> choices = new HashSet<>(); for (int i = 0; i < s.getAllActions().size(); i++) { EAction action = s.getAllActions().get(i); @@ -207,7 +216,11 @@ public Map generateApi(GProtocolName fullname) throws ScribbleEx case TERMINAL: break; case ACCEPT: - throw new ScribbleException(null, "Unsupported action " + s.getStateKind()); + EAction action = s.getAllActions().get(0); + String next = getStateTypeName(s.getAllSuccessors().get(0)); + String to = action.obj.toString(); + instances.add(new TypeClassInstance(("connect" + curr), "Connect", new String[]{r.toString(), to, curr, next})); + break; case WRAP_SERVER: throw new ScribbleException(null, "Unsupported action " + s.getStateKind()); } From fa5305d1bec9ad7940441cb37a95651be58a14b3 Mon Sep 17 00:00:00 2001 From: Jonathan King Date: Sat, 12 Jan 2019 20:47:02 +0000 Subject: [PATCH 15/20] Fix codegen + improve command line usage --- .../java/org/scribble/cli/CLArgParser.java | 2 +- .../codegen/purescript/ForeignType.java | 5 +++ .../purescript/PSEndpointApiGenerator.java | 39 ++++++++++++------- 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/scribble-cli/src/main/java/org/scribble/cli/CLArgParser.java b/scribble-cli/src/main/java/org/scribble/cli/CLArgParser.java index 753c7f8cb..e3d02f7eb 100644 --- a/scribble-cli/src/main/java/org/scribble/cli/CLArgParser.java +++ b/scribble-cli/src/main/java/org/scribble/cli/CLArgParser.java @@ -227,7 +227,6 @@ protected int parseFlag(int i) throws CommandLineException case CLArgParser.VALIDATION_EFSM_FLAG: case CLArgParser.UNFAIR_EFSM_FLAG: case CLArgParser.API_GEN_FLAG: - case CLArgParser.API_GEN_PS_FLAG: case CLArgParser.STATECHAN_API_GEN_FLAG: { return parseProtoAndRoleArgs(flag, i); @@ -241,6 +240,7 @@ protected int parseFlag(int i) throws CommandLineException case CLArgParser.SGRAPH_FLAG: case CLArgParser.UNFAIR_SGRAPH_FLAG: case CLArgParser.SESSION_API_GEN_FLAG: + case CLArgParser.API_GEN_PS_FLAG: { return parseProtoArg(flag, i); } diff --git a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/ForeignType.java b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/ForeignType.java index 979dd47ee..8f2ec7331 100644 --- a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/ForeignType.java +++ b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/ForeignType.java @@ -1,5 +1,7 @@ package org.scribble.codegen.purescript; +import org.scribble.main.RuntimeScribbleException; + import java.util.*; public class ForeignType { @@ -31,6 +33,9 @@ public static String generateImports(Set types) { for (String source : imports.keySet()) { // No need to explicitly import if (source.equals(PRIM_MODULE)) continue; + if (source.trim().length() == 0) { + throw new RuntimeScribbleException("Foreign type import source for " + imports.get(source) + " cannot be empty"); + } is.append("import " + source + " ("); List ts = new ArrayList<>(imports.get(source)); is.append(ts.get(0)); diff --git a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java index 4da8a22d1..4e3c0e415 100644 --- a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java +++ b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java @@ -20,6 +20,7 @@ import org.scribble.ast.global.GProtocolDecl; import org.scribble.main.Job; import org.scribble.main.JobContext; +import org.scribble.main.RuntimeScribbleException; import org.scribble.main.ScribbleException; import org.scribble.model.endpoint.EGraph; import org.scribble.model.endpoint.EState; @@ -48,20 +49,24 @@ public Map generateApi(GProtocolName fullname) throws ScribbleEx GProtocolName simpname = fullname.getSimpleName(); GProtocolDecl gpd = (GProtocolDecl) mod.getProtocolDecl(simpname); + if (!gpd.isExplicitModifier()) { + throw new RuntimeScribbleException("Only protocols with explicit connections are currently supported in this version"); + } + String moduleName = fullname.getPrefix().toString(); String protocolName = fullname.getSimpleName().toString(); // Generate protocol type-level information DataType protocolType = new DataType(protocolName, null, "Protocol", true); - TypeClassInstance protocolNameInst = new TypeClassInstance("protocolName" + protocolType.name, "ProtocolName", new String[] {protocolType.name, ("\"" + protocolType.name + "\"")}); +// TypeClassInstance protocolNameInst = new TypeClassInstance("protocolName" + protocolType.name, "ProtocolName", new String[] {protocolType.name, ("\"" + protocolType.name + "\"")}); - StringBuilder roleNames = new StringBuilder("("); - // For each role make a projection, then traverse the graph getting the states + transitions - for (Role r : gpd.header.roledecls.getRoles()) { - roleNames.append("\"" + r.toString() + "\" ::: "); - } - roleNames.append("SNil)"); - TypeClassInstance protocolRoleNames = new TypeClassInstance("protocolRoleNames" + protocolType.name, "ProtocolRoleNames", new String[] {protocolType.name, roleNames.toString()}); +// StringBuilder roleNames = new StringBuilder("("); +// // For each role make a projection, then traverse the graph getting the states + transitions +// for (Role r : gpd.header.roledecls.getRoles()) { +// roleNames.append("\"" + r.toString() + "\" ::: "); +// } +// roleNames.append("SNil)"); +// TypeClassInstance protocolRoleNames = new TypeClassInstance("protocolRoleNames" + protocolType.name, "ProtocolRoleNames", new String[] {protocolType.name, roleNames.toString()}); // Message actions and their corresponding datatype Map datatypes = new HashMap<>(); @@ -131,7 +136,13 @@ public Map generateApi(GProtocolName fullname) throws ScribbleEx instances.add(new TypeClassInstance(("disconnect" + curr), "Disconnect", new String[]{r.toString(), to, curr, next})); } else if (action.isRequest()) { - instances.add(new TypeClassInstance(("request" + curr), "Request", new String[]{r.toString(), to, curr, next})); + String connectedState = curr + "Connected"; + instances.add(new TypeClassInstance(("connect" + curr), "Connect", new String[]{r.toString(), to, curr, connectedState})); + String type = action.mid.toString(); + // Add the instance and message data type + states.add(new DataType(curr + "Connected", null, DataType.KIND_TYPE, true)); + instances.add(new TypeClassInstance(("send" + curr), "Send", new String[]{to, connectedState, next, type})); + addDatatype(datatypes, action, foreignImports); } else { // TODO: What is wrap-client + do we need to handle it? throw new ScribbleException(null, "Unsupported action " + s.getStateKind()); @@ -219,10 +230,10 @@ public Map generateApi(GProtocolName fullname) throws ScribbleEx EAction action = s.getAllActions().get(0); String next = getStateTypeName(s.getAllSuccessors().get(0)); String to = action.obj.toString(); - instances.add(new TypeClassInstance(("connect" + curr), "Connect", new String[]{r.toString(), to, curr, next})); + instances.add(new TypeClassInstance(("accept" + curr), "Accept", new String[]{r.toString(), to, curr, next})); break; case WRAP_SERVER: - throw new ScribbleException(null, "Unsupported action " + s.getStateKind()); + throw new RuntimeScribbleException("Unsupported action " + s.getStateKind()); } } @@ -258,8 +269,8 @@ public Map generateApi(GProtocolName fullname) throws ScribbleEx // Protocol sections.add(protocolType.generateDataType()); - sections.add(protocolNameInst.generateInstance()); - sections.add(protocolRoleNames.generateInstance()); +// sections.add(protocolNameInst.generateInstance()); +// sections.add(protocolRoleNames.generateInstance()); // ProtocolRoleNames @@ -302,7 +313,7 @@ private static String moduleDeclaration(String moduleName, String protocolName) private static String staticImports() { StringBuilder sb = new StringBuilder(); sb.append("import Scribble.FSM\n"); - sb.append("import Scribble.Type.SList (type (:::), SLProxy(..), SNil, symbols)\n"); +// sb.append("import Scribble.Type.SList (type (:::), SLProxy(..), SNil, symbols)\n"); sb.append("import Type.Row (Cons, Nil)\n"); sb.append("import Data.Void (Void)\n"); sb.append("import Data.Tuple (Tuple)\n"); From d9a713b363bd3bd57f38cfeb4e27d398066bd452 Mon Sep 17 00:00:00 2001 From: Jonathan King Date: Sun, 13 Jan 2019 15:27:06 +0000 Subject: [PATCH 16/20] Include both roles in Branch --- .../scribble/codegen/purescript/PSEndpointApiGenerator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java index 4e3c0e415..19f1202a6 100644 --- a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java +++ b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java @@ -211,7 +211,7 @@ public Map generateApi(GProtocolName fullname) throws ScribbleEx } // All messages must be to the same role, so we can pick the first one - EAction action = s.getAllActions().get(0); + String choosing = s.getAllActions().get(0).obj.toString(); String end = "Nil"; StringBuilder labels = new StringBuilder(); @@ -221,7 +221,7 @@ public Map generateApi(GProtocolName fullname) throws ScribbleEx } labels.append(end); - instances.add(new TypeClassInstance("branch" + curr, "Branch", new String[] {role.name, curr, labels.toString()})); + instances.add(new TypeClassInstance("branch" + curr, "Branch", new String[] {role.name, choosing, curr, labels.toString()})); } break; case TERMINAL: From d95014c26b71b46f5cf5f17d0eb08fad7b757175 Mon Sep 17 00:00:00 2001 From: Jonathan King <979725+jonathanlking@users.noreply.github.com> Date: Sun, 15 Dec 2019 22:59:46 +0000 Subject: [PATCH 17/20] Update PSEndpointApiGenerator.java Fix compatibility with new compiler version --- .../org/scribble/codegen/purescript/PSEndpointApiGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java index 19f1202a6..ebdbf9710 100644 --- a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java +++ b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java @@ -314,7 +314,7 @@ private static String staticImports() { StringBuilder sb = new StringBuilder(); sb.append("import Scribble.FSM\n"); // sb.append("import Scribble.Type.SList (type (:::), SLProxy(..), SNil, symbols)\n"); - sb.append("import Type.Row (Cons, Nil)\n"); + sb.append("import Prim.RowList (Cons, Nil)\n"); sb.append("import Data.Void (Void)\n"); sb.append("import Data.Tuple (Tuple)\n"); return sb.toString(); From 8e72bfde6193c8c8a78cc888e2befc05ee70117d Mon Sep 17 00:00:00 2001 From: Jonathan King <979725+jonathanlking@users.noreply.github.com> Date: Mon, 30 Dec 2019 00:39:24 +0000 Subject: [PATCH 18/20] Use new json encoding --- .../scribble/codegen/purescript/DataType.java | 4 ++-- .../purescript/PSEndpointApiGenerator.java | 21 +++++++++++++++++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/DataType.java b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/DataType.java index e75c23fab..5215f4f3a 100644 --- a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/DataType.java +++ b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/DataType.java @@ -58,9 +58,9 @@ public String generateDataType() { if (kind.equals(KIND_TYPE)) { sb.append("derive instance generic" + name + " :: Generic " + name + " _\n"); sb.append("instance encodeJson" + name + " :: EncodeJson " + name + " where\n"); - sb.append(" encodeJson = genericEncodeJson\n"); + sb.append(" encodeJson = genericEncodeJsonWith jsonEncoding\n"); sb.append("instance decodeJson" + name + " :: DecodeJson " + name + " where\n"); - sb.append(" decodeJson = genericDecodeJson\n"); + sb.append(" decodeJson = genericDecodeJsonWith jsonEncoding\n"); } return sb.toString(); } diff --git a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java index ebdbf9710..940f2ca1e 100644 --- a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java +++ b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java @@ -255,6 +255,9 @@ public Map generateApi(GProtocolName fullname) throws ScribbleEx // Foreign imports sections.add(ForeignType.generateImports(new HashSet<>(foreignImports.values()))); + // TODO: Maybe this should be refactored out? + sections.add(jsonEncoding()); + // Message data types StringBuilder dt = new StringBuilder(); for (String type : datatypes.keySet()) { @@ -328,8 +331,22 @@ private static String jsonImports() { sb.append("import Data.Argonaut.Core (Json) -- From purescript-argonaut-core\n"); sb.append("import Data.Generic.Rep (class Generic) -- From purescript-generics-rep\n"); sb.append("-- From purescript-argonaut-generic\n"); - sb.append("import Data.Argonaut.Decode.Generic.Rep (genericDecodeJson)\n"); - sb.append("import Data.Argonaut.Encode.Generic.Rep (genericEncodeJson)\n"); + sb.append("import Data.Argonaut.Decode.Generic.Rep (genericDecodeJsonWith)\n"); + sb.append("import Data.Argonaut.Encode.Generic.Rep (genericEncodeJsonWith)\n"); + sb.append("import Data.Argonaut.Types.Generic.Rep (Encoding)\n"); + + return sb.toString(); + } + + private static String jsonEncoding() { + StringBuilder sb = new StringBuilder(); + sb.append("jsonEncoding :: Encoding\n"); + sb.append("jsonEncoding =\n"); + sb.append(" { tagKey: \"tag\"\n"); + sb.append(" , valuesKey: \"values\"\n"); + sb.append(" , unwrapSingleArguments: true\n"); + sb.append(" }\n"); + return sb.toString(); } From d08cca6d4eb0f59b2c77a450c509199c0bca36b9 Mon Sep 17 00:00:00 2001 From: Jonathan King <979725+jonathanlking@users.noreply.github.com> Date: Mon, 30 Dec 2019 22:59:09 +0000 Subject: [PATCH 19/20] Generate row instead of rowlist --- .../purescript/PSEndpointApiGenerator.java | 27 +++++++------------ 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java index 940f2ca1e..104eaaeb3 100644 --- a/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java +++ b/scribble-codegen/src/main/java/org/scribble/codegen/purescript/PSEndpointApiGenerator.java @@ -32,6 +32,8 @@ import org.scribble.type.name.Role; import org.scribble.util.Pair; +import static java.util.stream.Collectors.joining; + public class PSEndpointApiGenerator { public static final String PURESCRIPT_SCHEMA = "purescript"; @@ -170,15 +172,11 @@ public Map generateApi(GProtocolName fullname) throws ScribbleEx EAction action = s.getAllActions().get(0); String to = action.obj.toString(); - String end = "Nil"; - StringBuilder labels = new StringBuilder(); - for (Pair option : choices) { - labels.append("(Cons \"" + option.left + "\" " + option.right + " "); - end += ")"; - } - labels.append(end); + String branches = choices.stream() + .map(option -> "\"" + option.left + "\" :: " + option.right) + .collect(joining(", ", "(", ")")); - instances.add(new TypeClassInstance("select" + curr, "Select", new String[] {to, curr, labels.toString()})); + instances.add(new TypeClassInstance("select" + curr, "Select", new String[] {to, curr, branches})); } } break; @@ -213,15 +211,11 @@ public Map generateApi(GProtocolName fullname) throws ScribbleEx // All messages must be to the same role, so we can pick the first one String choosing = s.getAllActions().get(0).obj.toString(); - String end = "Nil"; - StringBuilder labels = new StringBuilder(); - for (Pair option : choices) { - labels.append("(Cons \"" + option.left + "\" " + option.right + " "); - end += ")"; - } - labels.append(end); + String branches = choices.stream() + .map(option -> "\"" + option.left + "\" :: " + option.right) + .collect(joining(", ", "(", ")")); - instances.add(new TypeClassInstance("branch" + curr, "Branch", new String[] {role.name, choosing, curr, labels.toString()})); + instances.add(new TypeClassInstance("branch" + curr, "Branch", new String[] {role.name, choosing, curr, branches})); } break; case TERMINAL: @@ -317,7 +311,6 @@ private static String staticImports() { StringBuilder sb = new StringBuilder(); sb.append("import Scribble.FSM\n"); // sb.append("import Scribble.Type.SList (type (:::), SLProxy(..), SNil, symbols)\n"); - sb.append("import Prim.RowList (Cons, Nil)\n"); sb.append("import Data.Void (Void)\n"); sb.append("import Data.Tuple (Tuple)\n"); return sb.toString(); From 0fafea1e3b2a58a31802d9a625ccf63ca8e229c2 Mon Sep 17 00:00:00 2001 From: Jonathan King <979725+jonathanlking@users.noreply.github.com> Date: Sun, 12 Apr 2020 01:20:29 +0100 Subject: [PATCH 20/20] Fix Dockerfile There is now also a .dockerignore which should prevent cache invalidation of the Java project whenever the Dockerfile is modified. --- .dockerignore | 2 ++ Dockerfile | 10 ++++------ 2 files changed, 6 insertions(+), 6 deletions(-) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..4a246ec6c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +Dockerfile +.dockerignore diff --git a/Dockerfile b/Dockerfile index 0a2e2d652..2a1001e5c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,11 @@ -FROM maven:latest -RUN apt-get update && apt-get install vim tree less -y - -# RUN git clone https://github.com/scribble/scribble-java.git \ +FROM maven:3.6.3-jdk-14 +RUN yum install -y unzip COPY . /scribble-java RUN cd scribble-java \ && mvn install -Dlicense.skip=true \ && mv scribble-dist/target/scribble-dist* / + RUN unzip scribble-dist* -RUN cp scribble-java/scribble-core/src/test/scrib/tmp/Test.scr . RUN chmod 755 scribblec.sh -ENTRYPOINT ["/bin/bash"] +ENTRYPOINT ["./scribblec.sh"]