Skip to content

Commit

Permalink
allow unicode modifiers after ' (#37247)
Browse files Browse the repository at this point in the history
  • Loading branch information
simeonschaub authored Oct 16, 2020
1 parent 9d6da9c commit 8407c59
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 21 deletions.
3 changes: 3 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ New language features
can now be used to rename imported modules and identifiers ([#1255]).
* Unsigned literals (starting with `0x`) which are too big to fit in an `UInt128` object
are now interpreted as `BigInt` ([#23546]).
* The postfix conjugate transpose operator `'` now accepts Unicode modifiers as
suffixes, so e.g. `a'ᵀ` is parsed as `var"'ᵀ"(a)`, which can be defined by the
user. `a'ᵀ` parsed as `a' * ᵀ` before, so this is a minor change ([#37247]).

Language changes
----------------
Expand Down
54 changes: 40 additions & 14 deletions base/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1131,6 +1131,10 @@ function isidentifier(s::AbstractString)
end
isidentifier(s::Symbol) = isidentifier(string(s))

is_op_suffix_char(c::AbstractChar) = ccall(:jl_op_suffix_char, Cint, (UInt32,), c) != 0

_isoperator(s) = ccall(:jl_is_operator, Cint, (Cstring,), s) != 0

"""
isoperator(s::Symbol)
Expand All @@ -1142,7 +1146,7 @@ julia> Base.isoperator(:+), Base.isoperator(:f)
(true, false)
```
"""
isoperator(s::Union{Symbol,AbstractString}) = ccall(:jl_is_operator, Cint, (Cstring,), s) != 0
isoperator(s::Union{Symbol,AbstractString}) = _isoperator(s) || ispostfixoperator(s)

"""
isunaryoperator(s::Symbol)
Expand All @@ -1169,7 +1173,26 @@ julia> Base.isbinaryoperator(:-), Base.isbinaryoperator(:√), Base.isbinaryoper
(true, false, false)
```
"""
isbinaryoperator(s::Symbol) = isoperator(s) && (!isunaryoperator(s) || is_unary_and_binary_operator(s))
function isbinaryoperator(s::Symbol)
return _isoperator(s) && (!isunaryoperator(s) || is_unary_and_binary_operator(s)) &&
s !== Symbol("'")
end

"""
ispostfixoperator(s::Union{Symbol,AbstractString})
Return `true` if the symbol can be used as a postfix operator, `false` otherwise.
# Examples
```jldoctest
julia> Base.ispostfixoperator(Symbol("'")), Base.ispostfixoperator(Symbol("'ᵀ")), Base.ispostfixoperator(:-)
(true, true, false)
```
"""
function ispostfixoperator(s::Union{Symbol,AbstractString})
s = String(s)
return startswith(s, '\'') && all(is_op_suffix_char, SubString(s, 2))
end

"""
operator_precedence(s::Symbol)
Expand Down Expand Up @@ -1594,6 +1617,20 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int, quote_level::In
end
head !== :row && print(io, cl)

# transpose
elseif (head === Symbol("'") && nargs == 1) || (
# ' with unicode suffix is a call expression
head === :call && nargs == 2 && args[1] isa Symbol &&
ispostfixoperator(args[1]) && args[1] !== Symbol("'")
)
op, arg1 = head === Symbol("'") ? (head, args[1]) : (args[1], args[2])
if isa(arg1, Expr) || (isa(arg1, Symbol) && isoperator(arg1))
show_enclosed_list(io, '(', [arg1], ", ", ')', indent, 0)
else
show_unquoted(io, arg1, indent, 0, quote_level)
end
print(io, op)

# function call
elseif head === :call && nargs >= 1
func = args[1]
Expand Down Expand Up @@ -1622,7 +1659,7 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int, quote_level::In
end

# unary operator (i.e. "!z")
elseif isa(func,Symbol) && func in uni_ops && length(func_args) == 1
elseif isa(func,Symbol) && length(func_args) == 1 && func in uni_ops
show_unquoted(io, func, indent, 0, quote_level)
arg1 = func_args[1]
if isa(arg1, Expr) || (isa(arg1, Symbol) && isoperator(arg1))
Expand Down Expand Up @@ -1959,17 +1996,6 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int, quote_level::In
parens && print(io, ")")
end

# transpose
elseif head === Symbol('\'') && nargs == 1
if isa(args[1], Symbol)
show_unquoted(io, args[1], 0, 0, quote_level)
else
print(io, "(")
show_unquoted(io, args[1], 0, 0, quote_level)
print(io, ")")
end
print(io, head)

# `where` syntax
elseif head === :where && nargs > 1
parens = 1 <= prec
Expand Down
2 changes: 1 addition & 1 deletion src/flisp/julia_extensions.c
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ JL_DLLEXPORT int jl_id_char(uint32_t wc)
#include "julia_opsuffs.h"

// chars that can follow an operator (e.g. +) and be parsed as part of the operator
int jl_op_suffix_char(uint32_t wc)
JL_DLLEXPORT int jl_op_suffix_char(uint32_t wc)
{
static htable_t jl_opsuffs; // XXX: requires uv_once
if (!jl_opsuffs.size) { // initialize hash table of suffixes
Expand Down
18 changes: 12 additions & 6 deletions src/julia-parser.scm
Original file line number Diff line number Diff line change
Expand Up @@ -228,12 +228,12 @@

(define (op-or-sufchar? c) (or (op-suffix-char? c) (opchar? c)))

(define (read-operator port c)
(if (and (eqv? c #\*) (eqv? (peek-char port) #\*))
(define (read-operator port c0 (postfix? #f))
(if (and (eqv? c0 #\*) (eqv? (peek-char port) #\*))
(error "use \"x^y\" instead of \"x**y\" for exponentiation, and \"x...\" instead of \"**x\" for splatting."))
(if (or (eof-object? (peek-char port)) (not (op-or-sufchar? (peek-char port))))
(symbol (string c)) ; 1-char operator
(let ((str (let loop ((str (string c))
(symbol (string c0)) ; 1-char operator
(let ((str (let loop ((str (string c0))
(c (peek-char port))
(in-suffix? #f))
(if (eof-object? c)
Expand All @@ -257,7 +257,10 @@
(and (or (eq? opsym '<-) (eq? opsym '.<-))
(read-char port)
(begin0 (eqv? (peek-char port) #\-)
(io.ungetc port #\-))))
(io.ungetc port #\-)))
;; consume suffixes after ', only if parsing a call chain
;; otherwise 'ᵀ' would parse as (|'| |'ᵀ|)
(and postfix? (eqv? c0 #\') sufchar?))
(begin (read-char port)
(loop newop (peek-char port) sufchar?))
str))
Expand Down Expand Up @@ -1244,7 +1247,10 @@
(if (not (ts:space? s))
(begin
(take-token s)
(loop (list t ex)))
(let ((t (read-operator (ts:port s) #\' #t)))
(loop (if (eq? t '|'|)
(list t ex)
(list 'call t ex)))))
ex))
((|.'|) (error "the \".'\" operator is discontinued"))
((#\{ )
Expand Down
9 changes: 9 additions & 0 deletions test/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2028,3 +2028,12 @@ end

@test sprint(show, :(./)) == ":((./))"
@test sprint(show, :((.|).(.&, b))) == ":((.|).((.&), b))"

@test sprint(show, :(a'ᵀ)) == ":(a'ᵀ)"
@test sprint(show, :((+)')) == ":((+)')"
for s in (Symbol("'"), Symbol("'⁻¹"))
@test Base.isoperator(s)
@test !Base.isunaryoperator(s)
@test !Base.isbinaryoperator(s)
@test Base.ispostfixoperator(s)
end
7 changes: 7 additions & 0 deletions test/syntax.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2445,3 +2445,10 @@ end
import .TestImportAs.Mod2 as M2
@test !@isdefined(Mod2)
@test M2 === TestImportAs.Mod2

@testset "unicode modifiers after '" begin
@test Meta.parse("a'ᵀ") == Expr(:call, Symbol("'ᵀ"), :a)
@test Meta.parse("a'⁻¹") == Expr(:call, Symbol("'⁻¹"), :a)
@test Meta.parse("a'ᵀb") == Expr(:call, :*, Expr(:call, Symbol("'ᵀ"), :a), :b)
@test Meta.parse("a'⁻¹b") == Expr(:call, :*, Expr(:call, Symbol("'⁻¹"), :a), :b)
end

0 comments on commit 8407c59

Please sign in to comment.