Skip to content

Commit 307f05f

Browse files
committed
Implementing lambdas 1
1 parent 7022a5a commit 307f05f

8 files changed

Lines changed: 139 additions & 33 deletions

File tree

README.md

Lines changed: 93 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,94 @@
1-
NT Script
1+
Simple scripting langauge based on ANTLR4 that compiles into Java
22

3-
Simple scripting langauge based on ANTLR4 that compiles into Java
3+
## Language Reference ##
4+
5+
- Variable assignment
6+
- `@name = <value>`
7+
- `@stringVariable = "hello world"`
8+
- `@daysInWeek = 7`
9+
- `@boolVal = True`
10+
- Conditionals
11+
- ```
12+
IF @boolVal
13+
<Do stuff>
14+
END
15+
16+
- Loops
17+
- ```
18+
FOREACH @item IN @array
19+
<statements>
20+
END
21+
22+
- Function call
23+
- `fnName{namedArg1=@variable1, namedArg3=@variable3}`
24+
25+
26+
- Lambda expr
27+
28+
- ```
29+
@param1 = "text"
30+
@param2 = 456789
31+
@fn = FUNCTION @param1, @param2
32+
<statements>
33+
END
34+
35+
36+
## API ##
37+
38+
The compiler will generate only method bodies from the script. annotate method that you wish to generate
39+
```
40+
public abstract class Test {
41+
@ScriptMeta.ScriptTarget
42+
public abstract Result test();
43+
}
44+
```
45+
46+
To call java method from the script first define an annotated proxy
47+
48+
```
49+
@ScriptMeta.Function("print")
50+
public class PrintFn {
51+
@ScriptMeta.Handler
52+
public void print(@ScriptMeta.NamedParam("val") Object o, @ScriptMeta.NamedParam("int") int i) {
53+
System.out.println(o);
54+
System.out.println(i);
55+
}
56+
}
57+
```
58+
59+
and provide its instanceto the context via add the builder
60+
61+
```
62+
NTScript context = new NTScript.Builder()
63+
.package_("cz.neumimto.test")
64+
.debugOutput("/tmp/test")
65+
.implementingType(Test.class)
66+
.withEnum(Result.class)
67+
.add(List.of(new PrintFn()))
68+
.setClassNamePattern("aaa")
69+
.build();
70+
71+
Class aClass = script.parseScript("""
72+
@textVariable = "abcd"
73+
print{val=@textVariable}
74+
RETURN Result.OK
75+
""");
76+
```
77+
78+
The compiler will generate class as follows:
79+
80+
```
81+
public class aaa0 extends Test {
82+
83+
public PrintFn PrintFn;
84+
85+
public Result test() {
86+
String var1 = "abcd";
87+
PrintFn.print(var1, null) ;
88+
return Result.OK
89+
}
90+
}
91+
```
92+
93+
- After calling constructor you should inject the PrintFn field.
94+
- If you are using some sort of DI such as Guice you can use Builder methods `classAnnotations` and `fieldAnnotations` for simpler initialization.

src/main/java/cz/neumimto/nts/NTScript.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -145,10 +145,10 @@ public ClassVisitor wrap(TypeDescription instrumentedType, ClassVisitor classVis
145145

146146
List<Scope> scopes = visitor.getImpl().getScopes();
147147

148-
for (int i = scopes.size()-1; i > 1; i--) {
148+
for (int i = scopes.size()-1; i > 0; i--) {
149149
Scope scope = scopes.get(i);
150150

151-
bb = bb.defineMethod("lambda$"+i+"$a", void.class, Opcodes.ACC_PRIVATE | Opcodes.ACC_SYNTHETIC)
151+
bb = bb.defineMethod(Scope.LAMBDA_METHOD_NAME.apply(i), void.class, Opcodes.ACC_PRIVATE | Opcodes.ACC_SYNTHETIC)
152152
.intercept(new Implementation() {
153153
@Override
154154
public ByteCodeAppender appender(Target implementationTarget) {
@@ -191,9 +191,7 @@ private Implementation getImplementation(VisitorImpl visitor) {
191191
public ByteCodeAppender appender(Target implementationTarget) {
192192
return (methodVisitor, implementationContext, instrumentedMethod) -> {
193193

194-
List<StackManipulation> impl = visitor.getImpl().getScopes().iterator().next().impl;
195-
List<StackManipulation> list = new ArrayList<>();
196-
list.addAll(impl);
194+
List<StackManipulation> impl = visitor.getImpl().getScopes().get(0).impl;
197195

198196
StackManipulation.Size size = new StackManipulation.Compound(
199197
impl

src/main/java/cz/neumimto/nts/Scope.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@
44
import net.bytebuddy.implementation.bytecode.StackManipulation;
55

66
import java.util.*;
7+
import java.util.function.Function;
78

89
public class Scope {
10+
public static Function<Integer, String> LAMBDA_METHOD_NAME = i -> "lambda$a$" + i;
11+
912
public final Map<String, Variable> variables;
1013

1114
private final List<Scope> parentsForVarLookup;

src/main/java/cz/neumimto/nts/ScriptContext.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,18 @@
22

33
import cz.neumimto.nts.bytecode.Variable;
44
import net.bytebuddy.description.type.TypeDescription;
5-
import net.bytebuddy.dynamic.scaffold.InstrumentedType;
65
import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
76

87
import java.lang.reflect.Method;
98
import java.util.*;
109

11-
import static cz.neumimto.nts.annotations.ScriptMeta.*;
10+
11+
import static cz.neumimto.nts.annotations.ScriptMeta.Function;
12+
import static cz.neumimto.nts.annotations.ScriptMeta.Handler;
13+
1214

1315
public class ScriptContext {
16+
1417
private List<Scope> scopes = new ArrayList<>();
1518
int currentScopeIdx = 0;
1619
private final Collection<Object> mechanics;
Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package cz.neumimto.nts.bytecode;
22

3+
import cz.neumimto.nts.Scope;
4+
import cz.neumimto.nts.ScriptContext;
35
import net.bytebuddy.description.type.TypeDescription;
46
import net.bytebuddy.implementation.Implementation;
57
import net.bytebuddy.implementation.bytecode.StackManipulation;
@@ -12,10 +14,19 @@
1214
import java.lang.invoke.LambdaMetafactory;
1315
import java.lang.invoke.MethodHandles;
1416
import java.lang.invoke.MethodType;
17+
import java.util.List;
18+
import java.util.Map;
1519
import java.util.stream.Collectors;
1620

1721
public class InvokeDynamic implements StackManipulation {
1822

23+
private final Map<String, Variable> fnVars;
24+
private ScriptContext scriptContext;
25+
26+
public InvokeDynamic(ScriptContext scriptContext, Map<String, Variable> fnVars) {
27+
this.scriptContext = scriptContext;
28+
this.fnVars = fnVars;
29+
}
1930

2031
@Override
2132
public boolean isValid() {
@@ -24,24 +35,26 @@ public boolean isValid() {
2435

2536
@Override
2637
public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
27-
// String descriptor = "(%s)Ljava/lang/Runnable;";
28-
// String k = ctx.localVariables().values().stream().map(a-> new TypeDescription.ForLoadedType(a.aClass).getDescriptor()).collect(Collectors.joining());
29-
// descriptor = descriptor.replaceAll("%s",ctx.thisType().getDescriptor() + k);
30-
//
31-
// methodVisitor.visitInvokeDynamicInsn("run",
32-
// descriptor,
33-
// new Handle(Opcodes.H_INVOKESTATIC,
34-
// new TypeDescription.ForLoadedType(LambdaMetafactory.class).getInternalName(),
35-
// "metafactory",
36-
// MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, String.class, Object[].class).toMethodDescriptorString(),
37-
// false),
38-
// Type.VOID_TYPE,
39-
// new Handle(Opcodes.H_INVOKESPECIAL,
40-
// this.ctx.thisType().getInternalName(),
41-
// methodName,
42-
// "("+k+")V",
43-
// false),
44-
// Type.VOID_TYPE);
38+
String descriptor = "(%s)Ljava/lang/Runnable;";
39+
String k = fnVars.values().stream()
40+
.map(a-> new TypeDescription.ForLoadedType(a.getRuntimeType()).getDescriptor())
41+
.collect(Collectors.joining());
42+
descriptor = descriptor.replaceAll("%s",scriptContext.getInsnType().getDescriptor() + k);
43+
44+
methodVisitor.visitInvokeDynamicInsn("run",
45+
descriptor,
46+
new Handle(Opcodes.H_INVOKESTATIC,
47+
new TypeDescription.ForLoadedType(LambdaMetafactory.class).getInternalName(),
48+
"metafactory",
49+
MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, String.class, Object[].class).toMethodDescriptorString(),
50+
false),
51+
Type.VOID_TYPE,
52+
new Handle(Opcodes.H_INVOKESPECIAL,
53+
scriptContext.getInsnType().getInternalName(),
54+
Scope.LAMBDA_METHOD_NAME.apply(scriptContext.getScopes().size() - 1),
55+
"("+k+")V",
56+
false),
57+
Type.VOID_TYPE);
4558
return null;
4659
}
4760
}

src/main/java/cz/neumimto/nts/bytecode/VisitorImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ public ScriptContext visitLambda(ntsParser.LambdaContext ctx) {
255255
addInsn(fnVar.load());
256256
}
257257

258-
addInsn(new InvokeDynamic());
258+
addInsn(new InvokeDynamic(scriptContext, fnVars));
259259

260260
scriptContext.createNewScopeWithVars(fnVars);
261261
visitChildren(ctx.statement_list());

src/test/java/cz/neumimto/nts/P.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,9 @@
44

55
@ScriptMeta.Function("print")
66
public class P {
7-
int i = 0;
87
@ScriptMeta.Handler
98
public void print(@ScriptMeta.NamedParam("val") Object o, @ScriptMeta.NamedParam("int") int i) {
109
System.out.println(o);
1110
System.out.println(i);
12-
1311
}
1412
}

src/test/java/cz/neumimto/nts/Tests.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,9 @@ public void test() throws Exception {
6666
6767
@lesser = @int <= 70000
6868
69-
# @function = fn @int @lesser
70-
# print{val=@int}
71-
# END
69+
@function = fn @int @lesser
70+
print{val="FN"}
71+
END
7272
7373
7474
RETURN Result.OK

0 commit comments

Comments
 (0)