Skip to content

Commit

Permalink
hyperscript/analysis: analyze installs + impr. behavior analsysis
Browse files Browse the repository at this point in the history
  • Loading branch information
GraphR00t committed May 15, 2024
1 parent c679028 commit b714744
Show file tree
Hide file tree
Showing 8 changed files with 274 additions and 54 deletions.
77 changes: 70 additions & 7 deletions internal/hyperscript/hsanalysis/analyze.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,19 @@ type analyzer struct {
//state

inTellCommand bool
behaviorStack []*Behavior

//result
errors []Error
warnings []Warning
behaviors []Behavior
behaviors []*Behavior
functionDefinitions []FunctionDefinition
}

type Result struct {
Errors []Error
Warnings []Warning
Behaviors []Behavior
Behaviors []*Behavior
FunctionDefinitions []FunctionDefinition
}

Expand Down Expand Up @@ -76,7 +77,6 @@ func (a *analyzer) preVisitHyperscriptNode(
inComponentContext := component != nil && isInClientSideInterpolation

switch nodeType {
case hscode.SetCommand:
case hscode.TellCommand:
if a.inTellCommand {
return hscode.PruneAstTraversal, nil
Expand Down Expand Up @@ -122,15 +122,25 @@ func (a *analyzer) preVisitHyperscriptNode(
default:
}
case hscode.DefFeature:
a.functionDefinitions = append(a.functionDefinitions, MakeFunctionDefinitionFromDefFeature(node))
a.functionDefinitions = append(a.functionDefinitions, MakeFunctionDefinitionFromNode(node))
case hscode.BehaviorFeature:
a.behaviors = append(a.behaviors, MakeBehaviorFromBehaviorFeature(node))
behavior := MakeBehaviorFromNode(node)
a.behaviors = append(a.behaviors, behavior)
a.behaviorStack = append(a.behaviorStack, behavior)

preAnalyzeFeaturesOfBehaviorOrComponent(
&behavior.InitialElementScopeVarNames,
&behavior.InitializedDataAttributeNames,
&behavior.HandledEvents,
&behavior.Installs,
behavior.Features,
)
}

return hscode.ContinueAstTraversal, nil
}

func (c *analyzer) postVisitHyperscriptNode(
func (a *analyzer) postVisitHyperscriptNode(
node hscode.JSONMap,
nodeType hscode.NodeType,

Expand All @@ -143,8 +153,61 @@ func (c *analyzer) postVisitHyperscriptNode(

switch nodeType {
case hscode.TellCommand:
c.inTellCommand = false
a.inTellCommand = false
case hscode.BehaviorFeature:
a.behaviorStack = a.behaviorStack[:len(a.behaviorStack)-1]
}

return hscode.ContinueAstTraversal, nil
}

func preAnalyzeFeaturesOfBehaviorOrComponent(
initialElementScopeVarNames *[]string,
initializedDataAttributeNames *[]string,
handledEvents *[]DOMEvent,
installs *[]*InstallFeature,
features []any,
) {
walk := func(node hscode.JSONMap, inInit bool) {
hscode.Walk(node, func(node hscode.JSONMap, nodeType hscode.NodeType, _ hscode.JSONMap, _ hscode.NodeType, _ []hscode.JSONMap, _ bool) (hscode.AstTraversalAction, error) {
switch nodeType {
case hscode.SetCommand:
target, _ := hscode.GetSetCommandTarget(node)
switch hscode.GetTypeIfNode(target) {
case hscode.Symbol:
name := hscode.GetSymbolName(target)
if inInit && strings.HasPrefix(name, ":") && !slices.Contains(*initialElementScopeVarNames, name) {
*initialElementScopeVarNames = append(*initialElementScopeVarNames, name)
}
case hscode.AttributeRef:
name := hscode.GetAttributeRefName(target)
if inInit && strings.HasPrefix(name, "data-") && !slices.Contains(*initializedDataAttributeNames, name) {
*initializedDataAttributeNames = append(*initializedDataAttributeNames, name)
}
}

}
return hscode.ContinueAstTraversal, nil
}, nil)
}

for _, feature := range features {
feature := feature.(hscode.JSONMap)
switch hscode.GetTypeIfNode(feature) {
case hscode.InitFeature: //init
walk(feature, true)
case hscode.OnFeature: //on
onFeature := feature
events, _ := hscode.GetOnFeatureEvents(onFeature)
for _, event := range events {
*handledEvents = append(*handledEvents, DOMEvent{
Type: event.Name,
})
}
walk(feature, false)
case hscode.InstallFeature:
installfeature := MakeInstallFeatureFromNode(feature)
*installs = append(*installs, installfeature)
}
}
}
144 changes: 144 additions & 0 deletions internal/hyperscript/hsanalysis/analyze_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,150 @@ func TestAnalyzeHyperscripFile(t *testing.T) {
feature1 := behavior.Features[1]
assert.True(t, hscode.IsNodeOfType(feature1, hscode.InitFeature))
})

t.Run("initialization of an element scoped variable and an attribute", func(t *testing.T) {

file := utils.Must(hsparse.ParseFile(context.Background(), sourcecode.File{
NameString: "/a._hs",
Resource: "/a._hs",
ResourceDir: "/",
CodeString: `
behavior A
init
set :a to 0
set @data-x to 0
end
`,
}, nil))

result, err := Analyze(Parameters{
LocationKind: locationKind,
Chunk: file,
CodeStartIndex: 0,
ProgramOrExpression: file.Result.NodeData,
})

if !assert.NoError(t, err) {
return
}

if !assert.Len(t, result.Behaviors, 1) {
return
}

behavior := result.Behaviors[0]
assert.Equal(t, []string{":a"}, behavior.InitialElementScopeVarNames)
assert.Equal(t, []string{"data-x"}, behavior.InitializedDataAttributeNames)
})

t.Run("event handler", func(t *testing.T) {

file := utils.Must(hsparse.ParseFile(context.Background(), sourcecode.File{
NameString: "/a._hs",
Resource: "/a._hs",
ResourceDir: "/",
CodeString: `
behavior A
on click
end
`,
}, nil))

result, err := Analyze(Parameters{
LocationKind: locationKind,
Chunk: file,
CodeStartIndex: 0,
ProgramOrExpression: file.Result.NodeData,
})

if !assert.NoError(t, err) {
return
}

if !assert.Len(t, result.Behaviors, 1) {
return
}

behavior := result.Behaviors[0]
assert.Equal(t, []DOMEvent{{Type: "click"}}, behavior.HandledEvents)
})

t.Run("event handler", func(t *testing.T) {

file := utils.Must(hsparse.ParseFile(context.Background(), sourcecode.File{
NameString: "/a._hs",
Resource: "/a._hs",
ResourceDir: "/",
CodeString: `
behavior A
on click end
end
`,
}, nil))

result, err := Analyze(Parameters{
LocationKind: locationKind,
Chunk: file,
CodeStartIndex: 0,
ProgramOrExpression: file.Result.NodeData,
})

if !assert.NoError(t, err) {
return
}

if !assert.Len(t, result.Behaviors, 1) {
return
}

behavior := result.Behaviors[0]
assert.Equal(t, []DOMEvent{{Type: "click"}}, behavior.HandledEvents)
})

t.Run("behavior install", func(t *testing.T) {

file := utils.Must(hsparse.ParseFile(context.Background(), sourcecode.File{
NameString: "/a._hs",
Resource: "/a._hs",
ResourceDir: "/",
CodeString: `
behavior A
install B(x: 1)
end
`,
}, nil))

result, err := Analyze(Parameters{
LocationKind: locationKind,
Chunk: file,
CodeStartIndex: 0,
ProgramOrExpression: file.Result.NodeData,
})

if !assert.NoError(t, err) {
return
}

if !assert.Len(t, result.Behaviors, 1) {
return
}

behavior := result.Behaviors[0]
if !assert.Len(t, behavior.Installs, 1) {
return
}

install := behavior.Installs[0]

assert.Equal(t, "B", install.BehaviorFullName)
if !assert.Len(t, install.Fields, 1) {
return
}
field := install.Fields[0]

assert.Equal(t, "x", field.Name)
assert.True(t, hscode.IsNodeOfType(field.Value, hscode.NumberLiteral))
})
})

