Skip to content

Commit

Permalink
more work on parsing paths and constraints, refactor, rename
Browse files Browse the repository at this point in the history
  • Loading branch information
bjornregnell committed Apr 10, 2024
1 parent eb5aff9 commit 23e85ec
Show file tree
Hide file tree
Showing 10 changed files with 359 additions and 129 deletions.
89 changes: 0 additions & 89 deletions src/main/scala/00-zero-dep-extensions.scala

This file was deleted.

154 changes: 154 additions & 0 deletions src/main/scala/00-zero-dep-utils.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package reqt

object StringExtensions:
extension (s: String)
def p: Unit = println(s)

def toLines: Array[String] = s.split("\n")
def toWords: Array[String] = s.split(" ").map(_.trim).filter(_.nonEmpty)

def firstWord: String = s.takeWhile(_.isLetter)

def deCapitalize: String = s.take(1).toLowerCase ++ s.drop(1)

def skipIndent: String = s.dropWhile(ch => ch.isSpaceChar || ch == '\t')
def skipFirstWord: String = s.dropWhile(ch => !(ch.isSpaceChar || ch == '\t'))
def skipFirstToken: String = s.skipIndent.skipFirstWord.trim

def dropQuotes: String =
s.stripPrefix("\"").stripPrefix("\"\"").stripSuffix("\"").stripSuffix("\"\"")

def splitEscaped(c: Char, esc: Char): Array[String] =
if s.isEmpty then Array(s) else
val result = collection.mutable.ArrayBuffer.empty[String]
var prev = 0
var i = 0
var isInsideEscape = false
while i < s.length do
if s(i) == esc then
isInsideEscape = !isInsideEscape
else if s(i) == c && !isInsideEscape then
result.append(s.substring(prev, i))
prev = i + 1
i += 1
result.append(s.substring(prev, i))
result.toArray

def partitionByCharEscaped(c: Char, esc: Char): (String, String) =
if s.isEmpty then ("", "") else
var i = 0
var isInsideEscape = false
var continue = true
while continue && i < s.length do
if s(i) == esc then
isInsideEscape = !isInsideEscape
i += 1
else if s(i) == c && !isInsideEscape then
continue = false
else
i += 1
(s.substring(0, i), s.substring(i, s.length))


def level(base: Int): Int =
val initSpace = s.takeWhile(ch => ch.isSpaceChar || ch == '\t')
initSpace.replace("\\t", " ").length + base

def wrapLongLineAtWords(n: Int): String =
val words = s.split(" ").iterator
val sb = StringBuilder()
var i = 0
while words.hasNext do
val w: String = words.next
i += w.length
if i > n then
if w.length > n then
sb.append(w)
sb.append('\n')
i = 0
else
sb.append('\n')
sb.append(w)
i = w.length
else sb.append(w)
if words.hasNext then sb.append(' ')
end while
sb.toString

def wrap(n: Int): String = s.split("\n").map(_.wrapLongLineAtWords(n)).mkString("\n")

def editDistanceTo(t: String): Int =
//https://github.com/scala/scala/blob/4e03eb5a1c7dc2cb5274a453dbff38fef12f12f4/src/compiler/scala/tools/nsc/util/EditDistance.scala#L26
val insertCost: Int = 1
val deleteCost: Int = 1
val subCost: Int = 1
val matchCost: Int = 0
val caseCost: Int = 1
val transpositions: Boolean = false
val n = s.length
val m = t.length
if (n == 0) return m
if (m == 0) return n

val d = Array.ofDim[Int](n + 1, m + 1)
0 to n foreach (x => d(x)(0) = x)
0 to m foreach (x => d(0)(x) = x)

for
i <- 1 to n
s_i = s(i - 1)
j <- 1 to m
do
val t_j = t(j - 1)
val cost =
if s_i == t_j then matchCost
else if s_i.toLower == t_j.toLower then caseCost
else subCost

val c1 = d(i - 1)(j) + deleteCost
val c2 = d(i)(j - 1) + insertCost
val c3 = d(i - 1)(j - 1) + cost

d(i)(j) = c1 min c2 min c3

if transpositions then
if i > 1 && j > 1 && s(i - 1) == t(j - 2) && s(i - 2) == t(j - 1) then
d(i)(j) = d(i)(j) min (d(i - 2)(j - 2) + cost)
end if

end for

d(n)(m)
end editDistanceTo

end extension
end StringExtensions

object err:
class ParseException(msg: String) extends Exception(msg)
def unknown(s: String) = ParseException(s"Unknown constraint: $s")
def missingPar(s: String) = ParseException(s"Missing enclosing () in: $s")
def missingEndPar(s: String) = ParseException(s"Missing matching ) at end: $s")
def badIdentifier(s: String) = ParseException(s"Bad identifier: $s")
def varExpected(s: String) = ParseException(s"Var expected: $s")
def identExpected(s: String) = ParseException(s"Identifier expected: $s")
def operatorExpected(s: String) = ParseException(s"Operator expected: $s")
def unknownTrailing(s: String) = ParseException(s"Unknown trailing chars: $s")
def illegalPath(s: String) = ParseException(s"Illegal path: $s")

