(...or trees, what are they good for?)
Born & Raised in Argentina.
OOP for a long time (mostly C# & Java).
FP for the last 3+ years (Erlang, Clojure).
Open-source contributor (e.g. Elvis).
Love music, singing, asado and soccer.
Founded in 2010.
Acquired by Erlang Solutions in 2014.
End-to-end applications with Erlang, Ruby, Web, iOS and Android components.
Highly concurrent application servers such as Whisper and TigerText.
Active contributors to the open-source community.
//drunk, fix later
//magic, do not
//touch
Source Code
Tokens
Abstract Syntax Tree (AST)
(4+3)*7
{lparen, "("},
{int, "4"}, {op, "+"},
{int, "3"}, {rparen, ")"},
{op, "*"}, {int, "7"}
Lexer
Parser
tools.reader
(defn
hello
[name]
(prn "Hello " name))
"(defn hello
[name]
(prn \"Hello \" name))"
tools.reader.edn/read-string
String
Nested Data Structures & Values
{:op :def,
:children [:meta :init],
:meta {:op :const, ...},
:init {:op :fn, ...},
...}
(defn hello
[name]
(prn "Hello " name))
tools.analyzer.jvm/analyze
tools.analyzer
Nested Maps w/ Lots of Information
Nested Data Structures & Values
{:tag :playground.parsley/root,
:content [{:tag :list,
:content ["("
{:tag :symbol, ...}
{:tag :whitespace, ...}
...
{:tag :symbol, ...}
")"]}]}
"(defn hello [name] name)"
(parsley/make-parser opts grammar)
Nested Maps w/ Original Source
String
parsley/instaparse
erl_scan + erl_parse
[{'(',1}, {integer,1,3},
{'+',1}, {integer,1,4}, {')',1},
{'*',1}, {integer,1,7},
{dot,1}]
"(3 + 4) * 7."
erl_scan:string/1
List of Tokens (Tuples)
String
erl_scan + erl_parse
[{op, 1, '*',
{op, 1, '+',
{integer, 1, 3},
{integer, 1, 4}},
{integer, 1, 7}}]
[{'(',1}, {integer,1,3},
{'+',1}, {integer,1,4}, {')',1},
{'*',1}, {integer,1,7}, {dot,1}]
erl_parse:parse_exprs/1
Abstract Syntax Forms (Nested Tuples)
List of Tokens
ktn_code (erlang-katana)
#{type => root,
attrs => #{},
content => [#{type => module,
attrs => #{...}},
#{type => function,
attrs => #{arity => 1,
name => two_times},
content => [#{type => clause,
attrs => #{...},
content => []}]}]}
"-module(double).
two_times(X) -> 2 * X."
ktn_code:parse_tree/1
Nested Maps
String
scalariform
tokens: List[scalariform.lexer.Token] =
List(Token(VAL,val,0,val), Token(VARID,x,4,x),
Token(EQUALS,=,6,=),
Token(INTEGER_LITERAL,1,8,1),
Token(NEWLINE,"\n",9, "\n"),
Token(VARID,x,10,x), Token(PLUS,+,12,+),
Token(INTEGER_LITERAL,2,14,2),
Token(EOF,,15,))
"val x = 1
x + 2"
ScalaLexer.tokenise
List of Tokens
String
scalariform
CompilationUnit( // "val x = 1\nx + 2"
StatSeq(
None, // val x = 1
Some(FullDefOrDcl(List(), List(), PatDefOrDcl(...))),
List((Token(NEWLINE,"\n",9,"\n"),
Some(Expr(List(InfixExpr(...))))) // x + 2
),
Token(EOF,"",15,"")
)
List(Token(VAL,val,0,val),
Token(VARID,x,4,x), ...)
new ScalaParse().compilationUnitOrScript
CompilationUnit
(Top Level AstNode)
List of Tokens
f(node) = Paint node Red
f(node) = Paint node Red
f(node) = Paint node Red
f(node) = Paint node Red
f(node) = Paint node Red
f(node) = Paint node Red
clojure.zip
akhudek/fast-zip
inaka/zipper
ferd/zippers
scalaz.TreeLoc
scalaz.Zipper
($ zloc [(defn ^:% vector? | _)]
do-something)
(if (and (-> zloc z/prev z/prev z/sexpr (= "defn"))
(-> zloc z/prev z/sexpr vector?))
(do-something zloc)
zloc)
zipper + CSS-like selector + pattern matching
($ zloc [(defn ^:% vector? | _)]
do-something)
(if (and (-> zloc z/prev z/prev z/sexpr (= "defn"))
(-> zloc z/prev z/sexpr vector?))
(do-something zloc)
zloc)
zipper + CSS-like selector + pattern matching
[(defn ^:% vector? | _)]
(and (-> zloc z/prev z/prev z/sexpr (= "defn"))
(-> zloc z/prev z/sexpr vector?))
tools.analyzer
tools.analyzer.ast
scalariform.lexer
scalariform.parser
scalastyle.VisitorHelper
ktn_code
(erlang-katana)
inaka/zipper
(defn deprecations [{:keys [asts]} opt]
(for [ast (map #(ast/postwalk % pass/reflect-validated) asts)
dexpr (filter deprecated? (ast/nodes ast))
:let [loc (pass/code-loc (pass/nearest-ast-with-loc dexpr))]]
(util/add-loc-info loc
{:linter :deprecations
:msg (msg dexpr)})))
(defn deprecations [{:keys [asts]} opt]
(defn deprecations [{:keys [asts]} opt]
(for [ast (map #(ast/postwalk % pass/reflect-validated) asts)
dexpr (filter deprecated? (ast/nodes ast))
:let [loc (pass/code-loc (pass/nearest-ast-with-loc dexpr))]]
(util/add-loc-info loc
{:linter :deprecations
:msg (msg dexpr)})))
(for [ast (map #(ast/postwalk % pass/reflect-validated) asts)
dexpr (filter deprecated? (ast/nodes ast))
(defn deprecations [{:keys [asts]} opt]
(for [ast (map #(ast/postwalk % pass/reflect-validated) asts)
dexpr (filter deprecated? (ast/nodes ast))
:let [loc (pass/code-loc (pass/nearest-ast-with-loc dexpr))]]
(util/add-loc-info loc
{:linter :deprecations
:msg (msg dexpr)})))
:let [loc (pass/code-loc (pass/nearest-ast-with-loc dexpr))]]
(util/add-loc-info loc
{:linter :deprecations
:msg (msg dexpr)})))
(defn deprecations [{:keys [asts]} opt]
(for [ast (map #(ast/postwalk % pass/reflect-validated) asts)
dexpr (filter deprecated? (ast/nodes ast))
:let [loc (pass/code-loc (pass/nearest-ast-with-loc dexpr))]]
(util/add-loc-info loc
{:linter :deprecations
:msg (msg dexpr)})))
no_if_expression(Config, Target, _RuleConfig) ->
{Root, _} = elvis_file:parse_tree(Config, Target),
Predicate = fun(Node) -> ktn_code:type(Node) == 'if' end,
ResultFun = result_node_line_fun(?NO_IF_EXPRESSION_MSG),
case elvis_code:find(Predicate, Root) of
[] -> [];
IfExprs -> lists:map(ResultFun, IfExprs)
end.
no_if_expression(Config, Target, _RuleConfig) ->
{Root, _} = elvis_file:parse_tree(Config, Target),
Predicate = fun(Node) -> ktn_code:type(Node) == 'if' end,
ResultFun = result_node_line_fun(?NO_IF_EXPRESSION_MSG),
case elvis_code:find(Predicate, Root) of
[] -> [];
IfExprs -> lists:map(ResultFun, IfExprs)
end.
no_if_expression(Config, Target, _RuleConfig) ->
no_if_expression(Config, Target, _RuleConfig) ->
{Root, _} = elvis_file:parse_tree(Config, Target),
Predicate = fun(Node) -> ktn_code:type(Node) == 'if' end,
ResultFun = result_node_line_fun(?NO_IF_EXPRESSION_MSG),
case elvis_code:find(Predicate, Root) of
[] -> [];
IfExprs -> lists:map(ResultFun, IfExprs)
end.
{Root, _} = elvis_file:parse_tree(Config, Target),
Predicate = fun(Node) -> ktn_code:type(Node) == 'if' end,
no_if_expression(Config, Target, _RuleConfig) ->
{Root, _} = elvis_file:parse_tree(Config, Target),
Predicate = fun(Node) -> ktn_code:type(Node) == 'if' end,
ResultFun = result_node_line_fun(?NO_IF_EXPRESSION_MSG),
case elvis_code:find(Predicate, Root) of
[] -> [];
IfExprs -> lists:map(ResultFun, IfExprs)
end.
case elvis_code:find(Predicate, Root) of
no_if_expression(Config, Target, _RuleConfig) ->
{Root, _} = elvis_file:parse_tree(Config, Target),
Predicate = fun(Node) -> ktn_code:type(Node) == 'if' end,
ResultFun = result_node_line_fun(?NO_IF_EXPRESSION_MSG),
case elvis_code:find(Predicate, Root) of
[] -> [];
IfExprs -> lists:map(ResultFun, IfExprs)
end.
[] -> [];
IfExprs -> lists:map(ResultFun, IfExprs)
end.
final def verify(ast: CompilationUnit): List[ScalastyleError] = {
val it = for {
t <- localvisit(ast.immediateChildren(0));
f <- traverse(t);
if (matches(f))
} yield {
PositionError(f.position.get, params(f))
}
it.toList
}
AbstractMethodChecker
final def verify(ast: CompilationUnit): List[ScalastyleError] = {
val it = for {
t <- localvisit(ast.immediateChildren(0));
f <- traverse(t);
if (matches(f))
} yield {
PositionError(f.position.get, params(f))
}
it.toList
}
final def verify(ast: CompilationUnit): List[ScalastyleError] = {
AbstractMethodChecker
final def verify(ast: CompilationUnit): List[ScalastyleError] = {
val it = for {
t <- localvisit(ast.immediateChildren(0));
f <- traverse(t);
if (matches(f))
} yield {
PositionError(f.position.get, params(f))
}
it.toList
}
val it = for {
t <- localvisit(ast.immediateChildren(0));
f <- traverse(t);
AbstractMethodChecker
final def verify(ast: CompilationUnit): List[ScalastyleError] = {
val it = for {
t <- localvisit(ast.immediateChildren(0));
f <- traverse(t);
if (matches(f))
} yield {
PositionError(f.position.get, params(f))
}
it.toList
}
if (matches(f))
AbstractMethodChecker
final def verify(ast: CompilationUnit): List[ScalastyleError] = {
val it = for {
t <- localvisit(ast.immediateChildren(0));
f <- traverse(t);
if (matches(f))
} yield {
PositionError(f.position.get, params(f))
}
it.toList
}
} yield {
PositionError(f.position.get, params(f))
}
AbstractMethodChecker
- Regex (Plain Text)
- Find Node & Check Type
- Use tokens!
[1,2, 3]
"1,2, 3"
"1, 2, 3,"
"4,5, 6"
"1,2, 3"
- Compare Every Node with Every Other Node. O(N^2)
- Ignore properties (var names, locations, etc.)
- Compare Subsets of Contiguous Expressions. O(N!?)
(if (odd? x)
:do-this)
(when (odd? x)
:do-this)
@jfacorro
jfacorro