代码之家  ›  专栏  ›  技术社区  ›  Platinum Azure

代码:Regex解析器

  •  53
  • Platinum Azure  · 技术社区  · 5 年前

    目标

    今天的代码挑战是用尽可能少的字符创建regex解析器。

    语法

    不,我不是要你匹配Perl风格的正则表达式。毕竟,已经有一个非常可靠的翻译了!:-)

    下面是您需要了解的有关正则表达式语法的所有信息:

    • A () .
    • * (星号)字符表示 克莱恩之星行动 在上学期。这意味着上一个术语的零个或多个串联在一起。
    • + a+ 相当于 aa* ,表示上一术语中的一个或多个。
    • ? (问号)字符表示上一个术语的零或一个。
    • 这个 | (pipe)字符表示替换,这意味着可以在匹配中使用任意一侧的正则表达式。
    • [0-9A-Za-z] (即所有英文字母数字)。

    或者换句话说: * + / ? + ?

    挑战

    我把意见留给你。我的建议是,regex可能应该先出现,然后再测试任意数量的字符串;但是如果您想让它最后出现,那就好了。如果您想将所有内容都放在命令行参数或stdin中,或者命令行中的regex和stdin中的字符串,或者诸如此类的东西中,那就好了。举一两个用法例子。

    true false ,每行一个,以反映正则表达式是否匹配。

    笔记:

    • 编辑: 如果需要拆分或连接字符串,可以使用regex。你不能直接用它来解决问题,例如,将输入regex转换成语言regex并使用它。)
    • 正则表达式必须与此质询的输入字符串完全匹配。(等价地,如果您熟悉类似于Perl的regex,那么假设所有匹配的字符串的起始和结束锚定都已就位)
    • ()*+?|
    • 应以区分大小写的方式评估要测试的输入字符串。

    这些例子

    myregex

    > myregex easy easy Easy hard
    true
    false
    false
    
    > myregex ab*a aa abba abab b
    true
    true
    false
    false
    
    > myregex 0*1|10 1 10 0110 00001
    true
    true
    false
    true
    
    > myregex 0*(1|1+0) 1 10 0110 00001
    true
    true
    true
    true
    
    > myregex a?b+|(a+b|b+a?)+ abb babab aaa aabba a b
    true
    true
    false
    true
    false
    true
    

    注: 抱歉,忘记做社区维基了!:-(

    7 回复  |  直到 14 年前
        1
  •  15
  •   Nabb    13 年前

    GolfScript-254字符

    n%([]:B:$:_"()"@*{:I"()*+|?"[{}/]?[{[[0B$,+:B))\;)]_]+}{B)):ß;:B;qß(:ß;}{8q}{[[0ß0$,)]]+}:8{[[0B-1=:ß)]]+:$q}{ß>$ß<\([0+$,+]\++}:q{[[I$,:ß)]]+}]=~:$}/;{n+[0]:3\{:c;;3:1_:3;{,}{)[$=]_*2/{~\.{c={3|:3}*;}{;.1|1,\:1,<{+0}*;}if}/}/;}/;1$,?)"true""false"if n}%
    

    有点直截了当,第一个循环将regex转换成NFA,第二个循环运行NFA。

    Sun Aug 22 00:58:24 EST 2010 (271266)
    Sun Aug 22 01:07:11 EST 2010 制造的 []
    Sun Aug 22 07:05:50 EST 2010 (265259) 内联进行空状态转换
    Sun Aug 22 07:19:21 EST 2010 (259256) 最终状态隐式化
    Mon Feb 7 19:24:19 EST 2011 使用 "()""str"*

    $ echo "ab*a aa abba abab b"|tr " " "\n"|golfscript regex.gs
    true
    true
    false
    false
    
    $ echo "0*1|10 1 10 0110 00001"|tr " " "\n"|golfscript regex.gs
    true
    true
    false
    true
    
    $ echo "0*(1|1+0) 1 10 0110 00001"|tr " " "\n"|golfscript regex.gs
    true
    true
    true
    true
    
    $ echo "a?b+|(a+b|b+a?)+ abb babab aaa aabba a b"|tr " " "\n"|golfscript regex.gs
    true
    true
    false
    true
    false
    true
    
    $ echo "((A|B|C)+(a|(bbbbb)|bb|c)+)+ ABCABCaccabbbbbaACBbbb ABCABCaccabbbbbaACBbbbb"|tr " " "\n"|golfscript regex.gs
    false
    true
    
        2
  •  20
  •   mvds    14 年前

    C(口译), 630 622 617 615 582 576 573 572 554 544 529 502 500 499个字符

    #define C char
    #define M m(s,o
    m(C*s,C*o,C*S,C*p,C*P,C T){C*n=P-1,*q=s,h=*P==41,c=1;for(;h*c;c-=*n--==40)c+=*n==41;
    c=*P-42;c=p-P?c-82?T&&c&~1&&c-21?h?2:*S==*P&s<S?M,S-1,p,n,2)||(T&4&&M,S-1,p,P,T|1)):
    4:M,T?S:o,p,P-1,T?c&~1?3:7-c:0):T&&s==S||M,o,p,P-1,2):T&&s==S;if(c==2)for(c=4;q<=S;q
    ++)c|=m(q,S,S,n+h,P-h,2)?M,q,p,n,2)||q<S&T/4&&M,q,p,P,T&6):0;return
    c&4?c&1?:T&1&&M,S,p,n,2)||M,o,p,n,0):c;}main(C*w,C**v){C*u;for(w=*++v;*++v;)puts(m(*v
    -1,u,u=index(*v,0)-1,w-1,index(w,0)-1,2)?"true":"false");}
    

    用-Wall编译时抱怨argc是char。会在非法模式下崩溃。

    argv上的输入,输出 颠倒 正常顺序:

    $ ./a.out ab*a aa abba abab b
    true
    true
    false
    false
    
    $ ./a.out "0*1|10" 1 10 0110 00001
    true
    true
    false
    true
    
    $ ./a.out "0*(1|1+0)" 1 10 0110 00001
    true
    true
    true
    true
    
    $ ./a.out "a?b+|(a+b|b+a?)+" abb babab aaa aabba a b
    true
    true
    false
    true
    false
    true
    
    $ ./a.out "((A|B|C)+(a|(bbbbb)|bb|c)+)+" ABCABCaccabbbbbaACBbbb
    false
    $ ./a.out "((A|B|C)+(a|(bbbbb)|bb|c)+)+" ABCABCaccabbbbbaACBbbbb
    true
    
        3
  •  7
  •   mvds    14 年前

    727 670个字符

    这还没有完全达到最低限度,但我想试试看,首先解析regexp是否会对解释它产生影响。确实如此,因为成本更高,尽管解析和匹配都更容易编写/理解。

    #define Q struct q
    #define C char
    #define R return
    Q{Q*u,*n,*a;C c,m};Q*P(C*p,C*e){Q*r=calloc(99,1);C*n=p+1,c=1,w;if(p==e)R
    r;if(*p==40){for(;c;)c+=(*n==40)-(*n++==41);r->u=P(p+1,n-1);}else
    if(*p=='|'){r->a=P(p+1,e);R r;}else r->c=*p;if(n<e){if(*n==43)*n=42,r->n=P(p,e);else
    w=*n==42|*n==63,r->n=P(n+w,e),r->m=w?*n:0;r->a=r->n->a;}R r;}M(Q*r,C*s,C*o,C*z){C*p,
    e;e=r?r->m==63|r->m==42&&M(r->n,s,o,z)?:*s&&r->c==*s?M(r->m==42?r:r->n,s+1,o,z):2:s
    ==z;if(e-2)R e;for(p=s,e=0;!r->c&p<=z;p++)e|=M(r->u,s,s,p)&(r->m!=42|p>s)&&M(r->m==
    42?r:r->n,p,p,z);R e||r->a&&M(r->a,o,o,z);}main(C
    c,C**v){for(;--c>1;)puts(M(P(v[1],index(v[1],0)),v[c],v[c],index(v[c],0))?"true":"false");}
    

    • 要匹配的字符 指向要匹配的子模式的结构的指针
    • 指向下一个符号结构的指针(如果有)
    • 一个\0的乘法器, * ? (pat)+ 被解析为 (pat)(pat)* 马上匹配就容易多了
        4
  •  7
  •   Thomas Eding    14 年前

    哈斯克尔:610 627

    main=getLine>>=f.words
    d=reverse
    u=0<1
    j=[]
    f(r:s)=mapM_(print.any null.c(d$b$'(':r++")"))s
    c%(x,y)=(c:x,y)
    s _ _ _[]=(j,j)
    s n a b (x:y)|n<1&&x==a=(j,x:y)|x==a=f(-1)|x==b=f 1|u=f 0where f k=x%s(n+k)a b y
    b r|m==j=r|u=b$d(o++"(("++x)++")("++z++")/)"++w where(c,m)=s 0'|''!'r;_:v=m;(o,_:x)=s 0'('')'$d c;(z,_:w)=s 0')''('v
    (!)g f x=f x>>=g
    c[]=(:j)
    c r=f!c s where(s,f)=i r
    p q@(r:s)|r=='('=(s,(:j))|u=(a,f!g)where(w,f)=i q;(a,g)=p w
    _?[]=j
    c?(h:r)|c==h=[r]|u=j
    i(r:q)=maybe(q,(r?))id$lookup r$zip")/*+?"$p q:zip[e,w,w,w][\s->f s++g s,\s->s:l s,l,\s->s:f s]where(w,f)=i q;(e,g)=i w;l s|f s==j=j|u=f s++(f s>>=l)
    

    import Control.Monad
    import Data.List
    
    -- (aa|bb|cc)   -->  )|)cc()|)bb()aa((( 
    
    testRegex = "a?b+|(a+b|b+a?)+"
    
    interpret regex = any null . interpret' regex
    
    interpret' regex = compile (rewrite regex)
    
    mapFst f (x, y) = (f x, y)
    
    splitWhileBalanced _ _ _ "" = ("", "")
    splitWhileBalanced n b1 b2 (x:xs)
      | n < 1 && x == b1 = ("", x:xs)
      | x == b1   = f (-1)
      | x == b2   = f 1
      | otherwise = f 0
      where
        f k = mapFst (x:) $ splitWhileBalanced (n+k) b1 b2 xs
    
    rewrite regex = reverse $ moveBars $ '(' : regex ++ ")"
    
    moveBars regex
      | mtBar == "" = regex
      | otherwise = moveBars $ reverse (hOpen ++ "((" ++ tOpen) ++ ")(" ++ hClose ++ ")/)" ++ tClose
      where
        (hBar, mtBar) = splitWhileBalanced 0 '|' '!' regex -- '!' is a dummy character
        b:tBar = mtBar
        (hOpen, o:tOpen) = splitWhileBalanced 0 '(' ')' $ reverse hBar
        (hClose, c:tClose) = splitWhileBalanced 0 ')' '(' $ tBar
    
    compile "" = \x -> [x]
    compile rs = f <=< compile rs'
      where 
        (rs', f) = compile' rs
    
    paren regex@(r:rs0)
      | r == '('  = (rs0, \x -> [x])
      | otherwise = (rs2, f <=< g)
      where
        (rs1, f) = compile' regex
        (rs2, g) = paren rs1
    
    compile' (r:rs0) = case r of
      '/' -> (rs2, bar)
      '*' -> (rs1, star)
      '+' -> (rs1, plus)
      '?' -> (rs1, mark)
      ')' -> paren rs0
      _   -> (rs0, lit)
      where
        (rs1, f) = compile' rs0
        (rs2, g) = compile' rs1
        bar str = f str ++ g str
        plus str
          | null (f str) = []
          | otherwise = f str ++ (f str >>= plus)
        star str = str : plus str
        mark str = str : f str
        lit = maybe [] (\x -> [x]) . stripPrefix [r]
    

    | 然后反转整个正则表达式。现在操作符是前缀形式的,因此很容易解析。程序像这样解析正则表达式。列表单子用于不确定性。

    用法:

    > runghc Regex.hs
    a?b+|(a+b|b+a?)+ abb babab aaa aabba a b
    True
    True
    False
    True
    False
    True
    
        5
  •  5
  •   AShelly    14 年前

    R=gets.chop;s='';k=[];n=a=0
    G={?(=>(A="(a-=1;s<<0)if a>1;")+"k<<[n,a];n=a=0",
    Y=?|=>(B="s<<0while 0<a-=1;")+"n+=1",
    ?)=>B+(C="s<<?|while 0<=n-=1;")+"n,a=k.pop"+F=";a+=1",
    ?*=>D="s<<c",?+=>D,??=>D}
    R.each_char{|c|eval G[c]||A+D+F};eval B+C
    def P l,s;l.map{|a|a<<s};end
    J={??=>(K="a=k.pop;")+"k<<[{Y=>n=[a[0]]},a[1]<<n]",
    ?*=>K+(H="P a[1],s={Y=>n=[a[0]]};")+"k<<[s,[n]]",
    ?+=>K+H+"k<<[a[0],[n]]",
    Y=>(I=K+"b=k.pop;")+"k<<[{Y=>[a[0],b[0]]},a[1]+b[1]]",
    ?\0=>I+"P b[1],a[0];k<<[b[0],a[1]]"}
    k=[];s.each_char{|c|eval J[c]||"k<<[{c=>a=[]},[a]]"}
    e=k[0];P e[1],R;
    p $<.map{|l|s=l.chop;*a=e[0]
    s.each_char{|c|eval@f="n=a;a=a.map{|h|h[Y]||[h]}.flatten"while a!=n
    a=a.inject([]){|a,h|a+(h[c]||[])}}
    eval@f;a.include? R}
    

    (在Ruby1.8中,通过添加下面的别名,还可以使用45个以上的字符)

    测试 type testcase.txt | ruby regex.rb

    Russ Cox's NFA parser 红宝石色。一个状态被表示为一个散列,其中一个键包含一个下一个状态的数组。

    无嗅:

    #needed to run on ruby 1.8
    class String
      alias :each_char :each_byte 
    end  
    
    ## Infix to Postfix
    
    R=gets.chop  
    p "regexp = #{R}"
    k=[] 
    s=''  #will hold postfix string
    n=a=0 #count braNches and Atoms
    postfix = R.each_char{|c|
      case c
        when ?(
            (a-=1;s<<0)if a>1
            k<<[n,a]
            n=a=0
        when ?|
          s<<0while 0<a-=1
          n+=1
        when ?)
          s<<0while 0<a-=1
          s<<?|while 0<=n-=1
          n,a=k.pop;a+=1
        when ?*,?+,??
          s<< c
        else
          (a-=1;s<<0)if a>1
          s<< c
          a+=1
      end
    }
    s<<0while 0<a-=1
    s<<?|while 0<=n-=1
    
    ## Postfix to NFA
    # State is {char=>s0=[state,state]]
    # Frag is [state, [s0,...]]
    
    Y=?|   #choice indicator
    X={:true=>[R]} #matcstate
    
     def patch l,s   #l is list of arrays, s is state.  Put s in each array
       l.map{|a|   a<< s   }
    end
    
    k=[]
    s.each_char{|c|
     case c
        when ??
          a=k.pop
          s={Y=>n=[a[0]]}
          k<<[s,a[1]<<n]
        when ?*
          a=k.pop
          s={Y=>n=[a[0]]}
          patch(a[1],s)
          k<<[s,[n]]
        when ?+
          a=k.pop
          s={Y=>n=[a[0]]}
          patch(a[1],s)
          k<<[a[0],[n]]
        when ?|
         b=k.pop
         a=k.pop
          s={Y=>[a[0],b[0]]}
          k<<[s,a[1]+b[1]]
        when 0
         b=k.pop
         a=k.pop
         patch(a[1],b[0])
         k<<[a[0],b[1]]
       else
         k<<[{c=>a=[]},[a]]
       end
    }
    e=k.pop
    patch(e[1],X)
    
    #Running the NFA
    
    E=[e[0]] #Evaluator
    p $<.map{|l|s=l.chop
      p "evaluating: #{s}" 
      a=E
      s.each_char{|c|
        begin     #skip past split nodes
          s=a.size
          a=a.map{|h|h[?|]||[h]}.flatten  
        end while a.size!=s
        a=a.inject([]){|a,h|
        a+(h[c]||[])}  #add next state or null
      }
      a=a.map{|h|h[?|]||[h]}.flatten
      r = a.include? X  #check for end of pattern
      p r
      r
    }
    
        6
  •  5
  •   hobbs    11 年前

    半解释:

    • 这是基于在第8章中发现的延续传递风格的解析器组合 高阶Perl .
    • 帮助我移除大约70个字符的功劳归于Vincent Pit(VPIT)。
    • S { block } sub { block }
    • $, 是nil(一个coderef,包含始终匹配的匹配器,不消耗任何内容)
    • c 是n元串联(取任意数量的匹配器,如果它们都按顺序成功,则返回一个成功的匹配器)。
    • a 是n元交替(取任意数量的匹配器,如果其中任何一个匹配器成功,则返回一个成功的匹配器)。
    • A 是regex生成器的助手,它采用连接和传递的交替结构 C 必要时,返回匹配器。
    • k k s() 解析为 s///
    • 这个 do @$r 是当前连接列表, @a 是当前替换列表, @p a+ 被视为 aa* ,和 a? 被视为 (a|) 内联输入 b (没有用于 plus maybe
    • 这个 S{!length pop}

    有关大部分已除雾且注释较多的代码,请参阅 this Gist .

    perl regexer.pl 'a?b+|(a+b|b+a?)+' abb babab aaa aabba a b . 代码中没有强制换行符。

    use feature':5.12';
    sub S(&){pop}
    $,=S{goto pop};
    sub p{push@{+shift},@_}
    sub c{my$l=$,;for my$r(@_){my$L=$l;
    $l=S{my($i,$c)=@_;&$L($i,S{&$r(shift,$c)})}}$l}
    sub a{my@A=@_;S{my($i,$c,$o)=@_;$o=&$_($i,$c)and return$o for@A;0}}
    sub A{$#_?a(map c(@$_),@_):c(@{+pop})}
    sub k{my($I,$k)=@_;$k=a c($I,S{&$k}),$,}
    $_=shift;$P=do{@a=$r=[];for(/./g){
    when('('){p\@p,[@a];@a=$r=[]}
    when(')'){$p=A@a;@a=@{pop@p};p$r=$a[-1],$p}
    p\@a,$r=[]when'|';
    p$r,k pop@$r when'*';
    p$r,c $$r[-1],k pop@$r when'+';
    p$r,a pop@$r,$,when '?';
    my$s=$_;p$r,S{my($_,$c)=@_;s/^\Q$s//&&$_->$c}}A@a};
    say&$P($_,S{!length pop})?"true":"false"for@ARGV
    
        7
  •  4
  •   Damien    14 年前

    // All whitespace is optional
    function c(f,p){
        var x=f[0],w=p[0],h="substr",s=f[h](2),b=p[h](1),m=0,t=0,r,g,a=0,l,u="length",y="*";
        switch(f[1]){
            case"+":if(x!=w)return;f=x+y+s;
            case y:return x==w&&c(f,b)||c(s,p);
            case"?":return x==w&&c(s,b)||c(s,p)
        }
        if(x=="("){
            o:for(l=[""];t<f[u];t++){
                switch(f[t]){
                    case"|":if(a==1){m=l.push("")-1;continue}break;
                    case"(":if(++a==1)continue;break;
                    case")":if(!--a)break o
                }
                l[m]+=f[t]
            }
            var v=0,e=t+1;
            return l.some(function(g){
                switch(f[t+1]){
                    case y:v=1;
                    case"+":e=t+2;
                        for(var q=0;q<f[u];q++)
                            if(c(g+Array(q).join(f[h](0,e))+f[h](e),p))
                                return 1;
                        return;
                    case"?":v=1;e=t+2;default:if(c(g+f[h](e),p))return 1;
                }
            })||(v&&c(f[h](e),p))
        }
        return p[u]?(x==w&&c(f[h](1),b)):!f[u]
    }
    
    // Make it look nicer
    function test(regex, string) { return !!c('(' + regex + ')', string); }
    
    test('a?b+|(a+b|b+a?)+', 'abb') // true
    test('a?b+|(a+b|b+a?)+', 'babab') // true
    

    function test(reg, str) {
        console.log('Testing ' + reg + ' against ' + str);
        var c = reg[0], d = str[0];
    
    
        switch (reg[1]) {
            case '+': 
                if (c != d) 
                    return false;   
                reg = c + '*' + reg.substr(2);
            case '*': 
                return (c == d && test(reg, str.substr(1))) || test(reg.substr(2), str);
            case '?': 
                return (c == d && test(reg.substr(2), str.substr(1))) || test(reg.substr(2), str);
        }
    
        if (c == '(') {
            var regs = [''];
            o: for (var level = n = i = 0; i < reg.length; i++) {
                //console.log(level + ': ' + n + ': ' + reg[i]);
                switch (reg[i]) {
                    case '|': 
                        if (level == 1) { n = regs.push('') - 1; continue; } 
                        break;
                    case '(': 
                        if (++level == 1) continue; 
                        break;
                    case ')': 
                        if (!--level) break o; 
                        break;
                };
                regs[n] += reg[i];
            }
            //console.log(regs); // An array of alternates (hello|hi) => ['hello', 'hi']        
    
            var optional = false, end = i+1;
            return regs.some(function(jitem) {
                switch (reg[i+1]) {
                    case '*': optional = true; // Fall through
                    case '+': 
                        end = i+2;
                        for (var k = 0; k < reg.length; k++)
                            if (test(jitem + Array(k).join(reg.substr(0, i+2)) + reg.substr(i+2), str))
                                return true;
                        return false;
    
                    case '?': optional = true; end = i+2; // Fall through
                    default: if (test(jitem + reg.substr(end), str)) return true;       
                }
    
            }) || (optional && test(reg.substr(end), str));
        }
    
        if (str == '')
            return reg == '';
    
        return c == d ? test(reg.substr(1), str.substr(1)) : false;
    
    }
    

    test("hello", "hello") => test("ello", "ello") => test("llo", "llo") => test("lo", "lo") => test("o", "o") => test("", "") 返回true。注:裸露 c 函数不支持隐含的替换。换句话说, hello|hi 不起作用;必须用括号括起来: (hello|hi) .