object parseUtils:
def isIdStart(s: String): Boolean = s.nonEmpty && s(0).isUnicodeIdentifierStart

def parseInside(s: String, open: Char = '(', close: Char = ')', esc: Char = '"'): (String, String) =
if !s.trim.startsWith("(") then throw err.missingPar(s)
var level = 1
var i = 1
var isInsideEscape = false
while i < s.length && (level >= 1 || isInsideEscape) do
if s(i) == open then level += 1
else if s(i) == close then level -= 1
else if s(i) == esc then isInsideEscape = !isInsideEscape
i += 1
if s(i - 1) != ')' then throw err.missingEndPar(s)
(s.substring(1,i - 1), s.substring(i))

2 changes: 1 addition & 1 deletion src/main/scala/01-api-exports.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export Model.{toModel, concatAdjacent} // extension methods on Seq[Elem]

export Show.show // extension for pretty Model using enum types and apply
export Selection.* // and/or-expressions for selecting Model parts
export Path.* // path factories for slash notation on Model
export Path.`/` // path factories for slash notation on Model

export MarkdownParser.m // string interpolator to parse markdown Model
export MarkdownParser.toModel // string extension to parse markdown Model
Expand Down
34 changes: 34 additions & 0 deletions src/main/scala/02-meta-model.scala
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,40 @@ object meta:
def isRelType: Boolean = relTypes.isDefinedAt(s)
def isElemStart: Boolean = isConceptName(s.skipIndent.takeWhile(ch => ch.isLetter)) //!(ch.isSpaceChar || ch == '\t')))

def parseConcept(s: String): (Option[Elem | ElemType | Link], String) =
val trimmed = s.trim
val fw = trimmed.firstWord
val rest1 = trimmed.drop(fw.length).trim
if rest1.isEmpty then
if fw.isNodeType then (Some(nodeTypes(fw)), "")
else if fw.isRelType then (Some(relTypes(fw)), "")
else (None, s)
else if rest1(0) != '(' then (None, s) else
val (param, rest2) = rest1.partitionByCharEscaped(')','"')
val rest2afterParen = rest2.stripPrefix(")").trim
val hasLinkDot = rest2afterParen.startsWith(".")
val inner = param.trim.drop(1)
val unquoted = inner.dropQuotes
if fw == "Undefined" then
if inner.isStrAttrType then (Some(Undefined(strAttrTypes(inner))), rest2afterParen)
if inner.isIntAttrType then (Some(Undefined(intAttrTypes(inner))), rest2afterParen)
else (None, s)
else if fw.isEntType then
if !hasLinkDot then
(Some(entTypes(fw).apply(unquoted)), rest2afterParen)
else
val relPart = rest2afterParen.drop(1)
val relWord = relPart.firstWord
val rest3 = relPart.drop(relWord.length)
if relWord.isRelType then (Some(Link(entTypes(fw).apply(unquoted), relTypes(relWord))), rest3)
else (None, s)
else if fw.isStrAttrType then (Some(strAttrTypes(fw).apply(unquoted)), rest2afterParen)
else if fw.isIntAttrType then
val intOpt = inner.toIntOption
if intOpt.isDefined then (Some(intAttrTypes(fw).apply(intOpt.get)), rest2afterParen)
else (None, s)
else (None, s)

def matrix: Seq[Seq[String]] = for Concept(n, d, t, g) <- concepts yield Seq(n, t, g, d)

def csv(delim: String = ";"): String =
Expand Down
6 changes: 3 additions & 3 deletions src/main/scala/04-ModelMembers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ transparent trait ModelMembers:

/** A new Model with other Model's elems appended to elems. Same as: `m :++ other`
* NOTE: Different from `m ++ other` */
def append(other: Model): Model = Model(elems ++ other.elems)
def append(other: Model): Model = Model(elems :++ other.elems)

def :++(other: Model): Model = append(other)

Expand Down Expand Up @@ -203,7 +203,7 @@ transparent trait ModelMembers:
def top: Model = cut(1)

def sub: Model =
elems.collect { case Rel(e, r, sub) => sub }.foldLeft(Model())(_ ++ _)
elems.collect { case Rel(e, r, sub) => sub }.foldLeft(Model())(_ :++ _)

/** Cut all relations so that no relations is deeper than depth. cut(0) == tip, cut(1) == top **/
def cut(depth : Int): Model =
Expand All @@ -223,7 +223,7 @@ transparent trait ModelMembers:

case Vector(link) =>
val ms: Vector[Model] = elems.collect{ case r: Rel if r.e == link.e && r.t == link.t => r.sub}
ms.foldLeft(Model())(_ ++ _)
ms.foldLeft(Model())(_ :++ _)

case Vector(link, rest*) =>
val m2 = self / link
Expand Down
Loading

0 comments on commit 23e85ec

Please sign in to comment.