Files

697 lines
17 KiB
Go

package parser
import (
"fmt"
"math/big"
"strings"
antlrParser "github.com/hanzo-fi/numscript/internal/parser/antlrParser"
"github.com/hanzo-fi/numscript/internal/utils"
"github.com/antlr4-go/antlr/v4"
)
type ParserError struct {
Range
Msg string
}
func (e ParserError) Error() string {
return e.Msg
}
type ParseResult struct {
Source string
Value Program
Errors []ParserError
}
type ErrorListener struct {
antlr.DefaultErrorListener
Errors []ParserError
}
func (l *ErrorListener) SyntaxError(recognizer antlr.Recognizer, offendingSymbol interface{}, startL, startC int, msg string, e antlr.RecognitionException) {
length := 1
if token, ok := offendingSymbol.(antlr.Token); ok {
length = len(token.GetText())
}
endL := startL
endC := startC + length - 1 // -1 so that end character is inside the offending token
l.Errors = append(l.Errors, ParserError{
Msg: msg,
Range: Range{
Start: Position{Character: startC, Line: startL - 1},
End: Position{Character: endC, Line: endL - 1},
},
})
}
func Parse(input string) ParseResult {
// TODO handle lexer errors
listener := &ErrorListener{}
is := antlr.NewInputStream(input)
lexer := antlrParser.NewLexer(is)
lexer.RemoveErrorListeners()
lexer.AddErrorListener(listener)
stream := antlr.NewCommonTokenStream(lexer, antlr.TokenDefaultChannel)
p := antlrParser.NewNumscriptParser(stream)
p.RemoveErrorListeners()
p.AddErrorListener(listener)
parsed := parseProgram(p.Program())
for _, tk := range stream.GetAllTokens() {
if tk.GetChannel() == antlr.TokenHiddenChannel {
parsed.Comments = append(parsed.Comments, Comment{
Content: tk.GetText()[2:],
Range: tokenToRange(tk),
})
}
}
return ParseResult{
Source: input,
Value: parsed,
Errors: listener.Errors,
}
}
func ParseErrorsToString(errors []ParserError, source string) string {
buf := "Got errors while parsing:\n"
for _, err := range errors {
buf += err.Msg + "\n" + err.ShowOnSource(source) + "\n"
}
return buf
}
func parseVarsDeclaration(varsCtx antlrParser.IVarsDeclarationContext) *VarDeclarations {
if varsCtx == nil {
return nil
}
varBlock := VarDeclarations{
Range: ctxToRange(varsCtx),
}
for _, varDecl := range varsCtx.AllVarDeclaration() {
decl := parseVarDeclaration(varDecl)
if decl != nil {
varBlock.Declarations = append(varBlock.Declarations, *decl)
}
}
return &varBlock
}
func parseProgram(programCtx antlrParser.IProgramContext) Program {
vars := parseVarsDeclaration(programCtx.VarsDeclaration())
featureCtx := programCtx.FeatureDecl()
var flagIdents []StringLiteral
if featureCtx != nil {
for _, flagTk := range featureCtx.GetFlag() {
flagIdents = append(flagIdents, *parseStringTk(flagTk))
}
}
var statements []Statement
for _, statementCtx := range programCtx.AllStatement() {
statements = append(statements, parseStatement(statementCtx))
}
return Program{
Flags: flagIdents,
Statements: statements,
Vars: vars,
}
}
func parseVarDeclaration(varDecl antlrParser.IVarDeclarationContext) *VarDeclaration {
if varDecl == nil {
return nil
}
var origin *ValueExpr
if varDecl.VarOrigin() != nil {
expr := parseValueExpr(varDecl.VarOrigin().ValueExpr())
// when the origin expression is missing or malformed,
// parseValueExpr returns nil; do not wrap it in a non-nil pointer
if expr != nil {
origin = &expr
}
}
return &VarDeclaration{
Range: ctxToRange(varDecl),
Type: parseVarType(varDecl.GetType_()),
Name: parseVarLiteral(varDecl.GetName()),
Origin: origin,
}
}
func parseVarLiteral(tk antlr.Token) *Variable {
if tk == nil || tk.GetTokenIndex() == -1 {
return nil
}
name := tk.GetText()[1:]
return &Variable{
Range: tokenToRange(tk),
Name: name,
}
}
func parseVarType(tk antlr.Token) *TypeDecl {
if tk == nil || tk.GetTokenIndex() == -1 {
return nil
}
return &TypeDecl{
Range: tokenToRange(tk),
Name: tk.GetText(),
}
}
func parseColorConstraint(colorConstraintCtx antlrParser.IColorConstraintContext) ValueExpr {
if colorConstraintCtx == nil {
return nil
}
return parseValueExpr(
colorConstraintCtx.ValueExpr(),
)
}
func parseSource(sourceCtx antlrParser.ISourceContext) Source {
if sourceCtx == nil {
return nil
}
range_ := ctxToRange(sourceCtx)
switch sourceCtx := sourceCtx.(type) {
case *antlrParser.SrcAccountContext:
return &SourceAccount{
Range: range_,
Color: parseColorConstraint(sourceCtx.ColorConstraint()),
ValueExpr: parseValueExpr(sourceCtx.ValueExpr()),
}
case *antlrParser.SrcInorderContext:
var sources []Source
for _, sourceCtx := range sourceCtx.AllSource() {
sources = append(sources, parseSource(sourceCtx))
}
return &SourceInorder{
Range: range_,
Sources: sources,
}
case *antlrParser.SrcOneofContext:
var sources []Source
for _, sourceCtx := range sourceCtx.AllSource() {
sources = append(sources, parseSource(sourceCtx))
}
return &SourceOneof{
Range: range_,
Sources: sources,
}
case *antlrParser.SrcAllotmentContext:
var items []SourceAllotmentItem
for _, itemCtx := range sourceCtx.AllAllotmentClauseSrc() {
item := SourceAllotmentItem{
Range: ctxToRange(itemCtx),
Allotment: parseAllotment(itemCtx.Allotment()),
From: parseSource(itemCtx.Source()),
}
items = append(items, item)
}
return &SourceAllotment{
Range: range_,
Items: items,
}
case *antlrParser.SrcCappedContext:
return &SourceCapped{
Range: range_,
From: parseSource(sourceCtx.Source()),
Cap: parseValueExpr(sourceCtx.GetCap_()),
}
case *antlrParser.SrcAccountUnboundedOverdraftContext:
return &SourceOverdraft{
Range: ctxToRange(sourceCtx),
Color: parseColorConstraint(sourceCtx.ColorConstraint()),
Address: parseValueExpr(sourceCtx.GetAddress()),
}
case *antlrParser.SrcAccountWithScalingContext:
return &SourceWithScaling{
Range: ctxToRange(sourceCtx),
Address: parseValueExpr(sourceCtx.GetAddress()),
Through: parseValueExpr(sourceCtx.GetSwap()),
}
case *antlrParser.SrcAccountBoundedOverdraftContext:
varMon := parseValueExpr(sourceCtx.GetMaxOvedraft())
return &SourceOverdraft{
Range: ctxToRange(sourceCtx),
Color: parseColorConstraint(sourceCtx.ColorConstraint()),
Address: parseValueExpr(sourceCtx.GetAddress()),
Bounded: &varMon,
}
case *antlrParser.SourceContext:
return nil
default:
return utils.NonExhaustiveMatchPanic[Source](sourceCtx.GetText())
}
}
// Returns (a, b) representing a/(10^b)
func ParsePercentageRatio(source string) (*big.Int, int, error) {
source = strings.TrimSuffix(source, "%")
scale := 0
if i := strings.Index(source, "."); i != -1 {
scale = len(source) - i - 1
}
intPart := strings.ReplaceAll(source, ".", "")
num, ok := new(big.Int).SetString(intPart, 10)
if !ok {
return nil, 0, fmt.Errorf("unexpected invalid string literal: %s", source)
}
return num, scale, nil
}
func parsePercentageRatio(source string, range_ Range) *PercentageLiteral {
num, floatingDigits, err := ParsePercentageRatio(source)
if err != nil {
// This should be unreachable for tokens emitted by the lexer.
// Degrade gracefully to a zero-value literal instead of panicking,
// so that a malformed token cannot crash the process (e.g. the LSP server).
return &PercentageLiteral{
Range: range_,
Amount: big.NewInt(0),
}
}
return &PercentageLiteral{
Range: range_,
Amount: num,
FloatingDigits: floatingDigits,
}
}
func parseAllotment(allotmentCtx antlrParser.IAllotmentContext) AllotmentValue {
switch allotmentCtx := allotmentCtx.(type) {
case *antlrParser.PortionedAllotmentContext:
expr := parseValueExpr(allotmentCtx.ValueExpr())
return &ValueExprAllotment{expr}
case *antlrParser.RemainingAllotmentContext:
return &RemainingAllotment{
Range: ctxToRange(allotmentCtx),
}
case *antlrParser.AllotmentContext:
return nil
default:
return utils.NonExhaustiveMatchPanic[AllotmentValue](allotmentCtx.GetText())
}
}
func parseStringTk(stringCtx antlr.Token) *StringLiteral {
rawStr := stringCtx.GetText()
// Remove leading and trailing '"'
innerStr := rawStr[1 : len(rawStr)-1]
return &StringLiteral{
Range: tokenToRange(stringCtx),
String: innerStr,
}
}
func parseValueExpr(valueExprCtx antlrParser.IValueExprContext) ValueExpr {
switch valueExprCtx := valueExprCtx.(type) {
case *antlrParser.ParenthesizedExprContext:
return parseValueExpr(valueExprCtx.ValueExpr())
case *antlrParser.AccountLiteralContext:
litRng := ctxToRange(valueExprCtx)
var parts []AccountNamePart
for _, accLit := range valueExprCtx.GetChildren() {
switch accLit := accLit.(type) {
case *antlrParser.AccountTextPartContext:
varPartText := accLit.GetText()
parts = append(parts, AccountTextPart{Name: varPartText})
case *antlrParser.AccountVarPartContext:
v := parseVarLiteral(accLit.VARIABLE_NAME_ACC().GetSymbol())
parts = append(parts, v)
case antlr.TerminalNode:
if accLit.GetSymbol().GetTokenType() == antlrParser.NumscriptParserCOLON {
parts = append(parts, AccountTextPart{Name: ":"})
}
}
}
return &AccountInterpLiteral{
Range: litRng,
Parts: parts,
}
case *antlrParser.MonetaryLiteralContext:
return parseMonetaryLit(valueExprCtx.MonetaryLit())
case *antlrParser.AssetLiteralContext:
return &AssetLiteral{
Range: ctxToRange(valueExprCtx),
Asset: valueExprCtx.GetText(),
}
case *antlrParser.NumberLiteralContext:
return parseNumberLiteral(valueExprCtx.NUMBER())
case *antlrParser.PercentagePortionLiteralContext:
return parsePercentageRatio(valueExprCtx.GetText(), ctxToRange(valueExprCtx))
case *antlrParser.VariableExprContext:
return variableLiteralFromCtx(valueExprCtx)
case *antlrParser.StringLiteralContext:
return parseStringTk(valueExprCtx.STRING().GetSymbol())
case *antlrParser.PrefixExprContext:
return &Prefix{
Range: ctxToRange(valueExprCtx),
Operator: PrefixOperator(valueExprCtx.GetOp().GetText()),
Expr: parseValueExpr(valueExprCtx.ValueExpr()),
}
case *antlrParser.InfixExprContext:
return &BinaryInfix{
Range: ctxToRange(valueExprCtx),
Operator: InfixOperator(valueExprCtx.GetOp().GetText()),
Left: parseValueExpr(valueExprCtx.GetLeft()),
Right: parseValueExpr(valueExprCtx.GetRight()),
}
case nil, *antlrParser.ValueExprContext:
return nil
case *antlrParser.ApplicationContext:
return parseFnCall(valueExprCtx.FunctionCall())
default:
return utils.NonExhaustiveMatchPanic[ValueExpr](valueExprCtx.GetText())
}
}
func variableLiteralFromCtx(ctx antlr.ParserRuleContext) *Variable {
// Discard the '$'
name := ctx.GetText()[1:]
return &Variable{
Range: ctxToRange(ctx),
Name: name,
}
}
func parseDestination(destCtx antlrParser.IDestinationContext) Destination {
if destCtx == nil {
return nil
}
range_ := ctxToRange(destCtx)
switch destCtx := destCtx.(type) {
case *antlrParser.DestAccountContext:
return &DestinationAccount{
ValueExpr: parseValueExpr(destCtx.ValueExpr()),
}
case *antlrParser.DestInorderContext:
var inorderClauses []CappedKeptOrDestination
for _, destInorderClause := range destCtx.AllDestinationInOrderClause() {
inorderClauses = append(inorderClauses, parseDestinationInorderClause(destInorderClause))
}
return &DestinationInorder{
Range: range_,
Clauses: inorderClauses,
Remaining: parseKeptOrDestination(destCtx.KeptOrDestination()),
}
case *antlrParser.DestOneofContext:
var inorderClauses []CappedKeptOrDestination
for _, destInorderClause := range destCtx.AllDestinationInOrderClause() {
inorderClauses = append(inorderClauses, parseDestinationInorderClause(destInorderClause))
}
return &DestinationOneof{
Range: range_,
Clauses: inorderClauses,
Remaining: parseKeptOrDestination(destCtx.KeptOrDestination()),
}
case *antlrParser.DestAllotmentContext:
var items []DestinationAllotmentItem
for _, itemCtx := range destCtx.AllAllotmentClauseDest() {
item := DestinationAllotmentItem{
Range: ctxToRange(itemCtx),
Allotment: parseDestinationAllotment(itemCtx.Allotment()),
To: parseKeptOrDestination(itemCtx.KeptOrDestination()),
}
items = append(items, item)
}
return &DestinationAllotment{
Range: range_,
Items: items,
}
case *antlrParser.DestinationContext:
return nil
default:
return utils.NonExhaustiveMatchPanic[Destination](destCtx.GetText())
}
}
func parseDestinationInorderClause(clauseCtx antlrParser.IDestinationInOrderClauseContext) CappedKeptOrDestination {
return CappedKeptOrDestination{
Range: ctxToRange(clauseCtx),
Cap: parseValueExpr(clauseCtx.ValueExpr()),
To: parseKeptOrDestination(clauseCtx.KeptOrDestination()),
}
}
func parseKeptOrDestination(clauseCtx antlrParser.IKeptOrDestinationContext) KeptOrDestination {
if clauseCtx == nil {
return nil
}
switch clauseCtx := clauseCtx.(type) {
case *antlrParser.DestinationToContext:
return &DestinationTo{
Destination: parseDestination(clauseCtx.Destination()),
}
case *antlrParser.DestinationKeptContext:
return &DestinationKept{
Range: ctxToRange(clauseCtx),
}
case *antlrParser.KeptOrDestinationContext:
return nil
default:
return utils.NonExhaustiveMatchPanic[KeptOrDestination](clauseCtx.GetText())
}
}
func parseDestinationAllotment(allotmentCtx antlrParser.IAllotmentContext) AllotmentValue {
switch allotmentCtx := allotmentCtx.(type) {
case *antlrParser.RemainingAllotmentContext:
return &RemainingAllotment{
Range: ctxToRange(allotmentCtx),
}
case *antlrParser.PortionedAllotmentContext:
expr := parseValueExpr(allotmentCtx.ValueExpr())
return &ValueExprAllotment{Value: expr}
case *antlrParser.AllotmentContext:
return nil
default:
return utils.NonExhaustiveMatchPanic[AllotmentValue](allotmentCtx.GetText())
}
}
func parseFnArgs(fnCallArgCtx antlrParser.IFunctionCallArgsContext) []ValueExpr {
if fnCallArgCtx == nil {
return nil
}
var args []ValueExpr
for _, valueExpr := range fnCallArgCtx.AllValueExpr() {
args = append(args, parseValueExpr(valueExpr))
}
return args
}
func parseFnCall(fnCallCtx antlrParser.IFunctionCallContext) *FnCall {
if fnCallCtx == nil {
return nil
}
ident := fnCallCtx.GetFnName()
if ident == nil {
return nil
}
allArgs := fnCallCtx.FunctionCallArgs()
return &FnCall{
Range: ctxToRange(fnCallCtx),
Caller: &FnCallIdentifier{
Range: tokenToRange(ident),
Name: ident.GetText(),
},
Args: parseFnArgs(allArgs),
}
}
func parseSaveStatement(saveCtx *antlrParser.SaveStatementContext) *SaveStatement {
return &SaveStatement{
Range: ctxToRange(saveCtx),
SentValue: parseSentValue(saveCtx.SentValue()),
Account: parseValueExpr(saveCtx.ValueExpr()),
}
}
func parseStatement(statementCtx antlrParser.IStatementContext) Statement {
switch statementCtx := statementCtx.(type) {
case *antlrParser.SendStatementContext:
return parseSendStatement(statementCtx)
case *antlrParser.SaveStatementContext:
return parseSaveStatement(statementCtx)
case *antlrParser.FnCallStatementContext:
return parseFnCall(statementCtx.FunctionCall())
case *antlrParser.StatementContext:
return nil
default:
return utils.NonExhaustiveMatchPanic[Statement](statementCtx.GetText())
}
}
func parseSentValue(statementCtx antlrParser.ISentValueContext) SentValue {
switch statementCtx := statementCtx.(type) {
case *antlrParser.SentLiteralContext:
return &SentValueLiteral{
Range: ctxToRange(statementCtx),
Monetary: parseValueExpr(statementCtx.ValueExpr()),
}
case *antlrParser.SentAllContext:
return &SentValueAll{
Range: ctxToRange(statementCtx),
Asset: parseValueExpr(statementCtx.SentAllLit().GetAsset()),
}
case *antlrParser.SentValueContext:
return nil
default:
return utils.NonExhaustiveMatchPanic[SentValue](statementCtx.GetText())
}
}
func parseSendStatement(statementCtx *antlrParser.SendStatementContext) *SendStatement {
return &SendStatement{
Source: parseSource(statementCtx.Source()),
Destination: parseDestination(statementCtx.Destination()),
Range: ctxToRange(statementCtx),
SentValue: parseSentValue(statementCtx.SentValue()),
}
}
func parseNumberLiteral(numNode antlr.TerminalNode) *NumberLiteral {
amtStr := numNode.GetText()
amtStr = strings.ReplaceAll(amtStr, "_", "")
amt, ok := new(big.Int).SetString(amtStr, 10)
if !ok {
// This should be unreachable for tokens emitted by the lexer.
// Degrade gracefully to a zero-value literal instead of panicking,
// so that a malformed token cannot crash the process (e.g. the LSP server).
amt = big.NewInt(0)
}
return &NumberLiteral{
Range: tokenToRange(numNode.GetSymbol()),
Number: amt,
}
}
func parseMonetaryLit(monetaryLitCtx antlrParser.IMonetaryLitContext) *MonetaryLiteral {
if monetaryLitCtx.GetAmt() == nil {
return nil
}
return &MonetaryLiteral{
Range: ctxToRange(monetaryLitCtx),
Asset: parseValueExpr(monetaryLitCtx.GetAsset()),
Amount: parseValueExpr(monetaryLitCtx.GetAmt()),
}
}
func ctxToRange(ctx antlr.ParserRuleContext) Range {
startTk := ctx.GetStart()
endTk := ctx.GetStop()
return Range{
Start: Position{
Line: startTk.GetLine() - 1,
Character: startTk.GetColumn(),
},
End: Position{
Line: endTk.GetLine() - 1,
// this is based on the assumption that a token cannot span multiple lines
Character: endTk.GetColumn() + len(endTk.GetText()),
},
}
}
func tokenToRange(tk antlr.Token) Range {
return Range{
Start: Position{
Line: tk.GetLine() - 1,
Character: tk.GetColumn(),
},
End: Position{
Line: tk.GetLine() - 1,
Character: tk.GetColumn() + len(tk.GetText()),
},
}
}