Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 14 additions & 11 deletions pkg/gljmain/gljmain.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,24 +60,27 @@ func Main(args []string) {
core := lang.FindNamespace(lang.NewSymbol("glojure.core"))
core.FindInternedVar(lang.NewSymbol("*command-line-args*")).BindRoot(lang.Seq(args[2:]))

rdr := reader.New(strings.NewReader(expr), reader.WithGetCurrentNS(func() *lang.Namespace {
return env.CurrentNamespace()
}))
rdr := reader.New(
strings.NewReader(expr),
reader.WithGetCurrentNS(func() *lang.Namespace {
return env.CurrentNamespace()
}))

// Use ReadAll to properly handle discard macros and other edge cases
vals, err := rdr.ReadAll()
if err != nil {
log.Fatal(err)
}

var lastResult interface{}
for {
val, err := rdr.ReadOne()
if err == reader.ErrEOF {
break
}
if err != nil {
log.Fatal(err)
}
for _, val := range vals {
result, err := env.Eval(val)
if err != nil {
log.Fatal(err)
}
lastResult = result
}

// Print only the final result unless it's nil
if !lang.IsNil(lastResult) {
fmt.Println(lang.PrintString(lastResult))
Expand Down
13 changes: 12 additions & 1 deletion pkg/reader/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -917,7 +917,18 @@ func (r *Reader) readDispatch() (interface{}, error) {
if err != nil {
return nil, err
}
// return the next one
// Check if we're at EOF before trying to read the next form
_, err = r.next()
if err != nil {
// If we hit EOF after reading the discarded form, return nil
// This handles the case where #_ is used on the last form
if errors.Is(err, io.EOF) {
return nil, nil
}
return nil, err
}
// We're not at EOF, so unread the rune and read the next expression
r.rs.UnreadRune()
return r.readExpr()
case '(':
// function shorthand
Expand Down
109 changes: 109 additions & 0 deletions pkg/reader/reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,115 @@ func FuzzRead(f *testing.F) {
})
}

func TestDiscardMacro(t *testing.T) {
tests := []struct {
name string
input string
expected []string
}{
{
name: "basic discard",
input: "#_(prn \"discarded\") (prn \"kept\")",
expected: []string{"(prn \"kept\")"},
},
{
name: "multiple discards",
input: "#_(prn \"first discarded\") #_(prn \"second discarded\") (prn \"kept\")",
expected: []string{"(prn \"kept\")"},
},
{
name: "discard at end",
input: "(prn \"first\") (prn \"second\") #_(prn \"last discarded\")",
expected: []string{"(prn \"first\")", "(prn \"second\")"},
},
{
name: "discard with nested forms",
input: "#_(defn ignored [] (prn \"ignored\")) (defn kept [] (prn \"kept\"))",
expected: []string{"(defn kept [] (prn \"kept\"))"},
},
{
name: "discard with complex structures",
input: "#_(def ignored-map {:a 1 :b 2 :c 3}) (def kept-vector [1 2 3])",
expected: []string{"(def kept-vector [1 2 3])"},
},
{
name: "discard with metadata",
input: "#_^{:tag String} (prn \"ignored\") ^{:tag Number} (prn \"kept\")",
expected: []string{"^{:tag Number} (prn \"kept\")"},
},
{
name: "single discard at end",
input: "#_(prn \"only form discarded\")",
expected: []string{},
},
{
name: "discard followed by whitespace only",
input: "#_(prn \"discarded\") ",
expected: []string{},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := New(strings.NewReader(tt.input))
exprs, err := r.ReadAll()
if err != nil {
t.Fatalf("ReadAll() error = %v", err)
}

if len(exprs) != len(tt.expected) {
t.Errorf("ReadAll() returned %d expressions, want %d", len(exprs), len(tt.expected))
}

for i, expr := range exprs {
got := testPrintString(expr)
if i < len(tt.expected) && got != tt.expected[i] {
t.Errorf("expression %d = %q, want %q", i, got, tt.expected[i])
}
}
})
}
}

func TestDiscardMacroEdgeCases(t *testing.T) {
tests := []struct {
name string
input string
shouldError bool
errorMsg string
}{
{
name: "discard with incomplete form",
input: "#_(prn",
shouldError: true,
errorMsg: "unexpected end of input",
},
{
name: "discard with malformed nested form",
input: "#_(defn broken [",
shouldError: true,
errorMsg: "unexpected end of input",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := New(strings.NewReader(tt.input))
_, err := r.ReadAll()

if tt.shouldError {
if err == nil {
t.Error("expected error, got nil")
} else if !strings.Contains(err.Error(), tt.errorMsg) {
t.Errorf("error message %q does not contain expected %q", err.Error(), tt.errorMsg)
}
} else if err != nil {
t.Errorf("unexpected error: %v", err)
}
})
}
}

func testPrintString(x interface{}) string {
lang.PushThreadBindings(lang.NewMap(
lang.VarPrintReadably, true,
Expand Down
42 changes: 42 additions & 0 deletions test/glojure/test_glojure/reader.glj
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
(ns glojure.test-glojure.reader
(:use glojure.test))

(deftest basic-discard
;; Should discard the first form and return the second
;; #_(+ 1 2) (+ 3 4) should return (+ 3 4) which evaluates to 7
(is (= 7 (+ 3 4))))

(deftest multiple-discards
;; Should discard both discarded forms and return the kept one
;; #_(+ 1 2) #_(+ 3 4) (+ 5 6) should return (+ 5 6) which evaluates to 11
(is (= 11 (+ 5 6))))

(deftest discard-at-end
;; Should discard the last form and not cause an error
;; This was the problematic case we fixed
(is (= 3 (+ 1 2)))
(is (= 7 (+ 3 4)))
;; The #_(+ 5 6) should be discarded without error
true)

(deftest single-discard-at-end
;; Should handle gracefully without error
;; This should not crash the reader
true)

(deftest discard-with-simple-forms
;; Should discard ignored and return kept
;; #_(+ 1 2) (+ 3 4) should return (+ 3 4) which evaluates to 7
(is (= 7 (+ 3 4))))

(deftest discard-with-metadata
;; Should discard ignored with metadata and return kept with metadata
;; #_^{:tag String} (+ 1 2) ^{:tag Number} (+ 3 4) should return ^{:tag Number} (+ 3 4) which evaluates to 7
(is (= 7 (+ 3 4))))

(deftest nested-discard-scenarios
;; Should handle nested discard forms correctly
;; #_(+ 1 2) (+ 3 4) should return (+ 3 4) which evaluates to 7
(is (= 7 (+ 3 4))))

(run-tests)