🎍 Mulang
A universal, multi-language, multi-paradigm code analyzer
Mulang is a tool for analysing source code, which is built on top of five main components:
- an Abstract Semantic Tree, an intermediate language which allows to express the semantic - as opposed to syntatic - structure of a multi-paradigm program;
- a set of more than 90 inspections for querying code querying code either explicitly - expectations - or implicitlt - smells.
- an Expectations Definition Language (EDL), a language for defining custom expectations
- a command line tool for analysing both source code in many languages and Mulang's AST. This tool is distributed as both a
linux-amd64
binary and a JavaScript package. See downloads section. - higher level interfaces in ruby and javascript that are easier to use and provides some additional capabilities like expectations parsing and automatic internationalized humanization.
Supported languages
Mulang can work with many different programming languages. it natively supports:
- C
- Haskell
- Java
- JavaScript (ES6)
- Python (2 and 3)
- Prolog
In addition, through external tools, it offers support for the following languages:
- Ruby, using mulang-ruby
- PHP, using mulang-php
- Gobstones, using gs-weblang-cli
- Sratch, using mulang-scratch
If you want to use it with a different language, you will have to:
- either add explicit support in this repo - we love Pull Requests :heart: -, or
- translate your language into one of the natively supported ones, or
- translate your language to the Mulang JSON AST
Quick start
The following section uses the ruby interface for demonstration purposes. However, you also can run all the examples either using the comand line tool, or the javascript interface. The javascript version is also available as a ruby gem, suitable for Rails integration
Better than explaining what Mulang is, let's see what it can do for you.
Inspections
Let's suppose we have the following JS code...
let aPlace = buenosAires;
let aBird = {position: aPlace, weight: 20};
...and we want to recognize some code patterns on it. We will first load the expression into Mulang:
> require "mulang"
> code = Mulang::Code.native "JavaScript", "let aPlace = buenosAires; let aBird = {position: aPlace, weight: 20};"
Now we want to know if the code expression uses - that is, contains any reference to - a given identifier. Such identifier could be a variable, function, or anything that has a name:
> code.expect 'Uses:buenosAires'
=> true # because of the reference in `...aPlace = buenosAires...`
> code.expect 'Uses:rosario'
=> false # no reference to the identifier `rosario` is found on the code
Uses:buenosAires
is our first inspection: a function that takes a Mulang AST and answers a boolean question about it. That seems easy, but just in case you are wondering: no, Mulang doesn't perform a string.contains
or something like that :stuck_out_tongue: :
> code.expect 'Uses:BuenosAires'
=> false # no reference to the identifier `buenos` is found on the code
Contexts
So let's ask something more interesting - does aPlace
use the identifier buenosAires
?
> code.expect 'aPlace', 'Uses:buenosAires'
=> true # again, because of the the reference in `...aPlace = buenosAires...`
> code.expect 'aPlace', 'Uses:aBird'
=> false # because `...aPlace = buenosAires...` does not reference `aBird`...
Here we have contextualized the inspection, so it runs only within the contexts of the given binding.
Let's tray again: does "aPlace"
use rosario
? And what about the object aBird
? Does it use aPlace
or rosario
?
> code.expect "aPlace", "Uses:rosario"
=> false
> code.expect "aBird", "Uses:aPlace"
=> true
> code.expect "aBird", "Uses:buenosAires"
=> true
Oh, wait! Is this a bug? Nope. Expectations are transitive by default: aBird
uses aPlace
, and aPlace
uses buenosAires
in turn, which means that aBird
actually uses buenosAires
. If you don't want that behaviour you can turn it off:
> code.expect "Intransitive:aBird", "Uses:buenosAires"
=> false
Contexts can be nested, too: for example, if you want to know whether aBird.position
uses aPlace
- ignoring that weight
attribute:
> code.expect "aBird.position", "Uses:aPlace"
=> true
> code.expect "aBird.weight", "Uses:aPlace"
=> false
Inspections do not only allow you to consult usages. They can tell you much more things. See the supported inspections list.
Predicates
Many inspections support an identifier predicate, that is, a matcher for the identifier.
For example, does the former piece of code declare any attribute?
> code.expect 'DeclaresAttribute:*'
=> true
> code.expect 'DeclaresAttribute' # shorter version
=> true
Does it declare an attribute like eight
?
> code.expect "DeclaresAttribute:~eight"
=> true
Notice:
like
in Mulang means that it contains that case-insensitive substring
Finally, does aBird
declares an attribute that is not named weight
?
> code.expect "DeclaresAttribute:^weight"
=> true # because it also declares position
Matchers
Finally, you can provide a matcher to many of the available expectations, that allows to match specific parts of code with some patterns.
> code.expect 'DeclaresAttribute:WithNumber:20'
=> true # because weight is initialized with that value
> code.expect 'DeclaresAttribute:WithNumber:21'
=> false
> code.expect 'DeclaresAttribute:position:WithNonliteral'
=> true # because position is initialized with a non-literal value
> code.expect 'DeclaresAttribute:WithLiteral'
=> true # because weight is initialized with a literal value
> code.expect 'DeclaresAttribute:WithNil'
=> false # because no attribute is initialied with null
The complete list of supported matchers is the following:
WithFalse
WithLiteral
WithLogic
WithMath
WithNil
WithNonliteral
WithTrue
WithChar:'value'
WithSymbol:value
WithNumber:value
WithString:"value"