Comments 4
Синтаксис Racc несколько «не рубишен». Я как-то сел портировать питоновский Ply (который очень хорош по читабельности) на Ruby. Результат можно посмотреть тут: github.com/farcaller/rly
В целом, я старался оптимизировать код, но лексер несколько медленный, так как на регулярках (хотя в ruby2.0 он заметно шустрее). Если интересно – я могу детальнее описать механизм работы и подводные камни LALR(1)-генераторов на руби.
В целом, я старался оптимизировать код, но лексер несколько медленный, так как на регулярках (хотя в ruby2.0 он заметно шустрее). Если интересно – я могу детальнее описать механизм работы и подводные камни LALR(1)-генераторов на руби.
+3
Да, спасибо, интересно! А в каком ваш Rly состоянии, достаточно stable для использования?
Когда мне нужно было сделать парсер, и я искал что-то по теме, находилось критически мало доступной информации с примерами по Racc, поэтому собственно и написал статью. А вот про ваш Rly вообще ничего не попалось.
Попробовал реализовать все то же самое на Rly.
Лексер делается проще, не вручную через StringScanner. Получился довольно компактный. Кажется, что так меньше тонкого контроля, но возможно он в большинстве случаев и не нужен, и все можно разрулить грамотными регулярными выражениями и их правильной очередностью.
И если я конечно тут все правильно понял, то при создании парсера не очень понравилось, что для свертывания нетерминала задается только один блок, несмотря на наличие в правилах choise. И возможные варианты нужно разруливать в блоке самостоятельно, например при помощи case.
Когда мне нужно было сделать парсер, и я искал что-то по теме, находилось критически мало доступной информации с примерами по Racc, поэтому собственно и написал статью. А вот про ваш Rly вообще ничего не попалось.
Попробовал реализовать все то же самое на Rly.
Лексер делается проще, не вручную через StringScanner. Получился довольно компактный. Кажется, что так меньше тонкого контроля, но возможно он в большинстве случаев и не нужен, и все можно разрулить грамотными регулярными выражениями и их правильной очередностью.
require "rly"
class Lexer < Rly::Lex
literals '{}(),;='
ignore " \t\n"
token :T_OBJECTID, /[\w\.]+:\"*\w*\"*:\w+/
token :T_NUMBER, /\d+/
token :T_NULL, /NULL(?=[^\w])/
token :T_BOOLEAN, /[TF]{1}(?=[^\w])/
token :T_IDENTIFIER, /\w+/
token :T_STRING, /\".*?(?<!\\)\"/m
token :T_REFERENCE, /\<.*?\>/
end
И если я конечно тут все правильно понял, то при создании парсера не очень понравилось, что для свертывания нетерминала задается только один блок, несмотря на наличие в правилах choise. И возможные варианты нужно разруливать в блоке самостоятельно, например при помощи case.
require "rly"
class Parser < Rly::Yacc
rule 'input: object' do |*t|
t[0].value = t[1].value
end
rule 'object: T_OBJECTID "{" attributes "}"' do |*t|
oid = t[1].value.split(':')
t[0].value = {
:id => dequote(oid[0]),
:name => convertNULL(dequote(oid[1])),
:type => dequote(oid[2])
}.merge(t[3].value)
end
rule 'attributes: attribute | attributes attribute' do |*t|
case
when t[1].type == :attribute
t[0].value = t[1].value
when t[1].type == :attributes
t[0].value = t[1].value.merge(t[2].value)
end
end
rule 'attribute: T_IDENTIFIER "=" value ";"' do |*t|
t[0].value = {t[1].value => t[3].value}
end
rule 'values: value | values "," value' do |*t|
case
when t[1].type == :value
t[0].value = [t[1].value]
when t[1].type == :values
t[0].value = t[1].value.concat([t[3].value])
end
end
rule 'value: T_STRING |
T_REFERENCE |
T_NUMBER |
T_BOOLEAN |
T_NULL |
"{" object "}" |
"(" values ")"' do |*t|
case
when t[1].type == :T_STRING || t[1].type == :T_REFERENCE
t[0].value = dequote(t[1].value)
when t[1].type == :T_NUMBER
t[0].value = t[1].value.to_i
when t[1].type == :T_BOOLEAN
t[0].value = t[1].value == 'T'
when t[1].type == :T_NULL
t[0].value = nil
when t[2].type == :object || t[2].type == :values
t[0].value = t[2].value
end
end
def dequote(text)
text.gsub(/\A["<]|[">]\Z/, '').strip
end
def convertNULL(text)
text.upcase == "NULL" ? nil : text
end
end
0
> что для свертывания нетерминала задается только один блок, несмотря на наличие в правилах choise
можно просто сделать несколько правил.
Я недавно выкатил 0.2.3, там несколько «хелперов» для типичных задач свертки + альтернативный вариант блоков (в аргументах – value, результат присваивается в lhs.value).
Парсер условно стабилен. В том плане, что он делает то что мне нужно на тех грамматиках, на которых я его гоняю (c, tablegen). Естественно, это 0.2.3, так что баги там, вероятно, есть. Так что багрепорты (особенно с патчами) принимаются на ура.
можно просто сделать несколько правил.
Я недавно выкатил 0.2.3, там несколько «хелперов» для типичных задач свертки + альтернативный вариант блоков (в аргументах – value, результат присваивается в lhs.value).
Парсер условно стабилен. В том плане, что он делает то что мне нужно на тех грамматиках, на которых я его гоняю (c, tablegen). Естественно, это 0.2.3, так что баги там, вероятно, есть. Так что багрепорты (особенно с патчами) принимаются на ура.
0
И да, мне кажется корректнее, если «типизацию» значения (например приведение значения токена в int) должен делать лексер. Фактически, я еще ни разу не использовал свойство *Token#type в парсере.
0
Sign up to leave a comment.
Articles
Change theme settings
Практическое использование Racc — генератора LALR(1) парсера для Ruby