如何在String值的格式上编码约束

时间:2011-08-27 19:47:24

标签: regex parsing scala syntax combinators

正如我经常观察到的以及我经常如何实现name属性,只需将其建模为String

现在,如果名称必须遵循某种语法,即格式化?在Java中,我可能会定义一个构造函数,并检查其参数,例如:

public Name(str: String) {
    if (str == null) throw new IllegalArgumentException("Str must not be null.");
    if (!str.matches("name format expressed as regex")) throw new IllegalArgumentException("Str must match 'regex' but was " + str);
    this.str = str;
}

在Scala中,我想出了以下解决方案:

import StdDef.Str
import StdDef.Bol
import StdDef.?

import scala.util.parsing.combinator.RegexParsers

final case class Name private (pfx: ?[Str] = None, sfx: Str) {

  override def toString = pfx.mkString + sfx

}

object Name extends RegexParsers {

  implicit def apply(str: Str): Name = parseAll(syntax, str) match {
    case Success(res, _) => Name(res._1, res._2)
    case rej: NoSuccess => error(rej.toString)
  }

  lazy val syntax = (prefix ?) ~! suffix

  lazy val prefix = (("x" | "X") ~! hyph) ^^ { case a ~ b => a + b }

  lazy val suffix = alpha ~! (alpha | digit | hyph *) ^^ { case a ~ b => a + b.mkString }

  lazy val alpha: Parser[Str] = """\p{Alpha}""".r

  lazy val digit: Parser[Str] = """\p{Digit}""".r

  lazy val hyph: Parser[Str] = "-"

  override lazy val skipWhitespace = false

}

我的意图是:

  1. 从其自然表示中撰写Name,即String
  2. 检查其自然表示是否在施工时形成有效Name
  3. 禁止任何其他结构,而不是通过工厂方法apply:(str:Str)Str
  4. 使构造从其自然表示隐含,例如val a: Name = "ISBN 978-0-9815316-4-9"
  5. 根据语法元素将Name分解为各个部分。
  6. 信息会引发错误,例如:
  7. ===

    --
    ^
    [1.3] error: string matching regex `\p{Alpha}' expected but end of source found
    

    我想知道您提出了哪些解决方案。

    在给出主题更多的想法之后,我现在采取以下方法。

    Token.scala:

    abstract class Token {
      val value: Str
    }
    object Token {
      def apply[A <: Token](ctor: Str => A, syntax: Regex) = (value: Str) => value match {
        case syntax() => ctor(value)
        case _ => error("Value must match '" + syntax + "' but was '" + value + "'.")
      }
    }
    

    Tokens.scala:

    final case class Group private (val value: Str) extends Token
    final case class Name private (val value: Str) extends Token
    trait Tokens {
      import foo.{ bar => outer }
      val Group = Token(outer.Group, """(?i)[a-z0-9-]++""".r)
      val Name = Token(outer.Name, """(?i)(?:x-)?+[a-z0-9-]++""".r)
    }
    

1 个答案:

答案 0 :(得分:1)

鉴于你习惯在Java中使用正则表达式,那么尝试用Scala中的解析器解决同样的问题似乎有点过分了。

坚持你所知道的,但添加一个Scala扭曲来清理解决方案。 Scala中的正则表达式还定义了提取器,允许它们用于模式匹配:

//triple-quote to make escaping easier, the .r makes it a regex
//Note how the value breaks normal naming conventions and starts in uppercase
//This is to avoid backticks when pattern matching

val TestRegex = """xxyyzz""".r

class Name(str: String) {
  str match {
    case Null => throw new IllegalArgumentException("Str must not be null")
    case TestRegex => //do nothing
    case _ => throw new IllegalArgumentException(
      "Str must match 'regex' but was " + str)
  }
}

免责声明:我实际上没有测试过这段代码,它可能包含错别字