-
Notifications
You must be signed in to change notification settings - Fork 0
Add BNF support package for bidirectional DS/Dsp syntax conversion #68
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
2017c4f
4829014
ed2e54e
b44b667
a0078b2
1d53c6a
a7267c9
d857bd4
944b137
4d9e30a
3deffca
8c73c76
2bc8ab8
0648afc
65b4e79
3b671b3
3890577
eb8eb52
b180301
80e2b46
29c49f4
d145b09
6de97da
733e393
dea927d
9d00ffa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| grammar Ds; | ||
|
|
||
| rule_pool | ||
| : NEWLINE* (rule (NEWLINE+ rule)*)? NEWLINE* EOF | ||
| ; | ||
|
|
||
| rule | ||
| : (term NEWLINE+)* RULE NEWLINE term | ||
| ; | ||
|
|
||
| term | ||
| : SYMBOL # symbol | ||
| | '(subscript' term* ')' # subscript | ||
| | '(function' term* ')' # function | ||
| | '(unary' SYMBOL term ')' # unary | ||
| | '(binary' SYMBOL term term ')' # binary | ||
| ; | ||
|
|
||
| RULE | ||
| : '--' '-'* | ||
| ; | ||
|
|
||
| WHITESPACE | ||
| : [ \t]+ -> skip | ||
| ; | ||
|
|
||
| COMMENT | ||
| : '//' ~[\r\n]* -> skip | ||
| ; | ||
|
|
||
| NEWLINE | ||
| : [\r\n] | ||
| ; | ||
|
|
||
| SYMBOL | ||
| : ~[ \t\r\n,()[\]]+ | ||
| ; | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,48 @@ | ||||||
| grammar Dsp; | ||||||
|
|
||||||
| rule_pool | ||||||
| : NEWLINE* (rule (NEWLINE+ rule)*)? NEWLINE* EOF | ||||||
| ; | ||||||
|
|
||||||
| rule | ||||||
| : term | ||||||
| | (term (',' term)*)? '->' term | ||||||
|
||||||
| | (term (',' term)*)? '->' term | |
| | term (',' term)* '->' term |
Copilot
AI
Dec 9, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Dsp grammar rule on line 9 allows an empty premise list with the optional (term (',' term)*)? before '->'. This means -> conclusion is valid syntax. However, the ParseVisitor doesn't handle this case - when result is empty after visiting terms, result.pop() on line 22 will fail with an IndexError. The visitor should check for empty premises.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| # BNF Support Package for DS | ||
|
|
||
| This package provides bidirectional conversion between two syntax formats for the DS deductive system: | ||
|
|
||
| - **Ds**: The lisp-like syntax currently used in DS | ||
| - **Dsp**: A traditional readable syntax with infix operators |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| * | ||
| !__init__.py |
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,88 @@ | ||||||||||||||||
| __all__ = ["parse", "unparse"] | ||||||||||||||||
|
|
||||||||||||||||
| from antlr4 import InputStream, CommonTokenStream | ||||||||||||||||
| from .DspLexer import DspLexer | ||||||||||||||||
| from .DspParser import DspParser | ||||||||||||||||
| from .DspVisitor import DspVisitor | ||||||||||||||||
| from .DsLexer import DsLexer | ||||||||||||||||
| from .DsParser import DsParser | ||||||||||||||||
| from .DsVisitor import DsVisitor | ||||||||||||||||
|
|
||||||||||||||||
|
|
||||||||||||||||
| class ParseVisitor(DspVisitor): | ||||||||||||||||
| def visitRule_pool(self, ctx): | ||||||||||||||||
| return "\n\n".join(self.visit(r) for r in ctx.rule_()) | ||||||||||||||||
|
|
||||||||||||||||
| def visitRule(self, ctx): | ||||||||||||||||
| result = [self.visit(t) for t in ctx.term()] | ||||||||||||||||
| if len(result) == 1: | ||||||||||||||||
| return f"----\n{result[0]}" | ||||||||||||||||
| else: | ||||||||||||||||
| conclusion = result.pop() | ||||||||||||||||
| length = max(len(premise) for premise in result) | ||||||||||||||||
| result.append("-" * max(length, 4)) | ||||||||||||||||
| result.append(conclusion) | ||||||||||||||||
| return "\n".join(result) | ||||||||||||||||
|
|
||||||||||||||||
| def visitSymbol(self, ctx): | ||||||||||||||||
| return ctx.SYMBOL().getText() | ||||||||||||||||
|
|
||||||||||||||||
| def visitParentheses(self, ctx): | ||||||||||||||||
| return self.visit(ctx.term()) | ||||||||||||||||
|
|
||||||||||||||||
| def visitSubscript(self, ctx): | ||||||||||||||||
| return f"(subscript {' '.join(self.visit(t) for t in ctx.term())})" | ||||||||||||||||
|
|
||||||||||||||||
| def visitFunction(self, ctx): | ||||||||||||||||
| return f"(function {' '.join(self.visit(t) for t in ctx.term())})" | ||||||||||||||||
|
|
||||||||||||||||
| def visitUnary(self, ctx): | ||||||||||||||||
| return f"(unary {ctx.getChild(0).getText()} {self.visit(ctx.term())})" | ||||||||||||||||
|
|
||||||||||||||||
| def visitBinary(self, ctx): | ||||||||||||||||
| return f"(binary {ctx.getChild(1).getText()} {self.visit(ctx.term(0))} {self.visit(ctx.term(1))})" | ||||||||||||||||
|
|
||||||||||||||||
|
|
||||||||||||||||
| class UnparseVisitor(DsVisitor): | ||||||||||||||||
| def visitRule_pool(self, ctx): | ||||||||||||||||
| return "\n".join(self.visit(r) for r in ctx.rule_()) | ||||||||||||||||
|
|
||||||||||||||||
| def visitRule(self, ctx): | ||||||||||||||||
| result = [self.visit(t) for t in ctx.term()] | ||||||||||||||||
| conclusion = result.pop() | ||||||||||||||||
| return ", ".join(result) + " -> " + conclusion | ||||||||||||||||
|
Comment on lines
+52
to
+53
|
||||||||||||||||
| conclusion = result.pop() | |
| return ", ".join(result) + " -> " + conclusion | |
| if len(result) == 1: | |
| return result[0] | |
| else: | |
| conclusion = result.pop() | |
| return ", ".join(result) + " -> " + conclusion |
Copilot
AI
Dec 9, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The parameter name is input which shadows the built-in Python function input(). While this works, it's considered poor practice. Consider renaming to input_str, text, or source to avoid shadowing built-ins.
Copilot
AI
Dec 9, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The parameter name is input which shadows the built-in Python function input(). While this works, it's considered poor practice. Consider renaming to input_str, text, or source to avoid shadowing built-ins.
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,2 @@ | ||||||
| * | ||||||
| !index.js | ||||||
|
||||||
| !index.js | |
| !index.mjs |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,122 @@ | ||||||||||||||||||||
| import { InputStream, CommonTokenStream } from "antlr4"; | ||||||||||||||||||||
| import DspLexer from "./DspLexer.js"; | ||||||||||||||||||||
| import DspParser from "./DspParser.js"; | ||||||||||||||||||||
| import DspVisitor from "./DspVisitor.js"; | ||||||||||||||||||||
| import DsLexer from "./DsLexer.js"; | ||||||||||||||||||||
| import DsParser from "./DsParser.js"; | ||||||||||||||||||||
| import DsVisitor from "./DsVisitor.js"; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| class ParseVisitor extends DspVisitor { | ||||||||||||||||||||
| visitRule_pool(ctx) { | ||||||||||||||||||||
| return ctx | ||||||||||||||||||||
| .rule_() | ||||||||||||||||||||
| .map((r) => this.visit(r)) | ||||||||||||||||||||
| .join("\n\n"); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| visitRule(ctx) { | ||||||||||||||||||||
| const result = ctx.term().map((t) => this.visit(t)); | ||||||||||||||||||||
| if (result.length === 1) { | ||||||||||||||||||||
| return `----\n${result[0]}`; | ||||||||||||||||||||
| } else { | ||||||||||||||||||||
| const conclusion = result.pop(); | ||||||||||||||||||||
| const length = Math.max(...result.map((premise) => premise.length)); | ||||||||||||||||||||
| result.push("-".repeat(Math.max(length, 4))); | ||||||||||||||||||||
| result.push(conclusion); | ||||||||||||||||||||
| return result.join("\n"); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| visitSymbol(ctx) { | ||||||||||||||||||||
| return ctx.SYMBOL().getText(); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| visitParentheses(ctx) { | ||||||||||||||||||||
| return this.visit(ctx.term()); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| visitSubscript(ctx) { | ||||||||||||||||||||
| return `(subscript ${ctx | ||||||||||||||||||||
| .term() | ||||||||||||||||||||
| .map((t) => this.visit(t)) | ||||||||||||||||||||
| .join(" ")})`; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| visitFunction(ctx) { | ||||||||||||||||||||
| return `(function ${ctx | ||||||||||||||||||||
| .term() | ||||||||||||||||||||
| .map((t) => this.visit(t)) | ||||||||||||||||||||
| .join(" ")})`; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| visitUnary(ctx) { | ||||||||||||||||||||
| return `(unary ${ctx.getChild(0).getText()} ${this.visit(ctx.term())})`; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| visitBinary(ctx) { | ||||||||||||||||||||
| return `(binary ${ctx.getChild(1).getText()} ${this.visit(ctx.term(0))} ${this.visit(ctx.term(1))})`; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| class UnparseVisitor extends DsVisitor { | ||||||||||||||||||||
| visitRule_pool(ctx) { | ||||||||||||||||||||
| return ctx | ||||||||||||||||||||
| .rule_() | ||||||||||||||||||||
| .map((r) => this.visit(r)) | ||||||||||||||||||||
| .join("\n"); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| visitRule(ctx) { | ||||||||||||||||||||
| const result = ctx.term().map((t) => this.visit(t)); | ||||||||||||||||||||
| const conclusion = result.pop(); | ||||||||||||||||||||
| return result.join(", ") + " -> " + conclusion; | ||||||||||||||||||||
|
Comment on lines
+71
to
+72
|
||||||||||||||||||||
| const conclusion = result.pop(); | |
| return result.join(", ") + " -> " + conclusion; | |
| if (result.length === 1) { | |
| // Only a conclusion, no premises | |
| return "----\n" + result[0]; | |
| } else { | |
| const conclusion = result.pop(); | |
| return result.join(", ") + " -> " + conclusion; | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The SYMBOL token definition in both grammars includes
[and]in the exclusion set, but the Ds grammar doesn't use these characters. While this doesn't cause issues, it's inconsistent with the actual syntax being parsed. The Dsp grammar needs this exclusion because it uses brackets for subscripts, but Ds uses s-expressions only.