Skip to content

Commit

Permalink
Allow matching a buffer with any chunk type
Browse files Browse the repository at this point in the history
e.g. match { retrieval [any] }
  • Loading branch information
asmaloney committed Sep 1, 2023
1 parent 222ceaa commit 7edb375
Show file tree
Hide file tree
Showing 10 changed files with 114 additions and 44 deletions.
15 changes: 15 additions & 0 deletions actr/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,21 @@ func (model *Model) FinalizeImplicitChunks() {
model.ImplicitChunks = list
}

// HasAnyBufferMatch checks if this model uses an "any" match.
// In this case a framework may need to declare a new chunk type for "any_chunk".
func (model Model) HasAnyBufferMatch() bool {
for _, production := range model.Productions {
for _, match := range production.Matches {
if (match.BufferPattern != nil) &&
match.BufferPattern.Pattern.AnyChunk {
return true
}
}
}

return false
}

// HasPrintStatement checks if this model uses the print statement.
// This is used to include extra code to handle printing in some frameworks.
func (model Model) HasPrintStatement() bool {
Expand Down
2 changes: 2 additions & 0 deletions actr/pattern.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package actr
import "fmt"

type Pattern struct {
AnyChunk bool

Chunk *Chunk
Slots []*PatternSlot
}
Expand Down
14 changes: 11 additions & 3 deletions amod/amod.go
Original file line number Diff line number Diff line change
Expand Up @@ -690,17 +690,25 @@ func fieldToKeyValue(f *field) *keyvalue.KeyValue {
}

func createChunkPattern(model *actr.Model, log *issueLog, cp *pattern) (*actr.Pattern, error) {
chunk := model.LookupChunk(cp.ChunkName)
if cp.AnyChunk != nil {
pattern := actr.Pattern{
AnyChunk: true,
}

return &pattern, nil
}

chunk := model.LookupChunk(cp.Chunk.Name)
if chunk == nil {
log.errorTR(cp.Tokens, 1, 2, "could not find chunk named '%s'", cp.ChunkName)
log.errorTR(cp.Tokens, 1, 2, "could not find chunk named '%s'", cp.Chunk.Name)
return nil, ErrCompile
}

pattern := actr.Pattern{
Chunk: chunk,
}

for _, slot := range cp.Slots {
for _, slot := range cp.Chunk.Slots {
actrSlot := actr.PatternSlot{
Negated: slot.Not,
}
Expand Down
15 changes: 15 additions & 0 deletions amod/amod_productions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,21 @@ func Example_productionPrintStatementWildcard() {
// ERROR: unexpected token "*" (expected "}") (line 9, col 13)
}

func Example_productionMatchBufferAny() {
generateToStdout(`
~~ model ~~
name: Test
~~ config ~~
~~ init ~~
~~ productions ~~
start {
match { retrieval [any] }
do { print 42 }
}`)

// Output:
}

func Example_productionMatchBufferState() {
generateToStdout(`
~~ model ~~
Expand Down
34 changes: 11 additions & 23 deletions amod/lex.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ const (

var keywords []string = []string{
"and",
"any",
"authors",
"buffer_state",
"chunks",
Expand Down Expand Up @@ -494,31 +495,18 @@ func lexIdentifier(l *lexer_amod) stateFn {
l.next()
}

if !l.inPattern {
// Perhaps not the best way to do this.
// I'm sure there's a char-by-char way we could implement which would be faster.
isKeyword := l.lookupKeyword(l.input[l.start:l.pos])
switch {
case isKeyword:
l.emit(lexemeKeyword)
// Perhaps not the best way to do this.
// I'm sure there's a char-by-char way we could implement which would be faster.
isKeyword := l.lookupKeyword(l.input[l.start:l.pos])
switch {
case isKeyword:
l.emit(lexemeKeyword)

case l.input[l.start] == '?':
l.emit(lexemePatternVar)
case l.input[l.start] == '?':
l.emit(lexemePatternVar)

default:
l.emit(lexemeIdentifier)
}
} else {
if l.input[l.start] == '?' {
l.emit(lexemePatternVar)
} else {
// hack(ish) since we only allow 'nil' keyword
if l.input[l.start:l.pos] == "nil" {
l.emit(lexemeKeyword)
} else {
l.emit(lexemeIdentifier)
}
}
default:
l.emit(lexemeIdentifier)
}

return lexStart
Expand Down
17 changes: 13 additions & 4 deletions amod/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,11 +272,20 @@ type patternSlot struct {
Tokens []lexer.Token
}

type chunkPattern struct {
Name string `parser:"@Ident ':'"`
Slots []*patternSlot `parser:"@@+"`

Tokens []lexer.Token
}

type pattern struct {
StartBracket string `parser:"'['"` // not used - must be set for parse
ChunkName string `parser:"@Ident ':'"`
Slots []*patternSlot `parser:"@@+"`
EndBracket string `parser:"']'"` // not used - must be set for parse
StartBracket string `parser:"'['"` // not used - must be set for parse

AnyChunk *string `parser:"( @('any':Keyword)"`
Chunk *chunkPattern `parser:"| @@ )"`

EndBracket string `parser:"']'"` // not used - must be set for parse

Tokens []lexer.Token
}
Expand Down
27 changes: 18 additions & 9 deletions amod/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,16 +139,21 @@ func validateInterModuleInitDependencies(model *actr.Model, log *issueLog, confi
return
}

// validatePattern ensures that the pattern's chunk exists and that its number of slots match.
// validatePattern ensures that the pattern is "any" OR
// its chunk exists and its number of slots match.
func validatePattern(model *actr.Model, log *issueLog, pattern *pattern) (err error) {
chunkName := pattern.ChunkName
if pattern.AnyChunk != nil {
return
}

chunkName := pattern.Chunk.Name
chunk := model.LookupChunk(chunkName)
if chunk == nil {
log.errorTR(pattern.Tokens, 1, 2, "could not find chunk named '%s'", chunkName)
return ErrCompile
}

if len(pattern.Slots) != chunk.NumSlots {
if len(pattern.Chunk.Slots) != chunk.NumSlots {
s := "slots"
if chunk.NumSlots == 1 {
s = "slot"
Expand Down Expand Up @@ -181,7 +186,7 @@ func validateBufferPatternMatch(item *matchBufferPatternItem, model *actr.Model,
if item.When != nil {
for _, expr := range *item.When.Expressions {
// Check that we haven't negated it in the pattern and then tried to constrain it further
for _, slot := range pattern.Slots {
for _, slot := range pattern.Chunk.Slots {
if slot.Not && slot.Var != nil {
if expr.LHS == *slot.Var {
log.errorTR(expr.Tokens, 1, 2, "cannot further constrain a negated variable '%s'", expr.LHS)
Expand Down Expand Up @@ -352,10 +357,10 @@ func validateSetStatement(set *setStatement, model *actr.Model, log *issueLog, p
return
}

chunkName := set.Pattern.ChunkName
chunkName := set.Pattern.Chunk.Name
chunk := model.LookupChunk(chunkName)

for slotIndex, slot := range set.Pattern.Slots {
for slotIndex, slot := range set.Pattern.Chunk.Slots {
if slot.Var == nil {
continue
}
Expand Down Expand Up @@ -390,7 +395,7 @@ func validateRecallStatement(recall *recallStatement, model *actr.Model, log *is
for _, v := range vars {
match := production.LookupMatchByVariable(v.text)
if match == nil {
log.errorT(recall.Pattern.Slots[v.index].Tokens, "recall statement variable '%s' not found in matches for production '%s'", v.text, production.Name)
log.errorT(recall.Pattern.Chunk.Slots[v.index].Tokens, "recall statement variable '%s' not found in matches for production '%s'", v.text, production.Name)
err = ErrCompile
}
}
Expand Down Expand Up @@ -512,13 +517,17 @@ func validateVariableUsage(log *issueLog, match *match, do *do) {

// Walks a pattern to add all vars within
addPatternRefs := func(p *pattern, insertIfNotFound bool) {
if p.AnyChunk != nil {
return
}

vars := varsFromPattern(p)

for _, v := range vars {
if r, ok := varRefCount[v.text]; ok {
r.count++
} else if insertIfNotFound {
tokens := p.Slots[v.index].Tokens
tokens := p.Chunk.Slots[v.index].Tokens
varRefCount[v.text] = &ref{
location: tokensToLocation(tokens),
count: 1,
Expand Down Expand Up @@ -612,7 +621,7 @@ func validateVariableUsage(log *issueLog, match *match, do *do) {

// Get a slice of all the vars referenced in a pattern
func varsFromPattern(pattern *pattern) (vars []varAndIndex) {
for i, slot := range pattern.Slots {
for i, slot := range pattern.Chunk.Slots {
if slot.Var != nil {
vars = append(vars, varAndIndex{text: *slot.Var, index: i})
}
Expand Down
6 changes: 5 additions & 1 deletion framework/ccm_pyactr/ccm_pyactr.go
Original file line number Diff line number Diff line change
Expand Up @@ -521,7 +521,11 @@ func (c CCMPyACTR) outputMatch(match *actr.Match) {
bufferName := match.BufferPattern.Buffer.Name()

c.Write("%s=", bufferName)
c.outputPattern(match.BufferPattern.Pattern)
if match.BufferPattern.Pattern.AnyChunk {
c.Write("'?'")
} else {
c.outputPattern(match.BufferPattern.Pattern)
}

case match.BufferState != nil:
bufferName := match.BufferState.Buffer.Name()
Expand Down
24 changes: 21 additions & 3 deletions framework/pyactr/pyactr.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@ import (
//go:embed pyactr_print.py
var pyactrPrintPython string

const pyactrPrintFileName = "pyactr_print.py"
const (
pyactrPrintFileName = "pyactr_print.py"

// When a pattern's AnyChunk is true, we use ANY_CHUNK_TYPE for the chunk type
ANY_CHUNK_TYPE = "any_chunk"
)

var Info framework.Info = framework.Info{
Name: "pyactr",
Expand Down Expand Up @@ -260,7 +265,14 @@ func (p *PyACTR) GenerateCode(initialBuffers framework.InitialBuffers) (code []b
p.Writeln("pyactr_print.PrintBuffer(%s)", p.className)
}

p.Write("\n")
p.Writeln("")

// additional chunk types
if p.model.HasAnyBufferMatch() {
p.Writeln("# Declare a chunk type so we can match 'any' chunks")
p.Writeln("actr.chunktype('%s', '')", ANY_CHUNK_TYPE)
p.Writeln("")
}

// chunks
for _, chunk := range p.model.Chunks {
Expand Down Expand Up @@ -529,7 +541,13 @@ func (p PyACTR) outputMatch(match *actr.Match) {
bufferName := match.BufferPattern.Buffer.Name()

p.Writeln(" =%s>", bufferName)
p.outputPattern(match.BufferPattern.Pattern, 2)

if match.BufferPattern.Pattern.AnyChunk {
tabbedItems.Add("isa", ANY_CHUNK_TYPE)
p.TabWrite(2, tabbedItems)
} else {
p.outputPattern(match.BufferPattern.Pattern, 2)
}

case match.BufferState != nil:
bufferName := match.BufferState.Buffer.Name()
Expand Down
4 changes: 3 additions & 1 deletion framework/vanilla_actr/vanilla_actr.go
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,9 @@ func (v VanillaACTR) outputMatch(match *actr.Match) {
bufferName := match.BufferPattern.Buffer.Name()

v.Writeln("\t=%s>", bufferName)
v.outputPattern(match.BufferPattern.Pattern, 2)
if !match.BufferPattern.Pattern.AnyChunk {
v.outputPattern(match.BufferPattern.Pattern, 2)
}

case match.BufferState != nil:
bufferName := match.BufferState.Buffer.Name()
Expand Down

0 comments on commit 7edb375

Please sign in to comment.