Après avoir défini un semblant de méthode de travail on s’attaque aujourd’hui à l’écriture d’un tokenizer pour Naam.

Le tokenizer

Le code du projet est en ligne sur Github à l’adresse suivante: naam. N’hésitez pas à le consulter. Les extraits qui suivront sont tirés de la version 0.0.1.

La tokenization est le processus de séparation des éléments basiques d’un langage. C’est la première étape de l’analyse lexicale. Par exemple, à partir du programme suivant:

sign(n)=
1  if n > 0
-1 if n < 0
0  else

print sign(4)

Je veut obtenir la suite de tokens suivante:

sign
(
n
)
=

1
if
n
>
0

-1
if
n
<
0

0
else


print
sign
(
4
)

Je vais d’abord introduire la classe Main, qui est certainement temporaire et aussi (exceptionnellement) la seule à ne pas être testée. Elle est juste là pour me permettre de visualiser les résultats. Le principe est très simple:

class Main
  def self.run(filename)
    source_lines = Reader.read filename
    source_lines.each do |line|
      tkr = Parser::Tokenizer.new(line)
      while tkr.has_more_token?
        puts tkr.next_token
      end
    end
  end
end

Maintenant, on passe au truc intéressant, le tokenizer proprement dit:

module Naam::Parser

  # Internal: Tokenize a string of naam code.
  class Tokenizer

    # Public: Initialize a new Tokenizer object, ready to
    # tokenize a string of naam code.
    #
    # code - A String of naam code.
    def initialize code
      @index = 0
      @token = ''
      @look_ahead = ''
      @codeline = code
      forward_look_ahead
    end

    # Public: Get the next token from the code.
    #
    # Returns The String token.
    def next_token
      token = produce_next_token
      skip_white
      @token = ''
      token
    end

    # Public: Tells if a token is available.
    #
    # Returns Boolean.
    def has_more_token?
      @index <= @codeline.size
    end

    private

    # Look to the next character in the code.
    #
    # Returns nothing.
    def forward_look_ahead
      @look_ahead = @codeline[@index, 1]
      @index += 1
    end

    # Returns the String next available token.
    def produce_next_token
      if @look_ahead =~ /[0-9]/
        get_integer
      elsif @look_ahead == '('
        get_paro
      elsif @look_ahead == ')'
        get_parc
      elsif @look_ahead == '='
        get_eq
      elsif @look_ahead == "\n"
        get_eol
      else
        get_word
      end
    end

    # Skip all «white» characters until next non-white one.
    # Currently only spaces are considered as white.
    #
    # Returns nothing.
    def skip_white
      forward_look_ahead while @look_ahead == ' '
    end

    # Returns a String of what naam considered an integer.
    def get_integer
      add_look_ahead while @look_ahead =~ /[0-9]/
      @token
    end

    # Returns String opened parenthesis.
    def get_paro; add_this_char; end

    # Returns String closed parenthesis.
    def get_parc; add_this_char; end

    # Returns String equal symbol "=".
    def get_eq; add_this_char; end

    # Returns String end of line "\n".
    def get_eol; add_this_char; end

    # Add current character to the current token and return it.
    # Usefull shorthand for single character's tokens.
    #
    # Returns the String current token.
    def add_this_char
      add_look_ahead
      @token
    end

    # If it's not anything else, it's what naam language call a word.
    #
    # Returns String.
    def get_word
      add_look_ahead while not @look_ahead =~ /[\(\)=\n ]/
      @token
    end

    # Add current character to current token, then look for the
    # next char, ready for another cycle.
    #
    # Returns nothing.
    def add_look_ahead
      @token << @look_ahead
      forward_look_ahead
    end

  end
end

Chaque méthode est suffisament documentée pour que vous puissiez comprendre la logique de la bestiole. La prochaine fois on s’attaque au lexer.

À demain.