feat: add config_parser

This commit is contained in:
mzz2017
2023-01-27 02:10:27 +08:00
parent 916a55d480
commit edbce81e88
53 changed files with 6696 additions and 733 deletions

View File

@ -0,0 +1,34 @@
/*
* SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, mzz2017 (mzz@tuta.io). All rights reserved.
*/
package config_parser
import (
"fmt"
"github.com/antlr/antlr4/runtime/Go/antlr/v4"
"github.com/v2rayA/dae-config-dist/go/dae_config"
)
func Parse(in string) (sections []*Section, err error) {
errorListener := NewConsoleErrorListener()
lexer := dae_config.Newdae_configLexer(antlr.NewInputStream(in))
lexer.RemoveErrorListeners()
lexer.AddErrorListener(errorListener)
input := antlr.NewCommonTokenStream(lexer, 0)
parser := dae_config.Newdae_configParser(input)
parser.RemoveErrorListeners()
parser.AddErrorListener(errorListener)
parser.BuildParseTrees = true
tree := parser.Start()
walker := NewWalker(parser)
antlr.ParseTreeWalkerDefault.Walk(walker, tree)
if errorListener.ErrorBuilder.Len() != 0 {
return nil, fmt.Errorf("%v", errorListener.ErrorBuilder.String())
}
return walker.Sections, nil
}

View File

@ -0,0 +1,91 @@
/*
* SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, mzz2017 (mzz@tuta.io). All rights reserved.
*/
package config_parser
import "testing"
func TestParse(t *testing.T) {
sections, err := Parse(`
# gugu
include {
another.conf
}
global {
# tproxy port to listen.
tproxy_port: 12345
# Node connectivity check url.
check_url: 'https://connectivitycheck.gstatic.com/generate_204'
# Now only support UDP and IP:Port.
# Please make sure DNS traffic will go through and be forwarded by dae.
dns_upstream: '1.1.1.1:53'
# Now only support one interface.
ingress_interface: docker0
}
# subscription will be resolved as nodes and merged into node pool below.
subscription {
https://LINK
}
node {
'ss://LINK'
'ssr://LINK'
'vmess://LINK'
'vless://LINK'
'trojan://LINK'
'trojan-go://LINK'
'socks5://LINK#name'
'http://LINK#name'
'https://LINK#name'
}
group {
my_group {
# Pass node links as input of lua script filter.
# gugu
filter: link(lua:filename.lua)
# Randomly select a node from the group for every connection.
policy: random
}
disney {
# Pass node names as input of keyword/regex filter.
filter: name(regex:'^.*hk.*$', keyword:'sg') && name(keyword:'disney')
# Select the node with min average of the last 10 latencies from the group for every connection.
policy: min_avg10
}
netflix {
# Pass node names as input of keyword filter.
filter: name(keyword:netflix)
# Select the first node from the group for every connection.
policy: fixed(0)
}
}
routing {
domain(geosite:category-ads) -> block
domain(geosite:disney) -> disney
domain(geosite:netflix) -> netflix
ip(geoip:cn) -> direct
domain(geosite:cn) -> direct
final: my_group
}
`)
if err != nil {
t.Fatalf("\n%v", err)
}
for _, section := range sections {
t.Logf("\n%v", section.String())
}
}

View File

