@@ -365,31 +365,18 @@ defmodule Macro do
365
365
end
366
366
367
367
@ doc """
368
- Receives an expression representation and expands it. The following
369
- contents are expanded:
368
+ Receives a AST node and expands it once. The following contents are expanded:
370
369
371
370
* Macros (local or remote);
372
371
* Aliases are expanded (if possible) and return atoms;
373
372
* All pseudo-variables (__FILE__, __MODULE__, etc);
374
373
* Module attributes reader (@foo);
375
374
376
- In case the expression cannot be expanded, it returns the expression itself.
377
-
378
- Notice that `Macro.expand` is not recursive and it does not
379
- expand child expressions. In this example:
380
-
381
- Macro.expand(quote(do: var && some_macro), __ENV__)
382
-
383
- `var && some_macro` will expand to something like:
384
-
385
- case var do
386
- _ in [false, nil] -> var
387
- _ -> some_macro
388
- end
389
-
390
- Notice that the `&&` operator is a macro that expands to a case.
391
- Even though `some_macro` is also a macro, it is not expanded
392
- because it is a child expression given to `&&` as argument.
375
+ In case the expression cannot be expanded, it returns the expression
376
+ itself. Notice that `Macro.expand_once/2` performs the expansion just
377
+ once and it is not recursive. Check `Macro.expand/2` for expansion
378
+ until the node no longer represents a macro and `Macro.expand_all/2`
379
+ for recursive expansion.
393
380
394
381
## Examples
395
382
@@ -452,46 +439,50 @@ defmodule Macro do
452
439
end
453
440
454
441
"""
455
- def expand ( aliases , env ) do
456
- expand ( aliases , env , nil )
442
+ def expand_once ( aliases , env ) do
443
+ expand_once ( aliases , env , nil ) |> elem ( 0 )
457
444
end
458
445
459
- defp expand ( { :__aliases__ , _ , _ } = original , env , cache ) do
446
+ defp expand_once ( { :__aliases__ , _ , _ } = original , env , cache ) do
460
447
case :elixir_aliases . expand ( original , env . aliases , env . macro_aliases ) do
461
- atom when is_atom ( atom ) -> atom
448
+ atom when is_atom ( atom ) -> { atom , true , cache }
462
449
aliases ->
463
- aliases = lc alias inlist aliases , do: expand ( alias , env , cache )
450
+ aliases = lc alias inlist aliases , do: ( expand_once ( alias , env , cache ) |> elem ( 0 ) )
464
451
465
452
case :lists . all ( is_atom ( & 1 ) , aliases ) do
466
- true -> :elixir_aliases . concat ( aliases )
467
- false -> original
453
+ true -> { :elixir_aliases . concat ( aliases ) , true , cache }
454
+ false -> { original , false , cache }
468
455
end
469
456
end
470
457
end
471
458
472
459
# Expand @ calls
473
- defp expand ( { :@ , _ , [ { name , _ , args } ] } = original , env , _cache ) when is_atom ( args ) or args == [ ] do
460
+ defp expand_once ( { :@ , _ , [ { name , _ , args } ] } = original , env , cache ) when is_atom ( args ) or args == [ ] do
474
461
case ( module = env . module ) && Module . open? ( module ) do
475
- true -> Module . get_attribute ( module , name )
476
- false -> original
462
+ true -> { Module . get_attribute ( module , name ) , true , cache }
463
+ false -> { original , false , cache }
477
464
end
478
465
end
479
466
480
467
# Expand pseudo-variables
481
- defp expand ( { :__MODULE__ , _ , atom } , env , _cache ) when is_atom ( atom ) , do: env . module
482
- defp expand ( { :__FILE__ , _ , atom } , env , _cache ) when is_atom ( atom ) , do: env . file
483
- defp expand ( { :__DIR__ , _ , atom } , env , _cache ) when is_atom ( atom ) , do: :filename . dirname ( env . file )
484
- defp expand ( { :__ENV__ , _ , atom } , env , _cache ) when is_atom ( atom ) , do: env
468
+ defp expand_once ( { :__MODULE__ , _ , atom } , env , cache ) when is_atom ( atom ) ,
469
+ do: { env . module , true , cache }
470
+ defp expand_once ( { :__FILE__ , _ , atom } , env , cache ) when is_atom ( atom ) ,
471
+ do: { env . file , true , cache }
472
+ defp expand_once ( { :__DIR__ , _ , atom } , env , cache ) when is_atom ( atom ) ,
473
+ do: { :filename . dirname ( env . file ) , true , cache }
474
+ defp expand_once ( { :__ENV__ , _ , atom } , env , cache ) when is_atom ( atom ) ,
475
+ do: { env , true , cache }
485
476
486
477
# Expand possible macro import invocation
487
- defp expand ( { atom , line , args } = original , env , cache ) when is_atom ( atom ) do
478
+ defp expand_once ( { atom , line , args } = original , env , cache ) when is_atom ( atom ) do
488
479
args = case is_atom ( args ) do
489
480
true -> [ ]
490
481
false -> args
491
482
end
492
483
493
484
case not is_partial? ( args ) do
494
- false -> original
485
+ false -> { original , false , cache }
495
486
true ->
496
487
module = env . module
497
488
@@ -501,43 +492,103 @@ defmodule Macro do
501
492
[ ]
502
493
end
503
494
495
+ cache = to_erl_env ( env , cache )
504
496
expand = :elixir_dispatch . expand_import ( line , { atom , length ( args ) } , args ,
505
- env . module , extra , to_erl_env ( env , cache ) )
497
+ env . module , extra , cache )
498
+
506
499
case expand do
507
- { :ok , _ , expanded } -> expanded
508
- { :error , _ } -> original
500
+ { :ok , _ , expanded } -> { expanded , true , cache }
501
+ { :error , _ } -> { original , false , cache }
509
502
end
510
503
end
511
504
end
512
505
513
506
# Expand possible macro require invocation
514
- defp expand ( { { :. , _ , [ left , right ] } , line , args } = original , env , cache ) when is_atom ( right ) do
515
- receiver = expand ( left , env )
507
+ defp expand_once ( { { :. , _ , [ left , right ] } , line , args } = original , env , cache ) when is_atom ( right ) do
508
+ { receiver , _ , _ } = expand_once ( left , env , cache )
516
509
517
510
case is_atom ( receiver ) and not is_partial? ( args ) do
518
- false -> original
511
+ false -> { original , false , cache }
519
512
true ->
513
+ cache = to_erl_env ( env , cache )
520
514
expand = :elixir_dispatch . expand_require ( line , receiver , { right , length ( args ) } ,
521
- args , env . module , to_erl_env ( env , cache ) )
515
+ args , env . module , cache )
516
+
522
517
case expand do
523
- { :ok , _receiver , expanded } -> expanded
524
- { :error , _ } -> original
518
+ { :ok , _receiver , expanded } -> { expanded , true , cache }
519
+ { :error , _ } -> { original , false , cache }
525
520
end
526
521
end
527
522
end
528
523
529
524
# Anything else is just returned
530
- defp expand ( other , _env , _cache ) , do: other
525
+ defp expand_once ( other , _env , cache ) , do: { other , false , cache }
531
526
532
527
defp to_erl_env ( env , nil ) , do: :elixir_scope . to_erl_env ( env )
533
528
defp to_erl_env ( _env , cache ) , do: cache
534
529
535
- ## Helpers
536
-
537
530
defp is_partial? ( args ) do
538
531
:lists . any ( match? ( { :& , _ , [ _ ] } , & 1 ) , args )
539
532
end
540
533
534
+ @ doc """
535
+ Receives a AST node and expands it until it no longer represents
536
+ a macro. Check `Macro.expand_once/2` for more information on how
537
+ expansion works and `Macro.expand_all/2` for recursive expansion.
538
+ """
539
+ def expand ( tree , env ) do
540
+ expand ( tree , env , nil ) |> elem ( 0 )
541
+ end
542
+
543
+ @ doc false # Used internally by Elixir
544
+ def expand ( tree , env , cache ) do
545
+ expand_until ( { tree , true , cache } , env )
546
+ end
547
+
548
+ defp expand_until ( { tree , true , cache } , env ) do
549
+ expand_until ( expand_once ( tree , env , cache ) , env )
550
+ end
551
+
552
+ defp expand_until ( { tree , false , cache } , _env ) do
553
+ { tree , cache }
554
+ end
555
+
556
+ @ doc """
557
+ Receives a AST node and expands it until it no longer represents
558
+ a macro. Then it expands all of its children recursively.
559
+
560
+ Check `Macro.expand_once/2` for more information on how expansion
561
+ works.
562
+ """
563
+ def expand_all ( tree , env ) do
564
+ expand_all ( tree , env , nil ) |> elem ( 0 )
565
+ end
566
+
567
+ @ doc false # Used internally by Elixir
568
+ def expand_all ( tree , env , cache ) do
569
+ expand_all_until ( expand ( tree , env , cache ) , env )
570
+ end
571
+
572
+ defp expand_all_until ( { { left , meta , right } , cache } , env ) do
573
+ { left , cache } = expand_all ( left , env , cache )
574
+ { right , cache } = expand_all ( right , env , cache )
575
+ { { left , meta , right } , cache }
576
+ end
577
+
578
+ defp expand_all_until ( { { left , right } , cache } , env ) do
579
+ { left , cache } = expand_all ( left , env , cache )
580
+ { right , cache } = expand_all ( right , env , cache )
581
+ { { left , right } , cache }
582
+ end
583
+
584
+ defp expand_all_until ( { list , cache } , env ) when is_list ( list ) do
585
+ :lists . mapfoldl ( expand_all ( & 1 , env , & 2 ) , cache , list )
586
+ end
587
+
588
+ defp expand_all_until ( { other , cache } , _env ) do
589
+ { other , cache }
590
+ end
591
+
541
592
@ doc """
542
593
Recurs the quoted expression checking if all sub terms are
543
594
safe (i.e. they represented data structured and don't actually
0 commit comments