Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 47 additions & 12 deletions sourcecode-parser/construct.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,16 @@ import (
)

type GraphNode struct {
ID string
Type string
Name string
CodeSnippet string
LineNumber uint32
OutgoingEdges []*GraphEdge
IsExternal bool
ID string
Type string
Name string
CodeSnippet string
LineNumber uint32
OutgoingEdges []*GraphEdge
IsExternal bool
Modifier string
ReturnType string
MethodArguments []string
}

type GraphEdge struct {
Expand Down Expand Up @@ -65,6 +68,17 @@ func (g *CodeGraph) FindNodesByType(nodeType string) []*GraphNode {
return nodes
}

func extractVisibilityModifier(modifiers string) string {
words := strings.Fields(modifiers)
for _, word := range words {
switch word {
case "public", "private", "protected":
return word
}
}
return "" // return an empty string if no visibility modifier is found
}

func buildGraphFromAST(node *sitter.Node, sourceCode []byte, graph *CodeGraph, currentContext *GraphNode) {

packageName, className := extractPackageAndClassName(node, sourceCode)
Expand All @@ -73,13 +87,34 @@ func buildGraphFromAST(node *sitter.Node, sourceCode []byte, graph *CodeGraph, c
case "method_declaration":
methodName, methodId := extractMethodName(node, sourceCode, packageName, className)
invokedNode, exists := graph.Nodes[methodId]
modifiers := ""
ReturnType := ""
MethodArguments := []string{}
for i := 0; i < int(node.ChildCount()); i++ {
if node.Child(i).Type() == "modifiers" {
modifiers = node.Child(i).Content(sourceCode)
} else if node.Child(i).Type() == "void_type" || node.Child(i).Type() == "type_identifier" {
// get return type of method
ReturnType = node.Child(i).Content(sourceCode)
} else if node.Child(i).Type() == "formal_parameters" {
// get method arguments
for j := 0; j < int(node.Child(i).NamedChildCount()); j++ {
param := node.Child(i).NamedChild(j)
MethodArguments = append(MethodArguments, param.Content(sourceCode))
}
}
}

if !exists || (exists && invokedNode.ID != methodId) {
invokedNode = &GraphNode{
ID: methodId, // In a real scenario, you would construct a unique ID, possibly using the method signature
Type: "method_declaration",
Name: methodName,
CodeSnippet: string(node.Content(sourceCode)),
LineNumber: node.StartPoint().Row + 1, // Lines start from 0 in the AST
ID: methodId, // In a real scenario, you would construct a unique ID, possibly using the method signature
Type: "method_declaration",
Name: methodName,
CodeSnippet: string(node.Content(sourceCode)),
LineNumber: node.StartPoint().Row + 1, // Lines start from 0 in the AST
Modifier: extractVisibilityModifier(modifiers),
ReturnType: ReturnType,
MethodArguments: MethodArguments,
// CodeSnippet and LineNumber are skipped as per the requirement
}
}
Expand Down
2 changes: 1 addition & 1 deletion sourcecode-parser/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func main() {
if strings.HasPrefix(input, ":quit") {
return
}
fmt.Print(input)
fmt.Print("Executing query: " + input)
lex := queryparser.NewLexer(input)
pars := queryparser.NewParser(lex)
query := pars.ParseQuery()
Expand Down
18 changes: 13 additions & 5 deletions sourcecode-parser/queryparser/lexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,22 @@ func (l *Lexer) NextToken() Token {
case '\'':
tok.Type = STRING
tok.Literal = l.readString()
case 0:
tok.Literal = ""
case '(':
tok = Token{Type: LPAREN, Literal: string(l.ch)}
case ')':
tok = Token{Type: RPAREN, Literal: string(l.ch)}
case 0: // End of file or input
tok.Type = EOF
tok.Literal = ""
default:
if isLetter(l.ch) {
tok.Literal = l.readIdentifier()
tok.Type = LookupIdent(tok.Literal)
return tok
} else if l.ch == '"' {
tok.Type = STRING
tok.Literal = l.readString()
return tok
} else {
tok = Token{Type: ILLEGAL, Literal: string(l.ch)}
}
Expand All @@ -57,14 +65,14 @@ func (l *Lexer) readIdentifier() string {
}

func (l *Lexer) readString() string {
position := l.position + 1
startPosition := l.position + 1 // skip the initial quote
for {
l.readChar()
if l.ch == '\'' || l.ch == 0 {
if l.ch == '"' || l.ch == '\'' || l.ch == 0 {
break
}
}
return l.input[position:l.position]
return l.input[startPosition:l.position]
}

func (l *Lexer) skipWhitespace() {
Expand Down
147 changes: 113 additions & 34 deletions sourcecode-parser/queryparser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,94 @@ func (p *Parser) nextToken() {
p.curToken = p.peekToken
}

func (p *Parser) parseExpression() Expr {
expr := p.parseLogicalOr() // Start with the lowest precedence
return expr
}

func (p *Parser) parseLogicalOr() Expr {
expr := p.parseLogicalAnd()
for p.curToken.Type == KEYWORD && p.peekToken.Literal == "OR" {
p.nextToken()
right := p.parseLogicalAnd()
expr = &BinaryExpr{
Left: expr,
Op: "OR",
Right: right,
}
}
return expr
}

func (p *Parser) parseLogicalAnd() Expr {
expr := p.parseGroup()
for p.curToken.Type == KEYWORD && p.peekToken.Literal == "AND" {
p.nextToken()
right := p.parseGroup()
expr = &BinaryExpr{
Left: expr,
Op: "AND",
Right: right,
}
}
return expr
}

func (p *Parser) parseGroup() Expr {
if p.curToken.Type == LPAREN {
p.nextToken() // Skip '('
expr := p.parseExpression() // Parse expression within parentheses
if p.curToken.Type != RPAREN {
p.peekError(p.curToken.Type)
return nil
}
p.nextToken() // Skip ')'
return expr
}
return p.parseCondition() // Parse a basic condition
}

func (p *Parser) parseCondition() *Condition {
if p.curToken.Type != IDENT {
p.peekError(IDENT)
return nil
}

field := p.curToken.Literal
p.nextToken()

if p.curToken.Type != OPERATOR {
p.peekError(OPERATOR)
return nil
}

operator := p.curToken.Literal
p.nextToken()

if p.curToken.Type != STRING && p.curToken.Type != IDENT {
p.peekError(STRING)
return nil
}
value := p.curToken.Literal
p.nextToken() // move past the value

return &Condition{Field: field, Operator: operator, Value: value}
}

type EvalContext interface {
GetValue(key string) string // Retrieves a value based on a key, which helps in condition evaluation.
}

type Expr interface {
Evaluate(ctx EvalContext) bool
}

type BinaryExpr struct {
Left Expr
Right Expr
Op string
}

func (p *Parser) ParseQuery() *Query {
// fmt.Printf("Current token: %s\n", p.curToken.Literal) // Debug output

Expand All @@ -42,7 +130,6 @@ func (p *Parser) ParseQuery() *Query {
}

query.Operation = p.curToken.Literal

p.nextToken()

if p.curToken.Type != IDENT {
Expand All @@ -59,41 +146,33 @@ func (p *Parser) ParseQuery() *Query {
}

p.nextToken()
expr := p.parseExpression()
if expr == nil {
// handle error or invalid expression
return nil
}
query.Conditions = expr

// Process conditions
for p.curToken.Type != EOF {
if p.curToken.Type != IDENT {
p.peekError(IDENT)
return nil
}

field := p.curToken.Literal
p.nextToken()

if p.curToken.Type != OPERATOR {
p.peekError(OPERATOR)
return nil
}

operator := p.curToken.Literal
p.nextToken()

if p.curToken.Type != STRING {
p.peekError(STRING)
return nil
}

value := p.curToken.Literal
cond := Condition{Field: field, Operator: operator, Value: value}
query.Conditions = append(query.Conditions, cond)

p.nextToken() // move to the next part of the condition or EOF
return query
}

// Handle AND/OR for multiple conditions
if p.curToken.Type == KEYWORD && (p.curToken.Literal == "AND" || p.curToken.Literal == "OR") {
p.nextToken() // Continue to next condition
}
func (b *BinaryExpr) Evaluate(ctx EvalContext) bool {
switch b.Op {
case "AND":
return b.Left.Evaluate(ctx) && b.Right.Evaluate(ctx)
case "OR":
return b.Left.Evaluate(ctx) || b.Right.Evaluate(ctx)
}
return false
}

return query
func (c *Condition) Evaluate(ctx EvalContext) bool {
fieldValue := ctx.GetValue(c.Field)
switch c.Operator {
case "=":
return fieldValue == c.Value
case "!=":
return fieldValue != c.Value
}
return false
}
4 changes: 3 additions & 1 deletion sourcecode-parser/queryparser/tokenizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ const (
STRING // String literals
KEYWORD // Keywords such as FIND, WHERE
OPERATOR // Operators such as =, INCLUDES, MATCHES
LPAREN // Left parenthesis '('
RPAREN
)

type Token struct {
Expand All @@ -28,7 +30,7 @@ type Node interface {
type Query struct {
Operation string
Entity string
Conditions []Condition
Conditions Expr
}

type Condition struct {
Expand Down
36 changes: 32 additions & 4 deletions sourcecode-parser/source_sink.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ type SourceSinkPath struct {
Sink *GraphNode
}

var MethodAttribute = map[string]int{
"name": 0,
"visibility": 1,
"returntype": 2,
// Add more attributes as needed
}

type Result struct {
IsConnected bool `json:"isConnected"`
SourceMethod string `json:"sourceMethod"`
Expand Down Expand Up @@ -70,12 +77,33 @@ func AnalyzeSourceSinkPatterns(graph *CodeGraph, sourceMethodName, sinkMethodNam
return Result{IsConnected: isConnected, SourceMethod: sourceNode.CodeSnippet, SinkMethod: sinkNode.CodeSnippet, SourceLine: sourceNode.LineNumber, SinkLine: sinkNode.LineNumber}
}

type GraphNodeContext struct {
Node *GraphNode
}

// GetValue returns the value of a field in a GraphNode based on the key.
func (gnc *GraphNodeContext) GetValue(key string) string {
switch key {
case "visibility":
return gnc.Node.Modifier
case "returntype":
return gnc.Node.ReturnType
case "name":
return gnc.Node.Name
// add other cases as necessary for your application
default:
fmt.Printf("Unsupported attribute key: %s\n", key)
return ""
}
}

func QueryEntities(graph *CodeGraph, query *queryparser.Query) []*GraphNode {
result := make([]*GraphNode, 0)
if query.Entity == "method" {
for _, node := range graph.Nodes {
// create array of nodes that match the query
if node.Type == "method_declaration" && node.Name == query.Conditions[0].Value {

for _, node := range graph.Nodes {
if node.Type == query.Entity {
ctx := GraphNodeContext{Node: node} // Create a context for each node
if query.Conditions.Evaluate(&ctx) { // Use the context in evaluation
result = append(result, node)
}
}
Expand Down