@ -0,0 +1,91 @@
/*
* SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2022, mzz2017 (mzz@tuta.io). All rights reserved.
*/
package config_parser
import (
"fmt"
"github.com/antlr/antlr4/runtime/Go/antlr/v4"
"reflect"
"strings"
)
type ErrorType string
const (
ErrorType_Unsupported ErrorType = "is not supported"
ErrorType_NotSet ErrorType = "is not set"
)
type ConsoleErrorListener struct {
ErrorBuilder strings.Builder
}
func NewConsoleErrorListener() *ConsoleErrorListener {
return &ConsoleErrorListener{}
}
func (d *ConsoleErrorListener) SyntaxError(recognizer antlr.Recognizer, offendingSymbol interface{}, line, column int, msg string, e antlr.RecognitionException) {
// Do not accumulate errors.
if d.ErrorBuilder.Len() > 0 {
return
}
backtrack := column
if backtrack > 30 {
backtrack = 30
}
starting := fmt.Sprintf("line %v:%v ", line, column)
offset := len(starting) + backtrack
var (
simplyWrite bool
token antlr.Token
)
if offendingSymbol == nil {
simplyWrite = true
} else {
token = offendingSymbol.(antlr.Token)
simplyWrite = token.GetTokenType() == -1
}
if simplyWrite {
d.ErrorBuilder.WriteString(fmt.Sprintf("%v%v", starting, msg))
return
}
beginOfLine := token.GetStart() - backtrack
strPeek := token.GetInputStream().GetText(beginOfLine, token.GetStop()+30)
wrap := strings.IndexByte(strPeek, '\n')
if wrap == -1 {
wrap = token.GetStop() + 30
} else {
wrap += beginOfLine - 1
}
strLine := token.GetInputStream().GetText(beginOfLine, wrap)
d.ErrorBuilder.WriteString(fmt.Sprintf("%v%v\n%v%v: %v\n", starting, strLine, strings.Repeat(" ", offset), strings.Repeat("^", token.GetStop()-token.GetStart()+1), msg))
}
func (d *ConsoleErrorListener) ReportAmbiguity(recognizer antlr.Parser, dfa *antlr.DFA, startIndex, stopIndex int, exact bool, ambigAlts *antlr.BitSet, configs antlr.ATNConfigSet) {
}
func (d *ConsoleErrorListener) ReportAttemptingFullContext(recognizer antlr.Parser, dfa *antlr.DFA, startIndex, stopIndex int, conflictingAlts *antlr.BitSet, configs antlr.ATNConfigSet) {
}
func (d *ConsoleErrorListener) ReportContextSensitivity(recognizer antlr.Parser, dfa *antlr.DFA, startIndex, stopIndex, prediction int, configs antlr.ATNConfigSet) {
}
func BaseContext(ctx interface{}) (baseCtx *antlr.BaseParserRuleContext) {
val := reflect.ValueOf(ctx)
for val.Kind() == reflect.Pointer && val.Type() != reflect.TypeOf(&antlr.BaseParserRuleContext{}) {
val = val.Elem()
}
if val.Type() == reflect.TypeOf(&antlr.BaseParserRuleContext{}) {
baseCtx = val.Interface().(*antlr.BaseParserRuleContext)
} else {
baseCtxVal := val.FieldByName("BaseParserRuleContext")
if !baseCtxVal.IsValid() {
panic("has no field BaseParserRuleContext")
}
baseCtx = baseCtxVal.Interface().(*antlr.BaseParserRuleContext)
}
return baseCtx
}

View File

@ -0,0 +1,187 @@
/*
* SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, mzz2017 (mzz@tuta.io). All rights reserved.
*/
package config_parser
import (
"fmt"
"strconv"
"strings"
)
type ItemType int
const (
ItemType_RoutingRule ItemType = iota
ItemType_Param
ItemType_Section
)
func (t ItemType) String() string {
switch t {
case ItemType_RoutingRule:
return "RoutingRule"
case ItemType_Param:
return "Param"
case ItemType_Section:
return "Section"
default:
return "<Unknown>"
}
}
func NewRoutingRuleItem(rule *RoutingRule) *Item {
return &Item{
Type: ItemType_RoutingRule,
Value: rule,
}
}
func NewParamItem(param *Param) *Item {
return &Item{
Type: ItemType_Param,
Value: param,
}
}
func NewSectionItem(section *Section) *Item {
return &Item{
Type: ItemType_Param,
Value: section,
}
}
type Item struct {
Type ItemType
Value interface{}
}
func (i *Item) String() string {
var builder strings.Builder
builder.WriteString("type: " + i.Type.String() + "\n")
var content string
switch val := i.Value.(type) {
case *RoutingRule:
content = val.String(false)
case *Param:
content = val.String()
case *Section:
content = val.String()
default:
return "<Unknown>\n"
}
lines := strings.Split(content, "\n")
for i := range lines {
lines[i] = "\t" + lines[i]
}
builder.WriteString(strings.Join(lines, "\n"))
return builder.String()
}
type Section struct {
Name string
Items []*Item
}
func (s *Section) String() string {
var builder strings.Builder
builder.WriteString("section: " + s.Name + "\n")
var strItemList []string
for _, item := range s.Items {
lines := strings.Split(item.String(), "\n")
for i := range lines {
lines[i] = "\t" + lines[i]
}
strItemList = append(strItemList, strings.Join(lines, "\n"))
}
builder.WriteString(strings.Join(strItemList, "\n"))
return builder.String()
}
type Param struct {
Key string
Val string
AndFunctions []*Function
}
func (p *Param) String() string {
if p.Key == "" {
return p.Val
}
if p.AndFunctions != nil {
a := paramAndFunctions{
Key: p.Key,
AndFunctions: p.AndFunctions,
}
return a.String()
}
return p.Key + ": " + p.Val
}
type Function struct {
Name string
Params []*Param
}
func (f *Function) String() string {
var builder strings.Builder
builder.WriteString(f.Name + "(")
var strParamList []string
for _, p := range f.Params {
strParamList = append(strParamList, p.String())
}
builder.WriteString(strings.Join(strParamList, ", "))
builder.WriteString(")")
return builder.String()
}
type paramAndFunctions struct {
Key string
AndFunctions []*Function
}
func (p *paramAndFunctions) String() string {
var builder strings.Builder
builder.WriteString(p.Key + ": ")
var strFunctionList []string
for _, f := range p.AndFunctions {
strFunctionList = append(strFunctionList, f.String())
}
builder.WriteString(strings.Join(strFunctionList, " && "))
return builder.String()
}
type RoutingRule struct {
AndFunctions []*Function
Outbound string
}
func (r *RoutingRule) String(calcN bool) string {
var builder strings.Builder
var n int
for _, f := range r.AndFunctions {
if builder.Len() != 0 {
builder.WriteString(" && ")
}
var paramBuilder strings.Builder
n += len(f.Params)
for _, p := range f.Params {
if paramBuilder.Len() != 0 {
paramBuilder.WriteString(", ")
}
if p.Key != "" {
paramBuilder.WriteString(p.Key + ": " + p.Val)
} else {
paramBuilder.WriteString(p.Val)
}
}
builder.WriteString(fmt.Sprintf("%v(%v)", f.Name, paramBuilder.String()))
}
builder.WriteString(" -> " + r.Outbound)
if calcN {
builder.WriteString(" [n = " + strconv.Itoa(n) + "]")
}
return builder.String()
}

