diff --git a/.travis.yml b/.travis.yml index 6888fec..881a388 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,16 +1,29 @@ +## Documentation: http://docs.travis-ci.com/user/languages/julia/ language: julia os: - linux - osx julia: - - 0.3 - - 0.4 + - 0.6 - nightly notifications: email: false -# uncomment the following lines to override the default test script -#script: -# - if [[ -a .git/shallow ]]; then git fetch --unshallow; fi -# - julia --check-bounds=yes -e 'Pkg.clone(pwd()); Pkg.build("ParserCombinator"); Pkg.test("ParserCombinator"; coverage=true)' +git: + depth: 99999999 + +## uncomment the following lines to allow failures on nightly julia +## (tests will run but not make your overall status red) +matrix: + allow_failures: + - julia: nightly + +## uncomment and modify the following lines to manually install system packages +#addons: +# apt: # apt-get for linux +# packages: +# - gfortran +#before_script: # homebrew for mac +# - if [ $TRAVIS_OS_NAME = osx ]; then brew install gcc; fi + after_success: - julia -e 'cd(Pkg.dir("ParserCombinator")); Pkg.add("Coverage"); using Coverage; Coveralls.submit(Coveralls.process_folder())' diff --git a/README.md b/README.md index c35e834..82f1a33 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ using ParserCombinator # the AST nodes we will construct, with evaluation via calc() -abstract Node +abstract type Node end ==(n1::Node, n2::Node) = n1.val == n2.val calc(n::Float64) = n type Inv<:Node val end @@ -68,7 +68,7 @@ sum.matcher = prd + (add | sub)[0:end] |> Sum all = sum + Eos() -# and test +# and test # this prints 2.5 calc(parse_one("1+2*3/4", all)[1]) @@ -111,11 +111,6 @@ Still, for large parsing tasks (eg parsing source code for a compiler) it would probably be better to use a wrapper around an external parser generator, like Anltr. -**Note:** There's an [issue](https://github.com/JuliaLang/Compat.jl/issues/94) - with the Compat library which means the code above (the assignment to - `Delayed.matcher`) doesn't work with 0.3. See [calc.jl](test/calc.jl) for - the uglier, hopefully temporary, 0.3 version. - ## Install ```julia @@ -596,7 +591,7 @@ character of the first line. Finally, note that this is implemented at the source level, by restricting what text is visible to the matchers. Matchers that *could* backtrack will -still make the attempt. So you should also [disable backtracking in the +still make the attempt. So you should also [disable backtracking in the matchers](#backtracking), where you do not need it, for an efficient grammar. #### Spaces - Pre And Post-Fixes @@ -727,15 +722,15 @@ matchers you care about): neg = Delayed() # allow multiple negations (eg ---3) neg.matcher = val | (E"-" + neg > Neg) - + mul = E"*" + neg div = E"/" + neg > Inv prd = neg + (mul | div)[0:end] |> Prd - + add = E"+" + prd sub = E"-" + prd > Neg sum.matcher = prd + (add | sub)[0:end] |> Sum - + all = sum + Eos() end @@ -1272,4 +1267,3 @@ patch. 1.1.0 - 2015-06-07 - Fixed calc example; debug mode; much rewriting. 1.0.0 - ~2015-06-03 - More or less feature complete. - diff --git a/REQUIRE b/REQUIRE index 999c790..8ccb18a 100644 --- a/REQUIRE +++ b/REQUIRE @@ -1,4 +1,2 @@ -julia 0.3 -Compat 0.7.12 +julia 0.6 AutoHashEquals 0.0.8 - diff --git a/src/ParserCombinator.jl b/src/ParserCombinator.jl index ddd5a59..fd6d696 100644 --- a/src/ParserCombinator.jl +++ b/src/ParserCombinator.jl @@ -1,31 +1,30 @@ - +__precompile__() module ParserCombinator -using Compat using AutoHashEquals import Base: start, next, done, endof, getindex, colon, isless, size, hash import Base: ==, ~, +, &, |, >=, >, |>, ! -export Matcher, +export Matcher, diagnostic, forwards, LineSource, LineIter, Config, Cache, NoCache, make, make_all, make_one, once, -parse_one, parse_one_cache, parse_one_nocache, +parse_one, parse_one_cache, parse_one_nocache, parse_all, parse_all_cache, parse_all_nocache, parse_lines, parse_lines_cache, -Debug, Trace, +Debug, Trace, parse_dbg, parse_one_dbg, parse_one_cache_dbg, parse_one_nocache_dbg, parse_all_dbg, parse_all_cache_dbg, parse_all_nocache_dbg, parse_lines_dbg, parse_lines_cache_dbg, Success, EMPTY, Failure, FAILURE, Execute, State, Clean, CLEAN, Dirty, DIRTY, ParserException, Value, Empty, EMPTY, Delegate, DelegateState, -Epsilon, Insert, Dot, Fail, Drop, Equal, -Repeat, Depth, Breadth, Depth!, Breadth!, ALL, +Epsilon, Insert, Dot, Fail, Drop, Equal, +Repeat, Depth, Breadth, Depth!, Breadth!, ALL, Series, Seq, And, Seq!, And!, Alt, Alt!, Lookahead, Not, Pattern, Delayed, Eos, ParserError, Error, Transform, App, Appl, ITransform, IApp, IAppl, @p_str, @P_str, @e_str, @E_str, Opt, Opt!, -Parse, PUInt, PUInt8, PUInt16, PUInt32, PUInt64, +Parse, PUInt, PUInt8, PUInt16, PUInt32, PUInt64, PInt, PInt8, PInt16, PInt32, PInt64, PFloat32, PFloat64, Word, Space, Star, Plus, Star!, Plus!, StarList, StarList!, PlusList, PlusList!, @@ -34,7 +33,6 @@ Star, Plus, Star!, Plus!, StarList, StarList!, PlusList, PlusList!, TrySource, Try, parse_try, parse_try_dbg, parse_try_cache, parse_try_cache_dbg, Parsers -FAST_REGEX = isdefined(Main, :FAST_REGEX) ? Main.FAST_REGEX : VERSION >= v"0.4.0-dev+6325" include("core/types.jl") include("core/sources.jl") diff --git a/src/Parsers.jl b/src/Parsers.jl index cd0012a..093a5f0 100644 --- a/src/Parsers.jl +++ b/src/Parsers.jl @@ -1,4 +1,4 @@ - +__precompile__() module Parsers export GML diff --git a/src/core/debug.jl b/src/core/debug.jl index 15e063a..2c5c6ac 100644 --- a/src/core/debug.jl +++ b/src/core/debug.jl @@ -8,22 +8,20 @@ # construct your own with any type by using the delegate=... keyword. see # test/calc.j for an example. -type Debug{S,I}<:Config{S,I} +mutable struct Debug{S}<:Config{S} source::S stack::Vector - delegate::Config{S,I} + delegate::Config{S} depth::Vector{Int} abs_depth::Int max_depth::Int max_iter n_calls::Int - function Debug(source::S; delegate=NoCache, kargs...) - k = delegate{S,I}(source; kargs...) - @compat new(k.source, k.stack, k, Vector{Int}(), 0, 0, start(k.source), 0) + function Debug(source::S; delegate=NoCache, kargs...) where S + k = delegate(source; kargs...) + new{S}(k.source, k.stack, k, Vector{Int}(), 0, 0, start(k.source), 0) end end -# i don't get why this is necessary, but it seems to work -Debug(source; kargs...) = Debug{typeof(source),typeof(start(source))}(source; kargs...) parent(k::Debug) = parent(k.delegate) @@ -73,15 +71,11 @@ MAX_RES = 50 MAX_SRC = 10 MAX_IND = 10 -if VERSION < v"0.4-" - shorten(s) = s -else # shorten(s) = replace(s, r"(?:[a-zA-Z]+\.)+([a-zA-Z]+)", s"\1") - shorten(s) = replace(s, r"(?:[a-zA-Z]+\.)+([a-zA-Z]+)", - Base.SubstitutionString("\1")) -end +shorten(s) = replace(s, r"(?:[a-zA-Z]+\.)+([a-zA-Z]+)", + Base.SubstitutionString("\1")) -function truncate(s::AbstractString, n=10) +function truncate(s::String, n=10) if length(s) <= n return s end @@ -97,17 +91,18 @@ function truncate(s::AbstractString, n=10) end end -pad(s::AbstractString, n::Int) = s * repeat(" ", n - length(s)) +pad(s::String, n::Int) = s * repeat(" ", n - length(s)) indent(k::Debug; max=MAX_IND) = repeat(" ", k.depth[end] % max) src(::Any, ::Any; max=MAX_SRC) = pad(truncate("...", max), max) -src(s::AbstractString, i::Int; max=MAX_SRC) = pad(truncate(escape_string(s[i:end]), max), max) +src(s::String, i::Int; max=MAX_SRC) = pad(truncate(escape_string(s[i:end]), max), max) -function debug{S<:AbstractString}(k::Debug{S}, e::Execute) +function debug(k::Debug{<:AbstractString}, e::Execute) @printf("%3d:%s %02d %s%s->%s\n", e.iter, src(k.source, e.iter), k.depth[end], indent(k), e.parent.name, e.child.name) end + function short(s::Value) result = string(s) if ismatch(r"^Any", result) @@ -116,12 +111,12 @@ function short(s::Value) truncate(result, MAX_RES) end -function debug{S<:AbstractString}(k::Debug{S}, s::Success) +function debug(k::Debug{<:AbstractString}, s::Success) @printf("%3d:%s %02d %s%s<-%s\n", s.iter, src(k.source, s.iter), k.depth[end], indent(k), parent(k).name, short(s.result)) end -function debug{S<:AbstractString}(k::Debug{S}, f::Failure) +function debug(k::Debug{<:AbstractString}, f::Failure) @printf(" :%s %02d %s%s<-!!!\n", pad(" ", MAX_SRC), k.depth[end], indent(k), parent(k).name) end @@ -138,17 +133,17 @@ function src(s::LineAt, i::LineIter; max=MAX_SRC) end end -function debug{S<:LineAt}(k::Debug{S}, e::Execute) +function debug(k::Debug{<:LineAt}, e::Execute) @printf("%3d,%-3d:%s %02d %s%s->%s\n", e.iter.line, e.iter.column, src(k.source, e.iter), k.depth[end], indent(k), e.parent.name, e.child.name) end -function debug{S<:LineAt}(k::Debug{S}, s::Success) +function debug(k::Debug{<:LineAt}, s::Success) @printf("%3d,%-3d:%s %02d %s%s<-%s\n", s.iter.line, s.iter.column, src(k.source, s.iter), k.depth[end], indent(k), parent(k).name, short(s.result)) end -function debug{S<:LineAt}(k::Debug{S}, f::Failure) +function debug(k::Debug{<:LineAt}, f::Failure) @printf(" :%s %02d %s%s<-!!!\n", pad(" ", MAX_SRC), k.depth[end], indent(k), parent(k).name) end @@ -163,7 +158,7 @@ end Trace(matcher) = new(:Trace, matcher) end -@auto_hash_equals immutable TraceState<:DelegateState +@auto_hash_equals struct TraceState<:DelegateState state::State end diff --git a/src/core/matchers.jl b/src/core/matchers.jl index 87dafde..f572b62 100644 --- a/src/core/matchers.jl +++ b/src/core/matchers.jl @@ -12,14 +12,14 @@ execute(k::Config, m::Matcher, s::Dirty, i) = FAILURE # many matchers delegate to a child, making only slight modifications. # we can describe the default behaviour just once, here. -# child matchers then need to implement (1) state creation (typically on +# child matchers then need to implement (1) state creation (typically on # response) and (2) anything unusual (ie what the matcher actually does) # assume this has a matcher field -abstract Delegate<:Matcher +abstract type Delegate<:Matcher end # assume this has a state field -abstract DelegateState<:State +abstract type DelegateState<:State end execute(k::Config, m::Delegate, s::Clean, i) = Execute(m, s, m.matcher, CLEAN, i) @@ -31,7 +31,7 @@ failure(k::Config, m::Delegate, s) = FAILURE # various weird things for completeness -@auto_hash_equals type Epsilon<:Matcher +@auto_hash_equals mutable struct Epsilon<:Matcher name::Symbol Epsilon() = new(:Epsilon) end @@ -39,7 +39,7 @@ end execute(k::Config, m::Epsilon, s::Clean, i) = Success(DIRTY, i, EMPTY) -@auto_hash_equals type Insert<:Matcher +@auto_hash_equals mutable struct Insert<:Matcher name::Symbol text Insert(text) = new(:Insert, text) @@ -48,7 +48,7 @@ end execute(k::Config, m::Insert, s::Clean, i) = Success(DIRTY, i, Any[m.text]) -@auto_hash_equals type Dot<:Matcher +@auto_hash_equals mutable struct Dot<:Matcher name::Symbol Dot() = new(:Dot) end @@ -63,7 +63,7 @@ function execute(k::Config, m::Dot, s::Clean, i) end -@auto_hash_equals type Fail<:Matcher +@auto_hash_equals mutable struct Fail<:Matcher name::Symbol Fail() = new(:Fail) end @@ -74,13 +74,13 @@ execute(k::Config, m::Fail, s::Clean, i) = FAILURE # evaluate the sub-matcher, but replace the result with EMPTY -@auto_hash_equals type Drop<:Delegate +@auto_hash_equals mutable struct Drop<:Delegate name::Symbol matcher::Matcher Drop(matcher) = new(:Drop, matcher) end -@auto_hash_equals immutable DropState<:DelegateState +@auto_hash_equals mutable struct DropState<:DelegateState state::State end @@ -90,7 +90,7 @@ success(k::Config, m::Drop, s, t, i, r::Value) = Success(DropState(t), i, EMPTY) # exact match -@auto_hash_equals type Equal<:Matcher +@auto_hash_equals mutable struct Equal<:Matcher name::Symbol string Equal(string) = new(:Equal, string) @@ -98,7 +98,7 @@ end always_print(::Equal) = true function print_field(m::Equal, ::Type{Val{:string}}) - if isa(m.string, AbstractString) + if isa(m.string, String) "\"$(m.string)\"" else :string @@ -126,11 +126,11 @@ end # of all possible states (limited by the maximum number of matches), # yielding when we have a result within the lo/hi range. -abstract Repeat_<:Matcher # _ to avoid conflict with function in 0.3 +abstract type Repeat_<:Matcher end # _ to avoid conflict with function in 0.3 ALL = typemax(Int) -abstract RepeatState<:State +abstract type RepeatState<:State end function Repeat(m::Matcher, lo, hi; flatten=true, greedy=true, backtrack=true) if greedy @@ -156,7 +156,7 @@ end # depth-first (greedy) state and logic -@auto_hash_equals type Depth<:Repeat_ +@auto_hash_equals mutable struct Depth<:Repeat_ name::Symbol matcher::Matcher lo::Integer @@ -166,23 +166,23 @@ end end # greedy matching is effectively depth first traversal of a tree where: -# * performing an additional match is moving down to a new level +# * performing an additional match is moving down to a new level # * performaing an alternate match (backtrack+match) moves across # the traversal requires a stack. the DepthState instances below all -# store that stack - actually three of them. the results stack is +# store that stack - actually three of them. the results stack is # unusual / neat in that it is also what we need to return. # unfortunately, things are a little more complex, because it's not just # DFS, but also post-order. which means there's some extra messing around # so that the node ordering is correct. -abstract DepthState<:RepeatState +abstract type DepthState<:RepeatState end # an arbitrary iter to pass to a state where it's not needed (typically from # failure) arbitrary(s::DepthState) = s.iters[1] -@auto_hash_equals immutable DepthSlurp{I}<:DepthState +@auto_hash_equals struct DepthSlurp{I}<:DepthState # there's a mismatch in lengths here because the empty results is # associated with an iter and state results::Vector{Value} # accumulated. starts [] @@ -191,13 +191,13 @@ arbitrary(s::DepthState) = s.iters[1] # since [] at i is returned last. end -@auto_hash_equals immutable DepthYield{I}<:DepthState +@auto_hash_equals struct DepthYield{I}<:DepthState results::Vector{Value} iters::Vector{I} states::Vector{State} end -@auto_hash_equals immutable DepthBacktrack{I}<:DepthState +@auto_hash_equals struct DepthBacktrack{I}<:DepthState results::Vector{Value} iters::Vector{I} states::Vector{State} @@ -205,7 +205,7 @@ end # when first called, create base state and make internal transition -@compat execute{S,I}(k::Config{S,I}, m::Depth, s::Clean, i::I) = execute(k, m, DepthSlurp{I}(Vector{Value}(), I[i], State[DIRTY]), i) +execute(k::Config{S}, m::Depth, s::Clean, i::I) where {S,I} = execute(k, m, DepthSlurp{I}(Vector{Value}(), I[i], State[DIRTY]), i) # repeat matching until at bottom of this branch (or maximum depth) @@ -273,7 +273,7 @@ end # breadth-first specific state and logic -@auto_hash_equals type Breadth<:Repeat_ +@auto_hash_equals mutable struct Breadth<:Repeat_ name::Symbol matcher::Matcher lo::Integer @@ -283,38 +283,38 @@ end end # minimal matching is effectively breadth first traversal of a tree where: -# * performing an additional match is moving down to a new level +# * performing an additional match is moving down to a new level # * performaing an alternate match (backtrack+match) moves across # the traversal requires a queue. unfortunately, unlike with greedy, # that means we need to store the entire result for each node. # on the other hand, because the results are pre-order, the logic is simpler -# than for the greedy match (wikipedia calls this "level order" so my +# than for the greedy match (wikipedia calls this "level order" so my # terminology may be wrong). -@auto_hash_equals immutable Entry{I} +@auto_hash_equals struct Entry{I} iter::I state::State results::Vector{Value} end -abstract BreadthState<:RepeatState +abstract type BreadthState<:RepeatState end arbitrary(s::BreadthState) = s.start -@auto_hash_equals immutable BreadthGrow{I}<:BreadthState +@auto_hash_equals struct BreadthGrow{I}<:BreadthState start::I # initial iter - queue::Vector{Entry{I}} # this has to be immutable for caching + queue::Vector{Entry{I}} # this has to be struct for caching end -@auto_hash_equals immutable BreadthYield{I}<:BreadthState +@auto_hash_equals struct BreadthYield{I}<:BreadthState start::I # initial iter - queue::Vector{Entry{I}} # this has to be immutable for caching + queue::Vector{Entry{I}} # this has to be struct for caching end # when first called, create base state and make internal transition -execute{S,I}(k::Config{S,I}, m::Breadth, s::Clean, i::I) = execute(k, m, BreadthYield{I}(i, Entry{I}[Entry{I}(i, CLEAN, Any[])]), i) +execute(k::Config, m::Breadth, s::Clean, i::I) where I = execute(k, m, BreadthYield{I}(i, Entry{I}[Entry{I}(i, CLEAN, Any[])]), i) # yield the top state @@ -354,7 +354,7 @@ end # largest first (greedy) repetition without backtracking of child # matchers -@auto_hash_equals type Depth!<:Repeat_ +@auto_hash_equals mutable struct Depth!<:Repeat_ name::Symbol matcher::Matcher lo::Integer @@ -363,7 +363,7 @@ end Depth!(m, lo, hi; flatten=true) = new(:Depth!, m, lo, hi, flatten) end -type DepthSlurp!{I}<:RepeatState +mutable struct DepthSlurp!{I}<:RepeatState result::Vector{Value} iters::Vector{I} end @@ -371,12 +371,12 @@ hash(::DepthSlurp!, h::UInt) = throw(CacheException()) arbitrary(s::DepthSlurp!) = s.iters[1] -@auto_hash_equals immutable DepthYield!{I}<:RepeatState +@auto_hash_equals struct DepthYield!{I}<:RepeatState result::Vector{Value} iters::Vector{I} end -execute{S,I}(k::Config{S,I}, m::Depth!, s::Clean, i::I) = execute(k, m, DepthSlurp!{I}(Value[], I[i]), i) +execute(k::Config{S}, m::Depth!, s::Clean, i::I) where {S,I} = execute(k, m, DepthSlurp!{I}(Value[], I[i]), i) function execute(k::Config, m::Depth!, s::DepthSlurp!, i) if length(s.result) < m.hi @@ -406,7 +406,7 @@ end # smallest first (non-greedy) repetition without backtracking of child # matchers -@auto_hash_equals type Breadth!<:Repeat_ +@auto_hash_equals mutable struct Breadth!<:Repeat_ name::Symbol matcher::Matcher lo::Integer @@ -415,7 +415,7 @@ end Breadth!(m, lo, hi; flatten=true) = new(:Breadth!, m, lo, hi, flatten) end -@auto_hash_equals immutable BreadthState!{I}<:RepeatState +@auto_hash_equals struct BreadthState!{I}<:RepeatState result::Vector{Value} iter::I end @@ -450,10 +450,10 @@ failure(k::Config, m::Breadth!, s::BreadthState!) = FAILURE # match all in a sequence with backtracking -# there are two nearly identical matchers here - the only difference is +# there are two nearly identical matchers here - the only difference is # whether results are merged (Seq/+) or not (And/&). -# we need two different types so that we can define + and & appropriately. +# we need two different types so that we can define + and & appropriately. # to make the user API more conssistent we also define Series (similar to # Repeat) that takes a flatten argument. finally, both are so similar # that they can share the same state. @@ -466,12 +466,12 @@ function Series(m::Matcher...; flatten=true, backtrack=true) end end -abstract Series_<:Matcher +abstract type Series_<:Matcher end # first, the backtracking version -@auto_hash_equals type Seq<:Series_ +@auto_hash_equals mutable struct Seq<:Series_ name::Symbol matchers::Vector{Matcher} Seq(m::Matcher...) = new(:Seq, [m...]) @@ -480,7 +480,7 @@ end serial_success(m::Seq, results::Vector{Value}) = flatten(results) -@auto_hash_equals type And<:Series_ +@auto_hash_equals mutable struct And<:Series_ name::Symbol matchers::Vector{Matcher} And(m::Matcher...) = new(:And, Matcher[m...]) @@ -490,7 +490,7 @@ end # copy to get type right (Array{Value,1} -> Array{Any,1}) serial_success(m::And, results::Vector{Value}) = Any[results...] -@auto_hash_equals immutable SeriesState{I}<:State +@auto_hash_equals struct SeriesState{I}<:State results::Vector{Value} iters::Vector{I} states::Vector{State} @@ -498,7 +498,7 @@ end # when first called, call first matcher -function execute(k::Config, m::Series_, s::Clean, i) +function execute(k::Config, m::Series_, s::Clean, i) if length(m.matchers) == 0 Success(DIRTY, i, EMPTY) else @@ -542,9 +542,9 @@ end # next, the non-backtracking version -abstract Series!<:Matcher +abstract type Series!<:Matcher end -@auto_hash_equals type Seq!<:Series! +@auto_hash_equals mutable struct Seq!<:Series! name::Symbol matchers::Vector{Matcher} Seq!(m::Matcher...) = new(:Seq!, Matcher[m...]) @@ -553,7 +553,7 @@ end serial_success(m::Seq!, results::Vector{Value}) = flatten(results) -@auto_hash_equals type And!<:Series! +@auto_hash_equals mutable struct And!<:Series! name::Symbol matchers::Vector{Matcher} And!(m::Matcher...) = new(:And!, Matcher[m...]) @@ -562,7 +562,7 @@ end serial_success(m::And!, results::Vector{Value}) = Any[results...] -@auto_hash_equals immutable SeriesState!<:State +@auto_hash_equals struct SeriesState!<:State results::Vector{Value} i # index into current alternative end @@ -588,19 +588,19 @@ function Alternatives(m::Matcher...; backtrack=true) backtrack ? Alt(m...) : Alt!(m...) end -abstract Alternatives_<:Matcher +abstract type Alternatives_<:Matcher end # first, the backtracking version -@auto_hash_equals type Alt<:Alternatives_ +@auto_hash_equals mutable struct Alt<:Alternatives_ name::Symbol matchers::Vector{Matcher} Alt(matchers::Matcher...) = new(:Alt, Matcher[matchers...]) - Alt(matchers::Vector{Matcher}) = new(:Alt, matchers) + Alt(matchers::Vector{Matcher}) = new(:Alt, matchers) end -@auto_hash_equals immutable AltState{I}<:State +@auto_hash_equals struct AltState{I}<:State state::State iter::I i::Int # index into current alternative @@ -635,14 +635,14 @@ end # it does not re-match children again, but does try other # alternatives) -@auto_hash_equals type Alt!<:Alternatives_ +@auto_hash_equals mutable struct Alt!<:Alternatives_ name::Symbol matchers::Vector{Matcher} Alt!(matchers::Matcher...) = new(:Alt!, Matcher[matchers...]) - Alt!(matchers::Vector{Matcher}) = new(:Alt!, matchers) + Alt!(matchers::Vector{Matcher}) = new(:Alt!, matchers) end -@auto_hash_equals immutable AltState!{I}<:State +@auto_hash_equals struct AltState!{I}<:State iter::I i::Int # index into current alternative end @@ -664,7 +664,7 @@ failure(k::Config, m::Alt!, s::AltState!) = execute(k, m, AltState!(s.iter, s.i) # evaluate the child, but discard values and do not advance the iter -@auto_hash_equals type Lookahead<:Delegate +@auto_hash_equals mutable struct Lookahead<:Delegate name::Symbol matcher::Matcher Lookahead(matcher) = new(:Lookahead, matcher) @@ -672,7 +672,7 @@ end always_print(::Delegate) = true -@auto_hash_equals immutable LookaheadState<:DelegateState +@auto_hash_equals struct LookaheadState<:DelegateState state::State iter end @@ -686,13 +686,13 @@ success(k::Config, m::Lookahead, s, t, i, r::Value) = Success(LookaheadState(t, # if the child matches, fail; if the child fails return EMPTY # repeated calls fail (the internal matcher is not backtracked). -@auto_hash_equals type Not<:Matcher +@auto_hash_equals mutable struct Not<:Matcher name::Symbol matcher::Matcher Not(matcher) = new(:Not, matcher) end -@auto_hash_equals immutable NotState<:State +@auto_hash_equals struct NotState<:State iter end @@ -708,7 +708,7 @@ failure(k::Config, m::Not, s::NotState) = Success(s, s.iter, EMPTY) # match a regular expression. -# because Regex match against strings, this matcher works only against +# because Regex match against strings, this matcher works only against # string sources. # for efficiency, we need to know the offset where the match finishes. @@ -717,14 +717,14 @@ failure(k::Config, m::Not, s::NotState) = Success(s, s.iter, EMPTY) # we also prepend ^ to anchor the match -@auto_hash_equals type Pattern<:Matcher +@auto_hash_equals mutable struct Pattern<:Matcher name::Symbol - text::AbstractString + text::String regex::Regex groups::Tuple Pattern(r::Regex, group::Int...) = new(:Pattern, r.pattern, Regex("^(?:" * r.pattern * ")(.??)"), group) - Pattern(s::AbstractString, group::Int...) = new(:Pattern, s, Regex("^(?:" * s * ")(.??)"), group) - Pattern(s::AbstractString, flags::AbstractString, group::Int...) = new(:Pattern. s, Regex("^(?:" * s * ")(.??)", flags), group) + Pattern(s::String, group::Int...) = new(:Pattern, s, Regex("^(?:" * s * ")(.??)"), group) + Pattern(s::String, flags::String, group::Int...) = new(:Pattern. s, Regex("^(?:" * s * ")(.??)", flags), group) end print_field(m::Pattern, ::Type{Val{:text}}) = "text=\"$(m.text)\"" @@ -754,30 +754,28 @@ end # support loops -type Delayed<:Matcher +mutable struct Delayed<:Matcher name::Symbol matcher::Nullable{Matcher} Delayed() = new(:Delayed, Nullable{Matcher}()) end -function print_matcher(m::Delayed, known::Set{Matcher}) - function producer() - tag = "$(m.name)" - if (isnull(m.matcher)) - produce("$(tag) OPEN") - elseif m in known - produce("$(tag)...") - else - produce("$(tag)") - push!(known, m) - for (i, line) in enumerate(print_matcher(get(m.matcher), known)) - produce(i == 1 ? "`-$(line)" : " $(line)") - end +print_matcher(m::Delayed, known::Set{Matcher}) = Channel() do c + tag = "$(m.name)" + if (isnull(m.matcher)) + put!(c, "$(tag) OPEN") + elseif m in known + put!(c, "$(tag)...") + else + put!(c, "$(tag)") + push!(known, m) + for (i, line) in enumerate(print_matcher(get(m.matcher), known)) + put!(c, i == 1 ? "`-$(line)" : " $(line)") end end - Task(producer) end + function execute(k::Config, m::Delayed, s::Dirty, i) Response(DIRTY, i, FAILURE) end @@ -794,7 +792,7 @@ end # end of stream / string -@auto_hash_equals type Eos<:Matcher +@auto_hash_equals mutable struct Eos<:Matcher name::Symbol Eos() = new(:Eos) end @@ -810,18 +808,18 @@ end # this is general, but usually not much use with backtracking -type ParserError{I}<:Exception +mutable struct ParserError{I}<:Exception msg::AbstractString iter::I end -@auto_hash_equals type Error<:Matcher +@auto_hash_equals mutable struct Error<:Matcher name::Symbol - msg::AbstractString - Error(msg::AbstractString) = new(:Error, msg) + msg::String + Error(msg::String) = new(:Error, msg) end -function execute{I}(k::Config, m::Error, s::Clean, i::I) +function execute(k::Config, m::Error, s::Clean, i::I) where I msg = m.msg try msg = diagnostic(k.source, i, m.msg) diff --git a/src/core/names.jl b/src/core/names.jl index f5f1d92..962ee37 100644 --- a/src/core/names.jl +++ b/src/core/names.jl @@ -4,7 +4,7 @@ set_name(x::Any, s::Symbol) = x -set_name(m::Matcher, s::Symbol) = (m.name = s; m) +set_name(m::Matcher, s::Symbol) = (m.name = s; m) set_names(x) = x function set_names(node::Expr) diff --git a/src/core/parsers.jl b/src/core/parsers.jl index f5dfbba..caee409 100644 --- a/src/core/parsers.jl +++ b/src/core/parsers.jl @@ -12,11 +12,11 @@ parent(k::Config) = k.stack[end][1] # evaluation without a cache (memoization) -type NoCache{S,I}<:Config{S,I} +mutable struct NoCache{S}<:Config{S} source::S - @compat stack::Vector{Tuple{Matcher, State}} - @compat NoCache(source; kargs...) = new(source, Vector{Tuple{Matcher,State}}()) + stack::Vector{Tuple{Matcher, State}} end +NoCache(source; kargs...) = NoCache(source, Vector{Tuple{Matcher,State}}()) function dispatch(k::NoCache, e::Execute) push!(k.stack, (e.parent, e.parent_state)) @@ -48,13 +48,17 @@ end # evaluation with a complete cache (all intermediate results memoized) -@compat typealias Key{I} Tuple{Matcher,State,I} +const Key{I} = Tuple{Matcher,State,I} -type Cache{S,I}<:Config{S,I} +mutable struct Cache{S,I}<:Config{S} source::S - @compat stack::Vector{Tuple{Matcher,State,Key{I}}} + stack::Vector{Tuple{Matcher,State,Key{I}}} cache::Dict{Key{I},Message} - @compat Cache(source; kargs...) = new(source, Vector{Tuple{Matcher,State,Key{I}}}(), Dict{Key{I},Message}()) +end + +function Cache(source; kargs...) + I = typeof(start(source)) + Cache(source, Vector{Tuple{Matcher,State,Key{I}}}(), Dict{Key{I},Message}()) end function dispatch(k::Cache, e::Execute) @@ -107,12 +111,12 @@ end # a dummy matcher used by the parser -type Root<:Delegate +mutable struct Root<:Delegate name::Symbol Root() = new(:Root) end -immutable RootState<:DelegateState +struct RootState<:DelegateState state::State end @@ -125,7 +129,7 @@ failure(k::Config, m::Root, s::State) = FAILURE # to modify the behaviour you can create a new Config subtype and then # add your own dispatch functions. -function producer(k::Config, m::Matcher; debug=false) +function producer(c::Channel, k::Config, m::Matcher; debug=false) root = Root() msg::Message = Execute(root, CLEAN, m, CLEAN, start(k.source)) @@ -138,7 +142,7 @@ function producer(k::Config, m::Matcher; debug=false) if isa(msg, Execute) error("Unexpected execute message") elseif isa(msg, Success) - produce(msg.result) + put!(c, msg.result) # my head hurts msg = Execute(root, CLEAN, m, msg.child_state.state, start(k.source)) else @@ -146,7 +150,7 @@ function producer(k::Config, m::Matcher; debug=false) end end end - + catch x if (debug) println("debug was set, so showing error from inside task") @@ -162,13 +166,11 @@ end # helper functions to generate the parsers from the above -# these assume that any config construct takes a single source argument +# these assume that any config construct takes a single source argument # plus optional keyword args - -function make{S}(config, source::S, matcher; debug=false, kargs...) - I = typeof(start(source)) - k = config{S,I}(source; debug=debug, kargs...) - (k, Task(() -> producer(k, matcher; debug=debug))) +function make(config, source::S, matcher; debug=false, kargs...) where S + k = config(source; debug=debug, kargs...) + (k, Channel(c -> producer(c, k, matcher; debug=debug))) end function make_all(config; kargs_make...) @@ -178,12 +180,15 @@ function make_all(config; kargs_make...) end end -function once(task) - result = consume(task) - if task.state == :done - throw(ParserException("cannot parse")) - else - return result +function once(c::Channel) + try + take!(c) + catch err + if err isa InvalidStateException + throw(ParserException("cannot parse")) + else + rethrow(err) + end end end diff --git a/src/core/print.jl b/src/core/print.jl index 8b75035..a1a53be 100644 --- a/src/core/print.jl +++ b/src/core/print.jl @@ -8,49 +8,49 @@ print_matcher(m::Matcher) = print_matcher(m, Set{Matcher}()) # this is optimized to be compact. it could be prettier with more # spaces, but then it's not as useful for large grammars. -function print_matcher(m::Matcher, known::Set{Matcher}) - function producer() - if m in known - produce("$(m.name)...") - else - produce("$(m.name)") - if !always_print(m) - push!(known, m) - end - names = filter(n -> n != :name, fieldnames(m)) - for name in names - if isa(getfield(m, name), Matcher) - for (i, line) = enumerate(print_matcher(getfield(m, name), known)) - if name == names[end] - produce(i == 1 ? "`-$(line)" : " $(line)") - else - produce(i == 1 ? "+-$(line)" : "| $(line)") - end +function printmatch_producer(c::Channel, m::Matcher, known::Set{Matcher}) + if m in known + put!(c, "$(m.name)...") + else + put!(c, "$(m.name)") + if !always_print(m) + push!(known, m) + end + names = filter(n -> n != :name, fieldnames(m)) + for name in names + if isa(getfield(m, name), Matcher) + for (i, line) = enumerate(print_matcher(getfield(m, name), known)) + if name == names[end] + put!(c, i == 1 ? "`-$(line)" : " $(line)") + else + put!(c, i == 1 ? "+-$(line)" : "| $(line)") end - elseif isa(getfield(m, name), Array{Matcher,1}) - for (j, x) in enumerate(getfield(m, name)) - tag = name == :matchers ? "[$j]" : "$(name)[$j]" - for (i, line) = enumerate(print_matcher(getfield(m, name)[j], known)) - if name == names[end] && j == length(getfield(m, name)) - produce(i == 1 ? "`-$(tag):$(line)" : " $(line)") - else - produce(i == 1 ? "+-$(tag):$(line)" : "| $(line)") - end + end + elseif isa(getfield(m, name), Array{Matcher,1}) + for (j, x) in enumerate(getfield(m, name)) + tag = name == :matchers ? "[$j]" : "$(name)[$j]" + for (i, line) = enumerate(print_matcher(getfield(m, name)[j], known)) + if name == names[end] && j == length(getfield(m, name)) + put!(c, i == 1 ? "`-$(tag):$(line)" : " $(line)") + else + put!(c, i == 1 ? "+-$(tag):$(line)" : "| $(line)") end end + end + else + if name == names[end] + put!(c, "`-$(print_field(m, Val{name}))") else - if name == names[end] - produce("`-$(print_field(m, Val{name}))") - else - produce("+-$(print_field(m, Val{name}))") - end + put!(c, "+-$(print_field(m, Val{name}))") end end end end - Task(producer) end +print_matcher(m::Matcher, known::Set{Matcher}) = Channel(c->printmatch_producer(c, m, known)) + + function Base.print(io::Base.IO, m::Matcher) print(io, join(print_matcher(m), "\n")) end diff --git a/src/core/sources.jl b/src/core/sources.jl index 5253ce4..fcd3c23 100644 --- a/src/core/sources.jl +++ b/src/core/sources.jl @@ -35,7 +35,7 @@ function round_down(s, i) nl(s[i]) ? i-1 : i end -function diagnostic(s::AbstractString, i, msg) +function diagnostic(s::String, i, msg) if i < 1 l, c, t = 0, 0, "[Before start]" elseif i > length(s) @@ -51,12 +51,12 @@ function diagnostic(s::AbstractString, i, msg) end # given a source and iterator, return following text for regexp -forwards(s::AbstractString, i) = SubString(s, i) +forwards(s::String, i) = SubString(s, i) # originally i thought that UTF8 would require code to move forwards # the given number of characters. but discard() is called only from # regex and that returns the number of *bytes*. -discard(::AbstractString, i, n) = i + n +discard(::String, i, n) = i + n # io instance, from which lines are read. the lines are stored so that we # have backtracking. this is pretty pointless, because you might as well just @@ -68,26 +68,27 @@ discard(::AbstractString, i, n) = i + n # somewhere. # all the below is based on line_at() -abstract LineAt +abstract type LineAt end -type LineSource{S}<:LineAt +mutable struct LineSource{S}<:LineAt io::IO zero::Int # offset to lines (lines[x] contains line x+zero) limit::Int # maximum number of lines lines::Vector{S} - LineSource(io::IO, line::S; limit=-1) = new(io, 0, limit, S[line]) end +LineSource(io::IO, line::S; limit=-1) where {S} = LineSource(io, 0, limit, S[line]) + function LineSource(io::IO; limit=-1) - line = readline(io) - LineSource{typeof(line)}(io, line; limit=limit) + line = readline(io; chomp=false) + LineSource(io, line; limit=limit) end -LineSource{S<:AbstractString}(s::S; limit=-1) = LineSource(IOBuffer(s); limit=limit) +LineSource(s::AbstractString; limit=-1) = LineSource(IOBuffer(s); limit=limit) -immutable LineException<:FailureException end +struct LineException<:FailureException end -@auto_hash_equals immutable LineIter +@auto_hash_equals struct LineIter line::Int column::Int end @@ -102,11 +103,12 @@ start(f::LineAt) = LineIter(1, 1) function line_at(s::LineSource, i::LineIter; check::Bool=true) if check && i.line <= s.zero || i.column < 1 + #@show i.line, s.zero, i.column throw(LineException()) else n = i.line - s.zero while length(s.lines) < n - push!(s.lines, readline(s.io)) + push!(s.lines, readline(s.io, chomp=false)) end while s.limit > 0 && length(s.lines) > s.limit s.zero += 1 @@ -114,6 +116,7 @@ function line_at(s::LineSource, i::LineIter; check::Bool=true) end line = s.lines[i.line - s.zero] if check && i.column > length(line) + #@show i.column, length(line) throw(LineException()) end end @@ -147,7 +150,7 @@ function diagnostic(s::LineAt, i::LineIter, msg) end catch x if !isa(x, FailureException) - throw(x) + rethrow(x) end end fmt_error(i.line, i.column, line, msg) @@ -172,5 +175,3 @@ function discard(s::LineAt, i::LineIter, n) end i end - - diff --git a/src/core/transforms.jl b/src/core/transforms.jl index fe40e1f..6abe6f0 100644 --- a/src/core/transforms.jl +++ b/src/core/transforms.jl @@ -10,7 +10,7 @@ Transform(matcher, f) = new(:Transform, matcher, f) end -@auto_hash_equals immutable TransformState<:DelegateState +@auto_hash_equals struct TransformState<:DelegateState state::State end @@ -30,7 +30,7 @@ success(k::Config, m::Transform, s, t, i, r::Value) = Success(TransformState(t), ITransform(matcher, f) = new(:ITransform, matcher, f) end -@auto_hash_equals immutable ITransformState<:DelegateState +@auto_hash_equals struct ITransformState<:DelegateState state::State end diff --git a/src/core/try.jl b/src/core/try.jl index 1e250bb..0e50543 100644 --- a/src/core/try.jl +++ b/src/core/try.jl @@ -13,21 +13,21 @@ # already available in memory). but strings can also be wrapped. -type TrySource{S}<:LineAt +mutable struct TrySource{S}<:LineAt io::IO frozen::Int # non-zero is frozen; count allows nested Try() zero::Int # offset to lines (lines[x] contains line x+zero) right::Int # rightmost expired column lines::Vector{S} - TrySource(io::IO, line::S) = new(io, 0, 0, 0, S[line]) + TrySource(io::IO, line::S) where {S} = new{S}(io, 0, 0, 0, S[line]) end function TrySource(io::IO) - line = readline(io) - TrySource{typeof(line)}(io, line) + line = readline(io, chomp=false) + TrySource(io, line) end -TrySource{S<:AbstractString}(s::S) = TrySource(IOBuffer(s)) +TrySource(s::AbstractString) = TrySource(IOBuffer(s)) function expire(s::TrySource, i::LineIter) @@ -51,7 +51,7 @@ function line_at(f::TrySource, s::LineIter; check::Bool=true) end n = s.line - f.zero while length(f.lines) < n - push!(f.lines, readline(f.io)) + push!(f.lines, readline(f.io, chomp=false)) end f.lines[n] end @@ -85,7 +85,7 @@ end Try(matcher) = new(:Try, matcher) end -@auto_hash_equals immutable TryState<:DelegateState +@auto_hash_equals struct TryState<:DelegateState state::State end diff --git a/src/core/types.jl b/src/core/types.jl index f79e100..6f411b5 100644 --- a/src/core/types.jl +++ b/src/core/types.jl @@ -7,30 +7,31 @@ # name::Symbol # which is set automatically to the matcher type by the constructor. # (re-set to a more useful type inside with_names() - see names.jl) -abstract Matcher +abstract type Matcher end -abstract Message # data sent between trampoline and methods -abstract State # state associated with Matchers during evaluation +abstract type Message end # data sent between trampoline and methods +abstract type State end # state associated with Matchers during evaluation # used to configure the parser. all Config subtypes must have associated # dispatch functions (see parser.jl), a parent() function, and have a # constructor that takes the source as first argument and additional arguments # as keywords. the type of the source is exposed and if it's a subclass of # string then the iterator is assumed to be a simple integer index. -abstract Config{S,I} +abstract type Config{S} end + # important notes on mutability / hash / equality -# 1 - immutable types in julia are not "just" immutable. they are +# 1 - struct types in julia are not "just" struct. they are # effectively values - they are passed by value. so do not use -# "immutable" just because the data should not change. think about +# "struct" just because the data should not change. think about # details. -# 2 - immutable types have an automatic equality and hash based on +# 2 - struct types have an automatic equality and hash based on # content (which is copied). mutable types have an automatic equality # and hash based on address. so default hash and equality for -# immutable types that contain mutable types, and for mutable types, +# struct types that contain mutable types, and for mutable types, # may not be what is required. # 3 - caching within the parser REQUIRES that bpth Matcher and State @@ -45,7 +46,7 @@ abstract Config{S,I} # 5 - for states, which often includes mutable result objects, more # care is needed: -# 5a - whether or not State instances are mutable or immutable, they, +# 5a - whether or not State instances are mutable or struct, they, # and their contents, must not change during matching. so all arrays, # for example, must be copied when new instances are created with # different values. @@ -65,7 +66,7 @@ abstract Config{S,I} # use an array to handle empty values in a natural way -typealias Value Vector{Any} +const Value = Vector{Any} EMPTY = Any[] @@ -83,7 +84,7 @@ end # parent and parent_state are popped from the stack. a call is made to # success(config, parent, parent_state, child_state, iter, result) -immutable Success{CS<:State,I}<:Message +struct Success{CS<:State,I}<:Message child_state::CS # parent to store, passed in next call for backtracking iter::I # advanced as appropriate result::Value # possibly empty @@ -91,12 +92,12 @@ end # parent and parent_state are popped from the stack. a call is made to # failure(config, parent, parent_state) -immutable Failure<:Message end +struct Failure<:Message end FAILURE = Failure() # parent and parent_state are pushed to the stack. a call is made to # execute(config, child, child_state, iter) -immutable Execute{I}<:Message +struct Execute{I}<:Message parent::Matcher # stored by trampoline, added to response parent_state::State # stored by trampoline, added to response child::Matcher # the matcher to evaluate @@ -108,14 +109,14 @@ end # State sub-types -# use immutable types because these are simple, magic values +# use struct types because these are simple, magic values # the state used on first call -immutable Clean<:State end +struct Clean<:State end CLEAN = Clean() # the state used when no further calls should be made -immutable Dirty<:State end +struct Dirty<:State end DIRTY = Dirty() @@ -124,19 +125,15 @@ DIRTY = Dirty() # user-generated errors (ie bad input, etc). # internal errors in the library (bugs) may raise Error -immutable ParserException<:Exception +struct ParserException<:Exception msg end # this cannot be cached (thrown by hash()) -immutable CacheException<:Exception end +struct CacheException<:Exception end # this is equivalent to a matcher returning Failure. used when source # information is not available. -abstract FailureException<:Exception +abstract type FailureException<:Exception end -if VERSION >= v"0.4.0-" - typealias Applicable Union{Function, DataType} -else - typealias Applicable Union(Function, DataType) -end +const Applicable = Union{Function, DataType} diff --git a/src/dot/DOT.jl b/src/dot/DOT.jl index 6532e3a..cb20cde 100644 --- a/src/dot/DOT.jl +++ b/src/dot/DOT.jl @@ -1,8 +1,7 @@ - +__precompile__() module DOT using ...ParserCombinator -using Compat using AutoHashEquals import Base: == @@ -25,32 +24,32 @@ export Statement, Statements, ID, StringID, NumericID, HtmlID, Attribute, # see test/dot/examples.jl for examples accessing fields in this structure -abstract Statement +abstract type Statement end -typealias Statements Vector{Statement} +const Statements = Vector{Statement} -abstract ID +abstract type ID end -@auto_hash_equals immutable StringID <: ID +@auto_hash_equals struct StringID <: ID id::AbstractString end -@auto_hash_equals immutable NumericID <: ID +@auto_hash_equals struct NumericID <: ID id::AbstractString end -@auto_hash_equals immutable HtmlID <: ID +@auto_hash_equals struct HtmlID <: ID id::AbstractString end -@auto_hash_equals immutable Attribute <: Statement +@auto_hash_equals struct Attribute <: Statement name::ID value::ID end -typealias Attributes Vector{Attribute} +const Attributes = Vector{Attribute} -@auto_hash_equals immutable Graph +@auto_hash_equals struct Graph strict::Bool directed::Bool id::Nullable{ID} @@ -59,54 +58,55 @@ typealias Attributes Vector{Attribute} Graph(s::Bool, d::Bool, st::Statements) = new(s, d, Nullable{ID}(), st) end -@auto_hash_equals immutable SubGraph <: Statement +@auto_hash_equals struct SubGraph <: Statement id::Nullable{ID} stmts::Statements SubGraph(id::ID, s::Statements) = new(Nullable{ID}(id), s) SubGraph(s::Statements) = new(Nullable{ID}(), s) end -@auto_hash_equals immutable Port +@auto_hash_equals struct Port id::Nullable{ID} point::Nullable{AbstractString} + Port(id::ID, p::AbstractString) = new(Nullable{ID}(id), Nullable{AbstractString}(p)) Port(id::ID) = new(Nullable{ID}(id), Nullable{AbstractString}()) Port(p::AbstractString) = new(Nullable{ID}(), Nullable{AbstractString}(p)) end -@auto_hash_equals immutable NodeID +@auto_hash_equals struct NodeID id::ID port::Nullable{Port} NodeID(id::ID, p::Port) = new(id, Nullable{Port}(p)) NodeID(id::ID) = new(id, Nullable{Port}()) end -@auto_hash_equals immutable Node <: Statement +@auto_hash_equals struct Node <: Statement id::NodeID attrs::Attributes Node(id::NodeID, a::Attributes) = new(id, a) Node(id::NodeID) = new(id, Attribute[]) end -@compat typealias EdgeNode Union{NodeID, SubGraph} -typealias EdgeNodes Vector{EdgeNode} +const EdgeNode = Union{NodeID, SubGraph} +const EdgeNodes = Vector{EdgeNode} -@auto_hash_equals immutable Edge <: Statement +@auto_hash_equals struct Edge <: Statement nodes::EdgeNodes attrs::Attributes Edge(n::EdgeNodes, a::Attributes) = new(n, a) Edge(n::EdgeNodes) = new(n, Attribute[]) end -@auto_hash_equals immutable GraphAttributes <: Statement +@auto_hash_equals struct GraphAttributes <: Statement attrs::Attributes end -@auto_hash_equals immutable NodeAttributes <: Statement +@auto_hash_equals struct NodeAttributes <: Statement attrs::Attributes end -@auto_hash_equals immutable EdgeAttributes <: Statement +@auto_hash_equals struct EdgeAttributes <: Statement attrs::Attributes end diff --git a/src/gml/GML.jl b/src/gml/GML.jl index edd954e..1944ed5 100644 --- a/src/gml/GML.jl +++ b/src/gml/GML.jl @@ -1,18 +1,11 @@ - +__precompile__() module GML using ...ParserCombinator -using Compat export parse_raw, parse_dict, GMLError -# symbol required in 0.3 -# both work in 0.4 -# symbol gives deprecation warning in 0.5 -Symbol_ = VERSION >= v"0.5-" ? Symbol : symbol - - function mk_parser(string_input) # this is such a simple grammar that we don't need backtracking, so we can @@ -33,7 +26,7 @@ function mk_parser(string_input) comment = P"(#.*)?" # we need string input as we match multiple lines - if string_input && ParserCombinator.FAST_REGEX + if string_input wspace = "([\t ]+|[\r\n]+(#.*)?)" wstar(x) = string(x, wspace, "*") @@ -44,7 +37,7 @@ function mk_parser(string_input) open = ~Pattern(wstar("\\[")) close = ~Pattern(wstar("]")) - key = Pattern(wplus("([a-zA-Z][a-zA-Z0-9]*)"), 1) > Symbol_ + key = Pattern(wplus("([a-zA-Z][a-zA-Z0-9]*)"), 1) > Symbol int = Pattern(wstar("((\\+|-)?\\d+)"), 1) > pint real = Pattern(wstar("((\\+|-)?\\d+.\\d+((E|e)(\\+|-)?\\d+)?)"), 1) > pflt str = Pattern(wstar("\"([^\"]*)\""), 1) @@ -58,7 +51,7 @@ function mk_parser(string_input) open = Seq!(E"[", spc) close = Seq!(E"]", spc) - key = Seq!(p"[a-zA-Z][a-zA-Z0-9]*", space) > Symbol_ + key = Seq!(p"[a-zA-Z][a-zA-Z0-9]*", space) > Symbol int = Seq!(p"(\+|-)?\d+", spc) > pint real = Seq!(p"(\+|-)?\d+.\d+((E|e)(\+|-)?\d+)?", spc) > pflt str = Seq!(Pattern("\"([^\"]*)\"", 1), spc) @@ -69,9 +62,9 @@ function mk_parser(string_input) sublist = Seq!(open, list, Alt!(close, expect("]"))) value = Alt!(real, int, str, sublist, expect("value")) element = Seq!(key, value) > tuple - + list.matcher = Nullable{Matcher}(element[0:end,:!] > vcat) - + # first line comment must be explicit (no previous linefeed) Seq!(comment, spc, list, Alt!(Seq!(spc, Eos()), expect("key"))) @@ -81,15 +74,11 @@ end # this returns the "natural" representation as nested arrays and tuples function parse_raw(s; debug=false) - parser = mk_parser(isa(s, AbstractString)) + parser = mk_parser(isa(s, String)) try - if ParserCombinator.FAST_REGEX - (debug ? parse_one_dbg : parse_one)(s, Trace(parser); debug=debug) - else - (debug ? parse_lines_dbg : parse_lines)(s, Trace(parser); debug=debug) - end + (debug ? parse_one_dbg : parse_one)(s, Trace(parser); debug=debug) catch x - if (debug) + if (debug) Base.show_backtrace(STDOUT, catch_backtrace()) end rethrow() @@ -127,13 +116,13 @@ end # users with different requirements are free to take the "raw" parse and build # their own object models. -type GMLError<:Exception +mutable struct GMLError<:Exception msg::AbstractString end LISTS = [:graph,:node,:edge] -typealias GMLDict Dict{Symbol, Any} +const GMLDict = Dict{Symbol, Any} function build_dict(raw; lists=LISTS, unsafe=false) root = GMLDict() diff --git a/test/bug/deadlock.jl b/test/bug/deadlock.jl index 2d07cab..704208d 100644 --- a/test/bug/deadlock.jl +++ b/test/bug/deadlock.jl @@ -1,10 +1,10 @@ using AutoHashEquals -abstract Graph +abstract type Graph end @auto_hash_equals type Node<:Graph - label::AbstractString + label::String children::Vector{Graph} Node(label, children...) = new(label, Graph[children...]) end @@ -14,31 +14,30 @@ type Cycle<:Graph Cycle() = new(Nullable{Graph}()) end -function gprint(known::Set{Graph}, n::Node) - function producer() - if n in known - produce(string(n.label, "...")) - else - push!(known, n) - produce(n.label) - for child in n.children - prefix = child == n.children[end] ? "`-" : "+-" - for line in gprint(known, child) - produce(string(prefix, line)) - prefix = child == n.children[end] ? " " : "| y" - end +function gprint_producer(c::Channel) + if n in known + put!(c, string(n.label, "...")) + else + push!(known, n) + put!(c, n.label) + for child in n.children + prefix = child == n.children[end] ? "`-" : "+-" + for line in gprint(known, child) + put!(c, string(prefix, line)) + prefix = child == n.children[end] ? " " : "| y" end - delete!(known, n) end + delete!(known, n) end - Task(producer) end +gprint(known::Set{Graph}, n::Node) = Channel(gprint_producer) + function gprint(known::Set{Graph}, c::Cycle) if isnull(c.node) - Task(() -> produce("?")) + Channel(c -> put!(c, "?")) elseif c in known - Task(() -> produce("...")) + Channel(c -> put!(c, "...")) else push!(known, c) t = gprint(known, get(c.node)) @@ -54,7 +53,7 @@ function Base.print(io::Base.IO, g::Graph) end x = Cycle() -g = Node("a", +g = Node("a", Node("b"), Node("c", x, diff --git a/test/core/calc.jl b/test/core/calc.jl index 65ca089..82b3304 100644 --- a/test/core/calc.jl +++ b/test/core/calc.jl @@ -8,15 +8,15 @@ neg = Delayed() # allow multiple negations (eg ---3) neg.matcher = Nullable{Matcher}(val | (E"-" + neg > Neg)) - + mul = E"*" + neg div = E"/" + neg > Inv prd = neg + (mul | div)[0:end] |> Prd - + add = E"+" + prd sub = E"-" + prd > Neg sum.matcher = Nullable{Matcher}(prd + (add | sub)[0:end] |> Sum) - + all = sum + Eos() end @@ -51,11 +51,11 @@ for (src, val) in [ ("-1-1", -2) ] # @test_approx_eq calc(parse_dbg(src, Trace(all))[1]) val - @test_approx_eq calc(parse_one(src, Trace(all))[1]) val + @test isapprox(calc(parse_one(src, Trace(all))[1]), val) println("$src = $val") end -for (src, ast, val) in +for (src, ast, val) in [ ("1.0", Sum([Prd([1.0])]), 1.0) ("-1.0", Sum([Prd([-1.0])]), -1.0) @@ -66,24 +66,24 @@ for (src, ast, val) in if ast != nothing @test parse_one(src, all)[1] == ast end - @test_approx_eq calc(parse_one(src, all)[1]) val + @test isapprox(calc(parse_one(src, all)[1]), val) println("$src = $val") end # some regression tests -@test_approx_eq calc(parse_one("-5.0/7.0+5.0-5.0", all)[1]) -0.7142857142857144 -@test_approx_eq eval(parse("-5.0/7.0+5.0-5.0")) -0.7142857142857144 -@test_approx_eq calc(parse_one("(0.0-9.0)", all)[1]) -9.0 -@test_approx_eq calc(parse_one("((0.0-9.0))", all)[1]) -9.0 -@test_approx_eq calc(parse_one("-((0.0-9.0))", all)[1]) 9.0 -@test_approx_eq calc(parse_one("(-6.0/5.0)", all)[1]) -1.2 -@test_approx_eq calc(parse_one("3.0*-((0.0-9.0))", all)[1]) 27 -@test_approx_eq calc(parse_one("-9.0*3.0*-((0.0-9.0))*9.0", all)[1]) -2187.0 -@test_approx_eq calc(parse_one("5.0/3.0*(-6.0/5.0)", all)[1]) -2.0 -@test_approx_eq calc(parse_one("3*6-9*1", all)[1]) 9 -@test_approx_eq calc(parse_one("4.0-5.0-0.0/8.0/5.0/3.0*(-6.0/5.0)-9.0*3.0*-((0.0-9.0))*9.0", all)[1]) -2188.0 -@test_approx_eq calc(parse_one("((-6.0/6.0+7.0))*((-1.0-3.0/5.0))+-(9.0)", all)[1]) -18.6 +@test isapprox(calc(parse_one("-5.0/7.0+5.0-5.0", all)[1]), -0.7142857142857144) +@test isapprox(eval(parse("-5.0/7.0+5.0-5.0")), -0.7142857142857144) +@test isapprox(calc(parse_one("(0.0-9.0)", all)[1]), -9.0) +@test isapprox(calc(parse_one("((0.0-9.0))", all)[1]), -9.0) +@test isapprox(calc(parse_one("-((0.0-9.0))", all)[1]), 9.0) +@test isapprox(calc(parse_one("(-6.0/5.0)", all)[1]), -1.2) +@test isapprox(calc(parse_one("3.0*-((0.0-9.0))", all)[1]), 27) +@test isapprox(calc(parse_one("-9.0*3.0*-((0.0-9.0))*9.0", all)[1]), -2187.0) +@test isapprox(calc(parse_one("5.0/3.0*(-6.0/5.0)", all)[1]), -2.0) +@test isapprox(calc(parse_one("3*6-9*1", all)[1]), 9) +@test isapprox(calc(parse_one("4.0-5.0-0.0/8.0/5.0/3.0*(-6.0/5.0)-9.0*3.0*-((0.0-9.0))*9.0", all)[1]), -2188.0) +@test isapprox(calc(parse_one("((-6.0/6.0+7.0))*((-1.0-3.0/5.0))+-(9.0)", all)[1]), -18.6) # this has a large numerical error (~1e-15) and i don't understand why @test abs(calc(parse_one("7.0/3.0*9.0-5.0-0.0+-(-9.0/7.0)*9.0*-0.0-7.0+-4.0-5.0", all)[1]) - 0.0) < 1e-10 @@ -119,7 +119,7 @@ p = Sum(Any[Prd(Any[-9.0]), Neg(Prd(Any[7.0,Inv(0.0),Inv(2.0),Inv(Sum(Any[Prd(Any[Neg(Sum(Any[Prd(Any[0.0])]))])])),3.0])), Neg(Prd(Any[7.0,Inv(Neg(Sum(Any[Prd(Any[9.0]),Prd(Any[5.0])])))])), Prd(Any[5.0]), - Neg(Prd(Any[7.0]))]) + Neg(Prd(Any[7.0]))]) a = eval(parse("-9.0-7.0/0.0/2.0/(-(0.0))*3.0-7.0/-(9.0+5.0)+5.0-7.0")) b = parse_one("-9.0-7.0/0.0/2.0/(-(0.0))*3.0-7.0/-(9.0+5.0)+5.0-7.0", all)[1] @@ -154,11 +154,11 @@ for i in 1:20 expr = replace(expr, r"-+", "-") println("expr $(expr)") try - a = eval(parse(expr)) + a = eval(parse(expr)) b = calc(parse_one(expr, all)[1]) println("$a $b") if ! isequal(a, b) # allow for Inf etc - @test_approx_eq a b + @test isapprox(a, b) end catch @test_throws Exception parse(expr) diff --git a/test/core/case.jl b/test/core/case.jl index 25a42dd..35dfbb0 100644 --- a/test/core/case.jl +++ b/test/core/case.jl @@ -33,7 +33,7 @@ function success(k::Config, m::Case, s, t, i, r::Value) new_s = CaseState(t) # get the string contents from the child matcher # (nicer code would check this was a list containing a single string) - contents::AbstractString = r[1] + contents::String = r[1] new_contents = uppercase(contents[1:1]) * contents[2:end] # and build the response from this matcher (see types.jl) Success(new_s, i, Any[new_contents]) diff --git a/test/core/fix.jl b/test/core/fix.jl index 02c8a3b..aaad726 100644 --- a/test/core/fix.jl +++ b/test/core/fix.jl @@ -5,7 +5,7 @@ import Base: == signed_prod(lst) = length(lst) == 1 ? lst[1] : Base.prod(lst) signed_sum(lst) = length(lst) == 1 ? lst[1] : Base.sum(lst) -abstract Node +abstract type Node end ==(n1::Node, n2::Node) = isequal(n1.val, n2.val) calc(n::Float64) = n type Inv<:Node val end @@ -22,21 +22,21 @@ calc(s::Sum) = signed_sum(map(calc, s.val)) spc = Drop(Star(Space())) @with_pre spc begin - + sum = Delayed() val = E"(" + spc + sum + spc + E")" | PFloat64() - + neg = Delayed() # allow multiple negations (eg ---3) neg.matcher = Nullable{Matcher}(val | (E"-" + neg > Neg)) - + mul = E"*" + neg div = E"/" + neg > Inv prd = neg + (mul | div)[0:end] |> Prd - + add = E"+" + prd sub = E"-" + prd > Neg sum.matcher = Nullable{Matcher}(prd + (add | sub)[0:end] |> Sum) - + all = sum + spc + Eos() end @@ -52,7 +52,7 @@ for (src, val) in [ (" - 1 - 1 ", -2) ] # @test_approx_eq calc(parse_dbg(src, Trace(all))[1]) val - @test_approx_eq calc(parse_one(src, Trace(all))[1]) val + @test isapprox(calc(parse_one(src, Trace(all))[1]), val) println("$src = $val") end diff --git a/test/core/stack.jl b/test/core/stack.jl index 5344df3..c4cf2de 100644 --- a/test/core/stack.jl +++ b/test/core/stack.jl @@ -16,7 +16,7 @@ stack(0, 10) @time println(stack(0, 100_000)) # stack limit is somewhere around 100,000 (certainly less than 200,000) -abstract Msg +abstract type Msg end type Call<:Msg before::Function @@ -27,7 +27,7 @@ end type Return<:Msg value::Int end - + function inc(n, m) if n > m Return(n) @@ -55,7 +55,7 @@ function trampoline(n, m) end n end - + trampoline(0, 10) @time println(trampoline(0, 100_000)) diff --git a/test/core/tests.jl b/test/core/tests.jl index 80cbe11..45cf916 100644 --- a/test/core/tests.jl +++ b/test/core/tests.jl @@ -2,7 +2,7 @@ @test parse_one("", Epsilon()) == [] @test parse_one("", Insert("foo")) == ["foo"] @test parse_one("", Drop(Insert("foo"))) == [] -@test_throws ParserException parse_one("x", Equal("a")) +@test_throws ParserException parse_one("x", Equal("a")) @test parse_one("a", Equal("a")) == ["a"] @test parse_one("aa", Equal("a")) == ["a"] @test_throws ParserException parse_one("a", Repeat(Equal("a"), 2, 2)) @@ -84,18 +84,18 @@ end for backtrack in (true, false) @test map(x -> [length(x[1]), length(x[2])], - collect(parse_all("aaa", + collect(parse_all("aaa", Seq((Repeat(Equal("a"), 0, 3; backtrack=backtrack) > tuple), - (Repeat(Equal("a"), 0, 3; backtrack=backtrack) > tuple))))) == + (Repeat(Equal("a"), 0, 3; backtrack=backtrack) > tuple))))) == Array[[3,0], [2,1],[2,0], [1,2],[1,1],[1,0], [0,3],[0,2],[0,1],[0,0]] @test map(x -> [length(x[1]), length(x[2])], - collect(parse_all("aaa", + collect(parse_all("aaa", Seq((Repeat(Equal("a"), 0, 3; backtrack=backtrack, greedy=false) > tuple), - (Repeat(Equal("a"), 0, 3; backtrack=backtrack, greedy=false) > tuple))))) == + (Repeat(Equal("a"), 0, 3; backtrack=backtrack, greedy=false) > tuple))))) == Array[[0,0],[0,1],[0,2],[0,3], [1,0],[1,1],[1,2], [2,0],[2,1], diff --git a/test/runtests.jl b/test/runtests.jl index 6a682d7..87d9ae7 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,4 +1,3 @@ - # set this to force testing for malmaud branch #FAST_REGEX=true @@ -28,7 +27,7 @@ include("gml/ok.jl") include("gml/errors.jl") include("gml/example1.jl") include("gml/example2.jl") -# need zip files unpacking +# #need zip files unpacking #include("gml/celegansneural.jl") #include("gml/polblogs.jl") #include("gml/10k-49963.jl")