diff --git a/.dockerignore b/.dockerignore index 8013153..eb5a316 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,5 +1 @@ -.test-gadget -.*_cache -*.pyc -__pycache__ -build +target diff --git a/.gitignore b/.gitignore index 28f2453..9483830 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ __pycache__ /.mypy_cache .test-gadget/ +test-gadget/ +test-gadget-client/ build .cache target diff --git a/compiler/.dockerignore b/compiler/.dockerignore deleted file mode 100644 index eb5a316..0000000 --- a/compiler/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -target diff --git a/compiler/src/main/scala/Main.scala b/compiler/src/main/scala/Main.scala index d1c978e..aee5512 100644 --- a/compiler/src/main/scala/Main.scala +++ b/compiler/src/main/scala/Main.scala @@ -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\"}" } diff --git a/compiler/src/main/scala/Parser.scala b/compiler/src/main/scala/Parser.scala index a01cc85..d169902 100644 --- a/compiler/src/main/scala/Parser.scala +++ b/compiler/src/main/scala/Parser.scala @@ -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) diff --git a/compiler/src/test/scala/ParserTests.scala b/compiler/src/test/scala/ParserTests.scala index eedc4d9..4bd8145 100644 --- a/compiler/src/test/scala/ParserTests.scala +++ b/compiler/src/test/scala/ParserTests.scala @@ -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) + } +} diff --git a/query_server.sh b/query_server.sh index 0152cc7..223358b 100755 --- a/query_server.sh +++ b/query_server.sh @@ -1 +1 @@ -echo '{"command": "compile", "code": "1+1 == 2"}' | netcat localhost 3000 +echo '{"command": "compile", "code": "1+1 == 2"}' | nc localhost 3000 diff --git a/test-gadget b/test-gadget new file mode 120000 index 0000000..c6403c3 --- /dev/null +++ b/test-gadget @@ -0,0 +1 @@ +test-gadget-client/target/release/test-gadget-client \ No newline at end of file diff --git a/test-gadget.py b/test-gadget.py deleted file mode 100755 index 2aca9e2..0000000 --- a/test-gadget.py +++ /dev/null @@ -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:])