suggest.el-0.7/0000755000175000017500000000000013324645427013304 5ustar dogslegdogslegsuggest.el-0.7/README.md0000644000175000017500000001446713324645427014577 0ustar dogslegdogsleg# suggest.el [![Build Status](https://travis-ci.org/Wilfred/suggest.el.svg?branch=master)](https://travis-ci.org/Wilfred/suggest.el) [![Coverage Status](https://coveralls.io/repos/github/Wilfred/suggest.el/badge.svg?branch=master)](https://coveralls.io/github/Wilfred/suggest.el?branch=master) [![MELPA](http://melpa.org/packages/suggest-badge.svg)](http://melpa.org/#/suggest) suggest.el is an Emacs package for **discovering elisp functions based on examples**. You supply an example input and output, and it makes suggestions. Interested readers may enjoy my blog posts: * [Example Driven Development](http://www.wilfred.me.uk/blog/2016/07/30/example-driven-development/) * [Synthesising Elisp Code](http://www.wilfred.me.uk/blog/2017/07/02/synthesising-elisp-code/) * [Suggest.el: Synthesising Constants](http://www.wilfred.me.uk/blog/2017/08/06/suggest-el-synthesising-constants/) ![suggest](suggest_screenshot.png) ## Examples suggest.el knows many string functions: ``` emacs-lisp ;; Inputs (one per line): "foo bar" ;; Desired output: "Foo Bar" ;; Suggestions: (capitalize "foo bar") ;=> "Foo Bar" ``` suggest.el can also help you find tricky dash.el functions: ``` emacs-lisp ;; Inputs (one per line): (list 'a 'b 'c 'd) 'c ;; Desired output: 2 ;; Suggestions: (-elem-index 'c (list 'a 'b 'c 'd)) ;=> 2 ``` suggest.el is particularly handy for path manipulation, using both built-in functions as well as f.el: ``` emacs-lisp ;; Inputs (one per line): "/foo/bar/baz.txt" ;; Desired output: "baz.txt" ;; Suggestions: (file-name-nondirectory "/foo/bar/baz.txt") ;=> "baz.txt" (f-filename "/foo/bar/baz.txt") ;=> "baz.txt" ``` It can even suggest calling functions with `apply`: ``` emacs-lisp ;; Inputs (one per line): '(1 2 3 4 5) ;; Desired output: 15 ;; Suggestions: (-sum '(1 2 3 4 5)) ;=> 15 (apply #'+ '(1 2 3 4 5)) ;=> 15 ``` It can also suggest composing functions: ``` emacs-lisp ;; Inputs (one per line): '(a b c) ;; Desired output: 'c ;; Suggestions: (cadr (cdr '(a b c))) ;=> 'c (car (last '(a b c))) ;=> 'c (cl-third '(a b c)) ;=> 'c ``` It will also suggest additional arguments of basic values (0 in this example): ``` emacs-lisp ;; Inputs (one per line): '(a b c d) ;; Desired output: 'a ;; Suggestions: (elt '(a b c d) 0) ;=> 'a (nth 0 '(a b c d)) ;=> 'a (car '(a b c d)) ;=> 'a (cl-first '(a b c d)) ;=> 'a (-first-item '(a b c d)) ;=> 'a ``` ## How it works suggest.el uses enumerative program synthesis. It tries your inputs (in any order) against every function in `suggest-functions`. `suggest-functions` is a carefully chosen function list: they're all pure functions with a small number of arguments using only simple data types. We only include functions that users could 'stumble upon' with the right set of inputs. ## Contributing To work on the code, clone the repo, then you can eval suggest.el in your current Emacs instance. To run the tests, you will need [Cask](https://github.com/cask/cask). Once you've installed Cask, you can install the suggest.el dependencies and run the tests as follows: ``` bash $ cask $ cask exec ert-runner ``` ## License GPLv3. ## Related projects Program synthesis or inductive programming is a computer science research topic, with a range of tools taking very different approaches to generate code. ### Smalltalk: Finder This project was inspired by the Finder in Smalltalk, which does something similar. There's [a great demo video here](https://www.youtube.com/watch?v=HOuZyOKa91o#t=5m05s). You give a list of values (inputs followed by an output): ``` 3. 4. 7. ``` The Finder iterates over all possible calls (with arguments in any order) to a list of known safe messages, and returns suggestions: ``` 3 + 4 --> 7 3 bitOr: 4 --> 7 3 bitXor: 4 --> 7 3 | 4 --> 7 ``` This only returns single messages that meet the requirements, no nesting (e.g. it won't find `'(data1 reversed asUppercase)'` from `'foo'. 'OOF'`). You can also supply multiple examples, using expressions as inputs: ``` MethodFinder methodFor: { {'29 Apr 1999' asDate}. 'Thursday'. {'30 Apr 1999' asDate}. 'Friday' }. ``` This returns `'(data1 weekday)'`. Amusingly, it will sometimes find `'(data1 shuffled)'` from `'fo'. 'of'.`, which is a random sort. ### Python: cant There are some other niche tools that take other approaches. For example, [cant for Python](https://github.com/kootenpv/cant) tries every function in scope (without a safety whitelist) to find functionality. ### Scheme: barliman barliman takes this idea of synthesis much, much further for Scheme. There's an [incredible demo video here](https://www.youtube.com/watch?v=er_lLvkklsk). ### C-like: sketch [sketch](https://bitbucket.org/gatoatigrado/sketch-frontend/wiki/Home) allows the user to specify value placeholders in code ([examples](https://bitbucket.org/gatoatigrado/sketch-frontend/wiki/Gallery)). ``` harness void doubleSketch(int x){ int t = x * ??; assert t == x + x; } ``` Generally you provide the structure of the code. ``` bit[W] firstLeadingZeroSketch (bit[W] x) implements firstLeadingZero { return !(x + ??) & (x + ??); } ``` Or you can specify a set of operators for sketch to explore. Each line here is an assignment to `x` or `tmp` of an expression that may contain `!` `&` `+` with `x`, `tmp` or a constant as arguments. ``` bit[W] firstLeadingZeroSketch (bit[W] x) implements firstLeadingZero { bit[W] tmp=0; {| x | tmp |} = {| (!)?((x | tmp) (& | +) (x | tmp | ??)) |}; {| x | tmp |} = {| (!)?((x | tmp) (& | +) (x | tmp | ??)) |}; {| x | tmp |} = {| (!)?((x | tmp) (& | +) (x | tmp | ??)) |}; return tmp; } ``` Constraints are fed to a SAT solver, then sketch finds a solution. The output can be converted to C. ### Liquid Haskell: Synquid [Synquid](https://bitbucket.org/nadiapolikarpova/synquid) (the first half of [this Microsoft Research talk](https://www.youtube.com/watch?v=Q-3tcbUyF34) gives a good overview) is a program synthesis tool leveraging refinement types. The user provides the type of the function they want to generate, and a collection of 'components', the building blocks that Synquid tries to combine. Synquid then lazily generates ASTs and type checks them. This allows it to prune the search tree by ignoring program structures that are never valid types. (This is the extent of my understanding: I have probably oversimplified.) Impressively, Synquid can generate recursive functions. suggest.el-0.7/.travis.yml0000644000175000017500000000051513324645427015416 0ustar dogslegdogsleglanguage: generic sudo: false before_install: - curl -fsSkL https://gist.github.com/rejeep/ebcd57c3af83b049833b/raw > x.sh && source ./x.sh - evm install $EVM_EMACS --use --skip - cask env: - EVM_EMACS=emacs-24.5-travis - EVM_EMACS=emacs-25.1-travis script: - emacs --version - make test notifications: email: false suggest.el-0.7/bench.sh0000755000175000017500000000047313324645427014726 0ustar dogslegdogsleg#!/bin/bash set -e rm -f suggest.elc echo "Interpreted:" cask eval "(progn (require 'suggest-bench) (suggest-bench) (suggest--possibilities-bench))" cask eval "(byte-compile-file \"suggest.el\")" echo -e "\nCompiled:" cask eval "(progn (require 'suggest-bench) (suggest-bench) (suggest--possibilities-bench))" suggest.el-0.7/suggest_screenshot.png0000644000175000017500000006131413324645427017735 0ustar dogslegdogslegPNG  IHDR-GisBIT|dtEXtSoftwaregnome-screenshot> IDATxwxf7^!!$"jAXR" X*( J/&$ ƦmM̜ܙ-'3wfQtB!L` p+(*:€...Z.]`ggWkY !h'ڵkŋ[@ 36mZNNBq Ro9sW( @3f0uT tX!-^o8ѻwo233...$''jk9U!BiT*ިj-[&UBQ#Z-˖-&\Cf%B;Vvv6>>>EG<k;!B H*BT\kXrB!H!BC!#B!,F ! ++ !G0dzaٝ 8AvvNw5G Cz^xi 6$%%B<==O`W󰲺y_Ê>=wǸ5 Um'!*;+{ڴa\t^/zFcfNs[I{#h"f+j`-f-fRY[₵p}Jǟw.^M4HKOܱ#?E}ϒ˘Gtlߞkט;o>E޺5ǎ=3cvǢVѡ];^yE길TS k&йcGfv6WEs}xsqqYv%;'N:2m$lXb%38;9sonhlllaCjZ>^D{/{P(4V!nesa^?ys>CP0v$N>MP``ږC3X[ xVET j('ObsAIII,^Ϥ9r(ݺtY&O+&z&N3gyj4?Q|< sQ Ǡ קq\mڲ-ZX|>o>޵ {gӖ-i݊@T6 %*:_777F<= ^wf+62 P}(O?q9^?B3ge'Um)<0RÙ3^Fâ\HLښkj5m¨gGRr;tGKN|{( 5mB޽&ǎ3G7U^MߨՙحKg<޸}t0p7OH`u%=;|8..%UkfY\\J|8sAk&qICCxsٶ_<'Kp>REU3o//P|Vmy Q0ᵱ@Q1R?(aͩW.[mg߁}A?!c|B۵-kSb~O~.^dvfe˞|lP£\ԯ_z{7@ wDF$$$ wUsEoݺv)sVe qVYckkVH!D5hּ2{;;,^Kv_,i7nc(CוٞJYRYn?6?^b]X<"ef۷7j"ZeX]ߎſ)krY3n*gnJUԴ4^7ÜyuϝmuoB^cFWP\zq W~/sBf_n$]Dx%9::T#׏Ԥr5ǡG׏mxx! ض_teʶw oL5a9p<'ORrC3:>=mcbmIF?y[ئMsOx6o݆M5ߝ;KoLLHt܉ȝL2-[/O<6qYz=/<;FWeu7NN?yƍѹcGv0v:kGZz:mlG;ShʜO>2ݘ?w'OUj[ !j-ݳ<yk4n IDڍ'gϿE >_/\]O֛,{"w$n>7*޽g߁슍?yӦ\pFy_CxV%( f6Q|G|?>#ĝӣ*-w.6iVޞ11G1l`c899!n{ ~b6ɩ!nQo7ٳa#6c&!ĭD !nAZGGGN??%K3,7jˣ_n}I~m;%11 ???&*6 fw,K.Bb".<گ/TW$/?zn |HL{]}s}Ŧ-[ܹqRiţ}f*}8?ʦ-[hf\ΞoT~l,ocOhlݾ,wBvyjϔUo$.$BG[qj ysVo|^աzV+C811hBQZY'n];w2Rr۴. n{_)LΤO| +J+9st,jL&n ,;=ʌ={ژ|\HL7UK^\NN&uo[@s!1ƍjpIMKcL}]¨}SN37quuǥT"(jILLb̨W.s/`|6yyyc v5jŊS^^vr!:ĨǾWǾxсpV=tz=Yhju&u\\j|lUF܏n|j3h8_W^˓)o͒˙2q<۶Cx{'.?{ڴAp0_Ckpu+kרs*Rx1Cv0',T*ڴnETtP(<=d0_/s<5I땞CT{*se+z=nŊO}33w:jqO |re|x,%%1xn3ԩSOwBX6&SΡgeex1 ޞ!wE\ W뭭qwwrraYfMqrr"(0I^'>!U׵kذi3c_y@길0f(TJ%۶kg-šZzt#>!FCAAQ?(АVi̝@_}޵ ~4n"c/OeZ̒[]:Kp 2,A89:ҹCbQ?(gg'o//.3XǽFF qrry( Ǩj.O79f35jmUh:ĺlj9p'Ivvv(J\E+(,0/舧*ptVp9B0j6) =?zt?급<T*`蓏y6vWψji眥~9bB-c'LWޙ6w77ƿQteC^~ZNW~,oaUgk:DEǐЧK.U朎M33s. lk32Օ%y??_t:*NSF[YY˫8V:3_cXj9~$AAF04wߜƄ^eO+khth4ÌvkԴ*Y%^f~)Μ;KfV&#FP`WS~~t:8Ykrƶ}k&7wOeú? ~1P(T|zc^5k,/*xjY9p'õNő3Gg_C`P]'ONΝ|W:}H =#ZrЃ֫Mrc7fSCKğ>}.''3/h9ssu%|.^$;;/ʊ]TFOH 33Ĥ$ JgǙdٺ}8|(6m6ss{y*o_s܏7LUŅB-;"#fuGt܉S_ߏzJc,+?ihMڷzmJ,ޥ3ii4wvjJ ._5azEgvߕƼk}Xw뫲ȩAeO+9~.cǼw?sDVXx o'\e?㲟J,+x(jܱC ލv0Q4o֔fΨrn*g/~'>>???ڴj3Æ-[X˯XYYѱC;F={1,a I~^>͛5峏?ƈu~X:Kx?) qY&|«/_S^]ztʌß>,1T*[k՗F[l?Ȝ;zw|;wLXNLxm,XŒY#4ߘDvvc,+1υ^5)*fڵ-uH?~}+}KJj*4 aoViAech}eǚx5EeVxEKNn+ĭn|V6mmN~%qB!)<Ba1Rx!bdB!,Bx!¢B!H!BQ$ͨB!5Eju5B!nK7}YY\\\Tl`({޼SB;^|WKueC?N 2,b7ÃQ*J>:qt4u~WX)MV-fT$Ck;!ry\;kg[mX;'leTԬg&b *k;2FB[-{@Ӳ}4m]GsVJNDž;hA\j;F`lr)IGX2mށ?ٓ9ұB[YG֙3ߏ[8S+5VJkVE_neDe]Ot|x oꅃ4/Q_װy)ډG~[K޽G?B[ΥdΕ %բKvj^Gq [{|13ܼMpCQ?(l_̞_N;{=#1Ƥo7mYx=it ѹkƨSkcqSkB&Yirh;׶_(*rWʩ<.vӿ򯶮mX/ʘTl0{=yȌQ !ĭ=bmг߳ol^1gbJ4 @JahǹQ]Ž^z0d|=>[SS44 uyo|M&ɧPlqiXb0> bl#^fElZ1}LxK mbcP(6|ڐo#%0a&]vw0/O<1om擙hvHpXozsVhյ xac:q|l,OKP*{rt6R۰x鴅e癅UOX!nQwDa̴ge\WuE&Ti8Т3t>I&쉃'yYdK~W@s(,o856v.xkiT>q@ sR^y9ʔk 0I}0}jr2J־NH,ژY#9 !Dm2Gnb"~ ;??z6;"xĝKE̾siZIN[@p^!pbQ\8om:^u7P G3D/خx ٛgwe_b r2n !ÆVJk:Ζټb<g%&VΙ}&L6.Kԟ3RZq69状suUG}v$+?텇S 'nDa׹})!/YYI: U*mx9 \=iWPЅVg=ڵR%<V(U6/0k8zI]qgB"/WMh.?#R? m{4+p_օGO}wyٸz5 bloRNaۂ\5L<Ӿxl*o&mF-?#|.!t4z&~r(NGt:k Faʸ\ߗ:  !Ŭo=Mr2֬)7ޘK(Րɛ \J+dgQpVs;ogKi X8QB澝V'm\ĈJYYi^^*:%wBܵLP{ު40[{\|j;QE~M?unc֩!Bܩ!B3I!BC!cre-BqBq#%TJZ 5vB!3U{oٷoC׺!OȰoۈYu=0z-D[+za-IO*ҫ9W.;&=:>{R}_ɺ>B.;=yFv2+/!nbrn /١.ph"?g7+sf4{:}gű~qQF+Yt:}>~SD~gO‡?@D}=͑8|,^+!=ۖjok"Iz%ׯ}BɅW :4/ ;W']@N]R1qK¯UC" [PTc,m~̛''Ee(qPum!nVcs<*|,*,KKbaR\}q4^cWo05y.^ÉuQ/-(DՖ[84|9! l\KނՉnEy\;kg1} !ܲG< Pq+^:_&k/VK-B3 3gH߿֭q 1;vnx43 YQ.CV%\ط,ZJ<Ξ4g^G8Kڻ؆! Kg(C^ x~֑R,uyz ɸ5$BimCҴF哭NFqqfog㗨^1G1_=\cqSkB&Yinεɠ~#Vԩ 1.vӿQlu {lR"G}|8wd`ӲWqqXүFemOƕ3lY9݆# nkXߠ^*~ϓPcc1bU_T*[9){`b r9P(pmT#},ژYnBۏYG.>4hgcܾx1ҚeD6O aXґ 'vL^_\[W\N[@#Bi'rSp^W z)B[YG^u[&_g@s]|tZJG7}/ѹߛv.!t4)PX!t4I`t/ jA&"mv xW qj@ٴ=qF+DN3ψxkE^\+s|[T>BqgRzZ]iѣ$'m͚r㍉ εB[Pfff1...fI۳c*y11B!>C޽UB!]ǬS-B!7S-B!fC!#B!,Ƭ#5gs:}K2~6Fdm'XYf b)\)SJ;\Ȋ'W-j ::OOO !UQ2o<ѣm\:p5s>-Bq @Nh޼9s孷ުtBjTN㫯bȪ}FKX!mr#F`hNE!ƎxlٲDh7-m}v Z7m1 VN'Fœh˘K+{X@{}(xvNIϤN{TEyn|6N`@wуLg lܸ>}T BUxx98o_|ˍټy3x{{#ݨ߭^C]^LD~nW!̖==i-"tncD>1&cNt:|2|Ӊ8zpߛ#qps!Y6OWBz-޺>D5K(;e\]]iڴ)7oC!D1pfp&ݻ-[ޫIzZoꄝ.PPn&n_j^}pXs .>'Xu&XZd^J1!(mA@Gi r4$=^y;wFx?B!^5Vxtܙ}j G<̥0nc;I{}M457f7smxhBAPXd_Iikn;zҥK#VcG!22 $-}>y 1Y3#wY/ZŘKiހ=gW.^CQi5a}G·/cn5Ӯ]`5ڏBЫeOZs17{G `f&,iܸq;vSBq2\\\j%""fejYn]vB!M-<ǷqAv`nTB!Du2krը(Oը*ŔGջXYeE. ?ǜT\31?QCfZ"lV>cqSkB&YiCMٹvy9oz$ߊ:<6v/1 : e.vӿY<ǒ~4*k{2a ^?Tlϱq@a"Fbo,f9BQ[TKēY)2e^bit|h m{5 "pS_Ъ`N_kRLA~11( }c>AmL[rtπ0OEpߓҼ0ԩRmfWLB:'q밶u*wpXozsVhqi ٹ4x6/"6->&<%Tmг߳ol^1gb *Y!Nf}NUH}xn;NgۯoQӹߛFq-yPYӲH +{\0&&9aL*]a^/Z~?azy.A>ov?VJkCYR.EpUر-5j\8NgQ~YuꇫWu,1!;ݲ0։Ώš)&7+g/^|u:cndT427; NJI19KlΠkLO"&m+s}aCs3Si RZi ETzcX*g!̜\q'MU)NWfvTb?qhP]=%کy߳ C?k49ؾذ<7+u@(rpv Eg>}2ӓJ^PwL;G7 r9[F@F ω9,pŏBa*x&&r߰ïwoc*z8|3pUKXg@ #N ϙ; '-ц#;?m eit4Ø;g@ԟ3ٸ|,QJiMVEZ;#8k%^upp]t,ў+.P]`q -p׹})!/YYI: Uo:[VNdD%&V؜WP˴ˣBbfI*kأ]*Ŕ>#Qqw݊kB qmGpXoB49=T[=OOT٢ Ꭱt;ǫn rk2 hN㱱+*%87!@MhsKeN3b!Z&t4 .!t4)EW$&t4RxW qj@ٴ=%{ IDATmmJB!DU)Z4shfMX:<ֶ;ye[VN#Q0:F!̬4ŌS-z=i{c!gŖqpF2i)bBQu zx5' ɧ)˦GM;Sߩ&!:"Bq#cO5T!Rx!bB!Ř]x۫3jB@Pп c Q(Zܘm۶*eCy|Ld DP!VZHo&nĭu+moumZ6ZZ"]%HDsI2y?6^f;gW۶lP߾K SvNs_%[9~֭)why r .ˋ^{2tv"11_ ʌEr]+aЦ9>gluСTMXyzR]g!I.-YB i-̩ /cרWWxvU\Jb fG:u9s=z!ϝLLi1'}q+Ceɒ%Ww*FVHZ[ML ql[_=U׷/I?ػw/7o$44ׯ' kkk %++<0:wᅬ...L6$%%icJj̻p3f<0a{:}|}7۷ԡ},hpzבE?,j.fFRk'$y\C q.C˝οGpq !==ݻw{r\FTWܔhNNBPG{9~ lĉ[Pˍ ۽;ۚ4ao` ׭iKch;[fg֜5K Ď֤ J9y֒!$ vvoOʎDaf&Wh(rHܯJÆ quuՙ?~<ܾ}M6зÇŋ9y$}ˋ#F转u뒘Hqq1 6ԙoccCbb"ݺuW^^δ,]\ʌwlU{ҹ3~Fa}LoX);vp~|))M,,41i{r⣏)S?'?s<no}bG_sY*-g&%.,XU:42˗QwϞiX֮[n:ōOT%%i=d;ZuUxPO=cܹs1yWm‚ @PPC%""  oooJP(GGGpiv};JG[X9ۃBC]݂3~61*.|@Mmx4G^hҫ&~椞J-ҧUa!.ݹHʕóW/<{ؑSH޺£^h(ރ,qcy<6g\Gjj*>>>#F0dڶmN\aa!yyyX1]٠P +lo(&8?*k= u4!\IIIόgܸq,Xdynnn鵼s%nܸAݼiӦ>tvvܹsz-Y>;qk#g];| f>+z*9)ᖛ1g뉩9<(zWwצ ^!iԨQ:}իqǮgčOAj*nݺ~*jwP|aMجG68|f 1OI d`[CVS\P[Uǝ86ҼoAվ(M ;yRϣCLL }v]FT*ׯ'OӬ[K׹sg{/_NӦMqwwל zm4_HOOVODOTTo&8;;zuOMjl,a\Gryz/Ƞ}Z9a?g,P1?k@יߣ*cj>}zɿy6*<,]\pyʕ%wϝóW/wԩPpjՊݻQTZWYTC3r$LG#3.6cl&čKQȽ|6|ƌ ;vڏ?kj̜YBj5Ygx](W[o1vX:īO\ƍ3f vZ^yo;m۶SN1ydt0`111L4;w0f-Zٶm&vњjv>3nܸA޽a5n!x]řb_Dž/Gi]O}ܒ/[W{|ò1[+ -woUѣdeeRv_ ~[׮uZ嗇{1Xjm[%p 9\Z?U:|*?.;sA?W_|pbjmM~0wpK'(ƣNT @ج׃OOO,YRE=[XX/_0ϗ>vYviit޴23cg͍3gVw*~9W^n.*>Ueoo_j g͚5yxf5Yvv6+V <1ڰannnhBkzZZ^zNݺu ͛Zqx{{?{Y/^۷3{lZjŔ)S8p ٜ;w2\*>Cf֬YСCGRUsKOI¥KhӦ dddhͯUXZZن>1PTlڴI???,26n܈SN]vL2Rƍubټys&88ѣGӹsgBBB LGM7jl;wMR~\T311a֬Y=z3go>fΜI5q*իWk.222QF;u%III|tԉ;w2{l5k… .ٯʐ!C/BM6Ejj*t҅#Gjܞ˗9r_ @:uXv,66ZMXX;vڇ ?Nzz:<9_Oiӆ8Ν;3oclmm|NR}kUJ{<={v@/'Ok+ /_ՅŪUͥM6OZZAZ|9vܾ}y+&22oTׯO~~>QQQ̘1Ch֬M4<ߟf͚ѸqcϟYLL u֥q4hРcGG*[Frr2uЌ{zzZ֊ˋ*yLJm۶q ٳ+Vo駟rΜ9cp̩S裏]6ӧO_~\;wjb]N֭5kƍ‚|J%DFFA߾}5}]kQTiǟJbtЁO>0"""ؾ};s~Dsؘ1cٳ'Pr4cjj ٶm&LW^|g:tȠ?P(Xx15"++!Cp^3sd…dffҷo_ΝJVs1lVصk*ZŋTY*lll8|0SNeڴiX[[STTDaaΰ UiŸq2e ~!vvvk׎6mj2ԩaaa4mڔo,____f̘BC?YQFi{m !333lmmy.QM7j*-1.\ //333-[(ҥKTJn?˗/EV#**-L<<<,RVVV:=spp?c͚59r9r{2ej_Vse# 40(3g0b fz-Xv-<oooT*s%((^z R(MM7jW/hq*S5jZ ׉al޼WWWΝy.wJބ]\\ J>H;G]3}bbbP(4i҄|7n 111Zxbnn^e훘T*w[;w333yFrz^=뵰K.t҅>c-[wᑛKQQvvvZO̽{(..fXB3]VV???NO?ĤI011G|>FMcaiis4RJy;yȻ*b*5N5kƲa._ʕ+6m"v|G_ٚLc}/>lm?j5ך~1 5}ّ[)mS2d<==K]\4!R$00TbffFNNVLNNz`jjʰaԩNnH^^;wd4o޼'U}1Ņdin*uڵkl۶k׮ږ͛7ժUbԩSdgg믿{@zz&Q'4ԩ%۷@s$iHhwe՚A}rˬ,C6˟Z^^gϞ7$""B׻wo.\yzNgړGݺu+Fn.\`֬Y|ԓ\j5P2t8[Jtt4...tY~fΜoΝ;)(( 44T'Zŋ:t{k1ydׯOlٲAibZlɲeˈ]vlڴLPf͚ŪUgݺu:'3|p&Mŋ\tIs-[HHHm۶8::rQJ-8С\S>qDШQ#طoF޽ubϟ?ѣGi׮/R%|*..&337o-={$,,LsիWIIIƆ={r)\]]5׽hтTbȐ!;v BСCQ(zԮ]4222PTԭ[`ڶm^9?իWIOO'55B͕.5UTT)));V˺VZǻ4h@NN֭cƍD}IVpssӚR>|8'33w~cbbe:Cvvvw^~'Z_?͛8q"666ϋi ΝcϞ=߿π4+jkײqFpppݻ%O(C}xWQTiO;͛7lذÇSPP@iذ!Prؑ#Gؾ}g`:IDAT;;w$''1cƔ6lؐӧO~z~'tQŋ9rioݺ%g?gP* 6pBbʔ)TX(7h xb]Ɯ9s*΋2^ CjH~<^cFMuVϟ/x^,Z''' fxb|ᇌ1Bj}jN>ͤI*G^^;vкfMSYRRR_FT{nrrr4="5)ooo5jč7(((ݝ=z0x`bhҤ FS(l޼yܾ}#Fмy k,fff 4` we would try the inputs reversed. This was slower and led to duplicate suggestions. Suggest.el now returns the same results regardless of the value of `default-directory` and `file-name-handler-alist`. This fixes a bug where suggest.el would try to make tramp connections from URLs ([#32](https://github.com/Wilfred/suggest.el/issues/32)). Added string function `s-truncate`. Added parsing function `read`. Improved sorting of suggestions to penalise `apply` relative to direct function calls. Added formatting function `format`. Defined a variable `suggest-extra-args` so suggest.el may consider additional function-specific arguments. Added functions `-reduce`, `-reduce-r` and `-iterate`. # v0.4 The search algorithm is now smarter at avoiding previoiusly-seen values, resulting in a larger search space being explored. You may see additional suggestions. suggest.el may now suggest additional arguments, e.g. it can suggest `(nth 0 '(1 2 3))` from just `'(1 2 3)` as an input. Added bitwise functions `lsh`, `logand`, `logior`, `logxor`, and `lognot`. Added string function `s-wrap`. Fixed crash on repeated `M-x suggest`. # v0.3 Suggest.el can now suggest sequences of function calls, e.g. `(cdr (cdr foo))`. Suggest.el now avoids calling some string functions with arguments that are known to segfault Emacs ([#16](https://github.com/Wilfred/suggest.el/issues/16)). Added `s-chop-prefix`, `s-chop-prefixes`, `s-chop-suffixes`, `f-relative`, `last`, `safe-length`, `remove`, `remq`, `number-sequence`, `alist-get`, `ignore`, `symbol-file`, `regexp-quote`, `shell-quote-argument`, `kbd`, `key-description` and `intern`. # v0.2 Added examples to the README to help new users understand why they'd use suggest.el. Added some additional functions: `elt`, `butlast`, `make-list`, `lax-plist-get`, `abbreviate-file-name`, `replace-regexp-in-string`, `string`, `make-string`, `split-string`, `string-to-list`, `vconcat`, `s-right`, `s-pad-left`, `s-pad-right`, `string-to-number`, `string-to-char`, `number-to-string`, `char-to-string`, `file-name-nondirectory`, `directory-file-name`, `aref`. Made more the of the `*suggest*` buffer editable, as users often press RET at the end of a heading line. We can now suggest `(apply #'some-func some-list)` too. Try an input of `'(2 3 4)` and an output of `24`. Fixed an issue displaying function outputs that spread over multiple lines. # v0.1 Initial release. suggest.el-0.7/Makefile0000644000175000017500000000015613324645427014746 0ustar dogslegdogslegCASK ?= cask EMACS ?= emacs all: test test: unit unit: ${CASK} exec ert-runner install: ${CASK} install suggest.el-0.7/suggest-bench.el0000644000175000017500000000203513324645427016364 0ustar dogslegdogsleg;;; suggest-bench.el --- measure suggest.el performance ;;; Code: (require 'suggest) (require 'dash) (require 'shut-up) (defmacro suggest--print-time (form) "Evaluate FORM, and print the time taken." `(progn (message "%s" ',form) (-let [(total-time gc-runs gc-time) (shut-up (benchmark-run 1 ,form))] (message " time: %fs (%fs in %d GCs)" total-time gc-time gc-runs)))) (defun suggest--possibilities-bench () (interactive) ;; Basic arithmetic. (suggest--print-time (suggest--possibilities '("x" "y") '(1 2) 3)) ;; List access. (suggest--print-time (suggest--possibilities '("x") '((?a ?b ?c)) ?c)) ;; List generation (suggest--print-time (suggest--possibilities '("x") '(4) '(1 2 3 4))) ;; Zero results. (suggest--print-time (suggest--possibilities '("x") '("foo") "bar"))) (defun suggest-bench () (interactive) ;; Overall call time. (suggest--print-time (suggest)) ) (provide 'suggest-bench) ;; suggest-bench.el ends here suggest.el-0.7/suggest.el0000644000175000017500000007352113324645427015317 0ustar dogslegdogsleg;;; suggest.el --- suggest elisp functions that give the output requested -*- lexical-binding: t; -*- ;; Copyright (C) 2017 ;; Author: Wilfred Hughes ;; Version: 0.7 ;; Keywords: convenience ;; Package-Requires: ((emacs "24.4") (loop "1.3") (dash "2.13.0") (s "1.11.0") (f "0.18.2") (spinner "1.7.3")) ;; URL: https://github.com/Wilfred/suggest.el ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; Suggest.el will find functions that give the output requested. It's ;; a great way of exploring list, string and arithmetic functions. ;;; Code: (require 'dash) (require 'loop) (require 's) (require 'f) (require 'spinner) (require 'subr-x) (require 'cl-extra) ;; cl-prettyprint (require 'cl-lib) ;; cl-incf, cl-evenp, cl-oddp (defcustom suggest-insert-example-on-start t "If t, insert example data in suggest buffer, else don't." :group 'suggest :type 'boolean) ;; See also `cl--simple-funcs' and `cl--safe-funcs'. (defvar suggest-functions (list ;; TODO: add funcall, apply and map? ;; Boolean functions #'not ;; Type predicates #'arrayp #'atom #'booleanp #'consp #'floatp #'functionp #'integerp #'listp #'numberp #'sequencep #'stringp #'symbolp ;; Built-in functions that access or examine lists. #'car #'cdr #'cadr #'cdar #'last #'cons #'nth #'nthcdr #'list #'length #'safe-length #'reverse #'remove #'remq #'append #'butlast ;; Built-in functions that create lists. #'make-list #'number-sequence ;; Sequence functions #'elt #'aref ;; CL list functions. #'cl-first #'cl-second #'cl-third #'cl-list* ;; dash.el list functions. #'-non-nil #'-slice #'-take #'-take-last #'-drop #'-drop-last #'-select-by-indices #'-select-column #'-concat #'-flatten #'-replace #'-replace-first #'-insert-at #'-replace-at #'-remove-at #'-remove-at-indices #'-sum #'-product #'-min #'-max #'-is-prefix-p #'-is-suffix-p #'-is-infix-p #'-split-at #'-split-on #'-partition #'-partition-all #'-elem-index #'-elem-indices #'-union #'-difference #'-intersection #'-distinct #'-rotate #'-sort #'-repeat #'-cons* #'-snoc #'-interpose #'-interleave #'-zip #'-first-item #'-last-item #'-butlast ;; alist functions #'assoc #'alist-get ;; plist functions #'plist-get #'lax-plist-get #'plist-member ;; hash tables #'gethash #'hash-table-keys #'hash-table-values ;; vectors ;; TODO: there must be more worth using #'vector #'vconcat ;; Arithmetic #'+ #'- #'* #'/ #'% #'mod #'max #'min #'ash #'lsh #'log #'expt #'sqrt #'abs #'float #'round #'truncate #'ceiling #'fceiling #'ffloor #'fround #'ftruncate #'1+ #'1- #'cl-evenp #'natnump #'cl-oddp #'zerop ;; Logical operators #'lsh #'logand #'logior #'logxor #'lognot ;; Strings #'string #'make-string #'upcase #'downcase #'substring #'concat #'split-string #'capitalize #'replace-regexp-in-string #'format #'string-join #'string-prefix-p #'string-suffix-p #'string-remove-prefix #'string-remove-suffix #'prin1-to-string ;; Quoting strings #'shell-quote-argument #'regexp-quote ;; s.el string functions #'s-trim #'s-trim-left #'s-trim-right #'s-pad-left #'s-pad-right #'s-chomp #'s-collapse-whitespace #'s-word-wrap #'s-left #'s-right #'s-chop-suffix #'s-chop-suffixes #'s-chop-prefix #'s-chop-prefixes #'s-shared-start #'s-shared-end #'s-truncate #'s-repeat #'s-concat #'s-prepend #'s-append #'s-lines #'s-split #'s-join #'s-ends-with-p #'s-starts-with-p #'s-contains-p #'s-replace #'s-capitalize #'s-index-of #'s-reverse #'s-count-matches #'s-split-words #'s-wrap ;; Symbols #'symbol-name #'symbol-value #'symbol-file #'intern ;; Converting between types #'string-to-list #'string-to-number #'string-to-char #'number-to-string #'char-to-string ;; Paths #'file-name-as-directory #'file-name-base #'file-name-directory #'file-name-nondirectory #'file-name-extension #'expand-file-name #'abbreviate-file-name #'directory-file-name ;; Paths with f.el #'f-join #'f-split #'f-filename #'f-parent #'f-common-parent #'f-ext #'f-no-ext #'f-base #'f-short #'f-long #'f-canonical #'f-slash #'f-depth #'f-relative ;; These are not pure, but still safe: #'f-files #'f-directories #'f-entries ;; Keyboard codes #'kbd #'key-description ;; Generic functions #'identity #'ignore ) "Functions that suggest will consider. These functions must not produce side effects, and must not be higher order functions. Side effects are obviously bad: we don't want to call `delete-file' with arbitrary strings! Higher order functions are any functions that call `funcall' or `apply'. These are not safe to call with arbitrary symbols, but see `suggest-funcall-functions'. The best functions for examples generally take a small number of arguments, and no arguments are functions. For other functions, the likelihood of users discovering them is too low. Likewise, we avoid predicates of one argument, as those generally need multiple examples to ensure they do what the user wants. See also `suggest-extra-args'.") (defvar suggest-funcall-functions (list ;; Higher order list functions. #'mapcar ;; When called with a symbol, read will call it. #'read ;; dash.el higher order list functions. #'-map #'-mapcat ;; dash.el folding/unfolding #'-reduce #'-reduce-r #'-iterate ) "Pure functions that may call `funcall'. We will consider consider these, but only with arguments that are known to be safe." ) (defvar suggest-extra-args (list ;; There's no special values for `list', and it produces silly ;; results when we add values. #'list '() ;; Similarly, we can use nil when building a list, but otherwise ;; we're just building an irrelevant list if we use the default ;; values. #'cons '(nil) #'-cons* '(nil) #'-snoc '(nil) #'append '(nil) ;; `format' has specific formatting strings that are worth trying. #'format '("%d" "%o" "%x" "%X" "%e" "%c" "%f" "%s" "%S") ;; `-iterate' is great for building incrementing/decrementing lists. ;; (an alternative to `number-sequence'). #'-iterate '(1+ 1-) ;; Lists can be sorted in a variety of ways. #'-sort '(< > string< string>) ;; For indexing functions, just use non-negative numbers. This ;; avoids confusing results like (last nil 5) => nil. #'elt '(0 1 2) #'nth '(0 1 2) #'nthcdr '(0 1 2) #'last '(0 1 2) #'-drop '(0 1 2) #'-drop-last '(0 1 2) #'-take '(0 1 2) ;; For functions that look up a value, don't supply any extra ;; arguments. #'plist-member '() #'assoc '() ;; Likewise for comparisons, there's no interesting extra value we can offer. #'-is-suffix-p '() #'-is-prefix-p '() #'-is-infix-p '() ;; Remove has some weird behaviours with t: (remove 'foo t) => t. ;; Only use nil as an extra value, so we can discover remove as an ;; alternative to `-non-nil'. #'remove '(nil) ;; mapcar with identity is a fun way to convert sequences (strings, ;; vectors) to lists. #'mapcar '(identity) ;; These common values often set flags in interesting ;; ways. t '(nil t -1 0 1 2)) "Some functions work best with a special extra argument. This plist associates functions with particular arguments that produce good results. If a function isn't explicitly mentioned, we look up `t' instead.") (defun suggest--safe (fn args) "Is FN safe to call with ARGS? Safety here means that we: * don't have any side effects, and * don't crash Emacs." (not (or ;; Due to Emacs bug #25684, string functions that call ;; caseify_object in casefiddle.c cause Emacs to segfault when ;; given negative integers. (and (memq fn '(upcase downcase capitalize upcase-initials)) (consp args) (null (cdr args)) (integerp (car args)) (< (car args) 0)) ;; If `read' is called with nil or t, it prompts interactively. (and (eq fn 'read) (member args '(nil (nil) (t)))) ;; Work around https://github.com/Wilfred/suggest.el/issues/37 on ;; Emacs 24, where it's possible to crash `read' with a list. (and (eq fn 'read) (eq emacs-major-version 24) (consp (car args))) ;; Work around https://github.com/magnars/dash.el/issues/241 (and (memq fn '(-interleave -zip)) (null args)) (and (memq fn suggest-funcall-functions) ; ;; TODO: what about circular lists? ;; ;; Does apply even handle that nicely? It looks like apply ;; tries to get the length of the list and hangs until C-g. (format-proper-list-p args) (--any (or ;; Don't call any higher order functions with symbols that ;; aren't known to be safe. (and (symbolp it) (not (memq it suggest-functions))) ;; Don't allow callable objects (interpreted or ;; byte-compiled function objects). (functionp it)) args))))) (defface suggest-heading '((((class color) (background light)) :foreground "DarkGoldenrod4" :weight bold) (((class color) (background dark)) :foreground "LightGoldenrod2" :weight bold)) "Face for headings." :group 'suggest) (defvar suggest--inputs-heading ";; Inputs (one per line):") (defvar suggest--outputs-heading ";; Desired output:") (defvar suggest--results-heading ";; Suggestions:") (defun suggest--insert-heading (text) "Highlight TEXT as a heading and insert in the current buffer." ;; Make a note of where the heading starts. (let ((excluding-last (substring text 0 (1- (length text)))) (last-char (substring text (1- (length text)))) (start (point)) end) ;; Insert the heading, ensuring it's not editable, (insert (propertize excluding-last 'read-only t)) ;; but allow users to type immediately after the heading. (insert (propertize last-char 'read-only t 'rear-nonsticky t)) ;; Point is now at the end of the heading, save that position. (setq end (point)) ;; Start the overlay after the ";; " bit. (let ((overlay (make-overlay (+ 3 start) end))) ;; Highlight the text in the heading. (overlay-put overlay 'face 'suggest-heading))) (insert "\n")) (defun suggest--on-heading-p () "Return t if point is on a heading." (get-char-property (point) 'read-only)) (defun suggest--raw-inputs () "Read the input lines in the current suggestion buffer." (let ((headings-seen 0) (raw-inputs nil)) (loop-for-each-line ;; Make a note of when we've passed the inputs heading. (when (and (suggest--on-heading-p)) (cl-incf headings-seen) (if (equal headings-seen 2) ;; Stop once we reach the outputs. (loop-return (nreverse raw-inputs)) (loop-continue))) ;; Skip over empty lines. (when (equal it "") (loop-continue)) (push (substring-no-properties it) raw-inputs)))) ;; TODO: check that there's only one line of output, or prevent ;; multiple lines being entered. (defun suggest--raw-output () "Read the output line in the current suggestion buffer." (save-excursion ;; Move past the 'desired output' heading. (suggest--nth-heading 2) (forward-line 1) ;; Skip blank lines. (while (looking-at "\n") (forward-line 1)) ;; Return the current line. (buffer-substring (point) (line-end-position)))) (defun suggest--keybinding (command keymap) "Find the keybinding for COMMAND in KEYMAP." (where-is-internal command keymap t)) (defun suggest--insert-section-break () "Insert section break." (insert "\n\n")) ;;;###autoload (defun suggest () "Open a Suggest buffer that provides suggestions for the inputs and outputs given." (interactive) (let ((buf (get-buffer-create "*suggest*")) (inhibit-read-only t) (inhibit-modification-hooks t)) (switch-to-buffer buf) (erase-buffer) (suggest-mode) (suggest--insert-heading suggest--inputs-heading) (when suggest-insert-example-on-start (insert "1\n2")) (suggest--insert-section-break) (suggest--insert-heading suggest--outputs-heading) (when suggest-insert-example-on-start (insert "3")) (suggest--insert-section-break) (suggest--insert-heading suggest--results-heading) ;; Populate the suggestions for 1, 2 => 3 (when suggest-insert-example-on-start (suggest-update)) ;; Put point on the first input. (suggest--nth-heading 1) (forward-line 1)) (add-hook 'first-change-hook (lambda () (suggest--update-needed t)) nil t)) (defun suggest--nth-heading (n) "Move point to Nth heading in the current *suggest* buffer. N counts from 1." (goto-char (point-min)) (let ((headings-seen 0)) (while (< headings-seen n) (when (suggest--on-heading-p) (cl-incf headings-seen)) (forward-line 1))) (forward-line -1)) (defun suggest--write-suggestions-string (text) "Write TEXT to the suggestion section." (let ((inhibit-read-only t)) (save-excursion ;; Move to the first line of the results. (suggest--nth-heading 3) (forward-line 1) ;; Remove the current suggestions text. (delete-region (point) (point-max)) ;; Insert the text, ensuring it can't be edited. (insert (propertize text 'read-only t))))) (defun suggest--format-output (value) "Format VALUE as the output to a function." (let* ((lines (s-lines (suggest--pretty-format value))) (prefixed-lines (--map-indexed (if (zerop it-index) (concat ";=> " it) (concat "; " it)) lines))) (s-join "\n" prefixed-lines))) (defun suggest--format-suggestion (suggestion output) "Format SUGGESTION as a lisp expression returning OUTPUT." (let ((formatted-call "")) ;; Build up a string "(func1 (func2 ... literal-inputs))" (let ((funcs (plist-get suggestion :funcs)) (literals (plist-get suggestion :literals))) (dolist (func funcs) (let ((func-sym (plist-get func :sym)) (variadic-p (plist-get func :variadic-p))) (if variadic-p (setq formatted-call (format "%s(apply #'%s " formatted-call func-sym)) (setq formatted-call (format "%s(%s " formatted-call func-sym))))) (setq formatted-call (format "%s%s" formatted-call (s-join " " literals))) (setq formatted-call (concat formatted-call (s-repeat (length funcs) ")")))) (let* (;; A string of spaces the same length as the suggestion. (matching-spaces (s-repeat (length formatted-call) " ")) (formatted-output (suggest--format-output output)) ;; Append the output to the formatted suggestion. If the ;; output runs over multiple lines, indent appropriately. (formatted-lines (--map-indexed (if (zerop it-index) (format "%s %s" formatted-call it) (format "%s %s" matching-spaces it)) (s-lines formatted-output)))) (s-join "\n" formatted-lines)))) (defun suggest--write-suggestions (suggestions output) "Write SUGGESTIONS to the current *suggest* buffer. SUGGESTIONS is a list of forms." (->> suggestions (--map (suggest--format-suggestion it output)) (s-join "\n") (suggest--write-suggestions-string))) ;; TODO: this is largely duplicated with refine.el and should be ;; factored out somewhere. (defun suggest--pretty-format (value) "Return a pretty-printed version of VALUE." (let ((cl-formatted (with-temp-buffer (cl-prettyprint value) (s-trim (buffer-string))))) (cond ((stringp value) ;; TODO: we should format newlines as \n (format "\"%s\"" value)) ;; Print nil and t as-is.' ((or (eq t value) (eq nil value)) (format "%s" value)) ;; Display other symbols, and lists, with a quote, so we ;; show usable syntax. ((or (symbolp value) (consp value)) (format "'%s" cl-formatted)) (t cl-formatted)))) (defun suggest--read-eval (form) "Read and eval FORM, but don't open a debugger on errors." (condition-case err (eval (read form)) (error (user-error "Could not eval %s: %s" form err)))) ;; TODO: this would be a good match for dash.el. (defun suggest--permutations (lst) "Return a list of all possible orderings of list LST." (cl-case (length lst) (0 nil) (1 (list lst)) (t ;; TODO: this is ugly. ;; TODO: extract an accumulate macro? (let ((permutations nil)) (--each-indexed lst (let* ((element it) (remainder (-remove-at it-index lst)) (remainder-perms (suggest--permutations remainder))) (--each remainder-perms (push (cons element it) permutations)))) (nreverse permutations))))) (defconst suggest--search-depth 4 "The maximum number of nested function calls to try. This tends to impact performance for values where many functions could work, especially numbers.") (defconst suggest--max-possibilities 20 "The maximum number of possibilities to return. This has a major impact on performance, and later possibilities tend to be progressively more silly.") (defconst suggest--max-intermediates 200) (defconst suggest--max-per-value 3) (defsubst suggest--classify-output (inputs func-output target-output) "Classify FUNC-OUTPUT so we can decide whether we should keep it." (cond ((equal func-output target-output) 'match) ;; If the function gave us nil, we're not going to ;; find any interesting values by further exploring ;; this value. ((null func-output) 'ignore) ;; If the function gave us the same target-output as our ;; input, don't bother exploring further. Too many ;; functions return the input if they can't do ;; anything with it. ((and (equal (length inputs) 1) (equal (-first-item inputs) func-output)) 'ignore) ;; The function returned a different result to what ;; we wanted, but might be worth exploring further. (t 'different))) (defsubst suggest--call (func values literals &optional variadic-p) "Call FUNC with VALUES, ignoring all errors. If FUNC returns a value, return a plist (:output ...). Returns nil otherwise." (when (suggest--safe func (if variadic-p (car values) values)) (let ((default-directory "/") (file-name-handler-alist nil) func-output func-success) (ignore-errors (setq func-output (if variadic-p (apply func (car values)) (apply func values))) (setq func-success t)) (when func-success (list :output func-output :variadic-p variadic-p :literals literals))))) (defun suggest--unread (value) "Convert VALUE to a string that can be read to obtain VALUE. This is primarily for quoting symbols." (cond ((consp value) (format "'%S" value)) ((functionp value) (format "#'%s" value)) ((and (symbolp value) (not (keywordp value)) (not (eq value nil)) (not (eq value t))) (format "'%s" value)) (t (format "%S" value)))) (defun suggest--try-call (iteration func input-values input-literals) "Try to call FUNC with INPUT-VALUES, and return a list of outputs" (let (outputs) ;; See if (func value1 value2...) gives us a value. (-when-let (result (suggest--call func input-values input-literals)) (push result outputs)) ;; See if (apply func input-values) gives us a value. (when (and (eq (length input-values) 1) (listp (car input-values))) (-when-let (result (suggest--call func input-values input-literals t)) (push result outputs))) ;; See if (func COMMON-CONSTANT value1 value2...) gives us a value. (when (zerop iteration) (dolist (extra-arg (if (plist-member suggest-extra-args func) (plist-get suggest-extra-args func) (plist-get suggest-extra-args t))) (dolist (position '(after before)) (let ((args (if (eq position 'before) (cons extra-arg input-values) (-snoc input-values extra-arg))) (literals (if (eq position 'before) (cons (suggest--unread extra-arg) input-literals) (-snoc input-literals (suggest--unread extra-arg))))) (-when-let (result (suggest--call func args literals)) (push result outputs)))))) ;; Return results in ascending order of preference, so we prefer ;; (+ 1 2) over (+ 0 1 2). (nreverse outputs))) (defun suggest--possibilities (input-literals input-values output) "Return a list of possibilities for these INPUTS-VALUES and OUTPUT. Each possbility form uses INPUT-LITERALS so we show variables rather than their values." (let (possibilities (possibilities-count 0) this-iteration intermediates (intermediates-count 0) (value-occurrences (make-hash-table :test #'equal)) (funcs (append suggest-functions suggest-funcall-functions)) ;; Since we incrementally build a huge list of results, ;; garbage collection does a load of work but doesn't find ;; much to collect. Disable GC, as it significantly improves ;; performance. (gc-cons-threshold 50000000)) ;; Setup: no function calls, all permutations of our inputs. (setq this-iteration (-map (-lambda ((values . literals)) (list :funcs nil :values values :literals literals)) ;; Only consider unique permutations. (-distinct (-zip-pair (suggest--permutations input-values) (suggest--permutations input-literals))))) (catch 'done (dotimes (iteration suggest--search-depth) (catch 'done-iteration (dolist (func funcs) ;; We need to call redisplay so the spinner keeps rotating ;; as we search. (redisplay) (loop-for-each item this-iteration (let ((literals (plist-get item :literals)) (values (plist-get item :values)) (funcs (plist-get item :funcs))) ;; Try to call the function, then classify its return values. (dolist (func-result (suggest--try-call iteration func values literals)) (let ((func-output (plist-get func-result :output))) (cl-case (suggest--classify-output values func-output output) ;; The function gave us the output we wanted, just save it. ('match (push (list :funcs (cons (list :sym func :variadic-p (plist-get func-result :variadic-p)) funcs) :literals (plist-get func-result :literals)) possibilities) (cl-incf possibilities-count) (when (>= possibilities-count suggest--max-possibilities) (throw 'done nil)) ;; If we're on the first iteration, we're just ;; searching all input permutations. Don't try any ;; other permutations, or we end up showing e.g. both ;; (+ 2 3) and (+ 3 2). (when (zerop iteration) ;; TODO: (throw 'done-func nil) (loop-break))) ;; The function returned a different result to what ;; we wanted. Build a list of these values so we ;; can explore them. ('different (when (and (< intermediates-count suggest--max-intermediates) (< (gethash func-output value-occurrences 0) suggest--max-per-value)) (puthash func-output (1+ (gethash func-output value-occurrences 0)) value-occurrences) (cl-incf intermediates-count) (push (list :funcs (cons (list :sym func :variadic-p (plist-get output :variadic-p)) funcs) :literals (plist-get func-result :literals) :values (list func-output)) intermediates)))))))))) (setq this-iteration intermediates) (setq intermediates nil) (setq intermediates-count 0))) ;; Return a plist of just :funcs and :literals, which is all we ;; need to render the result. (-map (lambda (res) (list :funcs (plist-get res :funcs) :literals (plist-get res :literals))) possibilities))) (defun suggest--cmp-relevance (pos1 pos2) "Compare two possibilities such that the more relevant result is smaller." (let ((pos1-func-count (length (plist-get pos1 :funcs))) (pos2-func-count (length (plist-get pos2 :funcs))) (pos1-arg-count (length (plist-get pos1 :literals))) (pos2-arg-count (length (plist-get pos2 :literals))) (pos1-apply-count (length (--filter (plist-get it :variadic-p) (plist-get pos1 :funcs)))) (pos2-apply-count (length (--filter (plist-get it :variadic-p) (plist-get pos2 :funcs))))) (cond ;; If we have the same number of function calls, with the same ;; number of arguments, prefer functions with shorter names. This ;; is a dumb but surprisingly effective heuristic. ((and (= pos1-func-count pos2-func-count) (= pos1-arg-count pos2-arg-count) (= pos1-apply-count pos2-apply-count)) (let ((join-names (lambda (pos) (->> (plist-get pos :funcs) (--map (plist-get it :sym)) (-map #'symbol-name) (apply #'concat))))) (< (length (funcall join-names pos1)) (length (funcall join-names pos2))))) ;; Prefer direct function calls to using apply. ((and (= pos1-func-count pos2-func-count) (= pos1-arg-count pos2-arg-count)) (< pos1-apply-count pos2-apply-count)) ;; Prefer calls that don't have extra arguments, so prefer (1+ 1) ;; over (+ 1 1). ((= pos1-func-count pos2-func-count) (< pos1-arg-count pos2-arg-count)) ;; Prefer fewer function calls over all else. (t (< (length (plist-get pos1 :funcs)) (length (plist-get pos2 :funcs))))))) (defvar suggest--spinner nil) ;;;###autoload (defun suggest-update () "Update the suggestions according to the latest inputs/output given." (interactive) (setq suggest--spinner (spinner-create 'progress-bar t)) (spinner-start suggest--spinner) ;; TODO: error on multiple inputs on one line. (let* ((raw-inputs (suggest--raw-inputs)) (inputs (--map (suggest--read-eval it) raw-inputs)) (raw-output (suggest--raw-output)) (desired-output (suggest--read-eval raw-output)) (possibilities (suggest--possibilities raw-inputs inputs desired-output))) ;; Sort, and take the top 5 most relevant results. (setq possibilities (-take 5 (-sort #'suggest--cmp-relevance possibilities))) (if possibilities (suggest--write-suggestions possibilities ;; We show the evalled output, not the raw input, so if ;; users use variables, we show the value of that variable. desired-output) (suggest--write-suggestions-string ";; No matches found."))) (setq suggest--spinner nil) (suggest--update-needed nil) (set-buffer-modified-p nil)) (define-derived-mode suggest-mode emacs-lisp-mode '("Suggest" (:eval (spinner-print suggest--spinner))) "A major mode for finding functions that provide the output requested.") (define-key suggest-mode-map (kbd "C-c C-c") #'suggest-update) (defun suggest--update-needed (update-needed) "Update the suggestions heading to say whether we need the user to call `suggest-update'." (save-excursion (suggest--nth-heading 3) (let ((inhibit-read-only t)) (delete-region (point) (line-end-position)) (if update-needed (suggest--insert-heading (format ";; Suggestions (press %s to update):" (key-description (suggest--keybinding #'suggest-update suggest-mode-map)))) (suggest--insert-heading suggest--results-heading))))) (provide 'suggest) ;;; suggest.el ends here suggest.el-0.7/Cask0000644000175000017500000000022013324645427014102 0ustar dogslegdogsleg(source melpa) (source gnu) (package-file "suggest.el") (development (depends-on "ert-runner") (depends-on "undercover") (depends-on "f")) suggest.el-0.7/test/0000755000175000017500000000000013324645427014263 5ustar dogslegdogslegsuggest.el-0.7/test/unit-test.el0000644000175000017500000000134613324645427016545 0ustar dogslegdogsleg(require 'suggest) (ert-deftest suggest--safe () (should (not (suggest--safe 'upcase '(-1)))) (should (suggest--safe 'upcase '(1))) (should (not (suggest--safe 'read '(nil)))) (should (suggest--safe 'read '(1 1))) (should (not (suggest--safe '-interleave '()))) (should (not (suggest--safe '-zip '()))) (should (suggest--safe '-interleave '(1 1)))) (ert-deftest suggest--unread () (should (equal (suggest--unread 'foo) "'foo")) (should (equal (suggest--unread nil) "nil")) (should (equal (suggest--unread '(1 2)) "'(1 2)")) (should (equal (suggest--unread :foo) ":foo")) (should (equal (suggest--unread 42) "42")) (should (equal (suggest--unread "foo") "\"foo\"")) (should (equal (suggest--unread '1+) "#'1+"))) suggest.el-0.7/test/suggest-commands-test.el0000644000175000017500000000436613324645427021053 0ustar dogslegdogsleg;;; suggest-command-test.el --- -*- lexical-binding: t; -*- (require 'ert) (require 'suggest) (ert-deftest suggest-smoke-test () "Basic test to verify that the suggest command works." (suggest)) (ert-deftest suggest-existing-buffer () "The suggest command should not error when called repeatedly." (suggest) (suggest)) (defmacro should-suggest (&rest inputs-output-form) "Given INPUT and OUTPUT, `suggest--possibilities' should propose FORM. FORM must use _ for the user's input." (-let* (((inputs (output form)) (-split-on '=> inputs-output-form)) (input-literals (-repeat (length inputs) "x"))) `(let* ((possibilities (suggest--possibilities ',input-literals (list ,@inputs) ,output)) (possibilities-funcs (--map (plist-get it :funcs) possibilities)) (possibilities-funcs-syms (-map (lambda (funcs) (--map (plist-get it :sym) funcs)) possibilities-funcs)) (expected-call ',(-butlast (-flatten form)))) (should (member expected-call possibilities-funcs-syms))))) (ert-deftest suggest-possibilities () ;; Ensure we offer built-in list functions. (should-suggest '(a b c d) => '(c d) (cdr (cdr _))) (should-suggest '(a b c d) => '(a b) (butlast (butlast _))) ;; We should reorder arguments if it gets the result desired. (should-suggest 2 3 => 9 (expt _)) ;; Compose functions. (should-suggest 2 3 => 7 (1- (expt _))) ;; Apply a list of arguments. (should-suggest '(2 3) => 5 (+ _)) ;; This reuqires custom extra arguments for `format', specifically ;; "%o". TODO: make `should-suggest' assert this argument. (should-suggest '25 => "31" (format _)) ;; Regression test. (should-suggest '(a b c d) 'c => 2 (-elem-index _)) (should-suggest 4 => '(1 2 3 4) (number-sequence _))) (ert-deftest suggest-higher-order-fns () "We should not call higher order functions with symbols that aren't whitelisted." ;; We should not end up calling the function `debug'. (suggest--possibilities (list "'debug") (list 'debug) 'foo)) suggest.el-0.7/test/suggest-format-test.el0000644000175000017500000000417613324645427020541 0ustar dogslegdogsleg(require 'ert) (require 'suggest) (ert-deftest suggest-format-t () (should (equal (suggest--pretty-format t) "t"))) (ert-deftest suggest-format-symbol () (should (equal (suggest--pretty-format 'x) "'x"))) (ert-deftest suggest-format-string () (should (equal (suggest--pretty-format "bar") "\"bar\""))) (ert-deftest suggest-format-string-list () (should (equal (suggest--pretty-format '("foo")) "'(\"foo\")"))) (ert-deftest suggest-format-output-symbol () (should (equal (suggest--format-output 'foo) ";=> 'foo"))) (ert-deftest suggest-format-output-multiline () (should (equal (suggest--format-output "foo\nbar") ";=> \"foo\n; bar\""))) (ert-deftest suggest-cmp () (let ((add-constant '(:funcs ((:sym + :variadic-p nil)) :literals ("1" "1"))) (call-fn '(:funcs ((:sym 1+ :variadic-p nil)) :literals ("1"))) (call-fn-varadic '(:funcs ((:sym 1+ :variadic-p t)) :literals ("1"))) (call-long '(:funcs ((:sym very-obscure-function :variadic-p nil)) :literals ("1"))) (call-2-fns '(:funcs ((:sym 1+ :variadic-p nil) (:sym identity :variadic-p nil)) :literals ("1")))) ;; Prefer a single function call to two. (should (equal (-sort #'suggest--cmp-relevance (list call-fn call-2-fns)) (list call-fn call-2-fns))) ;; Prefer shorter function names. (should (equal (-sort #'suggest--cmp-relevance (list call-fn call-long)) (list call-fn call-long))) ;; Prefer fewer literals (we add literals when suggesting values). (should (equal (-sort #'suggest--cmp-relevance (list call-fn add-constant)) (list call-fn add-constant))) ;; Prefer calling functions directly to `apply'. (should (equal (-sort #'suggest--cmp-relevance (list call-fn-varadic call-fn)) (list call-fn call-fn-varadic))) ;; TODO: prefer functions whose arguments are in the order ;; specified by the user. This helps with 1 3 => 3. )) suggest.el-0.7/test/test-helper.el0000644000175000017500000000067713324645427017053 0ustar dogslegdogsleg;;; test-helper.el --- Helper for tests -*- lexical-binding: t; -*- ;; Copyright (C) 2016 Wilfred Hughes ;; Author: ;;; Code: (require 'ert) (require 'f) (let ((suggest-dir (f-parent (f-dirname (f-this-file))))) (add-to-list 'load-path suggest-dir)) (require 'undercover) (undercover "suggest.el" (:exclude "*-test.el") (:report-file "/tmp/undercover-report.json")) ;;; test-helper.el ends here suggest.el-0.7/test/suggest-list-unit-test.el0000644000175000017500000000064713324645427021200 0ustar dogslegdogsleg(require 'ert) (require 'suggest) ;; Unit tests of pure list functions. (ert-deftest suggest-permutations-nil () (should (equal (suggest--permutations nil) nil))) (ert-deftest suggest-permutations-one-item () (should (equal (suggest--permutations '(x)) '((x))))) (ert-deftest suggest-permutations-two-items () (should (equal (suggest--permutations '(x y)) '((x y) (y x)))))