├── Makefile ├── images └── logos │ ├── DukeOfferingLFE-square-large.png │ ├── DukeOfferingLFE-square-small.png │ ├── DukeOfferingLFE-square-tiny.png │ └── DukeOfferingLFE-square-medium.png ├── .gitignore ├── src ├── java.lfe ├── jlfe.app.src ├── jlfe-types.lfe └── jlfe-java.lfe ├── .travis.yml ├── package.exs ├── test └── unit │ ├── unit-jlfe-tests.lfe │ └── unit-jlfe-types-tests.lfe ├── jlfe.mk ├── rebar.config ├── doc ├── goals.rst └── features.rst ├── patches ├── jlfe-syntax.patch └── string-pretty-print-workaround.patch ├── common.mk └── README.rst /Makefile: -------------------------------------------------------------------------------- 1 | include common.mk 2 | include jlfe.mk 3 | -------------------------------------------------------------------------------- /images/logos/DukeOfferingLFE-square-large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfeutre/jlfe/HEAD/images/logos/DukeOfferingLFE-square-large.png -------------------------------------------------------------------------------- /images/logos/DukeOfferingLFE-square-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfeutre/jlfe/HEAD/images/logos/DukeOfferingLFE-square-small.png -------------------------------------------------------------------------------- /images/logos/DukeOfferingLFE-square-tiny.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfeutre/jlfe/HEAD/images/logos/DukeOfferingLFE-square-tiny.png -------------------------------------------------------------------------------- /images/logos/DukeOfferingLFE-square-medium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfeutre/jlfe/HEAD/images/logos/DukeOfferingLFE-square-medium.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | deps 2 | *.sublime-project 3 | *.sublime-workspace 4 | *.beam 5 | .eunit 6 | debug-* 7 | ebin/* 8 | bin/expm 9 | *.dump 10 | .rebar 11 | -------------------------------------------------------------------------------- /src/java.lfe: -------------------------------------------------------------------------------- 1 | ;; This module is needed in order to use the following Erjang native calls: 2 | ;; * java:call/4 3 | ;; * java:get_static/2 4 | (defmodule java) 5 | 6 | (defun noop () 7 | 'noop) 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: erlang 2 | script: "make check-travis" 3 | notifications: 4 | #irc: "irc.freenode.org#YOUR-PROJECT-CHANNEL" 5 | recipients: 6 | - duncan@cogitat.io 7 | otp_release: 8 | - R16B03 9 | - R15B03 10 | -------------------------------------------------------------------------------- /package.exs: -------------------------------------------------------------------------------- 1 | Expm.Package.new( 2 | name: "jlfe", 3 | description: "jlfe DESCRIPTION", 4 | version: "0.0.1", 5 | keywords: ["LFE", "Lisp", "Library", "API"], 6 | maintainers: [[name: "YOUR NAME", email: "YOUR@EMAIL.com"]], 7 | repositories: [[github: "YOUR_GITHUB_NAME/jlfe]]) 8 | -------------------------------------------------------------------------------- /src/jlfe.app.src: -------------------------------------------------------------------------------- 1 | %% -*- erlang -*- 2 | {application, jlfe, 3 | [{description, "An Erjang wrapper for LFE"}, 4 | {vsn, "0.0.1"}, 5 | {modules, [java, 6 | 'jlfe-java', 7 | 'jlfe-types']}, 8 | {registered, []}, 9 | {applications, [kernel, stdlib]} 10 | ]}. 11 | -------------------------------------------------------------------------------- /test/unit/unit-jlfe-tests.lfe: -------------------------------------------------------------------------------- 1 | (defmodule unit-jlfe-tests 2 | (export all) 3 | (import 4 | (from lfeunit-util 5 | (check-failed-assert 2) 6 | (check-wrong-assert-exception 2)))) 7 | 8 | (include-lib "deps/lfeunit/include/lfeunit-macros.lfe") 9 | 10 | (defun noop () 11 | 'noop) 12 | -------------------------------------------------------------------------------- /test/unit/unit-jlfe-types-tests.lfe: -------------------------------------------------------------------------------- 1 | (defmodule unit-jlfe-types-tests 2 | (export all) 3 | (import 4 | (from lfeunit-util 5 | (check-failed-assert 2) 6 | (check-wrong-assert-exception 2)))) 7 | 8 | (include-lib "deps/lfeunit/include/lfeunit-macros.lfe") 9 | 10 | (deftest java-types 11 | (is-equal 12 (length (jlfe-types:java-types)))) 12 | 13 | (deftest java-type? 14 | (is 'true (jlfe-types:java-type? (java.lang.Double:new)))) 15 | -------------------------------------------------------------------------------- /jlfe.mk: -------------------------------------------------------------------------------- 1 | patch-syntax: 2 | @echo "Appling jlfe syntax patch to LFE ..." 3 | @git apply --check patches/jlfe-syntax.patch && \ 4 | echo $$(git apply patches/jlfe-syntax.patch && echo "Patch applied!") || \ 5 | echo "Skipping; patch already applied.\n" 6 | 7 | patch-string: 8 | @echo "Appling pretty-print java.lang.String workaround patch to LFE ..." 9 | @git apply --check patches/string-pretty-print-workaround.patch && \ 10 | echo $$(git apply patches/string-pretty-print-workaround.patch && \ 11 | echo "Patch applied!") || \ 12 | echo "Skipping; patch already applied.\n" 13 | 14 | compile: get-deps patch-syntax patch-string clean-ebin 15 | @echo "Compiling project code and dependencies ..." 16 | @rebar compile 17 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {erl_opts, [debug_info, {src_dirs, ["test/unit", 2 | "test/integration", 3 | "test/system", 4 | "src"]}]}. 5 | {deps_dir, ["deps"]}. 6 | {eunit_compile_opts, [ 7 | {src_dirs, ["test/unit", 8 | "test/integration", 9 | "test/system", 10 | "src"]} 11 | ]}. 12 | {deps, [ 13 | {lfe, ".*", {git, "git://github.com/rvirding/lfe.git", 14 | "be25cc135e6c8f0d50288b41d7cf6a0572fe83fb"}}, 15 | {'lfe-utils', ".*", {git, "https://github.com/lfe/lfe-utils.git", "master"}}, 16 | {lfeunit, ".*", {git, "git://github.com/lfe/lfeunit.git", "master"}} 17 | ]}. 18 | -------------------------------------------------------------------------------- /doc/goals.rst: -------------------------------------------------------------------------------- 1 | jlfe Development 2 | ================ 3 | 4 | 5 | Goals 6 | ----- 7 | 8 | 9 | Feature Development 10 | ,,,,,,,,,,,,,,,,,,, 11 | 12 | Provide language infrastructure/support that will: 13 | 14 | #. Make writing JVM-based LFE code **succinct** and **elegant**; 15 | 16 | #. Make the interactions with Java objects, types, and data a **seamless 17 | experience**; and 18 | 19 | #. Make LFE/Erlang hacking **more alluring** to adventurous Clojure hackers 20 | :-) 21 | 22 | 23 | Project Integrations 24 | ,,,,,,,,,,,,,,,,,,,, 25 | 26 | If these experiments prove to actually make wrting LFE+Erjang code easier 27 | and more enjoyable (and more conceptually consistent), then discussions 28 | will be opened on the LFE and Erjang mailing lists (as well as IRC) with 29 | regard to: 30 | 31 | #. The level of interest of these changes in the code bases of those 32 | projects, 33 | 34 | #. Identifying the minimal set of changes necessary to provide the 35 | desired/acceptable functionality, 36 | 37 | #. Contributing any patches to those projects that they would find 38 | acceptable. 39 | 40 | If, however, these experiments are *unsuccessful*, who knows? 41 | Perhaps more/different efforts to accomplish similar goals ... 42 | -------------------------------------------------------------------------------- /src/jlfe-types.lfe: -------------------------------------------------------------------------------- 1 | (defmodule jlfe-types 2 | (export all)) 3 | 4 | ; XXX add functions for checking for types and then dispatching to extract the 5 | ; actual value 6 | 7 | (defun java-types () 8 | '(java.lang.Boolean 9 | java.lang.Byte 10 | java.lang.Character 11 | java.lang.Double 12 | java.lang.Enum 13 | java.lang.Float 14 | java.lang.Integer 15 | java.lang.Long 16 | java.lang.Number 17 | java.lang.Short 18 | java.lang.String 19 | java.lang.StringBuffer)) 20 | 21 | (defun java-type? (java-object) 22 | (lists:member 23 | (java:call java-object 'getClass '() '()) 24 | (java-types))) 25 | 26 | (defun java-obj? (java-object) 27 | (lists:member 28 | '"java" 29 | (string:tokens 30 | (atom_to_list (call java-object 'getClass)) 31 | '"."))) 32 | 33 | (defun check (java-object) 34 | "" 35 | (let ((type ())) 36 | (cond 37 | ('true)) 38 | ) 39 | ) 40 | 41 | (defun value-of (java-object) 42 | (case (call java-object 'getClass) 43 | ('java.lang.Boolean 44 | (list_to_atom (call java-object 'toString))) 45 | ('java.lang.Float 46 | (call java-object 'doubleValue)) 47 | ('java.math.BigDecimal 48 | (call java-object 'doubleValue)) 49 | (_ java-object))) 50 | -------------------------------------------------------------------------------- /patches/jlfe-syntax.patch: -------------------------------------------------------------------------------- 1 | From 55d6eacdb07fea6f0020bd99b8034f7947513cf7 Tue Apr 15 10:26:36 2014 2 | From: Duncan McGreggor 3 | Date: Tue, 15 Apr 2014 10:26:36 -0700 4 | Subject: [PATCH] A patch fo support jlfe syntax. 5 | 6 | --- 7 | src/lfe_macro.erl | 14 ++++++++++---- 8 | 1 file changed, 9 insertions(+), 4 deletions(-) 9 | 10 | diff --git a/deps/lfe/src/lfe_macro.erl b/src/lfe_macro.erl 11 | index 493f8fc..888fe0e 100644 12 | --- a/deps/lfe/src/lfe_macro.erl 13 | +++ b/deps/lfe/src/lfe_macro.erl 14 | @@ -790,12 +790,17 @@ exp_predef(['MODULE'], _, St) -> 15 | {yes,?Q(St#mac.module),St}; 16 | exp_predef(['LINE'], _, St) -> 17 | {yes,?Q(St#mac.line),St}; 18 | -exp_predef([Fun|As], _, St) when is_atom(Fun) -> 19 | - case string:tokens(atom_to_list(Fun), ":") of 20 | - [M,F] -> 21 | +exp_predef([Fun|As]=Call, _, St) when is_atom(Fun) -> 22 | + FirstChar = lists:nth(1, atom_to_list(Fun)), 23 | + Tokens = string:tokens(atom_to_list(Fun), ":"), 24 | + case [FirstChar,Tokens] of 25 | + [46,_] -> 26 | + {yes,[call,?Q('jlfe-java'),?Q(dispatch),?Q(Call)],St}; 27 | + [_,[M,F]] -> 28 | {yes,[call,?Q(list_to_atom(M)),?Q(list_to_atom(F))|As],St}; 29 | - _ -> no %This will also catch a:b:c 30 | + [_,_] -> no %This will also catch a:b:c 31 | end; 32 | + 33 | %% This was not a call to a predefined macro. 34 | exp_predef(_, _, _) -> no. 35 | 36 | -- 37 | 1.8.2 38 | 39 | -------------------------------------------------------------------------------- /patches/string-pretty-print-workaround.patch: -------------------------------------------------------------------------------- 1 | From 3eaac2ad8322e8a05e3c18bf035fe015f91fd238 Web Apr 16 17:02:41 2014 2 | From: Duncan McGreggor 3 | Date: Wed, 16 Apr 2014 17:02:41 -0700 4 | Subject: [PATCH] Added a workaround for the Java String errors. 5 | 6 | We've been seeing this error when trying to print Java strings in the LFE REPL: 7 | 8 | exception error: badarg 9 | in (: lfe_io_pretty print1 ) 10 | 11 | This workaround is a fairly ugly hack that works for now, until we can find the 12 | root cause for this issue (Robert and I have had a hell of a time tracking it 13 | down). 14 | --- 15 | src/lfe_shell.erl | 12 +++++++++++- 16 | 1 file changed, 11 insertions(+), 1 deletion(-) 17 | 18 | diff --git a/deps/lfe/src/lfe_shell.erl b/src/lfe_shell.erl 19 | index 57a50b9..b8ee32b 100644 20 | --- a/deps/lfe/src/lfe_shell.erl 21 | +++ b/deps/lfe/src/lfe_shell.erl 22 | @@ -69,6 +69,15 @@ server(_) -> 23 | St = #state{curr=Base2,local=Base2,base=Base2}, 24 | server_loop(St). 25 | 26 | +%% This is a work-around for string pretty-printing of Erjang/Java 27 | +%% java.lang.String instances not rendering properly (throwing an error). 28 | +coerce_value(Val) when is_list(Val) -> 29 | + case io_lib:printable_list(Val) of 30 | + true -> binary_to_list(list_to_binary(Val)); 31 | + false -> Val 32 | + end; 33 | +coerce_value(Val) -> Val. 34 | + 35 | server_loop(St0) -> 36 | StX = try 37 | %% Read the form 38 | @@ -77,7 +86,8 @@ server_loop(St0) -> 39 | Form = lfe_io:read(), 40 | Ee1 = update_vbinding('-', Form, St0#state.curr), 41 | %% Macro expand and evaluate it. 42 | - {Value,St1} = eval_form(Form, St0#state{curr=Ee1}), 43 | + {Value0,St1} = eval_form(Form, St0#state{curr=Ee1}), 44 | + Value = coerce_value(Value0), 45 | %% Print the result, but only to depth 30. 46 | VS = lfe_io:prettyprint1(Value, 30), 47 | io:requests([{put_chars,VS},nl]), 48 | -- 49 | 1.8.2 50 | 51 | -------------------------------------------------------------------------------- /common.mk: -------------------------------------------------------------------------------- 1 | PROJECT = jlfe 2 | LIB = $(PROJECT) 3 | DEPS = ./deps 4 | BIN_DIR = ./bin 5 | EXPM = $(BIN_DIR)/expm 6 | LFETOOL=$(BIN_DIR)/lfetool 7 | SOURCE_DIR = ./src 8 | OUT_DIR = ./ebin 9 | TEST_DIR = ./test 10 | TEST_OUT_DIR = ./.eunit 11 | SCRIPT_PATH=.:./bin:$(PATH):/usr/local/bin 12 | 13 | $(BIN_DIR): 14 | mkdir -p $(BIN_DIR) 15 | 16 | $(LFETOOL): $(BIN_DIR) 17 | @[ -f $(LFETOOL) ] || \ 18 | curl -o ./lfetool https://raw.github.com/lfe/lfetool/master/lfetool && \ 19 | chmod 755 ./lfetool && \ 20 | mv ./lfetool $(BIN_DIR) 21 | 22 | get-version: 23 | @PATH=$(SCRIPT_PATH) lfetool info version 24 | 25 | $(EXPM): $(BIN_DIR) 26 | @[ -f $(EXPM) ] || \ 27 | PATH=$(SCRIPT_PATH) lfetool install expm $(BIN_DIR) 28 | 29 | get-deps: 30 | @echo "Getting dependencies ..." 31 | @rebar get-deps 32 | @PATH=$(SCRIPT_PATH) lfetool update deps 33 | 34 | clean-ebin: 35 | @echo "Cleaning ebin dir ..." 36 | @rm -f $(OUT_DIR)/*.beam 37 | 38 | clean-eunit: 39 | @PATH=$(SCRIPT_PATH) lfetool tests clean 40 | 41 | compile-no-deps: clean-ebin 42 | @echo "Compiling only project code ..." 43 | @rebar compile skip_deps=true 44 | 45 | compile-tests: 46 | @PATH=$(SCRIPT_PATH) lfetool tests build 47 | 48 | shell: compile 49 | @clear 50 | @echo "Starting shell ..." 51 | @PATH=$(SCRIPT_PATH) lfetool repl 52 | 53 | shell-no-deps: compile-no-deps 54 | @clear 55 | @echo "Starting shell ..." 56 | @PATH=$(SCRIPT_PATH) lfetool repl 57 | 58 | clean: clean-ebin clean-eunit 59 | @rebar clean 60 | 61 | check-unit-only: 62 | @PATH=$(SCRIPT_PATH) lfetool tests unit 63 | 64 | check-integration-only: 65 | @PATH=$(SCRIPT_PATH) lfetool tests integration 66 | 67 | check-system-only: 68 | @PATH=$(SCRIPT_PATH) lfetool tests system 69 | 70 | check-unit-with-deps: get-deps compile compile-tests check-unit-only 71 | check-unit: compile-no-deps check-unit-only 72 | check-integration: compile check-integration-only 73 | check-system: compile check-system-only 74 | check-all-with-deps: compile check-unit-only check-integration-only \ 75 | check-system-only 76 | check-all: get-deps compile-no-deps 77 | @PATH=$(SCRIPT_PATH) lfetool tests all 78 | 79 | check: check-unit-with-deps 80 | 81 | check-travis: $(LFETOOL) check 82 | 83 | push-all: 84 | @echo "Pusing code to github ..." 85 | git push --all 86 | git push upstream --all 87 | git push --tags 88 | git push upstream --tags 89 | 90 | install: compile 91 | @echo "Installing jlfe ..." 92 | @PATH=$(SCRIPT_PATH) lfetool install lfe 93 | 94 | upload: $(EXPM) get-version 95 | @echo "Preparing to upload jlfe ..." 96 | @echo 97 | @echo "Package file:" 98 | @echo 99 | @cat package.exs 100 | @echo 101 | @echo "Continue with upload? " 102 | @read 103 | $(EXPM) publish 104 | -------------------------------------------------------------------------------- /doc/features.rst: -------------------------------------------------------------------------------- 1 | jlfe Development 2 | ================ 3 | 4 | 5 | Features 6 | -------- 7 | 8 | Below we have classified the features as one of the following: 9 | 10 | * **Existing** - features that have been pushed to this repo on github. 11 | 12 | * **Under Development** - feature that are currently being worked on; 13 | these are incomplete and may be broken or may not have been pushed to the 14 | public repo. 15 | 16 | * **Planned** - no work has been done, but we're thinking about these. 17 | 18 | As we make progress, we will moving items from one category to another. 19 | 20 | Watch for changes! 21 | 22 | 23 | Existing 24 | ,,,,,,,, 25 | 26 | *Caveat:* for those coming from Clojure, note that even though the "dot" is 27 | part of the form (and not a separate form unto itself), it follows the ordering 28 | of the `Clojure dot form`_: instance/class, then member/method/field. It dos 29 | *not* follow the ordering for when the "dot" is part of the calling form. 30 | 31 | Many of the existing features have demonstrated usage here: 32 | `README - jlfe Usage`_ 33 | 34 | * ``java.lfe`` - an empty module that alleviates the user from having to 35 | compile a ``java.beam`` every time they want to call Erjang's 36 | ``java:get_static/2`` or ``java:call/4`` functions. 37 | 38 | * Convenience short-cut for ``java.lang.*``: any passed Classname that begins 39 | with a capital letter is prepended with ``java.lang.``. 40 | 41 | * An LFE REPL wrapper that provides syntax for the following: 42 | 43 | * Constructors 44 | 45 | * ``(.Class)`` (implicit ``:new``) 46 | 47 | * ``(.Class arg-1 ... arg-N)`` (implicit ``:new``) 48 | 49 | * ``(.Class:new)`` 50 | 51 | * ``(.Class:new arg-1 ... arg-N)`` 52 | 53 | * Static class methods, fields 54 | 55 | * ``(.Class:member)`` (static method) 56 | 57 | * ``(.Class:member arg-1 ... arg-N)`` (static method) 58 | 59 | * ``(.Class:member)`` (static field variable/constants) 60 | 61 | * Nested classes 62 | 63 | * ``(.OuterClass$InnerClass:member)`` 64 | 65 | * ``(.OuterClass$InnerClass:member arg-1 ... arg-N)`` 66 | 67 | * Easier type conversion/coercion than what Erjang provides. (In particular, 68 | ``jlfe-types:value-of``.) 69 | 70 | 71 | Under Development 72 | ,,,,,,,,,,,,,,,,, 73 | 74 | * An LFE REPL wrapper that provides syntax for the following: 75 | 76 | * Instance methods 77 | 78 | * ``(.instance:member)`` 79 | 80 | * ``(.instance:member arg-1 ... arg-N)`` 81 | 82 | 83 | Planned 84 | ,,,,,,, 85 | 86 | * Macros (syntax) for accessing values of nested objects. 87 | 88 | * Allowing expressions as instance and class arguments. 89 | 90 | 91 | .. Links 92 | .. ----- 93 | 94 | .. _README - jlfe Usage: https://github.com/oubiwann/jlfe/blob/master/README.rst#jlfe-usage 95 | .. _Clojure dot form: http://clojure.org/java_interop#Java%20Interop-The%20Dot%20special%20form 96 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | #### 2 | jlfe 3 | #### 4 | 5 | *An experimental wrapper around bits of LFE* 6 | 7 | .. image:: images/logos/DukeOfferingLFE-square-tiny.png 8 | 9 | 10 | Introduction 11 | ============ 12 | 13 | This project is 1100100% experimentation. 14 | 15 | Its sole purpose is to explore the possibility of slightly increasing 16 | programmer convenience when using LFE on `Erjang`_ (Erlang on the JVM). 17 | 18 | Initially, chunks of LFE code were copied, but the latest version requires 19 | that one manually make a single change to a function (instructions below), 20 | and then does the rest in the jlfe code. 21 | 22 | 23 | **Development Information** 24 | 25 | * `Goals`_ 26 | 27 | * `Features`_ 28 | 29 | 30 | Dependencies 31 | ============ 32 | 33 | This project assumes that you have `lfetool`_ installed somwhere in your 34 | ``$PATH``. Also, we're not going to cover the installation of Java -- you 35 | *will* need Java installed on your system in order to run jlfe ;-) 36 | 37 | This project depends upon the following, which are saved to ``./deps`` when 38 | you run ``make get-deps``: 39 | 40 | * `LFE`_ (Lisp Flavored Erlang; needed only to compile) 41 | * `lfeunit`_ (needed only to run the unit tests) 42 | * `lfe-utils`_ 43 | 44 | Dependencies not installed automatically: 45 | 46 | * `lfetool`_ (click the link for installation instructions) 47 | * `kerl`_ (see below) 48 | * `Erjang`_ (see below) 49 | * `rlwrap`_ (``readline`` support for the Erjang shell; installable on many 50 | linux distros; on Mac OS X, install with `Homebrew`_) 51 | 52 | If you don't have ``rebar``, ``kerl`` and Erlang installed: 53 | 54 | .. code:: bash 55 | 56 | $ lfetool install rebar 57 | $ lfetool install kerl 58 | $ lfetool install erlang R16B 59 | $ . /opt/erlang/R16B/activate 60 | 61 | Erjang installation is similarly easy: 62 | 63 | .. code:: bash 64 | 65 | $ lfetool install erjang 66 | 67 | 68 | Obtaining and Building jlfe 69 | =========================== 70 | 71 | Building jlfe and its dependencies is as easy as this: 72 | 73 | .. code:: bash 74 | 75 | $ git clone https://github.com/oubiwann/jlfe.git 76 | $ cd jlfe 77 | $ make compile 78 | 79 | That last ``make`` target will do the following: 80 | 81 | * Download all the project dependencies, 82 | 83 | * Apply a patch to LFE to accept the jlfe form ``(.XXX ...)``, and 84 | 85 | * Compile all the dependencies, the patched LFE, and jlfe. 86 | 87 | 88 | jlfe Usage 89 | ========== 90 | 91 | 92 | With everything built, you're now ready to play. To run the examples below, 93 | start the jlfe REPL: 94 | 95 | .. code:: bash 96 | 97 | $ lfetool repl jlfe 98 | 99 | 100 | Syntax Additions 101 | ---------------- 102 | 103 | 104 | Constructors 105 | ,,,,,,,,,,,, 106 | 107 | 108 | .. code:: cl 109 | 110 | > (.java.util.HashMap) 111 | () 112 | > 113 | > (.java.lang.Double 42) 114 | 42.0 115 | 116 | Or you can use the short-cut for all ``java.lang.*`` classes: 117 | 118 | .. code:: cl 119 | 120 | > (.Double 42) 121 | 42.0 122 | 123 | 124 | Static Methods 125 | ,,,,,,,,,,,,,, 126 | 127 | .. code:: cl 128 | 129 | > (.java.lang.String:getName) 130 | java.lang.String 131 | 132 | or 133 | 134 | .. code:: cl 135 | 136 | > (.String:getName) 137 | java.lang.String 138 | > 139 | > (.Math:sin 0.5) 140 | 0.479425538604203 141 | 142 | 143 | Static Field Variables 144 | ,,,,,,,,,,,,,,,,,,,,,, 145 | 146 | e.g., constants: 147 | 148 | .. code:: cl 149 | 150 | > (.Math:PI) 151 | 3.141592653589793 152 | > 153 | > (.java.math.BigDecimal:ROUND_CEILING) 154 | 2 155 | 156 | 157 | Nested Classes 158 | ,,,,,,,,,,,,,, 159 | 160 | .. code:: cl 161 | 162 | > (.java.util.AbstractMap$SimpleEntry "a" "b") 163 | #B() 164 | 165 | 166 | Utility Functions 167 | ----------------- 168 | 169 | Some Java types from Erjang don't render anything useful when evaluated: 170 | 171 | .. code:: cl 172 | 173 | > (set bool (.Boolean true)) 174 | #B() 175 | > (set flt (.Float 42)) 176 | #B() 177 | > (set bigdec (.java.math.BigDecimal 42)) 178 | #B() 179 | 180 | 181 | The ``value-of`` function lets us treat Java objects as distinct values 182 | while still keeping the object around, should we want to call any methods on 183 | it, etc.: 184 | 185 | .. code:: cl 186 | 187 | > (jlfe-types:value-of bool) 188 | true 189 | > (jlfe-types:value-of flt) 190 | 42.0 191 | > (jlfe-types:value-of bigdec) 192 | 42.0 193 | 194 | Types that don't need special treatment are passed through, as-is: 195 | 196 | .. code:: cl 197 | 198 | > (jlfe-types:value-of (.Integer 42)) 199 | 42 200 | 201 | 202 | .. Links 203 | .. ----- 204 | .. _rebar: https://github.com/rebar/rebar 205 | .. _LFE: https://github.com/rvirding/lfe 206 | .. _lfeunit: https://github.com/lfe/lfeunit 207 | .. _lfe-utils: https://github.com/lfe/lfe-utils 208 | .. _Erjang: https://github.com/trifork/erjang 209 | .. _lfetool: https://github.com/lfe/lfetool/ 210 | .. _kerl: https://github.com/spawngrid/kerl 211 | .. _rlwrap: http://utopia.knoware.nl/~hlub/uck/rlwrap/#rlwrap 212 | .. _Homebrew: http://brew.sh/ 213 | .. _Goals: doc/goals.rst 214 | .. _Features: doc/features.rst 215 | -------------------------------------------------------------------------------- /src/jlfe-java.lfe: -------------------------------------------------------------------------------- 1 | (defmodule jlfe-java 2 | (export all) 3 | (import 4 | (from lists 5 | (map 2)) 6 | (from lfe-utils 7 | (capitalized? 1) 8 | (atom-cat 2)) 9 | (from string 10 | (tokens 2)) 11 | )) 12 | 13 | (defun get-java-name(name) 14 | (if 15 | (lfe-utils:capitalized? 16 | (atom_to_list name)) (lfe-utils:atom-cat 'java.lang. name) 17 | name)) 18 | 19 | (defun call-old 20 | (((cons mod (cons func args))) 21 | (lfe_io:format 22 | '"Got mod: ~p~nGot func: ~p~nGot args:~p~n" 23 | (list mod func args)) 24 | (let ((mod (get-java-name mod))) 25 | (cond 26 | ;; this condition looks for things like (.String:getName) and converts 27 | ;; to (.java.lang.String:getName) 28 | ((capitalized? (atom_to_list mod)) 29 | (let ((args (++ (list mod func '()) args))) 30 | (lfe_io:format '"New args: ~p~n" (list args)) 31 | (eval `(call ,@args)))) 32 | ('true 33 | (eval `(call ,@args))))))) 34 | 35 | (defun get-names 36 | "If the passed name doesn't have a ':' in it, there's no method/member 37 | defined, so assume the 'new' method. 38 | 39 | This function accepts a list of either one or two elements. It is expected 40 | that the Java mod:fun string will be tokenized on ':' and the result of that 41 | passed to this function. 42 | " 43 | (((cons class (cons method _))) 44 | (list class method)) 45 | (((cons class _)) 46 | (list class '"new"))) 47 | 48 | (defun -parse-mod-func (mod-func) 49 | "Every passed mod-func will have an initial dot. If not, something has gone 50 | really wrong. 51 | 52 | Once the dot is taken care of, any one of the following could be true: 53 | * no ':' divider, in which case assume 'new' and add ':new' 54 | * a capital initial letter, in which case, prepend 'java.lang' 55 | " 56 | (let* (((list mod func) (get-names (tokens (atom_to_list mod-func) '":"))) 57 | (name (cdr mod))) 58 | (cond 59 | ((capitalized? name) 60 | (list (++ '"java.lang" mod) func)) 61 | ('true 62 | (list name func))))) 63 | 64 | (defun parse-mod-func (mod-func) 65 | "Let another function do all the work. We're just gonna kick back and convert 66 | the results to atoms." 67 | (map #'list_to_atom/1 (-parse-mod-func mod-func))) 68 | 69 | (defun dispatch 70 | "Cases to consider: 71 | * dot forms with abbreviated Classnames 72 | * full dot forms (both classes and instances) 73 | * constants (static fields) 74 | 75 | We want to map native Erjang supported calls mapped jlfe calls in the 76 | following manner: 77 | 78 | * (java.lang.Double:new 42) -> (.Double 42) or (.java.lang.Double 42) 79 | * (dbl:intValue) -> (.dbl:intValue) 80 | * (java:get_static 'java.lang.Double 'SIZE) -> (.java.lang.Double:SIZE) or 81 | (.Double:SIZE) 82 | Some examples with args: 83 | 84 | * (set hm (java.util.HashMap:new)) 85 | * (call hm 'put 1 'foo) -> (.hm:put 1 'foo) 86 | * (call hm 'get 1) -> (.hm:get 1) 87 | 88 | These can all be boiled down to two approaches, with 'call' and 89 | 'java:get_static'. 90 | 91 | With 'call': 92 | 93 | * (call Class member) 94 | * (call Class member arg-1 ... arg-N) 95 | * (call instannce member) 96 | * (call instannce member arg-1 ... arg-N) 97 | 98 | With 'java:get_static': 99 | 100 | * (java:get_static Class member) 101 | 102 | We can unify these with one syntax: 103 | 104 | * (.Class) -> constructor 105 | * (.Class arg-1 ... arg-N) -> constructor 106 | * (.Class:new) -> constructor 107 | * (.Class:new arg-1 ... arg-N) -> constructor 108 | * (.Class:member) -> use try/catch to distinguish between static method 109 | calls and static field variable access 110 | * (.Class:member arg-1 ... arg-N) 111 | * (.instance:member) 112 | * (.instance:member arg-1 ... arg-N) 113 | 114 | As such, this function needs to do the following (in order): 115 | 116 | 1. Call a function that: 117 | a. Checks to see if there is a mod:func; if not, assume mod:new. 118 | b. Checks to see if the class/instance begins with a capital letter; 119 | if so, prepend 'java.lang. to it. 120 | 2. Attempt to (call ...) with the passed args inside a try form. 121 | 3. If this fails, try to call (java:get_static ...). 122 | 4. If that fails, re-throw the first error. 123 | 5. Call a method on an instance that's a saved variable. 124 | " 125 | (((cons mod-func args)) 126 | (let* (((list mod func) (parse-mod-func mod-func)) 127 | (all (++ (list mod func) args))) 128 | ; XXX debug 129 | ; (lfe_io:format '"mod: ~w~nfunc: ~w~nargs: ~w~n" (list mod func args)) 130 | ; (lfe_io:format '"all args: ~p~n" (list all)) 131 | ; (lfe_io:format '"all args, no eval: ~w~n" (list all)) 132 | (java-call all) 133 | )) 134 | ((args) 135 | (tuple 'error 136 | (list (io_lib:format '"No match; got args: ~p~n" (list args)))))) 137 | 138 | (defun java-call (args) 139 | (try 140 | ;; This will work for classes, but not instances. 141 | (java-call-multi-arity args) 142 | (catch 143 | ((= error (tuple type value _)) (when (== value 'undef)) 144 | ;; This will work for instances. 145 | (java-call-instance-method args error))))) 146 | ;(error error))))) 147 | 148 | ;; XXX This is one ugly hack. Counldn't get a macro to work, though, so I fell 149 | ;; back on this. Erlang and LFE do this some thing in a couple places :-/ 150 | ;; PRs welcome! 151 | (defun java-call-multi-arity (args) 152 | "Awwww, yeah. Who wants boiled plate for dinner?" 153 | ; XXX debug 154 | ; (io:format '"Arg count: ~p~nArgs: ~w~n" (list (length args) args)) 155 | (case (length args) 156 | (2 (java-call-2-arity args)) 157 | (3 (apply #'call/3 args)) 158 | (4 (apply #'call/4 args)) 159 | (5 (apply #'call/5 args)) 160 | (6 (apply #'call/6 args)) 161 | (7 (apply #'call/7 args)) 162 | (8 (apply #'call/8 args)) 163 | (9 (apply #'call/9 args)) 164 | (10 (apply #'call/10 args)) 165 | (11 (apply #'call/11 args)) 166 | (12 (apply #'call/12 args)) 167 | (13 (apply #'call/13 args)) 168 | (14 (apply #'call/14 args)) 169 | (15 (apply #'call/15 args)))) 170 | 171 | (defun java-call-2-arity (args) 172 | "This is the normal 2-arity call, wrapped with tender loving care in a catch 173 | that will try to make a static method call if this one fails." 174 | (try 175 | ;; The only time this is expected to fail is when a user is trying 176 | ;; to : 177 | ;; * make calls such as (.String:getName) when (call ...) won't 178 | ;; cut it, or 179 | ;; * access a field variable 180 | (apply #'call/2 args) 181 | (catch 182 | ((= error (tuple type value _)) (when (== value 'badfun)) 183 | ; XXX debug 184 | ; (io:format '"Got error type: ~p~nGot error value: ~p~n" 185 | ; (list type value)) 186 | ;; We've gotten the error we expect when a user needs to make the 187 | ;; other calls; let's try one: 188 | (java-call-static-method args error))))) 189 | 190 | (defun java-call-static-method (args error) 191 | "The function for static methods. This called when the normal call/2 fails." 192 | (try 193 | (apply #'java:call/4 (++ args '(() ()))) 194 | (catch 195 | (_ 196 | ;; Looks like that didn't work; let's try the other call: 197 | (java-call-static-field args error))))) 198 | 199 | (defun java-call-static-field (args error) 200 | "The function for static fields (e.g., constants). This is called when the 201 | call for static methods fails." 202 | (try 203 | (apply #'java:get_static/2 args) 204 | (catch 205 | ;; We don't actually care about this error, so let's error 206 | ;; the original one. 207 | (_ (error error))))) 208 | 209 | (defun java-call-instance-method (args error) 210 | "The function for calling members on Java instances. This is called when the 211 | multiple arity caller fails." 212 | (try 213 | ; XXX debug 214 | (let* ((first (car args)) 215 | (rest (cdr args)) 216 | ;(first-eval (eval first)) 217 | ;(first-exp (macroexpand-all first-eval)) 218 | ) 219 | (io:format '"first: ~w~nrest: ~w~neval'ed: ~w~n: exp'ed: ~w~n" 220 | (list first rest '() '())) 221 | (java-call-multi-arity (++ (list (macroexpand-all (eval (car args)))) (cdr args))) 222 | ) 223 | (catch 224 | ;; We don't actually care about this error, so let's error 225 | ;; the original one. 226 | ; XXX debug 227 | ((= new-error (tuple type value _)) 228 | (io:format '"Got error (~p, ~p): ~w~n" (list type value error)) 229 | (error error) 230 | )))) 231 | ;(_ (error error))))) 232 | --------------------------------------------------------------------------------