So you may have noticed a bit of redundancy
in our handling of "quoted strings".
We return the entire matched text,
which includes these double quotes at the end.
But, in some sense, they're not as much part of the meaning,
as they are beginning and ending markers to tell us when the string starts.
This is our default token value,
but we might want to take a small pair of scissors to this string,
and snip off the quotes at the beginning--and at the end.
Here we have an example of a token definition
that does just that.
After matching the right kind of string,
we take the token value--
the entire thing--
and we're going to use substring selection,
starting at character 1--
this is going to be character 1--
and going up to, but not including,
character negative 1.
Now if you haven't seen this trick before in Python this might surprise you a bit,
but you can count back from the end of the string,
using negative numbers.
So this is actually the negative first character.
And remember that substring inclusion
starts at 1 and goes up to, but not including, the negative 1.
So this is going to get everything from the "q"
over to the "s" in strings--
or in other words, have exactly the effect that we wanted.
Cute little trick, huh?
So now I'm going to show you how to make
a lexical analyzer--which, recall--
is just a bunch of token definitions put together.
I'm going to write it out in Python
and we'll follow along.
This top line--the import statement--is a lot like Import RE.
It's telling Python where to find our lexical analyzer software
or libraries that we're going to build upon.
Just like regular expressions were called RE to save space,
a lexical analyzer is just called "lex"--to save space.
And now I'm going to give a list of all of the tokens that I care about.
Here, I'm just going to be concerned with the 6 that we've previously spoken about:
the Left Angle bracket; the Left Angle bracket, followed by a slash; the Right Angle bracket--
these 3 make tags--
an Equal sign, Strings that are surrounded by quotes,
and every other word.
I'm also going to use a little shortcut.
Before, we used a Whitespace token,
but if you like, you can write the word t_ignore instead
and, implicitly, we'll ignore everything matching this regular expression.
Here's my first token definition rule.
It's for LANGLESLASH.
Here's the regular expression that it matches.
We return the text, unchanged.
Here's another rule for the Left Angle bracket,
the regular expression that it matches--and we return the text, unchanged.
And you'll note that I have the LANGELSLASH rule ahead--
before it--in the file.
And that's because I want this one to win, on ties.
If I see a Left Angle, followed by a slash,
I want it to be the LANGLESLASH (token)--
and not the Left Angle, followed by--say--a WORD(token).
More on that in just a bit; I'll test that out and show it to you.
Here's our rule for the Right Angle bracket.
Here's our rule for the Equal sign token.
Note that while these are long--
they take up a bit of space--they're not actually particularly complicated.
This has mostly been listing 5 regular expressions.
Here's one now.
This one is a little bit more complicated--here's are rule for STRING(token)s.
Here's our regular expression that matches it.
And there I am, dropping off--shaving off--
the surrounding double quotes,
just as you've seen before.
Finally, there's our definition for the WORD(token).
And now what we want to do is use
these regular expressions, together--these token definitions--
to break up a Web page.
So here, I'll make a variable that holds the text of a hypothetical Web page.
"This is my webpage!"
Let's make it more exciting; Ho, ho--this is at least 10 percent more exciting!
This function call tells our lexical analysis library
that we want to use all of the token definitions above
to make a lexical analyzer, and break up strings.
This function call tells it which string to break up.
I want to break up this Web page:
"This is my webpage!"
Now, recall that the output of a lexical analyzer
is a list of tokens.
I want to print out every element of that list.
This call, .token, returns the next token that's available.
If there are not more tokens,
then we're going to break out of this loop.
Otherwise, we print out the token.
Well, let's go see what sort of output we get.
The odds of me having written this, making no mistakes
the first time, from scratch, are about zero.
Let's go see what happens.
Oh! I actually don't really believe it!
We can see the output here at the bottom:
LexToken (WORD, ' T ',' h ', ' i ', ' s '
but it's not quite the output I was expecting.
Oh, here's the mistake that I made--
right now, I only have one character in t_WORD
and if you look down here, instead of seeing
the word, "This"--for "This is my webpage!"--
I have each letter spelled out separately.
Let me fix that.
And now we get more of the output that we were expecting.
Our first token is ' This ';
our next token is a word, ' is '.
Then we saw the Left Angle bracket,
a word, ' b '--for bold,
the Right Angle bracket; a word, ' my ';
the LANGLESLASH,
and then the word, ' webpage '.
“”がついた文字列を扱うと
重複が見られることにお気づきでしたか?
合致した文字はすべて返されますので
“”も含まれています
この記号自体に意味があるわけではなく
文字列の始まりと終わりを知らせてくれます
デフォルトのトークン値は
この部分ですがハサミで不要な部分を切るように
最初と最後に付いた“”を取ってしまいましょう
このハサミを使い変更できる
トークン定義の例を書き出しました
まず文字列を合致させます
そのあと合致した文字列の
トークンの値を抜き出します
そして部分文字列の選択をします
最初の文字を1として始めます
そして文字-1まで進みますが
文字-1は含みません
Pythonに詳しい人はご存知だと思いますが
Pythonでは負の数を使って
文字列の終わりから数えることができます
この部分が-1として始まるのです
部分文字列に含まれるのは
最初の文字である文字1から
文字-1と認識される”の前までです
この文字列の場合qから始まり
最後のsまでが含まれます
まさに私たちが求めていた結果です
使える技ですよね
ではこれから字句解析プログラムの作り方を
皆さんにお見せしましょう
字句解析プログラムとはトークンの定義を
まとめたものです
Pythonを使って
説明をしていきます
1行目にあるimportステートメントは
import reと似ていて
字句解析ソフトウェアやライブラリが
どこにあるのかを
Pythonに知らせています
正規表現がreと省略されているように
字句解析プログラムにはlexと入力します
このあとは入れたいトークンを
リストにしていきます
先ほど説明をした6個を書き出しました
LANGLEは<、LANGLESLASHは</、
RANGLEは>
これら3つはタグに必要な記号です
EQUALは=、
STRINGは“”で囲まれた文字列を表します
それ以外の単語はWORDです
ショートカットを利用しましょう
ホワイトスペースのトークンを書く代わりに
t_ignoreを使ってください
間接的にこの正規表現に合致するものを
すべて無視します
最初のトークン定義のルールは
</についてです
これが合致する正規表現で
テキストは変えずに返します
次は<のルールです
合致する正規表現はこれで
テキストは変えず返します
</を先に持ってきたのには
理由があります
その理由は</を優先させたいからです
</と<が同時に来た時
<のあとに言葉が続くものよりも
</を先に持っていきたいのです
これについてはあとで説明します
>のルールを書きました
これが=のトークンルールです
長いリストになりましたが
内容はそんなに複雑ではありません
5つの正規表現をリストアップしただけです
次は少し複雑になります
文字列のルールを入れていきます
これが合致する正規表現になります
そしてトークン値を囲んでいる“”を
切り取る技を入れました
先ほどお見せしましたね
最後に単語のルールを入れます
今入れた正規表現や
トークンの定義を使い
Webページを分割してみます
ではまず仮想のWebページ用に
このような変数を入れましょう
“This is <b>my</b> webpage”
!を加えて楽しさを10%ほどアップさせましょう
この関数は字句解析ライブラリに
字句解析プログラムを作り文字列を分割する際に
上のトークン定義をすべて使いたいと示しています
次に分割する文字列を指定します
分割したいWebページはこれです
This is my webpage!
字句解析プログラムが出力するのは
トークンのリストです
リストからすべての要素をプリントアウトするには
.tokenを使い次にあるトークンを返します
もし最後のトークンまで行った場合は
ループから抜け出しましょう
トークンがプリントアウトされます
ではどんな出力が得られるか試してみましょう
今回は最初からすべて自分で入力したので
間違いが出てくる可能性は極めて高いです
何が起こるか見てみましょう
これは信じられない結果が出ました
画面下の部分に出力結果が表示されています
WORDとしてT、h、i、sと分割されています
私の予想していた結果ではありません
この部分に間違いを発見しました
t_WORDが1文字しか持てない設定です
出力された結果を見ると
Thisという単語の代わりに
1文字ずつ分割されてしまっています
修正しましょう
これで予想どおりの出力結果が表示されました
最初のトークンはThis
次のトークンはisです
そして次に<が返され
次に太字にするためのb
そして>とmyと続き
</のあとに続くのが
webpage!という単語です
Você deve ter percebido uma certa redundância
no nosso tratamento de strings entre aspas.
Retornamos todo o string que foi casado,
que inclui as aspas duplas no início e no fim.
Mas, em certo sentido, as aspas não são parte do significada,
elas ocorrem no início e no fim apenas para marcar onde o string começa e termina.
Este é o valor default do nosso token,
mas queremos cortar este string,
eliminando as aspas que ocorrem no início e no fim.
Aqui temos um exemplo de uma definição de token
que faze exatamente isso.
Depois de casar o tipo correto de string,
pegamos o valor do token --
o string inteiro --
e vamos usar seleção de substring,
começando no caractere 1 --
este vai ser o caractere de índice 1 --
e indo até, mas não inclusive,
o caractere na posição -1.
Se você não viu este truque em Python antes, isso pode parecer um pouco estranho,
mas é possível contar do fim para o início do string,
usando números negativos.
Portanto, este é de fato o caractere na posição -1.
E, lembre-se que o substring incluído
começa em 1 e vai até, mas não inclusive, o caractere da posição -1.
Portanto, isso é tudo o que vai de `q'
até `s' em `strings' --
ou, em outras palavras, exatamente o que desejamos.
Truque legal, né?
Então, agora vou mostrar como construir
um analisador léxico -- lembre-se:
é simplesmente uma sequência de definições de tokens.
Vou escrever o código Python
e você vai acompanhar comigo.
Esta linha no início -- a cláusula import -- é semelhante a import re.
Ela informa ao compilador Python onde encontrar o software,
ou as bibliotecas, que vão ser utilizadas.
Assim como expressões regulares são chamadas re, de modo conciso,
um analisador léxico é chamado lex, para economizar espaço.
E agora vou escrever uma lista de todos os tokens que nos interessam.
Aqui, estou interessado apenas nos 6 de que falamos anteriormente:
LANGLE, LANGLESLASH, RANGLE --
estas são 3 tags --
EQUAL, STRINGS (entre aspas),
e WORD -- qualquer outra palavra.
Vou também utilizar um pequeno atalho.
Antes, usamos um token WHITESPACE,
mas se você quiser, pode utilizar a palavra t_ignore ao invés disso
e, implicitamente, estaremos ignorando tudo que case com essa expressão regular.
Aqui está minha primeira regra de definição de token.
é para LANGLSLAH.
Aqui está a expressão regular que ele casa,
e retornamos o texto, inalterado.
Aqui outra regra, para LANGLE,
a expressão que ele casa, e retornamos o texto, inalterado.
E você deve notar que a regra LANGLESLASH vem antes
dessa, no arquivo do programa.
E é assim porque queremos que essa tenha preferência.
Se eu vejo < seguido de /,
quero casar com o token LANGLESLASH,
e não com LANGLE seguido, digamos, de WORD.
Mais sobre isso daqui a pouco. Vou testar e mostrar para você.
Aqui está nossa regra RANGLE.
Aqui nossa regra para o token EQUAL.
Note que, embora elas sejam lonhas --
ocupam um certo espaço -- elas não são complicadas.
Em essência, isso consiste em listar 5 expressões regulares.
Aqui está uma.
Esta é um pouco mais complicada -- é a regra para o token STRING.
Aqui está a expressão regular que deve casar,
e aqui estou eliminando
as aspas duplas em torno do string,
como vimos antes.
Finalmente, aqui está nossa definição para WORD.
E agora o que queremos é usar
essas expressões regulares, juntas -- essas definições de tokens --
para `quebrar' uma página web.
Então, aqui, vou definir uma variável que contém o texto de uma página web hipotética.
"This is my webpage!"
Vamos tornar isso mais interessante: ho, ho, isso é pelo menos 10% mais interessante!
Essa chamada de função informa nossa biblioteca de análise léxica
que queremos usar todas essas definições de tokens acima,
para construir um analisador léxico e quebrar strings.
Essa chamada de função indica qual o string que será quebrado.
Queremos quebrar esta página web:
"This is my webpage!"
Agora, lembre-se que a saída do analisador léximo
é uma lista de tokens.
Eu quero imprimir cada elemento desta lista.
Esta chamada -- .token -- retorna o próximo token disponível.
Se não existirem mais tokens,
então saímos fora deste loop.
Caso contrário, imprimimos o token.
Bem, vejamos que tipo de saída obtemos.
A chance de eu ter escrito isto sem cometer erros,
de cara, a partir do zero, é quase zero.
Vamos ver o que acontece.
Oh! Nem acredito!
Podemos ver a saída aqui embaixo:
LexToken WORD, 'T', 'h', í', 's'
mas isso não era a saída que esperávamos!
Oh, aqui está o erro que eu cometi:
agora, eu tenho apenas um caractere em t_WORD,
e se voc6e olhar aqui embaixo, ao invés de ver
a palavra "ThIs" -- de "This is my webpage! " --
temos cada letra separadamente.
Vamos consertar isso.
E agora temos uma saída mais pareceida com o que esperávamos.
Noss primeiro tokens é "This";
nosso token seguinte é um WORD -- "is".
Então vemos LANGLE,
um WORD -- "b" -- que indica negrito,
o LANGLE, um WORD -- "my",
o LANGLESLASH,
e então o WORD "webpage".