├── src ├── cwp │ └── core.clj └── leiningen │ └── cwp.clj ├── doc ├── imgs │ └── words-count.png ├── overview.md └── syntax-and-transpiling.md ├── .gitignore ├── src-java └── cwp │ ├── lexer │ ├── readers │ │ ├── Comment.java │ │ ├── Raw.java │ │ ├── Conditional.java │ │ ├── SymbolicValue.java │ │ ├── NamespaceMap.java │ │ ├── Char.java │ │ ├── Number.java │ │ ├── Str.java │ │ └── Identifier.java │ ├── IdentReader.java │ ├── Common.java │ ├── CharReader.java │ ├── Token.java │ └── LexerReader.java │ ├── parser │ ├── ParserException.java │ ├── Controls.java │ └── Parser.java │ └── ast │ ├── EofExpr.java │ ├── SimpleExpr.java │ ├── Expr.java │ ├── WithMetaExpr.java │ ├── InfixExpr.java │ ├── VectorExpr.java │ ├── SetExpr.java │ ├── UnaryExpr.java │ ├── FunctionCallExpr.java │ ├── ParensExpr.java │ ├── MapExpr.java │ ├── ConditionalExpr.java │ ├── FnExpr.java │ ├── CatchExpr.java │ ├── MultiInfixExpr.java │ ├── IfElseExpr.java │ ├── TryCatchFinallyExpr.java │ ├── ControlExpr.java │ ├── DefExpr.java │ └── NsExpr.java ├── project.clj ├── README.md ├── test └── cwp │ └── core_test.clj └── LICENSE /src/cwp/core.clj: -------------------------------------------------------------------------------- 1 | (ns cwp.core) 2 | -------------------------------------------------------------------------------- /doc/imgs/words-count.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilevd/cwp/HEAD/doc/imgs/words-count.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | profiles.clj 5 | pom.xml 6 | pom.xml.asc 7 | *.jar 8 | *.class 9 | /.lein-* 10 | /.nrepl-port 11 | /.prepl-port 12 | .hgignore 13 | .hg/ 14 | *.iml 15 | .idea/ 16 | .lein-failures 17 | .nrepl-port -------------------------------------------------------------------------------- /src-java/cwp/lexer/readers/Comment.java: -------------------------------------------------------------------------------- 1 | package cwp.lexer.readers; 2 | 3 | import cwp.lexer.CharReader; 4 | import cwp.lexer.Token; 5 | 6 | public class Comment { 7 | 8 | 9 | static public void read(CharReader r) { 10 | 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src-java/cwp/parser/ParserException.java: -------------------------------------------------------------------------------- 1 | package cwp.parser; 2 | 3 | import cwp.lexer.Token; 4 | 5 | public class ParserException extends Exception { 6 | 7 | public ParserException(String message) { 8 | super(message); 9 | } 10 | 11 | public ParserException(String message, Token token) { 12 | super(message + ", at line: " + token.line + ", column: " + token.column); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src-java/cwp/ast/EofExpr.java: -------------------------------------------------------------------------------- 1 | package cwp.ast; 2 | 3 | import cwp.lexer.Token; 4 | 5 | public class EofExpr extends Expr { 6 | 7 | public static EofExpr EOF_EXPR = new EofExpr(new Token(Token.Type.EOF)); 8 | 9 | public EofExpr(Token token) { 10 | super(token); 11 | } 12 | 13 | @Override 14 | public String toString() { 15 | return "EofExpr: " + initTok.toString(); 16 | } 17 | 18 | public String gen() { 19 | return ""; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject org.clojars.ilevd/cwp "0.1.1" 2 | :description "Indentation-based syntax for Clojure" 3 | :url "https://github.com/ilevd/cwp" 4 | :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0" 5 | :url "https://www.eclipse.org/legal/epl-2.0/"} 6 | 7 | :dependencies [[org.clojure/clojure "1.12.0"] 8 | [zprint "1.2.9"]] 9 | 10 | :java-source-paths ["src-java"] 11 | 12 | :repl-options {:init-ns cwp.core} 13 | 14 | :eval-in-leiningen true) 15 | -------------------------------------------------------------------------------- /src-java/cwp/ast/SimpleExpr.java: -------------------------------------------------------------------------------- 1 | package cwp.ast; 2 | 3 | import cwp.lexer.Token; 4 | 5 | public class SimpleExpr extends Expr { 6 | 7 | public SimpleExpr(Token initTok) { 8 | super(initTok); 9 | } 10 | 11 | public SimpleExpr(Token initTok, boolean callable) { 12 | super(initTok, callable); 13 | } 14 | 15 | @Override 16 | public String toString() { 17 | return "SimpleExpr: " + initTok.toString(); 18 | } 19 | 20 | @Override 21 | public String gen() { 22 | return initTok.str; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src-java/cwp/ast/Expr.java: -------------------------------------------------------------------------------- 1 | package cwp.ast; 2 | 3 | import cwp.lexer.Token; 4 | 5 | public class Expr { 6 | 7 | public Token initTok; 8 | public boolean callable = false; 9 | 10 | public Expr() { 11 | } 12 | 13 | public Expr(Token initTok) { 14 | this.initTok = initTok; 15 | } 16 | 17 | public Expr(Token initTok, boolean callable) { 18 | this.initTok = initTok; 19 | this.callable = callable; 20 | } 21 | 22 | @Override 23 | public String toString() { 24 | return "Expr: " + initTok.toString(); 25 | } 26 | 27 | public String gen() { 28 | return ""; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src-java/cwp/ast/WithMetaExpr.java: -------------------------------------------------------------------------------- 1 | package cwp.ast; 2 | 3 | import cwp.lexer.Token; 4 | 5 | public class WithMetaExpr extends Expr { 6 | 7 | Expr meta; 8 | Expr expr; 9 | 10 | public WithMetaExpr(Token initTok, Expr meta, Expr expr) { 11 | super(initTok); 12 | this.expr = expr; 13 | this.meta = meta; 14 | } 15 | 16 | @Override 17 | public String toString() { 18 | return "WithMetaExpr{" + 19 | "meta=" + meta + 20 | ", expr=" + expr + 21 | '}'; 22 | } 23 | 24 | @Override 25 | public String gen() { 26 | return "^" + meta.gen() + 27 | " " + 28 | expr.gen(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src-java/cwp/lexer/readers/Raw.java: -------------------------------------------------------------------------------- 1 | package cwp.lexer.readers; 2 | 3 | import clojure.lang.Util; 4 | import cwp.lexer.CharReader; 5 | import cwp.lexer.Token; 6 | 7 | public class Raw { 8 | 9 | public static Token read(CharReader r, int line, int column) { 10 | r.read1(); 11 | r.read1(); 12 | StringBuilder sb = new StringBuilder(); 13 | for (; ; ) { 14 | int ch = r.read1(); 15 | if (ch == -1) 16 | throw Util.runtimeException("EOF while reading raw string"); 17 | if (ch == '"' && r.cur() == '"' && r.next() == '"') { 18 | r.read1(); 19 | r.read1(); 20 | return new Token(Token.Type.RAW, sb.toString(), null, line, column); 21 | } 22 | sb.append((char) ch); 23 | } 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src-java/cwp/lexer/readers/Conditional.java: -------------------------------------------------------------------------------- 1 | package cwp.lexer.readers; 2 | 3 | import clojure.lang.Util; 4 | import cwp.lexer.CharReader; 5 | import cwp.lexer.Token; 6 | 7 | public class Conditional { 8 | 9 | static public Token read(CharReader r, int line, int column) { 10 | boolean splicing = false; 11 | int splicingChar = r.read1(); 12 | if (splicingChar == '@') 13 | splicing = true; 14 | else 15 | r.unread1(splicingChar); 16 | 17 | int nextChar = r.read1(); 18 | if (nextChar == -1) 19 | throw Util.runtimeException("EOF while reading character"); 20 | if (nextChar != '(') { 21 | throw Util.runtimeException("read-cond body must be a list"); 22 | } 23 | return new Token(Token.Type.CONDITIONAL, "#?" + (splicing ? "@" : "") + "(", null, line, column); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src-java/cwp/ast/InfixExpr.java: -------------------------------------------------------------------------------- 1 | package cwp.ast; 2 | 3 | import cwp.lexer.Token; 4 | 5 | public class InfixExpr extends Expr { 6 | 7 | public Expr leftExpr; 8 | public Expr rightExpr; 9 | 10 | public InfixExpr(Token initTok, Expr leftExpr, Expr rightExpr) { 11 | super(initTok); 12 | this.leftExpr = leftExpr; 13 | this.rightExpr = rightExpr; 14 | } 15 | 16 | @Override 17 | public String toString() { 18 | return "InfixExpr: " + initTok.toString(); 19 | } 20 | 21 | public static String toOp(String s) { 22 | if (s.equals("|>")) return "->"; 23 | if (s.equals("|>>")) return "->>"; 24 | if (s.equals("!=")) return "not="; 25 | return s; 26 | } 27 | 28 | @Override 29 | public String gen() { 30 | return "(" + toOp(initTok.str) + " " + leftExpr.gen() + " " + rightExpr.gen() + ")"; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src-java/cwp/ast/VectorExpr.java: -------------------------------------------------------------------------------- 1 | package cwp.ast; 2 | 3 | import cwp.lexer.Token; 4 | 5 | import java.util.ArrayList; 6 | 7 | public class VectorExpr extends Expr { 8 | public ArrayList args; 9 | 10 | public VectorExpr(Token initTok, ArrayList a, boolean callable) { 11 | super(initTok, callable); 12 | this.args = a; 13 | } 14 | 15 | @Override 16 | public String toString() { 17 | return "VectorExpr: " + initTok.toString(); 18 | } 19 | 20 | @Override 21 | public String gen() { 22 | StringBuilder sb = new StringBuilder("["); 23 | for (int i = 0; i < args.size() - 1; i++) { 24 | sb.append(args.get(i).gen()); 25 | sb.append(" "); 26 | } 27 | if (!args.isEmpty()) { 28 | sb.append(args.get(args.size() - 1).gen()); 29 | } 30 | sb.append("]"); 31 | return sb.toString(); 32 | } 33 | } -------------------------------------------------------------------------------- /src-java/cwp/ast/SetExpr.java: -------------------------------------------------------------------------------- 1 | package cwp.ast; 2 | 3 | import cwp.lexer.Token; 4 | 5 | import java.util.ArrayList; 6 | 7 | public class SetExpr extends Expr { 8 | public ArrayList args; 9 | 10 | public SetExpr(Token initTok, ArrayList a, boolean callable) { 11 | super(initTok, callable); 12 | this.args = a; 13 | } 14 | 15 | @Override 16 | public String toString() { 17 | return "SetExpr: " + initTok.toString(); 18 | } 19 | 20 | @Override 21 | public String gen() { 22 | StringBuilder sb = new StringBuilder("#{"); 23 | for (int i = 0; i < args.size() - 1; i++) { 24 | sb.append(args.get(i).gen()); 25 | sb.append(" "); 26 | } 27 | if (!args.isEmpty()) { 28 | sb.append(args.get(args.size() - 1).gen()); 29 | } 30 | sb.append("}"); 31 | return sb.toString(); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src-java/cwp/lexer/readers/SymbolicValue.java: -------------------------------------------------------------------------------- 1 | package cwp.lexer.readers; 2 | 3 | import clojure.lang.Util; 4 | import cwp.lexer.CharReader; 5 | import cwp.lexer.LexerReader; 6 | import cwp.lexer.Token; 7 | 8 | public class SymbolicValue { 9 | public static Token read(CharReader r, int line, int column) { 10 | Token t = LexerReader.read(r); 11 | if (t.type != Token.Type.SYMBOL) 12 | throw Util.runtimeException("Invalid token: ##" + t.str); 13 | if (t.str.equals("NaN")) 14 | return new Token(Token.Type.SYMBOLIC_VALUE, "##NaN", null, line, column); 15 | if (t.str.equals("Inf")) 16 | return new Token(Token.Type.SYMBOLIC_VALUE, "##Inf", null, line, column); 17 | if (t.str.equals("-Inf")) 18 | return new Token(Token.Type.SYMBOLIC_VALUE, "##-Inf", null, line, column); 19 | throw Util.runtimeException("Unknown symbolic value: ##" + t.str); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src-java/cwp/ast/UnaryExpr.java: -------------------------------------------------------------------------------- 1 | package cwp.ast; 2 | 3 | import cwp.lexer.Token; 4 | 5 | public class UnaryExpr extends Expr { 6 | 7 | Expr val; 8 | boolean asFn = false; 9 | 10 | public UnaryExpr(Token initTok, Expr val) { 11 | super(initTok); 12 | this.val = val; 13 | } 14 | 15 | public UnaryExpr(Token initTok, Expr val, boolean asFn) { 16 | super(initTok); 17 | this.val = val; 18 | this.asFn = asFn; 19 | } 20 | 21 | @Override 22 | public String toString() { 23 | return "UnaryExpr: " + initTok.toString() + val.initTok.str; 24 | } 25 | 26 | @Override 27 | public String gen() { 28 | if (asFn) { 29 | return genFn(); 30 | } 31 | return genPrefix(); 32 | } 33 | 34 | public String genFn() { 35 | return "(" + initTok.str + " " + val.gen() + ")"; 36 | } 37 | 38 | public String genPrefix() { 39 | return initTok.str + val.gen(); 40 | } 41 | 42 | } 43 | 44 | 45 | -------------------------------------------------------------------------------- /src-java/cwp/ast/FunctionCallExpr.java: -------------------------------------------------------------------------------- 1 | package cwp.ast; 2 | 3 | import java.util.ArrayList; 4 | 5 | public class FunctionCallExpr extends Expr { 6 | public Expr first; 7 | public ArrayList args; 8 | 9 | public FunctionCallExpr(Expr first, ArrayList args, boolean callable) { 10 | super(first.initTok, callable); 11 | this.first = first; 12 | this.args = args; 13 | } 14 | 15 | @Override 16 | public String toString() { 17 | return "FunctionCallExpr: " + initTok.toString(); 18 | } 19 | 20 | @Override 21 | public String gen() { 22 | StringBuilder sb = new StringBuilder("("); 23 | sb.append(first.gen()); 24 | if (!args.isEmpty()) sb.append(" "); 25 | for (int i = 0; i < args.size() - 1; i++) { 26 | sb.append(args.get(i).gen()); 27 | sb.append(" "); 28 | } 29 | if (!args.isEmpty()) { 30 | sb.append(args.get(args.size() - 1).gen()); 31 | } 32 | sb.append(")"); 33 | return sb.toString(); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src-java/cwp/ast/ParensExpr.java: -------------------------------------------------------------------------------- 1 | package cwp.ast; 2 | 3 | import cwp.lexer.Token; 4 | 5 | import java.util.ArrayList; 6 | 7 | public class ParensExpr extends Expr { 8 | public ArrayList args; 9 | 10 | public ParensExpr(Token initTok, ArrayList a, boolean callable) { 11 | super(initTok, callable); 12 | this.args = a; 13 | } 14 | 15 | @Override 16 | public String toString() { 17 | return "ParensExpr: " + initTok.toString(); 18 | } 19 | 20 | @Override 21 | public String gen() { 22 | if (args.size() == 1) { 23 | return args.get(0).gen(); 24 | } else { 25 | StringBuilder sb = new StringBuilder("(do "); 26 | for (int i = 0; i < args.size() - 1; i++) { 27 | sb.append(args.get(i).gen()); 28 | sb.append(" "); 29 | } 30 | if (!args.isEmpty()) { 31 | sb.append(args.get(args.size() - 1).gen()); 32 | } 33 | sb.append(")"); 34 | return sb.toString(); 35 | } 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /src-java/cwp/ast/MapExpr.java: -------------------------------------------------------------------------------- 1 | package cwp.ast; 2 | 3 | import cwp.lexer.Token; 4 | 5 | import java.util.ArrayList; 6 | 7 | public class MapExpr extends Expr { 8 | public ArrayList args; 9 | 10 | public MapExpr(Token initTok, ArrayList a, boolean callable) { 11 | super(initTok, callable); 12 | this.args = a; 13 | } 14 | 15 | @Override 16 | public String toString() { 17 | return "MapExpr: " + initTok.toString(); 18 | } 19 | 20 | @Override 21 | public String gen() { 22 | StringBuilder sb = new StringBuilder(initTok.str); 23 | for (int i = 0; i < args.size() - 2; i += 2) { 24 | sb.append(args.get(i).gen()); 25 | sb.append(" "); 26 | sb.append(args.get(i + 1).gen()); 27 | sb.append(", "); 28 | } 29 | if (!args.isEmpty()) { 30 | sb.append(args.get(args.size() - 2).gen()); 31 | sb.append(" "); 32 | sb.append(args.get(args.size() - 1).gen()); 33 | } 34 | sb.append("}"); 35 | return sb.toString(); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src-java/cwp/ast/ConditionalExpr.java: -------------------------------------------------------------------------------- 1 | package cwp.ast; 2 | 3 | import cwp.lexer.Token; 4 | 5 | import java.util.ArrayList; 6 | 7 | public class ConditionalExpr extends Expr { 8 | public ArrayList args; 9 | 10 | public ConditionalExpr(Token initTok, ArrayList a, boolean callable) { 11 | super(initTok, callable); 12 | this.args = a; 13 | } 14 | 15 | @Override 16 | public String toString() { 17 | return "ConditionalExpr: " + initTok.toString(); 18 | } 19 | 20 | @Override 21 | public String gen() { 22 | StringBuilder sb = new StringBuilder(initTok.str); 23 | for (int i = 0; i < args.size() - 2; i += 2) { 24 | sb.append(args.get(i).gen()); 25 | sb.append(" "); 26 | sb.append(args.get(i + 1).gen()); 27 | sb.append(", "); 28 | } 29 | if (!args.isEmpty()) { 30 | sb.append(args.get(args.size() - 2).gen()); 31 | sb.append(" "); 32 | sb.append(args.get(args.size() - 1).gen()); 33 | } 34 | sb.append(")"); 35 | return sb.toString(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src-java/cwp/ast/FnExpr.java: -------------------------------------------------------------------------------- 1 | package cwp.ast; 2 | 3 | import cwp.lexer.Token; 4 | 5 | import java.util.ArrayList; 6 | 7 | public class FnExpr extends Expr { 8 | 9 | ArrayList args; 10 | ArrayList body; 11 | 12 | public FnExpr(Token initTok, ArrayList args, ArrayList body) { 13 | super(initTok); 14 | this.args = args; 15 | this.body = body; 16 | } 17 | 18 | @Override 19 | public String gen() { 20 | StringBuilder sb = new StringBuilder("(fn ["); 21 | for (int i = 0; i < args.size() - 1; i++) { 22 | sb.append(args.get(i).gen()); 23 | sb.append(" "); 24 | } 25 | if (!args.isEmpty()) { 26 | sb.append(args.get(args.size() - 1).gen()); 27 | } 28 | sb.append("] "); 29 | for (int i = 0; i < body.size() - 1; i++) { 30 | sb.append(body.get(i).gen()); 31 | sb.append(" "); 32 | } 33 | if (!body.isEmpty()) { 34 | sb.append(body.get(body.size() - 1).gen()); 35 | } 36 | sb.append(")"); 37 | return sb.toString(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src-java/cwp/lexer/IdentReader.java: -------------------------------------------------------------------------------- 1 | package cwp.lexer; 2 | 3 | import clojure.lang.Util; 4 | 5 | import java.util.ArrayList; 6 | 7 | public class IdentReader { 8 | 9 | 10 | CharReader r; 11 | ArrayList identation; 12 | int curLine = 1; 13 | 14 | public IdentReader(String s) { 15 | CharReader r = new CharReader(s); 16 | identation.add(1); 17 | } 18 | 19 | 20 | public Token read() { 21 | Token t = LexerReader.read(r); 22 | return t; 23 | } 24 | 25 | public ArrayList readAll() { 26 | ArrayList arr = new ArrayList(); 27 | Token t = LexerReader.read(r); 28 | 29 | curLine = t.line; 30 | if (t.column != identation.get(identation.size() - 1)) { 31 | Util.runtimeException("EOF while reading character"); 32 | } 33 | 34 | while (t.type != Token.Type.EOF) { 35 | arr.add(t); 36 | 37 | t = LexerReader.read(r); 38 | if (t.type == Token.Type.COLON) { 39 | 40 | } 41 | 42 | 43 | } 44 | arr.add(t); 45 | return arr; 46 | 47 | } 48 | 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src-java/cwp/ast/CatchExpr.java: -------------------------------------------------------------------------------- 1 | package cwp.ast; 2 | 3 | import cwp.lexer.Token; 4 | 5 | import java.util.ArrayList; 6 | 7 | public class CatchExpr extends Expr { 8 | public Token exceptionType; 9 | public Token exceptionName; 10 | public ArrayList body; 11 | 12 | public CatchExpr(Token initTok, Token exceptionType, Token exceptionName, ArrayList body) { 13 | super(initTok); 14 | this.exceptionName = exceptionName; 15 | this.exceptionType = exceptionType; 16 | this.body = body; 17 | } 18 | 19 | @Override 20 | public String toString() { 21 | return initTok.toString(); 22 | } 23 | 24 | @Override 25 | public String gen() { 26 | StringBuilder sb = new StringBuilder("(catch "); 27 | sb.append(exceptionType.str); 28 | sb.append(" "); 29 | sb.append(exceptionName.str); 30 | sb.append(" "); 31 | for (int i = 0; i < body.size() - 1; i++) { 32 | sb.append(body.get(i).gen()); 33 | sb.append("\n"); 34 | } 35 | if (!body.isEmpty()) { 36 | sb.append(body.get(body.size() - 1).gen()); 37 | } 38 | sb.append(")"); 39 | return sb.toString(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src-java/cwp/lexer/Common.java: -------------------------------------------------------------------------------- 1 | /* 2 | Based on Clojure LispReader: 3 | https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/LispReader.java 4 | */ 5 | package cwp.lexer; 6 | 7 | public class Common { 8 | 9 | static Integer[] macros = new Integer[256]; 10 | 11 | static { 12 | macros['"'] = 1; // String 13 | macros[';'] = 1; // Comment 14 | // macros['\''] // Quote 15 | // macros['@'] // Deref 16 | macros['^'] = 1; // Meta 17 | macros['`'] = 1; // SyntaxQuote 18 | // macros['~'] // Unquote 19 | macros['('] = 1; // List 20 | macros[')'] = 1; // UnmatchedDelimiter 21 | macros['['] = 1; // Vector 22 | macros[']'] = 1; // UnmatchedDelimiter 23 | macros['{'] = 1; // Map 24 | macros['}'] = 1; // UnmatchedDelimiter 25 | // macros['|'] // ArgVector 26 | macros['\\'] = 1;// Character 27 | // macros['%'] // Arg 28 | macros['#'] = 1; // DispatchReader 29 | } 30 | 31 | public static boolean isWhitespace(int c) { 32 | return Character.isWhitespace(c); 33 | } 34 | 35 | static public boolean isMacro(int ch) { 36 | return (ch < macros.length && macros[ch] != null); 37 | } 38 | 39 | static public boolean isTerminatingMacro(int ch) { 40 | return (ch != '#' && ch != '\'' && ch != '%' && isMacro(ch)); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/leiningen/cwp.clj: -------------------------------------------------------------------------------- 1 | (ns leiningen.cwp 2 | (:require [clojure.java.io :as io] 3 | [clojure.string :as str] 4 | [zprint.core :as zp]) 5 | (:import (cwp.parser Parser))) 6 | 7 | 8 | (def ^:dynamic ext-mappings {"cw" "clj" 9 | "cws" "cljs" 10 | "cwc" "cljc"}) 11 | 12 | (defn ends-with-ext? [s] 13 | (->> (keys ext-mappings) 14 | (map #(str "." %)) 15 | (some #(str/ends-with? s %)))) 16 | 17 | (defn ext-reg [] 18 | (->> (keys ext-mappings) 19 | (str/join "$|") 20 | re-pattern)) 21 | 22 | (defn run [{:keys [in out]}] 23 | (doseq [f (file-seq (io/file in)) 24 | :let [in-path (.getPath f)]] 25 | (when (ends-with-ext? in-path) 26 | (let [o (-> in-path 27 | (str/replace-first (re-pattern in) out) 28 | (str/replace (ext-reg) ext-mappings)) 29 | file-out (io/file o) 30 | in-str (slurp f) 31 | out-str (Parser/genStr in-str) 32 | format-out-str (zp/zprint-file-str out-str in-path)] 33 | (.mkdirs (.getParentFile file-out)) 34 | (spit file-out format-out-str))))) 35 | 36 | 37 | (defn cwp [project & args] 38 | (let [builds (-> project :cwp :builds)] 39 | (doseq [b builds] 40 | (run b)))) 41 | 42 | 43 | (comment 44 | (run {:in "src-e" 45 | :out "src"}) 46 | ) -------------------------------------------------------------------------------- /src-java/cwp/ast/MultiInfixExpr.java: -------------------------------------------------------------------------------- 1 | package cwp.ast; 2 | 3 | import cwp.lexer.Token; 4 | 5 | import java.util.ArrayList; 6 | 7 | public class MultiInfixExpr extends Expr { 8 | 9 | public ArrayList exprs; 10 | public Token opToken; 11 | 12 | public MultiInfixExpr(Token initTok, Token opToken, Expr expr) { 13 | super(initTok); 14 | this.opToken = opToken; 15 | this.exprs = new ArrayList(); 16 | this.exprs.add(expr); 17 | } 18 | 19 | public void add(Expr e) { 20 | this.exprs.add(e); 21 | } 22 | 23 | public Expr getExpr() { 24 | if (exprs.size() == 1) { 25 | return exprs.get(0); 26 | } else { 27 | return this; 28 | } 29 | } 30 | 31 | @Override 32 | public String toString() { 33 | return "MultiInfixExpr: " + initTok.toString(); 34 | } 35 | 36 | public static String toOp(String s) { 37 | if (s.equals("|>")) return "->"; 38 | if (s.equals("|>>")) return "->>"; 39 | if (s.equals("!=")) return "not="; 40 | return s; 41 | } 42 | 43 | @Override 44 | public String gen() { 45 | StringBuilder sb = new StringBuilder(); 46 | sb.append("(").append(toOp(opToken.str)); 47 | for (Expr expr : exprs) { 48 | sb.append(" ").append(expr.gen()); 49 | } 50 | sb.append(")"); 51 | return sb.toString(); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src-java/cwp/ast/IfElseExpr.java: -------------------------------------------------------------------------------- 1 | package cwp.ast; 2 | 3 | import cwp.lexer.Token; 4 | 5 | import java.util.ArrayList; 6 | 7 | public class IfElseExpr extends Expr { 8 | 9 | public Expr condExpr; 10 | public ArrayList ifExprs; 11 | public ArrayList elseExprs; 12 | 13 | public IfElseExpr(Token initTok, Expr condExpr, ArrayList ifExprs, ArrayList elseExprs) { 14 | super(initTok); 15 | this.condExpr = condExpr; 16 | this.ifExprs = ifExprs; 17 | this.elseExprs = elseExprs; 18 | } 19 | 20 | @Override 21 | public String toString() { 22 | return "IfElseExpr: " + initTok.toString(); 23 | } 24 | 25 | 26 | public String gen() { 27 | return "(if " + condExpr.gen() + " " + 28 | genIf() + 29 | (elseExprs != null ? " " + genElse() : "") + 30 | ")"; 31 | } 32 | 33 | public String genIf() { 34 | return (ifExprs.size() == 1 ? ifExprs.get(0).gen() : 35 | ("(do " + arrToStr(ifExprs) + ")")); 36 | } 37 | 38 | public String genElse() { 39 | return (elseExprs.size() == 1 ? elseExprs.get(0).gen() : 40 | ("(do " + arrToStr(elseExprs) + ")")); 41 | } 42 | 43 | 44 | public String arrToStr(ArrayList a) { 45 | StringBuilder sb = new StringBuilder(); 46 | for (int i = 0; i < a.size() - 1; i++) { 47 | sb.append(a.get(i).gen()); 48 | sb.append(" "); 49 | } 50 | sb.append(a.get(a.size() - 1).gen()); 51 | return sb.toString(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src-java/cwp/lexer/readers/NamespaceMap.java: -------------------------------------------------------------------------------- 1 | /* 2 | Based on Clojure LispReader: 3 | https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/LispReader.java 4 | */ 5 | package cwp.lexer.readers; 6 | 7 | import clojure.lang.Util; 8 | import cwp.lexer.CharReader; 9 | import cwp.lexer.Common; 10 | import cwp.lexer.Token; 11 | 12 | public class NamespaceMap { 13 | 14 | static public Token read(CharReader r, int line, int column) { 15 | String sym = ""; 16 | boolean auto = false; 17 | int autoChar = r.read1(); 18 | if (autoChar == ':') 19 | auto = true; 20 | else 21 | r.unread1(autoChar); 22 | int nextChar = r.read1(); 23 | if (Common.isWhitespace(nextChar)) { // the #:: { } case or an error 24 | if (auto) { 25 | while (Common.isWhitespace(nextChar)) 26 | nextChar = r.read1(); 27 | if (nextChar != '{') { 28 | r.unread1(nextChar); 29 | throw Util.runtimeException("Namespaced map must specify a namespace"); 30 | } 31 | } else { 32 | r.unread1(nextChar); 33 | throw Util.runtimeException("Namespaced map must specify a namespace"); 34 | } 35 | } else if (nextChar != '{') { // #:foo { } or #::foo { } 36 | sym = Identifier.read(r, (char) nextChar); 37 | nextChar = r.read1(); 38 | while (Common.isWhitespace(nextChar)) 39 | nextChar = r.read1(); 40 | } 41 | if (nextChar != '{') 42 | throw Util.runtimeException("Namespaced map must specify a map"); 43 | return new Token(Token.Type.NAMESPACE_MAP, "#:" + (auto ? ":" : "") + sym + "{", null, line, column); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src-java/cwp/ast/TryCatchFinallyExpr.java: -------------------------------------------------------------------------------- 1 | package cwp.ast; 2 | 3 | import cwp.lexer.Token; 4 | 5 | import java.util.ArrayList; 6 | 7 | public class TryCatchFinallyExpr extends Expr { 8 | 9 | ArrayList body; 10 | ArrayList catches; 11 | ArrayList finallyExprs; 12 | 13 | 14 | public TryCatchFinallyExpr(Token initTok, ArrayList body, ArrayList catches, ArrayList finallies) { 15 | super(initTok); 16 | this.body = body; 17 | this.catches = catches; 18 | this.finallyExprs = finallies; 19 | } 20 | 21 | @Override 22 | public String gen() { 23 | StringBuilder sb = new StringBuilder("(try "); 24 | for (int i = 0; i < body.size() - 1; i++) { 25 | sb.append(body.get(i).gen()); 26 | sb.append("\n"); 27 | } 28 | if (!body.isEmpty()) { 29 | sb.append(body.get(body.size() - 1).gen()); 30 | } 31 | // add catches 32 | for (int i = 0; i < catches.size() - 1; i++) { 33 | sb.append(catches.get(i).gen()); 34 | sb.append("\n"); 35 | } 36 | if (!catches.isEmpty()) { 37 | sb.append(catches.get(catches.size() - 1).gen()); 38 | } 39 | // add finally 40 | if (!finallyExprs.isEmpty()) { 41 | sb.append("(finally "); 42 | for (int i = 0; i < finallyExprs.size() - 1; i++) { 43 | sb.append(finallyExprs.get(i).gen()); 44 | sb.append("\n"); 45 | } 46 | if (!finallyExprs.isEmpty()) { 47 | sb.append(finallyExprs.get(finallyExprs.size() - 1).gen()); 48 | } 49 | sb.append(")"); 50 | } 51 | sb.append(")"); 52 | return sb.toString(); 53 | } 54 | 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src-java/cwp/lexer/CharReader.java: -------------------------------------------------------------------------------- 1 | package cwp.lexer; 2 | 3 | public class CharReader { 4 | 5 | static private final char NEW_LINE = '\n'; 6 | 7 | public String s; 8 | public int i = 0; 9 | public int lineNumber = 1; 10 | public int columnNumber = 1; 11 | 12 | public CharReader(String s) { 13 | this.s = s; 14 | } 15 | 16 | public String subs(int start) { 17 | return subs(start, i); 18 | } 19 | 20 | public String subs(int start, int end) { 21 | return s.substring(start, end); 22 | } 23 | 24 | public int read1() { 25 | int c = -1; 26 | if (i < s.length()) { 27 | c = s.charAt(i); 28 | } 29 | if (c == NEW_LINE) { 30 | lineNumber++; 31 | columnNumber = 1; 32 | } else { 33 | columnNumber++; 34 | } 35 | i++; 36 | // System.out.println("IdentReader: " + (char) c + "," + i); 37 | return c; 38 | } 39 | 40 | public int at(int i) { 41 | if (i < s.length()) { 42 | return s.charAt(i); 43 | } else { 44 | return -1; 45 | } 46 | } 47 | 48 | public int cur() { 49 | return at(i); 50 | } 51 | 52 | public int next() { 53 | return at(i + 1); 54 | } 55 | 56 | public int nnext() { 57 | return at(i + 2); 58 | } 59 | 60 | public void unread1(int ch) { 61 | i--; 62 | if (i < s.length() && s.charAt(i) == NEW_LINE) { 63 | columnNumber = 0; 64 | lineNumber--; 65 | } else { 66 | columnNumber--; 67 | } 68 | } 69 | 70 | public int getLineNumber() { 71 | return lineNumber; 72 | } 73 | 74 | public int getColumnNumber() { 75 | return columnNumber; 76 | } 77 | 78 | } -------------------------------------------------------------------------------- /src-java/cwp/ast/ControlExpr.java: -------------------------------------------------------------------------------- 1 | package cwp.ast; 2 | 3 | import cwp.lexer.Token; 4 | import cwp.parser.Controls; 5 | 6 | import java.util.ArrayList; 7 | 8 | public class ControlExpr extends Expr { 9 | 10 | ArrayList args; 11 | ArrayList body; 12 | Controls.Type type; 13 | 14 | public ControlExpr(Token initTok, ArrayList args, ArrayList body, Controls.Type type) { 15 | super(initTok); 16 | this.args = args; 17 | this.body = body; 18 | this.type = type; 19 | } 20 | 21 | @Override 22 | public String toString() { 23 | return initTok.toString(); 24 | } 25 | 26 | @Override 27 | /* public String gen() { 28 | *if (type == Controls.Type.FLAT) { 29 | return genFlat(); 30 | } 31 | if (type == Controls.Type.MAP) { 32 | return genMap(); 33 | } 34 | return genVec(); 35 | }*/ 36 | public String gen() { 37 | StringBuilder sb = new StringBuilder(); 38 | sb.append("("); 39 | sb.append(initTok.str); 40 | sb.append(" "); 41 | 42 | if (type == Controls.Type.VEC) { 43 | sb.append("["); 44 | } else if (type == Controls.Type.MAP) { 45 | sb.append("{"); 46 | } 47 | 48 | // args 49 | for (int i = 0; i < args.size() - 1; i++) { 50 | sb.append(args.get(i).gen()); 51 | sb.append(" "); 52 | } 53 | if (!args.isEmpty()) { 54 | sb.append(args.get(args.size() - 1).gen()); 55 | } 56 | if (type == Controls.Type.VEC) { 57 | sb.append("]"); 58 | } else if (type == Controls.Type.MAP) { 59 | sb.append("}"); 60 | } 61 | if (!args.isEmpty()) 62 | sb.append(" "); 63 | // body 64 | for (int i = 0; i < body.size() - 1; i++) { 65 | sb.append(body.get(i).gen()); 66 | sb.append(" "); 67 | } 68 | if (!body.isEmpty()) { 69 | sb.append(body.get(body.size() - 1).gen()); 70 | } 71 | 72 | sb.append(")"); 73 | return sb.toString(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src-java/cwp/ast/DefExpr.java: -------------------------------------------------------------------------------- 1 | package cwp.ast; 2 | 3 | import cwp.lexer.Token; 4 | 5 | import java.util.ArrayList; 6 | 7 | public class DefExpr extends Expr { 8 | 9 | public Token initTok; 10 | public Token nameTok; 11 | public boolean isFunction = false; 12 | 13 | public ArrayList metas; 14 | public ArrayList args; 15 | public ArrayList body; 16 | 17 | public DefExpr(Token initTok, Token nameTok, boolean isFunction, ArrayList metas, 18 | ArrayList args, ArrayList body) { 19 | super(initTok); 20 | this.initTok = initTok; 21 | this.nameTok = nameTok; 22 | this.isFunction = isFunction; 23 | this.metas = metas; 24 | this.args = args; 25 | this.body = body; 26 | } 27 | 28 | @Override 29 | public String toString() { 30 | return "DefExpr: " + initTok.toString(); 31 | } 32 | 33 | public String gen() { 34 | if (isFunction) 35 | return genDefn(); 36 | else 37 | return genDef(); 38 | } 39 | 40 | public String genMetas() { 41 | StringBuilder sb = new StringBuilder(); 42 | for (int i = 0; i < metas.size(); i++) { 43 | sb.append("^"); 44 | sb.append(metas.get(i).gen()); 45 | sb.append(" "); 46 | } 47 | return sb.toString(); 48 | } 49 | 50 | public String genDef() { 51 | return "(def " + genMetas() + nameTok.str + " " 52 | + arrToStr(body) + 53 | ")"; 54 | } 55 | 56 | public String genDefn() { 57 | return "(defn " + genMetas() + nameTok.str + " [" 58 | + arrToStr(args) + "] " 59 | + arrToStr(body) 60 | + ")"; 61 | } 62 | 63 | public String arrToStr(ArrayList a) { 64 | StringBuilder sb = new StringBuilder(); 65 | for (int i = 0; i < a.size() - 1; i++) { 66 | sb.append(a.get(i).gen()); 67 | sb.append(" "); 68 | } 69 | if (!a.isEmpty()) 70 | sb.append(a.get(a.size() - 1).gen()); 71 | return sb.toString(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src-java/cwp/parser/Controls.java: -------------------------------------------------------------------------------- 1 | package cwp.parser; 2 | 3 | import java.util.ArrayList; 4 | 5 | public class Controls { 6 | 7 | public static ArrayList FLAT = new ArrayList<>(); 8 | public static ArrayList VEC = new ArrayList<>(); 9 | public static ArrayList MAP = new ArrayList<>(); 10 | 11 | public static ArrayList CUSTOM_FLAT = new ArrayList<>(); 12 | public static ArrayList CUSTOM_VEC = new ArrayList<>(); 13 | public static ArrayList CUSTOM_MAP = new ArrayList<>(); 14 | 15 | public enum Type { 16 | FLAT, 17 | VEC, 18 | MAP 19 | } 20 | 21 | static { 22 | FLAT.add("while"); 23 | FLAT.add("case"); 24 | FLAT.add("cond"); 25 | FLAT.add("condp"); 26 | FLAT.add("cond->"); 27 | FLAT.add("cond->>"); 28 | FLAT.add("locking"); 29 | FLAT.add("time"); 30 | FLAT.add("when"); 31 | 32 | VEC.add("let"); 33 | VEC.add("letfn"); 34 | VEC.add("for"); 35 | VEC.add("loop"); 36 | VEC.add("doseq"); 37 | VEC.add("dotimes"); 38 | VEC.add("binding"); 39 | VEC.add("with-open"); 40 | VEC.add("with-redefs"); 41 | VEC.add("with-local-vars"); 42 | VEC.add("when-let"); 43 | VEC.add("when-first"); 44 | 45 | MAP.add("with-bindings"); 46 | MAP.add("with-redefs-fn"); 47 | } 48 | 49 | public static boolean isFlat(String s) { 50 | return FLAT.contains(s) || CUSTOM_FLAT.contains(s); 51 | } 52 | 53 | public static boolean isVec(String s) { 54 | return VEC.contains(s) || CUSTOM_VEC.contains(s); 55 | } 56 | 57 | public static boolean isMap(String s) { 58 | return MAP.contains(s) || CUSTOM_MAP.contains(s); 59 | } 60 | 61 | public static void addFlat(String s) { 62 | remove(s); 63 | CUSTOM_FLAT.add(s); 64 | } 65 | 66 | public static void addVec(String s) { 67 | remove(s); 68 | CUSTOM_VEC.add(s); 69 | } 70 | 71 | public static void addMap(String s) { 72 | remove(s); 73 | CUSTOM_MAP.add(s); 74 | } 75 | 76 | public static void remove(String s) { 77 | CUSTOM_FLAT.remove(s); 78 | CUSTOM_VEC.remove(s); 79 | CUSTOM_MAP.remove(s); 80 | } 81 | 82 | public static void reset() { 83 | CUSTOM_FLAT.clear(); 84 | CUSTOM_VEC.clear(); 85 | CUSTOM_MAP.clear(); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src-java/cwp/ast/NsExpr.java: -------------------------------------------------------------------------------- 1 | package cwp.ast; 2 | 3 | import cwp.lexer.Token; 4 | 5 | import java.util.ArrayList; 6 | 7 | public class NsExpr extends Expr { 8 | 9 | Expr name; 10 | ArrayList requires; 11 | ArrayList imports; 12 | 13 | public NsExpr(Token initTok, Expr name, ArrayList requires, ArrayList imports) { 14 | super(initTok); 15 | this.name = name; 16 | this.requires = requires; 17 | this.imports = imports; 18 | } 19 | 20 | @Override 21 | public String toString() { 22 | return ""; 28 | } 29 | 30 | public String subs(String s) { 31 | return s.substring(1, s.length() - 1); 32 | } 33 | 34 | @Override 35 | public String gen() { 36 | StringBuilder sb = new StringBuilder(); 37 | sb.append("(ns "); 38 | sb.append(name.gen()); 39 | // requires 40 | if (requires != null) { 41 | if (!requires.isEmpty()) { 42 | sb.append("\n (:require "); 43 | sb.append(requires.get(0).gen()); 44 | } 45 | for (int i = 1; i < requires.size() - 1; i++) { 46 | sb.append("\n "); 47 | sb.append(requires.get(i).gen()); 48 | } 49 | if (requires.size() > 1) { 50 | sb.append("\n "); 51 | sb.append(requires.get(requires.size() - 1).gen()); 52 | } 53 | if (!requires.isEmpty()) { 54 | sb.append(")"); 55 | } 56 | } 57 | // imports 58 | if (imports != null) { 59 | if (!imports.isEmpty()) { 60 | sb.append("\n (:import ("); 61 | sb.append(subs(imports.get(0).gen())); 62 | sb.append(")"); 63 | } 64 | for (int i = 1; i < imports.size() - 1; i++) { 65 | sb.append("\n ("); 66 | sb.append(subs(imports.get(i).gen())); 67 | sb.append(")"); 68 | } 69 | if (imports.size() > 1) { 70 | sb.append("\n ("); 71 | sb.append(subs(imports.get(imports.size() - 1).gen())); 72 | sb.append(")"); 73 | } 74 | if (!imports.isEmpty()) { 75 | sb.append(")"); 76 | } 77 | } 78 | sb.append(")"); 79 | return sb.toString(); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src-java/cwp/lexer/Token.java: -------------------------------------------------------------------------------- 1 | package cwp.lexer; 2 | 3 | public class Token { 4 | 5 | public enum Type { 6 | EOF, 7 | // Primitive 8 | NUMBER, 9 | CHAR, 10 | STRING, 11 | NULL, 12 | BOOL, 13 | SYMBOL, 14 | KEYWORD, 15 | 16 | // Macros 17 | QUOTE, // ' 18 | DEREF, // @ 19 | META, // ^ or #^ 20 | SYNTAX_QUOTE, // ` 21 | UNQUOTE, // ~ or ~@ 22 | ARG, // % 23 | 24 | // Dispatch macros 25 | SYMBOLIC_VALUE, // ##Inf, ##NaN 26 | VAR_QUOTE, // #'var 27 | REGEX, // #"asfd" 28 | SET, // #{} 29 | COMMENT, // ##Inf, ##NaN 30 | CONDITIONAL, // #? or #?@ 31 | NAMESPACE_MAP, // #:a/b{} , #::{:a 1, :b 2} 32 | 33 | LBRACE, 34 | RBRACE, 35 | LPAREN, 36 | RPAREN, 37 | LCURLY, 38 | RCURLY, 39 | 40 | // Delimiters 41 | TO, 42 | COLON, // ':' 43 | COMMA, // ',' 44 | RAW, 45 | 46 | // Operators 47 | PIPE_OP, 48 | OR_OP, 49 | AND_OP, 50 | NOT_OP, 51 | COMPARISON_OP, 52 | SUM_OP, 53 | PRODUCT_OP 54 | 55 | // Concatenators 56 | /* 57 | PLUS, 58 | MINUS, 59 | MUL, 60 | DIV, 61 | AND, 62 | OR, 63 | */ 64 | // Braces TODO: ? 65 | // Dispatch TODO: ? 66 | 67 | // Identation 68 | // INDENT, 69 | // DEDENT, 70 | } 71 | 72 | public Type type; 73 | public String str; 74 | public Object val; 75 | public int line; 76 | public int column; 77 | 78 | public Boolean callable = false; 79 | 80 | public Token(Type type) { 81 | this.type = type; 82 | } 83 | 84 | public Token(Type type, String str) { 85 | this.type = type; 86 | this.str = str; 87 | } 88 | 89 | public Token(Type type, String str, Object val) { 90 | this.type = type; 91 | this.str = str; 92 | this.val = val; 93 | } 94 | 95 | public Token(Type type, String str, Object val, int line, int column) { 96 | this.type = type; 97 | this.str = str; 98 | this.val = val; 99 | this.line = line; 100 | this.column = column; 101 | } 102 | 103 | public String toString() { 104 | return "<" + type + 105 | (str == null ? "" : (": '" + str + "'")) + 106 | ((str != null && val != null) ? " " : "") + 107 | (val == null ? "" : (": " + val)) + 108 | (callable ? " callable" : "") + 109 | ", line: " + line + ", column: " + column + 110 | ">"; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src-java/cwp/lexer/readers/Char.java: -------------------------------------------------------------------------------- 1 | package cwp.lexer.readers; 2 | 3 | import clojure.lang.Util; 4 | import cwp.lexer.Token; 5 | import cwp.lexer.CharReader; 6 | 7 | public class Char { 8 | 9 | static private int readUnicodeChar(String token, int offset, int length, int base) { 10 | if (token.length() != offset + length) 11 | throw new IllegalArgumentException("Invalid unicode character: \\" + token); 12 | int uc = 0; 13 | for (int i = offset; i < offset + length; ++i) { 14 | int d = Character.digit(token.charAt(i), base); 15 | if (d == -1) 16 | throw new IllegalArgumentException("Invalid digit: " + token.charAt(i)); 17 | uc = uc * base + d; 18 | } 19 | return (char) uc; 20 | } 21 | 22 | public static Token read(CharReader r, int line, int column) { 23 | int ch = r.read1(); 24 | if (ch == -1) 25 | throw Util.runtimeException("EOF while reading character"); 26 | String token = Identifier.read(r, (char) ch); //readToken(r, (char) ch); 27 | if (token.length() == 1) { 28 | // return Character.valueOf(token.charAt(0)) 29 | return new Token(Token.Type.CHAR, "\\" + token, null, line, column); 30 | } else if (token.equals("newline")) { 31 | //return '\n'; 32 | return new Token(Token.Type.CHAR, "\\newline", null, line, column); 33 | } else if (token.equals("space")) 34 | //return ' '; 35 | return new Token(Token.Type.CHAR, "\\space", null, line, column); 36 | else if (token.equals("tab")) 37 | //return '\t'; 38 | return new Token(Token.Type.CHAR, "\\t", null, line, column); 39 | else if (token.equals("backspace")) 40 | // return '\b'; 41 | return new Token(Token.Type.CHAR, "\\b", null, line, column); 42 | else if (token.equals("formfeed")) 43 | // return '\f'; 44 | return new Token(Token.Type.CHAR, "\\f", null, line, column); 45 | else if (token.equals("return")) 46 | // return '\r'; 47 | return new Token(Token.Type.CHAR, "\\r", null, line, column); 48 | else if (token.startsWith("u")) { 49 | char c = (char) readUnicodeChar(token, 1, 4, 16); 50 | if (c >= '\uD800' && c <= '\uDFFF') // surrogate code unit? 51 | throw Util.runtimeException("Invalid character constant: \\u" + Integer.toString(c, 16)); 52 | //return c; 53 | return new Token(Token.Type.CHAR, "\\" + token, null, line, column); 54 | } else if (token.startsWith("o")) { 55 | int len = token.length() - 1; 56 | if (len > 3) 57 | throw Util.runtimeException("Invalid octal escape sequence length: " + len); 58 | int uc = readUnicodeChar(token, 1, len, 8); 59 | if (uc > 0377) 60 | throw Util.runtimeException("Octal escape sequence must be in range [0, 377]."); 61 | //return (char) uc; 62 | return new Token(Token.Type.CHAR, "\\" + token, null, line, column); 63 | } 64 | throw Util.runtimeException("Unsupported character: \\" + token); 65 | } 66 | 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src-java/cwp/lexer/readers/Number.java: -------------------------------------------------------------------------------- 1 | /* 2 | Based on Clojure LispReader: 3 | https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/LispReader.java 4 | */ 5 | package cwp.lexer.readers; 6 | 7 | import clojure.lang.BigInt; 8 | import clojure.lang.Numbers; 9 | import cwp.lexer.Token; 10 | import cwp.lexer.Common; 11 | import cwp.lexer.CharReader; 12 | 13 | import java.math.BigDecimal; 14 | import java.math.BigInteger; 15 | import java.util.regex.Matcher; 16 | import java.util.regex.Pattern; 17 | 18 | 19 | public class Number { 20 | 21 | static Pattern intPat = 22 | Pattern.compile( 23 | "([-+]?)(?:(0)|([1-9][0-9]*)|0[xX]([0-9A-Fa-f]+)|0([0-7]+)|([1-9][0-9]?)[rR]([0-9A-Za-z]+)|0[0-9]+)(N)?"); 24 | static Pattern ratioPat = Pattern.compile("([-+]?[0-9]+)/([0-9]+)"); 25 | static Pattern floatPat = Pattern.compile("([-+]?[0-9]+(\\.[0-9]*)?([eE][-+]?[0-9]+)?)(M)?"); 26 | 27 | private static Object matchNumber(String s) { 28 | Matcher m = intPat.matcher(s); 29 | if (m.matches()) { 30 | if (m.group(2) != null) { 31 | if (m.group(8) != null) 32 | return BigInt.ZERO; 33 | return Numbers.num(0); 34 | } 35 | boolean negate = (m.group(1).equals("-")); 36 | String n; 37 | int radix = 10; 38 | if ((n = m.group(3)) != null) 39 | radix = 10; 40 | else if ((n = m.group(4)) != null) 41 | radix = 16; 42 | else if ((n = m.group(5)) != null) 43 | radix = 8; 44 | else if ((n = m.group(7)) != null) 45 | radix = Integer.parseInt(m.group(6)); 46 | if (n == null) 47 | return null; 48 | BigInteger bn = new BigInteger(n, radix); 49 | if (negate) 50 | bn = bn.negate(); 51 | if (m.group(8) != null) 52 | return BigInt.fromBigInteger(bn); 53 | return bn.bitLength() < 64 ? 54 | Numbers.num(bn.longValue()) 55 | : BigInt.fromBigInteger(bn); 56 | } 57 | m = floatPat.matcher(s); 58 | if (m.matches()) { 59 | if (m.group(4) != null) 60 | return new BigDecimal(m.group(1)); 61 | return Double.parseDouble(s); 62 | } 63 | m = ratioPat.matcher(s); 64 | if (m.matches()) { 65 | String numerator = m.group(1); 66 | if (numerator.startsWith("+")) numerator = numerator.substring(1); 67 | 68 | return Numbers.divide(Numbers.reduceBigInt(BigInt.fromBigInteger(new BigInteger(numerator))), 69 | Numbers.reduceBigInt(BigInt.fromBigInteger(new BigInteger(m.group(2))))); 70 | } 71 | return null; 72 | } 73 | 74 | static public Token readNumber(CharReader r, char initch, int line, int column) { 75 | // int line = r.getLineNumber(); 76 | // int column = r.getColumnNumber(); 77 | StringBuilder sb = new StringBuilder(); 78 | sb.append(initch); 79 | 80 | for (; ; ) { 81 | int ch = r.read1(); 82 | if (ch == -1 || Common.isWhitespace(ch) || ch == ',' || Common.isMacro(ch) 83 | || ch == ':') { 84 | r.unread1(ch); 85 | break; 86 | } 87 | sb.append((char) ch); 88 | } 89 | 90 | String s = sb.toString(); 91 | Object n = matchNumber(s); 92 | if (n == null) 93 | throw new NumberFormatException("Invalid number: " + s); 94 | return new Token(Token.Type.NUMBER, s, n, line, column); 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /src-java/cwp/lexer/readers/Str.java: -------------------------------------------------------------------------------- 1 | package cwp.lexer.readers; 2 | 3 | import clojure.lang.Util; 4 | import cwp.lexer.Common; 5 | import cwp.lexer.CharReader; 6 | import cwp.lexer.Token; 7 | 8 | import java.util.regex.Pattern; 9 | 10 | public class Str { 11 | 12 | static private int readUnicodeChar(CharReader r, int initch, int base, int length, boolean exact) { 13 | int uc = Character.digit(initch, base); 14 | if (uc == -1) 15 | throw new IllegalArgumentException("Invalid digit: " + (char) initch); 16 | int i = 1; 17 | for (; i < length; ++i) { 18 | int ch = r.read1(); 19 | if (ch == -1 || Common.isWhitespace(ch) || Common.isMacro(ch)) { 20 | r.unread1(ch); 21 | break; 22 | } 23 | int d = Character.digit(ch, base); 24 | if (d == -1) 25 | throw new IllegalArgumentException("Invalid digit: " + (char) ch); 26 | uc = uc * base + d; 27 | } 28 | if (i != length && exact) 29 | throw new IllegalArgumentException("Invalid character length: " + i + ", should be: " + length); 30 | return uc; 31 | } 32 | 33 | public static Token read(CharReader r, int line, int column) { 34 | StringBuilder sb = new StringBuilder(); 35 | int start = r.i - 1; 36 | 37 | for (int ch = r.read1(); ch != '"'; ch = r.read1()) { 38 | if (ch == -1) 39 | throw Util.runtimeException("EOF while reading string"); 40 | // escape 41 | if (ch == '\\') { 42 | ch = r.read1(); 43 | if (ch == -1) 44 | throw Util.runtimeException("EOF while reading string"); 45 | switch (ch) { 46 | case 't': 47 | ch = '\t'; 48 | break; 49 | case 'r': 50 | ch = '\r'; 51 | break; 52 | case 'n': 53 | ch = '\n'; 54 | break; 55 | case '\\': 56 | break; 57 | case '"': 58 | break; 59 | case 'b': 60 | ch = '\b'; 61 | break; 62 | case 'f': 63 | ch = '\f'; 64 | break; 65 | case 'u': { 66 | ch = r.read1(); 67 | if (Character.digit(ch, 16) == -1) 68 | throw Util.runtimeException("Invalid unicode escape: \\u" + (char) ch); 69 | ch = readUnicodeChar(r, ch, 16, 4, true); 70 | break; 71 | } 72 | default: { 73 | if (Character.isDigit(ch)) { 74 | ch = readUnicodeChar(r, ch, 8, 3, false); 75 | if (ch > 0377) 76 | throw Util.runtimeException("Octal escape sequence must be in range [0, 377]."); 77 | } else 78 | throw Util.runtimeException("Unsupported escape character: \\" + (char) ch); 79 | } 80 | } 81 | } 82 | sb.append((char) ch); 83 | } 84 | return new Token(Token.Type.STRING, r.subs(start), sb.toString(), line, column); 85 | } 86 | 87 | public static Token readRegex(CharReader r, int line, int column) { 88 | StringBuilder sb = new StringBuilder(); 89 | int start = r.i - 1; 90 | for (int ch = r.read1(); ch != '"'; ch = r.read1()) { 91 | if (ch == -1) 92 | throw Util.runtimeException("EOF while reading regex"); 93 | sb.append((char) ch); 94 | if (ch == '\\') //escape 95 | { 96 | ch = r.read1(); 97 | if (ch == -1) 98 | throw Util.runtimeException("EOF while reading regex"); 99 | sb.append((char) ch); 100 | } 101 | } 102 | return new Token(Token.Type.REGEX, "#" + r.subs(start), Pattern.compile(sb.toString()), line, column); 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /src-java/cwp/lexer/readers/Identifier.java: -------------------------------------------------------------------------------- 1 | package cwp.lexer.readers; 2 | 3 | import clojure.lang.Util; 4 | import cwp.lexer.Token; 5 | import cwp.lexer.Common; 6 | import cwp.lexer.CharReader; 7 | 8 | import java.util.regex.Matcher; 9 | import java.util.regex.Pattern; 10 | 11 | public class Identifier { 12 | 13 | static Pattern symbolPat = Pattern.compile("[:]?([\\D&&[^/]].*/)?(/|[\\D&&[^/]][^/]*)"); 14 | static Pattern arraySymbolPat = Pattern.compile("([\\D&&[^/:]].*)/([1-9])"); 15 | 16 | static public String read(CharReader r, char initch) { 17 | StringBuilder sb = new StringBuilder(); 18 | sb.append(initch); 19 | for (; ; ) { 20 | int ch = r.read1(); 21 | // System.out.println("Token read: " + (char) ch + 22 | // " " + (ch == -1 || Common.isWhitespace(ch) || Common.isTerminatingMacro(ch)) 23 | // + " " + r.i); 24 | if (ch == -1 || 25 | Common.isWhitespace(ch) || 26 | Common.isTerminatingMacro(ch) || 27 | // ch == ':' || 28 | ch == ',') { 29 | r.unread1(ch); 30 | String s = sb.toString(); 31 | if (s.charAt(s.length() - 1) == ':') { 32 | s = s.substring(0, s.length() - 1); 33 | r.unread1(':'); 34 | } 35 | /* System.out.println(">>endread: " + s); 36 | int i = s.length() - 1; 37 | while (i >= 0 && s.charAt(i) == ':') { 38 | System.out.println(">>decolon: " + i + " " + s.charAt(i)); 39 | r.unread1(s.charAt(i)); 40 | i--; 41 | } 42 | s = s.substring(0, i + 1); 43 | if (s.isEmpty()) { 44 | throw Util.runtimeException("Invalid token: " + s); 45 | }*/ 46 | return s; 47 | } 48 | sb.append((char) ch); 49 | } 50 | } 51 | 52 | static public Token interpret(String s) { 53 | if (s.equals("nil")) { 54 | return new Token(Token.Type.NULL, "nil", null); 55 | } else if (s.equals("true")) { 56 | return new Token(Token.Type.BOOL, "true", Boolean.TRUE); 57 | } else if (s.equals("false")) { 58 | return new Token(Token.Type.BOOL, "false", Boolean.FALSE); 59 | } else if (s.equals("to")) { 60 | return new Token(Token.Type.TO, "to", Boolean.FALSE); 61 | } 62 | // else if (s.equals("|>") || s.equals("|>>")) { 63 | // return new Token(Token.Type.PIPE_OP, s); 64 | // } else if (s.equals("or")) { 65 | // return new Token(Token.Type.OR_OP, s); 66 | // } else if (s.equals("and")) { 67 | // return new Token(Token.Type.AND_OP, s); 68 | // } else if (s.equals("not")) { 69 | // return new Token(Token.Type.NOT_OP, s); 70 | // } else if (s.equals("=") || s.equals("==") || s.equals("!=") || s.equals(">=") || s.equals("<=") 71 | // || s.equals(">") || s.equals("<")) { 72 | // return new Token(Token.Type.COMPARISON_OP, s); 73 | // } else if (s.equals("+") || s.equals("-")) { 74 | // return new Token(Token.Type.SUM_OP, s); 75 | // } else if (s.equals("*") || s.equals("/")) { 76 | // return new Token(Token.Type.PRODUCT_OP, s); 77 | // } 78 | Token ret = matchSymbol(s); 79 | if (ret != null) 80 | return ret; 81 | throw Util.runtimeException("Invalid token: " + s); 82 | } 83 | 84 | static public Token readToken(CharReader r, char initchString, int line, int column) { 85 | Token t = interpret(read(r, initchString)); 86 | t.line = line; 87 | t.column = column; 88 | if ((t.type == Token.Type.SYMBOL || 89 | t.type == Token.Type.KEYWORD) && r.cur() == '(') { 90 | t.callable = true; 91 | } 92 | return t; 93 | } 94 | 95 | private static Token matchSymbol(String s) { 96 | Matcher m = symbolPat.matcher(s); 97 | if (m.matches()) { 98 | int gc = m.groupCount(); 99 | String ns = m.group(1); 100 | String name = m.group(2); 101 | if (ns != null && ns.endsWith(":/") 102 | || name.endsWith(":") 103 | || s.indexOf("::", 1) != -1) 104 | return null; 105 | if (s.startsWith("::")) { 106 | return new Token(Token.Type.KEYWORD, s); 107 | } 108 | boolean isKeyword = s.charAt(0) == ':'; 109 | if (isKeyword) 110 | return new Token(Token.Type.KEYWORD, s); 111 | return new Token(Token.Type.SYMBOL, s); 112 | } else { 113 | Matcher am = arraySymbolPat.matcher(s); 114 | if (am.matches()) 115 | return new Token(Token.Type.SYMBOL, s); 116 | } 117 | return null; 118 | } 119 | 120 | 121 | } 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cwp 2 | 3 | ![alt words-count](doc/imgs/words-count.png) 4 | 5 | [![Clojars Project](https://img.shields.io/clojars/v/org.clojars.ilevd/cwp.svg)](https://clojars.org/org.clojars.ilevd/cwp) 6 | 7 | Indentation-based syntax for [Clojure](https://clojure.org/). 8 | 9 | Clojure is a practical dynamic functional programming language. 10 | This project provides familiar syntax, so it's easy to switch to it, from e.g. Python. 11 | 12 | Features: 13 | * Indentation-based, Python-like syntax 14 | * Easy to write math operations 15 | * In most cases separators: `,` - comma and `to`- keyword are optional 16 | * Readable Clojure code generation 17 | 18 | Being just a syntax for Clojure, it provides access to Clojure features such as: 19 | * Functional programming - immutable data structures, higher-order functions... 20 | * Concurrent primitives 21 | * Clojure/Java (JVM) ecosystem with a lot of libraries 22 | 23 | It's a transpiler and a [Leiningen](https://leiningen.org/) plugin. 24 | 25 | ## Examples 26 | 27 | FizzBuzz 28 | 29 | ```scala 30 | doseq i to range(1, 101): 31 | cond: 32 | mod(i, 3) = 0 and mod(i, 5) = 0 to print("FizzBuzz") 33 | mod(i, 3) = 0 to print("Fizz") 34 | mod(i, 5) = 0 to print("Buzz") 35 | :else print(i) 36 | ``` 37 | 38 | Caesar cipher 39 | 40 | ```scala 41 | def encode(^String s, ^long i): 42 | let sb StringBuilder.(): 43 | doseq c s: 44 | cond: 45 | int(c) >= int(\a) and int(c) <= int(\z) 46 | .append(sb, char(int(\a) + mod(int(c) - int(\a) + i, 26))) 47 | 48 | int(c) >= int(\A) and int(c) <= int(\Z) 49 | .append(sb, char(int(\A) + mod(int(c) - int(\A) + i, 26))) 50 | 51 | :else .append(sb, c) 52 | .toString(sb) 53 | 54 | def decode(^String s, ^long i): 55 | encode(s, 26 - i) 56 | ``` 57 | 58 | 59 | Data manipulation 60 | 61 | ```scala 62 | def users: [{:name to "John", :age to 20} 63 | {:name to "Anna", :age to 32} 64 | {:name to "Smith", :age to 27}] 65 | 66 | def avg-age(users): 67 | let ages to users |>> map(:age) 68 | |>> reduce(+): 69 | ages / count(users) |> double 70 | 71 | def greetings(users): 72 | let names to users |>> map(:name) 73 | |>> str/join(", "): 74 | str("Hello, ", names, "!") 75 | 76 | println(avg-age(users)) 77 | println(greetings(users)) 78 | ``` 79 | 80 | Simple HTTP server with [http-kit](https://github.com/http-kit/http-kit), 81 | [Hiccup](https://github.com/weavejester/hiccup) and [Ring](https://github.com/ring-clojure/ring) 82 | 83 | ```scala 84 | ns my-project.server 85 | require: [ring.middleware.params :as params] 86 | [ring.middleware.keyword-params :as kparams] 87 | [org.httpkit.server :refer [run-server]] 88 | [hiccup2.core :as h] 89 | 90 | def fruits: ["Banana", "Apple", "Lemon"] 91 | 92 | def get-html(user): 93 | [:div 94 | [:p {:style {:font-weight :bold}} str("Hello, ", user or "User", "!")] 95 | "Fruits:" 96 | for fruit to fruits: 97 | [:p {} fruit]] 98 | |> h/html |> str 99 | 100 | def app(req): 101 | {:status 200 102 | :headers {"Content-Type" "text/html"} 103 | :body get-html(req |> :params |> :name)} 104 | 105 | run-server(app |> kparams/wrap-keyword-params 106 | |> params/wrap-params, 107 | {:port 8080}) 108 | ``` 109 | 110 | Some function from [clojure.core](https://github.com/clojure/clojure/blob/clojure-1.11.1/src/clj/clojure/core.clj#L7918) 111 | rewritten with CWP 112 | 113 | ```scala 114 | def load-data-reader-file(mappings, ^java.net.URL url): 115 | with-open rdr to clojure.lang.LineNumberingPushbackReader.( 116 | java.io.InputStreamReader.( 117 | .openStream(url), "UTF-8")): 118 | binding *file* to .getFile(url): 119 | let read-opts to if .endsWith(.getPath(url), "cljc"): 120 | {:eof nil :read-cond :allow} 121 | else {:eof nil} 122 | new-mappings to read(read-opts, rdr): 123 | if not map?(new-mappings): 124 | throw ex-info(str("Not a valid data-reader map"), {:url url}) 125 | reduce(fn m, [k, v]: 126 | if not symbol?(k): 127 | throw ex-info(str("Invalid form in data-reader file"), 128 | {:url url 129 | :form k}) 130 | let v-var to data-reader-var(v): 131 | if contains?(mappings, k) and mappings(k) != v-var: 132 | throw ex-info("Conflicting data-reader mapping", 133 | {:url url 134 | :conflict k 135 | :mappings m}) 136 | assoc(m, k, v-var), 137 | mappings, 138 | new-mappings) 139 | ``` 140 | 141 | 142 | ## Documentation 143 | * [Overview](doc/overview.md) 144 | * [Syntax and transpiling](doc/syntax-and-transpiling.md) 145 | 146 | 147 | ## Usage 148 | 149 | Add to `project.clj` :plugins section: 150 | ```edn 151 | [org.clojars.ilevd/cwp ""] 152 | ``` 153 | 154 | Add builds info to `project.clj` root: 155 | ```edn 156 | :cwp {:builds [{:in "src-cwp" 157 | :out "src-out"}]} 158 | ``` 159 | `:in` - folder where CWP sources are, 160 | `:out` - folder for generated Clojure code 161 | 162 | Files extensions mapping: 163 | * `.cw` -> `.clj` 164 | * `.cws` -> `.cljs` 165 | * `.cwc` -> `.cljc` 166 | 167 | Run: `lein cwp` 168 | 169 | After that you can compile Clojure code to `.jar`. 170 | 171 | 172 | ## License 173 | 174 | Copyright © 2024 ilevd -------------------------------------------------------------------------------- /doc/overview.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | The purpose of this document is to provide quick overview of some Clojure concepts 4 | using CWP syntax. 5 | 6 | 7 | ```scala 8 | // this is a comment 9 | // Simple literals 10 | 1 // long 11 | "this is a string" 12 | nil // null is written as nil 13 | true // boolean 14 | :keyword // keyword is a object that contains a string itself, they are fast in equality tests 15 | 'symbol // symbol is an identifier that is used to refer to something else (function, global var etc) 16 | // Symbols can contain such characters: *, +, !, -, _, ', ?, <, >, = 17 | // Thus this is valid symbol: 'a*b+c!d-e_f'g?h>i 5 and 20 > 3 28 | 29 | // Pipe operators: 30 | // |> insert left value as a first argument to right expression, 31 | // |>> inserts left value as a last argument to right expression, 32 | // 'str' function - concatenates provided arguments to one string, 33 | // 'println' takes 1 argument here, so both operators can be used with it 34 | "a" |> str("b") |> println // prints "ab" 35 | "a" |>> str("b") |>> println // prints "ba" 36 | 37 | // if-else control structure: 38 | if true: println("Yes") 39 | else: println("No") 40 | // In Clojure nil and false are falsy values, and everything else is truthy 41 | if 10 or "Hello": 42 | println("Yes") // prints: Yes 43 | 44 | // "loop" is used to make a cycle, 45 | // "recur" is used to rebind values for new iteration 46 | loop i to 0: 47 | if i < 10: 48 | println(i) // prints values from 0 to 9 49 | recur(i + 1) // provide new increased value for new iteration 50 | 51 | // There are rich set of built-in Clojure core functions and macros, 52 | // so previous cycle can be written with `dotimes` 53 | dotimes i to 10: 54 | println(i) 55 | 56 | // To bind symbols with values 'let' can be used, 57 | // this can be considered as declaring new local vars 58 | let a to 10, 59 | b to 20: 60 | a + b 61 | 62 | // Base Clojure collections are: list, vector, map and set. 63 | // They are immutable and persistent, so adding/removing/replacing a value creates new collection 64 | // Creating a list and calling some functions on it: 65 | let xs to list(1, 2, 3): 66 | println(conj(xs, 4)) // (4 1 2 3) 67 | println(rest(xs)) // (2 3) 68 | println(xs) // (1 2 3) 69 | 70 | // Creating a vector and calling some functions on it: 71 | let v to [1, 2, 3]: 72 | println(conj(v, 4)) // [1 2 3 4] 73 | println(rest(v)) // (2 3 4) 74 | println(v) // [1 2 3] 75 | 76 | // Creating a map and calling some functions on it: 77 | let m to {:name "John", 78 | :age 32}: 79 | println(keys(m)) // (:name :age) 80 | println(vals(m)) // (John 32) 81 | println(assoc(m, :job, :developer)) // {:name John, :age 32, :job :developer} 82 | println(dissoc(m, :age)) // {:name John} 83 | println(merge(m, {:address "Some Street"})) // {:name John, :age 32, :address Some Street} 84 | println(m) // {:name John, :age 32} 85 | 86 | // Creating a set and calling some functions on it: 87 | let st to #{1, 2, 3}: 88 | println(conj(st, 4)) // #{1 4 3 2} 89 | println(conj(st, 2)) // #{1 3 2} 90 | println(st) // #{1 3 2} 91 | 92 | // Most of Clojure sequence functions can be applied to different collection types: 93 | let v to [1, 2, 3, 4, 5], 94 | m to {:name "John", :age 32}: 95 | println(first(v)) // 1 96 | println(map(fn x: x + 1, v)) // (2 3 4 5 6) 97 | println(filter(fn x: x > 3, v)) // (4 5) 98 | // prints first map entry of map which consists of key an value 99 | println(first(m)) // [:name John] 100 | // [k, v] - is destructuring for map entry 101 | println(map(fn [k, v]: str(k, v), m)) // (:nameJohn :age32) 102 | 103 | // It's useful to use '|>>' when processing sequences: 104 | range(10) // creating sequence: (0 1 2 3 4 5 6 7 8 9) 105 | |>> map(fn x: x * x) // (0 1 4 9 16 25 36 49 64 81) 106 | |>> filter(fn x: x > 20) // (25 36 49 64 81) 107 | |>> group-by(even?) // {false [25 49 81], true [36 64]} 108 | 109 | // Destructuring is useful feature to get values from nested data structure, 110 | // some examples: 111 | let [h & t] to [1, 2, 3, 4]: 112 | println(h) // 1 113 | println(t) // (2 3 4) 114 | 115 | let {:keys [age, name]} to {:name "John", :age 32}: 116 | println(name) // John 117 | println(age) // 32 118 | 119 | // Clojure has good support for concurrency and parallelism, here are some examples below. 120 | // To have shared state in multithread environment 'atom' can be used: 121 | let *a to atom(0): // create a reference type that contains initial value 0 122 | dotimes _ to 100: // 100 times 123 | future(swap!(*a, inc)) // run a thread that increment initial value 124 | Thread/sleep(100) // wait some time for all threads finish 125 | println(@*a) // @*a is a short syntax for deref(*a) for getting value from atom 126 | 127 | // To apply f for coll in parallel, 'pmap' can be used: 128 | pmap(fn x: x * x, range(10)) // (0 1 4 9 16 25 36 49 64 81) 129 | ``` 130 | ## What's next? 131 | 132 | * [clojure.org](https://clojure.org/) - to read more about Clojure 133 | * [Syntax and transpiling](syntax-and-transpiling.md) - to read more about CWP syntax and transpiling -------------------------------------------------------------------------------- /src-java/cwp/lexer/LexerReader.java: -------------------------------------------------------------------------------- 1 | package cwp.lexer; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Stack; 5 | 6 | import cwp.lexer.readers.*; 7 | import cwp.lexer.readers.Number; 8 | 9 | public class LexerReader { 10 | 11 | CharReader r; 12 | Stack tokenStack = new Stack<>(); 13 | 14 | public LexerReader(String s) { 15 | r = new CharReader(s); 16 | } 17 | 18 | public Token read() { 19 | if (!tokenStack.empty()) { 20 | return tokenStack.pop(); 21 | } 22 | return read(r); 23 | } 24 | 25 | public void unread(Token t) { 26 | tokenStack.push(t); 27 | } 28 | 29 | 30 | public static Object readString(String s) { 31 | //IdentReader r = new IdentReader(new StringReader(s)); 32 | CharReader r = new CharReader(s); 33 | return read(r); 34 | } 35 | 36 | public static Token read(CharReader r) { 37 | Token t = readBaseToken(r); 38 | int ch = r.read1(); 39 | if (ch == '(') { 40 | t.callable = true; 41 | } 42 | r.unread1(ch); 43 | return t; 44 | } 45 | 46 | public static Token readBaseToken(CharReader r) { 47 | try { 48 | for (; ; ) { 49 | int ch = r.read1(); 50 | while (Common.isWhitespace(ch)) ch = r.read1(); 51 | //System.out.println("Lexer read: " + (char) ch + " , r i:" + r.i); 52 | 53 | int line = r.getLineNumber(); 54 | int column = r.getColumnNumber() - 1; 55 | 56 | if (ch == -1) return new Token(Token.Type.EOF, "", null, line, column); 57 | 58 | // number 59 | if (Character.isDigit(ch)) { 60 | return Number.readNumber(r, (char) ch, line, column); 61 | } 62 | if (ch == '+' || ch == '-') { 63 | int ch2 = r.read1(); 64 | if (Character.isDigit(ch2)) { 65 | r.unread1(ch2); 66 | return Number.readNumber(r, (char) ch, line, column); 67 | } 68 | r.unread1(ch2); 69 | } 70 | 71 | switch (ch) { 72 | case '\\': 73 | return Char.read(r, line, column); 74 | case '"': 75 | if (r.cur() == '"' && r.next() == '"') 76 | return Raw.read(r, line, column); 77 | return Str.read(r, line, column); 78 | // Macros 79 | case '\'': 80 | return new Token(Token.Type.QUOTE, "'", null, line, column); 81 | case '@': 82 | return new Token(Token.Type.DEREF, "@", null, line, column); 83 | case '^': 84 | return new Token(Token.Type.META, "^", null, line, column); 85 | case '`': 86 | return new Token(Token.Type.SYNTAX_QUOTE, "`", null, line, column); 87 | case '~': 88 | int nextChar = r.read1(); 89 | if (nextChar == '@') { 90 | return new Token(Token.Type.UNQUOTE, "~@", null, line, column); 91 | } 92 | r.unread1(nextChar); 93 | return new Token(Token.Type.UNQUOTE, "~", null, line, column); 94 | // case '%': 95 | // return new Token(Token.Type.UNQUOTE, "'", null, line, column); 96 | case '(': 97 | return new Token(Token.Type.LPAREN, "(", null, line, column); 98 | case ')': 99 | return new Token(Token.Type.RPAREN, ")", null, line, column); 100 | case '[': 101 | return new Token(Token.Type.LBRACE, "[", null, line, column); 102 | case ']': 103 | return new Token(Token.Type.RBRACE, "]", null, line, column); 104 | case '{': 105 | return new Token(Token.Type.LCURLY, "{", null, line, column); 106 | case '}': 107 | return new Token(Token.Type.RCURLY, "}", null, line, column); 108 | case ':': 109 | if (Character.isWhitespace(r.cur()) || r.cur() == -1) { 110 | return new Token(Token.Type.COLON, ":", null, line, column); 111 | } 112 | break; 113 | case ',': 114 | return new Token(Token.Type.COMMA, ",", null, line, column); 115 | // if (Character.isWhitespace(r.cur())) { 116 | // return new Token(Token.Type.COMMA, ",", null, line, column); 117 | // } 118 | case '/': 119 | if (r.cur() == '/') { 120 | r.read1(); 121 | readComment(r); 122 | continue; 123 | } 124 | 125 | // Dispatch macros 126 | case '#': 127 | int cc = r.read1(); 128 | switch (cc) { 129 | case '^': 130 | return new Token(Token.Type.META, "#^", null, line, column); 131 | case '#': 132 | return SymbolicValue.read(r, line, column); 133 | case '\'': 134 | return new Token(Token.Type.VAR_QUOTE, "#'", null, line, column); 135 | case '\"': 136 | return Str.readRegex(r, line, column); 137 | case '{': 138 | return new Token(Token.Type.SET, "#{", null, line, column); 139 | case '?': 140 | return Conditional.read(r, line, column); 141 | case ':': 142 | return NamespaceMap.read(r, line, column); 143 | case '!': 144 | readComment(r); 145 | continue; 146 | 147 | 148 | } 149 | r.unread1(cc); 150 | } 151 | //System.out.println("Lexer read: " + (char) ch + " , r i:" + r.i); 152 | return Identifier.readToken(r, (char) ch, line, column); 153 | 154 | } 155 | } catch (Exception e) { 156 | throw new RuntimeException("Reader exception at line: " + r.getLineNumber() + ", number: " + r.getColumnNumber(), e); 157 | } 158 | 159 | } 160 | 161 | public static void readComment(CharReader r) { 162 | for (; ; ) { 163 | int c = r.read1(); 164 | if (c == '\n' || c == '\r' || c == -1) break; 165 | } 166 | } 167 | 168 | public static ArrayList readAll(String s) { 169 | ArrayList arr = new ArrayList(); 170 | CharReader r = new CharReader(s); 171 | Token t = read(r); 172 | int i = 0; 173 | while (t.type != Token.Type.EOF) { 174 | //System.out.println(t); 175 | arr.add(t); 176 | t = read(r); 177 | i++; 178 | } 179 | arr.add(t); 180 | return arr; 181 | } 182 | 183 | 184 | } 185 | -------------------------------------------------------------------------------- /test/cwp/core_test.clj: -------------------------------------------------------------------------------- 1 | (ns cwp.core-test 2 | (:require [clojure.string :as str] 3 | [clojure.test :refer :all] 4 | ) 5 | (:import (cwp.lexer LexerReader) 6 | (cwp.parser Parser))) 7 | 8 | 9 | (defn gen [s] 10 | (time 11 | (try 12 | (let [p (Parser. s)] 13 | (.gen (.readExpr ^Parser p))) 14 | (catch Exception e (prn e))))) 15 | 16 | 17 | (defn read-eval [s] 18 | (-> s gen read-string eval)) 19 | 20 | 21 | (defn eq [s1 s2] 22 | (= (str/replace s1 #"\s+" "") 23 | (str/replace s2 #"\s+" ""))) 24 | 25 | (deftest test 26 | (testing "Testing" 27 | 28 | (is (= (gen "[to , to , to to ,,]") 29 | "[]")) 30 | 31 | (is (= (gen "[1 1.0 1e5 1/3 -5]") 32 | "[1 1.0 1e5 1/3 -5]")) 33 | 34 | (is (= (gen "[\\a \\b \\newline \\space \"\" \"hello\"]") 35 | "[\\a \\b \\newline \\space \"\" \"hello\"]")) 36 | 37 | (is (= (gen "[nil true false ##Inf ##NaN ##-Inf]") 38 | "[nil true false ##Inf ##NaN ##-Inf]")) 39 | 40 | (is (= (gen "[#\"\" #\"regex\"]") 41 | "[#\"\" #\"regex\"]")) 42 | 43 | (is (= (gen "[#?() #?(:clj 1) #?@() #?@(:clj [1 2 3])]") 44 | "[#?() #?(:clj 1) #?@() #?@(:clj [1 2 3])]")) 45 | 46 | (is (= (gen "[#{} #{1} {} {1 2} [] #{[] {} #{}}]") 47 | "[#{} #{1} {} {1 2} [] #{[] {} #{}}]")) 48 | 49 | (is (= (gen "[#::{} #::{1 2} #:hello{} #:hello{:a 1}]") 50 | "[#::{} #::{1 2} #:hello{} #:hello{:a 1}]")) 51 | 52 | (is (= (gen "[@a 'a `a ~a ~@a]") 53 | "[@a 'a `a ~a ~@a]")) 54 | 55 | (is (= (gen "[:a :a:a]") 56 | "[:a :a:a]")) 57 | 58 | (is (= "{:a (+ (- 10 20) 5)}" (gen "{:a to 10 - 20 + 5}"))) 59 | (is (= "{:a 10, - (+ 20 5)}" (gen "{:a to 10, - 20 + 5}"))) 60 | (is (= "{:a 10, - 20, + 5}" (gen "{:a to 10, - 20, + 5}"))) 61 | )) 62 | 63 | 64 | (deftest unary-test 65 | (testing "Unary testing" 66 | (is (eq "@a" (gen "@a"))) 67 | (is (eq "'a" (gen "'a"))) 68 | (is (eq "`a" (gen "`a"))) 69 | (is (eq "~a" (gen "~a"))) 70 | (is (eq "~@a" (gen "~@a"))) 71 | (is (eq "(throw a)" (gen "throw a"))) 72 | (is (eq "(not a)" (gen "not a"))) 73 | (is (eq "^:dynamic ^:const a" (gen "^:dynamic ^:const a"))) 74 | (is (eq "^:dynamic ^:const ^{:a 1} a" (gen "^:dynamic ^:const ^{:a 1} a"))))) 75 | 76 | 77 | (deftest infix-test 78 | (testing "Infix testing" 79 | (is (= "(+ 1 2)" (gen "1 + 2"))) 80 | (is (= "(- (+ 1 2) 3)" (gen "1 + 2 - 3"))) 81 | (is (= "(* (+ 5 5) (- 4 2))" (gen "(5 + 5) * (4 - 2)"))) 82 | (is (= "(+ (* 1 2) (* 3 4))" (gen "1 * 2 + 3 * 4"))) 83 | ; (is (= "(+ (+ (* 1 2) (* 3 4)) (* 5 6))" (gen "1 * 2 + 3 * 4 + 5 * 6"))) 84 | (is (= "(+ (* 1 2) (* 3 4) (* 5 6))" (gen "1 * 2 + 3 * 4 + 5 * 6"))) 85 | (is (= "(+ 1 (* 2 3) (* 4 5) 6)" (gen "1 + 2 * 3 + 4 * 5 + 6"))) 86 | (is (= "(+ (- (+ (- (+ 10 4) 3) 5) 2) 5)" (gen "10 + 4 - 3 + 5 - 2 + 5"))) 87 | 88 | (is (= "(and (+ 10 20) (+ 4 5))" (gen "10 + 20 and 4 + 5"))) 89 | ;; (is (= "(-> (-> (-> (-> s f1) (+ f2 d)) f4) f5)" (gen "s |> f1 |> f2 + d |> f4 |> f5 "))) 90 | (is (= "(-> s f1 (+ f2 d) f4 f5)" (gen "s |> f1 |> f2 + d |> f4 |> f5 "))) 91 | 92 | )) 93 | 94 | 95 | (deftest def-test 96 | (testing "def testing" 97 | (is (= "(def a 10)" (gen "def a: 10"))) 98 | (is (= "(def a 10 20)" (gen " 99 | def a: 100 | 10 101 | 20 102 | "))) 103 | (is (eq "(def ^:const a 10)" (gen "def ^:const a: 10"))) 104 | (is (eq "(def ^:const ^:dynamic a 10)" (gen "def ^:const ^:dynamic a: 10"))) 105 | (is (eq "(defn a [] 10)" (gen "def a(): 10"))) 106 | (is (eq "(defn a [x] x)" (gen "def a(x): x"))) 107 | (is (eq "(defn a [x y] (+ x y))" (gen "def a(x, y): x + y"))) 108 | (is (eq "(defn f [x [a b]] (+ a b))" (gen "def f(x, [a, b]): a + b"))) 109 | (is (eq "(defn ^{:a 1} f [x [a b]] (+ a b))" (gen "def ^{:a 1} f(x, [a, b]): a + b"))) 110 | )) 111 | 112 | 113 | (deftest if-else-test 114 | (testing "if-else testing" 115 | (is (= "(if a 10)" (gen "if a 10"))) 116 | 117 | (is (= "(if a (do 10 20))" (gen " 118 | if a: 119 | 10 120 | 20"))) 121 | 122 | (is (= "(if a 10 40)" (gen "if a 10 else 40"))) 123 | 124 | (is (= "(if a 10 (do 20 40))" (gen " 125 | if a 10 126 | else: 127 | 20 128 | 40"))) 129 | 130 | (is (= "(if a (do 10 20) (do 40 50))" 131 | (gen " 132 | if a: 133 | 10 134 | 20 135 | else: 136 | 40 137 | 50"))) 138 | 139 | (is (eq "((if a f))" (gen "(if a f)()"))) 140 | (is (eq "((if a f1 f2))" (gen "(if a f1 else f2)()"))) 141 | )) 142 | 143 | 144 | (deftest try-catch-finally-test 145 | (testing "Try Testing" 146 | (is (= "(try 10)" (gen "try: 10"))) 147 | 148 | (is (eq "(try 10 (catch Exception e 20))" (gen " 149 | try: 10 150 | catch Exception e: 20"))) 151 | 152 | (is (eq "(def a 153 | (try (println \"Hello\") 154 | (+ 1 1) 155 | (catch Exception e (println \"Err\")) 156 | (catch Exception e 157 | (println \"Err\") 158 | (println \"Err\")) 159 | (finally 100)))" 160 | (gen " 161 | def a: 162 | try: 163 | println(\"Hello\") 164 | 1 + 1 165 | catch Exception e: 166 | println(\"Err\") 167 | catch Exception e: 168 | println(\"Err\") 169 | println(\"Err\") 170 | finally: 171 | 100"))) 172 | )) 173 | 174 | 175 | (deftest fn-test 176 | (testing "Fn testing" 177 | (is (eq "(fn [] (println))" (gen "fn: println()"))) 178 | (is (eq "(fn [[w n]] (println w n))" (gen "fn [w, n]: println(w, n))"))) 179 | (is (eq "((fn [x] (println (+ x 1))) 20)" (gen "(fn x: println(x + 1))(20)"))) 180 | (is (eq "[(fn [x] x) (fn [y] (+ y 1))]" (gen "[fn x: x, fn y: y + 1]"))) 181 | )) 182 | 183 | 184 | (deftest controls-test 185 | (testing "Controls testing" 186 | (is (eq "(let [a 10 b 20] (* 10 20))" (gen "let a to 10, b to 20: 10 * 20"))) 187 | (is (eq "(let [a 10 b 30 c 40] (+ a (* b c)))" (gen " 188 | let a to 10, b to 30, c to 40: 189 | a + b * c"))) 190 | (is (eq "(let [a (case 20 10 11 20 12)] (println \"a:\" a))" 191 | (gen " 192 | let a to case 20: 193 | 10 11 194 | 20 12: 195 | println(\"a:\", a) 196 | "))) 197 | (is (eq "(while 20 30)" (gen "while 20: 30"))) 198 | (is (eq "(with-redefs-fn {a 10 b 20} (+ a b))" (gen "with-redefs-fn a to 10 b to 20: a + b"))) 199 | )) 200 | 201 | 202 | (deftest nstest 203 | (testing "NST Testing" 204 | (is (eq "(ns my-server.core)" (gen "ns my-server.core"))) 205 | 206 | (is (eq "(ns my-server.core\n(:require [clojure.str :as str]))" 207 | (gen " 208 | ns my-server.core 209 | require: [clojure.str :as str]"))) 210 | 211 | (is (eq "(ns my-server.core\n(:require [clojure.str :as str]\n [clojure.walk :as walk]))" 212 | (gen " 213 | ns my-server.core 214 | require: 215 | [clojure.str :as str] 216 | [clojure.walk :as walk] 217 | "))) 218 | (is (eq "(ns my-server.core\n(:require [clojure.str :as str]\n [clojure.walk :as walk]))" 219 | (gen " 220 | ns my-server.core 221 | require: [clojure.str :as str] 222 | [clojure.walk :as walk] 223 | "))) 224 | 225 | 226 | 227 | (is (eq "(ns my-server.core 228 | (:require [clojure.str :as str] 229 | [clojure.walk :as walk] 230 | [clojure.edn :as edn]))" 231 | (gen " 232 | ns my-server.core 233 | require: 234 | [clojure.str :as str] 235 | [clojure.walk :as walk] 236 | [clojure.edn :as edn] 237 | "))) 238 | 239 | (is (eq "(ns my-server.core\n(:import (com.clickhouse.client ClickHouseException)))" 240 | (gen " 241 | ns my-server.core 242 | import: [com.clickhouse.client ClickHouseException] 243 | "))) 244 | 245 | (is (eq "(ns my-server.core\n(:import (com.clickhouse.client ClickHouseException)\n (com.somePack SomeClass)))" 246 | (gen " 247 | ns my-server.core 248 | import: 249 | [com.clickhouse.client ClickHouseException] 250 | [com.somePack SomeClass] 251 | "))) 252 | 253 | )) 254 | 255 | (deftest comment-test 256 | (testing "Comment Testing" 257 | (is (= "20" (gen " 258 | // this is comment 259 | #! this is comment to 260 | 20 // comment 261 | // yet another comment 262 | #! comment again 263 | "))) 264 | ) 265 | ) 266 | 267 | 268 | (deftest complex 269 | (testing "Complex Testing" 270 | 271 | (is (= "[(not 10) (throw (Exception. \"asfd\"))]" 272 | (gen "[not 10 throw Exception.(\"asfd\")]"))) 273 | 274 | (is (= "(clojure.lang.LineNumberingPushbackReader. (java.io.InputStreamReader. (.openStream url) \"UTF-8\"))" 275 | (gen " clojure.lang.LineNumberingPushbackReader.( 276 | java.io.InputStreamReader.( 277 | .openStream(url), \"UTF-8\"))"))) 278 | )) 279 | 280 | 281 | (deftest readeval-test 282 | (testing "Read-eval testing" 283 | 284 | (is (= '(["hello" 2] ["hi" 1]) 285 | (read-eval 286 | "( 287 | def wordsCount(s): 288 | s |> clojure.string/split(#\"\\s+\") |>> frequencies |>> sort-by(second, >) 289 | wordsCount(\"hello hi hello\") )"))) 290 | 291 | (is (= 20 (read-eval 292 | "(def f(x): x + 10 + 20 / 10 293 | f(8))" 294 | )))) 295 | ) -------------------------------------------------------------------------------- /doc/syntax-and-transpiling.md: -------------------------------------------------------------------------------- 1 | ## Syntax and transpiling 2 | 3 | ### Function call 4 | 5 | Function call is written without whitespaces between function name and parentheses: 6 | 7 | ```scala 8 | f() 9 | ``` 10 | 11 | is transpiled to 12 | 13 | ```clojure 14 | (f) 15 | ``` 16 | 17 | There can be chained function call: 18 | 19 | ```f()()``` => ```((f))``` 20 | 21 | Function call here can be considered not only in relation to function, but to Clojure macro ot other form. 22 | 23 | ### Comments 24 | 25 | Comments are written in C/Java style. Example: 26 | ```scala 27 | // comment 28 | ``` 29 | 30 | 31 | ### Basic expressions 32 | 33 | Basic expressions are transpiled to themselves: 34 | 35 | | Type | Expression | Transpiled | 36 | |---------------|--------------|:------------:| 37 | | **Boolean** | true, false | true, false | 38 | | **Null** | nil | nil | 39 | | **Numbers** | 1, 10.2, 3/4 | 1, 10.2, 3/4 | 40 | | **String** | "string" | "string" | 41 | | **Keywrod** | :keyword | :keyword | 42 | | **Character** | \a, \newline | \a, \newline | 43 | | **Symbol** | symbol | symbol | 44 | 45 | ### Data structures 46 | 47 | Vectors, maps, sets are declared in Clojure style: 48 | 49 | | Type | Expression | Transpiled | 50 | |------------|--------------|:------------:| 51 | | **Map** | {:a 1, :b 2} | {:a 1, :b 2} | 52 | | **Vector** | [1, 2, 3] | [1, 2, 3] | 53 | | **Sets** | #{1, 2} | #{1, 2} | 54 | 55 | Parentheses are used for grouping expressions and setting precedence, 56 | so `list` can be declared as: `list(1, 2, 3)` => `(list 1 2 3)` 57 | 58 | ### Other expressions 59 | 60 | There are other supported expressions. 61 | Some of them can be considered as 62 | unary operators (quote, syntax quote, unquote, var quote, deref): 63 | 64 | | Type | Expression | Transpiled | 65 | |------------------------|----------------------|:--------------------:| 66 | | **Regular expression** | #"hello" | #"hello" | 67 | | **Symbolic value** | ##Inf, ##-Inf, ##NaN | ##Inf, ##-Inf, ##NaN | 68 | | **Var quote** | #'some-var | #'some-var | 69 | | **Quote** | 'symbol | 'symbol | 70 | | **Syntax quote** | `sym | `sym | 71 | | **Unquote** | ~a, ~@a | ~a, ~@a | 72 | | **Deref** | @val | @val | 73 | | **Meta** | ^:hello c | ^:hello c | 74 | | **Conditional** | #?(), #?@() | #?(), #?@() | 75 | 76 | ### Additional expressions 77 | 78 | There are two syntax keywords that can be used without parentheses: **throw** and **not**: 79 | 80 | | | Example | Transpiled | 81 | |-----------|------------------------------|:--------------------------------:| 82 | | **throw** | `throw ex-info("My ex", {})` | `(throw (ex-info \"My ex\" {}))` | 83 | | **not** | `not val` | `(not val)` | 84 | 85 | ### Infix operators 86 | 87 | | Operator | Description | 88 | |-------------------------|:------------------------:| 89 | | \|>, \|>> | pipe | 90 | | or, and | boolean | 91 | | =, ==, !=, >, <, >=, <= | comparison | 92 | | +, - | sum, subtraction | 93 | | *, / | multiplication, division | 94 | 95 | Infix operators are used as *concatenators* - they are grouping two expressions into one. 96 | 97 | `[1 2]` - vector of 2 expressions: `1` and `2`, 98 | 99 | `[1 + 2]` - vector of 1 expression: `1 + 2` 100 | 101 | ### Optionality of separators 102 | Keyword `to` and comma `,` are equivalents. 103 | They are used as separators and in most cases can be omitted. 104 | 105 | Expressions below are identical: 106 | 107 | ```scala 108 | [1, 2, 3] 109 | [1 2 3] 110 | [1 to 2 to 3] 111 | [1 , ,, to to to 2 to 3] 112 | ```` 113 | If they are used in infix expression, they prevent *concatenation* of expressions, thus: 114 | 115 | `[1 + 2]` - vector of 1 expression: `1 + 2` 116 | 117 | `[1, +, 2}` - vector of 3 expressions: `1`, `+` and `2` 118 | 119 | 120 | ### Parentheses 121 | 122 | If parentheses are not a part of a function call, 123 | they are used for setting precedence: 124 | 125 | 126 | ```scala 127 | 10 * 2 + 3 // => 23 (+ (* 10 2) 3) 128 | 10 * (2 + 3) // => 50 (* 10 (+ 2 3)) 129 | ``` 130 | 131 | and grouping expressions: 132 | ```scala 133 | (println("Hello"), println("World")) // => (do (println "Hello") (println "World")) 134 | 135 | ``` 136 | 137 | Thus, if there are more than one expression in parentheses, expression transpiles to `do` block. 138 | 139 | ### Code block 140 | 141 | Code block is the main part that makes syntax indentation-based. 142 | 143 | The base structure of code block consists of starting symbol (token), 144 | expressions that are following starting symbol, 145 | colon `:` and block expressions: 146 | 147 | ``` 148 | [ ...]? : 149 | 150 | ? 151 | ... 152 | ``` 153 | Block expression can be at the same line as a starting symbol: 154 | 155 | ``` 156 | [ ...]? : 157 | 158 | ``` 159 | There can be multiple block expressions one a line starting from the second line: 160 | 161 | ``` 162 | [ ...]? : 163 | 164 | 165 | ... 166 | ``` 167 | But if there is another expression in first line after first block expression - 168 | this expression is not related to main block construct. 169 | This enables to define, for example, vector of simple functions in one line: 170 | ```scala 171 | [fn x: x + 1, fn x: x * 2] // => [(fn [x] (+ x 1)) (fn [x] (* x 2))] 172 | ``` 173 | Comparing with: 174 | ```scala 175 | [fn x: 176 | x + 1, fn x: x * 2] // => [(fn [x] (+ x 1) (fn [x] (* x 2)))] 177 | ``` 178 | 179 | Code blocks are used in various code structures (constructs), such as `ns` declaration, `if`-`else`, `let` and others. 180 | 181 | ### Special structures 182 | 183 | There are several structures that are handled by parser to facilitate declarations. 184 | These structures are: `ns`, `def`, `if`-`else`, `try`-`catch`-`finally`, `fn` (`lambda`) 185 | and they are started with `ns`, `def`, `if`, `try`, `fn` (or `lambda`) keywords respectively. 186 | 187 | ### `ns` 188 | 189 | Namespace declaration starts with `ns` keyword followed by namespace name and optional 190 | `require`, `import` blocks. 191 | 192 | Example: 193 | ```scala 194 | ns my-proj.core 195 | require: 196 | [clojure.str :as str] 197 | [clojure.walk :as walk] 198 | [clojure.edn :as edn] 199 | import: [com.some-proj SomeClass] 200 | ``` 201 | There can be also `flat`, `vec`, `map` blocks in `ns` declaration. More about them later. 202 | 203 | ### `def` 204 | 205 | `def` is used to define top-level var. 206 | 207 | Simple function declarations: 208 | ```scala 209 | def add(x, y): x + y 210 | 211 | def print-greeting(s): 212 | println(str("Hello, " s "!")) 213 | 214 | def print-hi(): print("Hi") 215 | ``` 216 | 217 | Simple declarations that can be considered as constants: 218 | 219 | ```scala 220 | def hello: "Hello, world" 221 | 222 | def error-code: -1 223 | ``` 224 | 225 | ### `if-else` 226 | 227 | Simple control flow structure example: 228 | ```scala 229 | if 10 > 5: 230 | println("Yes") 231 | else: println("No") 232 | ``` 233 | 234 | if there is only one expression in `if` or `else` blocks, colon `:` can be omitted: 235 | ```scala 236 | if 10 > 5 println("Yes") else println("No") 237 | ``` 238 | 239 | ### `try-catch-finally` 240 | 241 | Example: 242 | ```scala 243 | try: 244 | 10 / 0 245 | catch SQLException e: 246 | println("SQL error!") 247 | catch Exception e: 248 | println("Division by zero!") 249 | finally: 250 | println("The end") 251 | ``` 252 | There can be one or more `catch` branches, `finally` branch is optional. 253 | 254 | ### `fn` 255 | 256 | `fn` and `lambda` are equivalents and are used to define anonymous functions. 257 | 258 | Example: 259 | ```scala 260 | map(fn x: x + 2, [1,2,3,4,5]) // => (3 4 5 6 7) (map (fn [x] (+ x 2)) [1 2 3 4 5]) 261 | 262 | filter(lambda x: x > 3, [1,2,3,4,5]) // => (4 5) (filter (fn [x] (> x 3)) [1 2 3 4 5]) 263 | ``` 264 | 265 | ### Other structures 266 | 267 | There are many structures that match code block declaration: 268 | ``` 269 | [ ...]? : 270 | 271 | ? 272 | ... 273 | ``` 274 | Such structures are `let`, `case`, `with-bindings` and others. 275 | 276 | They differ in Clojure code generation. 277 | 278 | ### `flat`-structures 279 | 280 | *Flat* structures are resulted in simple Clojure code generation 281 | (without `[]` and `{}`). 282 | 283 | Example: 284 | ```scala 285 | case val: 286 | 1 to println("one") 287 | 2 to println("tow") 288 | println("Something else") 289 | ``` 290 | Corresponding generated Clojure code (formatted): 291 | ```clojure 292 | (case val 293 | 1 (println "one") 294 | 2 (println "tow") 295 | (println "Something else")) 296 | ``` 297 | ### `vec`-structures 298 | *Vec* structures are resulted in Clojure code generation with brackets `[]`. 299 | 300 | Example: 301 | ```scala 302 | dotimes i to 10: 303 | println("Iteration:", i) 304 | ``` 305 | Corresponding generated Clojure code (formatted): 306 | ```clojure 307 | (dotimes [i 10] 308 | (println "Iteration:" i)) 309 | ``` 310 | Here expressions before colon: `i` and `10` are inserted into brackets: `[i 10]`. 311 | 312 | 313 | ### `map`-structures 314 | 315 | *Map* structures are resulted in Clojure code generation with braces `{}`. 316 | 317 | Example: 318 | ```scala 319 | with-bindings #'x to 1: 320 | println("x:", x) 321 | ``` 322 | Corresponding generated Clojure code (formatted): 323 | ```clojure 324 | (with-bindings {#'x 10} 325 | (println "x:" x)) 326 | ``` 327 | Here expressions before colon: `#'x` and `1` are inserted into curly braces: `{#'x 10}`. 328 | 329 | ### Table of structures 330 | 331 | | `flat` | `vec` | `map` | 332 | |----------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-----------------------------:| 333 | | `while`,
`case`,
`cond`,
`condp`,
`cond->`,
`cond->>`,
`locking`,
`time`,
`when` | `let`,
`for`,
`loop`,
`doseq`,
`dotimes`,
`binding`,
`with-open`,
`with-redefs`,
`with-local-vars`,
`when-let`,
`when-first` | `with-bindings`,
`with-redefs-fn` | 334 | 335 | ### Custom constructs 336 | 337 | It's possible to use a function or a macro from another namespace 338 | in a `flat`, `vec` or `map` style with indentation block. 339 | 340 | To do that, there are `flat`, `map` or `vec` blocks in a namespace declaration. 341 | For example, to use built-in Clojure `str` function with that style: 342 | 343 | ```scala 344 | ns example.core 345 | flat: str 346 | 347 | str "Hello": 348 | "John" 349 | "Angela" 350 | "Smith" 351 | ``` 352 | 353 | `flat`-block here is for transpiler. Generated Clojure code (formatted): 354 | 355 | ```clojure 356 | (ns example.core) 357 | 358 | (str "Hello" "John" "Angela" "Smith") 359 | ``` 360 | 361 | ### Clojure code 362 | Sometimes it can be necessary to insert Clojure code. 363 | Supposing, there is a library that has function `to` 364 | (`to`keyword is used as separator) and it needs to be used. 365 | For that purpose there are triple quotes: 366 | 367 | ```scala 368 | """to"""(arg1, arg2) // => (to arg1 arg2) 369 | ``` 370 | It's possible to insert big Clojure blocks: 371 | ```scala 372 | """ 373 | (defn hello [user] 374 | (println "Hello," user)) 375 | """ 376 | 377 | hello("User") 378 | ``` -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Eclipse Public License - v 2.0 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE 4 | PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION 5 | OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 6 | 7 | 1. DEFINITIONS 8 | 9 | "Contribution" means: 10 | 11 | a) in the case of the initial Contributor, the initial content 12 | Distributed under this Agreement, and 13 | 14 | b) in the case of each subsequent Contributor: 15 | i) changes to the Program, and 16 | ii) additions to the Program; 17 | where such changes and/or additions to the Program originate from 18 | and are Distributed by that particular Contributor. A Contribution 19 | "originates" from a Contributor if it was added to the Program by 20 | such Contributor itself or anyone acting on such Contributor's behalf. 21 | Contributions do not include changes or additions to the Program that 22 | are not Modified Works. 23 | 24 | "Contributor" means any person or entity that Distributes the Program. 25 | 26 | "Licensed Patents" mean patent claims licensable by a Contributor which 27 | are necessarily infringed by the use or sale of its Contribution alone 28 | or when combined with the Program. 29 | 30 | "Program" means the Contributions Distributed in accordance with this 31 | Agreement. 32 | 33 | "Recipient" means anyone who receives the Program under this Agreement 34 | or any Secondary License (as applicable), including Contributors. 35 | 36 | "Derivative Works" shall mean any work, whether in Source Code or other 37 | form, that is based on (or derived from) the Program and for which the 38 | editorial revisions, annotations, elaborations, or other modifications 39 | represent, as a whole, an original work of authorship. 40 | 41 | "Modified Works" shall mean any work in Source Code or other form that 42 | results from an addition to, deletion from, or modification of the 43 | contents of the Program, including, for purposes of clarity any new file 44 | in Source Code form that contains any contents of the Program. Modified 45 | Works shall not include works that contain only declarations, 46 | interfaces, types, classes, structures, or files of the Program solely 47 | in each case in order to link to, bind by name, or subclass the Program 48 | or Modified Works thereof. 49 | 50 | "Distribute" means the acts of a) distributing or b) making available 51 | in any manner that enables the transfer of a copy. 52 | 53 | "Source Code" means the form of a Program preferred for making 54 | modifications, including but not limited to software source code, 55 | documentation source, and configuration files. 56 | 57 | "Secondary License" means either the GNU General Public License, 58 | Version 2.0, or any later versions of that license, including any 59 | exceptions or additional permissions as identified by the initial 60 | Contributor. 61 | 62 | 2. GRANT OF RIGHTS 63 | 64 | a) Subject to the terms of this Agreement, each Contributor hereby 65 | grants Recipient a non-exclusive, worldwide, royalty-free copyright 66 | license to reproduce, prepare Derivative Works of, publicly display, 67 | publicly perform, Distribute and sublicense the Contribution of such 68 | Contributor, if any, and such Derivative Works. 69 | 70 | b) Subject to the terms of this Agreement, each Contributor hereby 71 | grants Recipient a non-exclusive, worldwide, royalty-free patent 72 | license under Licensed Patents to make, use, sell, offer to sell, 73 | import and otherwise transfer the Contribution of such Contributor, 74 | if any, in Source Code or other form. This patent license shall 75 | apply to the combination of the Contribution and the Program if, at 76 | the time the Contribution is added by the Contributor, such addition 77 | of the Contribution causes such combination to be covered by the 78 | Licensed Patents. The patent license shall not apply to any other 79 | combinations which include the Contribution. No hardware per se is 80 | licensed hereunder. 81 | 82 | c) Recipient understands that although each Contributor grants the 83 | licenses to its Contributions set forth herein, no assurances are 84 | provided by any Contributor that the Program does not infringe the 85 | patent or other intellectual property rights of any other entity. 86 | Each Contributor disclaims any liability to Recipient for claims 87 | brought by any other entity based on infringement of intellectual 88 | property rights or otherwise. As a condition to exercising the 89 | rights and licenses granted hereunder, each Recipient hereby 90 | assumes sole responsibility to secure any other intellectual 91 | property rights needed, if any. For example, if a third party 92 | patent license is required to allow Recipient to Distribute the 93 | Program, it is Recipient's responsibility to acquire that license 94 | before distributing the Program. 95 | 96 | d) Each Contributor represents that to its knowledge it has 97 | sufficient copyright rights in its Contribution, if any, to grant 98 | the copyright license set forth in this Agreement. 99 | 100 | e) Notwithstanding the terms of any Secondary License, no 101 | Contributor makes additional grants to any Recipient (other than 102 | those set forth in this Agreement) as a result of such Recipient's 103 | receipt of the Program under the terms of a Secondary License 104 | (if permitted under the terms of Section 3). 105 | 106 | 3. REQUIREMENTS 107 | 108 | 3.1 If a Contributor Distributes the Program in any form, then: 109 | 110 | a) the Program must also be made available as Source Code, in 111 | accordance with section 3.2, and the Contributor must accompany 112 | the Program with a statement that the Source Code for the Program 113 | is available under this Agreement, and informs Recipients how to 114 | obtain it in a reasonable manner on or through a medium customarily 115 | used for software exchange; and 116 | 117 | b) the Contributor may Distribute the Program under a license 118 | different than this Agreement, provided that such license: 119 | i) effectively disclaims on behalf of all other Contributors all 120 | warranties and conditions, express and implied, including 121 | warranties or conditions of title and non-infringement, and 122 | implied warranties or conditions of merchantability and fitness 123 | for a particular purpose; 124 | 125 | ii) effectively excludes on behalf of all other Contributors all 126 | liability for damages, including direct, indirect, special, 127 | incidental and consequential damages, such as lost profits; 128 | 129 | iii) does not attempt to limit or alter the recipients' rights 130 | in the Source Code under section 3.2; and 131 | 132 | iv) requires any subsequent distribution of the Program by any 133 | party to be under a license that satisfies the requirements 134 | of this section 3. 135 | 136 | 3.2 When the Program is Distributed as Source Code: 137 | 138 | a) it must be made available under this Agreement, or if the 139 | Program (i) is combined with other material in a separate file or 140 | files made available under a Secondary License, and (ii) the initial 141 | Contributor attached to the Source Code the notice described in 142 | Exhibit A of this Agreement, then the Program may be made available 143 | under the terms of such Secondary Licenses, and 144 | 145 | b) a copy of this Agreement must be included with each copy of 146 | the Program. 147 | 148 | 3.3 Contributors may not remove or alter any copyright, patent, 149 | trademark, attribution notices, disclaimers of warranty, or limitations 150 | of liability ("notices") contained within the Program from any copy of 151 | the Program which they Distribute, provided that Contributors may add 152 | their own appropriate notices. 153 | 154 | 4. COMMERCIAL DISTRIBUTION 155 | 156 | Commercial distributors of software may accept certain responsibilities 157 | with respect to end users, business partners and the like. While this 158 | license is intended to facilitate the commercial use of the Program, 159 | the Contributor who includes the Program in a commercial product 160 | offering should do so in a manner which does not create potential 161 | liability for other Contributors. Therefore, if a Contributor includes 162 | the Program in a commercial product offering, such Contributor 163 | ("Commercial Contributor") hereby agrees to defend and indemnify every 164 | other Contributor ("Indemnified Contributor") against any losses, 165 | damages and costs (collectively "Losses") arising from claims, lawsuits 166 | and other legal actions brought by a third party against the Indemnified 167 | Contributor to the extent caused by the acts or omissions of such 168 | Commercial Contributor in connection with its distribution of the Program 169 | in a commercial product offering. The obligations in this section do not 170 | apply to any claims or Losses relating to any actual or alleged 171 | intellectual property infringement. In order to qualify, an Indemnified 172 | Contributor must: a) promptly notify the Commercial Contributor in 173 | writing of such claim, and b) allow the Commercial Contributor to control, 174 | and cooperate with the Commercial Contributor in, the defense and any 175 | related settlement negotiations. The Indemnified Contributor may 176 | participate in any such claim at its own expense. 177 | 178 | For example, a Contributor might include the Program in a commercial 179 | product offering, Product X. That Contributor is then a Commercial 180 | Contributor. If that Commercial Contributor then makes performance 181 | claims, or offers warranties related to Product X, those performance 182 | claims and warranties are such Commercial Contributor's responsibility 183 | alone. Under this section, the Commercial Contributor would have to 184 | defend claims against the other Contributors related to those performance 185 | claims and warranties, and if a court requires any other Contributor to 186 | pay any damages as a result, the Commercial Contributor must pay 187 | those damages. 188 | 189 | 5. NO WARRANTY 190 | 191 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT 192 | PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" 193 | BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR 194 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF 195 | TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR 196 | PURPOSE. Each Recipient is solely responsible for determining the 197 | appropriateness of using and distributing the Program and assumes all 198 | risks associated with its exercise of rights under this Agreement, 199 | including but not limited to the risks and costs of program errors, 200 | compliance with applicable laws, damage to or loss of data, programs 201 | or equipment, and unavailability or interruption of operations. 202 | 203 | 6. DISCLAIMER OF LIABILITY 204 | 205 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT 206 | PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS 207 | SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 208 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST 209 | PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 210 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 211 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 212 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE 213 | POSSIBILITY OF SUCH DAMAGES. 214 | 215 | 7. GENERAL 216 | 217 | If any provision of this Agreement is invalid or unenforceable under 218 | applicable law, it shall not affect the validity or enforceability of 219 | the remainder of the terms of this Agreement, and without further 220 | action by the parties hereto, such provision shall be reformed to the 221 | minimum extent necessary to make such provision valid and enforceable. 222 | 223 | If Recipient institutes patent litigation against any entity 224 | (including a cross-claim or counterclaim in a lawsuit) alleging that the 225 | Program itself (excluding combinations of the Program with other software 226 | or hardware) infringes such Recipient's patent(s), then such Recipient's 227 | rights granted under Section 2(b) shall terminate as of the date such 228 | litigation is filed. 229 | 230 | All Recipient's rights under this Agreement shall terminate if it 231 | fails to comply with any of the material terms or conditions of this 232 | Agreement and does not cure such failure in a reasonable period of 233 | time after becoming aware of such noncompliance. If all Recipient's 234 | rights under this Agreement terminate, Recipient agrees to cease use 235 | and distribution of the Program as soon as reasonably practicable. 236 | However, Recipient's obligations under this Agreement and any licenses 237 | granted by Recipient relating to the Program shall continue and survive. 238 | 239 | Everyone is permitted to copy and distribute copies of this Agreement, 240 | but in order to avoid inconsistency the Agreement is copyrighted and 241 | may only be modified in the following manner. The Agreement Steward 242 | reserves the right to publish new versions (including revisions) of 243 | this Agreement from time to time. No one other than the Agreement 244 | Steward has the right to modify this Agreement. The Eclipse Foundation 245 | is the initial Agreement Steward. The Eclipse Foundation may assign the 246 | responsibility to serve as the Agreement Steward to a suitable separate 247 | entity. Each new version of the Agreement will be given a distinguishing 248 | version number. The Program (including Contributions) may always be 249 | Distributed subject to the version of the Agreement under which it was 250 | received. In addition, after a new version of the Agreement is published, 251 | Contributor may elect to Distribute the Program (including its 252 | Contributions) under the new version. 253 | 254 | Except as expressly stated in Sections 2(a) and 2(b) above, Recipient 255 | receives no rights or licenses to the intellectual property of any 256 | Contributor under this Agreement, whether expressly, by implication, 257 | estoppel or otherwise. All rights in the Program not expressly granted 258 | under this Agreement are reserved. Nothing in this Agreement is intended 259 | to be enforceable by any entity that is not a Contributor or Recipient. 260 | No third-party beneficiary rights are created under this Agreement. 261 | 262 | Exhibit A - Form of Secondary Licenses Notice 263 | 264 | "This Source Code may also be made available under the following 265 | Secondary Licenses when the conditions for such availability set forth 266 | in the Eclipse Public License, v. 2.0 are satisfied: GNU General Public 267 | License as published by the Free Software Foundation, either version 2 268 | of the License, or (at your option) any later version, with the GNU 269 | Classpath Exception which is available at 270 | https://www.gnu.org/software/classpath/license.html." 271 | 272 | Simply including a copy of this Agreement, including this Exhibit A 273 | is not sufficient to license the Source Code under Secondary Licenses. 274 | 275 | If it is not possible or desirable to put the notice in a particular 276 | file, then You may include the notice in a location (such as a LICENSE 277 | file in a relevant directory) where a recipient would be likely to 278 | look for such a notice. 279 | 280 | You may add additional accurate notices of copyright ownership. 281 | -------------------------------------------------------------------------------- /src-java/cwp/parser/Parser.java: -------------------------------------------------------------------------------- 1 | package cwp.parser; 2 | 3 | import clojure.lang.Util; 4 | import cwp.ast.*; 5 | 6 | import cwp.lexer.LexerReader; 7 | import cwp.lexer.Token; 8 | 9 | import java.util.*; 10 | 11 | public class Parser { 12 | // Precedence 13 | final static int LOWEST = 0; 14 | final static int PIPE = 1; // |> , |>> 15 | final static int OR = 2; 16 | final static int AND = 3; 17 | final static int NOT = 4; 18 | final static int COMPARISON = 5; // >, <, >= ,<=, =, ==, !=, 19 | final static int SUM = 6; // +, - 20 | final static int PRODUCT = 7; // *, / 21 | 22 | static HashMap precedence = new HashMap<>(); 23 | 24 | static { 25 | precedence.put("|>", PIPE); 26 | precedence.put("|>>", PIPE); 27 | precedence.put("or", OR); 28 | precedence.put("and", AND); 29 | // precedence.put("not", NOT); 30 | precedence.put("=", COMPARISON); 31 | precedence.put("==", COMPARISON); 32 | precedence.put("!=", COMPARISON); 33 | precedence.put(">", COMPARISON); 34 | precedence.put("<", COMPARISON); 35 | precedence.put(">=", COMPARISON); 36 | precedence.put("<=", COMPARISON); 37 | precedence.put("+", SUM); 38 | precedence.put("-", SUM); 39 | precedence.put("*", PRODUCT); 40 | precedence.put("/", PRODUCT); 41 | } 42 | 43 | public static int getPrecedence(String s) { 44 | return precedence.getOrDefault(s, -1); 45 | } 46 | 47 | 48 | LexerReader lexerReader; 49 | ArrayList indentation = new ArrayList(); 50 | int curLine = 1; 51 | 52 | 53 | public int lastIndentation() { 54 | 55 | int lastIndex = indentation.size() - 1; 56 | if (lastIndex < 0) { 57 | return -1; 58 | } 59 | return indentation.get(lastIndex); 60 | } 61 | 62 | public void addIndentation(int i) { 63 | indentation.add(i); 64 | /*if (i > lastIndentation()) 65 | indentation.add(i); 66 | else { 67 | Util.sneakyThrow(new ParserException("Bad indentation level: " + indentation + " " + i)); 68 | }*/ 69 | } 70 | 71 | public void popIndentation() { 72 | indentation.remove(indentation.size() - 1); 73 | } 74 | 75 | public boolean checkIndentation(int indent) { 76 | return indentation.contains(indent); 77 | } 78 | 79 | 80 | public Parser(String s) { 81 | lexerReader = new LexerReader(s); 82 | } 83 | 84 | public void unreadToken(Token t) { 85 | lexerReader.unread(t); 86 | } 87 | 88 | public Token nextTokenWithSep() { 89 | return lexerReader.read(); 90 | } 91 | 92 | public Token nextToken() { 93 | Token t = lexerReader.read(); 94 | while (t.type == Token.Type.COMMA || t.type == Token.Type.TO) { 95 | t = lexerReader.read(); 96 | } 97 | return t; 98 | } 99 | 100 | public Expr readExpr() { 101 | return readExpr(Token.Type.EOF, EofExpr.EOF_EXPR); 102 | } 103 | 104 | Stack stackExpr = new Stack<>(); 105 | 106 | // Dangerous to use, because when read nextToken(), after unreadExpr() somewhere, 107 | // nextToken() return token after unreaded Expressions - because tokens aren't from unread expr aren't unread 108 | // public void unreadExpr(Expr e) { 109 | // stackExpr.push(e); 110 | // } 111 | 112 | public Expr readExpr(Token.Type delim, Expr delimReturn) { 113 | if (!stackExpr.empty()) { 114 | return stackExpr.pop(); 115 | } 116 | Token t = nextToken(); 117 | if (t.type == Token.Type.EOF) { 118 | return EofExpr.EOF_EXPR; 119 | } 120 | if (t.type == delim) { 121 | delimReturn.initTok = t; 122 | return delimReturn; 123 | } 124 | if (t.type == Token.Type.SYMBOL && t.str.equals("def") && !t.callable) { 125 | return readDef(t); 126 | } 127 | if (t.type == Token.Type.SYMBOL && t.str.equals("if") && !t.callable) { 128 | return readIfElse(t); 129 | } 130 | if (t.type == Token.Type.SYMBOL && t.str.equals("try") && !t.callable) { 131 | return readTryCatchFinally(t); 132 | } 133 | if (t.type == Token.Type.SYMBOL && (t.str.equals("fn") || t.str.equals("lambda")) && !t.callable) { 134 | return readFn(t); 135 | } 136 | if (t.type == Token.Type.SYMBOL && t.str.equals("ns") && !t.callable) { 137 | return readNS(t); 138 | } 139 | if (t.type == Token.Type.SYMBOL && Controls.isFlat(t.str)) { 140 | return readFlatCotrol(t, Controls.Type.FLAT); 141 | } 142 | if (t.type == Token.Type.SYMBOL && Controls.isVec(t.str)) { 143 | return readFlatCotrol(t, Controls.Type.VEC); 144 | } 145 | if (t.type == Token.Type.SYMBOL && Controls.isMap(t.str)) { 146 | return readFlatCotrol(t, Controls.Type.MAP); 147 | } 148 | Expr e = readInfixExpr(t, LOWEST); 149 | if (e == null) { 150 | throw Util.sneakyThrow(new ParserException("Unexpected token: " + t.str, t)); 151 | } 152 | return e; 153 | } 154 | 155 | public Expr readInfixExpr(Token t, int prevPrecedence) { 156 | Expr firstExpr = readUnaryExpr(t); 157 | if (firstExpr.initTok.type == Token.Type.EOF) throw Util.runtimeException("EOF while reading"); 158 | Token opToken = nextTokenWithSep(); 159 | MultiInfixExpr mExpr = new MultiInfixExpr(firstExpr.initTok, opToken, firstExpr); 160 | for (; ; ) { 161 | int curPrecedence = getPrecedence(opToken.str); 162 | if (curPrecedence == -1 || prevPrecedence >= curPrecedence) { 163 | unreadToken(opToken); 164 | break; 165 | } 166 | Expr rightExpr = readInfixExpr(nextToken(), curPrecedence); 167 | mExpr.add(rightExpr); 168 | opToken = nextTokenWithSep(); 169 | if (!opToken.str.equals(mExpr.opToken.str)) { 170 | mExpr = new MultiInfixExpr(mExpr.initTok, opToken, mExpr); 171 | } 172 | } 173 | return mExpr.getExpr(); 174 | } 175 | 176 | public Expr readUnaryExpr(Token t) { 177 | if (t.type == Token.Type.META) { 178 | Expr meta = readExpr(); 179 | if (meta.initTok.type == Token.Type.EOF) throw Util.runtimeException("EOF while reading"); 180 | Expr expr = readExpr(); 181 | if (expr.initTok.type == Token.Type.EOF) throw Util.runtimeException("EOF while reading"); 182 | return new WithMetaExpr(t, meta, expr); 183 | } 184 | if (t.type == Token.Type.DEREF || 185 | t.type == Token.Type.QUOTE || 186 | t.type == Token.Type.UNQUOTE || 187 | t.type == Token.Type.SYNTAX_QUOTE || 188 | t.type == Token.Type.VAR_QUOTE) { 189 | Expr nextExpr = readExpr(); 190 | if (nextExpr.initTok.type == Token.Type.EOF) throw Util.runtimeException("EOF while reading"); 191 | return new UnaryExpr(t, nextExpr); 192 | } 193 | if (t.type == Token.Type.SYMBOL && 194 | (t.str.equals("not")) || 195 | (t.str.equals("throw"))) { 196 | Expr nextExpr = readExpr(); 197 | if (nextExpr.initTok.type == Token.Type.EOF) throw Util.runtimeException("EOF while reading"); 198 | return new UnaryExpr(t, nextExpr, true); 199 | } 200 | return readFunctionCallExpr(t); 201 | } 202 | 203 | public Expr readFunctionCallExpr(Token t) { 204 | Expr e = readBaseExpr(t); 205 | while (e != null && e.callable) { 206 | Token nextTok = nextToken(); 207 | if (nextTok.type != Token.Type.LPAREN) { 208 | throw Util.sneakyThrow(new ParserException("After callable should be '('", nextTok)); 209 | } 210 | DelimitedListResult d = readDelimitedList(Token.Type.RPAREN); 211 | e = new FunctionCallExpr(e, d.a, d.last.initTok.callable); 212 | } 213 | return e; 214 | } 215 | 216 | public Expr readBaseExpr(Token t) { 217 | if (t.type == Token.Type.REGEX 218 | || t.type == Token.Type.NUMBER 219 | || t.type == Token.Type.STRING 220 | || t.type == Token.Type.KEYWORD 221 | || t.type == Token.Type.SYMBOL 222 | || t.type == Token.Type.BOOL 223 | || t.type == Token.Type.NULL 224 | || t.type == Token.Type.CHAR 225 | || t.type == Token.Type.SYMBOLIC_VALUE 226 | || t.type == Token.Type.RAW 227 | ) { 228 | return new SimpleExpr(t, t.callable); 229 | } 230 | if (t.type == Token.Type.LCURLY) { 231 | DelimitedListResult res = readDelimitedList(Token.Type.RCURLY); 232 | if (res.a.size() % 2 != 0) { 233 | throw Util.sneakyThrow(new ParserException("Map literal must have even number of forms", t)); 234 | } 235 | return new MapExpr(t, res.a, res.last.initTok.callable); 236 | } 237 | if (t.type == Token.Type.NAMESPACE_MAP) { 238 | DelimitedListResult res = readDelimitedList(Token.Type.RCURLY); 239 | if (res.a.size() % 2 != 0) { 240 | throw Util.sneakyThrow(new ParserException("Map literal must have even number of forms", t)); 241 | } 242 | return new MapExpr(t, res.a, res.last.initTok.callable); 243 | } 244 | if (t.type == Token.Type.CONDITIONAL) { 245 | DelimitedListResult res = readDelimitedList(Token.Type.RPAREN); 246 | if (res.a.size() % 2 != 0) { 247 | throw Util.sneakyThrow(new ParserException("Conditional must have even number of forms", t)); 248 | } 249 | return new ConditionalExpr(t, res.a, res.last.initTok.callable); 250 | } 251 | if (t.type == Token.Type.LBRACE) { 252 | DelimitedListResult res = readDelimitedList(Token.Type.RBRACE); 253 | return new VectorExpr(t, res.a, res.last.initTok.callable); 254 | } 255 | if (t.type == Token.Type.SET) { 256 | DelimitedListResult res = readDelimitedList(Token.Type.RCURLY); 257 | return new SetExpr(t, res.a, res.last.initTok.callable); 258 | } 259 | if (t.type == Token.Type.LPAREN) { 260 | DelimitedListResult res = readDelimitedList(Token.Type.RPAREN); 261 | return new ParensExpr(t, res.a, res.last.initTok.callable); 262 | } 263 | if (t.type == Token.Type.EOF) { 264 | throw Util.runtimeException("EOF while reading"); 265 | } 266 | Util.sneakyThrow(new ParserException("Unexpected token: " + t.str, t)); 267 | return null; 268 | } 269 | 270 | public Expr readIfElse(Token initToken) { 271 | ArrayList ifExprs; 272 | ArrayList elseExprs = null; 273 | Expr condExpr = readExpr(); 274 | if (condExpr == EofExpr.EOF_EXPR) { 275 | throw Util.runtimeException("EOF while reading"); 276 | } 277 | // reading if clause 278 | Token nextToken = nextToken(); 279 | if (nextToken.type == Token.Type.EOF) { 280 | throw Util.runtimeException("EOF while reading"); 281 | } 282 | if (nextToken.type == Token.Type.COLON) { 283 | ifExprs = readBlock(initToken); 284 | } else { 285 | unreadToken(nextToken); 286 | Expr e = readExpr(); 287 | if (e == EofExpr.EOF_EXPR) throw Util.runtimeException("EOF while reading"); 288 | ifExprs = new ArrayList<>(); 289 | ifExprs.add(e); 290 | } 291 | // reading else clause 292 | // FIXED: error when block , because block return unreadExpr() but here is nextToken 293 | nextToken = nextToken(); 294 | // System.out.println(">>> nextToken, expect else: " + nextToken); 295 | if (nextToken.type == Token.Type.SYMBOL && nextToken.str.equals("else")) { 296 | nextToken = nextToken(); 297 | if (nextToken.type == Token.Type.COLON) { 298 | elseExprs = readBlock(initToken); 299 | } else { 300 | unreadToken(nextToken); 301 | Expr e = readExpr(); 302 | if (e == EofExpr.EOF_EXPR) throw Util.runtimeException("EOF while reading"); 303 | elseExprs = new ArrayList<>(); 304 | elseExprs.add(e); 305 | } 306 | } else { 307 | unreadToken(nextToken); 308 | } 309 | return new IfElseExpr(initToken, condExpr, ifExprs, elseExprs); 310 | } 311 | 312 | 313 | public DefExpr readDef(Token initTok) { 314 | boolean isFunction = false; 315 | ArrayList metas = new ArrayList<>(); 316 | ArrayList args = new ArrayList<>(); 317 | ArrayList body = new ArrayList<>(); 318 | Token nameTok; 319 | for (; ; ) { 320 | nameTok = nextToken(); 321 | if (nameTok.type != Token.Type.META) { 322 | break; 323 | } 324 | Expr expr = readExpr(); 325 | metas.add(expr); 326 | } 327 | if (nameTok.type != Token.Type.SYMBOL) { 328 | throw Util.sneakyThrow(new ParserException("Bad 'def' declaration, expected symbol, not: " + nameTok.str, 329 | nameTok)); 330 | } 331 | Token colonOrLBrace = nextToken(); 332 | if (colonOrLBrace.type != Token.Type.LPAREN && colonOrLBrace.type != Token.Type.COLON) { 333 | throw Util.sneakyThrow(new ParserException("Bad 'def' declaration, expected `(` or `:`, not: " + colonOrLBrace.str, 334 | colonOrLBrace)); 335 | } 336 | if (colonOrLBrace.type == Token.Type.LPAREN) { 337 | isFunction = true; 338 | DelimitedListResult d = readDelimitedList(Token.Type.RPAREN); 339 | args = d.a; 340 | colonOrLBrace = nextToken(); 341 | } 342 | if (colonOrLBrace.type != Token.Type.COLON) { 343 | throw Util.sneakyThrow(new ParserException("Bad 'def' declaration, expected `:`, not: " + colonOrLBrace.str, 344 | colonOrLBrace)); 345 | } 346 | body = readBlock(initTok); 347 | DefExpr defExpr = new DefExpr(initTok, nameTok, isFunction, metas, args, body); 348 | // System.out.println(" : " + defExpr.gen()); 349 | return defExpr; 350 | } 351 | 352 | public TryCatchFinallyExpr readTryCatchFinally(Token initTok) { 353 | Token nextTok = nextToken(); 354 | if (nextTok.type != Token.Type.COLON) { 355 | throw Util.sneakyThrow(new ParserException("Expected ':', but got: " + nextTok.str, nextTok)); 356 | } 357 | ArrayList body = readBlock(initTok); 358 | ArrayList catches = new ArrayList(); 359 | for (; ; ) { 360 | nextTok = nextToken(); 361 | if (nextTok.str.equals("catch") && nextTok.column == initTok.column) { 362 | CatchExpr e = readCatch(nextTok); 363 | catches.add(e); 364 | } else { 365 | break; 366 | } 367 | } 368 | ArrayList finallyExpr = new ArrayList(); 369 | if (nextTok.str.equals("finally") && nextTok.column == initTok.column) { 370 | /*if (nextTok.column != initTok.column) { 371 | throw Util.sneakyThrow(new ParserException("Finally keyword have to be at same column as try exception, at: " 372 | + nextTok.line + ", " + nextTok.column)); 373 | }*/ 374 | Token finallyTok = nextTok; 375 | nextTok = nextToken(); 376 | if (nextTok.type != Token.Type.COLON) { 377 | throw Util.sneakyThrow(new ParserException("Expected ':', but got:" + nextTok.str, nextTok)); 378 | } 379 | finallyExpr = readBlock(finallyTok); 380 | } else { 381 | unreadToken(nextTok); 382 | } 383 | TryCatchFinallyExpr tcfExpr = new TryCatchFinallyExpr(initTok, body, catches, finallyExpr); 384 | // System.out.println(" : " + tcfExpr.gen()); 385 | return tcfExpr; 386 | } 387 | 388 | public CatchExpr readCatch(Token initTok) { 389 | Token exceptionType = nextToken(); 390 | if (exceptionType.type != Token.Type.SYMBOL) { 391 | throw Util.sneakyThrow(new ParserException("Expected exception type, but got:" + exceptionType.str, 392 | exceptionType)); 393 | } 394 | Token exceptionName = nextToken(); 395 | //System.out.println(">>> Catch exType: " + exceptionType.str + " " + exceptionType.type); 396 | //System.out.println(">>> Catch exName: " + exceptionName.str + " " + exceptionName.type); 397 | if (exceptionName.type != Token.Type.SYMBOL) { 398 | throw Util.sneakyThrow(new ParserException("Expected exception name, but got:" + exceptionName.str, 399 | exceptionName)); 400 | } 401 | Token nextToken = nextToken(); 402 | if (nextToken.type != Token.Type.COLON) { 403 | throw Util.sneakyThrow(new ParserException("Expected ':', but got:" + nextToken.str, nextToken)); 404 | } 405 | ArrayList body = readBlock(initTok); 406 | return new CatchExpr(initTok, exceptionType, exceptionName, body); 407 | } 408 | 409 | 410 | public FnExpr readFn(Token initTok) { 411 | ArrayList args = new ArrayList<>(); 412 | ArrayList body = new ArrayList<>(); 413 | for (; ; ) { 414 | Token nextToken = nextToken(); 415 | if (nextToken.type == Token.Type.EOF) { 416 | throw Util.runtimeException("EOF while reading"); 417 | } 418 | if (nextToken.type == Token.Type.COLON) { 419 | break; 420 | } 421 | unreadToken(nextToken); 422 | Expr e = readExpr(); 423 | if (e == EofExpr.EOF_EXPR) { 424 | throw Util.runtimeException("EOF while reading"); 425 | } 426 | args.add(e); 427 | } 428 | body = readBlock(initTok); 429 | FnExpr fnExpr = new FnExpr(initTok, args, body); 430 | return fnExpr; 431 | } 432 | 433 | public ControlExpr readFlatCotrol(Token initTok, Controls.Type type) { 434 | //ArrayList args = readBlock(initTok); 435 | ArrayList args = new ArrayList(); 436 | for (; ; ) { 437 | Token nextToken = nextToken(); 438 | if (nextToken.type == Token.Type.COLON) { 439 | break; 440 | } 441 | if (nextToken.type == Token.Type.EOF) { 442 | throw Util.runtimeException("EOF while reading"); 443 | } 444 | unreadToken(nextToken); 445 | Expr e = readExpr(); 446 | args.add(e); 447 | } 448 | ArrayList body = readBlock(initTok); 449 | return new ControlExpr(initTok, args, body, type); 450 | } 451 | 452 | public NsExpr readNS(Token initTok) { 453 | ArrayList requires = null; 454 | ArrayList imports = null; 455 | ArrayList flat = new ArrayList<>(); 456 | ArrayList map = new ArrayList<>(); 457 | ArrayList vec = new ArrayList<>(); 458 | Expr nameExpr = readExpr(); 459 | if (nameExpr == EofExpr.EOF_EXPR) { 460 | throw Util.runtimeException("EOF while reading"); 461 | } 462 | 463 | Token nextToken = nextToken(); 464 | 465 | ArrayList names = new ArrayList<>(List.of("require", "import", "map", "flat", "vec")); 466 | Controls.reset(); 467 | 468 | while (names.contains(nextToken.str)) { 469 | Token colonToken = nextToken(); 470 | if (colonToken.type != Token.Type.COLON) { 471 | throw Util.sneakyThrow(new ParserException("Expected ':', but got: " + colonToken.str, colonToken)); 472 | } 473 | switch (nextToken.str) { 474 | case "require": 475 | requires = readBlock(nextToken); 476 | break; 477 | case "import": 478 | imports = readBlock(nextToken); 479 | break; 480 | case "vec": 481 | vec = readBlock(nextToken); 482 | break; 483 | case "flat": 484 | flat = readBlock(nextToken); 485 | break; 486 | case "map": 487 | map = readBlock(nextToken); 488 | break; 489 | } 490 | nextToken = nextToken(); 491 | } 492 | unreadToken(nextToken); 493 | // System.out.println(map); 494 | // System.out.println(vec); 495 | // System.out.println(flat); 496 | for (var e : map) Controls.addMap(e.gen()); 497 | for (var e : vec) Controls.addVec(e.gen()); 498 | for (var e : flat) Controls.addFlat(e.gen()); 499 | 500 | // Token nextToken = nextToken(); 501 | // if (nextToken.str.equals("require")) { 502 | // Token colonToken = nextToken(); 503 | // if (colonToken.type != Token.Type.COLON) { 504 | // throw Util.sneakyThrow(new ParserException("Expected ':', but got: " + colonToken.str + 505 | // " , at: " + colonToken.line + ", " + colonToken.column)); 506 | // } 507 | // requires = readBlock(nextToken); 508 | // } else { 509 | // unreadToken(nextToken); 510 | // } 511 | // nextToken = nextToken(); 512 | // if (nextToken.str.equals("import")) { 513 | // Token colonToken = nextToken(); 514 | // if (colonToken.type != Token.Type.COLON) { 515 | // throw Util.sneakyThrow(new ParserException("Expected ':', but got: " + colonToken.str + 516 | // " , at: " + colonToken.line + ", " + colonToken.column)); 517 | // } 518 | // imports = readBlock(nextToken); 519 | // } else { 520 | // unreadToken(nextToken); 521 | // } 522 | 523 | 524 | NsExpr nsExpr = new NsExpr(initTok, nameExpr, requires, imports); 525 | // System.out.println(nsExpr); 526 | return nsExpr; 527 | } 528 | 529 | public ArrayList readBlock(Token initTok) { 530 | ArrayList body = new ArrayList<>(); 531 | addIndentation(initTok.column); 532 | // System.out.println(">>> add indentation def: " + indentation + " initTok.column: " + initTok.column); 533 | Expr firstExpr = readExpr(); 534 | if (firstExpr.initTok.type == Token.Type.EOF) { 535 | throw Util.runtimeException("EOF while reading"); 536 | } 537 | /*if (firstExpr.initTok.line == initTok.line) { 538 | body.add(firstExpr); 539 | return body; 540 | }*/ 541 | if (firstExpr.initTok.column <= initTok.column) { 542 | throw Util.sneakyThrow(new ParserException("Bad indentation, for '" 543 | + firstExpr.initTok.str + "', should be nested to: " + initTok.str, firstExpr.initTok)); 544 | } 545 | body.add(firstExpr); 546 | addIndentation(firstExpr.initTok.column); 547 | //System.out.println(">>> add indentation first-tok: " + indentation + " initTok.column: " + firstExpr.initTok.column 548 | // + ", '" + firstExpr.gen() + "'"); 549 | //System.out.println(">>> indentation: " + indentation + " " + firstExpr.initTok.column + " " + firstExpr.gen()); 550 | Expr prevExpr = firstExpr; 551 | boolean second = true; 552 | for (; ; ) { 553 | Token nextToken = nextToken(); 554 | //System.out.println("NextExpr: " + nextToken.column + " " + initTok.column + " " + nextToken.toString() + "'"); 555 | if (nextToken.type == Token.Type.EOF || 556 | nextToken.type == Token.Type.COLON || 557 | nextToken.type == Token.Type.RPAREN || 558 | nextToken.type == Token.Type.RBRACE || 559 | nextToken.type == Token.Type.RCURLY 560 | ) { 561 | //System.out.println("NextExpr EOF: " + nextToken); 562 | unreadToken(nextToken); 563 | break; 564 | } else if (nextToken.column == firstExpr.initTok.column || 565 | (nextToken.line == prevExpr.initTok.line && nextToken.line != initTok.line) 566 | // || ((nextToken.line == prevExpr.initTok.line) && !second) 567 | ) { 568 | unreadToken(nextToken); 569 | Expr nextExpr = readExpr(); 570 | //System.out.println("NextExpr add: " + nextExpr.gen()); 571 | body.add(nextExpr); 572 | prevExpr = nextExpr; 573 | } else if (nextToken.column > firstExpr.initTok.column && nextToken.line != initTok.line) { 574 | //System.out.println("NextExpr throw: " + nextToken); 575 | Util.sneakyThrow(new ParserException("Bad indentation, greater than " 576 | + firstExpr.initTok.column + ": " + nextToken.str, nextToken)); 577 | } else /*if (!checkIndentation(nextToken.column)) { 578 | // System.out.println(">>: checkIndentation: " + indentation); 579 | // popIndentation(); 580 | Util.sneakyThrow(new ParserException("Bad indentation for: '" + nextToken.str 581 | + "', available: " + indentation.toString() + ", but get: " + nextToken.column + " " 582 | + ", at line: " + nextToken.line 583 | + ", column: " + nextToken.column)); 584 | break; 585 | } else */ { 586 | //System.out.println(">>block unread: " + nextToken); 587 | unreadToken(nextToken); 588 | break; 589 | } 590 | second = false; 591 | } 592 | popIndentation(); 593 | popIndentation(); 594 | // System.out.println(">>> return Block 2: " + body); 595 | return body; 596 | } 597 | 598 | 599 | public class DelimitedListResult { 600 | public ArrayList a; 601 | public Expr last; 602 | 603 | public DelimitedListResult(ArrayList a, Expr last) { 604 | this.a = a; 605 | this.last = last; 606 | } 607 | } 608 | 609 | public DelimitedListResult readDelimitedList(Token.Type tokDelim) { 610 | Expr onReturnExpr = new Expr(); 611 | ArrayList a = new ArrayList(); 612 | for (; ; ) { 613 | Expr form = readExpr(tokDelim, onReturnExpr); 614 | if (form == EofExpr.EOF_EXPR) { 615 | throw Util.runtimeException("EOF while reading"); 616 | } else if (form == onReturnExpr) { 617 | return new DelimitedListResult(a, form); 618 | } 619 | a.add(form); 620 | } 621 | } 622 | 623 | 624 | public ArrayList readAll() { 625 | ArrayList arr = new ArrayList(); 626 | //Token t = LexerReader.read(charReader); 627 | Token t = lexerReader.read(); 628 | curLine = t.line; 629 | if (t.column != lastIndentation()) { 630 | Util.runtimeException("EOF while reading character"); 631 | } 632 | while (t.type != Token.Type.EOF) { 633 | arr.add(t); 634 | // t = LexerReader.read(charReader); 635 | t = lexerReader.read(); 636 | if (t.type == Token.Type.COLON) { 637 | 638 | } 639 | } 640 | arr.add(t); 641 | return arr; 642 | } 643 | 644 | public static ArrayList readString(String s) { 645 | ArrayList arr = new ArrayList(); 646 | Parser p = new Parser(s); 647 | for (; ; ) { 648 | Expr e = p.readExpr(); 649 | if (e == EofExpr.EOF_EXPR) break; 650 | if (e.initTok.column != 1) { 651 | Util.sneakyThrow(new ParserException("Top level expression should start at column 1, but got: '" 652 | + e.initTok.str, e.initTok)); 653 | } 654 | arr.add(e); 655 | } 656 | return arr; 657 | } 658 | 659 | public static String genStr(String s) { 660 | ArrayList arr = readString(s); 661 | StringBuilder sb = new StringBuilder(); 662 | for (int i = 0; i < arr.size() - 1; i++) { 663 | Expr e = arr.get(i); 664 | sb.append(e.gen()); 665 | sb.append("\n\n"); 666 | } 667 | if (!arr.isEmpty()) { 668 | sb.append(arr.get(arr.size() - 1).gen()); 669 | } 670 | return sb.toString(); 671 | } 672 | } 673 | --------------------------------------------------------------------------------