@@ -18,6 +18,7 @@ const (
1818 TokenString
1919 TokenNationalString
2020 TokenBinary
21+ TokenMoney
2122 TokenStar
2223 TokenComma
2324 TokenDot
@@ -491,8 +492,11 @@ func (l *Lexer) NextToken() Token {
491492 case '"' :
492493 tok = l .readDoubleQuotedIdentifier ()
493494 default :
494- // Handle $ only if followed by a letter (for pseudo-columns like $ROWGUID)
495- if l .ch == '$' && isLetter (l .peekChar ()) {
495+ // Handle currency symbols for money literals
496+ if l .isCurrencySymbol () {
497+ tok = l .readMoneyLiteral ()
498+ } else if l .ch == '$' && isLetter (l .peekChar ()) {
499+ // Handle $ only if followed by a letter (for pseudo-columns like $ROWGUID)
496500 tok = l .readIdentifier ()
497501 } else if isLetter (l .ch ) || l .ch == '_' || l .ch == '@' || l .ch == '#' {
498502 tok = l .readIdentifier ()
@@ -817,6 +821,19 @@ func (l *Lexer) readNumber() Token {
817821 }
818822 }
819823 }
824+ // Handle scientific notation (e.g., 2e, 2e+5, 2E-10, 1.5e3)
825+ // T-SQL allows 'e' without exponent digits (e.g., "2e" is a valid real literal)
826+ if l .ch == 'e' || l .ch == 'E' {
827+ l .readChar () // consume e/E
828+ // Optional sign
829+ if l .ch == '+' || l .ch == '-' {
830+ l .readChar ()
831+ }
832+ // Optional exponent digits (T-SQL allows just "2e" with no exponent)
833+ for isDigit (l .ch ) {
834+ l .readChar ()
835+ }
836+ }
820837 return Token {
821838 Type : TokenNumber ,
822839 Literal : l .input [startPos :l .pos ],
@@ -837,6 +854,72 @@ func isDigit(ch byte) bool {
837854 return ch >= '0' && ch <= '9'
838855}
839856
857+ // isCurrencySymbol checks if current position is a currency symbol for money literals
858+ func (l * Lexer ) isCurrencySymbol () bool {
859+ if l .ch == '$' {
860+ // Check if followed by digit, space+digit, or +/- then digit
861+ next := l .peekChar ()
862+ if isDigit (next ) || next == ' ' || next == '+' || next == '-' {
863+ return true
864+ }
865+ return false
866+ }
867+ // Check for Unicode currency symbols
868+ if l .ch >= 0x80 {
869+ r , _ := l .peekRune ()
870+ // Common currency symbols: £ (U+00A3), ¤ (U+00A4), ¥ (U+00A5)
871+ // and various others in the Currency Symbols block
872+ if r == '£' || r == '¤' || r == '¥' || r == '৲' || r == '৳' ||
873+ r == '฿' || r == '₡' || r == '₢' || r == '₣' || r == '₤' ||
874+ r == '₦' || r == '₧' || r == '₨' || r == '₩' || r == '₪' || r == '₫' {
875+ return true
876+ }
877+ }
878+ return false
879+ }
880+
881+ // readMoneyLiteral reads a money literal starting with a currency symbol
882+ func (l * Lexer ) readMoneyLiteral () Token {
883+ startPos := l .pos
884+
885+ // Read currency symbol (may be multi-byte)
886+ if l .ch >= 0x80 {
887+ _ , size := l .peekRune ()
888+ for i := 0 ; i < size ; i ++ {
889+ l .readChar ()
890+ }
891+ } else {
892+ l .readChar () // consume $
893+ }
894+
895+ // Skip optional +/- after currency symbol
896+ if l .ch == '+' || l .ch == '-' {
897+ l .readChar ()
898+ }
899+
900+ // Skip optional whitespace after currency symbol
901+ for l .ch == ' ' || l .ch == '\t' {
902+ l .readChar ()
903+ }
904+
905+ // Read digits and decimal point
906+ for isDigit (l .ch ) {
907+ l .readChar ()
908+ }
909+ if l .ch == '.' {
910+ l .readChar ()
911+ for isDigit (l .ch ) {
912+ l .readChar ()
913+ }
914+ }
915+
916+ return Token {
917+ Type : TokenMoney ,
918+ Literal : l .input [startPos :l .pos ],
919+ Pos : startPos ,
920+ }
921+ }
922+
840923var keywords = map [string ]TokenType {
841924 "SELECT" : TokenSelect ,
842925 "FROM" : TokenFrom ,
0 commit comments