package org.jerlang; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import org.jerlang.erts.erlang.Error; import org.jerlang.stdlib.beam_lib.BeamData; import org.jerlang.type.Atom; import org.jerlang.type.Fun; import org.jerlang.type.Integer; import org.jerlang.type.List; import org.jerlang.type.Term; import org.jerlang.util.StringUtil; import org.jerlang.vm.VirtualMachine; /** * = Modules * * Erlang code is divided into modules. * A module consists of a sequence of attributes and function declarations, * each terminated by period (.). * * Example: * ---- * -module(m). % module attribute * -export([fact/1]). % module attribute * * fact(N) when N>0 -> % beginning of function declaration * N * fact(N-1); % | * fact(0) -> % | * 1. % end of function declaration * ---- * * http://www.erlang.org/doc/reference_manual/modules.html */ public class Module { private static final MethodType METHOD_TYPE = MethodType.methodType(Term.class, List.class); private static final MethodType NAME_METHOD = MethodType.methodType(Atom.class); private final BeamData beamData; private final Class<?> moduleClass; private final Atom name; private final Map<FunctionSignature, Fun> exported_functions; private final Map<FunctionSignature, Integer> labels; public Module(BeamData beamData, Atom name) { this.beamData = beamData; this.exported_functions = new HashMap<>(); this.labels = new HashMap<>(); this.moduleClass = null; this.name = name; } public Module(Class<?> moduleClass, Atom name) { this.beamData = null; this.exported_functions = new HashMap<>(); this.labels = new HashMap<>(); this.moduleClass = moduleClass; this.name = name; } public Term apply(FunctionSignature signature, Term params) throws Error { if (!hasFunction(signature)) { throw new Error("Unknown function: " + signature); } Process process = VirtualMachine.self(); if (process == null) { // We have been probably invoked from plain Java code, // So we need to spawn a process and execute the function // within that process. process = VirtualMachine.instance().spawn(signature.module(), signature.function(), params.toList()); ProcessRegistry.self(process); while (process.state() != ProcessState.EXITING) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } return process.getX(0); } else { process.pushSignature(signature); Fun fun = exported_functions.get(signature); Term result = fun.apply(params); process.popSignature(); return result; } } public BeamData beamData() { return beamData; } public void export() { if (moduleClass == null) { // The module was loaded from a BEAM file MethodHandle mh = null; try { mh = MethodHandles.lookup().findStatic(Interpreter.class, "dispatch", METHOD_TYPE); } catch (Exception e) { e.printStackTrace(); } for (FunctionSignature s : beamData.exportTableChunk().exports()) { labels.put(s, s.label()); exported_functions.put(s, new Fun(s, mh)); } } else { // The module is included in JErlang for (Method method : moduleClass.getDeclaredMethods()) { int arity = method.getParameterCount(); if (method.getName().startsWith("_")) { // If the method name is a reserved Java keyword, // the method is prefixed by an underscore. // For example: `erlang:throw` or `ets:new` export(method.getName().substring(1), arity); } else { export(method.getName(), arity); } } } } public Module export(String name, int arity) { return export(Atom.of(name), Integer.of(arity)); } public Module export(Atom name, Integer arity) { FunctionSignature s = new FunctionSignature( this.name, name, arity); try { String p = moduleClass.getPackage().getName(); String pm = p + "." + this.name.toString(); String cn = pm + "." + StringUtil.snakeToCamelCase(this.name.toString()) + StringUtil.snakeToCamelCase(name.toString()); Class<?> c = getClass().getClassLoader().loadClass(cn); MethodHandle handle = MethodHandles.lookup() .findStatic(c, "dispatch", METHOD_TYPE); s = maybe_override_name(s, c); exported_functions.put(s, new Fun(s, handle)); } catch (NoSuchMethodException | IllegalAccessException | ClassNotFoundException e) { System.err.println("Can not export: " + s); } return this; } /** * This module is included in JErlang and * not loaded from a BEAM file. */ public boolean isInternal() { return (moduleClass != null) && (beamData == null); } /** * Some functions have names that cannot be expressed in Java. * For example: "erlang:+/2". * If a function implementing class declares a static name() method, * its return value will be used as function name instead. */ private FunctionSignature maybe_override_name(FunctionSignature s, Class<?> c) { try { MethodHandle h = MethodHandles.lookup().findStatic(c, "name", NAME_METHOD); Atom newName = (Atom) h.invoke(); s = new FunctionSignature(s.module(), newName, s.element(3).toInteger()); } catch (Throwable e) { // Ignore } return s; } public boolean hasFunction(FunctionSignature functionSignature) { return exported_functions.containsKey(functionSignature); } public Atom name() { return name; } }