Skip to content

Commit 32ff823

Browse files
author
Oron Port
authored
Merge pull request #135 from erikerlandson/string-regex-ops
Fix #134 - add some string and regex ops
2 parents 14131af + 840cdc1 commit 32ff823

File tree

6 files changed

+205
-19
lines changed

6 files changed

+205
-19
lines changed

README.md

+15-3
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,21 @@ libraryDependencies ++= Seq(
8888
* `type ToString[P1]`
8989

9090
#### Supported string operations:
91-
* `type +[P1, P2]` (concat)
92-
* `type Reverse[P1]`
93-
* `type Substring[P1, P2]`
91+
* `type Length[S]`
92+
* `type +[S1, S2]` (concat)
93+
* `type Reverse[S]`
94+
* `type CharAt[S, I]`
95+
* `type Substring[S, I]`
96+
* `type SubSequence[S, IBeg, IEnd]`
97+
* `type StartsWith[S, Prefix]`
98+
* `type EndsWith[S, Suffix]`
99+
* `type Head[S]`
100+
* `type Tail[S]`
101+
* `type Matches[S, Regex]`
102+
* `type FirstMatch[S, Regex]`
103+
* `type PrefixMatch[S, Regex]`
104+
* `type ReplaceFirstMatch[S, Regex, R]`
105+
* `type ReplaceAllMatches[S, Regex, R]`
94106

95107
#### Supported constraints operations:
96108
* `type Require[P1]`

src/main/scala/singleton/ops/impl/GeneralMacros.scala

+73-2
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,18 @@ trait GeneralMacros {
7979
val Min = symbolOf[OpId.Min]
8080
val Max = symbolOf[OpId.Max]
8181
val Substring = symbolOf[OpId.Substring]
82+
val SubSequence = symbolOf[OpId.SubSequence]
83+
val StartsWith = symbolOf[OpId.StartsWith]
84+
val EndsWith = symbolOf[OpId.EndsWith]
85+
val Head = symbolOf[OpId.Head]
86+
val Tail = symbolOf[OpId.Tail]
8287
val CharAt = symbolOf[OpId.CharAt]
8388
val Length = symbolOf[OpId.Length]
89+
val Matches = symbolOf[OpId.Matches]
90+
val FirstMatch = symbolOf[OpId.FirstMatch]
91+
val PrefixMatch = symbolOf[OpId.PrefixMatch]
92+
val ReplaceFirstMatch = symbolOf[OpId.ReplaceFirstMatch]
93+
val ReplaceAllMatches = symbolOf[OpId.ReplaceAllMatches]
8494
}
8595

8696
////////////////////////////////////////////////////////////////////
@@ -1236,17 +1246,68 @@ trait GeneralMacros {
12361246
case _ => unsupported()
12371247
}
12381248
def Substring : Calc = (a, b) match {
1239-
case (CalcVal(at : String, att), CalcVal(bt : Int, btt)) => CalcVal.mayFail(Primitive.String, at.substring(bt), q"$att.substring($btt)")
1249+
case (CalcVal(at : String, att), CalcVal(bt : Int, btt)) =>
1250+
CalcVal.mayFail(Primitive.String, at.substring(bt), q"$att.substring($btt)")
1251+
case _ => unsupported()
1252+
}
1253+
def SubSequence : Calc = (a, b, cArg) match {
1254+
case (CalcVal(at : String, att), CalcVal(bt : Int, btt), CalcVal(ct : Int, ctt)) =>
1255+
CalcVal.mayFail(Primitive.String, at.subSequence(bt, ct), q"$att.subSequence($btt, $ctt)")
1256+
case _ => unsupported()
1257+
}
1258+
def StartsWith : Calc = (a, b) match {
1259+
case (CalcVal(at : String, att), CalcVal(bt : String, btt)) =>
1260+
CalcVal(at.startsWith(bt), q"$att.startsWith($btt)")
1261+
case _ => unsupported()
1262+
}
1263+
def EndsWith : Calc = (a, b) match {
1264+
case (CalcVal(at : String, att), CalcVal(bt : String, btt)) =>
1265+
CalcVal(at.endsWith(bt), q"$att.endsWith($btt)")
1266+
case _ => unsupported()
1267+
}
1268+
def Head : Calc = a match {
1269+
case CalcVal(at : String, att) =>
1270+
CalcVal.mayFail(Primitive.Char, at.head, q"$att.head")
1271+
case _ => unsupported()
1272+
}
1273+
def Tail : Calc = a match {
1274+
case CalcVal(at : String, att) => CalcVal(at.tail, q"$att.tail")
12401275
case _ => unsupported()
12411276
}
12421277
def CharAt : Calc = (a, b) match {
1243-
case (CalcVal(at : String, att), CalcVal(bt : Int, btt)) => CalcVal.mayFail(Primitive.Char, at.charAt(bt),q"$att.charAt($btt)")
1278+
case (CalcVal(at : String, att), CalcVal(bt : Int, btt)) =>
1279+
CalcVal.mayFail(Primitive.Char, at.charAt(bt), q"$att.charAt($btt)")
12441280
case _ => unsupported()
12451281
}
12461282
def Length : Calc = a match {
12471283
case CalcVal(at : String, att) => CalcVal(at.length, q"$att.length")
12481284
case _ => unsupported()
12491285
}
1286+
def Matches : Calc = (a, b) match {
1287+
case (CalcVal(at : String, att), CalcVal(bt : String, btt)) =>
1288+
CalcVal.mayFail(Primitive.Boolean, bt.r.matches(at), q"$btt.r.matches($att)")
1289+
case _ => unsupported()
1290+
}
1291+
def FirstMatch : Calc = (a, b) match {
1292+
case (CalcVal(at : String, att), CalcVal(bt : String, btt)) =>
1293+
CalcVal.mayFail(Primitive.String, bt.r.findFirstIn(at).get, q"$btt.r.findFirstIn($att).get")
1294+
case _ => unsupported()
1295+
}
1296+
def PrefixMatch : Calc = (a, b) match {
1297+
case (CalcVal(at : String, att), CalcVal(bt : String, btt)) =>
1298+
CalcVal.mayFail(Primitive.String, bt.r.findPrefixOf(at).get, q"$btt.r.findPrefixOf($att).get")
1299+
case _ => unsupported()
1300+
}
1301+
def ReplaceFirstMatch : Calc = (a, b, cArg) match {
1302+
case (CalcVal(at : String, att), CalcVal(bt : String, btt), CalcVal(ct : String, ctt)) =>
1303+
CalcVal.mayFail(Primitive.String, bt.r.replaceFirstIn(at, ct), q"$btt.r.replaceFirstIn($att, $ctt)")
1304+
case _ => unsupported()
1305+
}
1306+
def ReplaceAllMatches : Calc = (a, b, cArg) match {
1307+
case (CalcVal(at : String, att), CalcVal(bt : String, btt), CalcVal(ct : String, ctt)) =>
1308+
CalcVal.mayFail(Primitive.String, bt.r.replaceAllIn(at, ct), q"$btt.r.replaceAllIn($att, $ctt)")
1309+
case _ => unsupported()
1310+
}
12501311

12511312
funcType match {
12521313
case funcTypes.AcceptNonLiteral => AcceptNonLiteral
@@ -1293,8 +1354,18 @@ trait GeneralMacros {
12931354
case funcTypes.Min => Min
12941355
case funcTypes.Max => Max
12951356
case funcTypes.Substring => Substring
1357+
case funcTypes.SubSequence => SubSequence
1358+
case funcTypes.StartsWith => StartsWith
1359+
case funcTypes.EndsWith => EndsWith
1360+
case funcTypes.Head => Head
1361+
case funcTypes.Tail => Tail
12961362
case funcTypes.CharAt => CharAt
12971363
case funcTypes.Length => Length
1364+
case funcTypes.Matches => Matches
1365+
case funcTypes.FirstMatch => FirstMatch
1366+
case funcTypes.PrefixMatch => PrefixMatch
1367+
case funcTypes.ReplaceFirstMatch => ReplaceFirstMatch
1368+
case funcTypes.ReplaceAllMatches => ReplaceAllMatches
12981369
case _ => abort(s"Unsupported $funcType[$a, $b, $cArg]")
12991370
}
13001371
}

src/main/scala/singleton/ops/impl/OpId.scala

+10
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,18 @@ object OpId {
5050
sealed trait Min extends OpId
5151
sealed trait Max extends OpId
5252
sealed trait Substring extends OpId
53+
sealed trait SubSequence extends OpId
54+
sealed trait StartsWith extends OpId
55+
sealed trait EndsWith extends OpId
56+
sealed trait Head extends OpId
57+
sealed trait Tail extends OpId
5358
sealed trait Length extends OpId
5459
sealed trait CharAt extends OpId
60+
sealed trait Matches extends OpId
61+
sealed trait FirstMatch extends OpId
62+
sealed trait PrefixMatch extends OpId
63+
sealed trait ReplaceFirstMatch extends OpId
64+
sealed trait ReplaceAllMatches extends OpId
5565
sealed trait Abs extends OpId
5666
sealed trait Pow extends OpId
5767
sealed trait Floor extends OpId

src/main/scala/singleton/ops/package.scala

+13-3
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,19 @@ package object ops {
126126
type !=[P1, P2] = OpMacro[OpId.!=, P1, P2, NP]
127127
type &&[P1, P2] = OpMacro[OpId.&&, P1, P2, NP]
128128
type ||[P1, P2] = OpMacro[OpId.||, P1, P2, NP]
129-
type Substring[P1, P2] = OpMacro[OpId.Substring, P1, P2, NP]
130-
type Length[P1] = OpMacro[OpId.Length, P1, NP, NP]
131-
type CharAt[P1, P2] = OpMacro[OpId.CharAt, P1, P2, NP]
129+
type SubSequence[S, IBeg, IEnd] = OpMacro[OpId.SubSequence, S, IBeg, IEnd]
130+
type Substring[S, I] = OpMacro[OpId.Substring, S, I, NP]
131+
type StartsWith[S, Prefix] = OpMacro[OpId.StartsWith, S, Prefix, NP]
132+
type EndsWith[S, Suffix] = OpMacro[OpId.EndsWith, S, Suffix, NP]
133+
type Head[S] = OpMacro[OpId.Head, S, NP, NP]
134+
type Tail[S] = OpMacro[OpId.Tail, S, NP, NP]
135+
type Length[S] = OpMacro[OpId.Length, S, NP, NP]
136+
type CharAt[S, I] = OpMacro[OpId.CharAt, S, I, NP]
137+
type Matches[S, Regex] = OpMacro[OpId.Matches, S, Regex, NP]
138+
type FirstMatch[S, Regex] = OpMacro[OpId.FirstMatch, S, Regex, NP]
139+
type PrefixMatch[S, Regex] = OpMacro[OpId.PrefixMatch, S, Regex, NP]
140+
type ReplaceFirstMatch[S, Regex, R] = OpMacro[OpId.ReplaceFirstMatch, S, Regex, R]
141+
type ReplaceAllMatches[S, Regex, R] = OpMacro[OpId.ReplaceAllMatches, S, Regex, R]
132142

133143
type CompileTime[C] = Require[ITE[IsNonLiteral[C], W.`true`.T, C]]
134144
type RunTime[R] = SafeBoolean[IsNonLiteral[R]]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package singleton.ops
2+
3+
import org.scalacheck.Properties
4+
import shapeless.test.illTyped
5+
import singleton.TestUtils._
6+
7+
class StringOpsSpec extends Properties("StringOps") {
8+
property("Substring[foobar, 3] == bar") = wellTyped {
9+
def f[S <: XString, I <: XInt](implicit op : Substring[S, I]) : op.Out{} = op.value
10+
val r : W.`"bar"`.T = f[W.`"foobar"`.T, W.`3`.T]
11+
}
12+
property("Substring abort") = wellTyped {illTyped("""implicitly[Substring[W.`"abc"`.T, W.`4`.T]]""")}
13+
property("Substring unsupported") = wellTyped {illTyped("""implicitly[Substring[W.`true`.T, W.`2`.T]]""")}
14+
15+
property("SubSequence[foobar, 1, 4] == oob") = wellTyped {
16+
def f[S <: XString, B <: XInt, E <: XInt](implicit op : SubSequence[S, B, E]) : op.Out{} = op.value
17+
val r : W.`"oob"`.T = f[W.`"foobar"`.T, W.`1`.T, W.`4`.T]
18+
}
19+
property("SubSequence abort") = wellTyped {illTyped("""implicitly[SubSequence[W.`"abc"`.T, W.`5`.T, W.`7`.T]]""")}
20+
property("SubSequence unsupported") = wellTyped {illTyped("""implicitly[SubSequence[W.`true`.T, W.`2`.T, W.`3`.T]]""")}
21+
22+
property("StartsWith[foobar, foo] == true") = wellTyped {
23+
def f[S <: XString, Pre <: XString](implicit op : StartsWith[S, Pre]) : op.Out{} = op.value
24+
val r : W.`true`.T = f[W.`"foobar"`.T, W.`"foo"`.T]
25+
}
26+
property("StartsWith unsupported") = wellTyped {illTyped("""implicitly[StartsWith[W.`"foobar"`.T, W.`true`.T]]""")}
27+
28+
property("EndsWith[foobar, bar] == true") = wellTyped {
29+
def f[S <: XString, Suf <: XString](implicit op : EndsWith[S, Suf]) : op.Out{} = op.value
30+
val r : W.`true`.T = f[W.`"foobar"`.T, W.`"bar"`.T]
31+
}
32+
property("EndsWith unsupported") = wellTyped {illTyped("""implicitly[EndsWith[W.`"foobar"`.T, W.`true`.T]]""")}
33+
34+
property("Head[foobar] == f") = wellTyped {
35+
def f[S <: XString](implicit op : Head[S]) : op.Out{} = op.value
36+
val r : W.`'f'`.T = f[W.`"foobar"`.T]
37+
}
38+
property("Head abort") = wellTyped {illTyped("""implicitly[Head[W.`""`.T]]""")}
39+
property("Head unsupported") = wellTyped {illTyped("""implicitly[Head[W.`0`.T]]""")}
40+
41+
property("Tail[foobar] == oobar") = wellTyped {
42+
def f[S <: XString](implicit op : Tail[S]) : op.Out{} = op.value
43+
val r : W.`"oobar"`.T = f[W.`"foobar"`.T]
44+
}
45+
property("Tail unsupported") = wellTyped {illTyped("""implicitly[Tail[W.`0`.T]]""")}
46+
47+
property("CharAt[foobar, 3] == b") = wellTyped {
48+
def f[S <: XString, I <: XInt](implicit op : CharAt[S, I]) : op.Out{} = op.value
49+
val r : W.`'b'`.T = f[W.`"foobar"`.T, W.`3`.T]
50+
}
51+
property("CharAt abort") = wellTyped {illTyped("""implicitly[CharAt[W.`"abc"`.T, W.`5`.T]]""")}
52+
property("CharAt unsupported") = wellTyped {illTyped("""implicitly[CharAt[W.`true`.T, W.`2`.T]]""")}
53+
54+
property("Length[foobar] == 6") = wellTyped {
55+
def f[S <: XString](implicit op : Length[S]) : op.Out{} = op.value
56+
val r : W.`6`.T = f[W.`"foobar"`.T]
57+
}
58+
property("Length unsupported") = wellTyped {illTyped("""implicitly[Length[W.`true`.T]]""")}
59+
60+
property("Matches[foobar, fo+.*] == true") = wellTyped {
61+
def f[S <: XString, Regex <: XString](implicit op : Matches[S, Regex]) : op.Out{} = op.value
62+
val r : W.`true`.T = f[W.`"foobar"`.T, W.`"fo+.*"`.T]
63+
}
64+
property("Matches abort") = wellTyped {illTyped("""implicitly[Matches[W.`"abc"`.T, W.`"[a"`.T]]""")}
65+
property("Matches unsupported") = wellTyped {illTyped("""implicitly[Matches[W.`"foobar"`.T, W.`true`.T]]""")}
66+
67+
property("FirstMatch[foobar, b[ar]+] == bar") = wellTyped {
68+
def f[S <: XString, Regex <: XString](implicit op : FirstMatch[S, Regex]) : op.Out{} = op.value
69+
val r : W.`"bar"`.T = f[W.`"foobar"`.T, W.`"b[ar]+"`.T]
70+
}
71+
property("FirstMatch abort") = wellTyped {illTyped("""implicitly[FirstMatch[W.`"abc"`.T, W.`"[a"`.T]]""")}
72+
property("FirstMatch unsupported") = wellTyped {illTyped("""implicitly[FirstMatch[W.`0`.T, W.`".*"`.T]]""")}
73+
74+
property("PrefixMatch[foobar, fo+] == foo") = wellTyped {
75+
def f[S <: XString, Regex <: XString](implicit op : PrefixMatch[S, Regex]) : op.Out{} = op.value
76+
val r : W.`"foo"`.T = f[W.`"foobar"`.T, W.`"fo+"`.T]
77+
}
78+
property("PrefixMatch abort") = wellTyped {illTyped("""implicitly[PrefixMatch[W.`"abc"`.T, W.`"[a"`.T]]""")}
79+
property("PrefixMatch unsupported") = wellTyped {illTyped("""implicitly[PrefixMatch[W.`true`.T, W.`".*"`.T]]""")}
80+
81+
property("ReplaceFirstMatch[foobar, [oa], z] == fzobar") = wellTyped {
82+
def f[S <: XString, Regex <: XString, R <: XString](implicit op : ReplaceFirstMatch[S, Regex, R]) : op.Out{} = op.value
83+
val r : W.`"fzobar"`.T = f[W.`"foobar"`.T, W.`"[oa]"`.T, W.`"z"`.T]
84+
}
85+
property("ReplaceFirstMatch abort") = wellTyped {illTyped("""implicitly[ReplaceFirstMatch[W.`"abc"`.T, W.`"[a"`.T, W.`"z"`.T]]""")}
86+
property("ReplaceFirstMatch unsupported") = wellTyped {illTyped("""implicitly[ReplaceFirstMatch[W.`0`.T, W.`".*"`.T, W.`"z"`.T]]""")}
87+
88+
property("ReplaceAllMatches[foobar, [oa], z] == fzzbzr") = wellTyped {
89+
def f[S <: XString, Regex <: XString, R <: XString](implicit op : ReplaceAllMatches[S, Regex, R]) : op.Out{} = op.value
90+
val r : W.`"fzzbzr"`.T = f[W.`"foobar"`.T, W.`"[oa]"`.T, W.`"z"`.T]
91+
}
92+
property("ReplaceAllMatches abort") = wellTyped {illTyped("""implicitly[ReplaceAllMatches[W.`"abc"`.T, W.`"[a"`.T, W.`"z"`.T]]""")}
93+
property("ReplaceAllMatches unsupported") = wellTyped {illTyped("""implicitly[ReplaceAllMatches[W.`0`.T, W.`".*"`.T, W.`"z"`.T]]""")}
94+
}

src/test/scala/singleton/ops/SubstringSpec.scala

-11
This file was deleted.

0 commit comments

Comments
 (0)