Project

sexp_path

0.03
No commit activity in last 3 years
No release in over 3 years
Example based structural pattern matching for S-Expressions
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

Runtime

 Project Readme

SexpPath¶ ↑

Structural pattern matching against S-Expressions.

SexpPath allows you to define patterns that can be matched against S-Expressions. It draws inspiration from Regular Expressions, XPath, and CSS Selectors.

I’m still figuring out how this should work so either fork this, or send me some feedback.

http://github.com/adamsanderson/sexp_path
netghost@gmail.com

Installation¶ ↑

SexpPath is distributed as a ruby gem:

gem install sexp_path

Notation¶ ↑

In ruby you’re most likely to come across S-Expressions when dealing with Ruby’s representation of the abstract syntax tree. An S-Expression is just a set of nested lists. The SexpProcessor library displays them like this:

s(:a, :b, 
  s(:c)
)

Where that means that there is a list containing ‘:a`, `:b`, and then another list which contains `:c`. We will refer to `:a`,`:b`, and `:c` as atoms, while `s( something )` is an S-Expression or Sexp.

General Syntax¶ ↑

SexpPath is an internal ruby DSL, which means a SexpPath query is valid ruby code.

SexpPath queries are built with the SexpQueryBuilder through the Q? convenience method:

Q?{ s(:a, :b, :c)}    # Matches s(:a, :b, :c)

This will match the S-Expression ‘s(:a, :b, :c)`. If you want to match something more complicated, you will probably want to employ one of the many matchers built into SexpPath.

Wild Card

Matches anything.

_  => s(), :a, or s(:cat)
Atom

Matches any atom (or symbol).

atom => :a, :b, or :cat
Pattern

Matches any atom that matches the given string or regular expression.

m('cat') => :cat
m(/rat/) => :rat, :brat, or :rate
m(/^test_/) => :test_sexp_path
Includes

Matches any S-Expression that includes the sub expression.

include(:a) => s(:a), s(:a, :b), or s(:cat, :a, :b)
include( s(:cat) ) => s(:pet, s(:cat))
Child

Matches any S-Expression that has the sub expression as a child.

child( s(:a) ) => s(:b, s(:a)) or even s(s(s(s(s( s(:a))))))
Sibling

Matches any S-Expression that has the second expression as a sibling.

s(:a) >> s(:c) => s( s(:a), s(:b), s(:c) )
Type

The sexp type is considered to be the first atom. This matches any expression that has the given type.

type(:a) => s(:a), s(:a, :b), or s(:a, :b, s(:c))
Any

Matches any sub expression

any( s(:a), s(:b) ) => s(:a) or s(:b)
any( s(:a), s(atom, :b) ) => s(:a), s(:a, :b), or s(:cat, :b)
All

Matches anything that satisfies all the sub expressions

all( s(:a, atom), s(atom, :b) ) => s(:a,:b)
Not

Negates a matcher

-s(:a) => s(:a,:b), s(:b), but not s(:a)
s(is_not :a) => s(:b), s(:c), but not s(:a) or s(:a, :b)
Remaining

Matches the remainder of an s-expression.

s(:a, ___)  => s(:a), s(:a, :b, :c), but not s(:b)

Searching¶ ↑

You may use any SexpPath to search an S-Expression. SexpPath defines the ‘/` operator as search, so to search `s( s(:a) )` for `s(:a)` you may just do:

s( s(:a) ) / Q?{ s(:a) }

This will return a collection with just one result which is ‘s(:a)`. You could also do something more interesting:

s( s(:a), s(:b) ) / Q?{ s(atom) }

This will return two matches which are ‘s(:a)` and `s(:b)`. You can also chain searches, so this works just fine as well:

sexp = s(:class, :Calculator,
  s(:defn, :add),
  s(:defn, :sub)
) 

sexp / Q?{ s(:class, atom, _) } / Q?{ s(:defn, _) }

In this case you would get back ‘s(:defn, :add)` and `s(:defn, :sub)`.

Capturing¶ ↑

It is useful to also capture results from your queries. So using the same Sexp from above we could modify our query to actually capture some names. Capturing is done by using ‘%` operator followed by the name you would like the value to be captured as.

sexp / Q?{ s(:class, atom % 'class_name', _) } / Q?{ s(:defn, _ % 'method_name') }

The results will now capture ‘:Calculator` in `class_name`, and then `:add` and `:sub` in `method_name`.

Examples¶ ↑

Here is an example of using SexpPath to grab all the classes and their methods from a file:

require 'sexp_path'
require 'ruby_parser' # `gem install ruby_parser`

path = ARGV.shift
code = File.read(path)
sexp = RubyParser.new.parse(code, path)

class_query = Q?{ s(:class, atom % 'class_name', _, ___) }
method_query = Q?{ s(:defn, atom % 'method_name', ___) }

results = sexp / class_query / method_query

puts path
puts "-" * 80

results.each do |sexp_result|
  class_name = sexp_result['class_name']
  method_name = sexp_result['method_name']
  puts "#{class_name}##{method_name}"
end

Neat huh? Check the ‘examples` folder for some more little apps.

Project Information¶ ↑

Hop in and fork it or add some issues over at GitHub:

http://github.com/adamsanderson/sexp_path

Ideas for Hacking on SexpPath:

* More examples
* Add new matchers
* Convenience matchers, for instance canned matchers for matching ruby classes, methods, etc

I’d love to see what people do with this library, let me know if you find it useful.

Adam Sanderson, netghost@gmail.com
http://monkeyandcrow.com