Tokenizer de rapidito

Posted: June 7th, 2009 | Author: FreedomCoder | Filed under: Open Source, Programming | Tags: , , | No Comments »

Después de mirar un rato el estado de las bibliotecas para hacer wikis en Ruby, y descubrir que ninguna me servía, Decidí que tenía que poner cartas en el asunto y hacer la mía. Mi idea es implementar el markup de Trac, haciéndolo extensible, y agregarle un par de cositas que por ahora son un secreto :-p.

Lo importante del asunto es que puse manos en el asunto. Al final, después de un intento fallido de hackear mi camino al andar, decidí que lo mejor es armar un tokenizer y un parser que use esos tokens para generar el árbol del que extraeré el HTML.

Así que me puse a programar. Como no encontré ningún tokenizer en ruby, programé uno. El tokenizer se contruye con un montón de expresiones regulares que definen cada delimitador. Después se le setea una fuente de caracteres (un string) y separa el string en los delimitadores de arriba (que se devuelven como símbolos) y cadenas que no matchean con ninguno de los delimitadores (que devuelve como strings).

Bueno, basta de cháchara, acá tá el código:

module Rapidito
  class Tokenizer

    def initialize( *delimiters )
      @regexp = Regexp.union( *delimiters  + [/$/] )
    end

    attr_accessor :source

    def has_next?
      ! @source.empty?
    end

    def next_token
      p = (@source =~ @regexp)
      if p == 0 #delimiter
        token = nil
        @source.sub!( @regexp ) { |match| token=match.to_sym; "" }
        token
      else #text
        token = @source[0,p]
        @source = @source[p,@source.length]
        token
      end
    end

    def all_tokens
      tokens = []
      while has_next?
        tokens << next_token
      end
      tokens
    end

  end
end

Y, acá abajo la única documentación que hice hasta ahora, o sea los tests de unidad:

require 'test/unit'
require 'rapidito/tokenizer'

include Rapidito

class TokenizerTest < Test::Unit::TestCase

  def test_no_token
    tok = Tokenizer.new
    tok.source = "aaaa"
    assert_equal true, tok.has_next?
    assert_equal "aaaa", tok.next_token
    assert_equal false, tok.has_next?
  end

  def test_two_delimiters
    tok = Tokenizer.new( /\|/, /;;/ )
    tok.source = "aa|bbb;;;;cccc"
    assert_equal [ "aa", :"|", "bbb", :";;", :";;", "cccc" ], tok.all_tokens

    tok.source = "aa;;bbb||cccc"
    assert_equal [ "aa", :";;", "bbb", :"|", :"|", "cccc" ], tok.all_tokens
  end

  def test_choose_first_match
    tok = Tokenizer.new( /aa/, /aaa/ )
    tok.source = "aaa"
    assert_equal [ :aa, "a" ], tok.all_tokens
  end

end

Happy hacking,
Aureliano.

PD: ¿Prefieren que ponga el código con syntax highlighting?

(Via aurelianito.) Original Link: Tokenizer de rapidito