diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0dc7b4b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/.vscode \ No newline at end of file diff --git a/NutMeg.jar b/NutMeg.jar new file mode 100644 index 0000000..62830ca Binary files /dev/null and b/NutMeg.jar differ diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..14e9568 --- /dev/null +++ b/readme.md @@ -0,0 +1 @@ +[kimura](https://script.meg.tokyo/) diff --git a/src/main/scala/tokyo/meg/script/NutMeg.scala b/src/main/scala/tokyo/meg/script/NutMeg.scala new file mode 100644 index 0000000..1850d35 --- /dev/null +++ b/src/main/scala/tokyo/meg/script/NutMeg.scala @@ -0,0 +1,58 @@ +package tokyo.meg.script + +import java.io.IOException + +import scala.util._ +import scala.util.chaining._ + +import tokyo.meg.script.io._ +import tokyo.meg.script.util._ +import tokyo.meg.script.lexer._ +import tokyo.meg.script.parser._ +import tokyo.meg.script.treewalker._ +import tokyo.meg.script.treewalker.values._ + +@main def main(args: String*): Unit = + val path = if (args.length == 0) None else Some(args(0)) + + val reader = path match + case Some(path) => FileReader.open[Value](path) + case None => Reader.open[Value](System.in) + + reader: reader => + val cursor = Cursor(reader) + val lexer = Lexer(cursor) + val parser = Parser(lexer) + val ast = parser.parse() + val treeWalker = TreeWalker((ast, ".")) + + treeWalker.eval() + match { + case Failure(exception: IOException) => + System.err.println( + path match + case Some(path) => + s"An error occurred while processing the file \"$path\": ${exception.getMessage()}" + + case _ => + s"An error occurred while processing the input: ${exception.getMessage()}" + ) + + 1 + + case Failure(exception: Throwable) => + System.err.println(exception) + System.err.println( + s"An unexpected error occurred: ${exception.getMessage()}" + ) + exception.printStackTrace() + + 1 + + case Success(value) => + value match + case IntValue(value) => value.toInt + case RealValue(value) => value.toInt + case _ => 0 + + } pipe System.exit diff --git a/src/main/scala/tokyo/meg/script/io/FileReader.scala b/src/main/scala/tokyo/meg/script/io/FileReader.scala new file mode 100644 index 0000000..0e26e8d --- /dev/null +++ b/src/main/scala/tokyo/meg/script/io/FileReader.scala @@ -0,0 +1,26 @@ +package tokyo.meg.script.io + +import java.io.{ + BufferedReader, + File, + FileReader => JavaFileReader, + InputStreamReader, + FileInputStream +} +import java.nio.file.Paths + +import scala.util._ +import scala.util.chaining._ + +object FileReader: + def open[T](path: String): (FileReader => T) => Try[T] = + Reader.open(() => FileReader(path)) + +final class FileReader(val path: String) extends Reader(FileInputStream(path)): + val file = File(path) + val parentPath = Paths + .get(file.getAbsolutePath()) + .normalize() + .getParent() + .toString() + .replaceAll("\\\\", "/") diff --git a/src/main/scala/tokyo/meg/script/io/Reader.scala b/src/main/scala/tokyo/meg/script/io/Reader.scala new file mode 100644 index 0000000..f43e8f2 --- /dev/null +++ b/src/main/scala/tokyo/meg/script/io/Reader.scala @@ -0,0 +1,47 @@ +package tokyo.meg.script.io + +import java.io.{BufferedReader, InputStream, InputStreamReader} +import java.nio.file.Paths + +import scala.collection.mutable._ +import scala.util._ +import scala.util.chaining._ + +object Reader: + def open[T](stream: InputStream): (Reader => T) => Try[T] = + open(() => Reader(stream)) + + def open[T, U <: Reader](readerGenerator: () => U)(f: U => T): Try[T] = + var reader: Option[U] = None + + Try: + reader = Some(readerGenerator()) + f(reader.get) + .tap: _ => + reader match + case Some(reader) => reader.close() + case None => () + +class Reader(val stream: InputStream): + private val reader = BufferedReader(InputStreamReader(stream, "UTF-8")) + private val joined = Stack[Reader]() + + def readLine(): Option[String] = + if joined.length == 0 then + reader.readLine() match + case null => None + case line => Some(line) + else + joined.top.readLine() match + case None => + joined.pop() + readLine() + + case Some(line) => Some(line) + + def join: Reader => Reader = + joined.push(_).pipe(_ => this) + + def close(): Unit = + reader.close() + joined.foreach(_.close()) diff --git a/src/main/scala/tokyo/meg/script/io/StringReader.scala b/src/main/scala/tokyo/meg/script/io/StringReader.scala new file mode 100644 index 0000000..6c6eb9d --- /dev/null +++ b/src/main/scala/tokyo/meg/script/io/StringReader.scala @@ -0,0 +1,13 @@ +package tokyo.meg.script.io + +import java.io.ByteArrayInputStream + +import scala.util._ +import scala.util.chaining._ + +object StringReader: + def open[T](string: String): (StringReader => T) => Try[T] = + Reader.open(() => StringReader(string)) + +final class StringReader(val string: String) + extends Reader(ByteArrayInputStream(string.getBytes("UTF-8"))) diff --git a/src/main/scala/tokyo/meg/script/lexer/Cursor.scala b/src/main/scala/tokyo/meg/script/lexer/Cursor.scala new file mode 100644 index 0000000..263b6e3 --- /dev/null +++ b/src/main/scala/tokyo/meg/script/lexer/Cursor.scala @@ -0,0 +1,94 @@ +package tokyo.meg.script.lexer + +import scala.util.chaining._ + +import tokyo.meg.script.io._ + +final class Cursor(private val reader: Reader) extends Iterator[Char]: + val parentPath = reader match + case reader: FileReader => reader.parentPath + case _ => "." + + private var _currentLine: Option[String] = Some("") + private var _atEof = false + private var (_line, _column) = (0, 1) + private var (_currentChar, _nextChar) = ('\n', '\n') + + advance() + + override inline def hasNext: Boolean = + !atEof + + override inline def next(): Char = + currentChar tap advance + + inline def currentLine: Option[String] = + _currentLine + + inline def atEof: Boolean = + _atEof + + inline def line: Int = + _line + + inline def column: Int = + _column + + inline def currentChar: Char = + _currentChar + + inline def nextChar: Char = + _nextChar + + def join: Reader => Cursor = + reader.join(_).pipe(_ => this) + + @annotation.tailrec + def advance[T](value: T = this): T = + _currentChar = _nextChar + + currentLine match + case None => + _nextChar = '\u0000' + _atEof = 0 < column + _column += 1 + + value + + case Some(string) => + if string.length == column - 1 + then fetchLine().advance(value) + else + _nextChar = if (string.length == column) '\n' else string(column) + _column += 1 + + value + + def insert(deleteCount: Int, string: String): Unit = + currentLine match + case Some(currentLine) => + _currentLine = Some( + currentLine.slice(0, column - deleteCount - 1) + + string + + currentLine.slice(column - 1, currentLine.length) + ) + + _column -= deleteCount - 1 + _currentChar = _currentLine.get(column - 2) + _nextChar = try _currentLine.get(column - 1) catch _ => '\n' + + case _ => () + + private def fetchLine[T](value: T = this): T = + _currentLine = reader.readLine() + _column = 0 + + currentLine match + case Some(nextLine) => + _line += 1 + + if nextLine.filter(_.isWhitespace) == nextLine + then fetchLine(value) + else value + + case _ => value diff --git a/src/main/scala/tokyo/meg/script/lexer/Lexer.scala b/src/main/scala/tokyo/meg/script/lexer/Lexer.scala new file mode 100644 index 0000000..37f89ca --- /dev/null +++ b/src/main/scala/tokyo/meg/script/lexer/Lexer.scala @@ -0,0 +1,295 @@ +package tokyo.meg.script.lexer + +import scala.collection.mutable._ +import scala.util.chaining._ + +import tokyo.meg.script.io._ +import tokyo.meg.script.util._ +import syntax._ +import tokens._ + +final class Lexer( + private val cursor: Cursor, + var path: String = ".", + val symbols: Map[String, String] = Map(), + val declarableVars: Map[String, String] = Map(), + val syntaxes: Map[String, (Buffer[SyntaxMacroToken], String)] = Map(), + val reservedTokens: DefaultReservedTokens = DefaultReservedTokens(), + val precedences: Map[String, Int] = DefaultOperatorPrecedences().precedences +) extends Iterator[Token]: + + import TokenCategory._ + import DefaultReservedTokens._ + import cursor._ + import reservedTokens._ + + private val levels = Stack[Int](0) + private var level = 0 + private var atEof = false + + override inline def hasNext: Boolean = + !atEof + + override inline def next(): Token = + getToken() + + def getToken(): Token = + if (level < levels.top) unindentOrOtherToken() + else if (currentChar == '\n') indentOrOtherToken() + else eatWhiteSpaces().nonIndentToken() tap fetchChar tap eatWhiteSpaces + + private def unindentOrOtherToken(): Token = + if levels.size == 1 then + levels.update(0, level) + + getToken() + else + levels.pop() + + if (levels.top < level) levels.update(0, level) + + newToken(" " * level, Unindent) + + private def indentOrOtherToken(): Token = + level = readWhile(_ == ' ').length - 1 tap fetchChar + + if levels.top < level + then newToken(" " * level, Indent).tap(_ => levels.push(level)) + else getToken() + + private def nonIndentToken(): Token = + currentChar match + case '\u0000' => + atEof = true + + newToken("\u0000", Eof) + + case prefix @ '-' => + if isNumber(nextChar) + then fetchChar().number(prefix) + else declarableOrReserved() + + case '"' => + string() + + case c => + if (c == ':' && isCharacter(nextChar)) identifierOperator() + else if (isParenthesis(c)) parenthesis() + else if (isNumber(c)) number() + else declarableOrReserved() + + private def number(prefix: String | Char = ""): Token = + val int = prefix.toString() + readWhile(isNumber) + + if nextChar == '.' + then fetchChar().newToken(int + readWhile(isNumber), LiteralReal) + else newToken(int, LiteralInt) + + private def string(): Token = + buildStringWhile(!("\"\n" contains nextChar)): + fetchChar() + + if currentChar == '\\' then + fetchChar() + + currentChar match + case '0' => '\u0000' + case 'a' => '\u0007' + case 'b' => '\b' + case 'e' => '\u001b' + case 'f' => '\f' + case 'n' => '\n' + case 'r' => '\r' + case 's' => '\u0020' + case 't' => '\t' + case 'v' => '\u000b' + case 'u' => readUnicode() + case 'x' => readAscii() + case c => c + else currentChar + .append: + if nextChar == '\n' + then '\n' + else "" tap fetchChar + .pipe(newToken(LiteralStr)) + + private def readUnicode(): Char = + try Integer.parseInt(fetchChar().readFor(4), 16).toChar + catch _ => 0 + + private def readAscii(): Char = + try Integer.parseInt(fetchChar().readFor(2), 16).toChar + catch _ => 0 + + private def identifierOperator(): Token = + fetchChar().readWhile(isCharacterOrNumber) pipe newToken(IdentifierOperator) + + private def declarableOrReserved(): Token = + val (value, category) = + if isCharacter(currentChar) + then (readWhile(isCharacterOrNumber), Identifier) + else (readWhile(isSymbol), Operator) + + reserved get value match + case Some(Comment) => commentToken() + case Some(Macro) => macroToken() + case Some(category) => reservedToken(value, category) + case _ if symbols contains value => + cursor.insert(value.length, symbols(value)) + + nonIndentToken() + + case _ if declarableVars contains value => + newToken(declarableVars(value), Declarable) + + case _ => newToken(value, category) + + private def commentToken(): Token = + readWhile(_ != '\n') drop 1 pipe newToken(Comment) + + private def macroToken(): Token = + val value = currentLine.get.slice(column - 1, currentLine.get.length) + val directive = fetchChar().eatWhiteSpaces().readWhile(isCharacter) + + newToken(value, Macro).tap(_ => execMacro(directive)) + + private def reservedToken(value: String, category: TokenCategory): Token = + newToken(value, if (isCanceled(category)) Operator else category) + + private def isCanceled(category: TokenCategory): Boolean = + category match + case Colon => List(WHERE, ELSE).forall(!isDefault(_)) + case Quest => List(IF, PLACEHOLDER).forall(!isDefault(_)) + case _ => false + + private def parenthesis(): Token = + newToken(reserved)(currentChar) + + private def execMacro: String => Unit = + case "define" => execDefineMacro() + case "token" => execTokenMacro() + case "package" => execPackageMacro() + case "include" => execIncludeMacro() + case "precedence" => execPrecedenceMacro() + case "syntax" => execSyntaxMacro() + case _ => () + + private def execDefineMacro(): Unit = + symbols.update(readWord(), readUntilEnd()) + + private def execTokenMacro(): Unit = + val name = readWord().toUpperCase() + val value = readWord().tap(_ => readWhile(_ != '\n')) + + if tokens.contains(name) && !reserved.contains(value) then + reserved.update(value, reserved(tokens(name))) + reserved.remove(tokens(name)) + tokens.update(name, value) + + private def execPackageMacro(): Unit = + readWhile(_ != '\n') + + path = parentPath + + private def execIncludeMacro(): Unit = + val sourcePath = readUntilEnd() + + try cursor.join(FileReader(s"$path/$sourcePath")) + catch _ => () + + private def execPrecedenceMacro(): Unit = + val operator = readWord() + val precedence = + try Integer.parseInt(readUntilEnd()) + catch _ => 0 + + precedences.update(operator, precedence) + + private def execSyntaxMacro(): Unit = + val name = readWord() + val syntax = syntaxDefinition() + val replacement = readUntilEnd() match + case "" => + val currentLevel = level tap fetchChar + + buildStringWhile( + currentLine match + case Some(line) => + !line + .toLowerCase() + .matches: + " " * currentLevel + + tokens(MACRO) + + "\\s*endsyntax(\\s*|\\s+.*)" + + case None => false + )("\n" + readWhile(_ != '\n') tap fetchChar) + .toString() + + case replacement => replacement + + syntaxes.update(name, (syntax, replacement)) + + @annotation.tailrec + private def syntaxDefinition( + syntax: Buffer[SyntaxMacroToken] = ArrayBuffer() + ): Buffer[SyntaxMacroToken] = + val category = readWord().toLowerCase() + + if category == "as" || atEof + then syntax + else + syntax.addAll: + category match + case "expr" => Array(Expr(readWord(), 5)) + case "level0" => Array(Expr(readWord(), 0)) + case "level1" => Array(Expr(readWord(), 1)) + case "level2" => Array(Expr(readWord(), 2)) + case "level3" => Array(Expr(readWord(), 3)) + case "level4" => Array(Expr(readWord(), 4)) + case "level5" => Array(Expr(readWord(), 5)) + case "level6" => Array(Expr(readWord(), 6)) + case "level7" => Array(Expr(readWord(), 7)) + case "var" => Array(Var(readWord())) + case "word" => Array(Word(readWord())) + case "cancel" => Array(Cancel(readWord())) + case _ => Array(Word(category), Cancel(category)) + + syntaxDefinition(syntax) + + private def newToken(value: Any, category: TokenCategory): Token = + Token(line, column - 1, value.toString(), category) + + private def newToken(category: String => TokenCategory): Any => Token = + value => newToken(value, category(value.toString())) + + private def newToken(category: TokenCategory): Any => Token = + newToken(_ => category) + + private def fetchChar[T](value: T = this): T = + value tap advance + + @annotation.tailrec + private def eatWhiteSpaces[T](value: T = this): T = + if all(currentChar)(_ != '\n', _.isWhitespace) + then fetchChar().eatWhiteSpaces(value) + else value + + private def readWhile(f: Char => Boolean): String = + buildStringWhile(currentChar)(f(nextChar)): + nextChar tap fetchChar + .toString() + + private def readFor(count: Int): String = + var i = 0; + + readWhile(_ => { i += 1; i != count }) + + private def readWord(): String = + fetchChar().eatWhiteSpaces().readWhile(!_.isWhitespace) + + private def readUntilEnd(): String = + fetchChar().eatWhiteSpaces() + if currentChar == '\n' + then "" + else readWhile(_ != '\n').trim() diff --git a/src/main/scala/tokyo/meg/script/lexer/syntax/Cancel.scala b/src/main/scala/tokyo/meg/script/lexer/syntax/Cancel.scala new file mode 100644 index 0000000..8ff2ce5 --- /dev/null +++ b/src/main/scala/tokyo/meg/script/lexer/syntax/Cancel.scala @@ -0,0 +1,4 @@ +package tokyo.meg.script.lexer.syntax + +final case class Cancel(override val value: String) + extends SyntaxMacroToken(value) diff --git a/src/main/scala/tokyo/meg/script/lexer/syntax/DefaultOperatorPrecedences.scala b/src/main/scala/tokyo/meg/script/lexer/syntax/DefaultOperatorPrecedences.scala new file mode 100644 index 0000000..ca001d2 --- /dev/null +++ b/src/main/scala/tokyo/meg/script/lexer/syntax/DefaultOperatorPrecedences.scala @@ -0,0 +1,47 @@ +package tokyo.meg.script.lexer.syntax + +import scala.collection.mutable._ + +final class DefaultOperatorPrecedences: + val precedences: Map[String, Int] = Map( + "-<<" -> 1, + ">>-" -> 1, + "??" -> 1, + "||" -> 2, + "!|" -> 2, + "^^" -> 3, + "!^" -> 3, + "&&" -> 4, + "!&" -> 4, + "|" -> 5, + "~|" -> 5, + "^" -> 6, + "~^" -> 6, + "&" -> 7, + "~&" -> 7, + "==" -> 8, + "!=" -> 8, + "<" -> 9, + "<=" -> 9, + ">" -> 9, + ">=" -> 9, + "<=>" -> 9, + "<<" -> 10, + ">>" -> 10, + ">>>" -> 10, + ".:" -> 10, + ":." -> 10, + "-<>" -> 10, + "<>-" -> 10, + ">+" -> 10, + "<+" -> 10, + "++" -> 10, + "--" -> 10, + "+" -> 11, + "-" -> 11, + "*" -> 12, + "/" -> 12, + "\\" -> 12, + "%" -> 12, + "**" -> -13 + ) diff --git a/src/main/scala/tokyo/meg/script/lexer/syntax/Expr.scala b/src/main/scala/tokyo/meg/script/lexer/syntax/Expr.scala new file mode 100644 index 0000000..f996dfe --- /dev/null +++ b/src/main/scala/tokyo/meg/script/lexer/syntax/Expr.scala @@ -0,0 +1,4 @@ +package tokyo.meg.script.lexer.syntax + +final case class Expr(override val value: String, val level: Int) + extends SyntaxMacroToken(value) diff --git a/src/main/scala/tokyo/meg/script/lexer/syntax/SyntaxMacroToken.scala b/src/main/scala/tokyo/meg/script/lexer/syntax/SyntaxMacroToken.scala new file mode 100644 index 0000000..429d6e7 --- /dev/null +++ b/src/main/scala/tokyo/meg/script/lexer/syntax/SyntaxMacroToken.scala @@ -0,0 +1,3 @@ +package tokyo.meg.script.lexer.syntax + +abstract class SyntaxMacroToken(val value: String) diff --git a/src/main/scala/tokyo/meg/script/lexer/syntax/Var.scala b/src/main/scala/tokyo/meg/script/lexer/syntax/Var.scala new file mode 100644 index 0000000..28b0a1f --- /dev/null +++ b/src/main/scala/tokyo/meg/script/lexer/syntax/Var.scala @@ -0,0 +1,3 @@ +package tokyo.meg.script.lexer.syntax + +final case class Var(override val value: String) extends SyntaxMacroToken(value) diff --git a/src/main/scala/tokyo/meg/script/lexer/syntax/Word.scala b/src/main/scala/tokyo/meg/script/lexer/syntax/Word.scala new file mode 100644 index 0000000..f1d3777 --- /dev/null +++ b/src/main/scala/tokyo/meg/script/lexer/syntax/Word.scala @@ -0,0 +1,4 @@ +package tokyo.meg.script.lexer.syntax + +final case class Word(override val value: String) + extends SyntaxMacroToken(value) diff --git a/src/main/scala/tokyo/meg/script/lexer/tokens/DefaultReservedTokens.scala b/src/main/scala/tokyo/meg/script/lexer/tokens/DefaultReservedTokens.scala new file mode 100644 index 0000000..84ccfc7 --- /dev/null +++ b/src/main/scala/tokyo/meg/script/lexer/tokens/DefaultReservedTokens.scala @@ -0,0 +1,127 @@ +package tokyo.meg.script.lexer.tokens + +import scala.collection.mutable._ + +object DefaultReservedTokens: + val MACRO = "MACRO" + val COMMENT = "COMMENT" + val SEMI = "SEMI" + val COLON = "COLON" + val WHERE = "WHERE" + val ELSE = "ELSE" + val COLONCOLON = "COLONCOLON" + val EQUALS = "EQUALS" + val GTLT = "GTLT" + val LFUNNEL = "LFUNNEL" + val RFUNNEL = "RFUNNEL" + val QUEST = "QUEST" + val IF = "IF" + val PLACEHOLDER = "PLACEHOLDER" + val AT = "AT" + val QUESTCOLON = "QUESTCOLON" + val ATCOLON = "ATCOLON" + val LARROW = "LARROW" + val PERIODPERIOD = "PERIODPERIOD" + val PERIOD = "PERIOD" + val ELLIPSIS = "ELLIPSIS" + val RARROW = "RARROW" + val DOLLAR = "DOLLAR" + val DEFAULT_WHERE = ":where" + val DEFAULT_ELSE = ":else" + val DEFAULT_IF = ":if" + val DEFAULT_PLACEHOLDER = ":placeholder" + +final class DefaultReservedTokens: + import TokenCategory._ + import DefaultReservedTokens._ + + val tokens = Map( + MACRO -> "#", + COMMENT -> "//", + SEMI -> ";", + COLON -> ":", + WHERE -> DEFAULT_WHERE, + ELSE -> DEFAULT_ELSE, + COLONCOLON -> "::", + EQUALS -> "=", + GTLT -> "><", + LFUNNEL -> "-<", + RFUNNEL -> ">-", + QUEST -> "?", + IF -> DEFAULT_IF, + PLACEHOLDER -> DEFAULT_PLACEHOLDER, + AT -> "@", + QUESTCOLON -> "?:", + ATCOLON -> "@:", + LARROW -> "<-", + PERIODPERIOD -> "..", + PERIOD -> ".", + ELLIPSIS -> "...", + RARROW -> "->", + DOLLAR -> "$" + ) + + val reserved = Map( + tokens(MACRO) -> Macro, + tokens(COMMENT) -> Comment, + tokens(SEMI) -> Semi, + tokens(COLON) -> Colon, + tokens(WHERE) -> Where, + tokens(ELSE) -> Else, + tokens(COLONCOLON) -> ColonColon, + tokens(EQUALS) -> Equals, + tokens(GTLT) -> GtLt, + tokens(LFUNNEL) -> LFunnel, + tokens(RFUNNEL) -> RFunnel, + tokens(QUEST) -> Quest, + tokens(IF) -> If, + tokens(PLACEHOLDER) -> Placeholder, + tokens(AT) -> At, + tokens(QUESTCOLON) -> QuestColon, + tokens(ATCOLON) -> AtColon, + tokens(LARROW) -> LArrow, + tokens(PERIODPERIOD) -> PeriodPeriod, + tokens(PERIOD) -> Period, + tokens(ELLIPSIS) -> Ellipsis, + tokens(RARROW) -> RArrow, + tokens(DOLLAR) -> Dollar, + "(" -> LParen, + ")" -> RParen, + "{" -> LBrace, + "}" -> RBrace, + "[" -> LBrack, + "]" -> RBrack + ) + + inline def isReserved: String => Boolean = + reserved.contains + + def isParenthesis: Char => Boolean = + "()[]{}\"".contains + + def isSymbol: Char => Boolean = + "!#$%&'*+,-./:;<=>?@\\^`|~".contains + + def isNumber: Char => Boolean = + "0123456789".contains + + def isHex: Char => Boolean = + "0123456789abcdef".contains + + def isCharacterOrNumber(c: Char): Boolean = + isCharacter(c) || isNumber(c) + + def isCharacter(c: Char): Boolean = + List( + isParenthesis, + isSymbol, + isNumber, + Character.isWhitespace + ).forall(!_(c)) + + def isDefault: String => Boolean = + case key @ PLACEHOLDER => tokens(key) == DEFAULT_PLACEHOLDER + case key @ IF => tokens(key) == DEFAULT_IF + case key @ ELSE => tokens(key) == DEFAULT_ELSE + case key @ WHERE => tokens(key) == DEFAULT_WHERE + case _ => true diff --git a/src/main/scala/tokyo/meg/script/lexer/tokens/Token.scala b/src/main/scala/tokyo/meg/script/lexer/tokens/Token.scala new file mode 100644 index 0000000..8d2ca9a --- /dev/null +++ b/src/main/scala/tokyo/meg/script/lexer/tokens/Token.scala @@ -0,0 +1,41 @@ +package tokyo.meg.script.lexer.tokens + +import scala.collection.mutable._ + +final case class Token( + val line: Int, + val column: Int, + val value: String, + val category: TokenCategory +): + import TokenCategory._ + + inline infix def is: TokenCategory => Boolean = + category == _ + + inline def isDeclarable: Boolean = + category == Operator || category == Identifier + + def isAppliable: Boolean = + isParenthesisStart || isLiteral || List( + Identifier, + Placeholder, + Equals, + RArrow, + Dollar + ).contains(category) + + def isOperator: Boolean = + List(Operator, IdentifierOperator) contains category + + def isLiteral: Boolean = + List(LiteralInt, LiteralReal, LiteralStr) contains category + + def isParenthesisStart: Boolean = + List(LParen, LBrace, LBrack, Indent) contains category + + def isParenthesisEnd: Boolean = + List(RParen, RBrace, RBrack, Unindent) contains category + + override inline def toString(): String = + value diff --git a/src/main/scala/tokyo/meg/script/lexer/tokens/TokenCategory.scala b/src/main/scala/tokyo/meg/script/lexer/tokens/TokenCategory.scala new file mode 100644 index 0000000..8bbc59b --- /dev/null +++ b/src/main/scala/tokyo/meg/script/lexer/tokens/TokenCategory.scala @@ -0,0 +1,43 @@ +package tokyo.meg.script.lexer.tokens + +enum TokenCategory: + case Macro + case Comment + case Semi + case Colon + case Where + case Else + case ColonColon + case Equals + case GtLt + case LFunnel + case RFunnel + case Quest + case If + case Placeholder + case At + case QuestColon + case AtColon + case LArrow + case PeriodPeriod + case Ellipsis + case Period + case RArrow + case Dollar + case Indent + case Unindent + case LParen + case RParen + case LBrace + case RBrace + case LBrack + case RBrack + case Declarable + case Operator + case IdentifierOperator + case Identifier + case AnonymousVariable + case LiteralInt + case LiteralReal + case LiteralStr + case Eof diff --git a/src/main/scala/tokyo/meg/script/parser/Parser.scala b/src/main/scala/tokyo/meg/script/parser/Parser.scala new file mode 100644 index 0000000..2502f11 --- /dev/null +++ b/src/main/scala/tokyo/meg/script/parser/Parser.scala @@ -0,0 +1,333 @@ +package tokyo.meg.script.parser + +import scala.collection.mutable._ +import scala.util.chaining._ + +import tokyo.meg.script.io._ +import tokyo.meg.script.util._ +import tokyo.meg.script.lexer._ +import tokyo.meg.script.lexer.syntax._ +import tokyo.meg.script.lexer.tokens._ +import ast._ + +class Parser( + val lexer: Lexer, + private val exprVars: Map[String, Node] = Map(), + private val keywords: Stack[ArrayBuffer[String]] = Stack() +): + import TokenCategory._ + import DefaultReservedTokens._ + import lexer._ + import reservedTokens._ + + private var (currentToken, nextToken): (Token, Token) = (null, null) + + this()() + + inline def path: String = + lexer.path + + inline def parse(): Node = + try level0() + catch _ => Empty() + + private def level0(): Node = + nodeSequence(level1, Semi) pipe statements + + private def level1(): Node = + val WhereToken = if (isDefault(WHERE)) Colon else Where + + nodeSequence(level2, WhereToken).reverse pipe statements + + private inline def level2(): Node = + level2(level3()) + + @annotation.tailrec + private def level2(node: Node): Node = + currentToken.category match + case GtLt => this().level2(Merge(node, level3())) + case _ => node + + private inline def level3(): Node = + level3(level4()) + + @annotation.tailrec + private def level3(node: Node): Node = + currentToken.category match + case LFunnel => this().level3(Apply(node, level4())) + case RFunnel => this().level3(Apply(level4(), node)) + case _ => node + + private def level4(): Node = + val ConditionalToken = if (isDefault(IF)) Quest else If + + level5().pipe: + currentToken.category match + case ConditionalToken => ternary(Conditional.apply) + case At => ternary(Loop.apply) + case QuestColon => ShorthandConditional(_, this().level4()) + case AtColon => ShorthandLoop(_, this().level4()) + case _ => identity + + private def level5(): Node = + infixExpr(level6(), 0) + + private inline def level6(): Node = + level6(level7()) + + private def level6(node: Node): Node = + currentToken.category match + case LArrow => level6(Inherit(node, this().level7())) + case ColonColon => this().accessOrAttributeDefine(node) + case Period => this().accessApply(node) + case PeriodPeriod => level6(this(ParentOf(node))) + case _ => applyOrLevel7(node) + + private def level7(): Node = + val AnonymousToken = if (isDefault(PLACEHOLDER)) Quest else Placeholder + val value = currentToken.value + + currentToken.category match + case Dollar => importExpr() + case Identifier | Operator => macroOrStartWithDeclarable(value) + case Declarable => startWithDeclarable(value) + case IdentifierOperator => this(IdentifierSectionLeft(value)) + case AnonymousVariable => this(Variable(value)) + case Equals => Define("", this().level2()) + case RArrow => Return(this().level4()) + case Indent => Statements(nodesUntil(Unindent)) + case LParen => emptyOrGroupedExpr() + case LBrack => Sequence(nodesUntil(RBrack)) + case LBrace => Struct(nodesUntil(RBrace)) + case RParen => this(Empty()) + case RBrack => this(Sequence(Array())) + case RBrace => this(Struct(Array())) + case ColonColon => outerOrAccessSection() + case Period => thisOrAccessApplySection() + case PeriodPeriod => this(Parent()) + case Ellipsis => Spread(this().level2()) + case AnonymousToken => anonymous() + case At => decorator() + case LiteralStr => this(ConstString(value)) + case LiteralInt => this(ConstInt(value.toLong)) + case LiteralReal => this(ConstReal(value.toDouble)) + case _ => Empty() + + private def statements(nodes: Seq[Node]): Node = + if nodes.length == 1 + then nodes(0) + else Statements(nodes.toArray) + + private def ternary(constructor: (Node, Node, Node) => Node): Node => Node = + val ElseToken = if (isDefault(ELSE)) Colon else Else + val right = this().level4() + + if currentToken.category == ElseToken + then this(constructor(_, right, level4())) + else constructor(_, right, Empty()) + + private def infixExpr(node: Node, precedence: Int): Node = + if !isOperable(currentToken) + then node + else + val pa = Math.abs(precedence) + val cp = currentPrecedence() // current precedence + val cpa = Math.abs(cp) + + val op = currentToken.value + val construct: (Node, Node) => Node = currentToken.category match + case Operator => (a, b) => Apply(AccessApply(a, Variable(op)), b) + case _ => (a, b) => Apply(Apply(Variable(op), a), b) + + val right = this().level6() + + if !isOperable(currentToken) + then construct(node, right) + else + val np = currentPrecedence() // next precedence + val npa = Math.abs(np) + val expr = + if npa < cpa then construct(node, right) + else if cpa < npa || cp < 0 then construct(node, infixExpr(right, cp)) + else infixExpr(construct(node, right), cp) + + if isOperable(currentToken) && pa < Math.abs(currentPrecedence()) + then infixExpr(expr, precedence) + else expr + + private def accessOrAttributeDefine(node: Node): Node = + if all(currentToken)(_.isDeclarable, !isKeyword(_)) && nextToken.is(Equals) + then AttributeDefine(node, currentToken.value, this()().level2()) + else + currentToken.category match + case Equals => AttributeDefine(node, "", this().level2()) + case _ => level6(Access(node, level7())) + + private def accessApply(node: Node): Node = + level6(AccessApply(node, level7())) + + private def applyOrLevel7(node: Node): Node = + if all(currentToken)(_.isAppliable, !isKeyword(_)) + then level6(Apply(node, level7())) + else node + + @annotation.tailrec + private def importExpr(names: ArrayDeque[String] = ArrayDeque()): Node = + this() + + if all(currentToken)(_.is(Identifier), !isKeyword(_)) + then importExpr(names append currentToken.value) + else Import(names.toArray) + + private def macroOrStartWithDeclarable(value: String): Node = + if (isKeyword(currentToken)) Empty() + else if (syntaxes contains value) this().definedSyntax(value) + else if (exprVars contains value) this(exprVars(value)) + else startWithDeclarable(value) + + private def definedSyntax(symbol: String): Node = + val (syntax, replacement) = syntaxes(symbol) + val _declarableVars = Map[String, String]().addAll(declarableVars) + val _exprVars = Map[String, Node]().addAll(exprVars) + val _keywords = ArrayBuffer[String]() + + syntax.foreach: + case Var(value) => _declarableVars.update(value, "") + case Expr(value, _) => _exprVars.update(value, Empty()) + case Word(value) => _keywords.addOne(value) + case _ => () + + keywords.push(_keywords) + + syntax.takeWhileInPlace: + case Var(value) => + _declarableVars.addOne(value, currentToken.value) + + this(true) + + case Expr(value, level) => + val expr = level match + case 0 => level0() + case 1 => level1() + case 2 => level2() + case 3 => level3() + case 4 => level4() + case 5 => level5() + case 6 => level6() + case 7 => level7() + case _ => level5() + + _exprVars.update(value, expr) + + true + + case Word(value) => + if currentToken.value == value + then this(true) + else false + + case Cancel(value) => + try _keywords.remove(_keywords.indexOf(value)) + catch _ => () + + true + + keywords.pop() + + val cursor = Cursor(StringReader(replacement)) + val lexer = Lexer( + cursor, + path, + symbols, + _declarableVars, + syntaxes, + reservedTokens, + precedences + ) + val parser = Parser(lexer, _exprVars) + + parser.parse() + + private def startWithDeclarable(value: String): Node = + nextToken.category match + case Equals => Define(value, this()().level2()) + case RArrow => Lambda(value, this()().level4()) + case _ => unaryOrVariable(value) + + private def unaryOrVariable: String => Node = + if currentToken.is(Operator) + && all(nextToken)(any(_)(_.isAppliable, _.isOperator), !isKeyword(_)) + then Variable.apply.andThen(AccessApply(this().level7(), _)) + else this(Variable(_)) + + private def emptyOrGroupedExpr(): Node = + if nextToken.category == RParen + then this()(Empty(false)) + else Statements(nodesUntil(RParen)) + + private def outerOrAccessSection(): Node = + this().memberSections(AccessSection(_), Outer.apply) + + private def thisOrAccessApplySection(): Node = + this().memberSections(AccessApplySection(_), This.apply) + + private def memberSections(section: Node => Node, other: () => Node): Node = + if any(currentToken)(_.is(Operator), all(_)(_.isAppliable, !isKeyword(_))) + then section(level7()) + else other() + + private def anonymous(): Node = + val Token(line, column, value, _) = currentToken + + currentToken = Token(line, column, value, AnonymousVariable) + + Lambda(value, level4()) + + private def decorator(): Node = + this().decorator(level7(), level7()) + + private def decorator(node: Node, target: Node): Node = + target match + case Lambda(argument, body) => Lambda(argument, decorator(node, body)) + case _ => Apply(node, target) + + @annotation.tailrec + private def nodeSequence(parser: () => Node, separator: TokenCategory)( + implicit nodes: ArrayDeque[Node] = ArrayDeque(parser()) + ): Seq[Node] = + if currentToken is separator then + if nextToken.isParenthesisEnd + then this(nodes append Empty()) + else this().nodeSequence(parser, separator)(nodes append parser()) + else nodes + + private def nodesUntil(until: TokenCategory): Array[Node] = + this() + + if currentToken is until + then this(Array[Node]()) + else + nodeSequence(level1, Semi).toArray.tap: _ => + if currentToken is until + then this() + + private def isKeyword(token: Token): Boolean = + keywords.exists(_.contains(token.value)) + + private inline def isOperable(token: Token): Boolean = + !isKeyword(token) && token.isOperator + + private def currentPrecedence(): Int = + precedences.get(currentToken.value) match + case Some(precedence) => precedence + case None => 0 + + private def apply[T](value: T = this): T = + currentToken = nextToken + + getToken().tap: token => + if any(token)(_.is(Comment), _.is(Macro)) + then this() + else nextToken = token + + value diff --git a/src/main/scala/tokyo/meg/script/parser/ast/Access.scala b/src/main/scala/tokyo/meg/script/parser/ast/Access.scala new file mode 100644 index 0000000..73ef4b0 --- /dev/null +++ b/src/main/scala/tokyo/meg/script/parser/ast/Access.scala @@ -0,0 +1,3 @@ +package tokyo.meg.script.parser.ast + +final case class Access(val node: Node, val member: Node) extends Node diff --git a/src/main/scala/tokyo/meg/script/parser/ast/AccessApply.scala b/src/main/scala/tokyo/meg/script/parser/ast/AccessApply.scala new file mode 100644 index 0000000..3ba0050 --- /dev/null +++ b/src/main/scala/tokyo/meg/script/parser/ast/AccessApply.scala @@ -0,0 +1,3 @@ +package tokyo.meg.script.parser.ast + +final case class AccessApply(val node: Node, val member: Node) extends Node diff --git a/src/main/scala/tokyo/meg/script/parser/ast/AccessApplySection.scala b/src/main/scala/tokyo/meg/script/parser/ast/AccessApplySection.scala new file mode 100644 index 0000000..7abf10e --- /dev/null +++ b/src/main/scala/tokyo/meg/script/parser/ast/AccessApplySection.scala @@ -0,0 +1,3 @@ +package tokyo.meg.script.parser.ast + +final case class AccessApplySection(val member: Node) extends Node diff --git a/src/main/scala/tokyo/meg/script/parser/ast/AccessSection.scala b/src/main/scala/tokyo/meg/script/parser/ast/AccessSection.scala new file mode 100644 index 0000000..69d2534 --- /dev/null +++ b/src/main/scala/tokyo/meg/script/parser/ast/AccessSection.scala @@ -0,0 +1,3 @@ +package tokyo.meg.script.parser.ast + +final case class AccessSection(val member: Node) extends Node diff --git a/src/main/scala/tokyo/meg/script/parser/ast/Apply.scala b/src/main/scala/tokyo/meg/script/parser/ast/Apply.scala new file mode 100644 index 0000000..804bca0 --- /dev/null +++ b/src/main/scala/tokyo/meg/script/parser/ast/Apply.scala @@ -0,0 +1,3 @@ +package tokyo.meg.script.parser.ast + +final case class Apply(val left: Node, val right: Node) extends Node diff --git a/src/main/scala/tokyo/meg/script/parser/ast/AttributeDefine.scala b/src/main/scala/tokyo/meg/script/parser/ast/AttributeDefine.scala new file mode 100644 index 0000000..0d8092c --- /dev/null +++ b/src/main/scala/tokyo/meg/script/parser/ast/AttributeDefine.scala @@ -0,0 +1,7 @@ +package tokyo.meg.script.parser.ast + +final case class AttributeDefine( + val node: Node, + val attribute: String, + val value: Node +) extends Node diff --git a/src/main/scala/tokyo/meg/script/parser/ast/Conditional.scala b/src/main/scala/tokyo/meg/script/parser/ast/Conditional.scala new file mode 100644 index 0000000..f70f776 --- /dev/null +++ b/src/main/scala/tokyo/meg/script/parser/ast/Conditional.scala @@ -0,0 +1,7 @@ +package tokyo.meg.script.parser.ast + +final case class Conditional( + val condition: Node, + val consequence: Node, + val alternative: Node +) extends Node diff --git a/src/main/scala/tokyo/meg/script/parser/ast/Const.scala b/src/main/scala/tokyo/meg/script/parser/ast/Const.scala new file mode 100644 index 0000000..f1b3cc0 --- /dev/null +++ b/src/main/scala/tokyo/meg/script/parser/ast/Const.scala @@ -0,0 +1,3 @@ +package tokyo.meg.script.parser.ast + +abstract class Const[T](val value: T) extends Node diff --git a/src/main/scala/tokyo/meg/script/parser/ast/ConstInt.scala b/src/main/scala/tokyo/meg/script/parser/ast/ConstInt.scala new file mode 100644 index 0000000..98ed910 --- /dev/null +++ b/src/main/scala/tokyo/meg/script/parser/ast/ConstInt.scala @@ -0,0 +1,3 @@ +package tokyo.meg.script.parser.ast + +final case class ConstInt(override val value: Long) extends Const(value) diff --git a/src/main/scala/tokyo/meg/script/parser/ast/ConstReal.scala b/src/main/scala/tokyo/meg/script/parser/ast/ConstReal.scala new file mode 100644 index 0000000..f424267 --- /dev/null +++ b/src/main/scala/tokyo/meg/script/parser/ast/ConstReal.scala @@ -0,0 +1,3 @@ +package tokyo.meg.script.parser.ast + +final case class ConstReal(override val value: Double) extends Const(value) diff --git a/src/main/scala/tokyo/meg/script/parser/ast/ConstString.scala b/src/main/scala/tokyo/meg/script/parser/ast/ConstString.scala new file mode 100644 index 0000000..c2eb068 --- /dev/null +++ b/src/main/scala/tokyo/meg/script/parser/ast/ConstString.scala @@ -0,0 +1,3 @@ +package tokyo.meg.script.parser.ast + +final case class ConstString(override val value: String) extends Const(value) diff --git a/src/main/scala/tokyo/meg/script/parser/ast/Define.scala b/src/main/scala/tokyo/meg/script/parser/ast/Define.scala new file mode 100644 index 0000000..a79d5c5 --- /dev/null +++ b/src/main/scala/tokyo/meg/script/parser/ast/Define.scala @@ -0,0 +1,3 @@ +package tokyo.meg.script.parser.ast + +final case class Define(val name: String, val value: Node) extends Node diff --git a/src/main/scala/tokyo/meg/script/parser/ast/Empty.scala b/src/main/scala/tokyo/meg/script/parser/ast/Empty.scala new file mode 100644 index 0000000..7c36525 --- /dev/null +++ b/src/main/scala/tokyo/meg/script/parser/ast/Empty.scala @@ -0,0 +1,3 @@ +package tokyo.meg.script.parser.ast + +final case class Empty(val isImplicit: Boolean = true) extends Node diff --git a/src/main/scala/tokyo/meg/script/parser/ast/IdentifierSectionLeft.scala b/src/main/scala/tokyo/meg/script/parser/ast/IdentifierSectionLeft.scala new file mode 100644 index 0000000..2716132 --- /dev/null +++ b/src/main/scala/tokyo/meg/script/parser/ast/IdentifierSectionLeft.scala @@ -0,0 +1,3 @@ +package tokyo.meg.script.parser.ast + +final case class IdentifierSectionLeft(val operator: String) extends Node diff --git a/src/main/scala/tokyo/meg/script/parser/ast/Import.scala b/src/main/scala/tokyo/meg/script/parser/ast/Import.scala new file mode 100644 index 0000000..983bdcc --- /dev/null +++ b/src/main/scala/tokyo/meg/script/parser/ast/Import.scala @@ -0,0 +1,3 @@ +package tokyo.meg.script.parser.ast + +final case class Import(val names: Array[String]) extends Node diff --git a/src/main/scala/tokyo/meg/script/parser/ast/Inherit.scala b/src/main/scala/tokyo/meg/script/parser/ast/Inherit.scala new file mode 100644 index 0000000..8c7055f --- /dev/null +++ b/src/main/scala/tokyo/meg/script/parser/ast/Inherit.scala @@ -0,0 +1,3 @@ +package tokyo.meg.script.parser.ast + +final case class Inherit(val parent: Node, val child: Node) extends Node diff --git a/src/main/scala/tokyo/meg/script/parser/ast/Lambda.scala b/src/main/scala/tokyo/meg/script/parser/ast/Lambda.scala new file mode 100644 index 0000000..0c11760 --- /dev/null +++ b/src/main/scala/tokyo/meg/script/parser/ast/Lambda.scala @@ -0,0 +1,3 @@ +package tokyo.meg.script.parser.ast + +final case class Lambda(val argument: String, val body: Node) extends Node diff --git a/src/main/scala/tokyo/meg/script/parser/ast/Loop.scala b/src/main/scala/tokyo/meg/script/parser/ast/Loop.scala new file mode 100644 index 0000000..721c8cd --- /dev/null +++ b/src/main/scala/tokyo/meg/script/parser/ast/Loop.scala @@ -0,0 +1,7 @@ +package tokyo.meg.script.parser.ast + +final case class Loop( + val condition: Node, + val consequence: Node, + val alternative: Node +) extends Node diff --git a/src/main/scala/tokyo/meg/script/parser/ast/Merge.scala b/src/main/scala/tokyo/meg/script/parser/ast/Merge.scala new file mode 100644 index 0000000..234acbe --- /dev/null +++ b/src/main/scala/tokyo/meg/script/parser/ast/Merge.scala @@ -0,0 +1,3 @@ +package tokyo.meg.script.parser.ast + +final case class Merge(val left: Node, val right: Node) extends Node diff --git a/src/main/scala/tokyo/meg/script/parser/ast/Node.scala b/src/main/scala/tokyo/meg/script/parser/ast/Node.scala new file mode 100644 index 0000000..fe507d4 --- /dev/null +++ b/src/main/scala/tokyo/meg/script/parser/ast/Node.scala @@ -0,0 +1,3 @@ +package tokyo.meg.script.parser.ast + +abstract class Node diff --git a/src/main/scala/tokyo/meg/script/parser/ast/Outer.scala b/src/main/scala/tokyo/meg/script/parser/ast/Outer.scala new file mode 100644 index 0000000..2af3505 --- /dev/null +++ b/src/main/scala/tokyo/meg/script/parser/ast/Outer.scala @@ -0,0 +1,3 @@ +package tokyo.meg.script.parser.ast + +final case class Outer() extends Node diff --git a/src/main/scala/tokyo/meg/script/parser/ast/Parent.scala b/src/main/scala/tokyo/meg/script/parser/ast/Parent.scala new file mode 100644 index 0000000..c54637a --- /dev/null +++ b/src/main/scala/tokyo/meg/script/parser/ast/Parent.scala @@ -0,0 +1,3 @@ +package tokyo.meg.script.parser.ast + +final case class Parent() extends Node diff --git a/src/main/scala/tokyo/meg/script/parser/ast/ParentOf.scala b/src/main/scala/tokyo/meg/script/parser/ast/ParentOf.scala new file mode 100644 index 0000000..62fe4db --- /dev/null +++ b/src/main/scala/tokyo/meg/script/parser/ast/ParentOf.scala @@ -0,0 +1,3 @@ +package tokyo.meg.script.parser.ast + +final case class ParentOf(val node: Node) extends Node diff --git a/src/main/scala/tokyo/meg/script/parser/ast/Return.scala b/src/main/scala/tokyo/meg/script/parser/ast/Return.scala new file mode 100644 index 0000000..b8a2ff5 --- /dev/null +++ b/src/main/scala/tokyo/meg/script/parser/ast/Return.scala @@ -0,0 +1,3 @@ +package tokyo.meg.script.parser.ast + +final case class Return(val node: Node) extends Node diff --git a/src/main/scala/tokyo/meg/script/parser/ast/Sequence.scala b/src/main/scala/tokyo/meg/script/parser/ast/Sequence.scala new file mode 100644 index 0000000..38d1eca --- /dev/null +++ b/src/main/scala/tokyo/meg/script/parser/ast/Sequence.scala @@ -0,0 +1,5 @@ +package tokyo.meg.script.parser.ast + +final case class Sequence(val nodes: Array[Node]) extends Node: + override def toString(): String = + s"[${nodes.mkString("; ")}]" diff --git a/src/main/scala/tokyo/meg/script/parser/ast/ShorthandConditional.scala b/src/main/scala/tokyo/meg/script/parser/ast/ShorthandConditional.scala new file mode 100644 index 0000000..5a5faf8 --- /dev/null +++ b/src/main/scala/tokyo/meg/script/parser/ast/ShorthandConditional.scala @@ -0,0 +1,6 @@ +package tokyo.meg.script.parser.ast + +final case class ShorthandConditional( + val condition: Node, + val alternative: Node +) extends Node diff --git a/src/main/scala/tokyo/meg/script/parser/ast/ShorthandLoop.scala b/src/main/scala/tokyo/meg/script/parser/ast/ShorthandLoop.scala new file mode 100644 index 0000000..c80452b --- /dev/null +++ b/src/main/scala/tokyo/meg/script/parser/ast/ShorthandLoop.scala @@ -0,0 +1,6 @@ +package tokyo.meg.script.parser.ast + +final case class ShorthandLoop( + val condition: Node, + val alternative: Node +) extends Node diff --git a/src/main/scala/tokyo/meg/script/parser/ast/Spread.scala b/src/main/scala/tokyo/meg/script/parser/ast/Spread.scala new file mode 100644 index 0000000..4e79da0 --- /dev/null +++ b/src/main/scala/tokyo/meg/script/parser/ast/Spread.scala @@ -0,0 +1,3 @@ +package tokyo.meg.script.parser.ast + +final case class Spread(val node: Node) extends Node diff --git a/src/main/scala/tokyo/meg/script/parser/ast/Statements.scala b/src/main/scala/tokyo/meg/script/parser/ast/Statements.scala new file mode 100644 index 0000000..96b296b --- /dev/null +++ b/src/main/scala/tokyo/meg/script/parser/ast/Statements.scala @@ -0,0 +1,5 @@ +package tokyo.meg.script.parser.ast + +final case class Statements(val nodes: Array[Node]) extends Node: + override def toString() = + nodes.mkString("; ") diff --git a/src/main/scala/tokyo/meg/script/parser/ast/Struct.scala b/src/main/scala/tokyo/meg/script/parser/ast/Struct.scala new file mode 100644 index 0000000..b069503 --- /dev/null +++ b/src/main/scala/tokyo/meg/script/parser/ast/Struct.scala @@ -0,0 +1,5 @@ +package tokyo.meg.script.parser.ast + +final case class Struct(val nodes: Array[Node]) extends Node: + override def toString() = + s"{${nodes.mkString("; ")}}" diff --git a/src/main/scala/tokyo/meg/script/parser/ast/This.scala b/src/main/scala/tokyo/meg/script/parser/ast/This.scala new file mode 100644 index 0000000..97c93ab --- /dev/null +++ b/src/main/scala/tokyo/meg/script/parser/ast/This.scala @@ -0,0 +1,3 @@ +package tokyo.meg.script.parser.ast + +final case class This() extends Node diff --git a/src/main/scala/tokyo/meg/script/parser/ast/Variable.scala b/src/main/scala/tokyo/meg/script/parser/ast/Variable.scala new file mode 100644 index 0000000..c57ef52 --- /dev/null +++ b/src/main/scala/tokyo/meg/script/parser/ast/Variable.scala @@ -0,0 +1,3 @@ +package tokyo.meg.script.parser.ast + +final case class Variable(val name: String) extends Node diff --git a/src/main/scala/tokyo/meg/script/treewalker/TreeWalker.scala b/src/main/scala/tokyo/meg/script/treewalker/TreeWalker.scala new file mode 100644 index 0000000..49240ea --- /dev/null +++ b/src/main/scala/tokyo/meg/script/treewalker/TreeWalker.scala @@ -0,0 +1,214 @@ +package tokyo.meg.script.treewalker + +import scala.collection.mutable._ +import scala.util._ +import scala.util.chaining._ + +import tokyo.meg.script.io._ +import tokyo.meg.script.lexer._ +import tokyo.meg.script.parser._ +import tokyo.meg.script.parser.ast._ +import values._ + +final class TreeWalker( + private val module: (Node, String), + private val modules: Map[String, (Node, String)] = Map(), + private val environment: Value = AnyValue() +): + import Value._ + + private final case class ReturnValue(val value: () => Value) extends Throwable + + private val (ast, path) = module + private val environments: Stack[Value] = Stack(environment) + + inline def eval(): Value = + finalValue(ast.$) + + private def eval(accessFromParent: Boolean): Node => Value = + case node: Apply => applyExpr(node) + case node: ParentOf => parentOf(node) + case node: Variable => variable(accessFromParent)(node.name) + case node: Access => access(node) + case node: AccessSection => accessSection(node) + case node: AccessApply => accessApply(node) + case node: AccessApplySection => accessApplySection(node) + case node: Define => variableDefine(node) + case node: AttributeDefine => attributeDefine(node) + case node: Import => importExpr(node) + case node: Spread => spread(node) + case node: Conditional => conditional(node) + case node: ShorthandConditional => conditional(node) + case node: Loop => loop(node, None) + case node: ShorthandLoop => loop(node, None) + case node: Inherit => inherit(node) + case node: IdentifierSectionLeft => identifierSectionLeft(node) + case node: Merge => merge(node) + case node: Lambda => lambda(node) + case node: Statements => statements(node) + case node: Sequence => list(node) + case node: Struct => struct(node) + case node: Return => returns(node) + case This() => env + case Outer() => env.outer + case Parent() => env.parent + case ConstInt(value) => IntValue(value) + case ConstReal(value) => RealValue(value) + case ConstString(value) => StringValue(value) + case Empty(isImplicit) => EmptyValue(isImplicit) + case _ => EmptyValue() + + @annotation.tailrec + private def finalValue(value: => Value): Value = + try value + catch + case ReturnValue(value) => finalValue(value()) + case _: StackOverflowError => IntValue(1) + + private def applyExpr(node: Apply): Value = + node.left.$(node.right.$) + + private def parentOf(node: ParentOf): Value = + node.node.$.parent + + private def variable(accessFromParent: Boolean): String => Value = + env.get(if accessFromParent then _.parent else _.outer) + + private def access(node: Access): Value = + in(node.node.$)(_ => node.member.$$) + + private def accessSection: AccessSection => Value = + _.member.pipe(member => function(in(_)(_ => member.$$))) + + private def accessApply(node: AccessApply): Value = + in(node.node.$)(node.member.$$(_)) + + private def accessApplySection: AccessApplySection => Value = + _.member.pipe(member => function(in(_)(member.$$(_)))) + + private def variableDefine(node: Define): Value = + define(node.name, env, node.value.$) + + private def attributeDefine(node: AttributeDefine): Value = + define(node.attribute, node.node.$, node.value.$) + + private def define: (String, Value, Value) => Value = + operateAttributes(_.remove, _.define) + + private def importExpr(node: Import): Value = + s"$path/${node.names.mkString("/")}.meg" + .pipe: fileName => + modules get fileName match + case Some(module) => module + case None => + FileReader.open(fileName): + Cursor(_) + .pipe(Lexer(_, path)) + .pipe(Parser(_)) + .pipe(parser => (parser.parse(), parser.path)) + match + case Failure(_) => (Empty(), path) + case Success(module) => module.tap(modules.update(fileName, _)) + .pipe(TreeWalker(_, modules, env).eval()) + + private def spread: Spread => Value = + _.node.$.tap(env.attributes ++= _.attributes) + + private def conditional(node: Conditional): Value = + if node.condition.$.isTruthy + then node.consequence.$ + else node.alternative.$ + + private def conditional(node: ShorthandConditional): Value = + val left = node.condition.$ + + if (left.isTruthy) left else node.alternative.$ + + @annotation.tailrec + private def loop(node: Loop, value: Option[Value]): Value = + if node.condition.$.isTruthy + then loop(node, Some(node.consequence.$)) + else + value match + case Some(value) => value + case None => node.alternative.$ + + @annotation.tailrec + private def loop(node: ShorthandLoop, value: Option[Value] = None): Value = + val left = node.condition.$ + + if left.isTruthy + then loop(node, Some(left)) + else + value match + case Some(value) => value + case None => node.alternative.$ + + private def inherit(node: Inherit): Value = + node.parent.$.pipe(left => node.child.$.tap(_.parent = left)) + + private def identifierSectionLeft(node: IdentifierSectionLeft): Value = + val operator = variable(false)(node.operator) + + function(left => function(operator(_)(left))) + + private def merge(node: Merge): Value = + node.left.$.tap(_.attributes ++= node.right.$.attributes) + + private def lambda(node: Lambda): Value = + AnyValue(env).tap: func => + func.setBody: argument => + valueOrReturn: + in(AnyValue(func)): environment => + environment.define(argument)(node.argument) + + node.body.$ + + private def statements(node: Statements): Value = + if node.nodes.length == 0 + then EmptyValue() + else node.nodes.map(_.$).last + + private def list(node: Sequence): Value = + ListValue(ArrayBuffer()).tap: list => + try node.nodes.foreach(list.values addOne _.$) + catch case ReturnValue(value) => list.values.addOne(value()) + + private def struct(node: Struct): Value = + valueOrReturn: + in(AnyValue(env))(_.tap(_ => node.nodes.foreach(_.$))) + + private def returns(node: Return): Value = + throw ReturnValue(() => node.node.$) + + private def operateAttributes( + ifEmpty: Value => String => Value, + ifNotEmpty: Value => Value => String => Value + )(name: String, environment: Value, value: Value): Value = + value.tap: _ => + if name == "" + then environment.setBody(value.body) + else + value match + case EmptyValue(true) => ifEmpty(environment)(name) + case _ => ifNotEmpty(environment)(value)(name) + + private def valueOrReturn(value: => Value): Value = + try value + catch case ReturnValue(value) => value().tap(_ => environments.pop()) + + private inline def env: Value = + environments.top + + private def in[T](environment: Value)(expression: Value => T): T = + environments + .push(environment) + .pipe(_ => expression(environment)) + .tap(_ => environments.pop()) + + extension (node: Node) + inline def $ : Value = + eval(false)(node) + + inline def $$ : Value = + eval(true)(node) diff --git a/src/main/scala/tokyo/meg/script/treewalker/values/AnyValue.scala b/src/main/scala/tokyo/meg/script/treewalker/values/AnyValue.scala new file mode 100644 index 0000000..230023b --- /dev/null +++ b/src/main/scala/tokyo/meg/script/treewalker/values/AnyValue.scala @@ -0,0 +1,7 @@ +package tokyo.meg.script.treewalker.values + +import RootValue._ + +final case class AnyValue(_outer: Value = singletonRoot) extends Value: + outer = _outer + parent = singletonRoot diff --git a/src/main/scala/tokyo/meg/script/treewalker/values/ArrayValue.scala b/src/main/scala/tokyo/meg/script/treewalker/values/ArrayValue.scala new file mode 100644 index 0000000..66384b4 --- /dev/null +++ b/src/main/scala/tokyo/meg/script/treewalker/values/ArrayValue.scala @@ -0,0 +1,207 @@ +package tokyo.meg.script.treewalker.values + +import scala.collection.mutable._ +import scala.util.chaining._ + +object ArrayValue extends HasSingletonRoot[ArrayValue]: + val singletonRoot = ArrayValue() + + initialize() + +final private case class ArrayValue() extends Value: + import Value._ + + attributes.addAll: + Array( + "*" -> repeat, + "+" -> concat, + "-<>" -> indexOf, + "<>-" -> lastIndexOf, + "at" -> at, + "len" -> len, + "slice" -> slice, + "reversed" -> reversed, + "sorted" -> sorted, + "indexOf" -> indexOf, + "lastIndexOf" -> lastIndexOf, + "indexWhere" -> indexWhere, + "lastIndexWhere" -> lastIndexWhere, + "map" -> map, + "filter" -> filter, + "reduce" -> reduce, + "all" -> all, + "any" -> any, + "take" -> take, + "takeRight" -> takeRight, + "takeWhile" -> takeWhile, + "drop" -> drop, + "dropRight" -> dropRight, + "dropWhile" -> dropWhile, + "join" -> join + ) + + private val I = IntValue + private val R = RealValue + private val L = ListValue + private val S = StringValue + + private def s: Any => Value = + case value: Value => value.toStringValue + case value => value.toString() + + def index(length: Int, i: Int): Int = + try (i % length + length) % length + catch _ => 0 + + def index[T](a: Seq[T]): Int => Int = + index(a.length, _) + + def index(a: String): Int => Int = + index(a.length, _) + + def getValue(a: Seq[Value], i: Int): Value = + try a(index(a)(i)) + catch _ => EmptyValue() + + def getValue(a: String, i: Int): Value = + try a(index(a)(i)) + catch _ => "" + + def at: Value = biFunction: + try + (_, _) match + case (S(values), I(i)) => getValue(values, i.toInt) + case (S(values), R(i)) => getValue(values, i.toInt) + case (L(values), I(i)) => getValue(values, i.toInt) + case (L(values), R(i)) => getValue(values, i.toInt) + case (L(values), L(indices)) => indices map body + case _ => EmptyValue() + catch _ => (_, _) => EmptyValue() + + def len: Value = function: + case S(values) => I(values.length) + case L(values) => I(values.length) + case _ => EmptyValue() + + def slice: Value = triFunction: + case (S(values), I(a), I(b)) => values.slice(a.toInt, b.toInt) + case (S(values), I(a), R(b)) => values.slice(a.toInt, b.toInt) + case (S(values), R(a), I(b)) => values.slice(a.toInt, b.toInt) + case (S(values), R(a), R(b)) => values.slice(a.toInt, b.toInt) + case (L(values), I(a), I(b)) => values.slice(a.toInt, b.toInt) + case (L(values), I(a), R(b)) => values.slice(a.toInt, b.toInt) + case (L(values), R(a), I(b)) => values.slice(a.toInt, b.toInt) + case (L(values), R(a), R(b)) => values.slice(a.toInt, b.toInt) + case _ => EmptyValue() + + def reversed: Value = function: + case S(values) => values.reverse + case L(values) => L(values.reverse) + case _ => EmptyValue() + + def sorted: Value = biFunction: + case (S(values), f) => values.sortWith((a, b) => f(a)(b).isTruthy) + case (L(values), f) => values.sortWith((a, b) => f(a)(b).isTruthy) + case _ => EmptyValue() + + def repeat: Value = biFunction: + case (S(a), I(b)) => a.repeat(b.toInt) + case (S(a), R(b)) => a.repeat(b.toInt) + case (L(a), I(b)) => ArrayBuffer.fill(b.toInt)(a).flatten + case (L(a), R(b)) => ArrayBuffer.fill(b.toInt)(a).flatten + case _ => EmptyValue() + + def concat: Value = biFunction: + case (S(a), S(b)) => a + b + case (S(a), b) => a + b._toString + case (L(a), L(b)) => a concat b + case (list @ L(a), b) => list._toString + b._toString + case _ => EmptyValue() + + def indexOf: Value = biFunction: + case (S(a), b) => a.indexOf(b._toString) + case (L(a), b) => a indexWhere b.== + case _ => EmptyValue() + + def lastIndexOf: Value = biFunction: + case (S(a), b) => a.lastIndexOf(b._toString) + case (L(a), b) => a lastIndexWhere b.== + case _ => EmptyValue() + + def indexWhere: Value = biFunction: + case (S(a), b) => a map s indexWhere b.andThen(_.isTruthy) + case (L(a), b) => a indexWhere b.andThen(_.isTruthy) + case _ => EmptyValue() + + def lastIndexWhere: Value = biFunction: + case (S(a), b) => a map s lastIndexWhere b.andThen(_.isTruthy) + case (L(a), b) => a lastIndexWhere b.andThen(_.isTruthy) + case _ => EmptyValue() + + def map: Value = biFunction: + case (S(values), f) => values.toArray.map(f(_)).mkString("") + case (L(values), f) => values map f + case _ => EmptyValue() + + def filter: Value = biFunction: + case (S(values), f) => values.filter(f(_).isTruthy) + case (L(values), f) => values.filter(e => f(e).isTruthy) + case _ => EmptyValue() + + def reduce: Value = biFunction: + case (S(values), f) => values.toArray.map(s).reduce((a, b) => f(a)(b)) + case (L(values), f) => values.reduce((a, b) => f(a)(b)) + case _ => EmptyValue() + + def all: Value = biFunction: + case (S(values), f) => values.forall(f(_).isTruthy) + case (L(values), f) => values.forall(f(_).isTruthy) + case _ => EmptyValue() + + def any: Value = biFunction: + case (S(values), f) => values.exists(f(_).isTruthy) + case (L(values), f) => values.exists(f(_).isTruthy) + case _ => EmptyValue() + + def take: Value = biFunction: + case (S(value), I(n)) => value take n.toInt + case (S(value), R(n)) => value take n.toInt + case (L(value), I(n)) => value take n.toInt + case (L(value), R(n)) => value take n.toInt + case _ => EmptyValue() + + def takeRight: Value = biFunction: + case (S(value), I(n)) => value takeRight n.toInt + case (S(value), R(n)) => value takeRight n.toInt + case (L(value), I(n)) => value takeRight n.toInt + case (L(value), R(n)) => value takeRight n.toInt + case _ => EmptyValue() + + def takeWhile: Value = biFunction: + case (S(value), f) => value.takeWhile(f(_).isTruthy) + case (L(value), f) => value.takeWhile(f(_).isTruthy) + case _ => EmptyValue() + + def drop: Value = biFunction: + case (S(value), I(n)) => value drop n.toInt + case (S(value), R(n)) => value drop n.toInt + case (L(value), I(n)) => value drop n.toInt + case (L(value), R(n)) => value drop n.toInt + case _ => EmptyValue() + + def dropRight: Value = biFunction: + case (S(value), I(n)) => value dropRight n.toInt + case (S(value), R(n)) => value dropRight n.toInt + case (L(value), I(n)) => value dropRight n.toInt + case (L(value), R(n)) => value dropRight n.toInt + case _ => EmptyValue() + + def dropWhile: Value = biFunction: + case (S(value), f) => value.dropWhile(f(_).isTruthy) + case (L(value), f) => value.dropWhile(f(_).isTruthy) + case _ => EmptyValue() + + def join: Value = biFunction: + case (S(a), b) => a.map(s).mkString(b._toString) + case (L(a), b) => a.map(s).mkString(b._toString) + case _ => EmptyValue() diff --git a/src/main/scala/tokyo/meg/script/treewalker/values/EmptyRoot.scala b/src/main/scala/tokyo/meg/script/treewalker/values/EmptyRoot.scala new file mode 100644 index 0000000..4abb825 --- /dev/null +++ b/src/main/scala/tokyo/meg/script/treewalker/values/EmptyRoot.scala @@ -0,0 +1,8 @@ +package tokyo.meg.script.treewalker.values + +object EmptyRoot extends HasSingletonRoot[EmptyRoot]: + val singletonRoot = EmptyRoot() + + initialize() + +private final case class EmptyRoot() extends Value diff --git a/src/main/scala/tokyo/meg/script/treewalker/values/EmptyValue.scala b/src/main/scala/tokyo/meg/script/treewalker/values/EmptyValue.scala new file mode 100644 index 0000000..ad28100 --- /dev/null +++ b/src/main/scala/tokyo/meg/script/treewalker/values/EmptyValue.scala @@ -0,0 +1,8 @@ +package tokyo.meg.script.treewalker.values + +final case class EmptyValue(val isImplicit: Boolean = false) extends Value: + outer = RootValue.singletonRoot + parent = EmptyRoot.singletonRoot + + override def toString(): String = + "()" diff --git a/src/main/scala/tokyo/meg/script/treewalker/values/HasSingletonRoot.scala b/src/main/scala/tokyo/meg/script/treewalker/values/HasSingletonRoot.scala new file mode 100644 index 0000000..049893a --- /dev/null +++ b/src/main/scala/tokyo/meg/script/treewalker/values/HasSingletonRoot.scala @@ -0,0 +1,12 @@ +package tokyo.meg.script.treewalker.values + +import scala.util.chaining._ + +trait HasSingletonRoot[T <: Value]: + val singletonRoot: T + + final def initialize[U <: Value]( + Root: HasSingletonRoot[U] = RootValue + ): Unit = + singletonRoot.parent = Root.singletonRoot + singletonRoot.outer = Root.singletonRoot diff --git a/src/main/scala/tokyo/meg/script/treewalker/values/IntRoot.scala b/src/main/scala/tokyo/meg/script/treewalker/values/IntRoot.scala new file mode 100644 index 0000000..3c958e5 --- /dev/null +++ b/src/main/scala/tokyo/meg/script/treewalker/values/IntRoot.scala @@ -0,0 +1,17 @@ +package tokyo.meg.script.treewalker.values + +import scala.util.chaining._ + +object IntRoot extends HasSingletonRoot[IntRoot]: + val singletonRoot = IntRoot() + + initialize(NumberValue) + +final private case class IntRoot() extends Value: + import Value._ + + setBody[IntValue]: + case IntValue(value) => value + case RealValue(value) => value.toLong + case StringValue(values) => toIntValue(values) + case _ => 0 diff --git a/src/main/scala/tokyo/meg/script/treewalker/values/IntValue.scala b/src/main/scala/tokyo/meg/script/treewalker/values/IntValue.scala new file mode 100644 index 0000000..c7565a6 --- /dev/null +++ b/src/main/scala/tokyo/meg/script/treewalker/values/IntValue.scala @@ -0,0 +1,29 @@ +package tokyo.meg.script.treewalker.values + +final case class IntValue(val value: Long) extends Value: + outer = RootValue.singletonRoot + parent = IntRoot.singletonRoot + + setBody(NumberValue.singletonRoot.mul(this)) + + override def toString(): String = + value.toString() + +implicit inline final def toIntValue(value: Boolean): IntValue = + if (value) 1 else 0 + +implicit inline final def toIntValue(value: Long | Int): IntValue = + value match + case value: Long => IntValue(value) + case value: Int => IntValue(value) + +inline final def toIntValue(value: String): IntValue = + try java.lang.Long.parseLong(value) + catch _ => 0 + +inline final def toIntValue(value: Value): IntValue = + value match + case IntValue(a) => a + case RealValue(a) => a.toLong + case StringValue(a) => toIntValue(a) + case _ => 0 diff --git a/src/main/scala/tokyo/meg/script/treewalker/values/ListRoot.scala b/src/main/scala/tokyo/meg/script/treewalker/values/ListRoot.scala new file mode 100644 index 0000000..8faec6f --- /dev/null +++ b/src/main/scala/tokyo/meg/script/treewalker/values/ListRoot.scala @@ -0,0 +1,105 @@ +package tokyo.meg.script.treewalker.values + +import scala.collection.mutable._ +import scala.util.chaining._ + +object ListRoot extends HasSingletonRoot[ListRoot]: + val singletonRoot = ListRoot() + + initialize(ArrayValue) + +final private case class ListRoot() extends Value: + import Value._ + + private val L = ListValue + private val I = IntValue + private val R = RealValue + + setBody(function: + case L(values) => L(values) + case EmptyValue(_) => L(ArrayBuffer()) + case value => L(ArrayBuffer(value)) + ) + + attributes.addAll: + Array( + "set" -> set, + "swap" -> swap, + "push" -> push, + "unshift" -> unshift, + "insert" -> insertElement, + "remove" -> removeElement, + "pop" -> pop, + "shift" -> shift, + "sort" -> sort, + ">+" -> push, + "<+" -> unshift, + "++" -> insertElement, + "--" -> removeElement + ) + + private def set(self: ListValue, i: Int, value: Value): Value = + val index = ArrayValue.singletonRoot.index(self.values)(i) + + if self.values.length == index + then self + else self.tap(_.values.update(index, value)) + + def set: Value = triFunction: + case (self @ L(_), I(i), value) => set(self, i.toInt, value) + case (self @ L(_), R(i), value) => set(self, i.toInt, value) + case _ => EmptyValue() + + private def swap(self: ListValue, i: Int, j: Int): Value = + val index = ArrayValue.singletonRoot.index(self.values)(_) + + if self.values.length == index(i) || self.values.length == index(j) + then self + else + self.tap: + _.values(index(i)).pipe: e => + self.values.update(index(i), self.values(index(j))) + self.values.update(index(j), e) + + def swap: Value = triFunction: + case (self @ L(_), I(i), I(j)) => swap(self, i.toInt, j.toInt) + case (self @ L(_), I(i), R(j)) => swap(self, i.toInt, j.toInt) + case (self @ L(_), R(i), I(j)) => swap(self, i.toInt, j.toInt) + case (self @ L(_), R(i), R(j)) => swap(self, i.toInt, j.toInt) + case _ => EmptyValue() + + def push: Value = biFunction: + case (self @ L(values), value) => self.tap(_ => values.append(value)) + case _ => EmptyValue() + + def unshift: Value = biFunction: + case (self @ L(values), value) => self.tap(_ => values.prepend(value)) + case _ => EmptyValue() + + def pop: Value = function: + case L(values) => + if (values.length > 0) values.remove(values.length - 1) else EmptyValue() + + case _ => EmptyValue() + + def shift: Value = function: + case L(values) => if (values.length > 0) values.remove(0) else EmptyValue() + case _ => EmptyValue() + + def insertElement: Value = triFunction: + case (self @ L(_), I(i), value) => self.insert(i.toInt, value) + case (self @ L(_), R(i), value) => self.insert(i.toInt, value) + case _ => EmptyValue() + + def removeElement: Value = biFunction: + case (L(values), I(i)) => + if (values.length > i) values.remove(i.toInt) else EmptyValue() + + case (L(values), R(i)) => + if (values.length > i) values.remove(i.toInt) else EmptyValue() + + case _ => EmptyValue() + + def sort: Value = biFunction: + case (self @ L(_), f) => self.sort((a, b) => f(a)(b).isTruthy) + case _ => EmptyValue() diff --git a/src/main/scala/tokyo/meg/script/treewalker/values/ListValue.scala b/src/main/scala/tokyo/meg/script/treewalker/values/ListValue.scala new file mode 100644 index 0000000..9611df3 --- /dev/null +++ b/src/main/scala/tokyo/meg/script/treewalker/values/ListValue.scala @@ -0,0 +1,42 @@ +package tokyo.meg.script.treewalker.values + +import scala.collection.mutable._ +import scala.util.chaining._ + +final case class ListValue(val values: ArrayBuffer[Value]) extends Value: + outer = RootValue.singletonRoot + parent = ListRoot.singletonRoot + + setBody(ArrayValue.singletonRoot.at(this).body) + + def insert(i: Int, value: Value): Value = + val index = ArrayValue.singletonRoot.index(values.length, i) + + if values.length == index + then this + else this.tap(_ => values.insert(index, value)) + + def sort(f: (Value, Value) => Boolean): Value = + this.tap(_ => values.sortInPlaceWith(f)) + + override def toString(): String = + val seq = values + .map: e => + if e eq this + then "[...]" + else + e match + case StringValue(e) => s"\"${e}\"" + case _ => e._toString + .mkString("; ") + + s"[${seq}]" + +implicit inline final def toListValue(value: ArrayBuffer[Value]): ListValue = + ListValue(value) + +implicit inline final def toListValue[T <: Value](value: Array[T]): ListValue = + ListValue(ArrayBuffer(value*)) + +implicit inline final def toListValue[T <: Value](value: Seq[T]): ListValue = + value.toArray[Value] diff --git a/src/main/scala/tokyo/meg/script/treewalker/values/NumberValue.scala b/src/main/scala/tokyo/meg/script/treewalker/values/NumberValue.scala new file mode 100644 index 0000000..28ce4a7 --- /dev/null +++ b/src/main/scala/tokyo/meg/script/treewalker/values/NumberValue.scala @@ -0,0 +1,434 @@ +package tokyo.meg.script.treewalker.values + +object NumberValue extends HasSingletonRoot[NumberValue]: + val singletonRoot = NumberValue() + + initialize() + +final private case class NumberValue() extends Value: + import Value._ + + private val I = IntValue + private val R = RealValue + private val S = StringValue + + attributes.addAll: + Array( + "+" -> add, + "-" -> sub, + "*" -> mul, + "/" -> div, + "\\" -> intdiv, + "%" -> mod, + "**" -> pow, + "<" -> lt, + ">" -> gt, + "<=" -> le, + ">=" -> ge, + "<=>" -> spaceship, + "==" -> _eq, + "!=" -> neq, + "&&" -> and, + "||" -> or, + "^^" -> xor, + "!&" -> nand, + "!|" -> nor, + "!^" -> xnor, + "&" -> bitAnd, + "|" -> bitOr, + "^" -> bitXor, + "~&" -> bitNand, + "~|" -> bitNor, + "~^" -> bitXnor, + ">>>" -> unsignedRightShift, + ">>" -> rightShift, + "<<" -> leftShift, + "--" -> neg, + "!" -> not, + "~" -> inv, + "abs" -> abs, + "sign" -> sign, + "floor" -> floor, + "ceil" -> ceil, + "round" -> round, + "max" -> max, + "min" -> min, + "exp" -> exp, + "log" -> log, + "log10" -> log10, + "log2" -> log2, + "logE" -> logE, + "sin" -> sin, + "cos" -> cos, + "tan" -> tan, + "asin" -> asin, + "acos" -> acos, + "atan" -> atan, + "atan2" -> atan2, + "sinh" -> sinh, + "cosh" -> cosh, + "tanh" -> tanh, + "random" -> random + ) + + def add: Value = biFunction: + case (I(a), I(b)) => a + b + case (I(a), R(b)) => a + b + case (R(a), I(b)) => a + b + case (R(a), R(b)) => a + b + case (I(a), b) => a + b._toString + case (R(a), b) => a + b._toString + case _ => EmptyValue() + + def sub: Value = biFunction: + case (I(a), I(b)) => a - b + case (I(a), R(b)) => a - b + case (R(a), I(b)) => a - b + case (R(a), R(b)) => a - b + case _ => EmptyValue() + + def mul: Value = biFunction: + case (ref @ I(0), I(b)) => ref + case (ref @ R(0), R(b)) => ref + case (I(a), I(b)) => a * b + case (I(a), R(b)) => a * b + case (R(a), I(b)) => a * b + case (R(a), R(b)) => a * b + case _ => EmptyValue() + + def div: Value = biFunction: + case (I(a), I(b)) => a.toDouble / b + case (I(a), R(b)) => a / b + case (R(a), I(b)) => a / b + case (R(a), R(b)) => a / b + case _ => EmptyValue() + + def intdiv: Value = biFunction: + case (I(a), I(b)) => a / b + case (I(a), R(b)) => a / b.toLong + case (R(a), I(b)) => a.toLong / b + case (R(a), R(b)) => a.toLong / b.toLong + case _ => EmptyValue() + + def mod: Value = biFunction: + case (I(a), I(b)) => a % b + case (I(a), R(b)) => a % b + case (R(a), I(b)) => a % b + case (R(a), R(b)) => a % b + case _ => EmptyValue() + + def pow: Value = biFunction: + case (I(a), I(b)) => Math.pow(a, b) + case (I(a), R(b)) => Math.pow(a, b) + case (R(a), I(b)) => Math.pow(a, b) + case (R(a), R(b)) => Math.pow(a, b) + case _ => EmptyValue() + + def lt: Value = biFunction: + case (I(a), I(b)) => a < b + case (I(a), R(b)) => a < b + case (R(a), I(b)) => a < b + case (R(a), R(b)) => a < b + case _ => EmptyValue() + + def gt: Value = biFunction: + case (I(a), I(b)) => a > b + case (I(a), R(b)) => a > b + case (R(a), I(b)) => a > b + case (R(a), R(b)) => a > b + case _ => EmptyValue() + + def le: Value = biFunction: + case (I(a), I(b)) => a <= b + case (I(a), R(b)) => a <= b + case (R(a), I(b)) => a <= b + case (R(a), R(b)) => a <= b + case _ => EmptyValue() + + def ge: Value = biFunction: + case (I(a), I(b)) => a >= b + case (I(a), R(b)) => a >= b + case (R(a), I(b)) => a >= b + case (R(a), R(b)) => a >= b + case _ => EmptyValue() + + def spaceship: Value = biFunction: + case (I(a), I(b)) => a.compareTo(b) + case (I(a), R(b)) => a.compareTo(b.toLong) + case (R(a), I(b)) => a.compareTo(b.toDouble) + case (R(a), R(b)) => a.compareTo(b) + case _ => EmptyValue() + + def _eq: Value = biFunction: + case (I(a), I(b)) => a == b + case (I(a), R(b)) => a == b + case (R(a), I(b)) => a == b + case (R(a), R(b)) => a == b + case (I(_), _) => false + case (R(_), _) => false + case _ => EmptyValue() + + def neq: Value = biFunction: + case (I(a), I(b)) => a != b + case (I(a), R(b)) => a != b + case (R(a), I(b)) => a != b + case (R(a), R(b)) => a != b + case (I(_), _) => true + case (R(_), _) => true + case _ => EmptyValue() + + def and: Value = biFunction: + case (I(a), I(b)) => a * b != 0 + case (I(a), R(b)) => a * b != 0 + case (R(a), I(b)) => a * b != 0 + case (R(a), R(b)) => a * b != 0 + case _ => EmptyValue() + + def or: Value = biFunction: + case (I(a), I(b)) => a != 0 || b != 0 + case (I(a), R(b)) => a != 0 || b != 0 + case (R(a), I(b)) => a != 0 || b != 0 + case (R(a), R(b)) => a != 0 || b != 0 + case _ => EmptyValue() + + def xor: Value = biFunction: + case (I(a), I(b)) => a == 0 && b != 0 || a != 0 && b == 0 + case (I(a), R(b)) => a == 0 && b != 0 || a != 0 && b == 0 + case (R(a), I(b)) => a == 0 && b != 0 || a != 0 && b == 0 + case (R(a), R(b)) => a == 0 && b != 0 || a != 0 && b == 0 + case _ => EmptyValue() + + def nand: Value = biFunction: + case (I(a), I(b)) => a * b == 0 + case (I(a), R(b)) => a * b == 0 + case (R(a), I(b)) => a * b == 0 + case (R(a), R(b)) => a * b == 0 + case _ => EmptyValue() + + def nor: Value = biFunction: + case (I(a), I(b)) => a == 0 && b == 0 + case (I(a), R(b)) => a == 0 && b == 0 + case (R(a), I(b)) => a == 0 && b == 0 + case (R(a), R(b)) => a == 0 && b == 0 + case _ => EmptyValue() + + def xnor: Value = biFunction: + case (I(a), I(b)) => !(a == 0 && b != 0 || a != 0 && b == 0) + case (I(a), R(b)) => !(a == 0 && b != 0 || a != 0 && b == 0) + case (R(a), I(b)) => !(a == 0 && b != 0 || a != 0 && b == 0) + case (R(a), R(b)) => !(a == 0 && b != 0 || a != 0 && b == 0) + case _ => EmptyValue() + + def bitAnd: Value = biFunction: + case (I(a), I(b)) => a & b + case (I(a), R(b)) => a & b.toLong + case (R(a), I(b)) => a.toLong & b + case (R(a), R(b)) => a.toLong & b.toLong + case _ => EmptyValue() + + def bitOr: Value = biFunction: + case (I(a), I(b)) => a | b + case (I(a), R(b)) => a | b.toLong + case (R(a), I(b)) => a.toLong | b + case (R(a), R(b)) => a.toLong | b.toLong + case _ => EmptyValue() + + def bitXor: Value = biFunction: + case (I(a), I(b)) => a ^ b + case (I(a), R(b)) => a ^ b.toLong + case (R(a), I(b)) => a.toLong ^ b + case (R(a), R(b)) => a.toLong ^ b.toLong + case _ => EmptyValue() + + def bitNand: Value = biFunction: + case (I(a), I(b)) => ~(a & b) + case (I(a), R(b)) => ~(a & b.toLong) + case (R(a), I(b)) => ~(a.toLong & b) + case (R(a), R(b)) => ~(a.toLong & b.toLong) + case _ => EmptyValue() + + def bitNor: Value = biFunction: + case (I(a), I(b)) => ~(a | b) + case (I(a), R(b)) => ~(a | b.toLong) + case (R(a), I(b)) => ~(a.toLong | b) + case (R(a), R(b)) => ~(a.toLong | b.toLong) + case _ => EmptyValue() + + def bitXnor: Value = biFunction: + case (I(a), I(b)) => ~(a ^ b) + case (I(a), R(b)) => ~(a ^ b.toLong) + case (R(a), I(b)) => ~(a.toLong ^ b) + case (R(a), R(b)) => ~(a.toLong ^ b.toLong) + case _ => EmptyValue() + + def unsignedRightShift: Value = biFunction: + case (I(a), I(b)) => a >>> b + case (I(a), R(b)) => a >>> b.toLong + case (R(a), I(b)) => a.toLong >>> b + case (R(a), R(b)) => a.toLong >>> b.toLong + case _ => EmptyValue() + + def rightShift: Value = biFunction: + case (I(a), I(b)) => a >> b + case (I(a), R(b)) => a >> b.toLong + case (R(a), I(b)) => a.toLong >> b + case (R(a), R(b)) => a.toLong >> b.toLong + case _ => EmptyValue() + + def leftShift: Value = biFunction: + case (I(a), I(b)) => a << b + case (I(a), R(b)) => a << b.toLong + case (R(a), I(b)) => a.toLong << b + case (R(a), R(b)) => a.toLong << b.toLong + case _ => EmptyValue() + + def neg: Value = function: + case I(x) => -x + case R(x) => -x + case _ => EmptyValue() + + def not: Value = function: + case I(x) => x == 0 + case R(x) => x == 0 + case _ => EmptyValue() + + def inv: Value = function: + case I(x) => ~x + case R(x) => ~x.toLong + case _ => EmptyValue() + + def abs: Value = function: + case I(x) => Math.abs(x) + case R(x) => Math.abs(x) + case _ => EmptyValue() + + def sign: Value = function: + case I(x) => Math.signum(x) + case R(x) => Math.signum(x) + case _ => EmptyValue() + + def floor: Value = function: + case I(x) => Math.floor(x) + case R(x) => Math.floor(x) + case _ => EmptyValue() + + def ceil: Value = function: + case I(x) => Math.ceil(x) + case R(x) => Math.ceil(x) + case _ => EmptyValue() + + def round: Value = function: + case I(x) => Math.round(x) + case R(x) => Math.round(x) + case _ => EmptyValue() + + def max: Value = biFunction: + case (I(a), I(b)) => Math.max(a, b) + case (I(a), R(b)) => Math.max(a, b) + case (R(a), I(b)) => Math.max(a, b) + case (R(a), R(b)) => Math.max(a, b) + case _ => EmptyValue() + + def min: Value = biFunction: + case (I(a), I(b)) => Math.min(a, b) + case (I(a), R(b)) => Math.min(a, b) + case (R(a), I(b)) => Math.min(a, b) + case (R(a), R(b)) => Math.min(a, b) + case _ => EmptyValue() + + def exp: Value = function: + case I(x) => Math.exp(x) + case R(x) => Math.exp(x) + case _ => EmptyValue() + + def log: Value = biFunction: + case (I(base), I(x)) => Math.log(x) / Math.log(base) + case (I(base), R(x)) => Math.log(x) / Math.log(base) + case (R(base), I(x)) => Math.log(x) / Math.log(base) + case (R(base), R(x)) => Math.log(x) / Math.log(base) + case _ => EmptyValue() + + def log10: Value = function: + case I(x) => Math.log10(x) + case R(x) => Math.log10(x) + case _ => EmptyValue() + + def log2: Value = function: + case I(x) => Math.log(x) / Math.log(2) + case R(x) => Math.log(x) / Math.log(2) + case _ => EmptyValue() + + def logE: Value = function: + case I(x) => Math.log(x) + case R(x) => Math.log(x) + case _ => EmptyValue() + + def sin: Value = function: + case I(x) => Math.sin(x) + case R(x) => Math.sin(x) + case _ => EmptyValue() + + def cos: Value = function: + case I(x) => Math.cos(x) + case R(x) => Math.cos(x) + case _ => EmptyValue() + + def tan: Value = function: + case I(x) => Math.tan(x) + case R(x) => Math.tan(x) + case _ => EmptyValue() + + def asin: Value = function: + case I(x) => Math.asin(x) + case R(x) => Math.asin(x) + case _ => EmptyValue() + + def acos: Value = function: + case I(x) => Math.acos(x) + case R(x) => Math.acos(x) + case _ => EmptyValue() + + def atan: Value = function: + case I(x) => Math.atan(x) + case R(x) => Math.atan(x) + case _ => EmptyValue() + + def atan2: Value = biFunction: + case (I(a), I(b)) => Math.atan2(a, b) + case (I(a), R(b)) => Math.atan2(a, b) + case (R(a), I(b)) => Math.atan2(a, b) + case (R(a), R(b)) => Math.atan2(a, b) + case _ => EmptyValue() + + def sinh: Value = function: + case I(x) => Math.sinh(x) + case R(x) => Math.sinh(x) + case _ => EmptyValue() + + def cosh: Value = function: + case I(x) => Math.cosh(x) + case R(x) => Math.cosh(x) + case _ => EmptyValue() + + def tanh: Value = function: + case I(x) => Math.tanh(x) + case R(x) => Math.tanh(x) + case _ => EmptyValue() + + def random: Value = function: + case NumberValue() => Math.random() + case RealRoot() => random(0.0) + case IntRoot() => random(I(1)) + case R(a) => + function: + case I(b) => a + Math.random() * (b - a) + case R(b) => a + Math.random() * (b - a) + case _ => EmptyValue() + + case I(a) => + function: + case I(b) => (a + Math.random() * (b - a)).toLong + case R(b) => (a + Math.random() * (b - a)).toLong + case _ => EmptyValue() + + case _ => EmptyValue() diff --git a/src/main/scala/tokyo/meg/script/treewalker/values/ReaderRoot.scala b/src/main/scala/tokyo/meg/script/treewalker/values/ReaderRoot.scala new file mode 100644 index 0000000..e0c46dc --- /dev/null +++ b/src/main/scala/tokyo/meg/script/treewalker/values/ReaderRoot.scala @@ -0,0 +1,49 @@ +package tokyo.meg.script.treewalker.values + +import java.io._ + +import scala.util.chaining._ + +object ReaderRoot extends HasSingletonRoot[ReaderRoot]: + val singletonRoot = ReaderRoot() + + initialize() + +private final case class ReaderRoot() extends Value: + import Value._ + + setBody: path => + (() => FileInputStream(File(path._toString))).pipe: stream => + ReaderValue(stream, "UTF-8").setBody: encoding => + ReaderValue(stream, encoding._toString) + + attributes.addAll: + Array( + "close" -> close, + "read" -> read, + "getChar" -> getChar, + "getLine" -> getLine + ) + + def close: Value = function: + case reader @ ReaderValue(_, _) => reader tap (_ => reader.stream.close()) + case _ => EmptyValue() + + def read: Value = + input(_.stream.read().toLong pipe IntValue.apply) + + def getChar: Value = input: + _.charStream + .read() + .pipe(Array(_)) + .pipe(c => String(c, 0, c.length)) + + def getLine: Value = + input(_.charStream.readLine()) + + private def input(f: ReaderValue => Value): Value = function: + case reader @ ReaderValue(_, _) => + try f(reader) + catch _ => EmptyValue() + + case _ => EmptyValue() diff --git a/src/main/scala/tokyo/meg/script/treewalker/values/ReaderValue.scala b/src/main/scala/tokyo/meg/script/treewalker/values/ReaderValue.scala new file mode 100644 index 0000000..4a5732e --- /dev/null +++ b/src/main/scala/tokyo/meg/script/treewalker/values/ReaderValue.scala @@ -0,0 +1,27 @@ +package tokyo.meg.script.treewalker.values + +import java.io._ +import java.nio.charset._ + +import scala.util.chaining._ + +final case class ReaderValue( + val streamOpener: () => InputStream, + val encoding: String +) extends Value: + outer = RootValue.singletonRoot + parent = ReaderRoot.singletonRoot + + private val charset = Charset.forName(encoding) + private var _stream: Option[InputStream] = None + private var _charStream: Option[BufferedReader] = None + + def stream: InputStream = + _stream match + case None => streamOpener().tap(s => _stream = Some(s)) + case Some(stream) => stream + + def charStream: BufferedReader = + _charStream match + case None => BufferedReader(InputStreamReader(stream, charset)) + case Some(stream) => stream diff --git a/src/main/scala/tokyo/meg/script/treewalker/values/RealRoot.scala b/src/main/scala/tokyo/meg/script/treewalker/values/RealRoot.scala new file mode 100644 index 0000000..a055768 --- /dev/null +++ b/src/main/scala/tokyo/meg/script/treewalker/values/RealRoot.scala @@ -0,0 +1,24 @@ +package tokyo.meg.script.treewalker.values + +import scala.util.chaining._ + +object RealRoot extends HasSingletonRoot[RealRoot]: + val singletonRoot = RealRoot() + + initialize(NumberValue) + + NumberValue.singletonRoot.attributes.addAll: + Array( + "PI" -> RealValue(Math.PI), + "E" -> RealValue(Math.E), + "TAU" -> RealValue(Math.TAU) + ) + +final private case class RealRoot() extends Value: + import Value._ + + setBody[RealValue]: + case IntValue(value) => value.toDouble + case RealValue(value) => value + case StringValue(values) => toRealValue(values) + case _ => 0 diff --git a/src/main/scala/tokyo/meg/script/treewalker/values/RealValue.scala b/src/main/scala/tokyo/meg/script/treewalker/values/RealValue.scala new file mode 100644 index 0000000..225c672 --- /dev/null +++ b/src/main/scala/tokyo/meg/script/treewalker/values/RealValue.scala @@ -0,0 +1,17 @@ +package tokyo.meg.script.treewalker.values + +final case class RealValue(val value: Double) extends Value: + outer = RootValue.singletonRoot + parent = RealRoot.singletonRoot + + setBody(NumberValue.singletonRoot.mul(this)) + + override def toString(): String = + value.toString() + +implicit inline final def toRealValue(value: Double): RealValue = + RealValue(value) + +inline final def toRealValue(value: String): RealValue = + try java.lang.Double.parseDouble(value) + catch _ => 0 diff --git a/src/main/scala/tokyo/meg/script/treewalker/values/RootValue.scala b/src/main/scala/tokyo/meg/script/treewalker/values/RootValue.scala new file mode 100644 index 0000000..542daa4 --- /dev/null +++ b/src/main/scala/tokyo/meg/script/treewalker/values/RootValue.scala @@ -0,0 +1,75 @@ +package tokyo.meg.script.treewalker.values + +import scala.util.chaining._ + +object RootValue extends HasSingletonRoot[RootValue]: + val singletonRoot = RootValue() + + initialize() + + import Value._ + + singletonRoot.attributes.addAll: + Array( + "Root" -> RootValue.singletonRoot, + "Number" -> NumberValue.singletonRoot, + "Int" -> IntRoot.singletonRoot, + "Real" -> RealRoot.singletonRoot, + "Array" -> ArrayValue.singletonRoot, + "String" -> StringRoot.singletonRoot, + "List" -> ListRoot.singletonRoot, + "Empty" -> EmptyRoot.singletonRoot, + "Reader" -> ReaderRoot.singletonRoot, + "Writer" -> WriterRoot.singletonRoot, + "System" -> SystemRoot.singletonRoot, + "true" -> IntValue(1), + "false" -> IntValue(0), + "is" -> is, + "instanceOf" -> biFunction(instanceOf), + "input" -> input, + "print" -> _print, + "println" -> _println, + "toString" -> function(_.toString().pipe(StringValue(_))), + "==" -> is, + ".:" -> compose, + ":." -> andThen, + "-<<" -> applyToLeft, + ">>-" -> applyToRight + ) + + def is: Value = biFunction: (a, b) => + IntValue(if (a eq b) 1 else 0) + + @annotation.tailrec + def instanceOf(a: Value, b: Value): Value = + if a.eq(b) || b.eq(RootValue.singletonRoot) + then IntValue(1) + else + a match + case RootValue() => IntValue(0) + case _ => instanceOf(a.parent, b) + + def input: Value = function: message => + print(message) + + ReaderRoot.singletonRoot.getLine(ReaderValue(() => System.in, "UTF-8")) + + def _print: Value = + WriterRoot.singletonRoot._print(WriterValue(() => System.out, "UTF-8")) + + def _println: Value = + WriterRoot.singletonRoot._println(WriterValue(() => System.out, "UTF-8")) + + def compose: Value = biFunction: (a, b) => + a compose b + + def andThen: Value = biFunction: (a, b) => + a andThen b + + def applyToLeft: Value = biFunction: (a, b) => + a(b) + + def applyToRight: Value = biFunction: (a, b) => + b(a) + +final private case class RootValue() extends Value diff --git a/src/main/scala/tokyo/meg/script/treewalker/values/StringRoot.scala b/src/main/scala/tokyo/meg/script/treewalker/values/StringRoot.scala new file mode 100644 index 0000000..bdf1a01 --- /dev/null +++ b/src/main/scala/tokyo/meg/script/treewalker/values/StringRoot.scala @@ -0,0 +1,88 @@ +package tokyo.meg.script.treewalker.values + +import scala.collection.mutable._ +import scala.util.chaining._ + +object StringRoot extends HasSingletonRoot[StringRoot]: + val singletonRoot = StringRoot() + + initialize(ArrayValue) + +final private case class StringRoot() extends Value: + import Value._ + + setBody(function: + case EmptyValue(_) => "" + case value => value.toStringValue + ) + + attributes.addAll: + Array( + "<" -> lt, + ">" -> gt, + "<=" -> le, + ">=" -> ge, + "==" -> _eq, + "split" -> split, + "replace" -> replace, + "contains" -> contains, + "test" -> test, + "toList" -> toList, + "toCharCodes" -> toCharCodes, + "fromCharCodes" -> fromCharCodes + ) + + private val I = IntValue + private val L = ListValue + private val R = RealValue + private val S = StringValue + + def lt: Value = biFunction: + case (S(a), b) => a < b._toString + case _ => EmptyValue() + + def gt: Value = biFunction: + case (S(a), b) => a > b._toString + case _ => EmptyValue() + + def le: Value = biFunction: + case (S(a), b) => a <= b._toString + case _ => EmptyValue() + + def ge: Value = biFunction: + case (S(a), b) => a >= b._toString + case _ => EmptyValue() + + def _eq: Value = biFunction: + case (S(a), b) => a == b._toString + case _ => EmptyValue() + + def split: Value = biFunction: + case (S(a), b) => a.split(b._toString).map(S(_)) + case _ => EmptyValue() + + def replace: Value = triFunction: + case (S(a), b, c) => a.replaceAll(b._toString, c._toString) + case _ => EmptyValue() + + def contains: Value = biFunction: + case (S(a), b) => a.contains(b._toString) + case _ => EmptyValue() + + def test: Value = biFunction: + case (S(a), b) => a.matches(b._toString) + case _ => EmptyValue() + + def toList: Value = function: + case S(a) => a.toArray.map(_.toString()).map(StringValue(_)) + case _ => EmptyValue() + + def toCharCodes: Value = function: + case S(a) => a.toArray.map(_.toLong).map(IntValue(_)) + case _ => EmptyValue() + + def fromCharCodes: Value = function: + case I(a) => a.toChar.toString() + case R(a) => a.toChar.toString() + case L(a) => String(a.map(toIntValue).map(_.value.toChar).toArray) + case _ => EmptyValue() diff --git a/src/main/scala/tokyo/meg/script/treewalker/values/StringValue.scala b/src/main/scala/tokyo/meg/script/treewalker/values/StringValue.scala new file mode 100644 index 0000000..d826ee2 --- /dev/null +++ b/src/main/scala/tokyo/meg/script/treewalker/values/StringValue.scala @@ -0,0 +1,13 @@ +package tokyo.meg.script.treewalker.values + +final case class StringValue(val value: String) extends Value: + outer = RootValue.singletonRoot + parent = StringRoot.singletonRoot + + setBody(ArrayValue.singletonRoot.concat(this)) + + override def toString(): String = + value + +implicit inline final def toStringValue(value: String | Char): StringValue = + StringValue(value.toString()) diff --git a/src/main/scala/tokyo/meg/script/treewalker/values/SystemRoot.scala b/src/main/scala/tokyo/meg/script/treewalker/values/SystemRoot.scala new file mode 100644 index 0000000..753192b --- /dev/null +++ b/src/main/scala/tokyo/meg/script/treewalker/values/SystemRoot.scala @@ -0,0 +1,34 @@ +package tokyo.meg.script.treewalker.values + +import java.io.PrintStream +import java.nio.charset._ + +import scala.util.chaining._ + +object SystemRoot extends HasSingletonRoot[SystemRoot]: + val singletonRoot = SystemRoot() + + initialize() + +private final case class SystemRoot() extends Value: + import Value._ + + attributes.addAll( + Array( + "in" -> in, + "out" -> out, + "err" -> err + ) + ) + + def in: Value = + ReaderValue(() => System.in, "UTF-8").setBody: encoding => + ReaderValue(() => System.in, encoding._toString) + + def out: Value = + WriterValue(() => System.out, "UTF-8").setBody: encoding => + WriterValue(() => System.out, encoding._toString) + + def err: Value = + WriterValue(() => System.err, "UTF-8").setBody: encoding => + WriterValue(() => System.err, encoding._toString) diff --git a/src/main/scala/tokyo/meg/script/treewalker/values/SystemValue.scala b/src/main/scala/tokyo/meg/script/treewalker/values/SystemValue.scala new file mode 100644 index 0000000..c3ecd6c --- /dev/null +++ b/src/main/scala/tokyo/meg/script/treewalker/values/SystemValue.scala @@ -0,0 +1,5 @@ +package tokyo.meg.script.treewalker.values + +final case class SystemValue() extends Value: + outer = RootValue.singletonRoot + parent = SystemRoot.singletonRoot diff --git a/src/main/scala/tokyo/meg/script/treewalker/values/Value.scala b/src/main/scala/tokyo/meg/script/treewalker/values/Value.scala new file mode 100644 index 0000000..7f6b62c --- /dev/null +++ b/src/main/scala/tokyo/meg/script/treewalker/values/Value.scala @@ -0,0 +1,96 @@ +package tokyo.meg.script.treewalker.values + +import scala.collection.mutable._ +import scala.util.chaining._ + +abstract class Value: + var parent: Value = null + var outer: Value = null + var body: Value => Value = _ => this + val attributes: Map[String, Value] = Map() + + def setBody[T <: Value](value: Value => T): Value = + this.tap(_.body = value) + + def get: (Value => Value) => String => Value = + operate(_ => value => _ => value, _ => _ => EmptyValue()) + + infix def define(value: Value)(name: String): Value = + value.tap(_ => attributes.update(name, value)) + + def remove: (Value => Value) => String => Value = + operate(self => _ => self.remove, _ => _ => EmptyValue()) + + def remove(name: String): Value = + attributes.remove(name) match + case Some(value) => value + case None => EmptyValue() + + @annotation.tailrec + private def operate( + ifExists: Value => Value => String => Value, + ifRoot: Value => String => Value + )(target: Value => Value)(name: String): Value = + attributes get name match + case Some(value) => ifExists(this)(value)(name) + case None => + this match + case RootValue() => ifRoot(this)(name) + case _ => + target(this) match + case null => EmptyValue() + case value => value.operate(ifExists, ifRoot)(target)(name) + + final inline def isTruthy: Boolean = + !(this(this) eq this) + + final inline def apply(value: Value): Value = + this body value + + def ==(other: Value): Boolean = + method("==")(other).isTruthy + + final inline def method: String => Value = + this.get(_.parent)(_)(this) + + inline def toStringValue: Value = + method("toString") + + inline def _toString: String = + toStringValue.toString() + + override def toString(): String = + if attributes.size == 0 + then "{}" + else + attributes + .map((name, value) => + (value match + case StringValue(value) => s"\"$value\"" + case _ => if (value eq this) name else value + ).pipe(s => s"$name = $s") + ) + .mkString("; ") + .pipe(s => s"{ $s }") + +object Value: + final def function(body: Value => Value): Value = + AnyValue().tap(_.setBody(body)) + + final def biFunction(f: (Value, Value) => Value): Value = + function(left => function(f(left, _))) + + final def triFunction(f: (Value, Value, Value) => Value): Value = + function(left => biFunction(f(left, _, _))) + +implicit inline def toFunction(value: Value): Value => Value = + value.body + +implicit inline def toValue(body: Value => Value): Value = + Value.function(body) + +implicit inline def toValue(f: (Value, Value) => Value): Value = + Value.biFunction(f) + +final inline def toValue(f: (Value, Value, Value) => Value): Value = + Value.triFunction(f) diff --git a/src/main/scala/tokyo/meg/script/treewalker/values/WriterRoot.scala b/src/main/scala/tokyo/meg/script/treewalker/values/WriterRoot.scala new file mode 100644 index 0000000..1bd4c29 --- /dev/null +++ b/src/main/scala/tokyo/meg/script/treewalker/values/WriterRoot.scala @@ -0,0 +1,61 @@ +package tokyo.meg.script.treewalker.values + +import java.io._ +import java.nio.charset._ + +import scala.util.chaining._ + +object WriterRoot extends HasSingletonRoot[WriterRoot]: + val singletonRoot = WriterRoot() + + initialize() + +private final case class WriterRoot() extends Value: + import Value._ + + setBody: path => + val file = File(path._toString.toString()) + val F: (File, Boolean) => OutputStream = FileOutputStream(_, _) + val W = WriterValue + + W(() => F(file, false), "UTF-8").setBody: append => + W(() => F(file, append.isTruthy), "UTF-8").setBody: encoding => + W(() => F(file, append.isTruthy), encoding._toString.toString()) + + attributes.addAll: + Array( + "close" -> close, + "print" -> _print, + "println" -> _println, + "write" -> write + ) + + def close: Value = function: + case writer: WriterValue => writer.tap(_ => writer.stream.close()) + case _ => EmptyValue() + + def _print: Value = + output(writer => value => writer.charStream.print(value._toString)) + + def _println: Value = + output(writer => value => writer.charStream.println(value._toString)) + + def write: Value = + output: writer => + case IntValue(value) => writer.stream.write(value.toInt) + case RealValue(value) => writer.stream.write(value.toInt) + case StringValue(value) => writer.stream.write(value.getBytes()) + case ListValue(value) => value foreach writer(writer).body + case _ => () + + private def output(f: WriterValue => Value => Any): Value = function: + case writer @ WriterValue(_, _) => + def g: Value = function: + _.tap: + try f(writer)(_) + catch e => _ => () + .pipe(_ => g) + + g + + case _ => EmptyValue() diff --git a/src/main/scala/tokyo/meg/script/treewalker/values/WriterValue.scala b/src/main/scala/tokyo/meg/script/treewalker/values/WriterValue.scala new file mode 100644 index 0000000..f4501d0 --- /dev/null +++ b/src/main/scala/tokyo/meg/script/treewalker/values/WriterValue.scala @@ -0,0 +1,27 @@ +package tokyo.meg.script.treewalker.values + +import java.io._ +import java.nio.charset._ + +import scala.util.chaining._ + +final case class WriterValue( + val streamOpener: () => OutputStream, + val encoding: String +) extends Value: + outer = RootValue.singletonRoot + parent = WriterRoot.singletonRoot + + private val charset = Charset.forName(encoding) + private var _stream: Option[OutputStream] = None + private var _charStream: Option[PrintStream] = None + + def stream: OutputStream = + _stream match + case None => streamOpener().tap(s => _stream = Some(s)) + case Some(stream) => stream + + def charStream: PrintStream = + _charStream match + case None => PrintStream(stream, true, charset) + case Some(stream) => stream diff --git a/src/main/scala/tokyo/meg/script/util/all.scala b/src/main/scala/tokyo/meg/script/util/all.scala new file mode 100644 index 0000000..d574779 --- /dev/null +++ b/src/main/scala/tokyo/meg/script/util/all.scala @@ -0,0 +1,4 @@ +package tokyo.meg.script.util + +final def all[T](e: T)(f: T => Boolean*): Boolean = + f.forall(_(e)) diff --git a/src/main/scala/tokyo/meg/script/util/any.scala b/src/main/scala/tokyo/meg/script/util/any.scala new file mode 100644 index 0000000..68a970d --- /dev/null +++ b/src/main/scala/tokyo/meg/script/util/any.scala @@ -0,0 +1,4 @@ +package tokyo.meg.script.util + +final def any[T](e: T)(f: T => Boolean*): Boolean = + f.exists(_(e)) diff --git a/src/main/scala/tokyo/meg/script/util/buildStringWhile.scala b/src/main/scala/tokyo/meg/script/util/buildStringWhile.scala new file mode 100644 index 0000000..ccb38bd --- /dev/null +++ b/src/main/scala/tokyo/meg/script/util/buildStringWhile.scala @@ -0,0 +1,21 @@ +package tokyo.meg.script.util + +import scala.collection.mutable._ + +@annotation.tailrec +def buildStringWhile( + builder: StringBuilder +)(condition: => Boolean)(f: => Any): StringBuilder = + if condition + then + builder append f + buildStringWhile(builder)(condition)(f) + else builder + +def buildStringWhile(condition: => Boolean)(f: => Any): StringBuilder = + buildStringWhile(StringBuilder())(condition)(f) + +def buildStringWhile(initialChar: Any)(condition: => Boolean)( + f: => Any +): StringBuilder = + buildStringWhile(StringBuilder() append initialChar)(condition)(f)