diff --git a/M2/Macaulay2/d/actors5.d b/M2/Macaulay2/d/actors5.d index fdde6bcd9ae..1c15f103dae 100644 --- a/M2/Macaulay2/d/actors5.d +++ b/M2/Macaulay2/d/actors5.d @@ -198,6 +198,15 @@ setup(AmpersandS,ampersandfun); hathatfun(lhs:Code,rhs:Code):Expr := binarymethod(lhs,rhs,HatHatS); setup(HatHatS,hathatfun); +interpunctfun(lhs:Code, rhs:Code):Expr := binarymethod(lhs, rhs, InterpunctS); +setup(InterpunctS, interpunctfun); + +boxtimesfun(lhs:Code, rhs:Code):Expr := binarymethod(lhs, rhs, BoxTimesS); +setup(BoxTimesS, boxtimesfun); + +shuffleproductfun(lhs:Code, rhs:Code):Expr := binarymethod(lhs, rhs, ShuffleProductS); +setup(ShuffleProductS, shuffleproductfun); + Tildefun(rhs:Code):Expr := unarymethod(rhs,TildeS); setuppostfix(TildeS,Tildefun); @@ -1578,23 +1587,24 @@ fillnodes(n:LexNode):void := ( fillnodes(baseLexNode); setupconst("operatorNames",Expr(operatorNames)); +createSymbol(w:Word, d:Dictionary, s:string):Expr := ( + if !isvalidsymbol(s) then buildErrorPacket("invalid symbol") + else if d.Protected then ( + buildErrorPacket("attempted to create symbol in protected dictionary")) + else ( + t := makeSymbol(w, tempPosition, d); + globalFrame.values.(t.frameindex))); + getglobalsym(d:Dictionary,s:string):Expr := ( w := makeUniqueWord(s,parseWORD); when lookup(w,d.symboltable) is x:Symbol do Expr(SymbolClosure(globalFrame,x)) - is null do ( - if !isvalidsymbol(s) then return buildErrorPacket("invalid symbol"); - if d.Protected then return buildErrorPacket("attempted to create symbol in protected dictionary"); - t := makeSymbol(w,tempPosition,d); - globalFrame.values.(t.frameindex))); + is null do createSymbol(w, d, s)); getglobalsym(s:string):Expr := ( w := makeUniqueWord(s,parseWORD); when globalLookup(w) is x:Symbol do Expr(SymbolClosure(if x.thread then threadFrame else globalFrame,x)) - is null do ( - if globalDictionary.Protected then return buildErrorPacket("attempted to create symbol in protected dictionary"); - t := makeSymbol(w,tempPosition,globalDictionary); - globalFrame.values.(t.frameindex))); + is null do createSymbol(w, globalDictionary, s)); getGlobalSymbol(e:Expr):Expr := ( when e diff --git a/M2/Macaulay2/d/binding.d b/M2/Macaulay2/d/binding.d index 7276cf35803..f4b4985e516 100644 --- a/M2/Macaulay2/d/binding.d +++ b/M2/Macaulay2/d/binding.d @@ -285,8 +285,12 @@ bumpPrecedence(); export MinusS := makeKeyword(unarybinaryleft("-")); -- also binary export PlusS := makeKeyword(unarybinaryleft("+")); -- also binary export PlusPlusS := makeKeyword(binaryleft("++")); +bumpPrecedence(); + export InterpunctS := makeKeyword(binaryleft("·")); bumpPrecedence(); export StarStarS := makeKeyword(binaryleft("**")); + export BoxTimesS := makeKeyword(binaryleft("⊠")); + export ShuffleProductS := makeKeyword(binaryleft("⧢")); bumpPrecedence(); precBracket := prec; export leftbracket := parens("[","]",precBracket, precRightParen, precRightParen); @@ -511,7 +515,10 @@ export opsWithBinaryMethod := array(SymbolClosure)( PowerGreaterEqualS, UnderscoreGreaterEqualS, PowerLessS, UnderscoreLessS, PowerLessEqualS, UnderscoreLessEqualS, - PowerStarStarS + PowerStarStarS, + InterpunctS, + BoxTimesS, + ShuffleProductS ); export opsWithUnaryMethod := array(SymbolClosure)( StarS, MinusS, PlusS, LessLessS, QuestionQuestionS, diff --git a/M2/Macaulay2/d/ctype.d b/M2/Macaulay2/d/ctype.d index 2615df86ce3..c08b28f1d1b 100644 --- a/M2/Macaulay2/d/ctype.d +++ b/M2/Macaulay2/d/ctype.d @@ -25,8 +25,7 @@ foreach c in " \t\r" do setchartype(c,WHITE); foreach c in "\n" do setchartype(c,NEWLINE); foreach c in "$'" do setchartype(c,ALNUMEXTRA); -for c from 128 to 225 do setchartype(char(c),ALPHA); -- 226 is unicode math symbols -for c from 227 to 255 do setchartype(char(c),ALPHA); +for c from 128 to 255 do setchartype(char(c),ALPHA); setchartype('\"',QUOTE); chartype(c:int):int := if (c & ~255) == 0 then int(chartypes.c) else 0; @@ -52,10 +51,28 @@ export isspace (c:char):bool := (chartype(c) & SPACE ) != 0; export isnewline (c:char):bool := (chartype(c) & NEWLINE ) != 0; export isquote (c:char):bool := (chartype(c) & QUOTE ) != 0; -export isvalidsymbol (s:string):bool := ( - if int(uchar(s.0)) == 226 && length(s) == 3 then return true; -- ugly unicode math symbol hack +-- c = two bytes concatenated +export ismathoperator(c:int):bool := ( + (c & 0xffe0) == 0xc2a0 || -- latin-1 punctuation/symbols + c == 0xc397 || -- multiplication sign + c == 0xc3b7 || -- division sign + (c & 0xfffc) == 0xe288 || -- mathematical operators + (c & 0xfffc) == 0xe2a8 || -- supplemental mathematical operators + c == 0xe29f || -- misc. mathematical symbols A + (c & 0xfffe) == 0xe2a6 || -- misc. mathematical symbols B + (c & 0xfffc) == 0xe28c -- misc. technical + ); + +ismathoperator(c1:char, c2:char):bool := ( + ismathoperator((int(uchar(c1)) << 8) | int(uchar(c2)))); + +export isvalidsymbol(s:string):bool := ( if !isalpha(s.0) then return false; - foreach c in s do if !isalnum(c) then return false; + if ismathoperator(s.0, s.1) && length(s) == utf8charlength(s.0) + then return true; + for i from 0 to length(s) - 1 do ( + if !isalnum(s.i) || ismathoperator(s.i, s.(i + 1)) + then return false); true); -- Local Variables: diff --git a/M2/Macaulay2/d/lex.d b/M2/Macaulay2/d/lex.d index 6cf624aa8f5..9548e3d5b00 100644 --- a/M2/Macaulay2/d/lex.d +++ b/M2/Macaulay2/d/lex.d @@ -315,9 +315,15 @@ gettoken1(file:PosFile,sawNewline:bool):Token := ( return Token( if file.file.fulllines then wordEOC else NewlineW, newPosition(file, line, column), globalDictionary, dummySymbol, sawNewline)) - else if isalpha(ch) then ( -- valid symbols are an alpha (letters, any unicode except 226) followed by any number of alphanum (alpha, digit, dollar, prime) + else if ismathoperator(peek2(file)) then ( + for i from 1 to utf8charlength(char(ch)) + do tokenbuf << char(getc(file)); + return Token(makeUniqueWord(takestring(tokenbuf), parseWORD), + newPosition(file, line, column), globalDictionary, dummySymbol, sawNewline)) + else if isalpha(ch) then ( -- valid symbols are an alpha (letters, any unicode) followed by any number of alphanum (alpha, digit, dollar, prime) tokenbuf << char(getc(file)); - while isalnum(peek(file)) do tokenbuf << char(getc(file)); + while isalnum(peek(file)) && !ismathoperator(peek2(file)) + do tokenbuf << char(getc(file)); return Token(makeUniqueWord(takestring(tokenbuf), parseWORD), newPosition(file, line, column), globalDictionary, dummySymbol, sawNewline)) else if isdigit(ch) || ch==int('.') && isdigit(peek(file,1)) then ( @@ -390,7 +396,8 @@ gettoken1(file:PosFile,sawNewline:bool):Token := ( if int('.') == c then printWarningMessage(position(file),"character '"+char(c)+"' immediately following floating point number"); ); c = peek(file); - if isalpha(c) then printWarningMessage(position(file),"character '"+char(c)+"' immediately following number"); + if isalpha(c) && !ismathoperator(peek2(file)) + then printWarningMessage(position(file),"character '"+char(c)+"' immediately following number"); s := takestring(tokenbuf); return Token(Word(s,typecode,hash_t(0), parseWORD), newPosition(file, line, column), globalDictionary, dummySymbol, sawNewline)) @@ -410,12 +417,6 @@ gettoken1(file:PosFile,sawNewline:bool):Token := ( ) is word:Word do return Token(word, newPosition(file, line, column), globalDictionary, dummySymbol, sawNewline)) - else if ch == 226 then ( -- unicode math symbols - tokenbuf << char(getc(file)); - tokenbuf << char(getc(file)); - tokenbuf << char(getc(file)); - return Token(makeUniqueWord(takestring(tokenbuf), parseWORD), - newPosition(file, line, column), globalDictionary, dummySymbol, sawNewline)) else ( when recognize(file) is null do ( diff --git a/M2/Macaulay2/d/stdiop.d b/M2/Macaulay2/d/stdiop.d index 5cd4a8c6a59..1d9e7fc8a26 100644 --- a/M2/Macaulay2/d/stdiop.d +++ b/M2/Macaulay2/d/stdiop.d @@ -179,6 +179,8 @@ export peek(o:PosFile, offset:int):int := ( ); c); export peek(o:PosFile):int := peek(o,0); +-- concatenate the next two bytes into a single int +export peek2(o:PosFile):int := (peek(o) << 8) | peek(o, 1); export isatty(o:PosFile):bool := o.file.inisatty; export close(o:PosFile):int := ( when close(o.file) is errmsg do ERROR else 0 diff --git a/M2/Macaulay2/d/strings.d b/M2/Macaulay2/d/strings.d index f726f487615..9e48053329d 100644 --- a/M2/Macaulay2/d/strings.d +++ b/M2/Macaulay2/d/strings.d @@ -64,7 +64,7 @@ export index(s:string,offset:int,c:char,d:char):int := ( while i+1 < length(s) do if c == s.i && d==s.(i+1) then return i else i=i+1; -1); -utf8charlength(c0:char):int := ( +export utf8charlength(c0:char):int := ( c := int(uchar(c0)); if (c & 0x80) == 0 then 1 else if (c & 0xe0) == 0xc0 then 2 diff --git a/M2/Macaulay2/m2/exports.m2 b/M2/Macaulay2/m2/exports.m2 index c77d7ee82e6..bdd7f479a22 100644 --- a/M2/Macaulay2/m2/exports.m2 +++ b/M2/Macaulay2/m2/exports.m2 @@ -57,6 +57,9 @@ export { "_>=", "_<", "_<=", + "·", + "⊠", + "⧢", "Acknowledgement", "AdditionalPaths", "Adjacent", diff --git a/M2/Macaulay2/m2/latex.m2 b/M2/Macaulay2/m2/latex.m2 index 89f0ce9e38a..069e07c86ed 100644 --- a/M2/Macaulay2/m2/latex.m2 +++ b/M2/Macaulay2/m2/latex.m2 @@ -126,7 +126,8 @@ keywordTexMath = applyKeys(hashTable { -- both unary and binary keywords symbol || => "\\mid\\mid", symbol ^** => "{}^{\\otimes}", -- temporary solution to KaTeX issue https://github.com/KaTeX/KaTeX/issues/3576 symbol _* => "{}_*", -- temporary solution to KaTeX issue https://github.com/KaTeX/KaTeX/issues/3576 - symbol ^* => "{}^*" -- temporary solution to KaTeX issue https://github.com/KaTeX/KaTeX/issues/3576 + symbol ^* => "{}^*", -- temporary solution to KaTeX issue https://github.com/KaTeX/KaTeX/issues/3576 + symbol · => "\\cdot", },symbolBody) bbLetters := set characters "kABCDEFGHIJKLMNOPQRSTUVWXYZ" @@ -141,7 +142,8 @@ texMathLiteralTable := merge(texLiteralTable, "𝔩" => "\\mathfrak{l}","𝔪" => "\\mathfrak{m}","𝔫" => "\\mathfrak{n}","𝔬" => "\\mathfrak{o}","𝔭" => "\\mathfrak{p}","𝔮" => "\\mathfrak{q}","𝔯" => "\\mathfrak{r}","𝔰" => "\\mathfrak{s}","𝔱" => "\\mathfrak{t}","𝔲" => "\\mathfrak{u}","𝔳" => "\\mathfrak{v}", "𝔴" => "\\mathfrak{w}","𝔵" => "\\mathfrak{x}","𝔶" => "\\mathfrak{y}","𝔷" => "\\mathfrak{z}","𝔄" => "\\mathfrak{A}","𝔅" => "\\mathfrak{B}","𝔆" => "\\mathfrak{C}","𝔇" => "\\mathfrak{D}","𝔈" => "\\mathfrak{E}","𝔉" => "\\mathfrak{F}","𝔊" => "\\mathfrak{G}", "𝔋" => "\\mathfrak{H}","𝔌" => "\\mathfrak{I}","𝔍" => "\\mathfrak{J}","𝔎" => "\\mathfrak{K}","𝔏" => "\\mathfrak{L}","𝔐" => "\\mathfrak{M}","𝔑" => "\\mathfrak{N}","𝔒" => "\\mathfrak{O}","𝔓" => "\\mathfrak{P}","𝔔" => "\\mathfrak{Q}","𝔕" => "\\mathfrak{R}", - "𝔖" => "\\mathfrak{S}","𝔗" => "\\mathfrak{T}","𝔘" => "\\mathfrak{U}","𝔙" => "\\mathfrak{V}","𝔚" => "\\mathfrak{W}","𝔛" => "\\mathfrak{X}","𝔜" => "\\mathfrak{Y}","𝔝" => "\\mathfrak{Z}" + "𝔖" => "\\mathfrak{S}","𝔗" => "\\mathfrak{T}","𝔘" => "\\mathfrak{U}","𝔙" => "\\mathfrak{V}","𝔚" => "\\mathfrak{W}","𝔛" => "\\mathfrak{X}","𝔜" => "\\mathfrak{Y}","𝔝" => "\\mathfrak{Z}", + "×" => "\\times", "÷" => "\\div", "±" => "\\pm", },last) texMathLiteral = texLiteral1 texMathLiteralTable -- TODO: expand and document this behavior diff --git a/M2/Macaulay2/tests/normal/unicode.m2 b/M2/Macaulay2/tests/normal/unicode.m2 new file mode 100644 index 00000000000..0469edd6626 --- /dev/null +++ b/M2/Macaulay2/tests/normal/unicode.m2 @@ -0,0 +1,9 @@ +v = vector {1, 2} +w = vector {3, 4} +assert(v⊗w == vector {3, 4, 6, 8}) + +Vector·Vector := (v, w) -> ((transpose v#0) * w#0)_(0, 0) +assert(v·w == 11) + +-- should get "invalid symbol" error +assert try getSymbol "⟎⟎" then false else true