Current section
Files
Jump to
Current section
Files
lib/dogma/script.ex
defmodule Dogma.Script do
@moduledoc """
This module provides the struct that we use to represent source files, their
abstract syntax tree, etc, as well as a few convenient functions for working
with them.
"""
defmodule InvalidScriptError do
@moduledoc "An exception that can raised when source has invalid syntax."
defexception [:message]
def exception(script) do
%__MODULE__{ message: "Invalid syntax in #{script.path}" }
end
end
alias Dogma.Script
alias Dogma.Script.Metadata
alias Dogma.Error
alias Dogma.Util.ScriptSigils
alias Dogma.Util.ScriptStrings
alias Dogma.Util.Lines
defstruct path: nil,
source: nil,
lines: nil,
processed_source: nil,
processed_lines: nil,
ast: nil,
tokens: [],
valid?: false,
comments: [],
ignore_index: %{},
errors: []
@doc """
Builds a Script struct from the given source code and path
"""
def parse(source, path) do
script = %Script{
path: path,
source: source,
} |> add_ast
if script.valid? do
script
|> add_processed_source
|> add_tokens
|> add_lines
|> Metadata.add
else
script
end
end
@doc """
Builds a Script struct from the given source code and path.
Raises an exception if the source is invalid.
"""
def parse!(source, path) do
script = parse( source, path )
if script.valid? do
script
else
raise InvalidScriptError, script
end
end
defp add_ast(script) do
case Code.string_to_quoted( script.source, line: 1 ) do
{:ok, ast} ->
%Script{ script | valid?: true, ast: ast }
err ->
%Script{ script | valid?: false, errors: [error( err )] }
end
end
defp add_tokens(script) do
if script.valid? do
tokens = script.source |> tokenize
%Script{ script | tokens: tokens }
else
script
end
end
defp add_processed_source(script) when is_map(script) do
processed = script.source |> ScriptSigils.strip |> ScriptStrings.strip
%{ script | processed_source: processed }
end
defp add_lines(script) when is_map(script) do
lines = Lines.get( script.source )
pro = Lines.get( script.processed_source )
%{ script | lines: lines, processed_lines: pro }
end
defp tokenize(source) do
result =
source
|> String.to_char_list
|> :elixir_tokenizer.tokenize( 1, [] )
case result do
{:ok, _, tokens} -> tokens # Elixir 1.0.x
{:ok, _, _, tokens} -> tokens # Elixir 1.1.x
end
end
def line({line, _, _}), do: line
def line(line) when is_integer(line), do: line
@doc """
Postwalks the AST, calling the given `fun/2` on each.
script.errors is used as an accumulator for `fun/2`, the script with the new
errors is returned.
`fun/2` should take a node and the errors accumulator as arguments, and
return {node, errors}
"""
def walk(script, fun) do
{_, errors} = Macro.prewalk( script.ast, [], fun )
errors
end
defp error({:error, {pos, err, _}}) do
%Error{
rule: SyntaxError,
message: err,
line: line(pos) - 1,
}
end
end