diff --git a/README.md b/README.md index 8db85bd..5c40f08 100644 --- a/README.md +++ b/README.md @@ -174,22 +174,24 @@ Ensure the class for `multitask`: - implements the `java.lang.Runnable` interface - compiled with the `JUNGLE_CLASSPATH` environment variable with the classpath -```shell -# Compile the Java class used by our program -javac programs/classes/com/example/MultitaskRunnable.java +--- + +This program demonstrates: +- Multi-threading is possible with this language +- Threads print to the same standard output +- Runnable classes are compiled from a single program -# Declare or provide the jungle classpath before compiling or running, -# so that our Runnable class can be validated during compile and found during runtime. -# The jungle classpath is used to declare dependencies to compile the program and run the program. -export JUNGLE_CLASSPATH='.:./programs/classes' +The program describes 2 threads: +1. The first thread pauses program execution for a short duration and then prints out `thread!` +2. The second thread immediately prints out `Inline...` -# Compile and run the program -cat programs/multitask.source | jungle run +```shell +cat programs/multitask-inline.source | jungle run ``` Expected output: ``` -Hello, world! +Inline... thread! ``` ## Environment Variables diff --git a/out/artifacts/jungle_jar/jungle.jar b/out/artifacts/jungle_jar/jungle.jar index 557bcde..dbdbb0c 100644 Binary files a/out/artifacts/jungle_jar/jungle.jar and b/out/artifacts/jungle_jar/jungle.jar differ diff --git a/programs/multitask-inline.source b/programs/multitask-inline.source index d7653b3..3f7102c 100644 --- a/programs/multitask-inline.source +++ b/programs/multitask-inline.source @@ -1,9 +1,5 @@ multitask "com.example.RunnableTaskOne" : { - # Wait a little - x = 999999 - loop (greaterThan x 0) { - x = - x 1 - } + sleep 100 print "thread!\n" } diff --git a/programs/multitask.source b/programs/multitask.source index 4d32c82..44f9c95 100644 --- a/programs/multitask.source +++ b/programs/multitask.source @@ -1,2 +1,13 @@ +# Compile the Java class used by our program +# `javac programs/classes/com/example/MultitaskRunnable.java` + +# Declare or provide the jungle classpath before compiling or running, +# so that our Runnable class can be validated during compile and found during runtime. +# The jungle classpath is used to declare dependencies to compile the program and run the program. +# `export JUNGLE_CLASSPATH='.:./programs/classes'` + +# Compile and run the program +# `cat programs/multitask.source | jungle run` + multitask "com.example.MultitaskRunnable" print "Hello" diff --git a/programs/sleep.source b/programs/sleep.source new file mode 100644 index 0000000..fb71a90 --- /dev/null +++ b/programs/sleep.source @@ -0,0 +1 @@ +sleep 1000 diff --git a/src/main/java/com/jungle/ast/NodeType.java b/src/main/java/com/jungle/ast/NodeType.java index e9b5de1..64ecb64 100644 --- a/src/main/java/com/jungle/ast/NodeType.java +++ b/src/main/java/com/jungle/ast/NodeType.java @@ -60,6 +60,7 @@ public enum NodeType { ASSERT, PRINT, MULTITASK, + SLEEP, TRUE, FALSE; diff --git a/src/main/java/com/jungle/compiler/visitor/MainVisitor.java b/src/main/java/com/jungle/compiler/visitor/MainVisitor.java index 4290ca9..1f794e6 100644 --- a/src/main/java/com/jungle/compiler/visitor/MainVisitor.java +++ b/src/main/java/com/jungle/compiler/visitor/MainVisitor.java @@ -102,6 +102,17 @@ private MultitaskVisitor getMultitaskVisitor() { return multitaskVisitor; } + @Nullable + private SleepVisitor sleepVisitor; + + @NotNull + private SleepVisitor getSleepVisitor() { + if (sleepVisitor == null) { + sleepVisitor = new SleepVisitor(getCompilerOptions()); + } + return sleepVisitor; + } + @Nullable private SequenceVisitor sequenceVisitor; @@ -180,6 +191,11 @@ public void visit( return; } + if (getSleepVisitor().canVisit(ast)) { + getSleepVisitor().visit(mv, ast, context); + return; + } + throw new Error("unexpected node " + ast); } } diff --git a/src/main/java/com/jungle/compiler/visitor/SleepVisitor.java b/src/main/java/com/jungle/compiler/visitor/SleepVisitor.java new file mode 100644 index 0000000..ee9dd65 --- /dev/null +++ b/src/main/java/com/jungle/compiler/visitor/SleepVisitor.java @@ -0,0 +1,66 @@ +package com.jungle.compiler.visitor; + +import static org.objectweb.asm.Opcodes.I2L; +import static org.objectweb.asm.Opcodes.INVOKESTATIC; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.MethodVisitor; + +import com.jungle.ast.INode; +import com.jungle.ast.NodeType; +import com.jungle.compiler.ICompilerOptions; +import com.jungle.compiler.operand.OperandStackContext; +import com.jungle.compiler.operand.OperandStackType; + +public class SleepVisitor extends AbstractVisitor { + + @Nullable + private ExpressionVisitor expressionVisitor; + + @NotNull + private ExpressionVisitor getExpressionVisitor() { + if (expressionVisitor == null) { + expressionVisitor = new ExpressionVisitor(getCompilerOptions()); + } + return expressionVisitor; + } + + public SleepVisitor(@NotNull ICompilerOptions options) { + super(options); + } + + @Override + public boolean canVisit(@NotNull INode ast) { + return ast.getType().equals(NodeType.SLEEP); + } + + @Override + public void visit(@NotNull MethodVisitor mv, @NotNull INode ast, @NotNull OperandStackContext context) { + // generate code for the integer representing the number of milliseconds to wait + + if (ast.getLeft() == null) { + throw new Error("sleep missing expression"); + } + + // push expression onto operand stack + getExpressionVisitor().visit(mv, ast.getLeft(), context); + + // verify that the expression was an integer type + if (context.peek() != OperandStackType.INTEGER) { + throw new Error("sleep expression expected to be type integer"); + } + + // For now, convert the integer to long + mv.visitInsn(I2L); + + // invoke static method: Thread.sleep() + mv.visitMethodInsn( + INVOKESTATIC, + "java/lang/Thread", + "sleep", + "(J)V", + false + ); + } +} diff --git a/src/main/java/com/jungle/parser/Parser.java b/src/main/java/com/jungle/parser/Parser.java index e7a13a0..c4ac354 100644 --- a/src/main/java/com/jungle/parser/Parser.java +++ b/src/main/java/com/jungle/parser/Parser.java @@ -81,11 +81,15 @@ protected INode parseNumber() { public INode parseNumberExpression() { /* * number_expression := number + * | identifier * | "(" number_expression ")" * | ( "+" | "-" | "*" | "/" | "%" ) expression expression * ; */ consumeWhitespace(); + if (accepts(TokenType.SYMBOL)) { + return parseIdentifier(); + } if (accepts(TokenType.NUMBER)) { return parseNumber(); } @@ -362,6 +366,16 @@ protected INode parseStatementMultitask() { return node; } + @NotNull + protected INode parseStatementSleep() { + /* + * statement_sleep := "sleep" expression_number + */ + expectKeyword(KEYWORD_SLEEP); + consumeWhitespace(); + return new Node(NodeType.SLEEP).withLeft(parseNumberExpression()); + } + @Nullable protected INode parseStatementIf() { /* @@ -418,6 +432,7 @@ protected INode parseStatementKeyword() { * | statement_print * | statement_if * | statement_multitask + * | statement_wait * ; */ consumeWhitespace(); @@ -436,6 +451,9 @@ protected INode parseStatementKeyword() { if (acceptKeyword(KEYWORD_MULTITASK)) { return parseStatementMultitask(); } + if (acceptKeyword(KEYWORD_SLEEP)) { + return parseStatementSleep(); + } throw newError("not a keyword statement"); } diff --git a/src/main/java/com/jungle/scanner/Scanner.java b/src/main/java/com/jungle/scanner/Scanner.java index f61a241..4cdc9c7 100644 --- a/src/main/java/com/jungle/scanner/Scanner.java +++ b/src/main/java/com/jungle/scanner/Scanner.java @@ -17,6 +17,7 @@ public class Scanner extends AbstractScanner { public static final String KEYWORD_LOOP = "loop"; public static final String KEYWORD_PRINT = "print"; public static final String KEYWORD_MULTITASK = "multitask"; + public static final String KEYWORD_SLEEP = "sleep"; public static final String KEYWORD_AND = "and"; public static final String KEYWORD_OR = "or"; public static final String KEYWORD_NOT = "not"; @@ -35,6 +36,7 @@ public class Scanner extends AbstractScanner { KEYWORD_LOOP, KEYWORD_PRINT, KEYWORD_MULTITASK, + KEYWORD_SLEEP, KEYWORD_AND, KEYWORD_OR, KEYWORD_NOT,