[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[elpa] externals/parser-generator 75323b10e5 81/82: Merge branch 'featur
From: |
Christian Johansson |
Subject: |
[elpa] externals/parser-generator 75323b10e5 81/82: Merge branch 'feature/llk-parser' |
Date: |
Thu, 12 May 2022 13:28:20 -0400 (EDT) |
branch: externals/parser-generator
commit 75323b10e549448f3debbe2a0bbabcaf8f3848de
Merge: bf7229332f 5be162966b
Author: Christian Johansson <christian@cvj.se>
Commit: Christian Johansson <christian@cvj.se>
Merge branch 'feature/llk-parser'
---
Makefile | 12 +-
README.md | 2 +-
TODO.md | 8 +-
docs/Syntax-Analysis.md | 5 +-
docs/Syntax-Analysis/LL1.md | 174 ++++++
docs/Syntax-Analysis/LLk.md | 178 ++++++
parser-generator-ll-export.el | 816 +++++++++++++++++++++++++
parser-generator-ll.el | 1004 +++++++++++++++++++++++++++++++
parser-generator-lr.el | 4 +-
parser-generator.el | 276 ++++++++-
test/parser-generator-ll-export-test.el | 176 ++++++
test/parser-generator-ll-test.el | 943 +++++++++++++++++++++++++++++
test/parser-generator-test.el | 128 +++-
13 files changed, 3676 insertions(+), 50 deletions(-)
diff --git a/Makefile b/Makefile
index 4965a61a20..86a23ae36f 100644
--- a/Makefile
+++ b/Makefile
@@ -4,7 +4,7 @@ ifdef emacs
endif
EMACS_CMD := $(EMACS) -Q -batch -L . -L test/
-EL := parser-generator.el parser-generator-lex-analyzer.el
parser-generator-lr.el parser-generator-lr-export.el
test/parser-generator-test.el test/parser-generator-lex-analyzer-test.el
test/parser-generator-lr-export-test.el test/parser-generator-lr-test.el
+EL := parser-generator.el parser-generator-lex-analyzer.el
parser-generator-ll.el parser-generator-ll-export.el parser-generator-lr.el
parser-generator-lr-export.el test/parser-generator-test.el
test/parser-generator-lex-analyzer-test.el
test/parser-generator-lr-export-test.el test/parser-generator-ll-test.el
test/parser-generator-ll-export-test.el test/parser-generator-lr-test.el
ELC := $(EL:.el=.elc)
.PHONY: clean
@@ -31,5 +31,13 @@ test-lr:
test-lr-export:
$(EMACS_CMD) -l test/parser-generator-lr-export-test.el -f
"parser-generator-lr-export-test"
+.PHONY: test-ll
+test-ll:
+ $(EMACS_CMD) -l test/parser-generator-ll-test.el -f
"parser-generator-ll-test"
+
+.PHONY: test-ll-export
+test-ll-export:
+ $(EMACS_CMD) -l test/parser-generator-ll-export-test.el -f
"parser-generator-ll-export-test"
+
.PHONY: tests
-tests: test test-lex-analyzer test-lr test-lr-export
+tests: test test-lex-analyzer test-lr test-lr-export test-ll test-ll-export
diff --git a/README.md b/README.md
index 5432ef4407..777cb89246 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
The idea of this plugin is to provide functions for various kinds of
context-free grammar parser generations with support for
syntax-directed-translations (SDT) and semantic actions (SA) and the
possibility of exporting parsers and translators (as generated stand-alone
elisp code) to enable Emacs plugin-agnostic usage. This project is also about
implementing algorithms described in the book `The Theory of Parsing,
Translation and Compiling (Volume 1)` by `Alfred V. Aho and Jeffrey D. Ull [...]
-At the moment it is possible to generate canonical LR(k) parsers using this
library for complex languages like PHP 8.0.
+At the moment it is possible to generate canonical LR(k) parsers using this
library for complex languages like PHP 8.1.
## Lexical Analysis
diff --git a/TODO.md b/TODO.md
index 54a3e26d94..952c7d226e 100644
--- a/TODO.md
+++ b/TODO.md
@@ -2,17 +2,17 @@
## Main
-Functions (with validations) to set global variables
+Functions (with validations) to set global variables:
-* parser-generator--global-attributes
* parser-generator--context-sensitive-attributes
+* parser-generator--global-attributes
* parser-generator--global-declaration
## LR-Parser
-Functions (with validations) to set global variables
+Functions (with validations) to set global variables:
-* parser-generator-lr--global-precedence-attributes
* parser-generator-lr--context-sensitive-precedence-attribute
+* parser-generator-lr--global-precedence-attributes
[Back to start](../../)
diff --git a/docs/Syntax-Analysis.md b/docs/Syntax-Analysis.md
index 6f639d7b1f..c391d346f0 100644
--- a/docs/Syntax-Analysis.md
+++ b/docs/Syntax-Analysis.md
@@ -11,7 +11,8 @@ We use push down transducer (PDT) based algorithms.
## Without Backtracking
-* LL(k) *WIP*
+* [LL(1)](Syntax-Analysis/LL1.md)
+* [LL(k)](Syntax-Analysis/LLk.md)
* [LR(k)](Syntax-Analysis/LRk.md)
* [LR(0)](Syntax-Analysis/LR0.md)
* Formal Shift-Reduce Parsing Algorithms *WIP*
@@ -156,7 +157,7 @@ Calculate the first look-ahead number of terminals of the
sentential-form `S`, e
### E-FREE-FIRST(S)
-Calculate the e-free-first look-ahead number of terminals of sentential-form
`S`, if you have multiple symbols the e-free-first will only affect the first
symbol, the rest will be treated via first-function (above). Example:
+Calculate the e-free-first look-ahead number of terminals of sentential-form
`S`, if you have multiple symbols the e-free-first will only affect the first
symbol, the rest will be treated via the first-function (above). Example:
``` emacs-lisp
(require 'parser-generator)
diff --git a/docs/Syntax-Analysis/LL1.md b/docs/Syntax-Analysis/LL1.md
new file mode 100644
index 0000000000..0d0e5d9db2
--- /dev/null
+++ b/docs/Syntax-Analysis/LL1.md
@@ -0,0 +1,174 @@
+# LL(1) Parser
+
+LL(1) parser is a Left-to-right, Leftmost derivation with look-ahead number k
= 1.
+
+This library contains functions to parse, translate, validate grammars.
+
+## Parse
+
+Perform a left-parse of input-stream.
+
+```emacs-lisp
+(require 'parser-generator-ll)
+(require 'ert)
+
+(parser-generator-set-eof-identifier '$)
+(parser-generator-set-e-identifier 'e)
+(parser-generator-set-look-ahead-number 1)
+(parser-generator-set-grammar
+ '(
+ (S A)
+ (a b)
+ (
+ (S
+ (a A S (lambda(a b) (format "alfa %s %s" (nth 1 a) (nth 2 a))))
+ (b (lambda(a b) "beta"))
+ )
+ (A
+ (a (lambda(a b) "delta"))
+ (b S A (lambda(a b) (format "gamma %s %s" (nth 1 a) (nth 2 a))))
+ )
+ )
+ S
+ )
+ )
+(parser-generator-process-grammar)
+(parser-generator-ll-generate-table)
+(setq
+ parser-generator-lex-analyzer--function
+ (lambda (index)
+ (let* ((string '((a 1 . 2) (b 2 . 3) (b 3 . 4) (a 4 . 5) (b 5 . 6)))
+ (string-length (length string))
+ (max-index index)
+ (tokens))
+ (while (and
+ (< (1- index) string-length)
+ (< (1- index) max-index))
+ (push (nth (1- index) string) tokens)
+ (setq index (1+ index)))
+ (nreverse tokens))))
+(setq
+ parser-generator-lex-analyzer--get-function
+ (lambda (token)
+ (car token)))
+(should
+ (equal
+ "beta"
+ (parser-generator-ll-translate)))
+(message "Passed translation test 3")
+```
+
+## Translate
+
+Each production RHS can optionally contain a lambda-expression that will be
called if specified when stack is reduced:
+
+```emacs-lisp
+(require 'parser-generator-ll)
+(require 'ert)
+
+(parser-generator-set-eof-identifier '$)
+(parser-generator-set-e-identifier 'e)
+(parser-generator-set-look-ahead-number 2)
+(parser-generator-set-grammar
+ '(
+ (S A)
+ (a b)
+ (
+ (S
+ (a A a a (lambda(a b) (format "alfa %s laval" (nth 1 a))))
+ (b A b a (lambda(a b) (format "delta %s laval" (nth 1 a))))
+ )
+ (A
+ (b (lambda(a b) "sven"))
+ (e (lambda(a b) "ingrid"))
+ )
+ )
+ S
+ )
+ )
+(parser-generator-process-grammar)
+(parser-generator-ll-generate-table)
+(setq
+ parser-generator-lex-analyzer--function
+ (lambda (index)
+ (let* ((string '((b 1 . 2) (b 2 . 3) (a 3 . 4)))
+ (string-length (length string))
+ (max-index index)
+ (tokens))
+ (while (and
+ (< (1- index) string-length)
+ (< (1- index) max-index))
+ (push (nth (1- index) string) tokens)
+ (setq index (1+ index)))
+ (nreverse tokens))))
+(setq
+ parser-generator-lex-analyzer--get-function
+ (lambda (token)
+ (car token)))
+(should
+ (equal
+ "delta ingrid laval"
+ (parser-generator-ll-translate)))
+(message "Passed translation test 1")
+```
+
+## Export
+
+```emacs-lisp
+(require 'parser-generator-ll)
+(require 'ert)
+
+(parser-generator-set-eof-identifier '$)
+(parser-generator-set-e-identifier 'e)
+(parser-generator-set-look-ahead-number 1)
+(parser-generator-set-grammar
+ '(
+ (S A)
+ (a b)
+ (
+ (S (a A S) b)
+ (A a (b S A))
+ )
+ S
+ )
+ )
+(parser-generator-process-grammar)
+(parser-generator-ll-generate-table)
+(setq
+ parser-generator-lex-analyzer--function
+ (lambda (index)
+ (let* ((string '((a 1 . 2) (b 2 . 3) (b 3 . 4) (a 4 . 5) (b 5 . 6)))
+ (string-length (length string))
+ (max-index index)
+ (tokens))
+ (while (and
+ (< (1- index) string-length)
+ (< (1- index) max-index))
+ (push (nth (1- index) string) tokens)
+ (setq index (1+ index)))
+ (nreverse tokens))))
+(setq
+ parser-generator-lex-analyzer--get-function
+ (lambda (token)
+ (car token)))
+(let ((export (parser-generator-ll-export-to-elisp "ba3")))
+ (with-temp-buffer
+ (insert export)
+ (eval-buffer)
+ (should
+ (equal
+ t
+ (fboundp 'ba3-parse)))
+ (should
+ (equal
+ t
+ (fboundp 'ba3-translate)))
+ (when (fboundp 'ba3-parse)
+ (should
+ (equal
+ '(0 3 1 2 1)
+ (ba3-parse))))))
+(message "Passed exported test for example 5.5 p. 340")
+```
+
+[Back to syntax analysis](../Syntax-Analysis.md)
diff --git a/docs/Syntax-Analysis/LLk.md b/docs/Syntax-Analysis/LLk.md
new file mode 100644
index 0000000000..bf7130b452
--- /dev/null
+++ b/docs/Syntax-Analysis/LLk.md
@@ -0,0 +1,178 @@
+# LL(k) Parser
+
+LL(k) parser is a Left-to-right, Leftmost derivation with look-ahead number k
> 1.
+
+This library contains functions to parse, translate, validate grammars.
+
+## Parse
+
+Perform a left-parse of input-stream.
+
+```emacs-lisp
+(require 'parser-generator-ll)
+(require 'ert)
+
+(parser-generator-set-eof-identifier '$)
+(parser-generator-set-e-identifier 'e)
+(parser-generator-set-look-ahead-number 2)
+(parser-generator-set-grammar
+ '(
+ (S A)
+ (a b)
+ (
+ (S (a A a a) (b A b a))
+ (A b e)
+ )
+ S
+ )
+ )
+(parser-generator-process-grammar)
+(parser-generator-ll-generate-table)
+(setq
+ parser-generator-lex-analyzer--function
+ (lambda (index)
+ (let* ((string '((b 1 . 2) (b 2 . 3) (a 3 . 4)))
+ (string-length (length string))
+ (max-index index)
+ (tokens))
+ (while (and
+ (< (1- index) string-length)
+ (< (1- index) max-index))
+ (push (nth (1- index) string) tokens)
+ (setq index (1+ index)))
+ (nreverse tokens))))
+(setq
+ parser-generator-lex-analyzer--get-function
+ (lambda (token)
+ (car token)))
+
+(should
+ (equal
+ '(1 3) ;; Example is indexed from 1 so that is why they have '(2 4)
+ (parser-generator-ll-parse)))
+(message "Passed example 5.16 p. 352")
+```
+
+## Translate
+
+Each production RHS can optionally contain a lambda-expression that will be
called if specified when stack is reduced:
+
+```emacs-lisp
+ (parser-generator-set-eof-identifier '$)
+ (parser-generator-set-e-identifier 'e)
+ (parser-generator-set-look-ahead-number 2)
+ (parser-generator-set-grammar
+ '(
+ (S A)
+ (a b)
+ (
+ (S
+ (a A a a (lambda(a b) (format "alfa %s laval" (nth 1 a))))
+ (b A b a (lambda(a b) (format "delta %s laval" (nth 1 a))))
+ )
+ (A
+ (b (lambda(a b) "sven"))
+ (e (lambda(a b) "ingrid"))
+ )
+ )
+ S
+ )
+ )
+ (parser-generator-process-grammar)
+ (parser-generator-ll-generate-table)
+ (setq
+ parser-generator-lex-analyzer--function
+ (lambda (index)
+ (let* ((string '((b 1 . 2) (b 2 . 3) (a 3 . 4)))
+ (string-length (length string))
+ (max-index index)
+ (tokens))
+ (while (and
+ (< (1- index) string-length)
+ (< (1- index) max-index))
+ (push (nth (1- index) string) tokens)
+ (setq index (1+ index)))
+ (nreverse tokens))))
+ (setq
+ parser-generator-lex-analyzer--get-function
+ (lambda (token)
+ (car token)))
+ (should
+ (equal
+ "delta ingrid laval"
+ (parser-generator-ll-translate)))
+ (message "Passed translation test 1")
+```
+
+## Export
+
+```emacs-lisp
+(require 'parser-generator-ll)
+(require 'ert)
+
+(parser-generator-set-eof-identifier '$)
+(parser-generator-set-e-identifier 'e)
+(parser-generator-set-look-ahead-number 2)
+(parser-generator-set-grammar
+ '(
+ (S A)
+ (a b)
+ (
+ (S
+ (a A a a (lambda(a b) (format "alfa %s laval" (nth 1 a))))
+ (b A b a (lambda(a b) (format "delta %s laval" (nth 1 a))))
+ )
+ (A
+ (b (lambda(a b) "sven"))
+ (e (lambda(a b) "ingrid"))
+ )
+ )
+ S
+ )
+ )
+(parser-generator-process-grammar)
+(parser-generator-ll-generate-table)
+(setq
+ parser-generator-lex-analyzer--function
+ (lambda (index)
+ (let* ((string '((b 1 . 2) (b 2 . 3) (a 3 . 4)))
+ (string-length (length string))
+ (max-index index)
+ (tokens))
+ (while (and
+ (< (1- index) string-length)
+ (< (1- index) max-index))
+ (push (nth (1- index) string) tokens)
+ (setq index (1+ index)))
+ (nreverse tokens))))
+(setq
+ parser-generator-lex-analyzer--get-function
+ (lambda (token)
+ (car token)))
+(let ((export (parser-generator-ll-export-to-elisp "ba")))
+ (with-temp-buffer
+ (insert export)
+ (eval-buffer)
+ (should
+ (equal
+ t
+ (fboundp 'ba-parse)))
+ (should
+ (equal
+ t
+ (fboundp 'ba-translate)))
+ (when (fboundp 'ba-parse)
+ (should
+ (equal
+ '(1 3)
+ (ba-parse))))
+ (when (fboundp 'ba-translate)
+ (should
+ (equal
+ "delta ingrid laval"
+ (ba-translate))))))
+(message "Passed exported test for example 5.16 p. 352")
+```
+
+
+[Back to syntax analysis](../Syntax-Analysis.md)
diff --git a/parser-generator-ll-export.el b/parser-generator-ll-export.el
new file mode 100644
index 0000000000..80fef9cac2
--- /dev/null
+++ b/parser-generator-ll-export.el
@@ -0,0 +1,816 @@
+;;; parser-generator-ll-export.el --- Export LL(k) Parser -*- lexical-binding:
t -*-
+
+;; Copyright (C) 2020-2022 Free Software Foundation, Inc.
+
+
+;;; Commentary:
+
+
+;;; Code:
+
+
+(require 'parser-generator-ll)
+
+(defun parser-generator-ll-export-to-elisp (namespace &optional header
copyright)
+ "Export parser with NAMESPACE and a optional HEADER and COPYRIGHT."
+ (message "\n;; Starting generation of elips..\n")
+
+ ;; Make sure all requisites are defined
+ (unless parser-generator-ll--table
+ (error "Missing generated table!"))
+ (unless parser-generator--table-productions-number-reverse
+ (error "Table for reverse production-numbers is undefined!"))
+ (unless parser-generator--table-look-aheads-p
+ (error "Table for valid look-aheads is undefined!"))
+ (unless parser-generator--look-ahead-number
+ (error "Missing a look-ahead number!"))
+ (unless parser-generator--e-identifier
+ (error "Missing definition for e-identifier!"))
+ (unless parser-generator--eof-identifier
+ (error "Missing definition for EOF-identifier!"))
+ (unless parser-generator--table-non-terminal-p
+ (error "Table for non-terminals is undefined!"))
+ (unless parser-generator--table-terminal-p
+ (error "Table for terminals is undefined!"))
+ (unless parser-generator--table-translations
+ (error "Table for translations by production-number is undefined!"))
+ (unless parser-generator-lex-analyzer--get-function
+ (error "Missing lex-analyzer get function!"))
+ (unless parser-generator-lex-analyzer--function
+ (error "Missing lex-analyzer function!"))
+
+ (let ((code))
+ (with-temp-buffer
+ (goto-char (point-min))
+
+ ;; Header
+ (insert
+ (format
+ ";;; %s.el --- Exported Emacs Parser Generator -*- lexical-binding: t
-*-\n\n"
+ namespace))
+
+ ;; Optional copyright
+ (when copyright
+ (insert copyright))
+
+ (insert ";;; Commentary:\n\n\n;;; Code:\n\n")
+
+ ;; Optional header
+ (when header
+ (insert header))
+
+ (insert "\n;;; Variables:\n\n\n")
+
+ ;; Grammar start
+ (insert
+ (format
+ "(defvar\n %s--grammar-start\n %s\n \"The start of grammar.\")\n\n"
+ namespace
+ (if (symbolp (parser-generator--get-grammar-start))
+ (format "'%s" (parser-generator--get-grammar-start))
+ (format "\"%s\"" (parser-generator--get-grammar-start)))))
+
+ ;; Generated table
+ (insert
+ (format
+ "(defvar\n %s--table\n %S\n \"The generated table.\")\n\n"
+ namespace
+ parser-generator-ll--table))
+
+ ;; Table production-number
+ (insert
+ (format
+ "(defvar\n %s--table-productions-number-reverse\n %S\n \"The
hash-table indexed by production-number and value is production.\")\n\n"
+ namespace
+ parser-generator--table-productions-number-reverse))
+
+ ;; Table terminals
+ (insert
+ (format
+ "(defvar\n %s--table-terminal-p\n %S\n \"The hash-table of valid
terminals.\")\n\n"
+ namespace
+ parser-generator--table-terminal-p))
+
+ ;; Table non-terminals
+ (insert
+ (format
+ "(defvar\n %s--table-non-terminal-p\n %S\n \"The hash-table of
valid non-terminals.\")\n\n"
+ namespace
+ parser-generator--table-non-terminal-p))
+
+ ;; Table translations
+ (insert
+ (format
+ "(defvar\n %s--table-translations\n %S\n \"The hash-table of
translations.\")\n\n"
+ namespace
+ parser-generator--table-translations))
+
+ ;; E-identifier
+ (insert
+ (format
+ "(defvar\n %s--e-identifier\n '%S\n \"The e-identifier.\")\n\n"
+ namespace
+ parser-generator--e-identifier))
+
+ ;; EOF-identifier
+ (insert
+ (format
+ "(defvar\n %s--eof-identifier\n '%S\n \"The
end-of-file-identifier.\")\n\n"
+ namespace
+ parser-generator--eof-identifier))
+
+ ;; Look-ahead number
+ (insert
+ (format
+ "(defvar\n %s--look-ahead-number\n %S\n \"The look-ahead
number.\")\n\n"
+ namespace
+ parser-generator--look-ahead-number))
+
+ (insert "\n;;; Local Variables:\n\n")
+
+ ;; Index
+ (insert
+ (format
+ "(defvar-local\n %s-lex-analyzer--index\n 0\n \"The current index
of the lex-analyzer.\")\n\n"
+ namespace))
+
+ ;; Move to index flag
+ (insert
+ (format
+ "(defvar-local\n %s-lex-analyzer--move-to-index-flag\n nil\n
\"Non-nil means move index to value.\")\n\n"
+ namespace))
+
+ (insert "\n;;; Variable Functions:\n\n")
+
+ ;; Lex-Analyzer Get Function
+ (insert
+ (format
+ "(defvar\n %s-lex-analyzer--get-function\n (lambda %S %S)\n \"The
lex-analyzer get function.\")\n\n"
+ namespace
+ (nth 2 parser-generator-lex-analyzer--get-function)
+ (nth 3 parser-generator-lex-analyzer--get-function)))
+
+ ;; Lex-Analyzer Function
+ (insert
+ (format
+ "(defvar\n %s-lex-analyzer--function\n (lambda %S %S)\n \"The
lex-analyzer function.\")\n\n"
+ namespace
+ (nth 2 parser-generator-lex-analyzer--function)
+ (nth 3 parser-generator-lex-analyzer--function)))
+
+ ;; Lex-Analyzer Reset Function
+ (insert
+ (format
+ "(defvar\n %s-lex-analyzer--reset-function\n "
+ namespace))
+ (if parser-generator-lex-analyzer--reset-function
+ (insert
+ (format
+ "(lambda %S %S)\n"
+ (nth 2 parser-generator-lex-analyzer--reset-function)
+ (nth 3 parser-generator-lex-analyzer--reset-function)))
+ (insert "nil\n"))
+ (insert " \"The lex-analyzer reset function.\")\n\n")
+
+ (insert "\n;;; Functions:\n\n")
+
+ (insert "\n;;; Functions for Lex-Analyzer:\n\n")
+
+ ;; Lex-Analyzer Get Function
+ (insert
+ (format
+ "(defun
+ %s-lex-analyzer--get-function (token)
+ \"Get information about TOKEN.\"
+ (let ((meta-information))
+ (condition-case
+ error
+ (progn
+ (setq
+ meta-information
+ (funcall
+ %s-lex-analyzer--get-function
+ token)))"
+ namespace
+ namespace))
+ (insert "
+ (error (error
+ \"Lex-analyze failed to get token meta-data of %s, error: %s\"
+ token
+ (car (cdr error)))))
+ (unless meta-information
+ (error \"Could not find any token meta-information for: %s\" token))
+ meta-information))\n")
+
+ ;; Lex-Analyzer Reset Function
+ (insert
+ (format "
+(defun
+ %s-lex-analyzer--reset
+ ()
+ \"Reset Lex-Analyzer.\"
+ (setq
+ %s-lex-analyzer--index
+ 1)
+ (when
+ %s-lex-analyzer--reset-function
+ (funcall
+ %s-lex-analyzer--reset-function)))\n"
+ namespace
+ namespace
+ namespace
+ namespace))
+
+ ;; Lex-Analyzer Peek Next Look Ahead
+ (insert
+ (format "
+(defun
+ %s-lex-analyzer--peek-next-look-ahead
+ ()
+ \"Peek next look-ahead number of tokens via lex-analyzer.\"
+ (let ((look-ahead)
+ (look-ahead-length 0)
+ (index %s-lex-analyzer--index)
+ (k (max
+ 1
+ %s--look-ahead-number)))
+ (while (<
+ look-ahead-length
+ k)
+ (condition-case error
+ (progn
+ (setq-local
+ %s-lex-analyzer--move-to-index-flag
+ nil)
+ (let ((next-look-ahead
+ (funcall
+ %s-lex-analyzer--function
+ index)))
+ (if %s-lex-analyzer--move-to-index-flag
+ (setq
+ index
+ %s-lex-analyzer--move-to-index-flag)
+ (if next-look-ahead
+ (progn
+ (unless (listp (car next-look-ahead))
+ (setq next-look-ahead (list next-look-ahead)))
+ (dolist (next-look-ahead-item next-look-ahead)
+ (when (<
+ look-ahead-length
+ k)
+ (push next-look-ahead-item look-ahead)
+ (setq look-ahead-length (1+ look-ahead-length))
+ (setq index (cdr (cdr next-look-ahead-item))))))
+ (push (list %s--eof-identifier) look-ahead)
+ (setq look-ahead-length (1+ look-ahead-length))
+ (setq index (1+ index))))))"
+ namespace
+ namespace
+ namespace
+ namespace
+ namespace
+ namespace
+ namespace
+ namespace))
+ (insert "
+ (error
+ (error
+ \"Lex-analyze failed to peek next look-ahead at %s, error: %s\"
+ index
+ error))))
+ (nreverse look-ahead)))\n")
+
+ ;; Lex-Analyzer Pop Token
+ (insert
+ (format "
+(defun
+ %s-lex-analyzer--pop-token ()
+ \"Pop next token via lex-analyzer.\"
+ (let ((continue t)
+ (tokens))
+ (while continue
+ (condition-case error
+ (progn
+ (setq-local
+ %s-lex-analyzer--move-to-index-flag
+ nil)
+ (let ((token
+ (funcall
+ %s-lex-analyzer--function
+ %s-lex-analyzer--index)))
+ (if %s-lex-analyzer--move-to-index-flag
+ (progn
+ (setq-local
+ %s-lex-analyzer--index
+ %s-lex-analyzer--move-to-index-flag))
+ (when token
+ (unless (listp (car token))
+ (setq token (list token)))
+ (let ((first-token (car token)))
+ (setq
+ %s-lex-analyzer--index
+ (cdr (cdr first-token)))
+ (push
+ first-token
+ tokens)))
+ (setq
+ continue
+ nil))))"
+ namespace
+ namespace
+ namespace
+ namespace
+ namespace
+ namespace
+ namespace
+ namespace))
+ (insert "
+ (error (error
+ \"Lex-analyze failed to pop token at %s, error: %s\"")
+ (insert (format "
+ %s-lex-analyzer--index
+ (car (cdr error))))))
+ (nreverse tokens)))\n"
+ namespace))
+
+ (insert "\n\n;;; Functions for Syntax-Analyzer / Parser:\n\n");
+
+ ;; Get grammar production by number
+ (insert
+ (format "
+(defun
+ %s--get-grammar-production-by-number
+ (production-number)
+ \"If PRODUCTION-NUMBER exist, return it's production.\"
+ (gethash
+ production-number
+ %s--table-productions-number-reverse))\n"
+ namespace
+ namespace))
+
+ ;; Valid symbol p
+ (insert
+ (format "
+(defun
+ %s--valid-symbol-p
+ (symbol)
+ \"Return whether SYMBOL is valid or not.\"
+ (let ((is-valid t))
+ (unless (or
+ (%s--valid-e-p symbol)
+ (%s--valid-eof-p symbol)
+ (%s--valid-non-terminal-p symbol)
+ (%s--valid-terminal-p symbol))
+ (setq is-valid nil))
+ is-valid))\n"
+ namespace
+ namespace
+ namespace
+ namespace
+ namespace))
+
+ ;; Valid e-p
+ (insert
+ (format "
+(defun
+ %s--valid-e-p
+ (symbol)
+ \"Return whether SYMBOL is the e identifier or not.\"
+ (eq
+ symbol
+ %s--e-identifier))\n"
+ namespace
+ namespace))
+
+ ;; Valid EOF-p
+ (insert
+ (format "
+(defun
+ %s--valid-eof-p
+ (symbol)
+ \"Return whether SYMBOL is the EOF identifier or not.\"
+ (eq
+ symbol
+ %s--eof-identifier))\n"
+ namespace
+ namespace))
+
+ ;; Valid non-terminal-p
+ (insert
+ (format "
+(defun
+ %s--valid-non-terminal-p (symbol)
+ \"Return whether SYMBOL is a non-terminal in grammar or not.\"
+ (gethash
+ symbol
+ %s--table-non-terminal-p))\n"
+ namespace
+ namespace))
+
+ ;; Valid terminal-p
+ (insert
+ (format "
+(defun
+ %s--valid-terminal-p (symbol)
+ \"Return whether SYMBOL is a terminal in grammar or not.\"
+ (gethash
+ symbol
+ %s--table-terminal-p))\n"
+ namespace
+ namespace))
+
+ ;; Generate list of symbol
+ (insert
+ (format "
+(defun %s--generate-list-of-symbol (k symbol)
+ \"Generate list of K number of SYMBOL.\"
+ (let ((list-index 0)
+ (list))
+ (while (< list-index k)
+ (push symbol list)
+ (setq list-index (1+ list-index)))
+ list))
+"
+ namespace))
+
+ ;; Get grammar translation by number
+ (insert
+ (format "
+(defun
+ %s--get-grammar-translation-by-number
+ (production-number)
+ \"If translation for PRODUCTION-NUMBER exist, return it.\"
+ (gethash
+ production-number
+ %s--table-translations))\n"
+ namespace
+ namespace))
+
+ ;; Parse / translate function
+ (insert
+ (format "
+(defun %s--parse (&optional translate-p)
+ \"Parse input via lex-analyzer and return parse trail.\"
+ (let ((accept)
+ (stack
+ (if (> %s--look-ahead-number 1)
+ (list
+ (list
+ (list
+ %s--grammar-start)
+ (%s--generate-list-of-symbol
+ %s--look-ahead-number
+ %s--eof-identifier))
+ %s--eof-identifier)
+ (list
+ %s--grammar-start
+ %s--eof-identifier)))
+ (output)
+ (eof-look-ahead
+ (%s--generate-list-of-symbol
+ %s--look-ahead-number
+ %s--eof-identifier))
+ (e-reduction
+ (list %s--e-identifier))
+ (translation)
+ (translation-stack)
+ (translation-symbol-table
+ (make-hash-table :test 'equal))
+ (terminal-stack '()))
+ (%s-lex-analyzer--reset)
+ (while (not accept)
+ (let* ((state (car stack))
+ (state-action-table
+ (gethash
+ (format \"%%S\" state)
+ %s--table))
+ (look-ahead-list
+ (%s-lex-analyzer--peek-next-look-ahead))
+ (look-ahead))
+
+ (unless state-action-table
+ (signal
+ 'error
+ (list
+ (format
+ \"State action table lacks actions for state: '%%S'!\"
+ state)
+ state)))
+
+ (if look-ahead-list
+ (progn
+ (dolist (look-ahead-list-item look-ahead-list)
+ (push (car look-ahead-list-item) look-ahead))
+ (setq look-ahead (reverse look-ahead)))
+ (setq
+ look-ahead
+ eof-look-ahead))
+
+ (unless (gethash
+ (format \"%%S\" look-ahead)
+ state-action-table)
+ (let ((possible-look-aheads))
+ (maphash
+ (lambda (k _v) (push k possible-look-aheads))
+ state-action-table)
+ (signal
+ 'error
+ (list
+ (format
+ \"Invalid look-ahead '%%S' in state: '%%S', valid look-aheads:
'%%S'\"
+ look-ahead
+ state
+ possible-look-aheads)
+ look-ahead
+ state
+ possible-look-aheads))))
+
+ (let* ((action
+ (gethash
+ (format \"%%S\" look-ahead)
+ state-action-table))
+ (action-type action))
+ (when (listp action)
+ (setq action-type (car action)))
+ (cond
+
+ ((equal action-type 'pop)
+ (let ((popped-tokens
+ (%s-lex-analyzer--pop-token)))
+
+ ;; Is it time for SDT?
+ (when (and
+ translate-p
+ translation-stack
+ (string=
+ (car (car translation-stack))
+ (format \"%%S\" stack)))
+ (let* ((translation-item (pop translation-stack))
+ (partial-translation
+ (%s--perform-translation
+ (nth 1 translation-item)
+ translation-symbol-table
+ (reverse (pop terminal-stack)))))
+ (setq
+ translation
+ partial-translation)))
+
+ (pop stack)
+
+ (when translate-p
+ (let ((token-data)
+ (old-terminal-stack (car terminal-stack)))
+ (dolist (popped-token popped-tokens)
+ (push
+ popped-token
+ token-data))
+ (push
+ token-data
+ old-terminal-stack)
+ (setf
+ (car terminal-stack)
+ old-terminal-stack)))
+
+ ;; Is it time for SDT?
+ (when (and
+ translate-p
+ translation-stack
+ (string=
+ (car (car translation-stack))
+ (format \"%%S\" stack)))
+ (let* ((translation-item (pop translation-stack))
+ (partial-translation
+ (%s--perform-translation
+ (nth 1 translation-item)
+ translation-symbol-table
+ (reverse (pop terminal-stack)))))
+ (setq
+ translation
+ partial-translation)))
+
+ ))
+
+ ((equal action-type 'reduce)
+
+ ;; Is it time for SDT?
+ (when (and
+ translate-p
+ translation-stack
+ (string=
+ (car (car translation-stack))
+ (format \"%%S\" stack)))
+ (let* ((translation-item (pop translation-stack))
+ (partial-translation
+ (%s--perform-translation
+ (nth 1 translation-item)
+ translation-symbol-table
+ (reverse (pop terminal-stack)))))
+ (setq
+ translation
+ partial-translation)))
+
+ (pop stack)
+
+ ;; Is it time for SDT?
+ (when (and
+ translate-p
+ translation-stack
+ (string=
+ (car (car translation-stack))
+ (format \"%%S\" stack)))
+ (let* ((translation-item (pop translation-stack))
+ (partial-translation
+ (%s--perform-translation
+ (nth 1 translation-item)
+ translation-symbol-table
+ (reverse (pop terminal-stack)))))
+ (setq
+ translation
+ partial-translation)))
+
+ (when translate-p
+ (push
+ (list
+ (format \"%%S\" stack)
+ (nth 2 action))
+ translation-stack)
+ (push
+ '()
+ terminal-stack))
+
+ (unless (equal (nth 1 action) e-reduction)
+ (dolist (reduce-item (reverse (nth 1 action)))
+ (push reduce-item stack)))
+ (push
+ (nth 2 action)
+ output))
+
+ ((equal action-type 'accept)
+ (setq accept t))))))
+ (list
+ (reverse output)
+ translation)))
+
+(defun %s-parse ()
+ (let ((parse (%s--parse)))
+ (car parse)))
+
+(defun %s-translate ()
+ (let ((parse (%s--parse t)))
+ (car (cdr parse))))
+
+(defun %s--perform-translation (production-number symbol-table terminals)
+ \"Perform translation by PRODUCTION-NUMBER, with SYMBOL-TABLE and
TERMINALS.\"
+ (let* ((production
+ (%s--get-grammar-production-by-number
+ production-number))
+ (production-lhs
+ (car (nth 0 production)))
+ (production-rhs
+ (nth 1 production))
+ (translation)
+ (args-1)
+ (args-2))
+
+ ;; Collect arguments for translation
+ (let ((terminal-index 0))
+ (dolist (rhs-item production-rhs)
+ (cond
+
+ ((%s--valid-non-terminal-p
+ rhs-item)
+ (let* ((non-terminal-value-list
+ (gethash rhs-item symbol-table))
+ (non-terminal-value
+ (pop non-terminal-value-list)))
+ (push
+ (car non-terminal-value)
+ args-1)
+ (push
+ (car (cdr non-terminal-value))
+ args-2)
+ (puthash
+ rhs-item
+ non-terminal-value-list
+ symbol-table)))
+
+ ((%s--valid-terminal-p
+ rhs-item)
+ (push
+ (%s-lex-analyzer--get-function
+ (nth terminal-index terminals))
+ args-1)
+ (push
+ (nth terminal-index terminals)
+ args-2)
+ (setq
+ terminal-index
+ (1+ terminal-index))))))
+ (setq
+ args-1
+ (reverse args-1))
+ (setq
+ args-2
+ (reverse args-2))
+
+ (if (%s--get-grammar-translation-by-number
+ production-number)
+ (let ((partial-translation
+ (funcall
+ (%s--get-grammar-translation-by-number
+ production-number)
+ args-1
+ args-2))
+ (old-symbol-value
+ (gethash production-lhs symbol-table)))
+ (push
+ (list
+ partial-translation
+ args-2)
+ old-symbol-value)
+ (puthash
+ production-lhs
+ old-symbol-value
+ symbol-table)
+ (setq
+ translation
+ partial-translation))
+
+ ;; When no translation is specified just use popped contents as
translation
+ (let ((partial-translation
+ (list
+ args-1
+ args-2))
+ (old-symbol-value
+ (gethash production-lhs symbol-table)))
+ (push
+ partial-translation
+ old-symbol-value)
+ (puthash
+ production-lhs
+ old-symbol-value
+ symbol-table)
+ (setq
+ translation
+ (car partial-translation))))
+
+ translation))
+
+"
+ namespace
+ namespace
+ namespace
+ namespace
+ namespace
+ namespace
+ namespace
+ namespace
+ namespace
+ namespace
+ namespace
+ namespace
+ namespace
+ namespace
+ namespace
+ namespace
+ namespace
+ namespace
+ namespace
+ namespace
+ namespace
+ namespace
+ namespace
+ namespace
+ namespace
+ namespace
+ namespace
+ namespace
+ namespace
+ namespace
+ namespace
+ namespace
+ ))
+
+ ;; Footer
+ (insert
+ (format
+ "\n(provide '%s)"
+ namespace))
+
+ (insert
+ (format
+ "\n\n;;; %s.el ends here"
+ namespace))
+
+ (setq
+ code
+ (buffer-substring-no-properties
+ (point-min)
+ (point-max))))
+ (message "\n;; Completed generation of elips.\n")
+ code))
+
+
+(provide 'parser-generator-ll-export)
+
+;;; parser-generator-ll-export.el ends here
diff --git a/parser-generator-ll.el b/parser-generator-ll.el
new file mode 100644
index 0000000000..2696982d35
--- /dev/null
+++ b/parser-generator-ll.el
@@ -0,0 +1,1004 @@
+;;; parser-generator-ll.el --- LL(k) Parser Generator -*- lexical-binding: t
-*-
+
+;; Copyright (C) 2020-2022 Free Software Foundation, Inc.
+
+
+;;; Commentary:
+
+
+;;; Code:
+
+
+(require 'parser-generator)
+(require 'parser-generator-lex-analyzer)
+
+
+;;; Variables:
+
+
+(defvar
+ parser-generator-ll--table
+ nil
+ "Table for grammar.")
+
+
+;;; Functions
+
+
+(defun parser-generator-ll-generate-table ()
+ "Generate table for grammar."
+ (let ((list-parsing-table)
+ (hash-parsing-table (make-hash-table :test 'equal)))
+
+ (if (> parser-generator--look-ahead-number 1)
+ (progn
+ (message "\n;; Starting generation of LL(k) tables..\n")
+
+ (unless (parser-generator-ll--valid-grammar-k-gt-1-p)
+ (error "Invalid LL(k) grammar specified!"))
+
+ (setq
+ list-parsing-table
+ (parser-generator-ll--generate-action-table-k-gt-1
+ (parser-generator-ll--generate-goto-table))))
+
+ (message "\n;; Starting generation of LL(1) tables..\n")
+
+ (unless (parser-generator-ll--valid-grammar-k-eq-1-p)
+ (error "Invalid LL(1) grammar specified!"))
+
+ (setq
+ list-parsing-table
+ (parser-generator-ll--generate-action-table-k-eq-1
+ (parser-generator-ll--generate-goto-table))))
+
+ ;; Convert list-structure to hash-map
+ (dolist (state-list list-parsing-table)
+ (let ((state-key (nth 0 state-list))
+ (state-look-aheads (nth 1 state-list))
+ (state-hash-table (make-hash-table :test 'equal)))
+ (dolist (state-look-ahead-list state-look-aheads)
+ (let ((state-look-ahead-string (nth 0 state-look-ahead-list))
+ (state-look-ahead-action (nth 1 state-look-ahead-list)))
+ (if (equal state-look-ahead-action 'reduce)
+ (let ((state-look-ahead-reduction
+ (nth 2 state-look-ahead-list))
+ (state-look-ahead-production-number
+ (nth 3 state-look-ahead-list)))
+ (puthash
+ (format "%S" state-look-ahead-string)
+ (list
+ state-look-ahead-action
+ state-look-ahead-reduction
+ state-look-ahead-production-number)
+ state-hash-table))
+ (puthash
+ (format "%S" state-look-ahead-string)
+ state-look-ahead-action
+ state-hash-table))))
+ (puthash
+ (format "%S" state-key)
+ state-hash-table
+ hash-parsing-table)))
+ (setq
+ parser-generator-ll--table
+ hash-parsing-table)
+
+ (if (> parser-generator--look-ahead-number 1)
+ (message "\n;; Completed generation of LL(k) tables.\n")
+ (message "\n;; Completed generation of LL(1) tables.\n"))))
+
+(defun parser-generator-ll-parse ()
+ (let ((parse (parser-generator-ll--parse)))
+ (car parse)))
+
+(defun parser-generator-ll-translate ()
+ (let ((parse (parser-generator-ll--parse t)))
+ (car (cdr parse))))
+
+;; Generally described at .p 339
+(defun parser-generator-ll--parse (&optional translate-p)
+ "Parse input via lex-analyzer and return parse trail."
+ (let ((accept)
+ (stack
+ (if (> parser-generator--look-ahead-number 1)
+ (list
+ (list
+ (list
+ (parser-generator--get-grammar-start))
+ (parser-generator--generate-list-of-symbol
+ parser-generator--look-ahead-number
+ parser-generator--eof-identifier))
+ parser-generator--eof-identifier)
+ (list
+ (parser-generator--get-grammar-start)
+ parser-generator--eof-identifier)))
+ (output)
+ (eof-look-ahead
+ (parser-generator--generate-list-of-symbol
+ parser-generator--look-ahead-number
+ parser-generator--eof-identifier))
+ (e-reduction
+ (list parser-generator--e-identifier))
+ (translation)
+ (translation-stack)
+ (translation-symbol-table
+ (make-hash-table :test 'equal))
+ (terminal-stack '()))
+ (parser-generator-lex-analyzer--reset)
+ (while (not accept)
+ (let* ((state (car stack))
+ (state-action-table
+ (gethash
+ (format "%S" state)
+ parser-generator-ll--table))
+ (look-ahead-list
+ (parser-generator-lex-analyzer--peek-next-look-ahead))
+ (look-ahead))
+ (parser-generator--debug
+ (message "\nstack: %S" stack)
+ (message "translation-stack: %S" translation-stack)
+ (message "output: %S" output)
+ (message "state: %S" state)
+ (message "state-action-table: %S" state-action-table))
+
+ (unless state-action-table
+ (signal
+ 'error
+ (list
+ (format
+ "State action table lacks actions for state: '%S'!"
+ state)
+ state)))
+
+ (if look-ahead-list
+ (progn
+ (parser-generator--debug
+ (message "look-ahead-list: %S" look-ahead-list))
+ (dolist (look-ahead-list-item look-ahead-list)
+ (push (car look-ahead-list-item) look-ahead))
+ (setq look-ahead (reverse look-ahead)))
+ (setq
+ look-ahead
+ eof-look-ahead))
+
+ (parser-generator--debug
+ (message "look-ahead: %S" look-ahead))
+
+ (unless (gethash
+ (format "%S" look-ahead)
+ state-action-table)
+ (let ((possible-look-aheads))
+ (maphash
+ (lambda (k _v) (push k possible-look-aheads))
+ state-action-table)
+ (signal
+ 'error
+ (list
+ (format
+ "Invalid look-ahead '%S' in state: '%S', valid look-aheads:
'%S'"
+ look-ahead
+ state
+ possible-look-aheads)
+ look-ahead
+ state
+ possible-look-aheads))))
+
+ (let* ((action
+ (gethash
+ (format "%S" look-ahead)
+ state-action-table))
+ (action-type action))
+ (parser-generator--debug
+ (message "action: %S" action))
+ (when (listp action)
+ (setq action-type (car action)))
+ (parser-generator--debug
+ (message "action-type: %S" action-type))
+ (cond
+
+ ((equal action-type 'pop)
+ (parser-generator--debug
+ (message "popped: %S" look-ahead))
+ (let ((popped-tokens
+ (parser-generator-lex-analyzer--pop-token)))
+
+ ;; Is it time for SDT?
+ (when (and
+ translate-p
+ translation-stack
+ (string=
+ (car (car translation-stack))
+ (format "%S" stack)))
+ (let* ((translation-item (pop translation-stack))
+ (partial-translation
+ (parser-generator-ll--perform-translation
+ (nth 1 translation-item)
+ translation-symbol-table
+ (reverse (pop terminal-stack)))))
+ (setq
+ translation
+ partial-translation)))
+
+ (pop stack)
+
+ (when translate-p
+ (let ((token-data)
+ (old-terminal-stack (car terminal-stack)))
+ (dolist (popped-token popped-tokens)
+ (push
+ popped-token
+ token-data))
+ (push
+ token-data
+ old-terminal-stack)
+ (setf
+ (car terminal-stack)
+ old-terminal-stack)))
+
+ ;; Is it time for SDT?
+ (when (and
+ translate-p
+ translation-stack
+ (string=
+ (car (car translation-stack))
+ (format "%S" stack)))
+ (let* ((translation-item (pop translation-stack))
+ (partial-translation
+ (parser-generator-ll--perform-translation
+ (nth 1 translation-item)
+ translation-symbol-table
+ (reverse (pop terminal-stack)))))
+ (setq
+ translation
+ partial-translation)))
+
+ ))
+
+ ((equal action-type 'reduce)
+ (parser-generator--debug
+ (message "reduced: %S -> %S" state (nth 1 action)))
+
+ ;; Is it time for SDT?
+ (when (and
+ translate-p
+ translation-stack
+ (string=
+ (car (car translation-stack))
+ (format "%S" stack)))
+ (let* ((translation-item (pop translation-stack))
+ (partial-translation
+ (parser-generator-ll--perform-translation
+ (nth 1 translation-item)
+ translation-symbol-table
+ (reverse (pop terminal-stack)))))
+ (setq
+ translation
+ partial-translation)))
+
+ (pop stack)
+
+ ;; Is it time for SDT?
+ (when (and
+ translate-p
+ translation-stack
+ (string=
+ (car (car translation-stack))
+ (format "%S" stack)))
+ (let* ((translation-item (pop translation-stack))
+ (partial-translation
+ (parser-generator-ll--perform-translation
+ (nth 1 translation-item)
+ translation-symbol-table
+ (reverse (pop terminal-stack)))))
+ (setq
+ translation
+ partial-translation)))
+
+ (when translate-p
+ (push
+ (list
+ (format "%S" stack)
+ (nth 2 action))
+ translation-stack)
+ (push
+ '()
+ terminal-stack))
+
+ (unless (equal (nth 1 action) e-reduction)
+ (dolist (reduce-item (reverse (nth 1 action)))
+ (push reduce-item stack)))
+ (push
+ (nth 2 action)
+ output))
+
+ ((equal action-type 'accept)
+ (setq accept t))))))
+ (list
+ (reverse output)
+ translation)))
+
+(defun parser-generator-ll--perform-translation (production-number
symbol-table terminals)
+ "Perform translation by PRODUCTION-NUMBER, with SYMBOL-TABLE and TERMINALS."
+ (let* ((production
+ (parser-generator--get-grammar-production-by-number
+ production-number))
+ (production-lhs
+ (car (nth 0 production)))
+ (production-rhs
+ (nth 1 production))
+ (translation)
+ (args-1)
+ (args-2))
+ (parser-generator--debug
+ (message
+ "Perform translation %S %S %S = %S"
+ production-number
+ symbol-table
+ terminals
+ production-rhs))
+
+ ;; Collect arguments for translation
+ (let ((terminal-index 0))
+ (dolist (rhs-item production-rhs)
+ (cond
+
+ ((parser-generator--valid-non-terminal-p
+ rhs-item)
+ (let* ((non-terminal-value-list
+ (gethash rhs-item symbol-table))
+ (non-terminal-value
+ (pop non-terminal-value-list)))
+ (push
+ (car non-terminal-value)
+ args-1)
+ (push
+ (car (cdr non-terminal-value))
+ args-2)
+ (puthash
+ rhs-item
+ non-terminal-value-list
+ symbol-table)))
+
+ ((parser-generator--valid-terminal-p
+ rhs-item)
+ (push
+ (parser-generator-lex-analyzer--get-function
+ (nth terminal-index terminals))
+ args-1)
+ (push
+ (nth terminal-index terminals)
+ args-2)
+ (setq
+ terminal-index
+ (1+ terminal-index))))))
+ (setq
+ args-1
+ (reverse args-1))
+ (setq
+ args-2
+ (reverse args-2))
+
+ (parser-generator--debug
+ (message
+ "Perform translation %d: %S -> %S via args-1: %S and args-2: %S"
+ production-number
+ production-lhs
+ production-rhs
+ args-1
+ args-2))
+
+ (if (parser-generator--get-grammar-translation-by-number
+ production-number)
+ (let ((partial-translation
+ (funcall
+ (parser-generator--get-grammar-translation-by-number
+ production-number)
+ args-1
+ args-2))
+ (old-symbol-value
+ (gethash production-lhs symbol-table)))
+ (parser-generator--debug
+ (message
+ "\ntranslation-symbol-table: %S = %S (processed)\n"
+ production-lhs
+ partial-translation))
+ (push
+ (list
+ partial-translation
+ args-2)
+ old-symbol-value)
+ (puthash
+ production-lhs
+ old-symbol-value
+ symbol-table)
+ (setq
+ translation
+ partial-translation))
+
+ ;; When no translation is specified just use popped contents as
translation
+ (let ((partial-translation
+ (list
+ args-1
+ args-2))
+ (old-symbol-value
+ (gethash production-lhs symbol-table)))
+ (parser-generator--debug
+ (message
+ "\ntranslation-symbol-table: %S = %S (generic)\n"
+ production-lhs
+ partial-translation))
+ (push
+ partial-translation
+ old-symbol-value)
+ (puthash
+ production-lhs
+ old-symbol-value
+ symbol-table)
+ (setq
+ translation
+ (car partial-translation))))
+
+ translation))
+
+
+;;; Algorithms
+
+
+(defun parser-generator-ll--generate-action-table-k-eq-1 (goto-table)
+ "Generate action-table for LL(1) grammar using GOTO-TABLE."
+ (let ((parsing-table))
+
+ ;; Iterate all possible look-aheads
+ ;; Add EOF symbol look-ahead
+ (let ((eof-look-ahead
+ (parser-generator--generate-list-of-symbol
+ parser-generator--look-ahead-number
+ parser-generator--eof-identifier))
+ (terminal-mutations
+ (parser-generator--get-grammar-look-aheads))
+ (terminal-buffer)
+ (last-terminal))
+ (dolist (terminal-mutation terminal-mutations)
+ (if (equal terminal-mutation eof-look-ahead)
+ (push
+ (list
+ parser-generator--eof-identifier
+ (list
+ (list
+ eof-look-ahead
+ 'accept)))
+ parsing-table)
+ (let ((stack-item (nth 0 terminal-mutation)))
+ (when (and
+ last-terminal
+ (not (equal last-terminal stack-item)))
+ (push
+ (list
+ last-terminal
+ terminal-buffer)
+ parsing-table)
+ (setq
+ terminal-buffer
+ nil))
+ (push
+ (list terminal-mutation 'pop)
+ terminal-buffer)
+ (setq
+ last-terminal
+ stack-item))))
+ (when (and
+ last-terminal
+ terminal-buffer)
+ (push
+ (list
+ last-terminal
+ terminal-buffer)
+ parsing-table)))
+
+ ;; Add non-terminal -> FIRST(non-terminal) -> reduce RHS, production-number
+ (let ((non-terminal-look-ahead-p (make-hash-table :test 'equal))
+ (non-terminal-look-ahead-list (make-hash-table :test 'equal)))
+ (dolist (goto-row goto-table)
+ (let* ((stack (nth 0 goto-row))
+ (non-terminal (car (nth 0 stack)))
+ (local-follows (nth 1 stack))
+ (look-aheads (nth 1 goto-row)))
+ (parser-generator--debug
+ (message "\nnon-terminal: %S" non-terminal)
+ (message "local-follows: %S" local-follows)
+ (message "look-aheads: %S" look-aheads))
+ (dolist (look-ahead look-aheads)
+ (let* ((rhs
+ (nth 1 look-ahead))
+ (production
+ (list (list non-terminal) rhs))
+ (production-number
+ (parser-generator--get-grammar-production-number
+ production))
+ (look-ahead-terminal
+ (nth 0 look-ahead))
+ (hashmap-key
+ (format "%S-%S" non-terminal look-ahead-terminal)))
+ (parser-generator--debug
+ (message "\nrhs: %S" rhs)
+ (message "production: %S" production)
+ (message "production-number: %S" production-number)
+ (message "hashmap-key: %S" hashmap-key))
+ (unless (gethash hashmap-key non-terminal-look-ahead-p)
+ (let ((old-non-terminal-look-aheads
+ (gethash
+ non-terminal
+ non-terminal-look-ahead-list)))
+ (push
+ (list
+ look-ahead-terminal
+ 'reduce
+ rhs
+ production-number)
+ old-non-terminal-look-aheads)
+ (puthash
+ non-terminal
+ old-non-terminal-look-aheads
+ non-terminal-look-ahead-list)
+ (puthash
+ hashmap-key
+ t
+ non-terminal-look-ahead-p)))))))
+ (maphash
+ (lambda (non-terminal look-ahead)
+ (push
+ (list
+ non-terminal
+ look-ahead)
+ parsing-table))
+ non-terminal-look-ahead-list))
+
+ parsing-table))
+
+;; Algorithm 5.2 p. 350
+(defun parser-generator-ll--generate-goto-table ()
+ "Construction of LL(k) GOTO-table. Output the set of LL(k) tables needed to
construct a action table for the grammar G."
+ (let ((tables (make-hash-table :test 'equal))
+ (distinct-item-p (make-hash-table :test 'equal))
+ (stack)
+ (distinct-stack-item-p (make-hash-table :test 'equal))
+ (stack-item))
+
+ ;; (1) Construct T_0, the LL(k) table associated with S {e}
+ (let* ((start (parser-generator--get-grammar-start))
+ (start-rhss (parser-generator--get-grammar-rhs start)))
+ (dolist (start-rhs start-rhss)
+ (let* ((initial-stack-item
+ (list
+ (list start)
+ start-rhs
+ (parser-generator--generate-list-of-symbol
+ parser-generator--look-ahead-number
+ parser-generator--eof-identifier))))
+ (puthash
+ initial-stack-item
+ t
+ distinct-stack-item-p)
+ (push
+ initial-stack-item
+ stack))))
+
+ (setq stack (nreverse stack))
+ (parser-generator--debug
+ (message "stack: %S" stack))
+
+ (while stack
+ (setq stack-item (pop stack))
+ (let* ((production-lhs
+ (nth 0 stack-item))
+ (production-rhs
+ (nth 1 stack-item))
+ (parent-follow
+ (nth 2 stack-item))
+ (concatenated-follow
+ (append production-rhs parent-follow))
+ (first-concatenated-follow
+ (parser-generator--first concatenated-follow nil t t))
+ (look-aheads
+ (parser-generator--merge-max-terminal-sets
+ first-concatenated-follow))
+ (sets))
+
+ (parser-generator--debug
+ (message "\nproduction-lhs: %S" production-lhs)
+ (message "production-rhs: %S" production-rhs)
+ (message "parent-follow: %S" parent-follow)
+ (message "concatenated-follow: %S" concatenated-follow)
+ (message "first-concatenated-follow: %S" first-concatenated-follow)
+ (message "look-aheads: %S" look-aheads))
+
+ ;; For each non-terminal in the production right-hand side
+ ;; push a new item to stack with a local-follow
+ ;; and a new left-hand-side
+ (let ((sub-symbol-index 0)
+ (sub-symbol-length (length production-rhs)))
+ (while (< sub-symbol-index sub-symbol-length)
+ (let ((sub-symbol (nth sub-symbol-index production-rhs)))
+ (when (parser-generator--valid-non-terminal-p
+ sub-symbol)
+ (let* ((follow-set
+ (nthcdr (1+ sub-symbol-index) production-rhs))
+ (concatenated-follow-set
+ (append follow-set parent-follow))
+ (first-concatenated-follow-set
+ (parser-generator--first concatenated-follow-set nil t
t))
+ (local-follow-set
+ (parser-generator--merge-max-terminal-sets
+ first-concatenated-follow-set
+ nil
+ t))
+ (sub-symbol-rhss
+ (parser-generator--get-grammar-rhs
+ sub-symbol)))
+ (parser-generator--debug
+ (message
+ "\nnon-terminal sub-symbol: %S" sub-symbol)
+ (message
+ "follow-set: %S for %S in %S"
+ follow-set
+ (nth sub-symbol-index production-rhs)
+ production-rhs)
+ (message
+ "concatenated-follow-set: %S"
+ concatenated-follow-set)
+ (message
+ "first-concatenated-follow-set: %S"
+ first-concatenated-follow-set)
+ (message
+ "local-follow-set: %S"
+ local-follow-set)
+ (message
+ "sub-symbol-rhss: %S"
+ sub-symbol-rhss))
+ (unless local-follow-set
+ (setq local-follow-set '(nil)))
+
+ (push
+ local-follow-set
+ sets)
+ (parser-generator--debug
+ (message
+ "pushed local follow set to sets: %S"
+ local-follow-set))
+ (dolist (local-follow local-follow-set)
+ (dolist (sub-symbol-rhs sub-symbol-rhss)
+ (let* ((new-stack-item
+ (list
+ (list sub-symbol)
+ sub-symbol-rhs
+ local-follow)))
+ (unless (gethash
+ new-stack-item
+ distinct-stack-item-p)
+ (parser-generator--debug
+ (message
+ "new-stack-item: %S"
+ new-stack-item))
+ (puthash
+ new-stack-item
+ t
+ distinct-stack-item-p)
+ (push
+ new-stack-item
+ stack))))))))
+ (setq
+ sub-symbol-index
+ (1+ sub-symbol-index))))
+
+ (setq sets (reverse sets))
+ (parser-generator--debug
+ (message
+ "\nsets: %S"
+ sets))
+
+ ;; Add all distinct combinations of left-hand-side,
+ ;; look-aheads and parent-follow to tables list here
+ (when look-aheads
+ (dolist (look-ahead look-aheads)
+ (let ((table
+ (list
+ look-ahead
+ production-rhs
+ sets))
+ (item-hash-key
+ (format
+ "%S-%S-%S"
+ production-lhs
+ parent-follow
+ look-ahead))
+ (table-hash-key
+ (list
+ production-lhs
+ parent-follow)))
+
+ ;; Only add distinct items
+ (unless (gethash item-hash-key distinct-item-p)
+ (puthash
+ item-hash-key
+ t
+ distinct-item-p)
+ (parser-generator--debug
+ (message "\nnew table: %S" table))
+ (if (gethash
+ table-hash-key
+ tables)
+ (puthash
+ table-hash-key
+ (push
+ table
+ (gethash
+ table-hash-key
+ tables))
+ tables)
+ (puthash
+ table-hash-key
+ (list table)
+ tables))))))))
+
+ (let ((sorted-tables))
+ (maphash
+ (lambda (k v)
+ (push
+ (list k (sort v 'parser-generator--sort-list))
+ sorted-tables))
+ tables)
+ sorted-tables)))
+
+;; Algorithm 5.3 p. 351
+(defun parser-generator-ll--generate-action-table-k-gt-1 (tables)
+ "Generate a action table for an LL(k) grammar G and TABLES. Output M, a
valid parsing table for G."
+ (let ((parsing-table))
+
+ ;; (3) M($, e) = accept
+ ;; (2) M(a, av) = pop for all v in E where |E| = k-1
+ (let ((eof-look-ahead
+ (parser-generator--generate-list-of-symbol
+ parser-generator--look-ahead-number
+ parser-generator--eof-identifier))
+ (terminal-mutations
+ (parser-generator--get-grammar-look-aheads))
+ (terminal-buffer)
+ (last-terminal))
+ (dolist (terminal-mutation terminal-mutations)
+ (if (equal terminal-mutation eof-look-ahead)
+ (push
+ (list
+ parser-generator--eof-identifier
+ (list
+ (list
+ eof-look-ahead
+ 'accept)))
+ parsing-table)
+ (let ((stack-item (nth 0 terminal-mutation)))
+ (when (and
+ last-terminal
+ (not (equal last-terminal stack-item)))
+ (push
+ (list
+ last-terminal
+ terminal-buffer)
+ parsing-table)
+ (setq
+ terminal-buffer
+ nil))
+
+ (push
+ (list terminal-mutation 'pop)
+ terminal-buffer)
+ (setq
+ last-terminal
+ stack-item))))
+ (when (and
+ last-terminal
+ terminal-buffer)
+ (push
+ (list
+ last-terminal
+ terminal-buffer)
+ parsing-table)))
+
+ (dolist (table tables)
+ (let* ((key (nth 0 table))
+ (value (nth 1 table))
+ (left-hand-side (nth 0 key))
+ (parse-table))
+ (dolist (look-ahead-row value)
+ (let* ((look-ahead (nth 0 look-ahead-row))
+ (right-hand-side (nth 1 look-ahead-row))
+ (local-follow-sets (nth 2 look-ahead-row))
+ (non-terminal-index 0)
+ (sub-symbol-index 0)
+ (sub-symbol-length (length right-hand-side))
+ (production (list left-hand-side right-hand-side))
+ (production-number
+ (parser-generator--get-grammar-production-number
+ production))
+ (modified-right-hand-side))
+ (while (< sub-symbol-index sub-symbol-length)
+ (let ((sub-symbol (nth sub-symbol-index right-hand-side)))
+ (if (parser-generator--valid-non-terminal-p
+ sub-symbol)
+ (let ((local-follow
+ (car (nth non-terminal-index local-follow-sets))))
+ (push
+ (list
+ (list sub-symbol)
+ local-follow)
+ modified-right-hand-side)
+ (setq
+ non-terminal-index
+ (1+ non-terminal-index)))
+ (push
+ sub-symbol
+ modified-right-hand-side)))
+ (setq
+ sub-symbol-index
+ (1+ sub-symbol-index)))
+ (setq
+ modified-right-hand-side
+ (reverse modified-right-hand-side))
+
+ (push
+ (list
+ look-ahead
+ 'reduce
+ modified-right-hand-side
+ production-number)
+ parse-table)))
+ (push
+ (list
+ key
+ parse-table)
+ parsing-table)))
+
+ parsing-table))
+
+(defun parser-generator-ll--valid-grammar-k-eq-1-p ()
+ "Test for LL(1)-ness. Output t if grammar is LL(1), nil otherwise."
+ (let* ((non-terminals (parser-generator--get-grammar-non-terminals))
+ (non-terminal-length (length non-terminals))
+ (non-terminal-index 0)
+ (non-terminal)
+ (valid t))
+ (while (and
+ valid
+ (< non-terminal-index non-terminal-length))
+ (setq non-terminal (nth non-terminal-index non-terminals))
+ (let* ((rhss (parser-generator--get-grammar-rhs non-terminal))
+ (rhss-length (length rhss))
+ (rhss-index 0)
+ (rhs)
+ (look-aheads (make-hash-table :test 'equal)))
+ (while (and
+ valid
+ (< rhss-index rhss-length))
+ (setq rhs (nth rhss-index rhss))
+ (let* ((firsts-rhs (parser-generator--first rhs))
+ (firsts-rhs-length (length firsts-rhs))
+ (firsts-index 0)
+ (first-rhs))
+ (while (and
+ valid
+ (< firsts-index firsts-rhs-length))
+ (setq first-rhs (nth firsts-index firsts-rhs))
+ (let ((first-rhs-hash (format "%S" first-rhs)))
+ (if (gethash first-rhs-hash look-aheads)
+ (setq valid nil)
+ (puthash first-rhs-hash t look-aheads)))
+ (setq firsts-index (1+ firsts-index))))
+ (setq rhss-index (1+ rhss-index))))
+ (setq non-terminal-index (1+ non-terminal-index)))
+ valid))
+
+;; Algorithm 5.4 p. 357
+(defun parser-generator-ll--valid-grammar-k-gt-1-p ()
+ "Test for LL(k)-ness. Output t if grammar is LL(k), nil otherwise."
+ (let ((stack)
+ (stack-item)
+ (distinct-production-p (make-hash-table :test 'equal))
+ (valid t))
+
+ ;; (1) Construct T_0, the LL(k) table associated with S {e}
+ (let* ((start (parser-generator--get-grammar-start))
+ (start-rhss (parser-generator--get-grammar-rhs start)))
+ (dolist (start-rhs start-rhss)
+ (let* ((production (list (list start) start-rhs)))
+ (push
+ production
+ stack)
+ (puthash
+ production
+ t
+ distinct-production-p))))
+ (setq stack (nreverse stack))
+ (parser-generator--debug
+ (message "stack: %S" stack))
+
+ (while (and
+ stack
+ valid)
+ (setq stack-item (pop stack))
+ (let ((production-rhs
+ (nth 1 stack-item)))
+
+ ;; For each non-terminal in the production right-hand side
+ ;; push a new item to stack with a local-follow
+ ;; and a new left-hand-side
+ (let ((sub-symbol-index 0)
+ (sub-symbol-length (length production-rhs)))
+ (while (< sub-symbol-index sub-symbol-length)
+ (let ((sub-symbol (nth sub-symbol-index production-rhs)))
+ (when (parser-generator--valid-non-terminal-p
+ sub-symbol)
+ (let* ((local-follow
+ (nthcdr (1+ sub-symbol-index) production-rhs))
+ (first-local-follow-sets
+ (parser-generator--first local-follow nil t t))
+ (sub-symbol-rhss
+ (parser-generator--get-grammar-rhs sub-symbol))
+ (distinct-item-p
+ (make-hash-table :test 'equal)))
+ (parser-generator--debug
+ (message "\nsub-symbol: %S" sub-symbol)
+ (message "local-follow: %S" local-follow)
+ (message "first-local-follow-sets: %S"
first-local-follow-sets)
+ (message "sub-symbol-rhss: %S" sub-symbol-rhss))
+
+ ;; Calculate following terminals to see if there is a
conflict
+ (dolist (sub-symbol-rhs sub-symbol-rhss)
+ (let ((first-sub-symbol-rhs
+ (parser-generator--first sub-symbol-rhs nil t t)))
+ (let ((merged-terminal-sets
+ (parser-generator--merge-max-terminal-sets
+ first-sub-symbol-rhs
+ first-local-follow-sets)))
+ (parser-generator--debug
+ (message "sub-symbol-rhs: %S" sub-symbol-rhs)
+ (message "first-sub-symbol-rhs: %S"
first-sub-symbol-rhs)
+ (message "merged-terminal-sets: %S"
merged-terminal-sets))
+ (dolist (merged-terminal-set merged-terminal-sets)
+ (if (gethash
+ merged-terminal-set
+ distinct-item-p)
+ (progn
+ (setq valid nil)
+ (parser-generator--debug
+ (message
+ "merged-terminal-set: %S was not distinct"
+ merged-terminal-set)))
+ (puthash
+ merged-terminal-set
+ t
+ distinct-item-p)))))
+
+ ;; Add production to stack if it has not been added already
+ (let ((production
+ (list
+ (list sub-symbol)
+ sub-symbol-rhs)))
+ (unless
+ (gethash
+ production
+ distinct-production-p)
+ (push
+ production
+ stack)
+ (puthash
+ production
+ t
+ distinct-production-p)))))))
+ (setq
+ sub-symbol-index
+ (1+ sub-symbol-index))))))
+ valid))
+
+
+(provide 'parser-generator-ll)
+
+;;; parser-generator-ll.el ends here
diff --git a/parser-generator-lr.el b/parser-generator-lr.el
index d57f691906..47c0baa3b3 100644
--- a/parser-generator-lr.el
+++ b/parser-generator-lr.el
@@ -254,13 +254,13 @@
(defun parser-generator-lr-generate-parser-tables ()
"Generate parsing tables for grammar."
- (message "\n;; Starting generation of parser-tables..\n")
+ (message "\n;; Starting generation of LR(k) parser-tables..\n")
(parser-generator-lr--generate-precedence-tables)
(let ((table-lr-items
(parser-generator-lr--generate-goto-tables)))
(parser-generator-lr--generate-action-tables
table-lr-items)
- (message "\n;; Completed generation of parser-tables.\n")
+ (message "\n;; Completed generation of LR(k) parser-tables.\n")
table-lr-items))
(defun parser-generator-lr--get-expanded-action-tables ()
diff --git a/parser-generator.el b/parser-generator.el
index 5ee265c407..2690799da0 100644
--- a/parser-generator.el
+++ b/parser-generator.el
@@ -367,7 +367,10 @@
(if (hash-table-p hash-table)
(progn
(maphash
- (lambda (k v) (push (list k v) result))
+ (lambda (k v)
+ (if (hash-table-p v)
+ (push (list k (parser-generator--hash-to-list v un-sorted))
result)
+ (push (list k v) result)))
hash-table)
(if un-sorted
(nreverse result)
@@ -662,6 +665,14 @@
(error "E-identifier must be a symbol or string!"))
(setq parser-generator--e-identifier e-identifier))
+(defun parser-generator-set-eof-identifier (eof-identifier)
+ "Set EOF-IDENTIFIER."
+ (unless (or
+ (stringp eof-identifier)
+ (symbolp eof-identifier))
+ (error "EOF-identifier must be a symbol or string!"))
+ (setq parser-generator--eof-identifier eof-identifier))
+
(defun parser-generator-set-look-ahead-number (k)
"Set look-ahead number K."
(unless (parser-generator--valid-look-ahead-number-p k)
@@ -1230,36 +1241,155 @@
look-ahead)))
(nreverse look-ahead)))
-(defun parser-generator--merge-max-terminals (a b k)
- "Merge terminals from A and B to a maximum length of K."
- (let ((merged)
+(defun parser-generator--merge-max-terminal-sets (a &optional b
allow-any-length)
+ "Calculate list of all lists of L1 (+) L2 which is a merge of all terminals
in lists A combined with all terminals in lists B but with maximum length of
the set look-ahead number."
+ (let ((a-length (length a))
+ (a-index 0)
+ (b-length (length b))
+ (merged-lists))
+ (cond
+ ((and a b)
+ (while (< a-index a-length)
+ (let ((a-element (nth a-index a))
+ (b-index 0))
+ (while (< b-index b-length)
+ (let ((b-element (nth b-index b)))
+ (when-let
+ ((merged-element
+ (parser-generator--merge-max-terminals
+ a-element
+ b-element
+ allow-any-length)))
+ (if merged-lists
+ (setq
+ merged-lists
+ (append
+ merged-lists
+ (list merged-element)))
+ (setq
+ merged-lists
+ (list merged-element)))))
+ (setq b-index (1+ b-index)))
+ (setq a-index (1+ a-index)))))
+ (a
+ (while (< a-index a-length)
+ (let ((a-element (nth a-index a)))
+ (when-let
+ ((merged-element
+ (parser-generator--merge-max-terminals
+ a-element
+ nil
+ allow-any-length)))
+ (if merged-lists
+ (setq
+ merged-lists
+ (append
+ merged-lists
+ (list merged-element)))
+ (setq
+ merged-lists
+ (list merged-element)))))
+ (setq a-index (1+ a-index))))
+
+ (b
+ (let ((b-index 0))
+ (while (< b-index b-length)
+ (let ((b-element (nth b-index b)))
+ (when-let
+ ((merged-element
+ (parser-generator--merge-max-terminals
+ nil
+ b-element
+ allow-any-length)))
+ (if merged-lists
+ (setq
+ merged-lists
+ (append
+ merged-lists
+ (list merged-element)))
+ (setq
+ merged-lists
+ (list merged-element)))))
+ (setq b-index (1+ b-index))))))
+ (setq
+ merged-lists
+ (parser-generator--distinct
+ merged-lists))
+ (setq
+ merged-lists
+ (sort
+ merged-lists
+ 'parser-generator--sort-list))
+ merged-lists))
+
+;; Lemma 5.1 p. 348
+(defun parser-generator--merge-max-terminals (a b &optional allow-any-length)
+ "Calculate L1 (+) L2 which is a merge of all terminals in A and B but with
exactly length of the set look-ahead number. Optionally ALLOW-ANY-LENGTH."
+ (let ((k (max 1 parser-generator--look-ahead-number))
+ (merged)
(merge-count 0)
- (continue t)
(a-element)
(a-index 0)
(a-length (length a))
(b-element)
(b-index 0)
- (b-length (length b)))
+ (b-length (length b))
+ (only-eof))
+
(while (and
(< a-index a-length)
- (< merge-count k)
- continue)
+ (< merge-count k))
(setq a-element (nth a-index a))
- (when (parser-generator--valid-e-p a-element)
- (setq continue nil))
- (push a-element merged)
+
+ (when (parser-generator--valid-eof-p
+ a-element)
+ (setq only-eof t))
+
+ (when (or
+ (and
+ only-eof
+ (parser-generator--valid-eof-p
+ a-element))
+ (and
+ (not only-eof)
+ (parser-generator--valid-terminal-p
+ a-element)))
+ (push a-element merged)
+ (setq merge-count (1+ merge-count)))
+
(setq a-index (1+ a-index)))
+
(while (and
(< b-index b-length)
- (< merge-count k)
- continue)
+ (< merge-count k))
(setq b-element (nth b-index b))
- (when (parser-generator--valid-e-p b-element)
- (setq continue nil))
- (push b-element merged)
+
+ (when (parser-generator--valid-eof-p
+ b-element)
+ (setq only-eof t))
+
+ (when (or
+ (and
+ only-eof
+ (parser-generator--valid-eof-p
+ b-element))
+ (and
+ (not only-eof)
+ (parser-generator--valid-terminal-p
+ b-element)))
+ (push b-element merged)
+ (setq merge-count (1+ merge-count)))
+
(setq b-index (1+ b-index)))
- (nreverse merged)))
+
+ (if (or
+ (and
+ allow-any-length
+ (> merge-count 0))
+ (and (not allow-any-length)
+ (= merge-count k)))
+ (nreverse merged)
+ nil)))
;; p. 357
(defun parser-generator--f-set (input-tape state stack)
@@ -1512,8 +1642,8 @@
;; Algorithm 5.5, p. 357
(defun parser-generator--first
- (β &optional disallow-e-first ignore-validation skip-sorting)
- "For sentential-form Β, calculate first terminals, optionally
DISALLOW-E-FIRST, IGNORE-VALIDATION and SKIP-SORTING."
+ (β &optional disallow-e-first ignore-validation skip-sorting
use-eof-for-trailing-symbols)
+ "For sentential-form Β, calculate first terminals, optionally
DISALLOW-E-FIRST, IGNORE-VALIDATION, SKIP-SORTING and
USE-EOF-FOR-TRAILING-SYMBOLS."
;; Make sure we are dealing with a list of symbols
(unless (listp β)
@@ -1644,8 +1774,13 @@
(parser-generator--valid-eof-p input-symbol)
(parser-generator--valid-terminal-p input-symbol))
(parser-generator--debug
- (message
- "symbol is a terminal, the e-identifier or the EOF-identifier"))
+ (cond
+ ((parser-generator--valid-e-p input-symbol)
+ (message "symbol is the e-identifier"))
+ ((parser-generator--valid-eof-p input-symbol)
+ (message "symbol is the EOF-identifier"))
+ ((parser-generator--valid-terminal-p input-symbol)
+ (message "symbol is a terminal"))))
(let ((expanded-list-index 0)
(expanded-list-count
(length expanded-lists)))
@@ -1706,7 +1841,9 @@
(setq
expanded-lists-index
(1+ expanded-lists-index)))
- (when (>= minimum-terminal-count k)
+ (when (and
+ minimum-terminal-count
+ (>= minimum-terminal-count k))
(setq still-looking nil)
(parser-generator--debug
(message
@@ -1866,14 +2003,18 @@
(missing-symbol-index 0))
(while (< missing-symbol-index missing-symbol-count)
(push
- parser-generator--e-identifier
+ (if use-eof-for-trailing-symbols
+ parser-generator--eof-identifier
+ parser-generator--e-identifier)
processed-list)
(setq
missing-symbol-index
(1+ missing-symbol-index)))
(parser-generator--debug
(message
- "Added %d trailing e-identifiers to set"
+ (if use-eof-for-trailing-symbols
+ "Added %d trailing EOF-identifiers to set"
+ "Added %d trailing e-identifiers to set")
missing-symbol-count))))
(when (> (length processed-list) k)
@@ -1999,6 +2140,93 @@
(parser-generator--distinct follow-set)))
follow-set))
+(defun parser-generator-generate-terminal-saturated-first-set (first-set)
+ "Generated a set from FIRST-SET with items that does not end with the
e-identifier if there is alternative items that continues with terminals."
+ (let ((max-terminal-count
+ (parser-generator-calculate-max-terminal-count
+ first-set))
+ (saturated-list))
+ (when (> max-terminal-count 0)
+ (setq
+ saturated-list
+ (parser-generator-generate-sets-of-terminals
+ first-set
+ max-terminal-count)))
+ saturated-list))
+
+(defun parser-generator-generate-sets-of-terminals (sets count)
+ "Generate set of terminals in sequence from SETS with COUNT."
+ (let ((sets-of-terminals)
+ (terminal-set-exists-p (make-hash-table :test 'equal)))
+ (dolist (set sets)
+ (let ((item-count (length set))
+ (item-index 0)
+ (only-terminals t)
+ (terminal-count 0)
+ (terminals))
+ (while (and
+ only-terminals
+ (< terminal-count count)
+ (< item-index item-count))
+ (let ((item (nth item-index set)))
+ (if (parser-generator--valid-terminal-p item)
+ (progn
+ (push
+ item
+ terminals)
+ (setq
+ terminal-count
+ (1+ terminal-count)))
+ (setq
+ only-terminals
+ nil)))
+ (setq
+ item-index
+ (1+ item-index)))
+ (when (and
+ only-terminals
+ (= terminal-count count)
+ (not
+ (gethash
+ terminals
+ terminal-set-exists-p)))
+ (puthash
+ terminals
+ t
+ terminal-set-exists-p)
+ (push
+ (reverse terminals)
+ sets-of-terminals))))
+ (reverse sets-of-terminals)))
+
+(defun parser-generator-calculate-max-terminal-count (sets)
+ "Calculate maximum number of terminals in sequence in SETS."
+ (let ((max-terminal-count 0))
+ (dolist (set sets)
+ (let ((item-count (length set))
+ (item-index 0)
+ (only-terminals t)
+ (terminal-count 0))
+ (while (and
+ only-terminals
+ (< item-index item-count))
+ (let ((item (nth item-index set)))
+ (if (parser-generator--valid-terminal-p item)
+ (setq
+ terminal-count
+ (1+ terminal-count))
+ (setq
+ only-terminals
+ nil)))
+ (setq
+ item-index
+ (1+ item-index)))
+ (when (> terminal-count max-terminal-count)
+ (setq
+ max-terminal-count
+ terminal-count))))
+ max-terminal-count))
+
(provide 'parser-generator)
diff --git a/test/parser-generator-ll-export-test.el
b/test/parser-generator-ll-export-test.el
new file mode 100644
index 0000000000..17d7ac0468
--- /dev/null
+++ b/test/parser-generator-ll-export-test.el
@@ -0,0 +1,176 @@
+;; parser-generator-ll-export-test.el --- Tests for Exported Generated LL(k)
Parser -*- lexical-binding: t -*-
+
+;; Copyright (C) 2020-2022 Free Software Foundation, Inc.
+
+
+;;; Commentary:
+
+
+;;; Code:
+
+
+(require 'parser-generator-ll-export)
+(require 'ert)
+
+(defun parser-generator-ll-export-test-to-elisp ()
+ "Test `parser-generator-ll-export-to-elisp'."
+ (message "Started tests for (parser-generator-ll-export-to-elisp)")
+
+ (parser-generator-set-eof-identifier '$)
+ (parser-generator-set-e-identifier 'e)
+ (parser-generator-set-look-ahead-number 2)
+ (parser-generator-set-grammar
+ '(
+ (S A)
+ (a b)
+ (
+ (S
+ (a A a a (lambda(a b) (format "alfa %s laval" (nth 1 a))))
+ (b A b a (lambda(a b) (format "delta %s laval" (nth 1 a))))
+ )
+ (A
+ (b (lambda(a b) "sven"))
+ (e (lambda(a b) "ingrid"))
+ )
+ )
+ S
+ )
+ )
+ (parser-generator-process-grammar)
+ (parser-generator-ll-generate-table)
+ (setq
+ parser-generator-lex-analyzer--function
+ (lambda (index)
+ (let* ((string '((b 1 . 2) (b 2 . 3) (a 3 . 4)))
+ (string-length (length string))
+ (max-index index)
+ (tokens))
+ (while (and
+ (< (1- index) string-length)
+ (< (1- index) max-index))
+ (push (nth (1- index) string) tokens)
+ (setq index (1+ index)))
+ (nreverse tokens))))
+ (setq
+ parser-generator-lex-analyzer--get-function
+ (lambda (token)
+ (car token)))
+ (let ((export (parser-generator-ll-export-to-elisp "ba")))
+ (with-temp-buffer
+ (insert export)
+ (eval-buffer)
+ (should
+ (equal
+ t
+ (fboundp 'ba-parse)))
+ (should
+ (equal
+ t
+ (fboundp 'ba-translate)))
+ (when (fboundp 'ba-parse)
+ (should
+ (equal
+ '(1 3)
+ (ba-parse))))
+ (when (fboundp 'ba-translate)
+ (should
+ (equal
+ "delta ingrid laval"
+ (ba-translate))))))
+ (message "Passed exported test for example 5.16 p. 352")
+
+ (setq
+ parser-generator-lex-analyzer--function
+ (lambda (index)
+ (let* ((string '((b 1 . 2) (b 2 . 3) (b 3 . 4) (a 4 . 5)))
+ (string-length (length string))
+ (max-index index)
+ (tokens))
+ (while (and
+ (< (1- index) string-length)
+ (< (1- index) max-index))
+ (push (nth (1- index) string) tokens)
+ (setq index (1+ index)))
+ (nreverse tokens))))
+ (let ((export (parser-generator-ll-export-to-elisp "ba2")))
+ (with-temp-buffer
+ (insert export)
+ (eval-buffer)
+ (should
+ (equal
+ t
+ (fboundp 'ba2-parse)))
+ (should
+ (equal
+ t
+ (fboundp 'ba2-translate)))
+ (when (fboundp 'ba2-translate)
+ (should
+ (equal
+ "delta sven laval"
+ (ba2-translate))))))
+ (message "Passed exported test failing parse")
+
+ (parser-generator-set-eof-identifier '$)
+ (parser-generator-set-e-identifier 'e)
+ (parser-generator-set-look-ahead-number 1)
+ (parser-generator-set-grammar
+ '(
+ (S A)
+ (a b)
+ (
+ (S (a A S) b)
+ (A a (b S A))
+ )
+ S
+ )
+ )
+ (parser-generator-process-grammar)
+ (parser-generator-ll-generate-table)
+ (setq
+ parser-generator-lex-analyzer--function
+ (lambda (index)
+ (let* ((string '((a 1 . 2) (b 2 . 3) (b 3 . 4) (a 4 . 5) (b 5 . 6)))
+ (string-length (length string))
+ (max-index index)
+ (tokens))
+ (while (and
+ (< (1- index) string-length)
+ (< (1- index) max-index))
+ (push (nth (1- index) string) tokens)
+ (setq index (1+ index)))
+ (nreverse tokens))))
+ (setq
+ parser-generator-lex-analyzer--get-function
+ (lambda (token)
+ (car token)))
+ (let ((export (parser-generator-ll-export-to-elisp "ba3")))
+ (with-temp-buffer
+ (insert export)
+ (eval-buffer)
+ (should
+ (equal
+ t
+ (fboundp 'ba3-parse)))
+ (should
+ (equal
+ t
+ (fboundp 'ba3-translate)))
+ (when (fboundp 'ba3-parse)
+ (should
+ (equal
+ '(0 3 1 2 1)
+ (ba3-parse))))))
+ (message "Passed exported test for example 5.5 p. 340")
+
+ (message "Passed tests for (parser-generator-ll-export-to-elisp)"))
+
+
+(defun parser-generator-ll-export-test ()
+ "Run test."
+ (parser-generator-ll-export-test-to-elisp))
+
+
+(provide 'parser-generator-ll-export-test)
+
+;;; parser-generator-ll-export-test.el ends here
diff --git a/test/parser-generator-ll-test.el b/test/parser-generator-ll-test.el
new file mode 100644
index 0000000000..5e927cbf3f
--- /dev/null
+++ b/test/parser-generator-ll-test.el
@@ -0,0 +1,943 @@
+;; parser-generator-ll-test.el --- Tests for LL(k) Parser Generator -*-
lexical-binding: t -*-
+
+;; Copyright (C) 2020-2022 Free Software Foundation, Inc.
+
+
+;;; Commentary:
+
+
+;;; Code:
+
+
+(require 'parser-generator-ll)
+(require 'ert)
+
+(defun parser-generator-ll-test--generate-goto-table ()
+ "Test `parser-generator-ll--generate-goto-table'."
+ (message "Started tests for (parser-generator-ll--generate-goto-table)")
+
+ (parser-generator-set-e-identifier 'e)
+ (parser-generator-set-look-ahead-number 2)
+ (parser-generator-set-grammar
+ '(
+ (S A)
+ (a b)
+ (
+ (S (a A a a) (b A b a))
+ (A b e)
+ )
+ S
+ )
+ )
+ (parser-generator-process-grammar)
+ (let ((tables (parser-generator-ll--generate-goto-table)))
+ ;; (message "tables: %S" tables)
+ (should
+ (equal
+ tables
+ '(
+ (
+ ((A) (b a)) ;; T A,{ba}
+ (
+ ((b b) (b) nil)
+ ((b a) (e) nil)
+ )
+ )
+ (
+ ((A) (a a)) ;; T A,{aa}
+ (
+ ((a a) (e) nil)
+ ((b a) (b) nil)
+ )
+ )
+ (
+ ((S) ($ $)) ;; T0
+ (
+ ((a b) (a A a a) (((a a))))
+ ((a a) (a A a a) (((a a))))
+ ((b b) (b A b a) (((b a))))
+ )
+ )
+ )
+ )
+ ))
+ (message "Passed Example 5.14 p. 350 and 5.15 p. 351")
+
+ (parser-generator-set-eof-identifier '$)
+ (parser-generator-set-e-identifier 'e)
+ (parser-generator-set-look-ahead-number 2)
+ (parser-generator-set-grammar
+ '(
+ (S A)
+ (a b)
+ (
+ (S e (a b A))
+ (A (S a a) b)
+ )
+ S
+ )
+ )
+ (parser-generator-process-grammar)
+ (let* ((tables
+ (parser-generator-ll--generate-goto-table)))
+ ;; (message "tables: %S" tables)
+ (should
+ (equal
+ tables
+ '(
+ (
+ ((A) (a a)) ;; T3
+ (
+ ((a b) (S a a) (((a a))))
+ ((a a) (S a a) (((a a))))
+ ((b a) (b) nil)
+ )
+ )
+ (
+ ((S) (a a)) ;; T2
+ (
+ ((a b) (a b A) (((a a))))
+ ((a a) (e) nil)
+ )
+ )
+ (
+ ((A) ($ $)) ;; T1
+ (
+ ((a b) (S a a) (((a a))))
+ ((a a) (S a a) (((a a))))
+ ((b $) (b) nil)
+ )
+ )
+ (
+ ((S) ($ $)) ;; T0
+ (
+ (($ $) (e) nil)
+ ((a b) (a b A) ((($ $))))
+ )
+ )
+ )
+ ))
+ )
+ (message "Passed Example 5.17 p. 354")
+
+ (message "Passed tests for (parser-generator-ll--generate-goto-table)"))
+
+(defun parser-generator-ll-test--generate-action-table-k-gt-1 ()
+ "Test `parser-generator-ll--generate-action-table-k-gt-1'."
+ (message "Started tests for
(parser-generator-ll--generate-action-table-k-gt-1)")
+
+ (parser-generator-set-e-identifier 'e)
+ (parser-generator-set-look-ahead-number 2)
+ (parser-generator-set-grammar
+ '(
+ (S A)
+ (a b)
+ (
+ (S (a A a a) (b A b a))
+ (A b e)
+ )
+ S
+ )
+ )
+ (parser-generator-process-grammar)
+ (let* ((goto-table
+ (parser-generator-ll--generate-goto-table))
+ (action-table
+ (parser-generator-ll--generate-action-table-k-gt-1
+ goto-table)))
+ ;; (message "goto-table: %S" goto-table)
+ ;; (message "action-table: %S" action-table)
+ (should
+ (equal
+ '(
+ (
+ ((S) ($ $)) ;; T0
+ (
+ ((b b) reduce (b ((A) (b a)) b a) 1)
+ ((a a) reduce (a ((A) (a a)) a a) 0)
+ ((a b) reduce (a ((A) (a a)) a a) 0)
+ )
+ )
+ (
+ ((A) (a a)) ;; T1
+ (
+ ((b a) reduce (b) 2)
+ ((a a) reduce (e) 3)
+ )
+ )
+ (
+ ((A) (b a)) ;; T2
+ (
+ ((b a) reduce (e) 3)
+ ((b b) reduce (b) 2)
+ )
+ )
+ (b (((b b) pop) ((b a) pop) ((b $) pop)))
+ (a (((a b) pop) ((a a) pop) ((a $) pop)))
+ ($ ((($ $) accept)))
+ )
+ action-table)))
+ (message "Passed Example 5.15 p. 351 and 5.16 p. 352")
+
+ (parser-generator-set-eof-identifier '$)
+ (parser-generator-set-e-identifier 'e)
+ (parser-generator-set-look-ahead-number 2)
+ (parser-generator-set-grammar
+ '(
+ (S A)
+ (a b)
+ (
+ (S e (a b A))
+ (A (S a a) b)
+ )
+ S
+ )
+ )
+ (parser-generator-process-grammar)
+ (let* ((goto-table
+ (parser-generator-ll--generate-goto-table))
+ (action-table
+ (parser-generator-ll--generate-action-table-k-gt-1
+ goto-table)))
+ ;; (message "goto-tables: %S" goto-table)
+ ;; (message "action-table: %S" action-table)
+ (should
+ (equal
+ '(
+ (
+ ((S) ($ $)) ;; T0
+ (
+ ((a b) reduce (a b ((A) ($ $))) 1)
+ (($ $) reduce (e) 0)
+ )
+ )
+ (
+ ((A) ($ $)) ;; T1
+ (
+ ((b $) reduce (b) 3)
+ ((a a) reduce (((S) (a a)) a a) 2)
+ ((a b) reduce (((S) (a a)) a a) 2)
+ )
+ )
+ (
+ ((S) (a a)) ;; T2
+ (
+ ((a a) reduce (e) 0)
+ ((a b) reduce (a b ((A) (a a))) 1)
+ )
+ )
+ (
+ ((A) (a a)) ;; T3
+ (
+ ((b a) reduce (b) 3)
+ ((a a) reduce (((S) (a a)) a a) 2)
+ ((a b) reduce (((S) (a a)) a a) 2)
+ )
+ )
+ (b (((b b) pop) ((b a) pop) ((b $) pop)))
+ (a (((a b) pop) ((a a) pop) ((a $) pop)))
+ ($ ((($ $) accept)))
+ )
+ action-table)))
+ (message "Passed Example 5.17 p. 356")
+
+ (message "Passed tests for
(parser-generator-ll--generate-action-table-k-gt-1)"))
+
+(defun parser-generator-ll-test-parse ()
+ "Test `parser-generator-ll-parse'."
+ (message "Started tests for (parser-generator-ll-parse)")
+
+ (parser-generator-set-eof-identifier '$)
+ (parser-generator-set-e-identifier 'e)
+ (parser-generator-set-look-ahead-number 2)
+ (parser-generator-set-grammar
+ '(
+ (S A)
+ (a b)
+ (
+ (S (a A a a) (b A b a))
+ (A b e)
+ )
+ S
+ )
+ )
+ (parser-generator-process-grammar)
+ (parser-generator-ll-generate-table)
+ (setq
+ parser-generator-lex-analyzer--function
+ (lambda (index)
+ (let* ((string '((b 1 . 2) (b 2 . 3) (a 3 . 4)))
+ (string-length (length string))
+ (max-index index)
+ (tokens))
+ (while (and
+ (< (1- index) string-length)
+ (< (1- index) max-index))
+ (push (nth (1- index) string) tokens)
+ (setq index (1+ index)))
+ (nreverse tokens))))
+ (setq
+ parser-generator-lex-analyzer--get-function
+ (lambda (token)
+ (car token)))
+ (should
+ (equal
+ '(1 3) ;; Example is indexed from 1 so that is why they have '(2 4)
+ (parser-generator-ll-parse)))
+ (message "Passed example 5.16 p. 352")
+
+ (setq
+ parser-generator-lex-analyzer--function
+ (lambda (index)
+ (let* ((string '((b 1 . 2) (b 2 . 3)))
+ (string-length (length string))
+ (max-index index)
+ (tokens))
+ (while (and
+ (< (1- index) string-length)
+ (< (1- index) max-index))
+ (push (nth (1- index) string) tokens)
+ (setq index (1+ index)))
+ (nreverse tokens))))
+ (setq
+ parser-generator-lex-analyzer--get-function
+ (lambda (token)
+ (car token)))
+ (should-error
+ (parser-generator-ll-parse))
+ (message "Passed failing variant of example 5.16 p. 352")
+
+ (parser-generator-set-eof-identifier '$)
+ (parser-generator-set-e-identifier 'e)
+ (parser-generator-set-look-ahead-number 2)
+ (parser-generator-set-grammar
+ '(
+ (S A)
+ (a b)
+ (
+ (S e (a b A))
+ (A (S a a) b)
+ )
+ S
+ )
+ )
+ (parser-generator-process-grammar)
+ (parser-generator-ll-generate-table)
+ (setq
+ parser-generator-lex-analyzer--function
+ (lambda (index)
+ (let* ((string '((a 1 . 2) (b 2 . 3) (a 3 . 4) (a 4 . 5)))
+ (string-length (length string))
+ (max-index index)
+ (tokens))
+ (while (and
+ (< (1- index) string-length)
+ (< (1- index) max-index))
+ (push (nth (1- index) string) tokens)
+ (setq index (1+ index)))
+ (nreverse tokens))))
+ (setq
+ parser-generator-lex-analyzer--get-function
+ (lambda (token)
+ (car token)))
+ (should
+ (equal
+ '(1 2 0) ;; Example is indexed from 1 so that is why they have '(2 3 1)
+ (parser-generator-ll-parse)))
+ (message "Passed example 5.17 p. 355")
+
+ (parser-generator-set-eof-identifier '$)
+ (parser-generator-set-e-identifier 'e)
+ (parser-generator-set-look-ahead-number 1)
+ (parser-generator-set-grammar
+ '(
+ (S A)
+ (a b)
+ (
+ (S (a A S) b)
+ (A a (b S A))
+ )
+ S
+ )
+ )
+ (parser-generator-process-grammar)
+ (parser-generator-ll-generate-table)
+ (setq
+ parser-generator-lex-analyzer--function
+ (lambda (index)
+ (let* ((string '((a 1 . 2) (b 2 . 3) (b 3 . 4) (a 4 . 5) (b 5 . 6)))
+ (string-length (length string))
+ (max-index index)
+ (tokens))
+ (while (and
+ (< (1- index) string-length)
+ (< (1- index) max-index))
+ (push (nth (1- index) string) tokens)
+ (setq index (1+ index)))
+ (nreverse tokens))))
+ (setq
+ parser-generator-lex-analyzer--get-function
+ (lambda (token)
+ (car token)))
+ (should
+ (equal
+ '(0 3 1 2 1) ;; Example is indexed from 1 so that is why they have '(1 4 2
3 2)
+ (parser-generator-ll-parse)))
+ (message "Passed example 5.5 p. 340")
+
+ (parser-generator-set-eof-identifier '$)
+ (parser-generator-set-e-identifier 'e)
+ (parser-generator-set-look-ahead-number 1)
+ (parser-generator-set-grammar
+ '(
+ (E E2 T T2 F)
+ ("a" "(" ")" "+" "*")
+ (
+ (E (T E2))
+ (E2 ("+" T E2) e)
+ (T (F T2))
+ (T2 ("*" F T2) e)
+ (F ("(" E ")") "a")
+ )
+ E
+ )
+ )
+ (parser-generator-process-grammar)
+ (parser-generator-ll-generate-table)
+ (setq
+ parser-generator-lex-analyzer--function
+ (lambda (index)
+ (let* ((string '(("(" 1 . 2) ("a" 2 . 3) ("*" 3 . 4) ("a" 4 . 5) (")" 5 .
6)))
+ (string-length (length string))
+ (max-index index)
+ (tokens))
+ (while (and
+ (< (1- index) string-length)
+ (< (1- index) max-index))
+ (push (nth (1- index) string) tokens)
+ (setq index (1+ index)))
+ (nreverse tokens))))
+ (setq
+ parser-generator-lex-analyzer--get-function
+ (lambda (token)
+ (car token)))
+ (should
+ (equal
+ '(0 3 6 0 3 7 4 7 5 2 5 2) ;; Example is 1-indexed '(1 4 7 1 4 8 5 8 6 3 6
3)
+ (parser-generator-ll-parse)))
+ (message "Passed example 5.12 p. 346-347")
+
+ (parser-generator-set-eof-identifier '$)
+ (parser-generator-set-e-identifier 'e)
+ (parser-generator-set-look-ahead-number 1)
+ (parser-generator-set-grammar
+ '(
+ (S F)
+ ("(" "a" ")" "+")
+ (
+ (S F)
+ (S ("(" S "+" F ")"))
+ (F "a")
+ )
+ S
+ )
+ )
+ (parser-generator-process-grammar)
+ (parser-generator-ll-generate-table)
+ (setq
+ parser-generator-lex-analyzer--function
+ (lambda (index)
+ (let* ((string '(("(" 1 . 2) ("a" 2 . 3) ("+" 3 . 4) ("a" 4 . 5) (")" 5 .
6)))
+ (string-length (length string))
+ (max-index index)
+ (tokens))
+ (while (and
+ (< (1- index) string-length)
+ (< (1- index) max-index))
+ (push (nth (1- index) string) tokens)
+ (setq index (1+ index)))
+ (nreverse tokens))))
+ (setq
+ parser-generator-lex-analyzer--get-function
+ (lambda (token)
+ (car token)))
+ (should
+ (equal
+ '(1 0 2 2) ;; Example is 1 indexed '(2 1 3 3)
+ (parser-generator-ll-parse)))
+ (message "Passed example from Wikipedia")
+
+ (setq
+ parser-generator-lex-analyzer--function
+ (lambda (index)
+ (let* ((string '(("(" 1 . 2) ("a" 2 . 3) ("+" 3 . 4) ("a" 4 . 5)))
+ (string-length (length string))
+ (max-index index)
+ (tokens))
+ (while (and
+ (< (1- index) string-length)
+ (< (1- index) max-index))
+ (push (nth (1- index) string) tokens)
+ (setq index (1+ index)))
+ (nreverse tokens))))
+ (setq
+ parser-generator-lex-analyzer--get-function
+ (lambda (token)
+ (car token)))
+ (should-error
+ (parser-generator-ll-parse))
+ (message "Passed failing variant of example from Wikipedia")
+
+ (message "Passed tests for (parser-generator-ll-parse)"))
+
+(defun parser-generator-ll-test-translate ()
+ "Test `parser-generator-ll-translate'."
+ (message "Started tests for (parser-generator-ll-translate)")
+
+ (parser-generator-set-eof-identifier '$)
+ (parser-generator-set-e-identifier 'e)
+ (parser-generator-set-look-ahead-number 2)
+ (parser-generator-set-grammar
+ '(
+ (S A)
+ (a b)
+ (
+ (S
+ (a A a a (lambda(a b) (format "alfa %s laval" (nth 1 a))))
+ (b A b a (lambda(a b) (format "delta %s laval" (nth 1 a))))
+ )
+ (A
+ (b (lambda(a b) "sven"))
+ (e (lambda(a b) "ingrid"))
+ )
+ )
+ S
+ )
+ )
+ (parser-generator-process-grammar)
+ (parser-generator-ll-generate-table)
+ (setq
+ parser-generator-lex-analyzer--function
+ (lambda (index)
+ (let* ((string '((b 1 . 2) (b 2 . 3) (a 3 . 4)))
+ (string-length (length string))
+ (max-index index)
+ (tokens))
+ (while (and
+ (< (1- index) string-length)
+ (< (1- index) max-index))
+ (push (nth (1- index) string) tokens)
+ (setq index (1+ index)))
+ (nreverse tokens))))
+ (setq
+ parser-generator-lex-analyzer--get-function
+ (lambda (token)
+ (car token)))
+ (should
+ (equal
+ "delta ingrid laval"
+ (parser-generator-ll-translate)))
+ (message "Passed translation test 1")
+
+ (setq
+ parser-generator-lex-analyzer--function
+ (lambda (index)
+ (let* ((string '((b 1 . 2) (b 2 . 3) (b 3 . 4) (a 4 . 5)))
+ (string-length (length string))
+ (max-index index)
+ (tokens))
+ (while (and
+ (< (1- index) string-length)
+ (< (1- index) max-index))
+ (push (nth (1- index) string) tokens)
+ (setq index (1+ index)))
+ (nreverse tokens))))
+ (should
+ (equal
+ "delta sven laval"
+ (parser-generator-ll-translate)))
+ (message "Passed translation test 2")
+
+ (parser-generator-set-eof-identifier '$)
+ (parser-generator-set-e-identifier 'e)
+ (parser-generator-set-look-ahead-number 1)
+ (parser-generator-set-grammar
+ '(
+ (S A)
+ (a b)
+ (
+ (S
+ (a A S (lambda(a b) (format "alfa %s %s" (nth 1 a) (nth 2 a))))
+ (b (lambda(a b) "beta"))
+ )
+ (A
+ (a (lambda(a b) "delta"))
+ (b S A (lambda(a b) (format "gamma %s %s" (nth 1 a) (nth 2 a))))
+ )
+ )
+ S
+ )
+ )
+ (parser-generator-process-grammar)
+ (parser-generator-ll-generate-table)
+ (setq
+ parser-generator-lex-analyzer--function
+ (lambda (index)
+ (let* ((string '((a 1 . 2) (b 2 . 3) (b 3 . 4) (a 4 . 5) (b 5 . 6)))
+ (string-length (length string))
+ (max-index index)
+ (tokens))
+ (while (and
+ (< (1- index) string-length)
+ (< (1- index) max-index))
+ (push (nth (1- index) string) tokens)
+ (setq index (1+ index)))
+ (nreverse tokens))))
+ (setq
+ parser-generator-lex-analyzer--get-function
+ (lambda (token)
+ (car token)))
+ (should
+ (equal
+ "beta"
+ (parser-generator-ll-translate)))
+ (message "Passed translation test 3")
+
+ (message "Passed tests for (parser-generator-ll-translate)"))
+
+(defun parser-generator-ll-test-generate-table ()
+ "Test `parser-generator-ll-generate-table'."
+ (message "Started tests for (parser-generator-ll-generate-table)")
+
+ (parser-generator-set-eof-identifier '$)
+ (parser-generator-set-e-identifier 'e)
+ (parser-generator-set-look-ahead-number 2)
+ (parser-generator-set-grammar
+ '(
+ (S A)
+ (a b)
+ (
+ (S e (a b A))
+ (A (S a a) b)
+ )
+ S
+ )
+ )
+ (parser-generator-process-grammar)
+ (parser-generator-ll-generate-table)
+ ;; (message "parsing-table: %S" (parser-generator--hash-to-list
parser-generator-ll--table t))
+ (should
+ (equal
+ '(
+ ("((S) ($ $))"
+ (
+ ("(a b)" (reduce (a b ((A) ($ $))) 1))
+ ("($ $)" (reduce (e) 0))
+ )
+ )
+ ("((A) ($ $))"
+ (
+ ("(b $)" (reduce (b) 3))
+ ("(a a)" (reduce (((S) (a a)) a a) 2))
+ ("(a b)" (reduce (((S) (a a)) a a) 2))
+ )
+ )
+ ("((S) (a a))"
+ (
+ ("(a a)" (reduce (e) 0))
+ ("(a b)" (reduce (a b ((A) (a a))) 1))
+ )
+ )
+ ("((A) (a a))"
+ (
+ ("(b a)" (reduce (b) 3))
+ ("(a a)" (reduce (((S) (a a)) a a) 2))
+ ("(a b)" (reduce (((S) (a a)) a a) 2))
+ )
+ )
+ ("b" (("(b b)" pop) ("(b a)" pop) ("(b $)" pop)))
+ ("a" (("(a b)" pop) ("(a a)" pop) ("(a $)" pop)))
+ ("$" (("($ $)" accept)))
+ )
+ (parser-generator--hash-to-list
+ parser-generator-ll--table
+ t)))
+
+ (parser-generator-set-eof-identifier '$)
+ (parser-generator-set-e-identifier 'e)
+ (parser-generator-set-look-ahead-number 1)
+ (parser-generator-set-grammar
+ '(
+ (S A)
+ (a b)
+ (
+ (S (a A S) b)
+ (A a (b S A))
+ )
+ S
+ )
+ )
+ (parser-generator-process-grammar)
+ (parser-generator-ll-generate-table)
+ ;; (message "parsing-table: %S" (parser-generator--hash-to-list
parser-generator-ll--table t))
+ (should
+ (equal
+ '(
+ ("S"
+ (
+ ("(b)" (reduce (b) 1))
+ ("(a)" (reduce (a A S) 0))
+ )
+ )
+ ("A"
+ (
+ ("(b)" (reduce (b S A) 3))
+ ("(a)" (reduce (a) 2))
+ )
+ )
+ ("b" (("(b)" pop)))
+ ("a" (("(a)" pop)))
+ ("$" (("($)" accept)))
+ )
+ (parser-generator--hash-to-list
+ parser-generator-ll--table
+ t)))
+
+ (message "Passed tests for (parser-generator-ll-generate-table)"))
+
+(defun parser-generator-ll-test--valid-grammar-k-gt-1-p ()
+ "Test `parser-generator-ll--valid-grammar-k-gt-1-p'."
+ (message "Started tests for (parser-generator-ll--valid-grammar-k-gt-1-p)")
+
+ ;; Example 5.14 p. 350
+ ;; Example 5.15 p. 351
+ (parser-generator-set-e-identifier 'e)
+ (parser-generator-set-look-ahead-number 2)
+ (parser-generator-set-grammar
+ '(
+ (S A)
+ (a b)
+ (
+ (S (a A a a) (b A b a))
+ (A b e)
+ )
+ S
+ )
+ )
+ (parser-generator-process-grammar)
+ (should
+ (equal
+ (parser-generator-ll--valid-grammar-k-gt-1-p)
+ t))
+ (message "Passed first valid test")
+
+ (parser-generator-set-e-identifier 'e)
+ (parser-generator-set-look-ahead-number 2)
+ (parser-generator-set-grammar
+ '(
+ (S A)
+ (a b)
+ (
+ (S (a A a a) (b A b a))
+ (A b e a)
+ )
+ S
+ )
+ )
+ (parser-generator-process-grammar)
+ (should
+ (equal
+ (parser-generator-ll--valid-grammar-k-gt-1-p)
+ nil))
+ (message "Passed second valid test")
+
+ (message "Passed tests for (parser-generator-ll--valid-grammar-k-gt-1-p)"))
+
+(defun parser-generator-ll-test--generate-action-table-k-eq-1 ()
+ "Test `parser-generator-ll--generate-action-table-k-eq-1'."
+ (message "Started tests for
(parser-generator-ll--generate-action-table-k-eq-1)")
+
+ (parser-generator-set-eof-identifier '$)
+ (parser-generator-set-e-identifier 'e)
+ (parser-generator-set-look-ahead-number 1)
+ (parser-generator-set-grammar
+ '(
+ (S A)
+ (a b)
+ (
+ (S (a A S) b)
+ (A a (b S A))
+ )
+ S
+ )
+ )
+ (parser-generator-process-grammar)
+ (let* ((tables
+ (parser-generator-ll--generate-action-table-k-eq-1
+ (parser-generator-ll--generate-goto-table))))
+ ;; (message "tables: %S" tables)
+ (should
+ (equal
+ '(
+ (S
+ (
+ ((b) reduce (b) 1)
+ ((a) reduce (a A S) 0)
+ )
+ )
+ (A
+ (
+ ((b) reduce (b S A) 3)
+ ((a) reduce (a) 2)
+ )
+ )
+ (b (((b) pop)))
+ (a (((a) pop)))
+ ($ ((($) accept)))
+ )
+ tables
+ )))
+ (message "Passed Example 5.5 p. 340")
+
+ (parser-generator-set-eof-identifier '$)
+ (parser-generator-set-e-identifier 'e)
+ (parser-generator-set-look-ahead-number 1)
+ (parser-generator-set-grammar
+ '(
+ (E E2 T T2 F)
+ ("a" "(" ")" "+" "*")
+ (
+ (E (T E2))
+ (E2 ("+" T E2) e)
+ (T (F T2))
+ (T2 ("*" F T2) e)
+ (F ("(" E ")") "a")
+ )
+ E
+ )
+ )
+ (parser-generator-process-grammar)
+ (let ((tables
+ (parser-generator-ll--generate-action-table-k-eq-1
+ (parser-generator-ll--generate-goto-table))))
+ ;; (message "tables: %S" tables)
+ (should
+ (equal
+ '(
+ (E
+ (
+ (("a") reduce (T E2) 0)
+ (("(") reduce (T E2) 0)
+ )
+ )
+ (E2
+ (
+ (($) reduce (e) 2)
+ (("+") reduce ("+" T E2) 1)
+ ((")") reduce (e) 2)
+ )
+ )
+ (T
+ (
+ (("a") reduce (F T2) 3)
+ (("(") reduce (F T2) 3)
+ )
+ )
+ (T2
+ (
+ (("+") reduce (e) 5)
+ ((")") reduce (e) 5)
+ (("*") reduce ("*" F T2) 4)
+ (($) reduce (e) 5)
+ )
+ )
+ (F
+ (
+ (("a") reduce ("a") 7)
+ (("(") reduce ("(" E ")") 6)
+ )
+ )
+ ("a" ((("a") pop)))
+ ("+" ((("+") pop)))
+ ("*" ((("*") pop)))
+ (")" (((")") pop)))
+ ("(" ((("(") pop)))
+ ($ ((($) accept)))
+ )
+ tables)))
+ (message "Passed Example 5.12 p. 346-347")
+
+ (message "Passed tests for
(parser-generator-ll--generate-action-table-k-eq-1)"))
+
+(defun parser-generator-ll-test--valid-grammar-k-eq-1-p ()
+ "Test `parser-generator-ll--valid-grammar-k-eq-1-p'."
+ (message "Started tests for (parser-generator-ll--valid-grammar-k-eq-1-p)")
+
+ (parser-generator-set-eof-identifier '$)
+ (parser-generator-set-e-identifier 'e)
+ (parser-generator-set-look-ahead-number 1)
+ (parser-generator-set-grammar
+ '(
+ (S A B)
+ (a b)
+ (
+ (S (a A S) b B)
+ (A a (b S A))
+ (B a)
+ )
+ S
+ )
+ )
+ (parser-generator-process-grammar)
+ (should
+ (equal
+ nil
+ (parser-generator-ll--valid-grammar-k-eq-1-p)))
+
+ (parser-generator-set-eof-identifier '$)
+ (parser-generator-set-e-identifier 'e)
+ (parser-generator-set-look-ahead-number 1)
+ (parser-generator-set-grammar
+ '(
+ (S A B)
+ (a b c)
+ (
+ (S (a A S) b B)
+ (A a (b S A))
+ (B c)
+ )
+ S
+ )
+ )
+ (parser-generator-process-grammar)
+ (should
+ (equal
+ t
+ (parser-generator-ll--valid-grammar-k-eq-1-p)))
+
+ (message "Passed tests for (parser-generator-ll--valid-grammar-k-eq-1-p)"))
+
+
+(defun parser-generator-ll-test ()
+ "Run test."
+
+ ;; Helpers
+ (parser-generator-ll-test--generate-goto-table)
+
+ ;; k > 1
+ (parser-generator-ll-test--generate-action-table-k-gt-1)
+ (parser-generator-ll-test--valid-grammar-k-gt-1-p)
+
+ ;; k = 1
+ (parser-generator-ll-test--generate-action-table-k-eq-1)
+ (parser-generator-ll-test--valid-grammar-k-eq-1-p)
+
+ ;; Main stuff
+ (parser-generator-ll-test-generate-table)
+ (parser-generator-ll-test-parse)
+ (parser-generator-ll-test-translate))
+
+
+(provide 'parser-generator-ll-test)
+
+;;; parser-generator-ll-test.el ends here
diff --git a/test/parser-generator-test.el b/test/parser-generator-test.el
index 1fab673dc2..45dd865238 100644
--- a/test/parser-generator-test.el
+++ b/test/parser-generator-test.el
@@ -967,27 +967,41 @@
(message "Passed tests for (parser-generator--valid-terminal-p)"))
-(defun parser-generator-test--merge-max-terminals ()
- "Test `parser-generator--merge-max-terminals'."
- (message "Starting tests for (parser-generator--merge-max-terminals)")
+(defun parser-generator-test--merge-max-terminal-sets ()
+ "Test `parser-generator--merge-max-terminal-sets'."
+ (message "Starting tests for (parser-generator--merge-max-terminal-sets)")
+ (parser-generator-set-eof-identifier '$)
+ (parser-generator-set-e-identifier 'e)
+ (parser-generator-set-look-ahead-number 2)
+ (parser-generator-set-grammar '((S A B) (a b) ((S A) (S (B)) (B a) (A a) (A
(b a))) S))
+ (parser-generator-process-grammar)
+
+ ;; Example 5.13 p. 348
+ (should
+ (equal
+ '((a b) (b) (b a))
+ (parser-generator--merge-max-terminal-sets
+ '((a b b) (e))
+ '((b) (b a b))
+ t)))
+
+ ;; Example 5.14 p. 350
(should
(equal
- '(a b e)
- (parser-generator--merge-max-terminals
- '(a)
- '(b e)
- 3)))
+ '((a a) (a b) (b b))
+ (parser-generator--merge-max-terminal-sets
+ '((a b) (a e a) (b b) (b e b))
+ nil)))
(should
(equal
- '(a e)
- (parser-generator--merge-max-terminals
- '(a e)
- '(b e)
- 3)))
+ '(($ $) (a $) (a a))
+ (parser-generator--merge-max-terminal-sets
+ '((a e) ($))
+ '(($ $) (a $)))))
- (message "Passed tests for (parser-generator--merge-max-terminals)"))
+ (message "Passed tests for (parser-generator--merge-max-terminal-sets)"))
(defun parser-generator-test--get-list-permutations ()
"Test `parser-generator--get-list-permutations'."
@@ -1051,6 +1065,87 @@
(message "Passed tests for
(parser-generator-test--generate-list-of-symbol)"))
+(defun parser-generator-test--calculate-max-terminal-count ()
+ "Test `parser-generator-calculate-max-terminal-count'."
+ (message "Starting tests for
(parser-generator-calculate-max-terminal-count)")
+
+ (parser-generator-set-look-ahead-number 1)
+ (parser-generator-set-grammar '((S A B) ("a" "b") ((S A) (S (B)) (B "a") (A
"a") (A ("b" "a"))) S))
+ (parser-generator-process-grammar)
+
+ (should
+ (equal
+ (parser-generator-calculate-max-terminal-count
+ '(("a" "a") ("b") ("a" e "b" "c") (B "a" "b" "c")))
+ 2))
+ (should
+ (equal
+ (parser-generator-calculate-max-terminal-count
+ '(("a") ("b") ("a" e "b" "c") (B "a" "b" "c")))
+ 1))
+
+ (message "Passed tests for (parser-generator-calculate-max-terminal-count)"))
+
+(defun parser-generator-test--generate-sets-of-terminals ()
+ "Test `parser-generator--generate-sets-of-terminals'."
+ (message "Starting tests for (parser-generator--generate-sets-of-terminals)")
+
+ (parser-generator-set-look-ahead-number 1)
+ (parser-generator-set-grammar '((S A B) ("a" "b") ((S A) (S (B)) (B "a") (A
"a") (A ("b" "a"))) S))
+ (parser-generator-process-grammar)
+
+ (should
+ (equal
+ (parser-generator-generate-sets-of-terminals
+ '(("a" "a") ("b") ("b" "a") ("a" "b" "a") ("a" e S "b" "a") ("b" "b" A))
+ 2)
+ '(("a" "a") ("b" "a") ("a" "b") ("b" "b"))))
+
+ (should
+ (equal
+ (parser-generator-generate-sets-of-terminals
+ '(("a" "a") ("b") ("b" "a") ("a" "b" "a") ("a" e S "b" "a") ("b" "b" A))
+ 1)
+ '(("a") ("b"))))
+
+ (should
+ (equal
+ (parser-generator-generate-sets-of-terminals
+ '(("a" "a") ("b") ("b" "a") ("a" "b" "a") ("a" e S "b" "a") ("b" "b" A))
+ 3)
+ '(("a" "b" "a"))))
+
+ (should
+ (equal
+ (parser-generator-generate-sets-of-terminals
+ '(("a" e) ("b") ("b" "a") ("a" "b" "a") ("a" e S "b" "a") ("b" "b" A))
+ 1)
+ '(("a") ("b"))))
+
+ (message "Passed tests for (parser-generator--generate-sets-of-terminals)"))
+
+(defun parser-generator-test--generate-terminal-saturated-first-set ()
+ "Test `parser-generator-generate-terminal-saturated-first-set'."
+ (message "Starting tests for
(parser-generator-generate-terminal-saturated-first-set)")
+
+ (parser-generator-set-look-ahead-number 1)
+ (parser-generator-set-grammar '((S A B) ("a" "b") ((S A) (S (B)) (B "a") (A
"a") (A ("b" "a"))) S))
+ (parser-generator-process-grammar)
+
+ (should
+ (equal
+ (parser-generator-generate-terminal-saturated-first-set
+ '(("a" "b") ("a" "a" e) ("b") ("a" e)))
+ '(("a" "b") ("a" "a"))))
+
+ (should
+ (equal
+ (parser-generator-generate-terminal-saturated-first-set
+ '(("a" "b") ("a" "a" e) ("b" "b") ("a" e)))
+ '(("a" "b") ("a" "a") ("b" "b"))))
+
+ (message "Passed tests for
(parser-generator-generate-terminal-saturated-first-set)"))
+
(defun parser-generator-test ()
"Run test."
;; (setq debug-on-error t)
@@ -1061,7 +1156,7 @@
(parser-generator-test--get-grammar-look-aheads)
(parser-generator-test--get-grammar-rhs)
(parser-generator-test--get-list-permutations)
- (parser-generator-test--merge-max-terminals)
+ (parser-generator-test--merge-max-terminal-sets)
(parser-generator-test--sort-list)
(parser-generator-test--valid-context-sensitive-attribute-p)
(parser-generator-test--valid-context-sensitive-attributes-p)
@@ -1074,6 +1169,9 @@
(parser-generator-test--valid-sentential-form-p)
(parser-generator-test--valid-terminal-p)
(parser-generator-test--generate-f-sets)
+ (parser-generator-test--calculate-max-terminal-count)
+ (parser-generator-test--generate-sets-of-terminals)
+ (parser-generator-test--generate-terminal-saturated-first-set)
;; Algorithms
(parser-generator-test--first)
- [elpa] externals/parser-generator fe0decba88 50/82: Passed one test for LLk where k=1, (continued)
- [elpa] externals/parser-generator fe0decba88 50/82: Passed one test for LLk where k=1, Christian Johansson, 2022/05/12
- [elpa] externals/parser-generator 72bbadddc0 51/82: Added TODO items, Christian Johansson, 2022/05/12
- [elpa] externals/parser-generator 2e2496d51f 54/82: Added notes, Christian Johansson, 2022/05/12
- [elpa] externals/parser-generator 2598402cc7 56/82: Added TODO item, Christian Johansson, 2022/05/12
- [elpa] externals/parser-generator 7f3c384b6d 55/82: Passing more LLk tests, Christian Johansson, 2022/05/12
- [elpa] externals/parser-generator 0856bb7784 58/82: Started on refactor were k=1 will be treated with different algorithm, Christian Johansson, 2022/05/12
- [elpa] externals/parser-generator 2181545d26 64/82: Implemented test for validation of LL(1) grammar, Christian Johansson, 2022/05/12
- [elpa] externals/parser-generator 4051737aeb 65/82: Added TODO item for LL(k) translation, Christian Johansson, 2022/05/12
- [elpa] externals/parser-generator 08af836006 69/82: More work on SDT for LL grammar, Christian Johansson, 2022/05/12
- [elpa] externals/parser-generator 7d87a2d154 79/82: Implemented exported LL(k) and LL(1) parser, Christian Johansson, 2022/05/12
- [elpa] externals/parser-generator 75323b10e5 81/82: Merge branch 'feature/llk-parser',
Christian Johansson <=
- [elpa] externals/parser-generator db91a5f203 82/82: Removed unused function, Christian Johansson, 2022/05/12
- [elpa] externals/parser-generator 315e40eff8 10/82: More work on LL table generation, Christian Johansson, 2022/05/12
- [elpa] externals/parser-generator 34ab0f1718 21/82: More various tweaks, Christian Johansson, 2022/05/12
- [elpa] externals/parser-generator 064bd259ff 26/82: Passing LLk validation tests, Christian Johansson, 2022/05/12
- [elpa] externals/parser-generator f0de6698b9 29/82: Added todo item, Christian Johansson, 2022/05/12
- [elpa] externals/parser-generator 97919972a7 35/82: Improved debug message, added TODO item, Christian Johansson, 2022/05/12
- [elpa] externals/parser-generator e55a3f8a37 38/82: Updated TODO items, Christian Johansson, 2022/05/12
- [elpa] externals/parser-generator 7ee5504003 45/82: More work on LLk parser, Christian Johansson, 2022/05/12
- [elpa] externals/parser-generator fd2f90dd81 47/82: Added TODO-item, Christian Johansson, 2022/05/12
- [elpa] externals/parser-generator b41b2dbffe 68/82: Removed debug output, Christian Johansson, 2022/05/12