Closed timotheecour closed 3 years ago
Pretty nice but please work out how backwards compat can work. Having to patch existing macros is not acceptable. Been there, done that, it's expensive and a desaster for the ecosystem.
It is my intent to have a PR for this placed before the end of February 2021. Perhaps sooner as I already have it working; I just need to write more test cases to confirm it handles more border conditions and errors gracefully.
However my solution takes a somewhat different tack: rather than combine all numeric literals into a single token for later parsing, my solution leaves the current literals as-is and adds a new one: tkStrNumPrefixLit
. (I'm not convinced that is a good name. I may shorten it to tkStrNumLit
.) The tokenizer only assigns this token when it peeks and sees an identifier adjacent to the number. The tokenizer scans the identifier on the next pass.
In the parser, when the new token is seen it attempts to run the dotExpr
function to resolve it. Essentially turning:
var a = 1234.56E7m # or 1234.56E7'm
into
var A = "1234.56E7".m
My motive is to allow this function to enable the IEEE 745 decimal library (#308) I'm writing to also use this. Specifically "m" will be the suffix (a convention used in C# and a few other languages.) One of my goals to have this fully convert/resolve at compile-time.
The f32
, f64
, u8
, etc suffixes will still be built-in to the lexer and will take priority over any proc/func/template.
Because this adds a token, other files such as docgen.nim etc will also need update, but those updates will be minimal. These changes do not modify the AST structure in any way, so macros should behave the same.
I will also make updates to documentation.
Admittedly, my solution is not as "pure" and comprehensive as yours. But it could be an intermediate place to make it more pure with another later PR.
https://github.com/nim-lang/Nim/pull/17489 will soon be merged. This RFC has been implemented.
summary
builtin literals (eg
-128'i8
) become regular user defined literals (UDL) that the parser represents as a litteral-call expressioni8("-128")
, with ASTnkLitCall(nkIdent("i8"), nkStrLit("-128"))
; which has same semantics asnkCall
parser becomes lazy, makes no attempt at parsing the string into a numerical type (parsing is deferred till actually needed insde semphase, if at all)
new UDL (bigint, rational, dec128, f80 80 bit FP) become possible as library code, see examples below
existing literal
nkCharLit .. nkFloat64Lit
get replaced by a singlenkLit
kind, andTNode
is simplified as follows:(or if
nkFloat128Lit
is still needed, toarray[2,uint64]
instead ofuint64
)all operations involving nkLit involve casting
value
fromuint64
to the appropriate type as specified bytyp : PType
(then casting back touint64
)IMO it's possible to do all this without introducing breaking changes, but this can be discussed separately
details
123'i8
) into litteral-call expressions as follows:'
that are in some set (eg: numbers +-
+ letters; precise set TBD); eg-123e-12
) is delayed until it's needed (eg for cgen, or vm)benefits
remove edge case where T.low can't be used as a litteral for signed types T, eg
-128'i8
, see https://github.com/timotheecour/Nim/issues/125; because it'd be parsed as:i8("-128")
repr
(and runnableExamples rendering etc) would preserve original source code formatting (refs https://github.com/nim-lang/Nim/issues/8871) in particalbinary/octal/1_000_000
; it'd also make it easier fornimpretty
andnim doc
parsing becomes lazier, leading to potentially faster compile times in case some large chunk of code is statically disabled, eg via
when defined cpp: let a = 12.3'f32
=>12.3
won't need to be parsed into a floatno more redundancy between the type (eg
tyInt32
) and the literal (egnkInt32Lit
) since we now just have the type + a single kind nkLit; AST is simplified, user macros and compiler code have lessTNodeKind
kinds to deal withparser backward compatibility when new literals are introduced suppose we implement these new literal handling in 1.3.7, then any nim version after that will be backward compatible using since, eg:
the "generalized" literal handling could also be backported to older nim (eg 1.2.2) using a simple hack: turn unrecognized literals (eg 1.2'f80) into an error PNode, but not a parser error, so that
since 1.3.7:
would work and not give parser errorenables user defined quote literals everything becomes user defined, so
dec128
(https://forum.nim-lang.org/t/6310#38884) can be written via:the builtin literals are not special builtins anymore, and require symbols defined in system.nim, eg:
with
-0x12e4567'bar
, ifbar
isn't defined in scope, it gives a regular CT error (bar
not defined)examples
all these types can be implemented as library solution and preserve nice native looking syntax, and also, would render as numerical types, not strings (pending updating syntax highlighters, including github linguist, as evidenced by ugly highlighting in this post)
80 bit float
bigint
decimal128
rational numbers
complex numbers
symbolic math
note
since it's user defined, module-scoped aliases are possible, eg if a module deals a lot with rationals it can write:
I originally suggested an initial concept of this in https://github.com/nim-lang/compilerdev/issues/7 but then realized it could be generalized to support arbitrary user defined literals and simplify the AST thanks to parser transformation, so that builtin literals (eg
123'i8
) are no longer builtin and naturally extend to other user defined literalsliterals without quote (quote as in
1'i8
) can be handled uniformly as literals with quote by a fake litteral-call, eg:openLitteral
preserves the same semantics asconst b = 1234
, in that the type is not bound but kept open so that this remains valid:VM
likewise for VM:
TFullReg
could be simplified as:with following benefits: