Вопрос

Как бы вы написали Разборная экспрессия грамматика в любом из следующих генераторов парсеров (Peg.js., Цитрусовые, Treetop) который может обрабатывать Python / Haskell / CoffeScript стиль вдавливания:

Примеры неиженадельного языка программирования:

square x =
    x * x

cube x =
    x * square x

fib n =
  if n <= 1
    0
  else
    fib(n - 2) + fib(n - 1) # some cheating allowed here with brackets

Обновлять:Не пытайтесь написать интерпретатор для примеров выше. Я заинтересован только в проблеме отступа. Другой пример может быть расставлен следующим образом:

foo
  bar = 1
  baz = 2
tap
  zap = 3

# should yield (ruby style hashmap):
# {:foo => { :bar => 1, :baz => 2}, :tap => { :zap => 3 } }
Это было полезно?

Решение

Чистый колыш не может разбирать углубление.

Но peg.js. могу.

Я сделал быстроразъемный эксперимент (вдохновленный комментарию IRA Baxter о обмане) и написал простой токенизатор.

Для более полного решения (полный парсер), пожалуйста, смотрите этот вопрос: Уровень вдавливания разбора с peg.js

/* Initializations */
{
  function start(first, tail) {
    var done = [first[1]];
    for (var i = 0; i < tail.length; i++) {
      done = done.concat(tail[i][1][0])
      done.push(tail[i][1][1]);
    }
    return done;
  }

  var depths = [0];

  function indent(s) {
    var depth = s.length;

    if (depth == depths[0]) return [];

    if (depth > depths[0]) {
      depths.unshift(depth);
      return ["INDENT"];
    }

    var dents = [];
    while (depth < depths[0]) {
      depths.shift();
      dents.push("DEDENT");
    }

    if (depth != depths[0]) dents.push("BADDENT");

    return dents;
  }
}

/* The real grammar */
start   = first:line tail:(newline line)* newline? { return start(first, tail) }
line    = depth:indent s:text                      { return [depth, s] }
indent  = s:" "*                                   { return indent(s) }
text    = c:[^\n]*                                 { return c.join("") }
newline = "\n"                                     {}

depths это стопка отступов. Отступ () возвращает массив токенов вдавливания и начать () разворачивать массив, чтобы максимальный парсер ведут себя как поток.

peg.js. производит для текста:

alpha
  beta
  gamma
    delta
epsilon
    zeta
  eta
theta
  iota

Эти результаты:

[
   "alpha",
   "INDENT",
   "beta",
   "gamma",
   "INDENT",
   "delta",
   "DEDENT",
   "DEDENT",
   "epsilon",
   "INDENT",
   "zeta",
   "DEDENT",
   "BADDENT",
   "eta",
   "theta",
   "INDENT",
   "iota",
   "DEDENT",
   "",
   ""
]

Этот токенизатор даже ловит плохие отступы.

Другие советы

Я думаю, что чувствительный к отступе язык, чувствительный к контексту. Я верю, что PEG может делать только свободные контекстные лангаги.

Обратите внимание, что, хотя ответ NALPO, безусловно, верна, что peg.js может сделать это через внешнее состояние (то есть страшные глобальные переменные), это может быть опасный путь к прогулке (хуже, чем обычные проблемы с глобальными переменными). Некоторые правила могут изначально соответствовать (а затем запускать свои действия), но родительские правила могут не удавать, что приведет к тому, что действие будет недействительным. Если внешнее состояние изменяется в таком действии, вы можете в конечном итоге с помощью недопустимого состояния. Это супер ужасно, и может привести к треморов, рвоту и смерти. Некоторые проблемы и решения этого находятся в комментариях здесь: https://github.com/dmajda/pegjs/issues/45.

Так что мы действительно делаем здесь с отступом, создают что-то вроде блоков в стиле C, которые часто имеют собственную лексику. Если бы я писал компилятор для такого языка, как, я думаю, я бы попробую, чтобы лексер следил за отступом. Каждый раз, когда отступа увеличивается, что он может вставить токен '{'. Точно так же каждый раз, когда он уменьшается, он может вставлять «}» токен. Затем написание грамматики экспрессии с явными фигурными скобками для представления лексической области становится более прямой вперед.

Вы можете сделать это в Treetop, используя семантические предикаты. В этом случае вам нужен семантический предикат, который обнаруживает закрытие блока с отступом в белосброс из-за возникновения другой линии, которая имеет одинаковую или меньшую отступ. Предикат должен подсчитать отступ от линии открывания и возвращает True (Block Clock), если отступ текущей линии закончил на той же или более короткой длине. Поскольку состояние закрытия является контекстно-зависимым, оно не должно быть изменено. Вот пример кода, который я собираюсь добавить в документацию Treetop. Обратите внимание, что я переопределил метод Syntaxnode Treetop Syntaxnode, чтобы облегчить визуализацию результата.

