initial commit
This commit is contained in:
commit
a98c7c875f
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
**/*_test.go
|
||||||
|
**/*.txt
|
8
consts.go
Normal file
8
consts.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package lexer
|
||||||
|
|
||||||
|
var (
|
||||||
|
Keywords = []string{"pipeline"}
|
||||||
|
Separators = []rune{'\n', ' ', '<', '>'}
|
||||||
|
StringSeparators = []rune{'\'', '"', '`'}
|
||||||
|
Operators = []rune{'='}
|
||||||
|
)
|
25
esc_seq.go
Normal file
25
esc_seq.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package lexer
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
var EscSeqPrintReplacer = strings.NewReplacer(
|
||||||
|
`\`, `\\`,
|
||||||
|
"\n", `\n`,
|
||||||
|
"\t", `\t`,
|
||||||
|
"\f", `\f`,
|
||||||
|
"\r", `\r`,
|
||||||
|
"\v", `\v`,
|
||||||
|
"\b", `\b`,
|
||||||
|
"\a", `\a`,
|
||||||
|
)
|
||||||
|
|
||||||
|
var EscSeqReplacer = strings.NewReplacer(
|
||||||
|
`\\`, `\`,
|
||||||
|
`\n`, "\n",
|
||||||
|
`\t`, "\t",
|
||||||
|
`\f`, "\f",
|
||||||
|
`\r`, "\r",
|
||||||
|
`\v`, "\v",
|
||||||
|
`\b`, "\b",
|
||||||
|
`\a`, "\a",
|
||||||
|
)
|
24
examples/example.slang
Normal file
24
examples/example.slang
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#!/usr/bin/env slash
|
||||||
|
|
||||||
|
anime-watcher # comment
|
||||||
|
exit_on_empty # line 6: filter = ""
|
||||||
|
format -o '$HOME/Anime/{0}.*' # line 10: format string to file
|
||||||
|
filter -f # line 10: check if file exists
|
||||||
|
branch == "hi" # branch/split/if/filter multiple stdout fds
|
||||||
|
cat
|
||||||
|
:a="hello"> tee out1.txt
|
||||||
|
-
|
||||||
|
/home> cat
|
||||||
|
tee out2.txt
|
||||||
|
merge ordered # merge multiple stdin fds
|
||||||
|
echo
|
||||||
|
|
||||||
|
# list all go files in pwd
|
||||||
|
ls *.go # filepath.Glob
|
||||||
|
|
||||||
|
echo $HOME # replace env vars TOOD cd not possible in pipeline
|
||||||
|
~:A="hi" B="bye"> ls # start ls in $HOME folder
|
||||||
|
|
||||||
|
echo -e "123\n321\nasd"
|
||||||
|
branch '^\w+?$'
|
||||||
|
echo # output: asd
|
24
examples/example2.slang
Normal file
24
examples/example2.slang
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#!/usr/bin/env slash
|
||||||
|
|
||||||
|
pipeline trimSpaces { # pipeline definition for future use
|
||||||
|
format -i '^\s*?(.*?)\s*?$' -o '{1}' # equivalent to functions in conventional programming languages
|
||||||
|
}
|
||||||
|
|
||||||
|
range 0-10 # range command ignores stdin and produces output according to arguments
|
||||||
|
branch async { # branch built-in command splits input stream based on case operators
|
||||||
|
# merge operator is optional and defines the merge strategy of output streams (can be one of sync,async | default: sync)
|
||||||
|
|
||||||
|
case >= 5: # cases represent a pipeline (can also be filtered by regex?)
|
||||||
|
~:A="hi" B="bye"> trimSpaces # every command call is first checked for available pipeline (pwd and env variables can be provided if needed)
|
||||||
|
# pipeline cannot by called 'command' so a command with same name can still be called with `command trimSpaces`
|
||||||
|
|
||||||
|
default: # default pipeline is optional (default: empty pipeline)
|
||||||
|
}
|
||||||
|
|
||||||
|
# implementation of fizzbazz
|
||||||
|
range 1..100
|
||||||
|
if (v % 3 == 0 && v % 5 == 0) {echo "fizzbazz"}
|
||||||
|
if (v % 3 == 0) {echo "fizz"} else if (v % 5 == 0) {echo "bazz"}
|
||||||
|
if (v % 5 == 0) {
|
||||||
|
echo "bazz"
|
||||||
|
}
|
12
examples/fizzbazz.slang
Normal file
12
examples/fizzbazz.slang
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#!/usr/bin/env slashlang
|
||||||
|
|
||||||
|
pipeline fizzbazz
|
||||||
|
if v % 3 == 0 && v % 5 == 0
|
||||||
|
print "fizzbazz"
|
||||||
|
if v % 3 == 0
|
||||||
|
print "fizz" # else if (v % 5 == 0) {print "bazz"}
|
||||||
|
if v % 5 == 0
|
||||||
|
<PWD="/home" A="asd"> print "bazz"
|
||||||
|
|
||||||
|
range 1..100
|
||||||
|
fizzbazz
|
14
go.mod
Normal file
14
go.mod
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
module git.milar.in/slash/lexer
|
||||||
|
|
||||||
|
go 1.20
|
||||||
|
|
||||||
|
require (
|
||||||
|
git.milar.in/milarin/bufr v0.0.12
|
||||||
|
git.milar.in/milarin/slices v0.0.7
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
git.milar.in/milarin/adverr v1.1.0 // indirect
|
||||||
|
git.milar.in/milarin/ds v0.0.2 // indirect
|
||||||
|
git.milar.in/milarin/gmath v0.0.3 // indirect
|
||||||
|
)
|
10
go.sum
Normal file
10
go.sum
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
git.milar.in/milarin/adverr v1.1.0 h1:jD9WnOvs40lfMhvqQ7cllOaRJNBMWr1f07/s9jAadp0=
|
||||||
|
git.milar.in/milarin/adverr v1.1.0/go.mod h1:joU9sBb7ySyNv4SpTXB0Z4o1mjXsArBw4N27wjgzj9E=
|
||||||
|
git.milar.in/milarin/bufr v0.0.12 h1:BZwLFOdi5hohQuugQceFHwvmz7ZGYwyhdrBcKfZPjGs=
|
||||||
|
git.milar.in/milarin/bufr v0.0.12/go.mod h1:yIRL89LWUgRlmfuVAwq12YfFs+Hq2Ji4SKEUyqXVTLo=
|
||||||
|
git.milar.in/milarin/ds v0.0.2 h1:vCA3mDxZUNfvHpzrdz7SeBUKiPn74NTopo915IUG7I0=
|
||||||
|
git.milar.in/milarin/ds v0.0.2/go.mod h1:HJK7QERcRvV9j7xzEocrKUtW+1q4JB1Ly4Bj54chfwI=
|
||||||
|
git.milar.in/milarin/gmath v0.0.3 h1:ii6rKNItS55O/wtIFhD1cTN2BMwDZjTBmiOocKURvxM=
|
||||||
|
git.milar.in/milarin/gmath v0.0.3/go.mod h1:HDLftG5RLpiNGKiIWh+O2G1PYkNzyLDADO8Cd/1abiE=
|
||||||
|
git.milar.in/milarin/slices v0.0.7 h1:s+e8W+pATa2NrAtniruUoNfjpmlTVQgyKu4ttfkE1cU=
|
||||||
|
git.milar.in/milarin/slices v0.0.7/go.mod h1:qMhdtMnfWswc1rHpwgNw33lB84aNEkdBn5BDiYA+G3k=
|
223
lexer.go
Normal file
223
lexer.go
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
package lexer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"git.milar.in/milarin/bufr"
|
||||||
|
"git.milar.in/milarin/slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Lexer struct {
|
||||||
|
src *bufr.Reader
|
||||||
|
Indent string
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(r io.Reader) *Lexer {
|
||||||
|
return &Lexer{
|
||||||
|
src: bufr.New(r),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Lexer) Pos() bufr.Position {
|
||||||
|
index, line, column := t.src.Pos()
|
||||||
|
return bufr.Position{Index: index, Line: line, Column: column}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Lexer) Next() (*Token, error) {
|
||||||
|
rn, err := t.src.Rune()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rn2, err := t.src.Rune()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := t.src.UnreadRunes(2); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if rn == '\t' || (rn == ' ' && rn2 == ' ') {
|
||||||
|
return t.parseIndent()
|
||||||
|
} else if rn == '#' {
|
||||||
|
return t.parseComment()
|
||||||
|
} else if slices.Contains(Separators, rn) {
|
||||||
|
return t.parseSeparator()
|
||||||
|
} else if slices.Contains(StringSeparators, rn) {
|
||||||
|
return t.parseStringLiteral()
|
||||||
|
} else if slices.Contains(Operators, rn) {
|
||||||
|
return t.parseOperator()
|
||||||
|
}
|
||||||
|
|
||||||
|
str, err := t.src.PeekStringUntil(bufr.OneOf(" \n"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if slices.Contains(Keywords, str) {
|
||||||
|
return t.parseKeyword()
|
||||||
|
}
|
||||||
|
|
||||||
|
return t.parseWord()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Lexer) parseComment() (*Token, error) {
|
||||||
|
start := t.Pos()
|
||||||
|
|
||||||
|
comment, err := t.src.StringUntil(bufr.IsNewLine)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := t.src.UnreadRune(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Token{
|
||||||
|
Type: TokenTypeComment,
|
||||||
|
Value: comment,
|
||||||
|
Start: start,
|
||||||
|
End: t.Pos(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Lexer) parseKeyword() (*Token, error) {
|
||||||
|
start := t.Pos()
|
||||||
|
|
||||||
|
keyword, err := t.src.StringUntil(bufr.IsWhitespace)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := t.src.UnreadRune(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Token{
|
||||||
|
Type: TokenTypeKeyword,
|
||||||
|
Value: keyword,
|
||||||
|
Start: start,
|
||||||
|
End: t.Pos(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Lexer) parseWord() (*Token, error) {
|
||||||
|
start := t.Pos()
|
||||||
|
|
||||||
|
word, err := t.src.StringUntil(bufr.IsWhitespace, bufr.Is('='))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := t.src.UnreadRune(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Token{
|
||||||
|
Type: TokenTypeWord,
|
||||||
|
Value: word,
|
||||||
|
Start: start,
|
||||||
|
End: t.Pos(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Lexer) parseSeparator() (*Token, error) {
|
||||||
|
start := t.Pos()
|
||||||
|
|
||||||
|
rn, err := t.src.Rune()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Token{
|
||||||
|
Type: TokenTypeSeparator,
|
||||||
|
Value: string(rn),
|
||||||
|
Start: start,
|
||||||
|
End: t.Pos(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Lexer) parseOperator() (*Token, error) {
|
||||||
|
start := t.Pos()
|
||||||
|
|
||||||
|
rn, err := t.src.Rune()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Token{
|
||||||
|
Type: TokenTypeOperator,
|
||||||
|
Value: string(rn),
|
||||||
|
Start: start,
|
||||||
|
End: t.Pos(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Lexer) parseStringLiteral() (*Token, error) {
|
||||||
|
start := t.Pos()
|
||||||
|
|
||||||
|
startRn, err := t.src.Rune()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
literal, err := t.src.StringUntil(bufr.Is(startRn))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
literal = EscSeqReplacer.Replace(literal)
|
||||||
|
|
||||||
|
return &Token{
|
||||||
|
Type: TokenTypeWord,
|
||||||
|
Value: literal,
|
||||||
|
Start: start,
|
||||||
|
End: t.Pos(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Lexer) parseIndent() (*Token, error) {
|
||||||
|
start := t.Pos()
|
||||||
|
|
||||||
|
// no indentation set yet
|
||||||
|
if t.Indent == "" {
|
||||||
|
str, err := t.src.StringWhile(bufr.OneOf("\t "))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := t.src.UnreadRune(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
//fmt.Printf("indentation set to '%s'\n", EscSeqReplacer.Replace(str))
|
||||||
|
t.Indent = str
|
||||||
|
|
||||||
|
return &Token{
|
||||||
|
Type: TokenTypeIndent,
|
||||||
|
Value: str,
|
||||||
|
Start: start,
|
||||||
|
End: t.Pos(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rn := range t.Indent {
|
||||||
|
ok, err := t.src.ExpectRune(bufr.Is(rn))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("mixed indentation styles at (%d:%d)", start.Line, start.Column)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Token{
|
||||||
|
Type: TokenTypeIndent,
|
||||||
|
Value: t.Indent,
|
||||||
|
Start: start,
|
||||||
|
End: t.Pos(),
|
||||||
|
}, nil
|
||||||
|
}
|
51
token.go
Normal file
51
token.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package lexer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.milar.in/milarin/bufr"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Token struct {
|
||||||
|
Type TokenType
|
||||||
|
Value string
|
||||||
|
Start bufr.Position
|
||||||
|
End bufr.Position
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Token) String() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"type: %s | value: '%s' | start: (%d:%d) | end: (%d:%d)",
|
||||||
|
t.Type, EscSeqPrintReplacer.Replace(t.Value), t.Start.Line, t.Start.Column, t.End.Line, t.End.Column,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type TokenType uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
TokenTypeComment TokenType = iota
|
||||||
|
TokenTypeKeyword
|
||||||
|
TokenTypeIndent
|
||||||
|
TokenTypeSeparator
|
||||||
|
TokenTypeWord
|
||||||
|
TokenTypeOperator
|
||||||
|
)
|
||||||
|
|
||||||
|
func (tt TokenType) String() string {
|
||||||
|
switch tt {
|
||||||
|
case TokenTypeComment:
|
||||||
|
return "comment"
|
||||||
|
case TokenTypeKeyword:
|
||||||
|
return "keyword"
|
||||||
|
case TokenTypeIndent:
|
||||||
|
return "indent"
|
||||||
|
case TokenTypeSeparator:
|
||||||
|
return "separator"
|
||||||
|
case TokenTypeWord:
|
||||||
|
return "word"
|
||||||
|
case TokenTypeOperator:
|
||||||
|
return "operator"
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("invalid token type: %d", tt))
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user