Go: Goji patternマッチ部分のコードリーディング

Gojiでは, sinatraライクにURLマッチングをさせることが出来ます. この記事では, URLマッチングさせる部分のソースコードを解説していきます.

まず, parsePatternメソッドから.

func parsePattern(p interface{}) Pattern {
  switch p.(type) {
  case Pattern:
    return p.(Pattern)
  case *regexp.Regexp:
    return parseRegexpPattern(p.(*regexp.Regexp))
  case string:
    return parseStringPattern(p.(string))
  default:
    log.Fatalf("Unknown pattern type %v. Expected a web.Pattern, "+
      "regexp.Regexp, or a string.", p)
  }
  panic("log.Fatalf does not return")
}

switch p.(type)で, 与えられたパターンの型によって処理を分岐させています. Pattern, *regexp.Regexp, stringの3パターンに対応しています.

今回は, stringの場合, parseStringPatternの処理を見ていきます.

parseStringPattern 中身

const bc = "/.;,"

var patternRe = regexp.MustCompile(`[` + bc + `]:([^` + bc + `]+)`)

func parseStringPattern(s string) stringPattern {
  raw := s
  var wildcard bool
  if strings.HasSuffix(s, "/*") {
    s = s[:len(s)-1]
    wildcard = true
  }

  matches := patternRe.FindAllStringSubmatchIndex(s, -1)
  pats := make([]string, len(matches))
  breaks := make([]byte, len(matches))
  literals := make([]string, len(matches)+1)
  n := 0
  for i, match := range matches {
    a, b := match[2], match[3]
    literals[i] = s[n : a-1] // Need to leave off the colon
    pats[i] = s[a:b]
    if b == len(s) {
      breaks[i] = '/'
    } else {
      breaks[i] = s[b]
    }
    n = b
  }
  literals[len(matches)] = s[n:]
  return stringPattern{
    raw:      raw,
    pats:     pats,
    breaks:   breaks,
    literals: literals,
    wildcard: wildcard,
  }
}

こんな感じです. 上から順番に見ていきます.

var wildcard bool
if strings.HasSuffix(s, "/*") {
  s = s[:len(s)-1]
  wildcard = true
}

ここでは, /*で終わる場合, 末尾の*を取り除いて, wildcardをtrueにしています. Goでは, 初期値が与えられないと0値で初期化されるので var wildcard boolの辞典ではfalseになります.

matches := patternRe.FindAllStringSubmatchIndex(s, -1)

FindAllStringSubmatchIndexは, FindStringSubmatchIndexの全文マッチ版になっています. FindStringSubmatchIndexは, 一致する一番左のテキストのindexのペアと, subexpression(部分式)のindexのペアを返してくれます.

例えば, 下の例だと[1 5 2 4]が返されます.

reg := regexp.MustCompile("h(og)e")
fmt.Println(reg.FindStringSubmatchIndex("ahogea"))
pats := make([]string, len(matches))
breaks := make([]byte, len(matches))
literals := make([]string, len(matches)+1)
n := 0
for i, match := range matches {
  a, b := match[2], match[3]
  literals[i] = s[n : a-1] // Need to leave off the colon
  pats[i] = s[a:b]
  if b == len(s) {
    breaks[i] = '/'
  } else {
    breaks[i] = s[b]
  }
  n = b
}

ここでは, /:nameと書かれたパターンを抽出します. patsには, :で区切られたパターンが, literalsには, 固定されたURLリテラル, breaksにはliteralとpatternの区切り文字が入ります.

例です. /users/:userid だと, literals{/users/}, breaks{/}, pats{userid}にそれぞれなります. /users/:userid/like/:likeid だと literals{/users/, /like/}, breaks{/, /}, pats{userid, loveid} になります.

Written by
あんどろいどでぃべろっぱぁー🍎