Add simple block support + other small things including base64 encoding

This commit is contained in:
Aaro Koivukangas
2026-03-03 13:21:43 +02:00
parent 9d2fc31eae
commit 08b6e380b0
9 changed files with 120 additions and 42 deletions
+1 -5
View File
@@ -1,5 +1 @@
.test-gadget
.*_cache
*.pyc
__pycache__
build
target
+2
View File
@@ -2,6 +2,8 @@
__pycache__
/.mypy_cache
.test-gadget/
test-gadget/
test-gadget-client/
build
.cache
target
-1
View File
@@ -1 +0,0 @@
target
+7 -2
View File
@@ -7,7 +7,9 @@ import java.net.{ServerSocket, Socket};
import java.util.stream.Stream
import compiler.Tokenizer._
import compiler.Parser._
import compiler.Parser.{Unit => _, _} // Scala also has Unit
import java.util.Base64
import java.nio.charset.StandardCharsets
@main def main(): Unit =
@@ -17,6 +19,9 @@ import compiler.Parser._
def msg = "I was compiled by Scala 3. :)"
def base64Encode(source: String): String =
return Base64.getEncoder.encodeToString(source.getBytes(StandardCharsets.UTF_8))
def run_server(): Unit =
println("Starting server...")
val serverSocket: ServerSocket = new ServerSocket(3000);
@@ -40,7 +45,7 @@ def run_server(): Unit =
val response = command match {
case "ping" => "{}"
case "compile" => s"{\"program\": \"${compile(json("code").str)}\"}"
case "compile" => s"{\"program\": \"${base64Encode(compile(json("code").str))}\"}"
case _ => s"{\"error\": \"Incorrect json request body\"}"
}
+33 -2
View File
@@ -7,8 +7,12 @@ import compiler.Tokenizer.{Token, TokenType}
sealed abstract class Expression:
override def toString() = s"Expression(TODO)"
// Return value of statement without return value
case class Unit() {
override def toString: String = "Unit()"
}
case class Literal(value: Int | Boolean) extends Expression {
case class Literal(value: Int | Unit) extends Expression {
override def toString: String = s"Literal($value)"
}
case class Identifier(name: String) extends Expression {
@@ -32,6 +36,9 @@ case class Declaration(name: Identifier, body: Expression) extends Expression {
case class Function(name: String, arguments: List[Expression]) extends Expression {
override def toString: String = s"Function($name(${arguments.mkString(", ")}))"
}
case class Block(expressions: List[Expression]) extends Expression {
override def toString: String = s"Block({\n${expressions.mkString(";\n")}\n})"
}
case class Other() extends Expression {
override def toString: String = s"Other()"
}
@@ -121,6 +128,7 @@ class Parser(tokens: List[Token]):
case TokenType.Identifier => parseIdentifier()
case TokenType.Operator if token.value == "-" => parseUnaryOperator()
case TokenType.Punctuation if token.value == "(" => parseParenthisized()
case TokenType.Punctuation if token.value == "{" => parseBlock()
case _ => throw new Exception(s"Incorrect token type: Expected (, int literal or identifier, got ${token.tokenType} token")
}
@@ -182,6 +190,28 @@ class Parser(tokens: List[Token]):
return Declaration(name, body)
def parseBlock(): Expression =
consume(Some("{")).get
var expressions: List[Expression] = List()
var hasSemicolon: Boolean = false
println(s"Peek value: ${peek().value}")
while
peek().value != "}"
do
hasSemicolon = false
expressions = expressions ::: List(parseExpression())
println(s"Expressions $expressions")
if peek().value != "}" then
consume(Some(";")).get
hasSemicolon = true
if hasSemicolon then
expressions = expressions ::: List(Literal(Unit()))
consume(Some("}")).get
return Block(expressions)
def parseExpression(initialDepth: Int = 0): Expression =
var depth = initialDepth
var left = parseFactor()
@@ -212,4 +242,5 @@ class Parser(tokens: List[Token]):
object Parser:
def parse(tokens: List[Token]): Try[Expression] =
val parser = Parser(tokens)
return Success(Other())
val result = parser.parseExpression()
return Success(result)
+75
View File
@@ -229,6 +229,21 @@ class ParserIfThenElseTests extends BaseParserTests {
assertEquals(result, expected)
}
test("should parse nested if else") {
val p = testParser("if a then b else if c then d else e")
val result = p.parseExpression()
val expected = IfThenElse(
Identifier("a"),
Identifier("b"),
Some(IfThenElse(
Identifier("c"),
Identifier("d"),
Some(Identifier("e"))
))
)
assertEquals(result, expected)
}
test("should not parse if then with extra identifiers") {
val token = Tokenizer.tokenize("if true while").get(2)
val p = testParser("if true while then 1 + 1")
@@ -623,3 +638,63 @@ class ParserDeclarationTests extends BaseParserTests {
}
}
}
class ParserBlockTests extends BaseParserTests {
test("should parse empty block") {
val p = testParser("{}")
val result = p.parseExpression()
val expected = Block(List())
assertEquals(result, expected)
}
test("should parse simple block with single expression (no semicolon)") {
val p = testParser("{ x }")
val result = p.parseExpression()
val expected = Block(List(Identifier("x")))
assertEquals(result, expected)
}
test("should parse simple block with single expression (semicolon)") {
val p = testParser("{ x; }")
val result = p.parseExpression()
val expected = Block(List(Identifier("x"), Literal(Unit())))
assertEquals(result, expected)
}
test("should parse block with single declaration (no semicolon)") {
val p = testParser("{ var x = 1 }")
val result = p.parseExpression()
val expected = Block(List(Declaration(Identifier("x"), Literal(1))))
assertEquals(result, expected)
}
test("should parse block with sigle declaration (semicolon)") {
val p = testParser("{ var x = 1; }")
val result = p.parseExpression()
val expected = Block(List(Declaration(Identifier("x"), Literal(1)), Literal(Unit())))
assertEquals(result, expected)
}
test("should parse block with multiple declarations") {
val p = testParser("{ var x = 1; var y = 2; }")
val result = p.parseExpression()
val expected = Block(List(
Declaration(Identifier("x"), Literal(1)),
Declaration(Identifier("y"), Literal(2)),
Literal(Unit()
)))
assertEquals(result, expected)
}
test("should parse with multiple function calls and return value") {
val p = testParser("{ f(a); x = y; f(x) }")
val result = p.parseExpression()
val expected = Block(List(
Function("f", List(Identifier("a"))),
BinaryOperator(Identifier("x"), "=", Identifier("y")),
Function("f", List(Identifier("x")))
))
assertEquals(result, expected)
}
}
+1 -1
View File
@@ -1 +1 @@
echo '{"command": "compile", "code": "1+1 == 2"}' | netcat localhost 3000
echo '{"command": "compile", "code": "1+1 == 2"}' | nc localhost 3000
+1
View File
@@ -0,0 +1 @@
test-gadget-client/target/release/test-gadget-client
-31
View File
@@ -1,31 +0,0 @@
#!/usr/bin/env python3
# Runs the correct test-gadget client program in .test-gadget/test-gadget-client-$PLATFORM
import os
import platform
import sys
from pathlib import Path
def get_platform_binary() -> str:
system = platform.system().lower()
if system == "darwin":
return "test-gadget-client-macos"
elif system == "windows":
return "test-gadget-client-windows.exe"
elif system == "linux":
return "test-gadget-client-linux"
else:
print(f"Unsupported platform: {system}", file=sys.stderr)
sys.exit(1)
script_dir = Path(__file__).parent
dist_dir = script_dir / ".test-gadget"
binary = dist_dir / get_platform_binary()
if not binary.exists():
print(f"Program not found: {binary}", file=sys.stderr)
sys.exit(1)
os.execv(str(binary), [str(binary)] + sys.argv[1:])