Uma ferramenta semelhante ao grep
que entende a sintaxe do código-fonte e permite a manipulação além da pesquisa.
Assim como grep
, as expressões regulares são uma primitiva básica. Ao contrário grep
, capacidades adicionais permitem maior precisão , com opções de manipulação . Isso permite que srgn
opere ao longo de dimensões que expressões regulares e ferramentas IDE ( Renomear tudo , Encontrar todas as referências , ...) por si só não podem, complementando-as.
srgn
é organizado em torno de ações a serem tomadas (se houver), agindo apenas dentro de escopos precisos e opcionalmente conscientes da gramática da linguagem . Em termos de ferramentas existentes, pense nisso como uma mistura de tr
, sed
, ripgrep e tree-sitter
, com um objetivo de design de simplicidade : se você conhece regex e o básico da linguagem com a qual está trabalhando, está pronto para prosseguir .
Dica
Todos os trechos de código exibidos aqui são verificados como parte de testes de unidade usando o binário srgn
real. O que é apresentado aqui certamente funcionará.
O uso mais simples srgn
funciona de forma semelhante a tr
:
$ echo ' Hello World! ' | srgn ' [wW]orld ' ' there ' # replacement
Hello there !
As correspondências para o padrão de expressão regular '[wW]orld'
(o escopo ) são substituídas (a ação ) pelo segundo argumento posicional. Zero ou mais ações podem ser especificadas:
$ echo ' Hello World! ' | srgn ' [wW]orld ' # zero actions: input returned unchanged
Hello World !
$ echo ' Hello World! ' | srgn --upper ' [wW]orld ' ' you ' # two actions: replacement, afterwards uppercasing
Hello YOU !
A substituição é sempre realizada primeiro e especificada posicionalmente. Quaisquer outras ações são aplicadas e fornecidas como sinalizadores de linha de comando.
Da mesma forma, mais de um escopo pode ser especificado: além do padrão regex, um escopo com reconhecimento de gramática de linguagem pode ser fornecido, que abrange elementos sintáticos do código-fonte (pense, por exemplo, "todos os corpos de definições class
em Python" ). Se ambos forem fornecidos, o padrão de expressão regular será aplicado apenas dentro do primeiro escopo da linguagem . Isso permite pesquisa e manipulação com precisão que normalmente não é possível usando expressões regulares simples e servindo uma dimensão diferente de ferramentas como Renomear tudo em IDEs.
Por exemplo, considere este arquivo fonte Python (inútil):
"""Module for watching birds and their age."""
from dataclasses import dataclass
@ dataclass
class Bird :
"""A bird!"""
name : str
age : int
def celebrate_birthday ( self ):
print ( "?" )
self . age += 1
@ classmethod
def from_egg ( egg ):
"""Create a bird from an egg."""
pass # No bird here yet!
def register_bird ( bird : Bird , db : Db ) -> None :
assert bird . age >= 0
with db . tx () as tx :
tx . insert ( bird )
que pode ser pesquisado usando:
$ cat birds.py | srgn --python ' class ' ' age '
11: age: int
15: self.age += 1
A string age
foi procurada e encontrada apenas nas definições class
Python (e não, por exemplo, em corpos de funções como register_bird
, onde age
também ocorre e seria quase impossível excluir da consideração no vanilla grep
). Por padrão, este 'modo de pesquisa' também imprime números de linha. O modo de pesquisa é inserido se nenhuma ação for especificada e uma linguagem como --python
recebe 1 - pense nisso como 'ripgrep, mas com elementos de linguagem sintática'.
A pesquisa também pode ser realizada entre linhas, por exemplo, para encontrar métodos (também conhecidos como def
dentro de class
) sem docstrings:
$ cat birds.py | srgn --python ' class ' ' def .+:ns+[^"s]{3} ' # do not try this pattern at home
13: def celebrate_birthday(self):
14: print("?")
Observe como isso não aparece from_egg
(tem uma docstring) ou register_bird
(não é um método, def
fora class
).
Os próprios escopos de linguagem também podem ser especificados várias vezes. Por exemplo, no trecho Rust
pub enum Genre {
Rock ( Subgenre ) ,
Jazz ,
}
const MOST_POPULAR_SUBGENRE : Subgenre = Subgenre :: Something ;
pub struct Musician {
name : String ,
genres : Vec < Subgenre > ,
}
vários itens podem ser perfurados cirurgicamente como
$ cat music.rs | srgn --rust ' pub-enum ' --rust ' type-identifier ' ' Subgenre ' # AND'ed together
2: Rock(Subgenre),
onde apenas as linhas que correspondem a todos os critérios são retornadas, agindo como um e lógico entre todas as condições. Observe que as condições são avaliadas da esquerda para a direita, impedindo que algumas combinações façam sentido: por exemplo, procurar por um corpo class
Python dentro de doc-strings
Python geralmente não retorna nada. O inverso funciona conforme o esperado:
$ cat birds.py | srgn --py ' class ' --py ' doc-strings '
8: """A bird!"""
19: """Create a bird from an egg."""
Nenhum documento fora dos órgãos class
é divulgado!
O sinalizador -j
altera esse comportamento: desde a interseção da esquerda para a direita até a execução de todas as consultas de forma independente e a junção de seus resultados, permitindo pesquisar de várias maneiras ao mesmo tempo:
$ cat birds.py | srgn -j --python ' comments ' --python ' doc-strings ' ' bird[^s] '
8: """A bird!"""
19: """Create a bird from an egg."""
20: pass # No bird here yet!
O padrão bird[^s]
foi encontrado dentro de comentários ou docstrings da mesma forma, não apenas "docstrings dentro de comentários".
Se a entrada padrão não for fornecida, srgn
sabe como encontrar arquivos de origem relevantes automaticamente, por exemplo, neste repositório:
$ srgn --python ' class ' ' age '
docs/samples/birds
11: age: int
15: self.age += 1
docs/samples/birds.py
9: age: int
13: self.age += 1
Ele percorre recursivamente seu diretório atual, encontrando arquivos baseados em extensões de arquivo e linhas shebang, processando em alta velocidade. Por exemplo, srgn --go strings 'd+'
encontra e imprime todas as ~140.000 execuções de dígitos em strings Go literais dentro da base de código Kubernetes de ~3.000.000 linhas de código Go em 3 segundos em 12 núcleos de M3. Para obter mais informações sobre como trabalhar com muitos arquivos, veja abaixo.
Escopos e ações podem ser combinados quase arbitrariamente (embora muitas combinações não sejam úteis – ou mesmo significativas). Por exemplo, considere este snippet Python (para exemplos usando outras linguagens suportadas, veja abaixo):
"""GNU module."""
def GNU_says_moo ():
"""The GNU function -> say moo -> ✅"""
GNU = """
GNU
""" # the GNU...
print ( GNU + " says moo" ) # ...says moo
contra o qual o seguinte comando é executado:
cat gnu.py | srgn --titlecase --python ' doc-strings ' ' (?' ' $1: GNU ? is not Unix '
A anatomia dessa invocação é:
--titlecase
(uma ação) irá Titlecase tudo encontrado no escopo
--python 'doc-strings'
(um escopo) terá como escopo (ou seja, levará apenas em consideração) docstrings de acordo com a gramática da linguagem Python
'(? (um escopo) vê apenas o que já estava no escopo da opção anterior e irá restringi-lo ainda mais. Nunca pode estender o escopo anterior. O escopo da expressão regular é aplicado após qualquer escopo de linguagem.
(? é uma sintaxe lookbehind negativa, demonstrando como esse recurso avançado está disponível. Strings de
GNU
prefixadas por The
não serão consideradas.
'$1: GNU ? is not Unix'
(uma ação) substituirá cada ocorrência correspondente (ou seja, cada seção de entrada encontrada no escopo) por esta string. As ocorrências correspondentes são padrões de '(? apenas em docstrings Python. Notavelmente, esta string de substituição demonstra:
$1
, que carrega o conteúdo capturado pelo primeiro grupo de regex de captura. Isso é ([az]+)
, pois (? não está capturando.
O comando faz uso de múltiplos escopos (linguagem e padrão regex) e múltiplas ações (substituição e titlecasing). O resultado então é lido
"""Module: GNU ? Is Not Unix."""
def GNU_says_moo ():
"""The GNU function -> say moo -> ✅"""
GNU = """
GNU
""" # the GNU...
print ( GNU + " says moo" ) # ...says moo
onde as alterações são limitadas a:
- """GNU module."""
+ """Module: GNU ? Is Not Unix."""
def GNU_says_moo():
"""The GNU -> say moo -> ✅"""
Aviso
Enquanto srgn
estiver na versão beta (versão principal 0), certifique-se de processar (recursivamente) apenas os arquivos que você pode restaurar com segurança.
O modo de pesquisa não substitui arquivos, por isso é sempre seguro.
Veja abaixo o resultado completo da ajuda da ferramenta.
Observação
Os idiomas suportados são
Baixe um binário pré-construído dos lançamentos.
Esta caixa fornece seus binários em um formato compatível com cargo-binstall
:
cargo install cargo-binstall
(pode demorar um pouco)cargo binstall srgn
(alguns segundos, enquanto baixa binários pré-construídos do GitHub)É garantido que essas etapas funcionem™, pois são testadas em CI. Eles também funcionam se nenhum binário pré-construído estiver disponível para sua plataforma, pois a ferramenta voltará a compilar a partir do código-fonte.
Uma fórmula está disponível via:
brew install srgn
Disponível via instável:
nix-shell -p srgn
Disponível através do AUR.
Uma porta está disponível:
sudo port install srgn
Todas as imagens do executor do GitHub Actions vêm com cargo
pré-instalado e cargo-binstall
fornece uma ação GitHub conveniente:
jobs :
srgn :
name : Install srgn in CI
# All three major OSes work
runs-on : ubuntu-latest
steps :
- uses : cargo-bins/cargo-binstall@main
- name : Install binary
run : >
cargo binstall
--no-confirm
srgn
- name : Use binary
run : srgn --version
O texto acima termina em apenas 5 segundos no total, pois nenhuma compilação é necessária. Para obter mais contexto, consulte o conselho do cargo-binstall
sobre CI.
No Linux, gcc
funciona.
No macOS, use clang
.
No Windows, o MSVC funciona.
Selecione "Desenvolvimento de desktop com C++" na instalação.
cargo install srgn
cargo add srgn
Veja aqui para mais.
Vários shells são suportados para scripts de conclusão de shell. Por exemplo, anexe eval "$(srgn --completions zsh)"
a ~/.zshrc
para conclusões em ZSH. Uma sessão interativa pode então ser semelhante a:
A ferramenta foi projetada em torno de escopos e ações . Os escopos restringem as partes da entrada a serem processadas. As ações então executam o processamento. Geralmente, tanto os escopos quanto as ações são combináveis, portanto, mais de um de cada pode ser passado. Ambos são opcionais (mas não fazer nada é inútil); não especificar nenhum escopo implica que toda a entrada está no escopo.
Ao mesmo tempo, há uma sobreposição considerável com tr
simples: a ferramenta foi projetada para ter correspondência estreita nos casos de uso mais comuns e só ir além quando necessário.
A ação mais simples é a substituição. É acessado especialmente (como argumento, não como opção) para compatibilidade com tr
e ergonomia geral. Todas as outras ações são fornecidas como sinalizadores ou opções, caso tenham um valor.
Por exemplo, substituições simples de um único caractere funcionam como em tr
:
$ echo ' Hello, World! ' | srgn ' H ' ' J '
Jello, World!
O primeiro argumento é o escopo (literal H
neste caso). Qualquer coisa que corresponda a ele está sujeita a processamento (substituição por J
, o segundo argumento, neste caso). No entanto, não existe um conceito direto de classes de caracteres como em tr
. Em vez disso, por padrão, o escopo é um padrão de expressão regular, portanto suas classes podem ser usadas com efeito semelhante:
$ echo ' Hello, World! ' | srgn ' [a-z] ' ' _ '
H____, W____!
A substituição ocorre avidamente durante toda a partida por padrão (observe a classe de caracteres UTS, que lembra tr
's [:alnum:]
):
$ echo ' ghp_oHn0As3cr3T!! ' | srgn ' ghp_[[:alnum:]]+ ' ' * ' # A GitHub token
*!!
Recursos avançados de regex são suportados, por exemplo, lookarounds:
$ echo ' ghp_oHn0As3cr3T ' | srgn ' (?<=ghp_)[[:alnum:]]+ ' ' * '
ghp_*
Tenha cuidado ao usá-los com segurança, pois os padrões avançados não possuem certas garantias de segurança e desempenho. Se não forem usados, o desempenho não será afetado.
A substituição não se limita a um único caractere. Pode ser qualquer string, por exemplo, para corrigir esta citação:
$ echo ' "Using regex, I now have no issues." ' | srgn ' no issues ' ' 2 problems '
"Using regex, I now have 2 problems."
A ferramenta é totalmente compatível com Unicode, com suporte útil para certas classes de caracteres avançadas:
$ echo ' Mood: ? ' | srgn ' ? ' ' ? '
Mood: ?
$ echo ' Mood: ???? :( ' | srgn ' p{Emoji_Presentation} ' ' ? '
Mood: ???? :(
As substituições reconhecem variáveis, que são disponibilizadas para uso por meio de grupos de captura de regex. Os grupos de captura podem ser numerados ou opcionalmente nomeados. O grupo de captura zero corresponde a toda a partida.
$ echo ' Swap It ' | srgn ' (w+) (w+) ' ' $2 $1 ' # Regular, numbered
It Swap
$ echo ' Swap It ' | srgn ' (w+) (w+) ' ' $2 $1$1$1 ' # Use as many times as you'd like
It SwapSwapSwap
$ echo ' Call +1-206-555-0100! ' | srgn ' Call (+?d-d{3}-d{3}-d{4}).+ ' ' The phone number in "$0" is: $1. ' # Variable `0` is the entire match
The phone number in "Call +1-206-555-0100!" is: +1-206-555-0100.
Um caso de uso mais avançado é, por exemplo, refatoração de código usando grupos de captura nomeados (talvez você possa criar um mais útil...):
$ echo ' let x = 3; ' | srgn ' let (?[a-z]+) = (?.+); ' ' const $var$var = $expr + $expr; '
const xx = 3 + 3;
Como no bash, use chaves para desambiguar variáveis do conteúdo imediatamente adjacente:
$ echo ' 12 ' | srgn ' (d)(d) ' ' $2${1}1 '
211
$ echo ' 12 ' | srgn ' (d)(d) ' ' $2$11 ' # will fail (`11` is unknown)
$ echo ' 12 ' | srgn ' (d)(d) ' ' $2${11 ' # will fail (brace was not closed)
Vendo como a substituição é apenas uma string estática, sua utilidade é limitada. É aqui que o molho secreto de tr
normalmente entra em jogo: usando suas classes de personagens, que também são válidas na segunda posição, traduzindo perfeitamente dos membros da primeira para a segunda. Aqui, essas classes são regexes e válidas apenas na primeira posição (o escopo). Sendo uma expressão regular uma máquina de estados, é impossível corresponder a uma 'lista de caracteres', que em tr
é o segundo argumento (opcional). Esse conceito foi descartado e sua flexibilidade foi perdida.
Em vez disso, são utilizadas as ações oferecidas, todas fixas . Uma olhada nos casos de uso mais comuns de tr
revela que o conjunto de ações fornecido abrange praticamente todos eles! Sinta-se à vontade para registrar um problema se o seu caso de uso não for coberto.
Para a próxima ação.
Remove tudo o que for encontrado na entrada. O mesmo nome do sinalizador de tr
.
$ echo ' Hello, World! ' | srgn -d ' (H|W|!) '
ello, orld
Observação
Como o escopo padrão corresponde a toda a entrada, é um erro especificar a exclusão sem um escopo.
Comprime repetições de caracteres correspondentes ao escopo em ocorrências únicas. O mesmo nome do sinalizador de tr
.
$ echo ' Helloooo Woooorld!!! ' | srgn -s ' (o|!) '
Hello World!
Se uma classe de personagem for aprovada, todos os membros dessa classe serão espremidos em qualquer membro da classe encontrado primeiro:
$ echo ' The number is: 3490834 ' | srgn -s ' d '
The number is: 3
A ganância na correspondência não é modificada, então tome cuidado:
$ echo ' Winter is coming... ??? ' | srgn -s ' ?+ '
Winter is coming... ???
Observação
O padrão combinava com toda a sequência de sóis, então não há nada para apertar. O verão prevalece.
Inverta a ganância se o caso de uso exigir:
$ echo ' Winter is coming... ??? ' | srgn -s ' ?+? ' ' ☃️ '
Winter is coming... ☃️
Observação
Novamente, como acontece com a exclusão, especificar a compressão sem um escopo explícito é um erro. Caso contrário, toda a entrada será comprimida.
Uma boa parte do uso tr
se enquadra nesta categoria. É muito simples.
$ echo ' Hello, World! ' | srgn --lower
hello, world!
$ echo ' Hello, World! ' | srgn --upper
HELLO, WORLD!
$ echo ' hello, world! ' | srgn --titlecase
Hello, World!
Decompõe a entrada de acordo com o Formulário de Normalização D e, em seguida, descarta os pontos de código da categoria Mark (ver exemplos). Isso significa aproximadamente: pegue um personagem sofisticado, arranque pedaços soltos e jogue-os fora.
$ echo ' Naïve jalapeño ärgert mgła ' | srgn -d ' P{ASCII} ' # Naive approach
Nave jalapeo rgert mga
$ echo ' Naïve jalapeño ärgert mgła ' | srgn --normalize # Normalize is smarter
Naive jalapeno argert mgła
Observe como mgła
está fora do escopo do NFD, pois é "atômico" e, portanto, não decomponível (pelo menos é o que ChatGPT sussurra em meu ouvido).
Esta ação substitui símbolos ASCII de vários caracteres por equivalentes Unicode nativos de ponto de código único apropriados.
$ echo ' (A --> B) != C --- obviously ' | srgn --symbols
(A ⟶ B) ≠ C — obviously
Alternativamente, se você estiver interessado apenas em matemática, use o escopo:
$ echo ' A <= B --- More is--obviously--possible ' | srgn --symbols ' <= '
A ≤ B --- More is--obviously--possible
Como existe uma correspondência 1:1 entre um símbolo ASCII e o seu substituto, o efeito é reversível 2 :
$ echo ' A ⇒ B ' | srgn --symbols --invert
A => B
Há apenas um conjunto limitado de símbolos suportados no momento, mas mais podem ser adicionados.
Esta ação substitui grafias alternativas de caracteres especiais alemães (ae, oe, ue, ss) pelas suas versões nativas (ä, ö, ü, ß) 3 .
$ echo ' Gruess Gott, Neueroeffnungen, Poeten und Abenteuergruetze! ' | srgn --german
Grüß Gott, Neueröffnungen, Poeten und Abenteuergrütze!
Esta ação é baseada em uma lista de palavras (compile sem o recurso german
se isso sobrecarregar demais o seu binário). Observe os seguintes recursos sobre o exemplo acima:
Poeten
permaneceu como está, em vez de ser ingenuamente e erroneamente convertido em Pöten
Abenteuergrütze
não será encontrado em nenhuma lista de palavras razoável, mas mesmo assim foi tratado adequadamenteAbenteuer
também permaneceu como está, em vez de ser convertido incorretamente para Abenteür
Neueroeffnungen
forma sorrateiramente um elemento ue
que nenhuma palavra constituinte ( neu
, Eröffnungen
) possui, mas ainda é processado corretamente (apesar dos invólucros incompatíveis também)Mediante solicitação, as substituições podem ser forçadas, o que é potencialmente útil para nomes:
$ echo ' Frau Loetter steht ueber der Mauer. ' | srgn --german-naive ' (?<=Frau )w+ '
Frau Lötter steht ueber der Mauer.
Através da antecipação positiva, nada além da saudação foi definido e, portanto, alterado. Mauer
permaneceu corretamente como está, mas ueber
não foi processado. Uma segunda passagem corrige isso:
$ echo ' Frau Loetter steht ueber der Mauer. ' | srgn --german-naive ' (?<=Frau )w+ ' | srgn --german
Frau Lötter steht über der Mauer.
Observação
As opções e sinalizadores pertencentes a algum "pai" são prefixados com o nome do pai e implicarão o pai quando fornecido, de modo que o último não precise ser passado explicitamente. É por isso que --german-naive
é nomeado como está e --german
não precisa ser aprovado.
Esse comportamento pode mudar quando clap
suportar o encadeamento de subcomandos.
Algumas ramificações são indecidíveis para esta modesta ferramenta, pois ela opera sem contexto linguístico. Por exemplo, tanto Busse
(ônibus) quanto Buße
(penitência) são palavras legais. Por padrão, as substituições são executadas avidamente se forem legais (afinal, esse é o objetivo de srgn
), mas há um sinalizador para alternar esse comportamento:
$ echo ' Busse und Geluebte ' | srgn --german
Buße und Gelübte
$ echo ' Busse ? und Fussgaenger ?♀️ ' | srgn --german-prefer-original
Busse ? und Fußgänger ?♀️
A maioria das ações pode ser composta, a menos que isso seja absurdo (como para exclusão). A ordem de aplicação é fixa, portanto a ordem dos sinalizadores fornecidos não tem influência (tubulação múltipla é uma alternativa, se necessário). As substituições sempre ocorrem primeiro. Geralmente, a CLI é projetada para evitar uso indevido e surpresas: ela prefere travar a fazer algo inesperado (o que é subjetivo, é claro). Observe que muitas combinações são tecnicamente possíveis, mas podem produzir resultados absurdos.
A combinação de ações pode ser semelhante a:
$ echo ' Koeffizienten != Bruecken... ' | srgn -Sgu
KOEFFIZIENTEN ≠ BRÜCKEN...
Pode ser especificado um âmbito mais restrito, que se aplicará igualmente a todas as ações:
$ echo ' Koeffizienten != Bruecken... ' | srgn -Sgu ' bw{1,8}b '
Koeffizienten != BRÜCKEN...
Os limites da palavra são obrigatórios, caso contrário Koeffizienten
é correspondido como Koeffizi
e enten
. Observe como os períodos finais não podem ser, por exemplo, reduzidos. O escopo necessário de .
iria interferir com o dado. A tubulação regular resolve isso:
$ echo ' Koeffizienten != Bruecken... ' | srgn -Sgu ' bw{1,8}b ' | srgn -s ' . '
Koeffizienten != BRÜCKEN.
Nota: o escape de regex ( .
) pode ser contornado usando escopo literal. A ação de substituição especialmente tratada também pode ser composta:
$ echo ' Mooood: ????!!! ' | srgn -s ' p{Emoji} ' ' ? '
Mooood: ?!!!
Os emojis são primeiro substituídos e depois compactados. Observe como nada mais é espremido.
Os escopos são o segundo conceito de condução do srgn
. No caso padrão, o escopo principal é uma expressão regular. A seção de ações apresentou esse caso de uso com alguns detalhes, por isso não será repetido aqui. É dado como um primeiro argumento posicional.
srgn
estende isso por meio de escopos preparados e com reconhecimento de gramática de linguagem, possibilitados por meio da excelente biblioteca tree-sitter
. Ele oferece um recurso de consultas, que funciona de forma semelhante à correspondência de padrões em uma estrutura de dados em árvore.
srgn
vem com algumas das consultas mais úteis. Através de sua API detectável (seja como uma biblioteca ou via CLI, srgn --help
), é possível aprender sobre as linguagens suportadas e as consultas preparadas disponíveis. Cada idioma suportado vem com uma saída de emergência, permitindo que você execute suas próprias consultas ad hoc personalizadas. A hachura vem na forma de --lang-query
, onde lang
é uma linguagem como python
. Veja abaixo mais informações sobre este tópico avançado.
Observação
Os escopos de linguagem são aplicados primeiro , portanto, qualquer regex, também conhecido como escopo principal, que você passar, ele opera em cada construção de linguagem correspondente individualmente.
Esta seção mostra exemplos de algumas das consultas preparadas .
unsafe
(Rust) Uma vantagem da palavra-chave unsafe
no Rust é sua "grepabilidade". No entanto, um rg 'unsafe'
irá, obviamente, revelar todas as correspondências de string ( rg 'bunsafeb'
ajuda até certo ponto), não apenas aquelas da palavra-chave real da linguagem Rust. srgn
ajuda a tornar isso mais preciso. Por exemplo:
// Oh no, an unsafe module!
mod scary_unsafe_operations {
pub unsafe fn unsafe_array_access ( arr : & [ i32 ] , index : usize ) -> i32 {
// UNSAFE: This function performs unsafe array access without bounds checking
* arr . get_unchecked ( index )
}
pub fn call_unsafe_function ( ) {
let unsafe_numbers = vec ! [ 1 , 2 , 3 , 4 , 5 ] ;
println ! ( "About to perform an unsafe operation!" ) ;
let result = unsafe {
// Calling an unsafe function
unsafe_array_access ( & unsafe_numbers , 10 )
} ;
println ! ( "Result of unsafe operation: {}" , result ) ;
}
}
pode ser pesquisado como