diff --git a/atlas-core/src/main/scala/com/netflix/atlas/core/stacklang/Interpreter.scala b/atlas-core/src/main/scala/com/netflix/atlas/core/stacklang/Interpreter.scala index cc0f2fc00..8098a3066 100644 --- a/atlas-core/src/main/scala/com/netflix/atlas/core/stacklang/Interpreter.scala +++ b/atlas-core/src/main/scala/com/netflix/atlas/core/stacklang/Interpreter.scala @@ -113,21 +113,25 @@ case class Interpreter(vocabulary: List[Word]) { if (s.program.isEmpty) s.context else execute(nextStep(s)) } - final def execute(program: List[Any], context: Context, unfreeze: Boolean = true): Context = { + final def executeProgram( + program: List[Any], + context: Context, + unfreeze: Boolean = true + ): Context = { val result = execute(Step(program, context.incrementCallDepth)).decrementCallDepth if (unfreeze) result.unfreeze else result } - final def execute(program: List[Any]): Context = { - execute(program, Context(this, Nil, Map.empty)) + final def executeProgram(program: List[Any]): Context = { + executeProgram(program, Context(this, Nil, Map.empty)) } - final def execute(program: String): Context = { - execute(splitAndTrim(program)) - } - - final def execute(program: String, vars: Map[String, Any], features: Features): Context = { - execute(splitAndTrim(program), Context(this, Nil, vars, vars, features = features)) + final def execute( + program: String, + vars: Map[String, Any] = Map.empty, + features: Features = Features.STABLE + ): Context = { + executeProgram(splitAndTrim(program), Context(this, Nil, vars, vars, features = features)) } @scala.annotation.tailrec @@ -145,7 +149,7 @@ case class Interpreter(vocabulary: List[Word]) { } final def debug(program: List[Any]): List[Step] = { - debug(program, Context(this, Nil, Map.empty)) + debug(program, Context(this, Nil, Map.empty, features = Features.UNSTABLE)) } final def debug(program: String): List[Step] = { diff --git a/atlas-core/src/main/scala/com/netflix/atlas/core/stacklang/StandardVocabulary.scala b/atlas-core/src/main/scala/com/netflix/atlas/core/stacklang/StandardVocabulary.scala index 05b07106b..12918340b 100644 --- a/atlas-core/src/main/scala/com/netflix/atlas/core/stacklang/StandardVocabulary.scala +++ b/atlas-core/src/main/scala/com/netflix/atlas/core/stacklang/StandardVocabulary.scala @@ -57,7 +57,7 @@ object StandardVocabulary extends Vocabulary { override def matches(stack: List[Any]): Boolean = true override def execute(context: Context): Context = { - context.interpreter.execute(body, context, unfreeze = false) + context.interpreter.executeProgram(body, context, unfreeze = false) } override def summary: String = @@ -81,7 +81,7 @@ object StandardVocabulary extends Vocabulary { override def execute(context: Context): Context = { context.stack match { case (vs: List[?]) :: stack => - context.interpreter.execute(vs, context.copy(stack = stack), unfreeze = false) + context.interpreter.executeProgram(vs, context.copy(stack = stack), unfreeze = false) case _ => invalidStack } } @@ -198,7 +198,7 @@ object StandardVocabulary extends Vocabulary { context.stack match { case (f: List[?]) :: (vs: List[?]) :: stack => vs.reverse.foldLeft(context.copy(stack = stack)) { (c, v) => - c.interpreter.execute(f, c.copy(stack = v :: c.stack), unfreeze = false) + c.interpreter.executeProgram(f, c.copy(stack = v :: c.stack), unfreeze = false) } case _ => invalidStack } @@ -362,7 +362,8 @@ object StandardVocabulary extends Vocabulary { val init = context.copy(stack = stack) val res = vs.foldLeft(List.empty[Any] -> init) { case ((rs, c), v) => - val rc = c.interpreter.execute(f, c.copy(stack = v :: c.stack), unfreeze = false) + val rc = + c.interpreter.executeProgram(f, c.copy(stack = v :: c.stack), unfreeze = false) (rc.stack.head :: rs) -> rc.copy(stack = rc.stack.tail) } res._2.copy(stack = res._1.reverse :: res._2.stack) diff --git a/atlas-core/src/test/scala/com/netflix/atlas/core/stacklang/InterpreterSuite.scala b/atlas-core/src/test/scala/com/netflix/atlas/core/stacklang/InterpreterSuite.scala index 1ee739e48..38701662b 100644 --- a/atlas-core/src/test/scala/com/netflix/atlas/core/stacklang/InterpreterSuite.scala +++ b/atlas-core/src/test/scala/com/netflix/atlas/core/stacklang/InterpreterSuite.scala @@ -15,6 +15,7 @@ */ package com.netflix.atlas.core.stacklang +import com.netflix.atlas.core.util.Features import munit.FunSuite class InterpreterSuite extends FunSuite { @@ -40,28 +41,28 @@ class InterpreterSuite extends FunSuite { } test("empty") { - assertEquals(interpreter.execute(Nil), context(Nil)) + assertEquals(interpreter.executeProgram(Nil), context(Nil)) } test("push items") { - assertEquals(interpreter.execute(List("foo", "bar")), context(List("bar", "foo"))) + assertEquals(interpreter.executeProgram(List("foo", "bar")), context(List("bar", "foo"))) } test("execute word") { - assertEquals(interpreter.execute(List(":push-foo")), context(List("foo"))) + assertEquals(interpreter.executeProgram(List(":push-foo")), context(List("foo"))) } test("overloaded word") { - assertEquals(interpreter.execute(List(":overloaded")), context(List("one"))) + assertEquals(interpreter.executeProgram(List(":overloaded")), context(List("one"))) } test("overloaded word and some don't match") { - assertEquals(interpreter.execute(List(":overloaded2")), context(List("two"))) + assertEquals(interpreter.executeProgram(List(":overloaded2")), context(List("two"))) } test("word with no matches") { val e = intercept[IllegalStateException] { - interpreter.execute(List(":no-match")) + interpreter.executeProgram(List(":no-match")) } val expected = "no matches for word ':no-match' with stack [], candidates: [exception]" assertEquals(e.getMessage, expected) @@ -69,7 +70,7 @@ class InterpreterSuite extends FunSuite { test("using unstable word fails by default") { val e = intercept[IllegalStateException] { - interpreter.execute(List(":unstable")) + interpreter.executeProgram(List(":unstable")) } val expected = "to use :unstable enable unstable features" assertEquals(e.getMessage, expected) @@ -77,53 +78,56 @@ class InterpreterSuite extends FunSuite { test("unknown word") { val e = intercept[IllegalStateException] { - interpreter.execute(List("foo", ":unknown")) + interpreter.executeProgram(List("foo", ":unknown")) } assertEquals(e.getMessage, "unknown word ':unknown'") } test("unmatched closing paren") { val e = intercept[IllegalStateException] { - interpreter.execute(List(")")) + interpreter.executeProgram(List(")")) } assertEquals(e.getMessage, "unmatched closing parenthesis") } test("unmatched closing paren 2") { val e = intercept[IllegalStateException] { - interpreter.execute(List("(", ")", ")")) + interpreter.executeProgram(List("(", ")", ")")) } assertEquals(e.getMessage, "unmatched closing parenthesis") } test("unmatched opening paren") { val e = intercept[IllegalStateException] { - interpreter.execute(List("(")) + interpreter.executeProgram(List("(")) } assertEquals(e.getMessage, "unmatched opening parenthesis") } test("list") { val list = List("(", "1", ")") - assertEquals(interpreter.execute(list), context(List(List("1")))) + assertEquals(interpreter.executeProgram(list), context(List(List("1")))) } test("nested list") { val list = List("(", "1", "(", ")", ")") - assertEquals(interpreter.execute(list), context(List(List("1", "(", ")")))) + assertEquals(interpreter.executeProgram(list), context(List(List("1", "(", ")")))) } test("multiple lists") { val list = List("(", "1", ")", "(", "2", ")") - assertEquals(interpreter.execute(list), context(List(List("2"), List("1")))) + assertEquals(interpreter.executeProgram(list), context(List(List("2"), List("1")))) } test("debug") { + def createContext(stack: List[Any]): Context = { + Context(interpreter, stack, Map.empty, features = Features.UNSTABLE) + } val list = List("(", "1", ")", "(", "2", ")") val expected = List( - Interpreter.Step(list, Context(interpreter, Nil, Map.empty)), - Interpreter.Step(list.drop(3), Context(interpreter, List(List("1")), Map.empty)), - Interpreter.Step(Nil, Context(interpreter, List(List("2"), List("1")), Map.empty)) + Interpreter.Step(list, createContext(Nil)), + Interpreter.Step(list.drop(3), createContext(List(List("1")))), + Interpreter.Step(Nil, createContext(List(List("2"), List("1")))) ) assertEquals(interpreter.debug(list), expected) } diff --git a/atlas-webapi/src/main/scala/com/netflix/atlas/webapi/ExprApi.scala b/atlas-webapi/src/main/scala/com/netflix/atlas/webapi/ExprApi.scala index 2d8733050..554089797 100644 --- a/atlas-webapi/src/main/scala/com/netflix/atlas/webapi/ExprApi.scala +++ b/atlas-webapi/src/main/scala/com/netflix/atlas/webapi/ExprApi.scala @@ -30,6 +30,7 @@ import com.netflix.atlas.core.model.TimeSeriesExpr import com.netflix.atlas.core.stacklang.Context import com.netflix.atlas.core.stacklang.Interpreter import com.netflix.atlas.core.stacklang.Word +import com.netflix.atlas.core.util.Features import com.netflix.atlas.core.util.Strings import com.netflix.atlas.json.Json import com.netflix.atlas.pekko.CustomDirectives.* @@ -154,7 +155,7 @@ class ExprApi extends WebApi { // macros it alwasy returns true. This ensures the operation will actually be successful before // returning to a user. private def execWorks(interpreter: Interpreter, w: Word, ctxt: Context): Boolean = { - Try(interpreter.execute(List(s":${w.name}"), ctxt)).isSuccess + Try(interpreter.executeProgram(List(s":${w.name}"), ctxt)).isSuccess } private def matches(interpreter: Interpreter, w: Word, ctxt: Context): Boolean = { @@ -163,7 +164,7 @@ class ExprApi extends WebApi { private def processCompleteRequest(query: String, vocabName: String): HttpResponse = { val interpreter = newInterpreter(vocabName) - val result = interpreter.execute(query) + val result = interpreter.execute(query, features = Features.UNSTABLE) val candidates = interpreter.vocabulary.filter { w => matches(interpreter, w, result) @@ -185,7 +186,7 @@ class ExprApi extends WebApi { */ private def processQueriesRequest(expr: String, vocabName: String): HttpResponse = { val interpreter = newInterpreter(vocabName) - val result = interpreter.execute(expr) + val result = interpreter.execute(expr, features = Features.UNSTABLE) val exprs = result.stack.collect { case ModelExtractors.PresentationType(t) => t @@ -291,7 +292,7 @@ object ExprApi { } private def eval(interpreter: Interpreter, expr: String): List[StyleExpr] = { - interpreter.execute(expr).stack.collect { + interpreter.execute(expr, features = Features.UNSTABLE).stack.collect { case ModelExtractors.PresentationType(t) => t } }