grammar IndentedBlocks
  rule top
    # Initialise the indent stack with a sentinel:
    &{|s| @indents = [-1] }
    nested_blocks
    {
      def inspect
        nested_blocks.inspect
      end
    }
  end

  rule nested_blocks
    (
      # Do not try to extract this semantic predicate into a new rule.
      # It will be memo-ized incorrectly because @indents.last will change.
      !{|s|
        # Peek at the following indentation:
        save = index; i = _nt_indentation; index = save
        # We're closing if the indentation is less or the same as our enclosing block's:
        closing = i.text_value.length <= @indents.last
      }
      block
    )*
    {
      def inspect
        elements.map{|e| e.block.inspect}*"\n"
      end
    }
  end

 rule block
    indented_line       # The block's opening line
    &{|s|               # Push the indent level to the stack
      level = s[0].indentation.text_value.length
      @indents << level
      true
    }
    nested_blocks       # Parse any nested blocks
    &{|s|               # Pop the indent stack
      # Note that under no circumstances should "nested_blocks" fail, or the stack will be mis-aligned
      @indents.pop
      true
    }
    {
      def inspect
        indented_line.inspect +
          (nested_blocks.elements.size > 0 ? (
            "\n{\n" +
            nested_blocks.elements.map { |content|
              content.block.inspect+"\n"
            }*'' +
            "}"
          )
          : "")
      end
    }
  end

  rule indented_line
    indentation text:((!"\n" .)*) "\n"
    {
      def inspect
        text.text_value
      end
    }
  end

  rule indentation
    ' '*
  end
end

Вот небольшой тестовый драйвер программы, чтобы вы могли пробовать ее легко:

require 'polyglot'
require 'treetop'
require 'indented_blocks'

parser = IndentedBlocksParser.new

input = <<END
def foo
  here is some indented text
    here it's further indented
    and here the same
      but here it's further again
      and some more like that
    before going back to here
      down again
  back twice
and start from the beginning again
  with only a small block this time
END 

parse_tree = parser.parse input

p parse_tree

Я знаю, что это старая нить, но я просто хотел добавить код PEGJS на ответы. Этот код будет проанализировать часть текста и «гнездить» в своем роде «AST-ISH» структуры. Это только один глубокий, и он выглядит уродливым, кроме того, он на самом деле не использует значения возврата для создания правильной структуры, но сохраняет дерево на память вашего синтаксиса, и он вернет это в конце. Это вполне может стать громоздким и вызвать некоторые проблемы с производительностью, но, по крайней мере, это делает то, что он должен.

Примечание. Убедитесь, что у вас есть вкладки вместо пробелов!

{ 
    var indentStack = [], 
        rootScope = { 
            value: "PROGRAM",
            values: [], 
            scopes: [] 
        };

    function addToRootScope(text) {
        // Here we wiggle with the form and append the new
        // scope to the rootScope.

        if (!text) return;

        if (indentStack.length === 0) {
            rootScope.scopes.unshift({
                text: text,
                statements: []
            });
        }
        else {
            rootScope.scopes[0].statements.push(text);
        }
    }
}

/* Add some grammar */

start
  = lines: (line EOL+)*
    { 
        return rootScope;
    }


line
  = line: (samedent t:text { addToRootScope(t); }) &EOL
  / line: (indent t:text { addToRootScope(t); }) &EOL
  / line: (dedent t:text { addToRootScope(t); }) &EOL
  / line: [ \t]* &EOL
  / EOF

samedent
  = i:[\t]* &{ return i.length === indentStack.length; }
    {
        console.log("s:", i.length, " level:", indentStack.length);
    }

indent
  = i:[\t]+ &{ return i.length > indentStack.length; }
    {
        indentStack.push(""); 
        console.log("i:", i.length, " level:", indentStack.length);
    }

dedent
    = i:[\t]* &{ return i.length < indentStack.length; }
      {
          for (var j = 0; j < i.length + 1; j++) {
              indentStack.pop();
          } 
          console.log("d:", i.length + 1, " level:", indentStack.length);  
      }

text
    = numbers: number+  { return numbers.join(""); } 
    / txt: character+   { return txt.join(""); }


number
    = $[0-9] 

character 
    = $[ a-zA-Z->+]  
__
    = [ ]+

_ 
    = [ ]*

EOF 
    = !.

EOL
    = "\r\n" 
    / "\n" 
    / "\r"
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top