-
Notifications
You must be signed in to change notification settings - Fork 0
/
csharp-analysis.el
1533 lines (1190 loc) · 54.8 KB
/
csharp-analysis.el
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
;;; csharp-analysis.el -- C# source code analysis in emacs
;;
;; Author: Dino Chiesa <[email protected]>
;; Maintainer: Dino Chiesa <[email protected]>
;; Created: January 2011
;; Modified: February 2011
;; Version: 0.2
;; Keywords: c# languages oop mode
;; X-URL: http://code.google.com/p/csharpmode/
;;
;;
;;; Commentary:
;;
;; This module provides sourcecode analysis to support the cscomp
;; (csharp-completion) module.
;;
;; ------------------------------------------------------------------
;;
;; The original csharp-completion package I wrote used the semantic
;; package from cedet. According to the description of the semantic
;; package, it is the perfect tool for the job. In practice, semantic is
;; complex, opaque, and brittle. In general I find it a bloody
;; nightmare to use. The documentation is absent or useless. Examining
;; the code reveals that it is disordered and haphazard. In operation,
;; it treats C# namespaces as classes, and no matter what I tried I
;; couldn't get it to treat the namespace as an independent kind of
;; thing. It uses cute names for various features, in lieu of names that
;; actually convey meaning. There are many other problems. Because of
;; all that, it took me a great effort to understand enough to make it
;; somewhat usable for my purpose. But finally, I got there.
;;
;; But then! I upgraded from emacs v22 to 23.2, and semantic blew up.
;; Even the wisent file broke. Back to square one. I have asked for
;; support and assistance multiple times from the author and have never
;; received a response.
;;
;; Rather than once again invest time in working with semantic, I've
;; moved to the Ast magic from SharpDevelop -
;; ICSharpCode.NRefactory.dll. The documentation for it is no better
;; than that for Semantic, but... it works as I expect it to. It is
;; doscoverable, and it behaves rationally. It is complete.
;;
;; The .NET Dll is used via the CscompShell, which is a powershell that
;; loads the CscompUtilities.dll. One of the methods in that DLL returns
;; an abstract syntax tree, formatted as a lisp s-expression. It
;; constructs that s-exp using the NRefactory assembly.
;;
;; The way it works is to save the current buffer to a temporary file,
;; then run the syntax analysis on that temporary file, before deleting
;; the temp file. Then, the code running in powershell loops through all the
;; source elements in the file, and emits a hierarchically arranged set
;; of lisp structures representing those elements. Each element is
;; known as a "tag", borrowing from the language of the semantic
;; package. A tag represents a namespace. the namespace tag has
;; children, which are "type" tags. Each type tag may have fields
;; methods and properties as children. A method has a code block as a
;; child. And so on.
;;
;; This module exports functions that do things with that abstract
;; syntax tree - such as:
;;
;; - list all of the namespaces in the current buffer
;; - list all of the types in the given namespace
;; - list all of the methods/fields/properties for the given type
;; - list the in-scope local variables for the current point in the buffer
;; - return the parent tag of a given tag
;;
;; All of this syntax analysis is performed in support of code
;; completion (intellisense).
;;
;; When the buffer changes, a flag is set that indicates that the AST is
;; out of date. When the buffer stops changing, the analysis is run
;; again. Each analysis takes a second or so for a 1000-line module.
;;
(require 'cscomp-base) ;; cscomp-log
(require 'csharp-shell) ;; csharp-shell-invoke-shell-fn
(require 'cl) ;; first, second, dolist, etc
(defvar csharp-analysis-last-change-time nil
"Time of last buffer change.")
(make-variable-buffer-local 'csharp-analysis-last-change-time)
(defvar csharp-analysis-syntax-tree nil
"buffer-local result of the most recent syntactic analysis of
the current buffer. It is a lisp s-expression. In general, this
variable should not be referenced directly. Instead
applications should call
`csharp-analysis-get-analysis'")
(make-variable-buffer-local 'csharp-analysis-syntax-tree)
(defvar csharp-analysis-is-running nil
"If t, source code analysis is running for the current C# buffer.")
(make-variable-buffer-local 'csharp-analysis-is-running)
(defvar csharp-analysis-no-changes-timeout 0.75
"Time to wait after last change before starting syntax analysis. In the
timer event, if the last change is less that this amount of time ago, then
analysis is NOT performed. ")
(make-variable-buffer-local 'csharp-analysis-no-changes-timeout)
(defvar csharp-analysis-timer-interval 2
"Interval in seconds for timer events. This is not the interval on
which analysis gets performed; it's the interval on which we check to
see if a new analysis is necessary. ")
(make-variable-buffer-local 'csharp-analysis-timer-interval)
(defvar csharp-analysis-syntax-tree-out-of-date t
"a boolean indicating whether this module thinks the analysis
of the sourcecode buffer is out of date. When this is true, and
when the results of an analysis is requested via a call to
`csharp-analysis-get-analysis', then a new analysis is
performed.")
(make-variable-buffer-local 'csharp-analysis-syntax-tree-out-of-date)
(defvar csharp-analysis-timer nil
"a timer object.")
(defvar csharp-analysis--query-cache nil
"an alist. For internal use of the csharp-analysis module only.")
(defalias 'csharp-analysis-float-time
(if (fboundp 'float-time)
'float-time
(if (featurep 'xemacs)
(lambda ()
(multiple-value-bind (s0 s1 s2) (values-list (current-time))
(+ (* (float (ash 1 16)) s0) (float s1) (* 0.0000001 s2)))))))
(defun csharp-analysis-after-change-fn (start stop len)
"invoked after the buffer changes. This fn simply records the
time of the last change.
"
(cscomp-log 4 "after-change: recording change time")
(setq csharp-analysis-last-change-time (csharp-analysis-float-time)))
(defun csharp-analysis-after-save-fn ()
"a fn that gets invoked after the save of a buffer. If there is a local
variable called `csharp-analysis-is-running', then cscomp immediately
analyzes the sourcecode in the buffer."
(if (local-variable-p 'csharp-analysis-is-running (current-buffer))
(progn
;;(setq csharp-analysis-syntax-tree-out-of-date t)
;;(cscomp-log 3 "after-save: marking out of date")
;;(csharp-analysis-analyze-buffer)
)))
(defun csharp-analysis-can-parse-file (file-name)
"return t if the buffer is a C# source file."
(unless (stringp file-name)
(error "Invalid file-name"))
(string-match "\\.cs$" file-name))
(defun csharp-analysis-timer-event (buffer)
"This fn gets called by a timer, on a regular interval. It
marks the buffer as out of date, in other words due for
sourcecode analysis, by setting `csharp-analysis-syntax-tree-out-of-date'
if appropriate.
A buffer is considered to be out of date when more than
`csharp-analysis-no-changes-timeout' seconds have elapsed since the last
change, and there have been other changes that have been made
during that interval. This is checked by comparing the current
time to `csharp-analysis-last-change-time', the last change time that is
recorded in `csharp-analysis-after-change-fn'.
If the timeout period has not expired since the last change,
it indicates that the user is actively changing the buffer, eg
typing, and so there's no sense marking the buffer out of date at
the moment.
If the user later requests a sourcecode analysis (abstract syntax
tree, or AST) for the buffer by implicitly invoking
`csharp-analysis-get-analysis', if the sourcecode
analysis has previously been marked out of date, then cscomp
computes a new analysis. If it is not marked out of date, then
cscomp returns the existing analysis.
When a buffer is initially loaded, the analysis is marked out of
date, so that it is always calculated upon the first request for
it.
"
(when (buffer-live-p buffer)
(with-current-buffer buffer
(if (and csharp-analysis-last-change-time
(not csharp-analysis-syntax-tree-out-of-date))
(let ((elapsed (- (csharp-analysis-float-time) csharp-analysis-last-change-time)))
(cscomp-log 4 "timer: elapsed since last change: %s"
(prin1-to-string elapsed))
;; see if the buffer has stopped changing
(when (> elapsed csharp-analysis-no-changes-timeout)
(setq csharp-analysis-syntax-tree-out-of-date t)))
(cscomp-log 4 "timer: no change since previous analysis.")))))
(defun csharp-analysis-stop-timer ()
(when csharp-analysis-timer
(cancel-timer csharp-analysis-timer)
(setq csharp-analysis-timer nil)))
(defun csharp-analysis-get-analysis ()
"Returns the s-expression representing the sourcecode analysis of the
current buffer. If the current sourcecode analysis is out-of-date, then
the analysis is performed anew, and that new result is returned. Several
events can trigger the out-of-date condition: addition of a newline (\n)
into the buffer; saving the buffer; other things. This module detects
these events in the appropriate emacs hooks - such as after-save-hook, or
the after-change-functions - and then sets the
`csharp-analysis-syntax-tree-out-of-date' variable to a non-nil value.
When this function is called and that variable is true, a new analysis is
performed. This may take some time, as it requires saving the buffer
contents to a temporary file, analyzing, then removing the file."
(if (or csharp-analysis-syntax-tree-out-of-date
(not csharp-analysis-syntax-tree))
(csharp-analysis-analyze-buffer)
csharp-analysis-syntax-tree))
(defun csharp-analysis-create-temp-filename (file-name)
(unless (stringp file-name)
(error "Invalid file-name"))
(let* ((prefix "cscomp")
(temp-name (concat (file-name-sans-extension file-name)
"_" prefix
"."
(file-name-extension file-name))))
(cscomp-log 3 "create-temp-filename: orig=%s temp=%s" file-name temp-name)
temp-name))
(defun csharp-analysis-analyze-buffer ()
"Actually do the analysis, by calling out to the AnalyzeSource method
available within the CscompShell. That method calls out to the NRefactory
library to perform the source analysis. The result is the lisp
s-expression that describes the syntax tree of the buffer. "
;; I'm not sure if timers create a multi-thread scenario or not.
;; in case they do, and in case there's an analysis already running,
;; let's just hack it and return the existing value.
(if (and csharp-analysis-is-running
csharp-analysis-syntax-tree)
(progn
(cscomp-log 4 "csharp-analysis-analyze-buffer: analysis is already running.")
;;;
csharp-analysis-syntax-tree)
(let
((temp-file-name (csharp-analysis-create-temp-filename (buffer-file-name)))
(delete-temp-file (lambda ()
(when (file-exists-p temp-file-name)
(delete-file temp-file-name)
(cscomp-log 3 "deleted temp file %s" temp-file-name)))))
;; handle exceptions that occur during file creation,
;; saving, and analysis, via condition-case
(condition-case exc1
(progn
(setq csharp-analysis-is-running t)
;; obtain mutex?
(cscomp-log 1 "re-analyzing... ")
(funcall delete-temp-file) ;; just in case it exists now
;; right here - maybe temporarily comment out the current
;; line? maybe?
;; The problem is that NRefactory *quits* its analysis when
;; it encounters some classes of syntax errors. which
;; errors? I don't know. But in the case where a person is
;; asking for completions "right here", sometimes NRefactory
;; chokes on the incomplete line of code, which the user is
;; asking for completions on. As a result it doesn't emit a
;; CompilationUnit that contains any members following that
;; point. If a variable gets initialized based on a field
;; that lies AFTER the current line in the buffer, then the
;; csharp-completion stuff won't work properly.
;; Just removing, or commenting out, the offending line
;; allows NRefactory to continue its analysis. That might be
;; the right thing to do here. Have to figure out
;; why/when/how that should be done.
;; write the buffer to the temporary file
(write-region nil nil temp-file-name nil 7)
(cscomp-log 3 "wrote temp file %s" temp-file-name)
(setq csharp-analysis-syntax-tree
(csharp-shell-invoke-shell-fn "GetAstForSourceFile" temp-file-name))
(funcall delete-temp-file)
(setq csharp-analysis-last-change-time nil
csharp-analysis-syntax-tree-out-of-date nil
csharp-analysis-is-running nil
csharp-analysis--query-cache nil) ;; clear cache
;; release mutex?
csharp-analysis-syntax-tree)
(progn
(error "analysis failed.")
(funcall delete-temp-file)
(setq csharp-analysis-is-running nil)
csharp-analysis-syntax-tree)))))
;;;###autoload
(define-minor-mode csharp-analysis-mode
"A minor mode to do on-the-fly c# source code parsing and analysis.
When this function is called interactively, it toggles the minor
mode.
With arg, turn csharp-analysis mode on if and only if arg
is positive.
When this mode is on, emacs periodically analyzes the current
buffer and stores a representation of the abstract syntax
tree (AST) for the C# source code. This AST is then used to
facilitate smart code completion, something like emacs'
autocomplete function, but smarter. See
`cscomp-complete-at-point'.
"
nil nil nil
(cond
;; turn the mode ON.
(csharp-analysis-mode
(if (not (csharp-analysis-can-parse-file buffer-file-name))
(cscomp-log 2 "cscomp cannot check syntax in buffer %s" (buffer-name))
(add-hook 'after-change-functions 'csharp-analysis-after-change-fn nil t)
(add-hook 'after-save-hook 'csharp-analysis-after-save-fn nil t)
(add-hook 'kill-buffer-hook 'csharp-analysis-stop-timer nil t)
;; set a timer to fire on an interval
(setq csharp-analysis-timer
(run-at-time nil csharp-analysis-timer-interval
'csharp-analysis-timer-event
(current-buffer)))
;; do the first analysis?
;;(csharp-analysis-analyze-buffer)
(cscomp-log 1 "csharp-analysis-mode: setting vars for initial conditions");
(setq csharp-analysis-is-running nil)))
;; turn the mode OFF.
(t
(remove-hook 'after-change-functions 'csharp-analysis-after-change-fn t)
(remove-hook 'after-save-hook 'csharp-analysis-after-save-fn t)
(remove-hook 'kill-buffer-hook 'csharp-analysis-stop-timer t)
(csharp-analysis-stop-timer)
(setq csharp-analysis-is-running nil))))
;; ==================================================================
;; ==================================================================
(defun csharp-analysis-useful-taglist (ast)
"returns the useful taglist from an AST. The first, toplevel
AST is contains the string 'CompilationUnit as its car. This fn
trims that, if it is present, and returns the list of children. If
it is not present, this fn just returhs the original taglist.
"
(if (and
(symbolp (first ast))
(string= 'CompilationUnit (symbol-name (first ast))))
(progn
;;(message "useful: %s" (prin1-to-string (cdadr ast)))
(cdadr ast))
ast))
;; -------------------------------------------------------
(defun csharp-analysis-get-toplevel-tags-from-ast (ast tag-type)
"From the given AST, get toplevel tags of the given TAG-TYPE.
For example, pass TAG-TYPE of \"type\" to get all the toplevel
using clauses in an AST. This would work for an AST representing
a namespace.
This fn does not consider nested elements.
Example:
(csharp-analysis-get-toplevel-tags-from-ast myast \"type\")
See also: `csharp-analysis-get-toplevel-tagnames-from-ast'.
"
(when (not tag-type) (error "tag-type must be non-nil"))
(let ((result (list)))
(dolist (entry (csharp-analysis-useful-taglist ast)) ;; loop over the list
(if (and
(consp entry)
(symbolp (first entry))
(string= tag-type (csharp-analysis-tag-flavor entry)))
(setq result (cons entry result))))
(reverse result)))
(defun csharp-analysis-get-toplevel-tags (tag-type)
"Get toplevel tags of the given TAG-TYPE from the AST for the
current buffer. For example, pass TAG-TYPE of \"using\" to get
all the toplevel using clauses in an AST. Does not get nested
elements.
Example:
(csharp-analysis-get-toplevel-tags \"attribute\")
See also: `csharp-analysis-get-toplevel-tagnames'.
"
(interactive "stag type: ")
(csharp-analysis-get-toplevel-tags-from-ast (csharp-analysis-get-analysis) tag-type))
;; -------------------------------------------------------
(defun csharp-analysis-nested-tags (node &optional include-params)
"Get the nested tags (or nodes) for the given NODE (or tag).
In a namespace node, nested tags would be nodes representing
types, enumerated in an element called \"children\". In a type,
the child nodes are also in a \"children\" node, and represent
methods, fields, and properties. In a method, the children are
the expressions that define the method logic, and are available
in a node called \"block\". Within a block, an \"if\" node has
children in \"then\" and \"else\" clauses. A \"trycatch\" node
has children in the \"try\" clause, all \"catch\" clauses, and
the \"finally\" clause, if any. And so on.
This fn is intended to support
`csharp-analysis-local-variables', which gets a list of local
variables. To do that, the fn needs to know the current scope
tree.
"
(when (not node) (return nil))
(let* ((sname (symbol-name (first node)))
(nested-tags
(cond
((string= sname "if")
(append
(assoc 'then node)
(csharp-analysis-get-toplevel-tags-from-ast node "elseif") ;; there may be multiple of these
(assoc 'else node))) ;; the else clause may be nil
((or
(string= sname "foreach")
(string= sname "for")
(string= sname "block")
(string= sname "then")
(string= sname "else")
(string= sname "elseif")
(string= sname "finally")
(string= sname "catch")
(string= sname "try"))
(cdr node)) ;; every child except the containing scope
((string= sname "trycatch")
(append
(csharp-analysis-get-toplevel-tags-from-ast node "try")
(csharp-analysis-get-toplevel-tags-from-ast node "catch")
(csharp-analysis-get-toplevel-tags-from-ast node "finally")))
((string= sname "property")
(append
(csharp-analysis-get-toplevel-tags-from-ast node "get") ;; may be nil
(csharp-analysis-get-toplevel-tags-from-ast node "set"))) ;; may be nil
(t
(let (c)
(setq c (assoc 'block node))
(if c (list c)
(setq c (assoc 'children node))
(if c (cdr c)
nil))))
;;(csharp-analysis-get-toplevel-tags-from-ast node "block")
;;(csharp-analysis-get-toplevel-tags-from-ast node "children")
)))
(if include-params
(let ((params (cadr (assoc 'params node))))
(if params
(setq nested-tags (append (list params) nested-tags)))))
nested-tags))
(defun csharp-analysis-get-local-arguments-from-ast (ast)
"From the given AST, get all the local arguments that are in scope at
the position defined by LINE and COL in the buffer described by the AST.
Within a method, this is the arguments to the method.
Within a property setter, this is the value argument.
Otherwise, nil.
"
(let ((method (or
(csharp-analysis-get-enclosing-tag "method")
(csharp-analysis-get-enclosing-tag "ctor")
(csharp-analysis-get-enclosing-tag "set"))))
(if method
(csharp-analysis-method-params method))
))
(defun csharp-analysis-local-arguments ()
"Gets all the local arguments that are in scope in the
current position in the buffer.
This includes arguments passed to the enclosing method, if any.
The result does not include local variables declared in the current method, or execution
block, or variables declared within any nested
curly-brace scopes that are active at the current buffer position.
An example output:
\((var \"args\" \"System.String[]\" (location (20 29) (20 38))))
This function prints its result if called interactively. This is for
diagnostic purposes only.
"
(interactive)
(let ((result
(csharp-analysis-get-local-arguments-from-ast
(csharp-analysis-useful-taglist (csharp-analysis-get-analysis)))))
(if (called-interactively-p 'any)
(message "result: %s" (prin1-to-string result)))
result))
(defun csharp-analysis-get-local-variables-from-ast (ast line col &optional indent)
"From the given AST, get all the local variables that are in scope at
the position defined by LINE and COL in the buffer described by the AST.
If the position is within a method, the result does not include
method parameters. The result does include variables defined
within the scope of the method, as well as within any active
nested curly-brace scopes.
If the position is within some other execution scope, such as a
getter for a property, the result includes variables defined in
that scope as well as any active nested scopes.
The list excludes instance variables and method arguments that may
be available at the given position in the buffer.
See also: `csharp-analysis-local-arguments'
"
(if (not indent)
(setq indent ""))
(let ((nodes ast)
result)
(while nodes
(let ((node (car nodes)))
(if (and (consp node) (symbolp (car node)))
(let ((flavor (symbol-name (car node)))
(tag-name (csharp-analysis-tag-name node))
(location (cdr (assoc 'location node))))
(cscomp-log 4 "%sconsidering tag (%s %s ...)"
indent flavor tag-name)
(if (and location (csharp-analysis-location-contains-line-col location line col))
(progn
(cscomp-log 4 "%sCONTAINING SCOPE" indent)
(setq nodes nil) ;; this will be the last time through the loop
(if (or
(string= flavor "foreach")
(string= flavor "for")
(string= flavor "using")
(string= flavor "block")
(string= flavor "then")
(string= flavor "else")
(string= flavor "elseif")
(string= flavor "finally")
(string= flavor "catch")
(string= flavor "try"))
(progn
(cscomp-log 4 "%slooking for vars " indent)
(let ((all-vars
(csharp-analysis-get-toplevel-tags-from-ast node "var"))
inner-result)
;; include only those vars within the block that precede the current position
(if all-vars
(progn
(cscomp-log 4 "%slooking at local vars..." indent)
(while all-vars
(let* ((one-var (car all-vars))
(var-location (cdr (assoc 'location one-var)))
(var-name (cadr one-var)))
(cscomp-log 4 "%sconsidering var %s ..." indent var-name)
;; if the var decl precedes the current location, OR
;; if this is a catch variable, then include it in the list.
(if (or
(string= flavor "catch")
(csharp-analysis-location-precedes-line-col var-location line col))
(progn
(cscomp-log 4 "%syes" indent)
(setq inner-result (cons one-var inner-result)))))
(setq all-vars (cdr all-vars)))
(if inner-result
(setq result (append result (reverse inner-result)))))
(cscomp-log 4 "%s no local vars" indent))))
(cscomp-log 4 "%snot a var container" indent))
(let ((children (csharp-analysis-nested-tags node nil)))
(if children
(progn
(cscomp-log 4 "%srecursing..." indent)
(let ((r1 (csharp-analysis-get-local-variables-from-ast children line col (concat " " indent))))
(if r1
(setq result (append result r1)))))
(cscomp-log 4 "%sno children" indent))))
(cscomp-log 4 "%snot contained within" indent)
))))
(setq nodes (cdr nodes)))
(cscomp-log 3 "%sresult '%s'" indent (prin1-to-string result))
result))
(defun csharp-analysis-local-variables ()
"Gets all the local variables that are in scope in the
current position in the buffer.
This includes variables declared in the current method, or execution
block, as well as variables declared within any nested
curly-brace scopes that are active at the current buffer position.
The list excludes instance variables that may be active at the
current point in the buffer, as well as parameters for the
enclosing method, if any.
An example output:
\((var \"args\" \"System.String[]\" (location (20 29) (20 38)))
(var \"flavor\" \"System.Int32\" (location (20 44) (20 48)))
(var \"z\" \"System.Int32\" (location (22 13) (22 22)))
(var \"t\" \"var\" (location (52 13) (52 46)))
(var \"i\" \"var\" (location (53 31) (53 32))))
This function prints its result if called interactively. This is for
diagnostic purposes only.
"
(interactive)
(cscomp-log 3 "ENTER local-variables")
(let* ((line (line-number-at-pos))
(col (current-column))
(result
(csharp-analysis-get-local-variables-from-ast
(csharp-analysis-useful-taglist (csharp-analysis-get-analysis))
line col)))
(if (called-interactively-p 'any)
(message "result: %s" (prin1-to-string result)))
(cscomp-log 3 "local-variables: result %s" (prin1-to-string result))
result))
;; -------------------------------------------------------
(defun csharp-analysis-get-toplevel-tagnames-from-ast (ast tag-type)
"Get names of toplevel tags from the AST, of the given TAG-TYPE.
For example, pass TAG-TYPE of \"using\" to get all the toplevel
using clauses in an AST.
Retrieving using clauses would work with an AST representing a
compilation unit (full buffer) or a namespace.
Example:
(csharp-analysis-get-toplevel-tagnames-from-ast myast \"using\")
See also: `csharp-analysis-get-toplevel-tags-from-ast'.
"
(mapcar
'csharp-analysis-tag-name
(csharp-analysis-get-toplevel-tags-from-ast ast tag-type)))
(defun csharp-analysis-get-toplevel-tagnames (tag-type)
"Get the names of toplevel tags of the given TAG-TYPE, from the ast
for the current buffer.
For example, pass TAG-TYPE of \"using\" to get all the
toplevel using clauses in an AST.
Example:
(csharp-analysis-get-toplevel-tagnames \"using\")
See also: `csharp-analysis-get-toplevel-tags'.
"
(interactive "stag type: ")
(csharp-analysis-get-toplevel-tagnames-from-ast (csharp-analysis-get-analysis) tag-type))
;; -------------------------------------------------------
;; -------------------------------------------------------
(defun csharp-analysis-get-tags-from-ast (ast tag-type)
"Get tags at any level from the given AST, of the given TAG-TYPE.
For example, pass TAG-TYPE of \"type\" to get all the types
defined in an AST.
Example:
(csharp-analysis-get-tags-from-ast myast \"type\")
"
(when (not tag-type) (error "tag-type must be non-nil"))
(let ((result (list))
(working-ast (csharp-analysis-useful-taglist ast)))
(dolist (node working-ast) ;; loop over the list
(cond
;; It's possible that not all entries in the list are cons cells.
((consp node)
(let ((node-flavor (first node))
(node-name (second node))
(children (csharp-analysis-nested-tags node t)))
;; (message "maybe: %s [%s]"
;; (prin1-to-string node-type)
;; (prin1-to-string node-name))
(if (string= tag-type node-flavor)
(setq result (cons node result)))
(if (consp children)
;;(message "recurse")
(setq result
(append
(reverse (csharp-analysis-get-tags-from-ast children tag-type))
result)))))
(t nil)))
(reverse result)))
(defun csharp-analysis-get-tags (tag-type)
"Get tags of the given TAG-TYPE at any level from the ast for the current buffer.
For example, pass TAG-TYPE of \"type\" to get all the types
defined in a buffer.
Example:
(csharp-analysis-get-tags \"type\")
"
(interactive "stag type: ")
(csharp-analysis-get-tags-from-ast (csharp-analysis-get-analysis) tag-type))
;; -------------------------------------------------------
;; -------------------------------------------------------
(defun csharp-analysis-get-tagnames-from-ast (ast tag-type)
"Get names of tags from the AST, of the given TAG-TYPE.
For example, pass TAG-TYPE of \"type\" to get all the toplevel
types declared in an AST.
Example:
(csharp-analysis-get-tagnames-from-ast myast \"type\")
See also: `csharp-analysis-get-toplevel-tagnames-from-ast'.
See also: `csharp-analysis-get-tagnames'.
"
(mapcar
'csharp-analysis-tag-name
(csharp-analysis-get-tags-from-ast ast tag-type)))
(defun csharp-analysis-get-tagnames (tag-type)
"Get names of all the tags of the given TAG-TYPE from the ast
for the current buffer. For example, pass TAG-TYPE of \"type\" to
get the names of all the types declared in an AST.
Example:
(csharp-analysis-get-tagnames \"type\")
You could also use this to get all the using clauses that are
present in the buffer.
See also: `csharp-analysis-get-tags'.
"
(interactive "stag type: ")
(csharp-analysis-get-tagnames-from-ast (csharp-analysis-get-analysis) tag-type))
;; -------------------------------------------------------
(defun csharp-analysis--find-parent-id-from-ast (taglist desired-id &optional indent)
"Helper fn."
(when (not taglist) (return nil))
(if (not indent)
(setq indent ""))
(cscomp-log 2 "%sfind-parent-id: looking for id: %d" indent desired-id)
(let ((working-taglist (csharp-analysis-useful-taglist taglist)))
;; loop over the list, looking for a node with the given
;; name, considering only toplevel nodes.
(dolist (node working-taglist)
(if (consp node)
(let ((this-flav (csharp-analysis-tag-flavor node))
(this-name (csharp-analysis-tag-name node))
(this-id (csharp-analysis-tag-id node)))
(cscomp-log 2 "%sfind-parent-id: considering: (%s %s...(id %s))"
indent this-flav (prin1-to-string this-name)
(if (numberp this-id)
(prin1-to-string this-id)
"xx"))
(if (numberp this-id)
(progn
(if (= this-id desired-id)
(progn
(cscomp-log 2 "%sfind-parent-id: found %s" indent (prin1-to-string node))
(return working-taglist)))
(if (> this-id desired-id)
;; recurse
(let ((children (csharp-analysis-nested-tags node t)))
(when children
(cscomp-log 2 "%sfind-parent-id: recurse" indent)
(let ((r1 (csharp-analysis--find-parent-id-from-ast
children desired-id
(concat indent " "))))
(cond
((numberp r1)
(return r1))
(r1
(return (csharp-analysis-tag-id node)))))))))))))))
(defun csharp-analysis-find-parent-tag-by-id-from-ast (taglist desired-id)
"From the list of tags TAGLIST, returns the tag which is the parent
of the tag with id DESIRED-ID.
Returns nil if the parent cannot be found.
A tag represents one node in an abstract syntax table for a C#
buffer. For example, a tag representing a using clause might
look like this:
(import \"System\" nil
(location (19 1) (19 14)) (id 2))
A tag representing a type declaration might look like this:
(type \"AstRunner\"
(modifier \"public\")
(children ....)
(location (37 5) (93 6)) (id 18))
"
(cscomp-log 3 "find-parent-id: (%d)" desired-id)
(if (eq desired-id 0) ;; special-case synthetic ID numbers
nil
(let* ((cache-key (format "parent-tag-by-id/%d" desired-id))
(result (cadr (assoc cache-key csharp-analysis--query-cache))))
(if result
(progn
(cscomp-log 3 "find-parent-id: cache hit, tag %s"
(prin1-to-string result))
result)
(let ((r1 (csharp-analysis--find-parent-id-from-ast taglist desired-id)))
(cond
((numberp r1)
(cscomp-log 2 "find-parent-id: found %d" r1)
(let ((r2 (csharp-analysis-find-tag-by-id-from-ast taglist r1)))
(cscomp-log 2 "find-parent-id: tag %s" (prin1-to-string r2))
;; insert into cache
(setq csharp-analysis--query-cache
(append csharp-analysis--query-cache (list (list cache-key r2))))
r2))
((consp r1)
(let* ((r2 (csharp-analysis-tag-id r1))
(r3 (csharp-analysis-find-tag-by-id-from-ast taglist r2)))
;; insert into cache
(setq csharp-analysis--query-cache
(append csharp-analysis--query-cache (list (list cache-key r3))))
r3))
(t nil)))))))
(defun csharp-analysis-find-tag-by-id-from-ast (taglist desired-id)
"From the list of tags TAGLIST, returns the tag with id DESIRED-ID.
A tag represents one node in an abstract syntax table for a C#
buffer. For example, a tag representing a using clause might
look like this:
(import \"System\" nil
(location (19 1) (19 14)) (id 2))
A tag representing a type declaration might look like this:
(type \"AstRunner\"
(modifier \"public\")
(children ....)
(location (37 5) (93 6)) (id 18))
"
(when (not taglist) (return nil))
(let ((working-taglist (csharp-analysis-useful-taglist taglist)))
;; loop over the list, looking for a node with the given
;; name, considering only toplevel nodes.
(dolist (node working-taglist)
(if (consp node)
(let ((this-id (csharp-analysis-tag-id node)))
(if (numberp this-id)
(progn
(if (= this-id desired-id)
(return node))
(if (> this-id desired-id)
;; recurse
(let ((children (csharp-analysis-nested-tags node t)))
(when children
(let ((r1 (csharp-analysis-find-tag-by-id-from-ast children desired-id)))
(when r1 (return r1)))))))))))))