harn-vm 0.10.18

Async bytecode virtual machine for the Harn programming language
Documentation
<tool_call>
edit({
  "action": "create",
  "path": "src/main/scala/rulekit/Rule.scala",
  "overwrite": true,
  "content": <<EOF
package rulekit

sealed trait Rule

final case class Predicate(value: String) extends Rule
final case class AllOf(rules: List[Rule]) extends Rule
final case class AnyOf(rules: List[Rule]) extends Rule
final case class NoneOf(rule: Rule) extends Rule
EOF
}
</tool_call>

<tool_call>
edit({
  "action": "create",
  "path": "src/main/scala/rulekit/RuleSerializer.scala",
  "overwrite": true,
  "content": <<EOF
package rulekit

import scala.util.matching.Regex

object RuleSerializer {

  /** Convert a Rule to a simple JSON string. */
  def toJson(rule: Rule): String = rule match {
    case Predicate(v) =>
      s"""{"type":"Predicate","value":${escapeString(v)}}"""
    case AllOf(rs) =>
      val inner = rs.map(toJson).mkString(",")
      s"""{"type":"AllOf","rules":[$inner]}"""
    case AnyOf(rs) =>
      val inner = rs.map(toJson).mkString(",")
      s"""{"type":"AnyOf","rules":[$inner]}"""
    case NoneOf(r) =>
      s"""{"type":"NoneOf","rule":${toJson(r)}}"""
  }

  /** Parse a JSON string produced by toJson back into a Rule. */
  def fromJson(json: String): Rule = {
    // simple helpers
    def extractField(name: String, src: String): Option[String] = {
      val pattern = new Regex(s""""$name":"([^"]*)"""")
      pattern.findFirstMatchIn(src).map(_.group(1))
    }

    def extractObjectField(name: String, src: String): Option[String] = {
      // matches "name":{...}
      val pattern = new Regex(s""""$name":\\{")
      val start = pattern.findFirstMatchIn(src).map(_.end - 1)
      start.flatMap { idx =>
        var depth = 0
        var i = idx
        while (i < src.length && (depth > 0 || src(i) != '}')) {
          if (src(i) == '{') depth += 1
          else if (src(i) == '}') depth -= 1
          i += 1
        }
        if (i < src.length) Some(src.substring(idx, i + 1)) else None
      }
    }

    def extractArrayField(name: String, src: String): List[String] = {
      // matches "name":[ ... ]
      val pattern = new Regex(s""""$name":\\[")
      pattern.findFirstMatchIn(src) match {
        case None => Nil
        case Some(m) =>
          var i = m.end
          var depth = 0
          val sb = new StringBuilder
          while (i < src.length && (depth > 0 || src(i) != ']')) {
            val ch = src(i)
            if (ch == '[') depth += 1
            else if (ch == ']') depth -= 1
            sb.append(ch)
            i += 1
          }
          // now sb contains the inner content (may be empty)
          splitTopLevel(sb.toString())
      }
    }

    def splitTopLevel(s: String): List[String] = {
      // split by commas that are not inside braces or brackets
      val res = scala.collection.mutable.ListBuffer.empty[String]
      var depthBrace = 0
      var depthBracket = 0
      var start = 0
      for (i <- 0 until s.length) {
        s(i) match {
          case '{' => depthBrace += 1
          case '}' => depthBrace -= 1
          case '[' => depthBracket += 1
          case ']' => depthBracket -= 1
          case ',' if depthBrace == 0 && depthBracket == 0 =>
            res += s.substring(start, i).trim
            start = i + 1
          case _ => ()
        }
      }
      if (start < s.length) res += s.substring(start).trim
      res.filter(_.nonEmpty).toList
    }

    def unescapeString(s: String): String = {
      // simple unescape for our escaped strings
      s.replace("\\\"", "\"").replace("\\\\", "\\")
    }

    // Determine type
    extractField("type", json) match {
      case Some("Predicate") =>
        val v = extractField("value", json).getOrElse("")
        Predicate(unescapeString(v))
      case Some("AllOf") =>
        val elems = extractArrayField("rules", json)
        AllOf(elems.map(fromJson))
      case Some("AnyOf") =>
        val elems = extractArrayField("rules", json)
        AnyOf(elems.map(fromJson))
      case Some("NoneOf") =>
        extractObjectField("rule", json) match {
          case Some(inner) => NoneOf(fromJson(inner))
          case None        => throw new IllegalArgumentException("Invalid NoneOf JSON")
        }
      case Some(other) =>
        throw new IllegalArgumentException(s"Unknown rule type: $other")
      case None =>
        throw new IllegalArgumentException("No type field found")
    }
  }

  private def escapeString(s: String): String = {
    // Very simple escaping for double quotes and backslashes
    val escaped = s.replace("\\", "\\\\").replace("\"", "\\\"")
    s"\"$escaped\""
  }
}
EOF
}
</tool_call>

<tool_call>
edit({
  "action": "create",
  "path": "src/test/scala/rulekit/RuleSerializerSpec.scala",
  "overwrite": true,
  "content": <<EOF
package rulekit

import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.should.Matchers

class RuleSerializerSpec extends AnyFunSuite with Matchers {

  test("Predicate round‑trip serialization") {
    val rule = Predicate("test")
    val json = RuleSerializer.toJson(rule)
    val parsed = RuleSerializer.fromJson(json)
    parsed shouldBe rule
  }

  test("AllOf round‑trip serialization") {
    val rule = AllOf(List(Predicate("a"), Predicate("b")))
    val json = RuleSerializer.toJson(rule)
    val parsed = RuleSerializer.fromJson(json)
    parsed shouldBe rule
  }

  test("AnyOf round‑trip serialization") {
    val rule = AnyOf(List(Predicate("x"), Predicate("y"), Predicate("z")))
    val json = RuleSerializer.toJson(rule)
    val parsed = RuleSerializer.fromJson(json)
    parsed shouldBe rule
  }

  test("NoneOf round‑trip serialization") {
    val rule = NoneOf(Predicate("solo"))
    val json = RuleSerializer.toJson(rule)
    val parsed = RuleSerializer.fromJson(json)
    parsed shouldBe rule
  }

  test("Nested structures round‑trip") {
    val rule = AllOf(List(
      Predicate("p1"),
      AnyOf(List(
        Predicate("p2"),
        NoneOf(AllOf(List(Predicate("p3"), Predicate("p4"))))
      ),
      Predicate("p5")
    ))
    val json = RuleSerializer.toJson(rule)
    val parsed = RuleSerializer.fromJson(json)
    parsed shouldBe rule
  }
}
EOF
}
</tool_call>

<tool_call>
run({
  "intent": "test",
  "filter": "",
  "reason": "Run all tests to verify serialization"
})
</tool_call>