239
pkg/config_parser/walker.go Normal file
View File

@ -0,0 +1,239 @@
/*
* SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2022, mzz2017 (mzz@tuta.io). All rights reserved.
*/
package config_parser
import (
"fmt"
"github.com/antlr/antlr4/runtime/Go/antlr/v4"
"github.com/v2rayA/dae-config-dist/go/dae_config"
"log"
"strconv"
)
type Walker struct {
*dae_config.Basedae_configListener
parser antlr.Parser
Sections []*Section
}
func NewWalker(parser antlr.Parser) *Walker {
return &Walker{
parser: parser,
}
}
type paramParser struct {
list []*Param
}
func getValueFromLiteral(literal *dae_config.LiteralContext) string {
quote := literal.Quote_literal()
if quote == nil {
return literal.GetText()
}
text := quote.GetText()
return text[1 : len(text)-1]
}
func (p *paramParser) parseParam(ctx *dae_config.ParameterContext) *Param {
children := ctx.GetChildren()
if len(children) == 3 {
return &Param{
Key: children[0].(*antlr.TerminalNodeImpl).GetText(),
Val: getValueFromLiteral(children[2].(*dae_config.LiteralContext)),
}
} else if len(children) == 1 {
return &Param{
Key: "",
Val: getValueFromLiteral(children[0].(*dae_config.LiteralContext)),
}
}
panic("unexpected")
}
func (p *paramParser) parseNonEmptyParamList(ctx *dae_config.NonEmptyParameterListContext) {
children := ctx.GetChildren()
if len(children) == 3 {
p.list = append(p.list, p.parseParam(children[2].(*dae_config.ParameterContext)))
p.parseNonEmptyParamList(children[0].(*dae_config.NonEmptyParameterListContext))
} else if len(children) == 1 {
p.list = append(p.list, p.parseParam(children[0].(*dae_config.ParameterContext)))
}
}
func (w *Walker) parseNonEmptyParamList(list *dae_config.NonEmptyParameterListContext) []*Param {
paramParser := new(paramParser)
paramParser.parseNonEmptyParamList(list)
return paramParser.list
}
func (w *Walker) reportKeyUnsupportedError(ctx interface{}, keyName, funcName string) {
w.ReportError(ctx, ErrorType_Unsupported, fmt.Sprintf("key %v in %v()", strconv.Quote(keyName), funcName))
}
type functionVerifier func(function *Function, ctx interface{}) bool
func (w *Walker) parseFunctionPrototype(ctx *dae_config.FunctionPrototypeContext, verifier functionVerifier) *Function {
children := ctx.GetChildren()
funcName := children[0].(*antlr.TerminalNodeImpl).GetText()
paramList := children[2].(*dae_config.OptParameterListContext)
children = paramList.GetChildren()
if len(children) == 0 {
w.ReportError(ctx, ErrorType_Unsupported, "empty parameter list")
return nil
}
nonEmptyParamList := children[0].(*dae_config.NonEmptyParameterListContext)
params := w.parseNonEmptyParamList(nonEmptyParamList)
f := &Function{
Name: funcName,
Params: params,
}
// Verify function name and param keys.
if verifier != nil && !verifier(f, ctx) {
return nil
}
return f
}
func (w *Walker) ReportError(ctx interface{}, errorType ErrorType, target ...string) {
//debug.PrintStack()
bCtx := BaseContext(ctx)
tgt := strconv.Quote(bCtx.GetStart().GetText())
if len(target) != 0 {
tgt = target[0]
}
if errorType == ErrorType_NotSet {
w.parser.NotifyErrorListeners(fmt.Sprintf("%v %v.", tgt, errorType), nil, nil)
return
}
w.parser.NotifyErrorListeners(fmt.Sprintf("%v %v.", tgt, errorType), bCtx.GetStart(), nil)
}
func (w *Walker) parseDeclaration(ctx dae_config.IDeclarationContext) *Param {
children := ctx.GetChildren()
key := children[0].(*antlr.TerminalNodeImpl).GetText()
switch valueCtx := children[2].(type) {
case *dae_config.LiteralContext:
value := getValueFromLiteral(valueCtx)
return &Param{
Key: key,
Val: value,
}
case *dae_config.FunctionPrototypeExpressionContext:
andFunctions := w.parseFunctionPrototypeExpression(valueCtx, nil)
return &Param{
Key: key,
AndFunctions: andFunctions,
}
default:
w.ReportError(valueCtx, ErrorType_Unsupported)
return nil
}
}
func (w *Walker) parseFunctionPrototypeExpression(ctx dae_config.IFunctionPrototypeExpressionContext, verifier functionVerifier) (andFunctions []*Function) {
children := ctx.GetChildren()
for _, child := range children {
// And rules.
if child, ok := child.(*dae_config.FunctionPrototypeContext); ok {
function := w.parseFunctionPrototype(child, verifier)
andFunctions = append(andFunctions, function)
}
}
return andFunctions
}
func (w *Walker) parseRoutingRule(ctx dae_config.IRoutingRuleContext) *RoutingRule {
children := ctx.GetChildren()
//logrus.Debugln(ctx.GetText(), children)
left, ok := children[0].(*dae_config.RoutingRuleLeftContext)
if !ok {
w.ReportError(ctx, ErrorType_Unsupported, "not *RoutingRuleLeftContext: "+ctx.GetText())
return nil
}
outbound := children[2].(*dae_config.Bare_literalContext).GetText()
// Parse functions.
children = left.GetChildren()
functionList, ok := children[1].(*dae_config.FunctionPrototypeExpressionContext)
if !ok {
w.ReportError(ctx, ErrorType_Unsupported, "not *FunctionPrototypeExpressionContext: "+ctx.GetText())
return nil
}
andFunctions := w.parseFunctionPrototypeExpression(functionList, nil)
return &RoutingRule{
AndFunctions: andFunctions,
Outbound: outbound,
}
}
type routingRuleOrDeclarationOrLiteralOrExpressionListParser struct {
Items []*Item
Walker *Walker
}
func (p *routingRuleOrDeclarationOrLiteralOrExpressionListParser) Parse(ctx dae_config.IRoutingRuleOrDeclarationOrLiteralOrExpressionListContext) {
for _, elem := range ctx.GetChildren() {
switch elem := elem.(type) {
case dae_config.IRoutingRuleContext:
rule := p.Walker.parseRoutingRule(elem)
if rule == nil {
return
}
p.Items = append(p.Items, NewRoutingRuleItem(rule))
case dae_config.IDeclarationContext:
param := p.Walker.parseDeclaration(elem)
if param == nil {
return
}
p.Items = append(p.Items, NewParamItem(param))
case *dae_config.LiteralContext:
p.Items = append(p.Items, NewParamItem(&Param{
Key: "",
Val: getValueFromLiteral(elem),
}))
case dae_config.IExpressionContext:
section := p.Walker.parseExpression(elem)
if section == nil {
return
}
p.Items = append(p.Items, NewSectionItem(section))
case dae_config.IRoutingRuleOrDeclarationOrLiteralOrExpressionListContext:
p.Parse(elem)
default:
log.Printf("? %v", elem.(*dae_config.ExpressionContext))
p.Walker.ReportError(elem, ErrorType_Unsupported)
return
}
}
}
func (w *Walker) parseRoutingRuleOrDeclarationOrLiteralOrExpressionListContext(ctx dae_config.IRoutingRuleOrDeclarationOrLiteralOrExpressionListContext) []*Item {
parser := routingRuleOrDeclarationOrLiteralOrExpressionListParser{
Items: nil,
Walker: w,
}
parser.Parse(ctx)
return parser.Items
}
func (w *Walker) parseExpression(exp dae_config.IExpressionContext) *Section {
children := exp.GetChildren()
name := children[0].(*antlr.TerminalNodeImpl).GetText()
list := children[2].(dae_config.IRoutingRuleOrDeclarationOrLiteralOrExpressionListContext)
items := w.parseRoutingRuleOrDeclarationOrLiteralOrExpressionListContext(list)
return &Section{
Name: name,
Items: items,
}
}
func (w *Walker) EnterProgramStructureBlcok(ctx *dae_config.ProgramStructureBlcokContext) {
section := w.parseExpression(ctx.Expression())
if section == nil {
return
}
w.Sections = append(w.Sections, section)
}