Skip to content

Commit

Permalink
Keeping line comments on same line. (#80)
Browse files Browse the repository at this point in the history
* Keeping line comments on same line. adding failing test first

* trim trailing whitespaces on comments

* add HadNewLine() to lexer so we can know if the comment (and other) are on same line as previous token or not

* And use the lexer to remember previous new line and print accordingly with new sameLine() check in ast - tests passing

* handle comments on same line as the open brace from a new block
  • Loading branch information
ldemailly authored Jul 29, 2024
1 parent 724b4a2 commit d4321a2
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 20 deletions.
21 changes: 18 additions & 3 deletions ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,16 +97,30 @@ type Statements struct {
Statements []Node
}

func sameLine(node Node) bool {
switch n := node.(type) { //nolint:exahustive // we may add more later
case *Comment:
return n.SameLine
default:
return false
}
}

func (p Statements) PrettyPrint(ps *PrintState) *PrintState {
oldExpressionLevel := ps.ExpressionLevel
if ps.IndentLevel > 0 {
ps.Println("{")
ps.Print("{") // first statement might be a comment on same line.
}
ps.IndentLevel++
ps.ExpressionLevel = 0
for i, s := range p.Statements {
if i > 0 {
ps.Println()
if i > 0 || ps.IndentLevel > 1 {
if sameLine(s) {
_, _ = ps.Out.Write([]byte{' '})
ps.IndentationDone = true
} else {
ps.Println()
}
}
s.PrettyPrint(ps)
}
Expand All @@ -130,6 +144,7 @@ func (i Identifier) PrettyPrint(out *PrintState) *PrintState {

type Comment struct {
Base
SameLine bool
}

func (c Comment) PrettyPrint(out *PrintState) *PrintState {
Expand Down
17 changes: 15 additions & 2 deletions lexer/lexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type Lexer struct {
pos int
lineMode bool
hadWhitespace bool
hadNewline bool
}

// Mode with input expected the be complete (multiline/file).
Expand Down Expand Up @@ -88,10 +89,22 @@ func (l *Lexer) HadWhitespace() bool {
return l.hadWhitespace
}

func (l *Lexer) HadNewline() bool {
return l.hadNewline
}

func (l *Lexer) skipWhitespace() {
l.hadWhitespace = false
l.hadNewline = false
// while whitespace, read next char
for isWhiteSpace(l.peekChar()) {
for {
ch := l.peekChar()
if !isWhiteSpace(ch) {
break
}
if ch == '\n' {
l.hadNewline = true
}
l.hadWhitespace = true
l.pos++
}
Expand Down Expand Up @@ -152,7 +165,7 @@ func (l *Lexer) readLineComment() string {
for notEOL(l.peekChar()) {
l.pos++
}
return string(l.input[pos:l.pos])
return strings.TrimSpace(string(l.input[pos:l.pos]))
}

func (l *Lexer) readNumber(ch byte) (token.Type, string) {
Expand Down
12 changes: 11 additions & 1 deletion lexer/lexer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,8 @@ j--
}

func TestNextTokenEOLMode(t *testing.T) {
input := `if .5 { x ( `
input := `if .5 { x (
`
l := NewLineMode(input)
tests := []struct {
expectedType token.Type
Expand Down Expand Up @@ -213,5 +214,14 @@ func TestNextTokenEOLMode(t *testing.T) {
t.Errorf("tests[%d] - expected whitespace", i)
}
}
if i == len(tests)-1 {
if !l.HadNewline() {
t.Errorf("last test (%d) - expected newline", i)
}
} else {
if l.HadNewline() {
t.Errorf("tests[%d] - didn't expect newline", i)
}
}
}
}
4 changes: 4 additions & 0 deletions parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ type Parser struct {
curToken *token.Token
peekToken *token.Token

prevNewline bool

errors []string
continuationNeeded bool

Expand Down Expand Up @@ -129,6 +131,7 @@ func (p *Parser) Errors() []string {
}

func (p *Parser) nextToken() {
p.prevNewline = p.l.HadNewline()
p.prevToken = p.curToken
p.curToken = p.peekToken
p.peekToken = p.l.NextToken()
Expand Down Expand Up @@ -165,6 +168,7 @@ func (p *Parser) parseStringLiteral() ast.Node {
func (p *Parser) parseComment() ast.Node {
r := &ast.Comment{}
r.Token = p.curToken
r.SameLine = !p.prevNewline
return r
}

Expand Down
46 changes: 46 additions & 0 deletions parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,3 +209,49 @@ func Test_OperatorPrecedenceParsing(t *testing.T) {
}
}
}

func TestFormat(t *testing.T) {
tests := []struct {
input string
expected string
}{
{
"a=((1+2)*3)",
"a = (1 + 2) * 3",
},
{
" // a comment ", // Should trim right whitespaces (but not ones between // and the comment)
"// a comment",
},
{
" a = 1+2 // interesting comment about a\nb = 23",
"a = 1 + 2 // interesting comment about a\nb = 23",
},
{
" a = 1+2 // interesting comment about a\n// and one for below:\nb=23",
"a = 1 + 2 // interesting comment about a\n// and one for below:\nb = 23",
},
{
`fact=func(n) { // function example
log("called fact ", n) // log output
}`,
"fact = func(n) { // function example\n\tlog(\"called fact \", n) // log output\n}",
},
}
for _, tt := range tests {
l := lexer.New(tt.input)
p := parser.New(l)
program := p.ParseProgram()
checkParserErrors(t, tt.input, p)
actual := program.PrettyPrint(ast.NewPrintState()).String()
last := actual[len(actual)-1]
if actual[len(actual)-1] != '\n' {
t.Errorf("expecting newline at end of program output, not found, got %q", last)
} else {
actual = actual[:len(actual)-1] // remove the last newline
}
if actual != tt.expected {
t.Errorf("---expected---\n%s\n---actual---\n%s\n---", tt.expected, actual)
}
}
}
6 changes: 3 additions & 3 deletions repl/repl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Factorial of 5 is 120` + " \n120\n" // there is an extra space before \n that vs

func TestEvalString50(t *testing.T) {
s := `
fact=func(n) { // function
fact=func(n) { // function
if (n<=1) {
return 1
}
Expand All @@ -44,8 +44,8 @@ fact(50.)`
t.Errorf("EvalString() got %v\n---\n%s\n---want---\n%s\n---", errs, got, expected)
}
// This tests that expression nesting is reset in function call list (ie formatted to `fact(n-1)` instead of `fact((n-1))`)
expected = `fact = func(n) {
// function
// and indirectly the handling of comments on same line as first statement in block.
expected = `fact = func(n) { // function
if n <= 1 {
return 1
}
Expand Down
18 changes: 7 additions & 11 deletions wasm/grol_wasm.html
Original file line number Diff line number Diff line change
Expand Up @@ -102,22 +102,18 @@

<div>
<label for="input">Edit the sample/Enter your GROL code here:</label>
<textarea id="input" rows="16" cols="80">
<textarea id="input" rows="12" cols="80">
print("Outputting a smiley: 😀\n")
// function example:
fact=func(n) {
// log output
log("called fact ", n)
// parenthesis are optional
fact=func(n) { // function example
log("called fact ", n) // log output
// parenthesis are optional:
if (n<=1) {
return 1
}
// recursion
n*fact(n-1)
n*fact(n-1) // recursion
}
// heterogeneous array
a=[fact(5), "abc", 76-3]
// maps also can have any key,value types
a=[fact(5), "abc", 76-3] // heterogeneous array
// maps also can have any key,value types:
m={"str key": a, 42: "str val"}</textarea>
</div>
<div>
Expand Down

0 comments on commit d4321a2

Please sign in to comment.