GiggleLiu / NiLang.jl

A differential eDSL that can run faster than light and go back to the past.
https://giggleliu.github.io/NiLang.jl/dev
Apache License 2.0
250 stars 16 forks source link

Weird syntax! #83

Closed c42f closed 1 year ago

c42f commented 1 year ago

Hey, as part of writing JuliaSyntax and trying to integrate it over at https://github.com/JuliaLang/julia/pull/46372 I've noticed you have some weird syntax in this package for a few function definitions with @i. For example:

https://github.com/GiggleLiu/NiLang.jl/blob/a80e991bf88021181b7e9b5199868152d8bd3a15/src/ulog.jl#L5-L7

The part with the function being named (:*=(identity)) - that seems really weird and maybe not entirely intentional that the reference parser supports this. I don't know what this syntax is meant to mean or whether I should try to accommodate it. NiLang and NiLangCore are the only package using such syntax as far as I can tell.

Here's the report from my test tool:

┌ Error: Parsers succeed but disagree
│   fpath = "/home/c42f/.julia/dev/JuliaSyntax/tools/pkgs/NiLangCore_0.10.6/test/compiler.jl"
│   failing_source =
│    
│        ex3 = :(
│    #           ┌──────────────────────────────
│        @inline function (~f)(x!::T, y) where T
│              anc ← zero(T)
│              anc += identity(x!)
│              x! -= y * anc
│              anc -= identity(x!)
│              anc → zero(T)
│        end)
│    #─────┘
│        @test nilang_ir(@__MODULE__, ex) |> NiLangCore.rmlines == ex2 |> NiLangCore.rmlines
│        @test nilang_ir(@__MODULE__, ex; reversed=true) |> NiLangCore.rmlines == ex3 |> NiLangCore.rmlines
│    
│    
│   reduced_failures =
│    1-element Vector{String}:
│     "function (~fy)()\nend"
└ @ Main /home/c42f/.julia/dev/JuliaSyntax/tools/check_all_packages.jl:44
┌ Error: Parsers succeed but disagree
│   fpath = "/home/c42f/.julia/dev/JuliaSyntax/tools/pkgs/NiLang_0.9.3/src/ulog.jl"
│   failing_source =
│    export ULogarithmic
│    
│    #          ┌─────────────────────────────────────────────────────────
│    @i @inline function (:*=(identity))(x::ULogarithmic, y::ULogarithmic)
│        x.log += y.log
│    end
│    #─┘
│    
│    @i @inline function (:*=(identity))(x::ULogarithmic, y::Real)
│    
│    end
│    
│    #          ┌─────────────────────────────────────────────────
│    @i @inline function (:*=(identity))(x::ULogarithmic, y::Real)
│        x.log += log(y)
│    end
│    #─┘
│    
│    for (OP1, OP2, OP3) in [(:*, :+, :(+=)), (:/, :-, :(-=))]
│    
│    
│    for (OP1, OP2, OP3) in [(:*, :+, :(+=)), (:/, :-, :(-=))]
│    #                  ┌─────────────────────────────────────────────────────────────────────────
│       @eval @i @inline function (:*=($OP1))(out!::ULogarithmic, x::ULogarithmic, y::ULogarithmic)
│          out!.log += $OP2(x.log, y.log)
│       end
│    #─────┘
│    
│       @eval @i @inline function (:*=($OP1))(out!::ULogarithmic, x::Real, y::Real)
│    
│       end
│    
│    #                  ┌─────────────────────────────────────────────────────────
│       @eval @i @inline function (:*=($OP1))(out!::ULogarithmic, x::Real, y::Real)
│          out!.log += log(x)
│           $(Expr(OP3, :(out!.log), :(log(y))))
│       end
│    #─────┘
│    
│       @eval @i @inline function (:*=($OP1))(out!::ULogarithmic, x::ULogarithmic, y::Real)
│    
│       end
│    
│    #                  ┌─────────────────────────────────────────────────────────────────
│       @eval @i @inline function (:*=($OP1))(out!::ULogarithmic, x::ULogarithmic, y::Real)
│          out!.log += x.log
│           $(Expr(OP3, :(out!.log), :(log(y))))
│       end
│    #─────┘
│    
│       @eval @i @inline function (:*=($OP1))(out!::ULogarithmic, x::Real, y::ULogarithmic)
│    
│       end
│    
│    #                  ┌─────────────────────────────────────────────────────────────────
│       @eval @i @inline function (:*=($OP1))(out!::ULogarithmic, x::Real, y::ULogarithmic)
│          out!.log += log(x)
│           $(Expr(OP3, :(out!.log), :(y.log)))
│       end
│    #─────┘
│    end
│    
│    
│    end
│    
│    #          ┌──────────────────────────────────────────────────────────────
│    @i @inline function (:*=(^))(out!::ULogarithmic, x::ULogarithmic, y::Real)
│        out!.log += x.log * y
│    end
│    #─┘
│    
│    gaussian_log(x) = log1p(exp(x))
│    
│    
│   reduced_failures =
│    7-element Vector{String}:
│     "function (:*=(i)ga)end"
│     "function(())end"
│     "function (:*=(\$)t)end"
│     "function ((\$)lE((l)))end"
│     "function (:*=(\$))(((ou)))\n\tend"
│     "function ((\$)t(O))end"
│     "function (:*=(^)oc) end"
└ @ Main /home/c42f/.julia/dev/JuliaSyntax/tools/check_all_packages.jl:44
GiggleLiu commented 1 year ago

Hi, thanks for the issue.

(:*=(f)) customizes the operation y *= f(args...), which does not obey Julia's semantics. In Julia, y *= f(args...) is equivalent to y = y * f(args...), however, assign in reversible programming is not allowed.

c42f commented 1 year ago

Thanks so much for the explanation :-)

The underlying parsing problem is that it's quite hard to disambiguate between anonymous functions argument lists vs parenthesized function names. The reference parser does this disambiguation one particular way but it has some inconsistencies which I've fixed in JuliaSyntax. However, this meant the particular syntax that NiLang uses parses differently.

The disambiguation I use currently gets confused because (:*=(f)) contains an Expr(:call) which leads the parser to think it's parsing the body next.

I'll see whether I can think of an extension to the current disambiguation rules which lets the NiLang syntax parse and also parses everything else correctly.

GiggleLiu commented 1 year ago

I see your point. I will consider changing the design to remove ambiguity. But it will take some time.

c42f commented 1 year ago

I think I've got a fix for this in JuliaSyntax. So despite this being "a bit weird" this syntax will continue to work! Understanding your use case was very helpful thanks :)