forked from metawilm/cl-python
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathparser.lisp
More file actions
126 lines (108 loc) · 5.54 KB
/
Copy pathparser.lisp
File metadata and controls
126 lines (108 loc) · 5.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: CLPYTHON.PARSER; Readtable: PY-AST-USER-READTABLE -*-
;;
;; This software is Copyright (c) Franz Inc. and Willem Broekema.
;; Franz Inc. and Willem Broekema grant you the rights to
;; distribute and use this software as governed by the terms
;; of the Lisp Lesser GNU Public License
;; (http://opensource.franz.com/preamble.html),
;; known as the LLGPL.
(in-package :clpython.parser)
(in-syntax *ast-user-readtable*)
;;;; Parser for Python code
(defvar *default-yacc-version* #+allegro :allegro-yacc #-allegro :cl-yacc)
(defparameter *catch-yacc-conditions* t
"Whether to catch YACC conditions, and translate them into Python exceptions.
\(Disable to debug the grammar rules.)")
(defgeneric parse (thing &rest options)
(:documentation "Parse THING (pathname or string); return AST.
Most important options:
:YACC-VERSION -- :allegro-yacc (default) or :cl-yacc
:ONE-EXPR -- only return first form read, not wrapped in module/suite
:TAB-WIDTH -- width of one tab character in spaces")
(:method ((x string) &rest options &key (yacc-version *default-yacc-version*) one-expr record-source-location tab-width)
(declare (ignore one-expr record-source-location tab-width))
(let ((lexer (apply #'make-lexer yacc-version x (sans options :one-expr :record-source-location))))
(apply #'parse-module-with-yacc yacc-version lexer (sans options :tab-width :yacc-version))))
(:method ((x pathname) &rest options)
(apply #'parse (slurp-file x) options))
(:method ((x stream) &rest options)
(apply #'parse (slurp-file x) options)))
(defmacro with-source-locations (&body body)
`(let (#+clpython-source-level-debugging
(*python-form->source-location* (make-weak-key-hash-table :test 'eq)))
,@body))
(defun parse-module-with-yacc (yacc-version lexer &key one-expr (record-source-location *python-form->source-location*))
"Collect all parsed top-level forms. If RECORD-SOURCE-LOCATION, the (new or existing)
source location hash-table is returned as second value."
(let ((*python-form->source-location*
(case record-source-location
((nil))
((t) (make-weak-key-hash-table :test 'eq))
(t record-source-location))))
(let (forms)
(loop (multiple-value-bind (form eof-p)
(parse-form-with-yacc yacc-version lexer)
(push form forms)
(when eof-p
(setf forms (nreverse forms))
(return))))
(cond (one-expr
(assert (= (length forms) 1) ()
"Got ~A forms, while only one expected (due to :ONE-EXPR), in AST for ~S." (length forms))
(setf forms (car forms)))
(t
(setf forms `([module-stmt] ([suite-stmt] ,forms)))))
(if *python-form->source-location*
(values forms *python-form->source-location*)
forms))))
(defparameter *signal-toplevel-form-finished-conditions* nil
"Whether to signal toplevel-form-finished-condition between top-level forms.")
(defmacro with-parser-eof-detection ((at-real-eof-var) &body body)
(check-type at-real-eof-var symbol)
`(let* ((*lex-fake-eof-after-toplevel-form* t)
(,at-real-eof-var nil))
(declare (special *lex-fake-eof-after-toplevel-form*))
(handler-bind ((next-eof-real (lambda (c)
(declare (ignore c))
(setf ,at-real-eof-var t)
(unless *signal-toplevel-form-finished-conditions*
(invoke-restart (find-restart 'muffle)))))
(next-eof-fake-after-toplevel-form
(lambda (c)
(declare (ignore c))
(unless *signal-toplevel-form-finished-conditions*
(invoke-restart (find-restart 'muffle))))))
,@body)))
(defgeneric handle-parser-condition (yacc-version condition lexer))
(defmacro with-parser-conditions-handled ((yacc-version lexer) &body body)
`(handler-bind ((condition (lambda (c)
(when *catch-yacc-conditions*
(handle-parser-condition ,yacc-version c ,lexer)))))
,@body))
(defgeneric parse-form-with-yacc (yacc-version lexer)
(:documentation "Parse one top-level form with the given yacc. Returns FORM, EOF-P.")
(:method :around (yacc-version lexer)
(with-parser-eof-detection (at-real-eof)
(with-parser-conditions-handled (yacc-version lexer)
(let ((res (call-next-method)))
(values res at-real-eof)))))
(:method (v lexer)
(declare (ignore lexer))
(error "Parser ~S is unavailable." v)))
(defun parse-with-replacements (string replacements &key (warn-unused t) parse-options)
"Parse STRING, but replace certain tokens in the resulting AST.
Used to parse a template string, then fill in certain \"gaps\".
REPLACEMENTS is list: ((old . new) ...)
E.g. to replace identifier 'foo' with 'bar', use this replacement:
( ([identifier-expr] {foo}) . ([identifier-expr] {foo}) )"
(let ((ast (apply #'parse string parse-options)))
(if warn-unused
(loop for (old . new) in replacements
do (let ((new-ast (subst new old ast :test 'equalp)))
(when (tree-equal ast new-ast)
(warn "[parse-with-replacements] This replacement is unused: ~S => ~S."
old new))
(setf ast new-ast)))
(loop for (old . new) in replacements
do (setf ast (nsubst new old ast :test 'equalp))))
ast))