t.Run("function definition", func(t *testing.T) {
Expand Down
12 changes: 10 additions & 2 deletions internal/hyperscript/hsanalysis/behavior.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,17 @@ type Behavior struct {
FullName string
Namespace []string
Features []any

HandledEvents []DOMEvent
InitialElementScopeVarNames []string // example: {":a", ":b"}
InitializedDataAttributeNames []string // data-xxx attributes that are properly initialized, example: {"data-count", "data-x"}
Installs []*InstallFeature
AppliedInstalls []*InstallFeature

//Note: applying an install updates InitialElementScopeVarNames and InitializedDataAttributeNames.
}

func MakeBehaviorFromBehaviorFeature(node hscode.JSONMap) Behavior {
func MakeBehaviorFromNode(node hscode.JSONMap) *Behavior {
hscode.AssertIsNodeOfType(node, hscode.BehaviorFeature)

var behavior Behavior
Expand All @@ -26,5 +34,5 @@ func MakeBehaviorFromBehaviorFeature(node hscode.JSONMap) Behavior {

behavior.Features, _ = node["features"].([]any)

return behavior
return &behavior
}
53 changes: 11 additions & 42 deletions internal/hyperscript/hsanalysis/component.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
package hsanalysis

import (
"strings"

"github.com/inoxlang/inox/internal/css"
"github.com/inoxlang/inox/internal/hyperscript/hscode"
"github.com/inoxlang/inox/internal/parse"
"github.com/inoxlang/inox/internal/utils"
"golang.org/x/exp/slices"
)

type Component struct {
Expand All @@ -19,6 +16,10 @@ type Component struct {
HandledEvents []DOMEvent
InitialElementScopeVarNames []string // example: {":a", ":b"}
InitializedDataAttributeNames []string // data-xxx attributes that are properly initialized, example: {"data-count", "data-x"}
Installs []*InstallFeature
AppliedInstalls []*InstallFeature

//Note: applying an install updates InitialElementScopeVarNames and InitializedDataAttributeNames.
}

type DOMEvent struct {
Expand Down Expand Up @@ -81,45 +82,13 @@ func PreanalyzeHyperscriptComponent(
return
}

walk := func(node hscode.JSONMap, inInit bool) {
hscode.Walk(node, func(node hscode.JSONMap, nodeType hscode.NodeType, _ hscode.JSONMap, _ hscode.NodeType, _ []hscode.JSONMap, _ bool) (hscode.AstTraversalAction, error) {
switch nodeType {
case hscode.SetCommand:
target, _ := hscode.GetSetCommandTarget(node)
switch hscode.GetTypeIfNode(target) {
case hscode.Symbol:
name := hscode.GetSymbolName(target)
if inInit && strings.HasPrefix(name, ":") && !slices.Contains(component.InitialElementScopeVarNames, name) {
component.InitialElementScopeVarNames = append(component.InitialElementScopeVarNames, name)
}
case hscode.AttributeRef:
name := hscode.GetAttributeRefName(target)
if inInit && strings.HasPrefix(name, "data-") && !slices.Contains(component.InitializedDataAttributeNames, name) {
component.InitializedDataAttributeNames = append(component.InitializedDataAttributeNames, name)
}
}

}
return hscode.ContinueAstTraversal, nil
}, nil)
}

for _, feature := range features {
feature := feature.(hscode.JSONMap)
switch hscode.GetTypeIfNode(feature) {
case hscode.InitFeature: //init
walk(feature, true)
case hscode.OnFeature: //on
onFeature := feature
events, _ := hscode.GetOnFeatureEvents(onFeature)
for _, event := range events {
component.HandledEvents = append(component.HandledEvents, DOMEvent{
Type: event.Name,
})
}
walk(feature, false)
}
}
preAnalyzeFeaturesOfBehaviorOrComponent(
&component.InitialElementScopeVarNames,
&component.InitializedDataAttributeNames,
&component.HandledEvents,
&component.Installs,
features,
)

return
}
2 changes: 1 addition & 1 deletion internal/hyperscript/hsanalysis/function_definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ type FunctionDefinition struct {
CommandList []any
}

func MakeFunctionDefinitionFromDefFeature(node hscode.JSONMap) FunctionDefinition {
func MakeFunctionDefinitionFromNode(node hscode.JSONMap) FunctionDefinition {
hscode.AssertIsNodeOfType(node, hscode.DefFeature)

var def FunctionDefinition
Expand Down
Loading

0 comments on commit b714744

Please sign in to comment.