Skip to content

Commit

Permalink
feat: cast operations
Browse files Browse the repository at this point in the history
  • Loading branch information
Eddie authored and Eddie committed Jun 18, 2024
1 parent 24990b2 commit 0f69779
Show file tree
Hide file tree
Showing 13 changed files with 171 additions and 40 deletions.
Binary file modified out/artifacts/jungle_jar/jungle.jar
Binary file not shown.
7 changes: 7 additions & 0 deletions programs/cast.source
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
c = 65

print c # expected output is '65'
assert equals c 65

print <unicode> c # expected output is 'A'
assert equals c 'A'
16 changes: 8 additions & 8 deletions programs/mandelbrot.source
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ xStep = 7
yStep = 15
maxIter = 200

yZero = topEdge
loop (greaterThan yZero bottomEdge) {
xZero = leftEdge
loop (lessThan xZero rightEdge) {
y0 = topEdge
loop (greaterThan y0 bottomEdge) {
x0 = leftEdge
loop (lessThan x0 rightEdge) {
y = 0
x = 0
theChar = ' '
Expand All @@ -24,13 +24,13 @@ loop (greaterThan yZero bottomEdge) {
}
i = maxIter
}
y = + (/ (* x y) 100) yZero
x = + (- xx yy) xZero
y = + (/ (* x y) 100) y0
x = + (- xx yy) x0
i = + i 1
}
print theChar
xZero = + xZero xStep
x0 = + x0 xStep
}
print '\n'
yZero = - yZero yStep
y0 = - y0 yStep
}
7 changes: 7 additions & 0 deletions programs/print-char.source
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
print(+ 'A' 0)
print(+ 'A' 1)
print(+ 'A' 2)
print(1)
print(2)
print(3)
print('\n')
3 changes: 3 additions & 0 deletions src/main/java/com/jungle/ast/NodeType.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ public enum NodeType {
LITERAL_FLOAT,
LITERAL_STRING,

CAST_CHAR,
CAST_BYTE,
CAST_SHORT,
CAST_INTEGER,
CAST_LONG,
CAST_FLOAT,
Expand Down
32 changes: 32 additions & 0 deletions src/main/java/com/jungle/common/MapBuilder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.jungle.common;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import org.jetbrains.annotations.NotNull;

public class MapBuilder<K, V> {
private final Map<K, V> map;

public MapBuilder() {
super();
this.map = new HashMap<>();
}

public MapBuilder(@NotNull Map<K, V> map) {
super();
this.map = map;
}

@NotNull
public MapBuilder<K, V> withEntry(K key, V value) {
map.put(key, value);
return this;
}

@NotNull
public Map<K, V> build() {
return Collections.unmodifiableMap(map);
}
}
14 changes: 14 additions & 0 deletions src/main/java/com/jungle/common/SetUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.jungle.common;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

public class SetUtils {
@SafeVarargs
public static final <T> Set<T> newSet(T... elements) {
Set<T> set = new HashSet<T>();
Collections.addAll(set, elements);
return set;
}
}
15 changes: 15 additions & 0 deletions src/main/java/com/jungle/compiler/operand/OperandType.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package com.jungle.compiler.operand;

import java.util.Set;

import org.jetbrains.annotations.NotNull;
import org.objectweb.asm.Opcodes;

import com.jungle.common.SetUtils;

/**
* Types that exist on the operand stack.
*
Expand Down Expand Up @@ -33,6 +37,17 @@ public enum OperandType {
DOUBLE,
FLOAT;

@NotNull
public static final Set<OperandType> INTEGER_OPERATION_TYPES = SetUtils.newSet(
OperandType.CHAR,
OperandType.BYTE,
OperandType.SHORT,
OperandType.INTEGER,
OperandType.LONG,
OperandType.FLOAT,
OperandType.DOUBLE
);

public int getConvertOpcode(@NotNull OperandType that) {
switch (this) {
case INTEGER: {
Expand Down
35 changes: 15 additions & 20 deletions src/main/java/com/jungle/compiler/visitor/CastVisitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@

import com.jungle.ast.INode;
import com.jungle.ast.NodeType;
import com.jungle.common.MapBuilder;
import com.jungle.compiler.ICompilerOptions;
import com.jungle.compiler.operand.OperandStackContext;
import com.jungle.compiler.operand.OperandType;
import com.jungle.logger.FileLogger;

import java.util.Map;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.objectweb.asm.MethodVisitor;
Expand All @@ -15,6 +18,16 @@ public class CastVisitor extends AbstractVisitor {
@NotNull
private static final FileLogger logger = new FileLogger(CastVisitor.class.getName());

private static final Map<NodeType, OperandType> NODE_TYPE_TO_OPERAND_TYPE_MAP = new MapBuilder<NodeType, OperandType>()
.withEntry(NodeType.CAST_CHAR, OperandType.CHAR)
.withEntry(NodeType.CAST_BYTE, OperandType.BYTE)
.withEntry(NodeType.CAST_SHORT, OperandType.SHORT)
.withEntry(NodeType.CAST_INTEGER, OperandType.INTEGER)
.withEntry(NodeType.CAST_LONG, OperandType.LONG)
.withEntry(NodeType.CAST_FLOAT, OperandType.FLOAT)
.withEntry(NodeType.CAST_DOUBLE, OperandType.DOUBLE)
.build();

@Nullable
private ExpressionVisitor expressionVisitor;

Expand All @@ -30,27 +43,9 @@ public CastVisitor(@NotNull ICompilerOptions options) {
super(options);
}

protected OperandType getCastType(@NotNull INode ast) {
switch (ast.getType()) {
case CAST_INTEGER: return OperandType.INTEGER;
case CAST_LONG: return OperandType.LONG;
case CAST_FLOAT: return OperandType.FLOAT;
case CAST_DOUBLE: return OperandType.DOUBLE;
default: break;
}
throw new Error("expected cast");
}

@Override
public boolean canVisit(@NotNull INode ast) {
switch (ast.getType()) {
case CAST_INTEGER:
case CAST_LONG:
case CAST_FLOAT:
case CAST_DOUBLE:
return true;
default: return false;
}
return NODE_TYPE_TO_OPERAND_TYPE_MAP.containsKey(ast.getType());
}

@Override
Expand All @@ -71,7 +66,7 @@ public void visit(

getExpressionVisitor().visit(mv, ast.getLeft(), context);
OperandType fromType = context.pop();
OperandType toType = getCastType(ast);
OperandType toType = NODE_TYPE_TO_OPERAND_TYPE_MAP.get(ast.getType());

mv.visitInsn(fromType.getConvertOpcode(toType));
context.push(toType);
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/com/jungle/compiler/visitor/IfVisitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ protected void visit(
}

getExpressionVisitor().visit(mv, conditionNode, context);
if (context.peek() != OperandType.INTEGER) {
throw new Error("if condition/expression expected to be type integer");
if (!OperandType.INTEGER_OPERATION_TYPES.contains(context.peek())) {
throw new Error("if condition/expression expected to be within the integer category");
}

INode bodyNode = ast.getRight();
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/com/jungle/compiler/visitor/MainVisitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,16 @@ private SequenceVisitor getSequenceVisitor() {
return sequenceVisitor;
}

@Nullable
private CastVisitor castVisitor;

@Nullable CastVisitor getCastVisitor() {
if (castVisitor == null) {
castVisitor = new CastVisitor(getCompilerOptions());
}
return castVisitor;
}

// endregion

public MainVisitor(@NotNull final ICompilerOptions options) {
Expand Down Expand Up @@ -196,6 +206,11 @@ public void visit(
return;
}

if (getCastVisitor().canVisit(ast)) {
getCastVisitor().visit(mv, ast, context);
return;
}

throw new Error("unexpected node " + ast);
}
}
40 changes: 32 additions & 8 deletions src/main/java/com/jungle/parser/Parser.java
Original file line number Diff line number Diff line change
@@ -1,21 +1,34 @@
package com.jungle.parser;

import com.jungle.token.IToken;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import com.jungle.ast.INode;
import com.jungle.ast.Node;
import com.jungle.ast.NodeType;
import com.jungle.common.MapBuilder;
import com.jungle.common.StringUtils;
import com.jungle.token.TokenType;

import java.util.Map;
import java.util.function.Supplier;

import static com.jungle.scanner.Scanner.*;

public class Parser extends AbstractParser {

private static Map<String, NodeType> CONVERT_NODE_TYPE_MAP = new MapBuilder<String, NodeType>()
.withEntry(KEYWORD_CONVERT_CHAR, NodeType.CAST_CHAR)
.withEntry(KEYWORD_CONVERT_BYTE, NodeType.CAST_BYTE)
.withEntry(KEYWORD_CONVERT_SHORT, NodeType.CAST_SHORT)
.withEntry(KEYWORD_CONVERT_INTEGER, NodeType.CAST_INTEGER)
.withEntry(KEYWORD_CONVERT_LONG, NodeType.CAST_LONG)
.withEntry(KEYWORD_CONVERT_FLOAT, NodeType.CAST_FLOAT)
.withEntry(KEYWORD_CONVERT_DOUBLE, NodeType.CAST_DOUBLE)
.build();

public Parser(@NotNull Iterable<IToken> tokenIterable) {
super(tokenIterable);
}
Expand Down Expand Up @@ -136,11 +149,11 @@ public INode parseNumberExpression() {

@NotNull
protected INode parseStringLiteral() {
String textValue = expect(TokenType.TEXT);
if (textValue == null) {
throw newError("text token missing value");
INode textNode = parseTextLiteral();
if (textNode.getType() != NodeType.LITERAL_STRING) {
throw newError("expected string literal");
}
return new Node(NodeType.LITERAL_STRING).withRawValue(textValue);
return textNode;
}

@Nullable
Expand Down Expand Up @@ -262,6 +275,7 @@ protected INode parseExpression() {
/*
* expression := identifier
* | "(" expression ")"
* | "<" keyword ">" expression
* | boolean_expression
* | numeric_expression
* | text_expression
Expand All @@ -274,6 +288,16 @@ protected INode parseExpression() {
if (accepts(TokenType.BRACKET_ROUND_OPEN)) {
return parseParenthesis(this::parseExpression);
}
if (accepts(TokenType.BRACKET_ANGLE_OPEN)) {
expect(TokenType.BRACKET_ANGLE_OPEN);
String convertKeyword = expect(TokenType.KEYWORD);
NodeType convertNodeType = CONVERT_NODE_TYPE_MAP.get(convertKeyword);
if (convertNodeType == null) {
throw newError("convert keyword not recognized - " + convertKeyword);
}
expect(TokenType.BRACKET_ANGLE_CLOSE);
return new Node(convertNodeType).withLeft(parseExpression());
}
boolean isBooleanExpression = acceptKeywords(
KEYWORD_AND,
KEYWORD_OR,
Expand All @@ -287,10 +311,6 @@ protected INode parseExpression() {
if (isBooleanExpression) {
return parseBooleanExpression();
}
boolean isTextExpression = accepts(TokenType.TEXT);
if (isTextExpression) {
return parseTextExpression();
}
boolean isNumberExpression = accepts(
TokenType.NUMBER,
TokenType.PLUS,
Expand All @@ -302,6 +322,10 @@ protected INode parseExpression() {
if (isNumberExpression) {
return parseNumberExpression();
}
boolean isTextExpression = accepts(TokenType.TEXT);
if (isTextExpression) {
return parseTextExpression();
}
throw newError("not an expression");
}

Expand Down
23 changes: 21 additions & 2 deletions src/main/java/com/jungle/scanner/Scanner.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ public class Scanner extends AbstractScanner {
public static final String KEYWORD_LESS_THAN = "lessThan";
public static final String KEYWORD_TRUE = "true";
public static final String KEYWORD_FALSE = "false";
public static final String KEYWORD_CONVERT_CHAR = "unicode";
public static final String KEYWORD_CONVERT_BYTE = "i8";
public static final String KEYWORD_CONVERT_SHORT = "i16";
public static final String KEYWORD_CONVERT_INTEGER = "i32";
public static final String KEYWORD_CONVERT_LONG = "i64";
public static final String KEYWORD_CONVERT_FLOAT = "f32";
public static final String KEYWORD_CONVERT_DOUBLE = "f64";

// TODO: keyword "in" - if x in 1...3 { }

Expand All @@ -44,7 +51,14 @@ public class Scanner extends AbstractScanner {
KEYWORD_GREATER_THAN,
KEYWORD_LESS_THAN,
KEYWORD_TRUE,
KEYWORD_FALSE
KEYWORD_FALSE,
KEYWORD_CONVERT_CHAR,
KEYWORD_CONVERT_BYTE,
KEYWORD_CONVERT_SHORT,
KEYWORD_CONVERT_INTEGER,
KEYWORD_CONVERT_LONG,
KEYWORD_CONVERT_FLOAT,
KEYWORD_CONVERT_DOUBLE
);

@NotNull
Expand Down Expand Up @@ -78,6 +92,11 @@ protected String consumeAlphabetic() {
return consumeUntil((c) -> !isAlphabetic(c));
}

@NotNull
protected String consumeAlphaNumeric() {
return consumeUntil((c) -> !(isAlphabetic(c) || isDigit(c)));
}

@NotNull
protected String consumeUntilAndSkip(char terminal) {
String s = consumeUntil((c) -> c == terminal);
Expand Down Expand Up @@ -186,7 +205,7 @@ public IToken scanToken() {
String s = c + consumeNumeric();
token = new Token(TokenType.NUMBER).withValue(s);
} else if (isAlphabetic(c)) {
String s = c + consumeAlphabetic();
String s = c + consumeAlphaNumeric();
if (isKeyword(s)) {
token = new Token(TokenType.KEYWORD).withValue(s);
} else {
Expand Down

0 comments on commit 0f69779

Please sign in to comment.