Skip to content

Commit b7f40d8

Browse files
committed
Allow indentation in REPL
1 parent 9a37f57 commit b7f40d8

File tree

11 files changed

+87
-71
lines changed

11 files changed

+87
-71
lines changed

examples/test.pf

Lines changed: 3 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,6 @@
1-
def
1+
def
22

3-
foo(i int) :
4-
"foo"
3+
qux :
4+
SQL --- foo bar
55

66

7-
zort(t ... any?) :
8-
foo +
9-
10-
troz(i, j, k int) :
11-
"troz"
12-
13-
var
14-
15-
T tuple = ()
16-
U tuple = 1, 2, 3
17-
V tuple = tuple(1)
18-
W tuple = tuple("zort")
19-
X tuple = "a", "b", "c"
20-
21-
x = set(1, 2, 3, 4, 5, 6)
22-
y = set(2, 3, 4)
23-
z = set(6, 5, 4)
24-
25-
a = set("a", "b", "c", "d", "e", "f")
26-
b = set("a", "b", "c")
27-
c = set("c", "f", "d")
28-
29-
const
30-
31-
Z tuple = "a", "b", "c"

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ require (
3131
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15 // indirect
3232
github.com/aws/aws-sdk-go-v2/service/s3 v1.58.2 // indirect
3333
github.com/aws/smithy-go v1.20.3 // indirect
34+
github.com/chzyer/readline v1.5.1 // indirect
3435
github.com/coreos/go-oidc/v3 v3.5.0 // indirect
3536
github.com/danieljoos/wincred v1.2.2 // indirect
3637
github.com/databricks/databricks-sql-go v1.5.7 // indirect

go.sum

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ github.com/aws/aws-sdk-go-v2/service/s3 v1.58.2 h1:sZXIzO38GZOU+O0C+INqbH7C2yALw
4747
github.com/aws/aws-sdk-go-v2/service/s3 v1.58.2/go.mod h1:Lcxzg5rojyVPU/0eFwLtcyTaek/6Mtic5B1gJo7e/zE=
4848
github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE=
4949
github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
50+
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
51+
github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI=
52+
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
53+
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
5054
github.com/coreos/go-oidc/v3 v3.5.0 h1:VxKtbccHZxs8juq7RdJntSqtXFtde9YpNpGn0yqgEHw=
5155
github.com/coreos/go-oidc/v3 v3.5.0/go.mod h1:ecXRtV4romGPeO6ieExAsUK9cb/3fp9hXNz1tlv8PIM=
5256
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
@@ -279,6 +283,7 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
279283
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
280284
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
281285
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
286+
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
282287
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
283288
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
284289
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

source/hub/hub.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1494,6 +1494,7 @@ var (
14941494
GOOD_BULLET = Green(" ▪ ")
14951495
BROKEN = Red(" ✖ ")
14961496
PROMPT = "→ "
1497+
INDENT_PROMPT = " "
14971498
ERROR = "$Error$"
14981499
RT_ERROR = "$Error$"
14991500
HUB_ERROR = "$Hub error$"

source/hub/repl.go

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ package hub
22

33
import (
44
"io"
5+
"regexp"
56
"strings"
67

7-
"github.com/lmorg/readline"
8+
"github.com/chzyer/readline"
89
)
910

1011
func StartHub(hub *Hub, in io.Reader, out io.Writer) {
11-
rline := readline.NewInstance()
12+
var rline *readline.Instance
13+
colonOrEmdash, _ := regexp.Compile(`.*[\w\s]*(:|---)[\s]*$`)
1214
for {
1315

1416
// The hub's CurrentForm setting allows it to ask for information from the user instead of
@@ -19,23 +21,15 @@ func StartHub(hub *Hub, in io.Reader, out io.Writer) {
1921

2022
for {
2123
queryString := hub.CurrentForm.Fields[len(hub.CurrentForm.Result)]
22-
// A * at the beginning of the query string indicates that the answer should be
23-
// masked.
24-
if queryString[0] == '*' {
25-
queryString = queryString[1:]
26-
rline.PasswordMask = '▪'
27-
}
28-
2924
// The readln utility doesn't like multiline prompts, so we must kludge a little.
3025
pos := strings.LastIndex(queryString, "\n")
3126
if pos == -1 {
32-
rline.SetPrompt(queryString + ": ")
27+
rline, _ = readline.New(queryString + ": ")
3328
} else {
3429
hub.WriteString(queryString[:pos+1])
35-
rline.SetPrompt(queryString[pos+1:] + ": ")
30+
rline, _ = readline.New(queryString[pos+1:] + ": ")
3631
}
3732
line, _ := rline.Readline()
38-
rline.PasswordMask = 0
3933
hub.CurrentForm.Result[hub.CurrentForm.Fields[len(hub.CurrentForm.Result)]] = line
4034
if len(hub.CurrentForm.Result) == len(hub.CurrentForm.Fields) {
4135
hub.CurrentForm.Call(hub.CurrentForm)
@@ -45,25 +39,52 @@ func StartHub(hub *Hub, in io.Reader, out io.Writer) {
4539
continue
4640
}
4741

48-
rline.SetPrompt(makePrompt(hub))
49-
line, _ := rline.Readline()
50-
51-
line = strings.TrimSpace(line)
42+
ws := ""
43+
input := ""
44+
c := 0
45+
for {
46+
rline, _ = readline.New(makePrompt(hub, ws != ""))
47+
line, _ := rline.ReadlineWithDefault(ws)
48+
c++
49+
input = input + line + "\n"
50+
ws = ""
51+
for _, c := range line {
52+
if c == ' ' || c == '\t' {
53+
ws = ws + string(c)
54+
} else {
55+
break
56+
}
57+
}
58+
if colonOrEmdash.Match([]byte(line)) {
59+
ws = ws + "\t"
60+
}
61+
if ws == "" {
62+
break
63+
}
64+
}
65+
input = strings.TrimSpace(input)
5266

53-
_, quitCharm := hub.Do(line, hub.Username, hub.Password, hub.currentServiceName())
54-
if quitCharm {
67+
_, quit := hub.Do(input, hub.Username, hub.Password, hub.currentServiceName())
68+
if quit {
5569
break
5670
}
5771
}
5872
}
5973

60-
func makePrompt(hub *Hub) string {
74+
func makePrompt(hub *Hub, indented bool) string {
75+
symbol := PROMPT
76+
left := hub.currentServiceName()
77+
if indented {
78+
symbol = INDENT_PROMPT
79+
left = strings.Repeat(" ", len(left))
80+
}
6181
if hub.currentServiceName() == "" {
62-
return PROMPT
82+
return symbol
6383
}
64-
promptText := hub.currentServiceName() + " " + PROMPT
84+
promptText := left + " " + symbol
6585
if hub.CurrentServiceIsBroken() {
6686
promptText = Red(promptText)
6787
}
6888
return promptText
6989
}
90+

source/initializer/initializer.go

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -334,10 +334,7 @@ func (iz *initializer) MakeParserAndTokenizedProgram() {
334334
)
335335

336336
tok = iz.p.TokenizedCode.NextToken() // note that we've already removed leading newlines.
337-
if settings.SHOW_RELEXER && !(settings.IGNORE_BOILERPLATE && settings.ThingsToIgnore.Contains(tok.Source)) {
338-
println(tok.Type, tok.Literal)
339-
}
340-
337+
341338
if tok.Type == token.EOF { // An empty file should still initiate a service, but one with no data.
342339
return
343340
}
@@ -352,9 +349,6 @@ func (iz *initializer) MakeParserAndTokenizedProgram() {
352349
line := token.NewCodeChunk()
353350

354351
for tok = iz.p.TokenizedCode.NextToken(); tok.Type != token.EOF; tok = iz.p.TokenizedCode.NextToken() {
355-
if settings.SHOW_RELEXER && !(settings.IGNORE_BOILERPLATE && settings.ThingsToIgnore.Contains(tok.Source)) {
356-
println(tok.Type, tok.Literal)
357-
}
358352
if token.TokenTypeIsHeadword(tok.Type) {
359353
if tok.Literal == "import" {
360354
iz.Throw("init/import/first", &tok)

source/initializer/rsc-pf/worldlite.pf

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ Input = struct(prompt string)
88
Output = struct()
99
Terminal = struct()
1010
HTML = snippet
11-
12-
Foo = enum FOO, BAR
11+
SQL = snippet
1312

1413
cmd
1514

source/lexer/lexer.go

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,15 @@ func (l *Lexer) NextToken() token.Token {
6464

6565
switch l.ch {
6666
case 0:
67-
return l.NewToken(token.EOF, "EOF")
67+
level := l.whitespaceStack.Find("")
68+
if level > 0 {
69+
for i := 0; i < level; i++ {
70+
l.whitespaceStack.Pop()
71+
}
72+
return l.MakeToken(token.END, fmt.Sprint(level))
73+
} else {
74+
return l.NewToken(token.EOF, "EOF")
75+
}
6876
case '\n':
6977
return l.NewToken(token.NEWLINE, ";")
7078
case '\\':
@@ -198,7 +206,7 @@ func (l *Lexer) NextToken() token.Token {
198206
l.readChar()
199207
return l.NewToken(tType, text)
200208
case token.EMDASH:
201-
return l.MakeToken(tType, l.readSnippet())
209+
return l.MakeToken(tType, strings.TrimSpace(l.readSnippet()))
202210
default:
203211
return l.NewToken(tType, lit)
204212
}
@@ -393,8 +401,10 @@ func (l *Lexer) readSnippet() string {
393401
}
394402
// There are two possibilities. Either we found a non-whitespace character, and the whole snippet is on the same line as the
395403
// `---` token, or we found a newline and the snipped is an indent on the succeeding lines. Just like with a colon.
396-
if l.peekChar() == '\n' { // --- then we have to mess with whitespace.
397-
l.readChar()
404+
if l.peekChar() == '\n' || l.peekChar() == '\r' { // --- then we have to mess with whitespace.
405+
for l.peekChar() == '\n' || l.peekChar() == '\r' {
406+
l.readChar()
407+
}
398408
langIndent := ""
399409
stackTop, ok := l.whitespaceStack.HeadValue()
400410
if !ok {
@@ -417,8 +427,8 @@ func (l *Lexer) readSnippet() string {
417427
return result
418428
}
419429
}
420-
if strings.HasPrefix(stackTop, currentWhitespace) || currentWhitespace == "\n" { // Then we've unindented. Dobby is free!
421-
if currentWhitespace != "\n" {
430+
if strings.HasPrefix(stackTop, currentWhitespace) || currentWhitespace == "\n" || currentWhitespace == "\r" || currentWhitespace == string(0) { // Then we've unindented. Dobby is free!
431+
if currentWhitespace != "\n" && currentWhitespace != "\r" && currentWhitespace == string(0) {
422432
l.snippetWhitespace = currentWhitespace
423433
}
424434
return result
@@ -427,18 +437,19 @@ func (l *Lexer) readSnippet() string {
427437
l.Throw("lex/emdash/indent/c", l.NewToken(token.ILLEGAL, "bad emdash"))
428438
return result
429439
}
430-
for l.peekChar() != '\n' && l.peekChar() != 0 {
440+
for l.peekChar() != '\n' && l.peekChar() != '\r' && l.peekChar() != 0 {
431441
l.readChar()
432442
result = result + string(l.ch)
433443
}
434444
if l.peekChar() == 0 {
445+
l.readChar()
435446
return result
436447
}
437448
l.readChar()
438449
result = result + "\n"
439450
}
440451
} else {
441-
for l.peekChar() != '\n' && l.peekChar() != 0 {
452+
for l.peekChar() != '\n' && l.peekChar() != '\r' && l.peekChar() != 0 {
442453
l.readChar()
443454
result = result + string(l.ch)
444455
}
@@ -621,7 +632,7 @@ func isHexDigit(ch rune) bool {
621632

622633
func isProtectedPunctuationOrWhitespace(ch rune) bool {
623634
return ch == '(' || ch == ')' || ch == '[' || ch == ']' || ch == '{' || ch == '}' || ch == ' ' || ch == ',' ||
624-
ch == ':' || ch == ';' || ch == '\t' || ch == '\n' || ch == 0
635+
ch == ':' || ch == ';' || ch == '\t' || ch == '\n' || ch == '\r' || ch == 0
625636
}
626637

627638
func isSymbol(ch rune) bool {
@@ -646,7 +657,7 @@ func (l *Lexer) NewToken(tokenType token.TokenType, st string) token.Token {
646657

647658
func (l *Lexer) MakeToken(tokenType token.TokenType, st string) token.Token {
648659
if settings.SHOW_LEXER && !(settings.IGNORE_BOILERPLATE && settings.ThingsToIgnore.Contains(l.source)) {
649-
fmt.Println(tokenType, st, l.line, l.tstart)
660+
fmt.Println(tokenType, st)
650661
}
651662
return token.Token{Type: tokenType, Literal: st, Source: l.source, Line: l.line, ChStart: l.tstart, ChEnd: l.char}
652663
}

source/lexer/relexer.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,8 +222,12 @@ func (rl *Relexer) NextSemanticToken() token.Token {
222222
return rl.burnToken()
223223
}
224224
rl.curTok.Literal = strconv.Itoa(n - 1)
225-
return token.Token{Type: token.NEWLINE, Literal: ";", Line: rl.curTok.Line,
226-
ChStart: 0, ChEnd: 0, Source: rl.curTok.Source}
225+
if rl.nexTok.Type != token.EOF {
226+
return token.Token{Type: token.NEWLINE, Literal: ";", Line: rl.curTok.Line,
227+
ChStart: 0, ChEnd: 0, Source: rl.curTok.Source}
228+
} else {
229+
return rl.nexTok
230+
}
227231
default:
228232
rl.nestingLevel = rl.nestingLevel - 1
229233
rl.curTok.Literal = strconv.Itoa(n - 1)

source/parser/parser.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import (
1010
"github.com/tim-hardcastle/Pipefish/source/dtypes"
1111
"github.com/tim-hardcastle/Pipefish/source/err"
1212
"github.com/tim-hardcastle/Pipefish/source/lexer"
13+
"github.com/tim-hardcastle/Pipefish/source/settings"
14+
"github.com/tim-hardcastle/Pipefish/source/text"
1315
"github.com/tim-hardcastle/Pipefish/source/token"
1416
"github.com/tim-hardcastle/Pipefish/source/values"
1517
)
@@ -978,6 +980,9 @@ func (p *Parser) NextToken() {
978980
}
979981

980982
func (p *Parser) SafeNextToken() {
983+
if settings.SHOW_RELEXER && !(settings.IGNORE_BOILERPLATE && settings.ThingsToIgnore.Contains(p.curToken.Source)) {
984+
println(text.PURPLE + p.curToken.Type, p.curToken.Literal + text.RESET)
985+
}
981986
p.curToken = p.peekToken
982987
p.peekToken = p.TokenizedCode.NextToken()
983988
}

0 commit comments

Comments
 (0)