mirror of
https://github.com/iritmaximus/compilers.git
synced 2026-10-12 19:13:35 +03:00
finished tokenizer and tests
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
version = 3.10.5
|
||||||
|
runner.dialect = scala3
|
||||||
@@ -4,7 +4,7 @@ import java.io.{BufferedReader, InputStreamReader, PrintWriter}
|
|||||||
import java.net.{ServerSocket, Socket};
|
import java.net.{ServerSocket, Socket};
|
||||||
import java.util.stream.Stream
|
import java.util.stream.Stream
|
||||||
|
|
||||||
import compiler.Tokenizer.tokenizer
|
import compiler.Tokenizer._
|
||||||
|
|
||||||
|
|
||||||
@main def hello(): Unit =
|
@main def hello(): Unit =
|
||||||
@@ -45,4 +45,4 @@ def run_server(): Unit =
|
|||||||
out.println(response);
|
out.println(response);
|
||||||
|
|
||||||
def compile(source: String): String =
|
def compile(source: String): String =
|
||||||
return tokenizer(source).toString()
|
return Tokenizer.tokenize(source).toString()
|
||||||
|
|||||||
@@ -1,44 +1,110 @@
|
|||||||
package compiler.Tokenizer;
|
package compiler.Tokenizer
|
||||||
|
|
||||||
import scala.util.matching.Regex.MatchIterator;
|
import scala.util.matching.Regex.MatchIterator
|
||||||
|
import scala.util.{Try, Failure, Success}
|
||||||
|
|
||||||
class TokenLocation(column: Int, line: Int):
|
|
||||||
override def toString: String = s"($line, $column)"
|
val TabLength = 4
|
||||||
|
|
||||||
|
|
||||||
|
class Position(var line: Int, var column: Int):
|
||||||
|
override def toString: String = s"($line,$column)"
|
||||||
|
override def equals(other: Any): Boolean = other match {
|
||||||
|
case that: Position => (that.line == this.line) && (that.column == this.column)
|
||||||
|
case _ => false
|
||||||
|
}
|
||||||
|
|
||||||
|
class TokenLocation(val start: Position, val end: Position):
|
||||||
|
override def toString: String = s"TokenLoc(S:$start, E:$end)"
|
||||||
|
override def equals(other: Any): Boolean = other match {
|
||||||
|
case that: TokenLocation => (that.start == this.start) && (that.end == this.end)
|
||||||
|
case that: TokenLocationDebug => true
|
||||||
|
case _ => false
|
||||||
|
}
|
||||||
|
|
||||||
class TokenLocationDebug():
|
class TokenLocationDebug():
|
||||||
override def toString: String =
|
override def toString: String = s"(debug-loc)"
|
||||||
s"(debug-loc)"
|
override def equals(other: Any): Boolean = other match {
|
||||||
|
case that: TokenLocation => true
|
||||||
|
case that: TokenLocationDebug => true
|
||||||
|
case _ => false
|
||||||
|
}
|
||||||
|
|
||||||
class Token(
|
sealed trait Token(val value: String, val location: TokenLocation | TokenLocationDebug = TokenLocationDebug()):
|
||||||
value: String,
|
override def toString: String = s"${this.getClass.getName.stripPrefix("compiler.Tokenizer.")}($value, $location)"
|
||||||
location: TokenLocation | TokenLocationDebug = TokenLocationDebug()
|
override def equals(other: Any): Boolean = other match {
|
||||||
):
|
case that: Token => {
|
||||||
override def toString: String =
|
that.location match {
|
||||||
s"Token($value, $location)"
|
case x: TokenLocation => (that.value == this.value) && (that.location == this.location)
|
||||||
|
case x: TokenLocationDebug => true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case _ => false
|
||||||
|
}
|
||||||
|
def isSkippable() =
|
||||||
|
this match {
|
||||||
|
case that: WhitespaceT => true
|
||||||
|
case that: CommentT => true
|
||||||
|
case _ => false
|
||||||
|
}
|
||||||
|
|
||||||
object Token:
|
object Token:
|
||||||
def tokenFromString(str: String): Token =
|
def tokenFromString(str: String, currentPos: Position): Option[Token] =
|
||||||
return Token("test")
|
|
||||||
|
|
||||||
def isIntLiteral(str: String): Boolean =
|
|
||||||
return true
|
|
||||||
|
|
||||||
class CurrentPosition(line: Int, column: Int);
|
|
||||||
|
|
||||||
def tokenizer(source: String): List[Token] =
|
if Token.isWhitespace(str) then
|
||||||
val tokenRegex =
|
val startWhitespace = Position(currentPos.line, currentPos.column)
|
||||||
raw"(((\#|\/\/).*)|([+\-\/*%(),;:{}])|([<>=!]=?)|([0-9]+)|([a-zA-Z_][a-zA-Z_0-9]*))".r
|
str.foreach(_ match {
|
||||||
val matches = tokenRegex.findAllIn(source)
|
case ' ' => currentPos.column += 1
|
||||||
|
case '\n' => currentPos.line += 1; currentPos.column = 1
|
||||||
|
case '\t' => currentPos.column += TabLength
|
||||||
|
})
|
||||||
|
|
||||||
var currentPos = CurrentPosition(0, 0)
|
val endWhitespace = Position(currentPos.line, currentPos.column)
|
||||||
val tokens = for (token <- matches) yield tokenFromMatch(token, currentPos)
|
val tokenLoc = TokenLocation(startWhitespace, endWhitespace)
|
||||||
|
return Some(WhitespaceT(str, tokenLoc))
|
||||||
|
|
||||||
return tokens.toList
|
// After whitespace is calculated, move currentPos to the end of the current token
|
||||||
|
// i.e. current column + token length
|
||||||
|
val start = Position(currentPos.line, currentPos.column)
|
||||||
|
currentPos.column += str.length()
|
||||||
|
val end = Position(currentPos.line, currentPos.column)
|
||||||
|
val tokenLoc = TokenLocation(start, end)
|
||||||
|
|
||||||
def tokenFromMatch(tokenStr: String, curPos: CurrentPosition): Token =
|
if Token.isComment(str) then return Some(CommentT(str, tokenLoc))
|
||||||
val location = TokenLocationDebug()
|
if Token.isIntLiteral(str) then return Some(IntLiteralT(str, tokenLoc))
|
||||||
val value = tokenStr
|
if Token.isOperator(str) then return Some(OperatorT(str, tokenLoc))
|
||||||
|
if Token.isIdentifier(str) then return Some(IdentifierT(str, tokenLoc))
|
||||||
|
if Token.isPunctuation(str) then return Some(PunctuationT(str, tokenLoc))
|
||||||
|
else return None
|
||||||
|
|
||||||
val token = Token(value, location)
|
|
||||||
println("Token: " + token)
|
def isWhitespace(str: String): Boolean = return raw"(\s+)".r.matches(str)
|
||||||
return token
|
def isComment(str: String): Boolean = return str.startsWith("#") || str.startsWith("//")
|
||||||
|
def isIntLiteral(str: String): Boolean = return str.forall(Character.isDigit)
|
||||||
|
def isOperator(str: String): Boolean = return raw"([+\-\/*%])|([<>=!]=?)".r.matches(str)
|
||||||
|
def isPunctuation(str: String): Boolean = raw"[(),;:{}]".r.matches(str)
|
||||||
|
def isIdentifier(str: String): Boolean = raw"[a-zA-Z_][a-zA-Z_0-9]*".r.matches(str)
|
||||||
|
|
||||||
|
class WhitespaceT(value: String, location: TokenLocation | TokenLocationDebug) extends Token(value, location)
|
||||||
|
class CommentT(value: String, location: TokenLocation | TokenLocationDebug) extends Token(value, location)
|
||||||
|
class IntLiteralT(value: String, location: TokenLocation | TokenLocationDebug) extends Token(value, location)
|
||||||
|
class OperatorT(value: String, location: TokenLocation | TokenLocationDebug) extends Token(value, location)
|
||||||
|
class PunctuationT(value: String, location: TokenLocation | TokenLocationDebug) extends Token(value, location)
|
||||||
|
class IdentifierT(value: String, location: TokenLocation | TokenLocationDebug) extends Token(value, location)
|
||||||
|
class OtherT(value: String, location: TokenLocation | TokenLocationDebug) extends Token(value, location)
|
||||||
|
|
||||||
|
|
||||||
|
object Tokenizer:
|
||||||
|
def tokenize(source: String): Try[List[Token]] =
|
||||||
|
val tokenRegex =
|
||||||
|
raw"(\s+)|(((?:\#|\/\/).*$$)|([+\-\/*%])|([(),;:{}])|([<>=!]=?)|([0-9]+)|([a-zA-Z_][a-zA-Z_0-9]*))".r
|
||||||
|
val matches = tokenRegex.findAllIn(source)
|
||||||
|
|
||||||
|
var currentPos = Position(1, 1)
|
||||||
|
val tokens = matches.map(token => Token.tokenFromString(token, currentPos)).toList
|
||||||
|
|
||||||
|
if tokens.exists(_.isEmpty)
|
||||||
|
then Failure(new Exception("Incorrect tokens found"))
|
||||||
|
else
|
||||||
|
Success(tokens.flatten.filter(!_.isSkippable()))
|
||||||
|
|||||||
@@ -2,34 +2,194 @@
|
|||||||
// https://scalameta.org/munit/docs/getting-started.html
|
// https://scalameta.org/munit/docs/getting-started.html
|
||||||
|
|
||||||
import compiler.Tokenizer.*
|
import compiler.Tokenizer.*
|
||||||
|
import scala.util.{Try, Failure, Success}
|
||||||
|
|
||||||
|
|
||||||
|
class TokenizerTestWithoutLocation extends munit.FunSuite {
|
||||||
|
val L = TokenLocationDebug()
|
||||||
|
|
||||||
|
def test_tokenizer(code: String, expected: List[Token]): Unit =
|
||||||
|
val result = Tokenizer.tokenize(code)
|
||||||
|
result match {
|
||||||
|
case Success(result) => assertEquals(result, expected)
|
||||||
|
case Failure(result) => fail("Tokenizing failed: " + result)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class TokenizerTest extends munit.FunSuite {
|
|
||||||
test("test test succeeds") {
|
test("test test succeeds") {
|
||||||
val obtained = 42
|
val obtained = 42
|
||||||
val expected = 42
|
val expected = 42
|
||||||
assertEquals(obtained, expected)
|
assertEquals(obtained, expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
test("should work with int literals") {
|
test("should work with identifiers") {
|
||||||
val code = "1453"
|
val code = "while true do print_int BigInt"
|
||||||
val expected = List(Token(code))
|
val expected = List(
|
||||||
val result = tokenizer(code)
|
IdentifierT("while", L),
|
||||||
assertEquals(result.toString(), expected.toString())
|
IdentifierT("true", L),
|
||||||
|
IdentifierT("do", L),
|
||||||
|
IdentifierT("print_int", L),
|
||||||
|
IdentifierT("BigInt", L),
|
||||||
|
)
|
||||||
|
test_tokenizer(code, expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
test("should work with simple operatorn and int literals") {
|
test("should work with simple operators and int literals") {
|
||||||
val code = "41 + 3 == 44"
|
val code = "41 + 3 == 44"
|
||||||
val expected =
|
val expected =
|
||||||
List(Token("41"), Token("+"), Token("3"), Token("=="), Token("44"))
|
List(
|
||||||
val result = tokenizer(code)
|
IntLiteralT("41", L),
|
||||||
assertEquals(result.toString(), expected.toString())
|
OperatorT("+", L),
|
||||||
|
IntLiteralT("3", L),
|
||||||
|
OperatorT("==", L),
|
||||||
|
IntLiteralT("44", L)
|
||||||
|
)
|
||||||
|
test_tokenizer(code, expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
test("should work with operators and int literals") {
|
test("should work with operators and int literals") {
|
||||||
val code = "1531 + 1241 == 2772"
|
val code = "1531 + 1241 == 2772"
|
||||||
val expected =
|
val expected =
|
||||||
List(Token("1531"), Token("+"), Token("1241"), Token("=="), Token("2772"))
|
List(IntLiteralT("1531", L), OperatorT("+", L), IntLiteralT("1241", L), OperatorT("==", L), IntLiteralT("2772", L))
|
||||||
val result = tokenizer(code)
|
test_tokenizer(code, expected)
|
||||||
assertEquals(result.toString(), expected.toString())
|
}
|
||||||
|
|
||||||
|
test("should work with comments") {
|
||||||
|
val code1 = "4/2==2 #math"
|
||||||
|
val code2 = "4/2==2 // math"
|
||||||
|
val code3 = "# this is a whole line comment"
|
||||||
|
val expected = List(
|
||||||
|
IntLiteralT("4", L),
|
||||||
|
OperatorT("/", L),
|
||||||
|
IntLiteralT("2", L),
|
||||||
|
OperatorT("==", L),
|
||||||
|
IntLiteralT("2", L),
|
||||||
|
)
|
||||||
|
test_tokenizer(code1, expected)
|
||||||
|
test_tokenizer(code2, expected)
|
||||||
|
test_tokenizer(code3, Nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should work with whole-line comments") {
|
||||||
|
val code = "# this is a whole line comment"
|
||||||
|
val expected = Nil
|
||||||
|
test_tokenizer(code, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
test("should work with function parenthesis") {
|
||||||
|
val code = "print_int(n);"
|
||||||
|
val expected = List(
|
||||||
|
IdentifierT("print_int", L),
|
||||||
|
PunctuationT("(", L),
|
||||||
|
IdentifierT("n", L),
|
||||||
|
PunctuationT(")", L),
|
||||||
|
PunctuationT(";", L),
|
||||||
|
)
|
||||||
|
test_tokenizer(code, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should work with functions with types") {
|
||||||
|
val code = "test(i: Int,j: Float);"
|
||||||
|
val expected = List(
|
||||||
|
IdentifierT("test", L),
|
||||||
|
PunctuationT("(", L),
|
||||||
|
IdentifierT("i", L),
|
||||||
|
PunctuationT(":", L),
|
||||||
|
IdentifierT("Int", L),
|
||||||
|
PunctuationT(",", L),
|
||||||
|
IdentifierT("j", L),
|
||||||
|
PunctuationT(":", L),
|
||||||
|
IdentifierT("Float", L),
|
||||||
|
PunctuationT(")", L),
|
||||||
|
PunctuationT(";", L),
|
||||||
|
)
|
||||||
|
test_tokenizer(code, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should work with more complicated control flow") {
|
||||||
|
val code = "if (i>0) { it(); }"
|
||||||
|
val expected = List(
|
||||||
|
IdentifierT("if", L),
|
||||||
|
PunctuationT("(", L),
|
||||||
|
IdentifierT("i", L),
|
||||||
|
OperatorT(">", L),
|
||||||
|
IntLiteralT("0", L),
|
||||||
|
PunctuationT(")", L),
|
||||||
|
PunctuationT("{", L),
|
||||||
|
IdentifierT("it", L),
|
||||||
|
PunctuationT("(", L),
|
||||||
|
PunctuationT(")", L),
|
||||||
|
PunctuationT(";", L),
|
||||||
|
PunctuationT("}", L),
|
||||||
|
)
|
||||||
|
test_tokenizer(code, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TokenizerTestWithLocation extends munit.FunSuite {
|
||||||
|
def test_tokenizer(code: String, expected: List[Token]): Unit =
|
||||||
|
val result = Tokenizer.tokenize(code)
|
||||||
|
result match {
|
||||||
|
case Success(result) => assertEquals(result, expected)
|
||||||
|
case Failure(result) => fail("Tokenizing failed: " + result)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should fail with incorrect positions") {
|
||||||
|
val code = "1453"
|
||||||
|
val location = TokenLocation(Position(2, 1), Position(4,10))
|
||||||
|
val expected = List(IntLiteralT(code, location))
|
||||||
|
val result = Tokenizer.tokenize(code)
|
||||||
|
result match {
|
||||||
|
case Success(result) => assertNotEquals(result, expected)
|
||||||
|
case Failure(result) => fail("Tokenizing failed: " + result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should work with int literals") {
|
||||||
|
val code = "1453"
|
||||||
|
val location = TokenLocation(Position(1, 1), Position(1,5))
|
||||||
|
val expected = List(IntLiteralT(code, location))
|
||||||
|
test_tokenizer(code, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should work with a newline") {
|
||||||
|
val code = "if 3\nwhile"
|
||||||
|
val expected = List(
|
||||||
|
IdentifierT("if", TokenLocation(Position(1,1), Position(1,3))),
|
||||||
|
IntLiteralT("3", TokenLocation(Position(1,5), Position(1,6))),
|
||||||
|
IdentifierT("while", TokenLocation(Position(2,1), Position(2,6)))
|
||||||
|
)
|
||||||
|
test_tokenizer(code, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should work with multiple newline") {
|
||||||
|
val code = "if 3\nwhile\n\nprint_int"
|
||||||
|
val expected = List(
|
||||||
|
IdentifierT("if", TokenLocation(Position(1,1), Position(1,3))),
|
||||||
|
IntLiteralT("3", TokenLocation(Position(1,5), Position(1,6))),
|
||||||
|
IdentifierT("while", TokenLocation(Position(2,1), Position(2,6))),
|
||||||
|
IdentifierT("print_int", TokenLocation(Position(4,1), Position(4,10)))
|
||||||
|
)
|
||||||
|
test_tokenizer(code, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should work with multiple tokens") {
|
||||||
|
val code = "1 + 2 % 4 / 3 * 3 <= 100"
|
||||||
|
val expected = List(
|
||||||
|
IntLiteralT("1", TokenLocation(Position(1,1), Position(1,2))),
|
||||||
|
OperatorT("+", TokenLocation(Position(1,3), Position(1,4))),
|
||||||
|
IntLiteralT("2", TokenLocation(Position(1,5), Position(1,6))),
|
||||||
|
OperatorT("%", TokenLocation(Position(1,7), Position(1,8))),
|
||||||
|
IntLiteralT("4", TokenLocation(Position(1,9), Position(1,10))),
|
||||||
|
OperatorT("/", TokenLocation(Position(1,11), Position(1,12))),
|
||||||
|
IntLiteralT("3", TokenLocation(Position(1,13), Position(1,14))),
|
||||||
|
OperatorT("*", TokenLocation(Position(1,15), Position(1,16))),
|
||||||
|
IntLiteralT("3", TokenLocation(Position(1,17), Position(1,18))),
|
||||||
|
OperatorT("<=", TokenLocation(Position(1,19), Position(1,21))),
|
||||||
|
IntLiteralT("100", TokenLocation(Position(1,22), Position(1,25)))
|
||||||
|
)
|
||||||
|
test_tokenizer(code, expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Executable
+1
@@ -0,0 +1 @@
|
|||||||
|
echo '{"command": "compile", "code": "1+1 == 2"}' | netcat localhost 3000
|
||||||
Reference in New Issue
Block a user