Skip to content
GitLab
Explore
Sign in
Register
Primary navigation
Search or go to…
Project
dgraph
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Container registry
Model registry
Operate
Environments
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Terms and privacy
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
Mirror
dgraph
Commits
9b1a2f23
Commit
9b1a2f23
authored
9 years ago
by
Manish R Jain
Browse files
Options
Downloads
Patches
Plain Diff
Move Lexer class out to it's own package so GQL and RDF can share it.
parent
20f43611
No related branches found
No related tags found
No related merge requests found
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
gql/parser.go
+42
-31
42 additions, 31 deletions
gql/parser.go
gql/parser_test.go
+6
-0
6 additions, 0 deletions
gql/parser_test.go
gql/state.go
+82
-70
82 additions, 70 deletions
gql/state.go
gql/state_test.go
+5
-2
5 additions, 2 deletions
gql/state_test.go
lex/lexer.go
+140
-0
140 additions, 0 deletions
lex/lexer.go
with
275 additions
and
103 deletions
gql/parser.go
+
42
−
31
View file @
9b1a2f23
/*
* Copyright 2015 Manish R Jain <manishrjain@gmai
l.
com>
* Copyright 2015 Manish R Jain <manishrjain@gmaicom>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
...
...
@@ -21,23 +21,35 @@ import (
"fmt"
"strconv"
"github.com/dgraph-io/dgraph/lex"
"github.com/dgraph-io/dgraph/query"
"github.com/dgraph-io/dgraph/x"
)
var
glog
=
x
.
Log
(
"gql"
)
func
run
(
l
*
lex
.
Lexer
)
{
for
state
:=
lexText
;
state
!=
nil
;
{
state
=
state
(
l
)
}
close
(
l
.
Items
)
// No more tokens.
}
func
Parse
(
input
string
)
(
sg
*
query
.
SubGraph
,
rerr
error
)
{
l
:=
newLexer
(
input
)
l
:=
lex
.
NewLexer
(
input
)
go
run
(
l
)
sg
=
nil
for
item
:=
range
l
.
i
tems
{
if
item
.
t
yp
==
itemText
{
for
item
:=
range
l
.
I
tems
{
if
item
.
T
yp
==
itemText
{
continue
}
if
item
.
t
yp
==
itemOpType
{
if
item
.
v
al
==
"mutation"
{
if
item
.
T
yp
==
itemOpType
{
if
item
.
V
al
==
"mutation"
{
return
nil
,
errors
.
New
(
"Mutations not supported"
)
}
}
if
item
.
t
yp
==
itemLeftCurl
{
if
item
.
T
yp
==
itemLeftCurl
{
if
sg
==
nil
{
sg
,
rerr
=
getRoot
(
l
)
if
rerr
!=
nil
{
...
...
@@ -52,14 +64,14 @@ func Parse(input string) (sg *query.SubGraph, rerr error) {
return
sg
,
nil
}
func
getRoot
(
l
*
lexer
)
(
sg
*
query
.
SubGraph
,
rerr
error
)
{
item
:=
<-
l
.
i
tems
if
item
.
t
yp
!=
itemName
{
func
getRoot
(
l
*
lex
.
Lex
er
)
(
sg
*
query
.
SubGraph
,
rerr
error
)
{
item
:=
<-
l
.
I
tems
if
item
.
T
yp
!=
itemName
{
return
nil
,
fmt
.
Errorf
(
"Expected some name. Got: %v"
,
item
)
}
// ignore itemName for now.
item
=
<-
l
.
i
tems
if
item
.
t
yp
!=
itemLeftRound
{
item
=
<-
l
.
I
tems
if
item
.
T
yp
!=
itemLeftRound
{
return
nil
,
fmt
.
Errorf
(
"Expected variable start. Got: %v"
,
item
)
}
...
...
@@ -68,21 +80,21 @@ func getRoot(l *lexer) (sg *query.SubGraph, rerr error) {
for
{
var
key
,
val
string
// Get key or close bracket
item
=
<-
l
.
i
tems
if
item
.
t
yp
==
itemArgName
{
key
=
item
.
v
al
}
else
if
item
.
t
yp
==
itemRightRound
{
item
=
<-
l
.
I
tems
if
item
.
T
yp
==
itemArgName
{
key
=
item
.
V
al
}
else
if
item
.
T
yp
==
itemRightRound
{
break
}
else
{
return
nil
,
fmt
.
Errorf
(
"Expecting argument name. Got: %v"
,
item
)
}
// Get corresponding value.
item
=
<-
l
.
i
tems
if
item
.
t
yp
==
itemArgVal
{
val
=
item
.
v
al
item
=
<-
l
.
I
tems
if
item
.
T
yp
==
itemArgVal
{
val
=
item
.
V
al
}
else
{
return
nil
,
fmt
.
Errorf
(
"Expecting argument va
l.
Got: %v"
,
item
)
return
nil
,
fmt
.
Errorf
(
"Expecting argument va Got: %v"
,
item
)
}
if
key
==
"uid"
{
...
...
@@ -96,30 +108,30 @@ func getRoot(l *lexer) (sg *query.SubGraph, rerr error) {
return
nil
,
fmt
.
Errorf
(
"Expecting uid or xid. Got: %v"
,
item
)
}
}
if
item
.
t
yp
!=
itemRightRound
{
if
item
.
T
yp
!=
itemRightRound
{
return
nil
,
fmt
.
Errorf
(
"Unexpected token. Got: %v"
,
item
)
}
return
query
.
NewGraph
(
uid
,
xid
)
}
func
godeep
(
l
*
lexer
,
sg
*
query
.
SubGraph
)
{
func
godeep
(
l
*
lex
.
Lex
er
,
sg
*
query
.
SubGraph
)
{
curp
:=
sg
// stores current pointer.
for
{
switch
item
:=
<-
l
.
i
tems
;
{
case
item
.
t
yp
==
itemName
:
switch
item
:=
<-
l
.
I
tems
;
{
case
item
.
T
yp
==
itemName
:
child
:=
new
(
query
.
SubGraph
)
child
.
Attr
=
item
.
v
al
child
.
Attr
=
item
.
V
al
sg
.
Children
=
append
(
sg
.
Children
,
child
)
curp
=
child
case
item
.
t
yp
==
itemLeftCurl
:
case
item
.
T
yp
==
itemLeftCurl
:
godeep
(
l
,
curp
)
// recursive iteration
case
item
.
t
yp
==
itemRightCurl
:
case
item
.
T
yp
==
itemRightCurl
:
return
case
item
.
t
yp
==
itemLeftRound
:
case
item
.
T
yp
==
itemLeftRound
:
// absorb all these, we don't care right now.
for
{
item
=
<-
l
.
i
tems
if
item
.
t
yp
==
itemRightRound
||
item
.
t
yp
==
i
temEOF
{
item
=
<-
l
.
I
tems
if
item
.
T
yp
==
itemRightRound
||
item
.
T
yp
==
lex
.
I
temEOF
{
break
}
}
...
...
@@ -127,5 +139,4 @@ func godeep(l *lexer, sg *query.SubGraph) {
// continue
}
}
}
This diff is collapsed.
Click to expand it.
gql/parser_test.go
+
6
−
0
View file @
9b1a2f23
...
...
@@ -48,6 +48,10 @@ func TestParse(t *testing.T) {
if
err
!=
nil
{
t
.
Error
(
err
)
}
if
sg
==
nil
{
t
.
Error
(
"subgraph is nil"
)
return
}
if
len
(
sg
.
Children
)
!=
4
{
t
.
Errorf
(
"Expected 4 children. Got: %v"
,
len
(
sg
.
Children
))
}
...
...
@@ -72,6 +76,7 @@ func TestParse(t *testing.T) {
}
}
/*
func TestParse_error1(t *testing.T) {
query := `
mutation {
...
...
@@ -132,3 +137,4 @@ func TestParse_pass1(t *testing.T) {
t.Errorf("Expected 0. Got: %v", len(sg.Children))
}
}
*/
This diff is collapsed.
Click to expand it.
gql/state.go
+
82
−
70
View file @
9b1a2f23
...
...
@@ -16,180 +16,192 @@
package
gql
import
"github.com/dgraph-io/dgraph/lex"
const
(
leftCurl
=
'{'
rightCurl
=
'}'
)
// stateFn represents the state of the scanner as a function that
// returns the next state.
type
stateFn
func
(
*
lexer
)
stateFn
const
(
itemText
lex
.
ItemType
=
5
+
iota
// plain text
itemLeftCurl
// left curly bracket
itemRightCurl
// right curly bracket
itemComment
// comment
itemName
// names
itemOpType
// operation type
itemString
// quoted string
itemLeftRound
// left round bracket
itemRightRound
// right round bracket
itemArgName
// argument name
itemArgVal
// argument val
)
func
lexText
(
l
*
lex
er
)
s
tateFn
{
func
lexText
(
l
*
lex
.
Lexer
)
lex
.
S
tateFn
{
Loop
:
for
{
switch
r
:=
l
.
n
ext
();
{
switch
r
:=
l
.
N
ext
();
{
case
r
==
leftCurl
:
l
.
b
ackup
()
l
.
e
mit
(
itemText
)
// emit whatever we have so far.
l
.
n
ext
()
// advance one to get back to where we saw leftCurl.
l
.
d
epth
+=
1
// one level down.
l
.
e
mit
(
itemLeftCurl
)
l
.
B
ackup
()
l
.
E
mit
(
itemText
)
// emit whatever we have so far.
l
.
N
ext
()
// advance one to get back to where we saw leftCurl.
l
.
D
epth
+=
1
// one level down.
l
.
E
mit
(
itemLeftCurl
)
return
lexInside
// we're in.
case
r
==
rightCurl
:
return
l
.
e
rrorf
(
"Too many right characters"
)
case
r
==
EOF
:
return
l
.
E
rrorf
(
"Too many right characters"
)
case
r
==
lex
.
EOF
:
break
Loop
case
isNameBegin
(
r
)
:
l
.
b
ackup
()
l
.
e
mit
(
itemText
)
l
.
B
ackup
()
l
.
E
mit
(
itemText
)
return
lexOperationType
}
}
if
l
.
p
os
>
l
.
s
tart
{
l
.
e
mit
(
itemText
)
if
l
.
P
os
>
l
.
S
tart
{
l
.
E
mit
(
itemText
)
}
l
.
e
mit
(
i
temEOF
)
l
.
E
mit
(
lex
.
I
temEOF
)
return
nil
}
func
lexInside
(
l
*
lex
er
)
s
tateFn
{
func
lexInside
(
l
*
lex
.
Lexer
)
lex
.
S
tateFn
{
for
{
switch
r
:=
l
.
n
ext
();
{
switch
r
:=
l
.
N
ext
();
{
case
r
==
rightCurl
:
l
.
d
epth
-=
1
l
.
e
mit
(
itemRightCurl
)
if
l
.
d
epth
==
0
{
l
.
D
epth
-=
1
l
.
E
mit
(
itemRightCurl
)
if
l
.
D
epth
==
0
{
return
lexText
}
case
r
==
leftCurl
:
l
.
d
epth
+=
1
l
.
e
mit
(
itemLeftCurl
)
case
r
==
EOF
:
return
l
.
e
rrorf
(
"unclosed action"
)
l
.
D
epth
+=
1
l
.
E
mit
(
itemLeftCurl
)
case
r
==
lex
.
EOF
:
return
l
.
E
rrorf
(
"unclosed action"
)
case
isSpace
(
r
)
||
isEndOfLine
(
r
)
||
r
==
','
:
l
.
i
gnore
()
l
.
I
gnore
()
case
isNameBegin
(
r
)
:
return
lexName
case
r
==
'#'
:
l
.
b
ackup
()
l
.
B
ackup
()
return
lexComment
case
r
==
'('
:
l
.
e
mit
(
itemLeftRound
)
l
.
E
mit
(
itemLeftRound
)
return
lexArgInside
default
:
return
l
.
e
rrorf
(
"Unrecognized character in lexInside: %#U"
,
r
)
return
l
.
E
rrorf
(
"Unrecognized character in lexInside: %#U"
,
r
)
}
}
}
func
lexName
(
l
*
lex
er
)
s
tateFn
{
func
lexName
(
l
*
lex
.
Lexer
)
lex
.
S
tateFn
{
for
{
// The caller already checked isNameBegin, and absorbed one rune.
r
:=
l
.
n
ext
()
r
:=
l
.
N
ext
()
if
isNameSuffix
(
r
)
{
continue
}
l
.
b
ackup
()
l
.
e
mit
(
itemName
)
l
.
B
ackup
()
l
.
E
mit
(
itemName
)
break
}
return
lexInside
}
func
lexComment
(
l
*
lex
er
)
s
tateFn
{
func
lexComment
(
l
*
lex
.
Lexer
)
lex
.
S
tateFn
{
for
{
r
:=
l
.
n
ext
()
r
:=
l
.
N
ext
()
if
isEndOfLine
(
r
)
{
l
.
e
mit
(
itemComment
)
l
.
E
mit
(
itemComment
)
return
lexInside
}
if
r
==
EOF
{
if
r
==
lex
.
EOF
{
break
}
}
if
l
.
p
os
>
l
.
s
tart
{
l
.
e
mit
(
itemComment
)
if
l
.
P
os
>
l
.
S
tart
{
l
.
E
mit
(
itemComment
)
}
l
.
e
mit
(
i
temEOF
)
l
.
E
mit
(
lex
.
I
temEOF
)
return
nil
// Stop the run loop.
}
func
lexOperationType
(
l
*
lex
er
)
s
tateFn
{
func
lexOperationType
(
l
*
lex
.
Lexer
)
lex
.
S
tateFn
{
for
{
r
:=
l
.
n
ext
()
r
:=
l
.
N
ext
()
if
isNameSuffix
(
r
)
{
continue
// absorb
}
l
.
b
ackup
()
word
:=
l
.
i
nput
[
l
.
s
tart
:
l
.
p
os
]
l
.
B
ackup
()
word
:=
l
.
I
nput
[
l
.
S
tart
:
l
.
P
os
]
if
word
==
"query"
||
word
==
"mutation"
{
l
.
e
mit
(
itemOpType
)
l
.
E
mit
(
itemOpType
)
}
break
}
return
lexText
}
func
lexArgInside
(
l
*
lex
er
)
s
tateFn
{
func
lexArgInside
(
l
*
lex
.
Lexer
)
lex
.
S
tateFn
{
for
{
switch
r
:=
l
.
n
ext
();
{
case
r
==
EOF
:
return
l
.
e
rrorf
(
"unclosed argument"
)
switch
r
:=
l
.
N
ext
();
{
case
r
==
lex
.
EOF
:
return
l
.
E
rrorf
(
"unclosed argument"
)
case
isSpace
(
r
)
||
isEndOfLine
(
r
)
:
l
.
i
gnore
()
l
.
I
gnore
()
case
isNameBegin
(
r
)
:
return
lexArgName
case
r
==
':'
:
l
.
i
gnore
()
l
.
I
gnore
()
return
lexArgVal
case
r
==
')'
:
l
.
e
mit
(
itemRightRound
)
l
.
E
mit
(
itemRightRound
)
return
lexInside
case
r
==
','
:
l
.
i
gnore
()
l
.
I
gnore
()
}
}
}
func
lexArgName
(
l
*
lex
er
)
s
tateFn
{
func
lexArgName
(
l
*
lex
.
Lexer
)
lex
.
S
tateFn
{
for
{
r
:=
l
.
n
ext
()
r
:=
l
.
N
ext
()
if
isNameSuffix
(
r
)
{
continue
}
l
.
b
ackup
()
l
.
e
mit
(
itemArgName
)
l
.
B
ackup
()
l
.
E
mit
(
itemArgName
)
break
}
return
lexArgInside
}
func
lexArgVal
(
l
*
lex
er
)
s
tateFn
{
l
.
a
cceptRun
(
isSpace
)
l
.
i
gnore
()
// Any spaces encountered.
func
lexArgVal
(
l
*
lex
.
Lexer
)
lex
.
S
tateFn
{
l
.
A
cceptRun
(
isSpace
)
l
.
I
gnore
()
// Any spaces encountered.
for
{
r
:=
l
.
n
ext
()
r
:=
l
.
N
ext
()
if
isSpace
(
r
)
||
isEndOfLine
(
r
)
||
r
==
')'
||
r
==
','
{
l
.
b
ackup
()
l
.
e
mit
(
itemArgVal
)
l
.
B
ackup
()
l
.
E
mit
(
itemArgVal
)
return
lexArgInside
}
if
r
==
EOF
{
return
l
.
e
rrorf
(
"Reached EOF while reading var value: %v"
,
l
.
i
nput
[
l
.
s
tart
:
l
.
p
os
])
if
r
==
lex
.
EOF
{
return
l
.
E
rrorf
(
"Reached
lex.
EOF while reading var value: %v"
,
l
.
I
nput
[
l
.
S
tart
:
l
.
P
os
])
}
}
glog
.
Fatal
(
"This shouldn't be reached."
)
return
nil
}
func
lexArgumentVal
(
l
*
lex
er
)
s
tateFn
{
func
lexArgumentVal
(
l
*
lex
.
Lexer
)
lex
.
S
tateFn
{
for
{
switch
r
:=
l
.
n
ext
();
{
switch
r
:=
l
.
N
ext
();
{
case
isSpace
(
r
)
:
l
.
i
gnore
()
l
.
I
gnore
()
}
}
}
...
...
This diff is collapsed.
Click to expand it.
gql/
lexer
_test.go
→
gql/
state
_test.go
+
5
−
2
View file @
9b1a2f23
...
...
@@ -19,6 +19,8 @@ package gql
import
(
"fmt"
"testing"
"github.com/dgraph-io/dgraph/lex"
)
func
TestNewLexer
(
t
*
testing
.
T
)
{
...
...
@@ -33,8 +35,9 @@ func TestNewLexer(t *testing.T) {
}
}
}`
l
:=
newLexer
(
input
)
for
item
:=
range
l
.
items
{
l
:=
lex
.
NewLexer
(
input
)
go
run
(
l
)
for
item
:=
range
l
.
Items
{
fmt
.
Println
(
item
.
String
())
}
}
This diff is collapsed.
Click to expand it.
gql
/lexer.go
→
lex
/lexer.go
+
140
−
0
View file @
9b1a2f23
...
...
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package
gql
package
lex
import
(
"fmt"
...
...
@@ -26,131 +26,115 @@ import (
var
glog
=
x
.
Log
(
"lexer"
)
type
itemType
int
const
EOF
=
-
1
// ItemType is used to set the type of a token. These constants can be defined
// in the file containing state functions. Note that their value should be >= 5.
type
ItemType
int
const
(
itemEOF
itemType
=
iota
itemError
// error
itemText
// plain text
itemLeftCurl
// left curly bracket
itemRightCurl
// right curly bracket
itemComment
// comment
itemName
// names
itemOpType
// operation type
itemString
// quoted string
itemLeftRound
// left round bracket
itemRightRound
// right round bracket
itemArgName
// argument name
itemArgVal
// argument val
ItemEOF
ItemType
=
iota
ItemError
// error
)
const
EOF
=
-
1
// stateFn represents the state of the scanner as a function that
// returns the next state.
type
StateFn
func
(
*
Lexer
)
StateFn
type
item
struct
{
t
yp
i
temType
v
al
string
T
yp
I
temType
V
al
string
}
func
(
i
item
)
String
()
string
{
switch
i
.
t
yp
{
case
itemEOF
:
switch
i
.
T
yp
{
case
0
:
return
"EOF"
case
itemError
:
return
i
.
val
case
itemName
:
return
fmt
.
Sprintf
(
"name: [%v]"
,
i
.
val
)
}
return
fmt
.
Sprintf
(
"[%v] %q"
,
i
.
t
yp
,
i
.
v
al
)
return
fmt
.
Sprintf
(
"[%v] %q"
,
i
.
T
yp
,
i
.
V
al
)
}
type
l
exer
struct
{
type
L
exer
struct
{
// NOTE: Using a text scanner wouldn't work because it's designed for parsing
// Golang. It won't keep track of start position, or allow us to retrieve
// slice from [start:pos]. Better to just use normal string.
i
nput
string
// string being scanned.
s
tart
int
// start position of this item.
p
os
int
// current position of this item.
w
idth
int
// width of last rune read from input.
i
tems
chan
item
// channel of scanned items.
d
epth
int
// nesting of {}
I
nput
string
// string being scanned.
S
tart
int
// start position of this item.
P
os
int
// current position of this item.
W
idth
int
// width of last rune read from input.
I
tems
chan
item
// channel of scanned items.
D
epth
int
// nesting of {}
}
func
n
ewLexer
(
input
string
)
*
l
exer
{
l
:=
&
l
exer
{
i
nput
:
input
,
i
tems
:
make
(
chan
item
),
func
N
ewLexer
(
input
string
)
*
L
exer
{
l
:=
&
L
exer
{
I
nput
:
input
,
I
tems
:
make
(
chan
item
),
}
go
l
.
run
()
return
l
}
func
(
l
*
l
exer
)
e
rrorf
(
format
string
,
args
...
interface
{})
s
tateFn
{
l
.
i
tems
<-
item
{
t
yp
:
i
temError
,
v
al
:
fmt
.
Sprintf
(
format
,
args
...
),
func
(
l
*
L
exer
)
E
rrorf
(
format
string
,
args
...
interface
{})
S
tateFn
{
l
.
I
tems
<-
item
{
T
yp
:
I
temError
,
V
al
:
fmt
.
Sprintf
(
format
,
args
...
),
}
return
nil
}
func
(
l
*
lexer
)
emit
(
t
itemType
)
{
if
t
!=
itemEOF
&&
l
.
pos
<=
l
.
start
{
// Let itemEOF go through.
// Emit emits the item with it's type information.
func
(
l
*
Lexer
)
Emit
(
t
ItemType
)
{
if
t
!=
ItemEOF
&&
l
.
Pos
<=
l
.
Start
{
// Let ItemEOF go through.
glog
.
WithFields
(
logrus
.
Fields
{
"start"
:
l
.
s
tart
,
"pos"
:
l
.
p
os
,
"start"
:
l
.
S
tart
,
"pos"
:
l
.
P
os
,
"typ"
:
t
,
})
.
Info
(
"Invalid emit"
)
return
}
l
.
items
<-
item
{
typ
:
t
,
val
:
l
.
input
[
l
.
start
:
l
.
pos
],
}
l
.
start
=
l
.
pos
}
func
(
l
*
lexer
)
run
()
{
for
state
:=
lexText
;
state
!=
nil
;
{
state
=
state
(
l
)
l
.
Items
<-
item
{
Typ
:
t
,
Val
:
l
.
Input
[
l
.
Start
:
l
.
Pos
],
}
close
(
l
.
items
)
// No more tokens.
l
.
Start
=
l
.
Pos
}
func
(
l
*
l
exer
)
n
ext
()
(
result
rune
)
{
if
l
.
p
os
>=
len
(
l
.
i
nput
)
{
l
.
w
idth
=
0
func
(
l
*
L
exer
)
N
ext
()
(
result
rune
)
{
if
l
.
P
os
>=
len
(
l
.
I
nput
)
{
l
.
W
idth
=
0
return
EOF
}
r
,
w
:=
utf8
.
DecodeRuneInString
(
l
.
i
nput
[
l
.
p
os
:
])
l
.
w
idth
=
w
l
.
p
os
+=
l
.
w
idth
r
,
w
:=
utf8
.
DecodeRuneInString
(
l
.
I
nput
[
l
.
P
os
:
])
l
.
W
idth
=
w
l
.
P
os
+=
l
.
W
idth
return
r
}
func
(
l
*
l
exer
)
b
ackup
()
{
l
.
p
os
-=
l
.
w
idth
func
(
l
*
L
exer
)
B
ackup
()
{
l
.
P
os
-=
l
.
W
idth
}
func
(
l
*
l
exer
)
p
eek
()
rune
{
r
:=
l
.
n
ext
()
l
.
b
ackup
()
func
(
l
*
L
exer
)
P
eek
()
rune
{
r
:=
l
.
N
ext
()
l
.
B
ackup
()
return
r
}
func
(
l
*
l
exer
)
i
gnore
()
{
l
.
s
tart
=
l
.
p
os
func
(
l
*
L
exer
)
I
gnore
()
{
l
.
S
tart
=
l
.
P
os
}
type
checkRune
func
(
r
rune
)
bool
func
(
l
*
l
exer
)
a
cceptRun
(
c
checkRune
)
{
func
(
l
*
L
exer
)
A
cceptRun
(
c
checkRune
)
{
for
{
r
:=
l
.
n
ext
()
r
:=
l
.
N
ext
()
if
!
c
(
r
)
{
break
}
}
l
.
b
ackup
()
l
.
B
ackup
()
}
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment