Domanda

Non ho questa stringa:

%{Children^10 Health "sanitation management"^5}

E voglio convertirlo in tokenize questo in un array di hash:

[{:keywords=>"children", :boost=>10}, {:keywords=>"health", :boost=>nil}, {:keywords=>"sanitation management", :boost=>5}]

Sono consapevole del StringScanner ed il Sintassi gioiello ma non riesco a trovare esempi di codice abbastanza per entrambi .

Tutti gli indicatori?

È stato utile?

Soluzione

Per un vero e proprio linguaggio, un lexer è la strada da percorrere - come Guss detto . Ma se la lingua è completo solo così complicato come il tuo esempio, è possibile utilizzare questo trucco veloce:

irb> text = %{Children^10 Health "sanitation management"^5}
irb> text.scan(/(?:(\w+)|"((?:\\.|[^\\"])*)")(?:\^(\d+))?/).map do |word,phrase,boost|
       { :keywords => (word || phrase).downcase, :boost => (boost.nil? ? nil : boost.to_i) }
     end
#=> [{:boost=>10, :keywords=>"children"}, {:boost=>nil, :keywords=>"health"}, {:boost=>5, :keywords=>"sanitation management"}]

Se si sta cercando di analizzare un linguaggio regolare, allora questo metodo sarà sufficiente -. Anche se non vorrebbero molti più complicazioni per rendere il linguaggio non regolare

Una rapida ripartizione del regex:

  • \w+ soddisfa le eventuali parole chiave singola termine
  • (?:\\.|[^\\"]])* utilizza non cattura parentesi ((?:...)) per abbinare il contenuto di una stringa doppia citato sfuggito - sia un simbolo di escape (\n, \", \\, etc.) o qualsiasi singolo carattere che non è un simbolo di fuga o un preventivo fine .
  • "((?:\\.|[^\\"]])*)" cattura solo il contenuto di una frase chiave citata.
  • (?:(\w+)|"((?:\\.|[^\\"])*)") corrisponde una qualsiasi parola chiave - singolo termine o una frase, catturando singoli termini in $1 e frase contenuto in $2
  • \d+ corrisponde a un numero.
  • \^(\d+) cattura un numero che segue un accento circonflesso (^). Dal momento che questo è il terzo set di catturare parentesi, sarà caputred in $3.
  • (?:\^(\d+))? cattura un numero che segue un accento circonflesso se è lì, corrisponde alla stringa vuota altrimenti.

String#scan(regex) corrisponde alla regex contro la stringa come numero di volte possibile, outputing una serie di "incontri". Se l'espressione regolare contiene parentesi cattura, un "match" è un array di oggetti catturati - così $1 diventa match[0], $2 diventa match[1], ecc Qualsiasi parentesi di cattura che non viene confrontata con una parte delle mappe di stringa a una voce nel nil con conseguente "match".

Il #map poi prende queste partite, usa un po 'di magia a blocchi di rompere ogni termine catturato in variabili differenti (avremmo potuto fare do |match| ; word,phrase,boost = *match), e quindi crea i tuoi hash desiderati. Esattamente uno di word o phrase saranno nil, poiché entrambi non possono essere confrontati con l'ingresso, in modo (word || phrase) restituirà il non-nil uno e #downcase convertirà in lettere minuscole. boost.to_i convertirà una stringa in un intero, mentre (boost.nil? ? nil : boost.to_i) garantirà che aumenta nil rimanere nil.

Altri suggerimenti

Ecco un esempio non robusta utilizzando StringScanner. Si tratta di codice che ho appena adattato da Rubino Quiz:. Analisi JSON , che ha un'eccellente spiegazione

require 'strscan'

def test_parse
  text = %{Children^10 Health "sanitation management"^5}
  expected = [{:keywords=>"children", :boost=>10}, {:keywords=>"health", :boost=>nil}, {:keywords=>"sanitation management", :boost=>5}]


  assert_equal(expected, parse(text))
end

def parse(text)
  @input = StringScanner.new(text)

  output = []

  while keyword = parse_string || parse_quoted_string
    output << {
      :keywords => keyword,
      :boost => parse_boost
    }
    trim_space
  end

  output
end

def parse_string
  if @input.scan(/\w+/)
    @input.matched.downcase
  else
    nil
  end
end

def parse_quoted_string
  if @input.scan(/"/)
    str = parse_quoted_contents
    @input.scan(/"/) or raise "unclosed string"
    str
  else
    nil
  end
end

def parse_quoted_contents
  @input.scan(/[^\\"]+/) and @input.matched
end

def parse_boost
  if @input.scan(/\^/)
    boost = @input.scan(/\d+/)
    raise 'missing boost value' if boost.nil?
    boost.to_i
  else
    nil
  end
end

def trim_space
  @input.scan(/\s+/)
end

Quello che avete qui è una grammatica arbitraria, e di analizzarlo cosa si vuole veramente è un lexer - è possibile scrivere un file di grammatica che descrive la sintassi e quindi utilizzare il lexer per generare un parser ricorsivo dalla grammatica.

Scrivi lexer (o anche un parser ricorsivo) non è realmente banale - anche se è un esercizio utile nella programmazione - ma è possibile trovare un elenco di Ruby lexer / parser in questo messaggio e-mail qui: http://newsgroups.derkeiler.com/Archive/Comp/comp. lang.ruby / 2005-11 / msg02233.html

RACC è disponibile come modulo standard di Ruby 1.8, quindi vi consiglio di concentrarsi su che, anche se il suo manuale non è davvero facile da seguire e richiede familiarità con Yacc.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top