diff --git a/src/main/scala/esmeta/analyzer/AbsTransfer.scala b/src/main/scala/esmeta/analyzer/AbsTransfer.scala index b0edc17d42..3b602eeae5 100644 --- a/src/main/scala/esmeta/analyzer/AbsTransfer.scala +++ b/src/main/scala/esmeta/analyzer/AbsTransfer.scala @@ -385,10 +385,10 @@ trait AbsTransferDecl { self: Analyzer => f <- transfer(from) t <- transfer(to) } yield v.substring(f, t) - case ETrim(expr, leading, trailing) => + case ETrim(expr, isStarting) => for { v <- transfer(expr) - } yield v.trim(leading, trailing) + } yield v.trim(isStarting) case ERef(ref) => for { rv <- transfer(ref) diff --git a/src/main/scala/esmeta/analyzer/domain/value/ValueBasicDomain.scala b/src/main/scala/esmeta/analyzer/domain/value/ValueBasicDomain.scala index e18c338e09..8a3e53d567 100644 --- a/src/main/scala/esmeta/analyzer/domain/value/ValueBasicDomain.scala +++ b/src/main/scala/esmeta/analyzer/domain/value/ValueBasicDomain.scala @@ -418,16 +418,10 @@ trait ValueBasicDomainDecl { self: Self => else if (t.isValidInt) apply(s.substring(f.toInt, t.toInt)) else Bot case _ => Bot - def trim(leading: Boolean, trailing: Boolean): Elem = elem.getSingle match - case Many => exploded("ETrim") - case One(Str(s)) => - apply( - if (leading && trailing) s.trim - else if (leading) s.replaceAll("^\\s+", "") - else if (trailing) s.replaceAll("\\s+$", "") - else s, - ) - case _ => Bot + def trim(isStarting: Boolean): Elem = elem.getSingle match + case Many => exploded("ETrim") + case One(Str(s)) => apply(trimString(s, isStarting, cfg.esParser)) + case _ => Bot def clamp(lower: Elem, upper: Elem): Elem = (elem.getSingle, lower.getSingle, upper.getSingle) match case (Zero, _, _) | (_, Zero, _) | (_, _, Zero) => Bot diff --git a/src/main/scala/esmeta/analyzer/domain/value/ValueDomain.scala b/src/main/scala/esmeta/analyzer/domain/value/ValueDomain.scala index e77e57481d..a2da915c37 100644 --- a/src/main/scala/esmeta/analyzer/domain/value/ValueDomain.scala +++ b/src/main/scala/esmeta/analyzer/domain/value/ValueDomain.scala @@ -152,7 +152,7 @@ trait ValueDomainDecl { self: Self => def duplicated(st: AbsState): Elem def substring(from: Elem): Elem def substring(from: Elem, to: Elem): Elem - def trim(leading: Boolean, trailing: Boolean): Elem + def trim(isStarting: Boolean): Elem def clamp(lower: Elem, upper: Elem): Elem def isArrayIndex: Elem diff --git a/src/main/scala/esmeta/analyzer/domain/value/ValueTypeDomain.scala b/src/main/scala/esmeta/analyzer/domain/value/ValueTypeDomain.scala index 5daf3beaea..baf9931674 100644 --- a/src/main/scala/esmeta/analyzer/domain/value/ValueTypeDomain.scala +++ b/src/main/scala/esmeta/analyzer/domain/value/ValueTypeDomain.scala @@ -371,7 +371,7 @@ trait ValueTypeDomainDecl { self: Self => def duplicated(st: AbsState): Elem = boolTop def substring(from: Elem): Elem = strTop def substring(from: Elem, to: Elem): Elem = strTop - def trim(leading: Boolean, trailing: Boolean): Elem = strTop + def trim(isStarting: Boolean): Elem = strTop def clamp(lower: Elem, upper: Elem): Elem = val xty = elem.ty.math val lowerTy = lower.ty.math diff --git a/src/main/scala/esmeta/compiler/Compiler.scala b/src/main/scala/esmeta/compiler/Compiler.scala index 8c37acf122..3dada68f0c 100644 --- a/src/main/scala/esmeta/compiler/Compiler.scala +++ b/src/main/scala/esmeta/compiler/Compiler.scala @@ -491,7 +491,12 @@ class Compiler( to.map(compile(fb, _)), ) case TrimExpression(expr, leading, trailing) => - ETrim(compile(fb, expr), leading, trailing) + (leading, trailing) match { + case (false, false) => compile(fb, expr) + case (true, false) => ETrim(compile(fb, expr), true) + case (false, true) => ETrim(compile(fb, expr), false) + case (true, true) => ETrim(ETrim(compile(fb, expr), true), false) + } case NumberOfExpression(ReferenceExpression(ref)) => toStrERef(compile(fb, ref), "length") case NumberOfExpression(expr) => diff --git a/src/main/scala/esmeta/interpreter/Interpreter.scala b/src/main/scala/esmeta/interpreter/Interpreter.scala index 209d544c28..69cf7d5fef 100644 --- a/src/main/scala/esmeta/interpreter/Interpreter.scala +++ b/src/main/scala/esmeta/interpreter/Interpreter.scala @@ -252,18 +252,8 @@ class Interpreter( case Math(n) if s.length < n => s.substring(f) case v => s.substring(f, v.asInt), )) - case ETrim(expr, leading, trailing) => - val sb = new java.lang.StringBuilder - val arr = eval(expr).asStr.codePoints.toArray - val cps = esParser.WhiteSpaceCPs ++ esParser.LineTerminatorCPs - def find(i: Int, next: Int => Int): Int = - if (i < 0 || i >= arr.length) i - else if (cps contains arr(i)) find(next(i), next) - else i - val start = if (leading) find(0, _ + 1) else 0 - val end = if (trailing) find(arr.length - 1, _ - 1) else arr.length - arr.slice(start, end + 1).foreach(sb.appendCodePoint) - Str(sb.toString) + case ETrim(expr, isStarting) => + Str(trimString(eval(expr).asStr, isStarting, esParser)) case ERef(ref) => st(eval(ref)) case EUnary(uop, expr) => diff --git a/src/main/scala/esmeta/ir/Expr.scala b/src/main/scala/esmeta/ir/Expr.scala index dab4004205..cd51416d60 100644 --- a/src/main/scala/esmeta/ir/Expr.scala +++ b/src/main/scala/esmeta/ir/Expr.scala @@ -18,7 +18,7 @@ case class ESourceText(expr: Expr) extends Expr case class EYet(msg: String) extends Expr case class EContains(list: Expr, expr: Expr) extends Expr case class ESubstring(expr: Expr, from: Expr, to: Option[Expr]) extends Expr -case class ETrim(expr: Expr, leading: Boolean, trailing: Boolean) extends Expr +case class ETrim(expr: Expr, isStarting: Boolean) extends Expr case class ERef(ref: Ref) extends Expr case class EUnary(uop: UOp, expr: Expr) extends Expr case class EBinary(bop: BOp, left: Expr, right: Expr) extends Expr diff --git a/src/main/scala/esmeta/ir/util/Parser.scala b/src/main/scala/esmeta/ir/util/Parser.scala index de4d7d75f2..9efca7db91 100644 --- a/src/main/scala/esmeta/ir/util/Parser.scala +++ b/src/main/scala/esmeta/ir/util/Parser.scala @@ -130,12 +130,10 @@ trait Parsers extends TyParsers { case l ~ e => EContains(l, e) } | "(" ~ "substring" ~> expr ~ expr ~ opt(expr) <~ ")" ^^ { case e ~ f ~ t => ESubstring(e, f, t) - } | "(" ~ "trim-start" ~> expr <~ ")" ^^ { - case e => ETrim(e, true, false) - } | "(" ~ "trim-end" ~> expr <~ ")" ^^ { - case e => ETrim(e, false, true) - } | "(" ~ "trim" ~> expr <~ ")" ^^ { - case e => ETrim(e, true, true) + } | "(" ~ "trim" ~ ">" ~> expr <~ ")" ^^ { + case e => ETrim(e, true) + } | "(" ~ "trim" ~> expr <~ "<" ~ ")" ^^ { + case e => ETrim(e, false) } | "(" ~> uop ~ expr <~ ")" ^^ { case u ~ e => EUnary(u, e) } | "(" ~> bop ~ expr ~ expr <~ ")" ^^ { diff --git a/src/main/scala/esmeta/ir/util/Stringifier.scala b/src/main/scala/esmeta/ir/util/Stringifier.scala index 9a0916c669..e96b644ffb 100644 --- a/src/main/scala/esmeta/ir/util/Stringifier.scala +++ b/src/main/scala/esmeta/ir/util/Stringifier.scala @@ -141,12 +141,9 @@ class Stringifier(detail: Boolean, location: Boolean) { app >> "(substring " >> expr >> " " >> from to.map(app >> " " >> _) app >> ")" - case ETrim(expr, leading, trailing) => - (leading, trailing) match - case (true, true) => app >> "(trim " >> expr >> ")" - case (true, false) => app >> "(trim-start " >> expr >> ")" - case (false, true) => app >> "(trim-end " >> expr >> ")" - case (false, false) => app >> expr + case ETrim(expr, isStarting) => + if (isStarting) app >> "(trim > " >> expr >> ")" + else app >> "(trim " >> expr >> " <)" case ERef(ref) => app >> ref case EUnary(uop, expr) => diff --git a/src/main/scala/esmeta/ir/util/UnitWalker.scala b/src/main/scala/esmeta/ir/util/UnitWalker.scala index ad984806fc..d0cc2ab089 100644 --- a/src/main/scala/esmeta/ir/util/UnitWalker.scala +++ b/src/main/scala/esmeta/ir/util/UnitWalker.scala @@ -79,8 +79,8 @@ trait UnitWalker extends BasicUnitWalker { walk(list); walk(elem) case ESubstring(expr, from, to) => walk(expr); walk(from); walkOpt(to, walk) - case ETrim(expr, leading, trailing) => - walk(expr); walk(leading); walk(trailing) + case ETrim(expr, isStarting) => + walk(expr); walk(isStarting) case ERef(ref) => walk(ref) case EUnary(uop, expr) => diff --git a/src/main/scala/esmeta/ir/util/Walker.scala b/src/main/scala/esmeta/ir/util/Walker.scala index 81ce997e50..f1c3e4bba6 100644 --- a/src/main/scala/esmeta/ir/util/Walker.scala +++ b/src/main/scala/esmeta/ir/util/Walker.scala @@ -87,8 +87,8 @@ trait Walker extends BasicWalker { EContains(walk(list), walk(elem)) case ESubstring(expr, from, to) => ESubstring(walk(expr), walk(from), walkOpt(to, walk)) - case ETrim(expr, leading, trailing) => - ETrim(walk(expr), walk(leading), walk(trailing)) + case ETrim(expr, isStarting) => + ETrim(walk(expr), walk(isStarting)) case ERef(ref) => ERef(walk(ref)) case EUnary(uop, expr) => diff --git a/src/main/scala/esmeta/state/package.scala b/src/main/scala/esmeta/state/package.scala index f5440833dd..2390f06480 100644 --- a/src/main/scala/esmeta/state/package.scala +++ b/src/main/scala/esmeta/state/package.scala @@ -3,6 +3,7 @@ package esmeta.state import esmeta.state.util.* import esmeta.util.BaseUtils.* import esmeta.ir.Global +import esmeta.parser.ESParser import java.math.MathContext.{UNLIMITED, DECIMAL128} /** IR state elements */ @@ -65,6 +66,19 @@ val NEG_INF = Infinity(pos = false) val NUMBER_POS_INF = Number(Double.PositiveInfinity) val NUMBER_NEG_INF = Number(Double.NegativeInfinity) +def trimString(x: String, isStarting: Boolean, esParser: ESParser): String = + val sb = new java.lang.StringBuilder + val arr = x.codePoints.toArray + val cps = esParser.WhiteSpaceCPs ++ esParser.LineTerminatorCPs + def find(i: Int, next: Int => Int): Int = + if (i < 0 || i >= arr.length) i + else if (cps contains arr(i)) find(next(i), next) + else i + val start = if (isStarting) find(0, _ + 1) else 0 + val end = if (isStarting) arr.length else find(arr.length - 1, _ - 1) + arr.slice(start, end + 1).foreach(sb.appendCodePoint) + sb.toString + /** conversion number to string */ def toStringHelper(x: Double, radix: Int = 10): String = { // get sign diff --git a/src/test/scala/esmeta/ir/IRTest.scala b/src/test/scala/esmeta/ir/IRTest.scala index 8bac5c3447..e7d784d7af 100644 --- a/src/test/scala/esmeta/ir/IRTest.scala +++ b/src/test/scala/esmeta/ir/IRTest.scala @@ -64,9 +64,9 @@ object IRTest { lazy val contains = EContains(xExpr, xExpr) lazy val substring = ESubstring(xExpr, xExpr, None) lazy val substringTo = ESubstring(xExpr, xExpr, Some(xExpr)) - lazy val trim = ETrim(xExpr, true, true) - lazy val trimStart = ETrim(xExpr, true, false) - lazy val trimEnd = ETrim(xExpr, false, true) + lazy val trim = ETrim(ETrim(xExpr, true), false) + lazy val trimStart = ETrim(xExpr, true) + lazy val trimEnd = ETrim(xExpr, false) lazy val xExpr = ERef(x) lazy val yExpr = ERef(y) lazy val unary = EUnary(UOp.Neg, xExpr) diff --git a/src/test/scala/esmeta/ir/JsonTinyTest.scala b/src/test/scala/esmeta/ir/JsonTinyTest.scala index a074162070..c8a0ec53f4 100644 --- a/src/test/scala/esmeta/ir/JsonTinyTest.scala +++ b/src/test/scala/esmeta/ir/JsonTinyTest.scala @@ -105,9 +105,9 @@ class JsonTinyTest extends IRTest { contains -> "(contains x x)", substring -> "(substring x x)", substringTo -> "(substring x x x)", - trim -> "(trim x)", - trimStart -> "(trim-start x)", - trimEnd -> "(trim-end x)", + trim -> "(trim (trim > x) <)", + trimStart -> "(trim > x)", + trimEnd -> "(trim x <)", xExpr -> "x", unary -> "(- x)", binary -> "(+ x x)", diff --git a/src/test/scala/esmeta/ir/StringifyTinyTest.scala b/src/test/scala/esmeta/ir/StringifyTinyTest.scala index 72fad6b871..c8f86c4a94 100644 --- a/src/test/scala/esmeta/ir/StringifyTinyTest.scala +++ b/src/test/scala/esmeta/ir/StringifyTinyTest.scala @@ -119,9 +119,9 @@ class StringifyTinyTest extends IRTest { contains -> "(contains x x)", substring -> "(substring x x)", substringTo -> "(substring x x x)", - trim -> "(trim x)", - trimStart -> "(trim-start x)", - trimEnd -> "(trim-end x)", + trim -> "(trim (trim > x) <)", + trimStart -> "(trim > x)", + trimEnd -> "(trim x <)", xExpr -> "x", unary -> "(- x)", binary -> "(+ x x)",