Fork of daniellemaywood.uk/gleam — Wasm codegen work
Something went wrong. Try again.
141 kB · 3865 lines
Rust
at wasm
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866// SPDX-License-Identifier: Apache-2.0// SPDX-FileCopyrightText: 2021 The Gleam contributors
use num_bigint::BigInt;use vec1::Vec1;
use super::{decision::ASSIGNMENT_VAR, *};use crate::{ ast::*, exhaustiveness::StringEncoding, line_numbers::LineNumbers, type_::{ ModuleValueConstructor, Type, TypedCallArg, ValueConstructor, ValueConstructorVariant, },};use pretty_arena::*;use std::sync::Arc;
#[derive(Debug, Clone)]pub enum Position { /// We are compiling the last expression in a function, meaning that it should /// use `return` to return the value it produces from the function. Tail, /// We are inside a function, but the value of this expression isn't being /// used, so we don't need to do anything with the returned value. Statement, /// The value of this expression needs to be used inside another expression, /// so we need to use the value that is returned by this expression. Expression(Ordering), /// We are compiling an expression inside a block, meaning we must assign /// to the `_block` variable at the end of the scope, because blocks are not /// expressions in JS. /// Since JS doesn't have variable shadowing, we must store the name of the /// variable being used, which will include the incrementing counter. /// For example, `block$2` Assign(EcoString),}
impl Position { /// Returns `true` if the position is [`Tail`]. /// /// [`Tail`]: Position::Tail #[must_use] pub fn is_tail(&self) -> bool { matches!(self, Self::Tail) }
#[must_use] pub fn ordering(&self) -> Ordering { match self { Self::Expression(ordering) => *ordering, Self::Tail | Self::Assign(_) | Self::Statement => Ordering::Loose, } }}
#[derive(Debug, Clone, Copy)]/// Determines whether we can lift blocks into statement level instead of using/// immediately invoked function expressions. Consider the following piece of code:////// ```gleam/// some_function(function_with_side_effect(), {/// let a = 10/// other_function_with_side_effects(a)/// })/// ```/// Here, if we lift the block that is the second argument of the function, we/// would end up running `other_function_with_side_effects` before/// `function_with_side_effects`. This would be invalid, as code in Gleam should be/// evaluated left-to-right, top-to-bottom. In this case, the ordering would be/// `Strict`, indicating that we cannot lift the block.////// However, in this example:////// ```gleam/// let value = !{/// let value = False/// some_function_with_side_effect()/// value/// }/// ```/// The only expression is the block, meaning it can be safely lifted without/// changing the evaluation order of the program. So the ordering is `Loose`.///pub enum Ordering { Strict, Loose,}
/// Tracking where the current function is a module function or an anonymous function.#[derive(Debug)]enum CurrentFunction { /// The current function is a module function /// /// ```gleam /// pub fn main() -> Nil { /// // we are here /// } /// ``` Module,
/// The current function is a module function, but one of its arguments shadows /// the reference to itself so it cannot recurse. /// /// ```gleam /// pub fn main(main: fn() -> Nil) -> Nil { /// // we are here /// } /// ``` ModuleWithShadowingArgument,
/// The current function is an anonymous function /// /// ```gleam /// pub fn main() -> Nil { /// fn() { /// // we are here /// } /// } /// ``` Anonymous,}
impl CurrentFunction { #[inline] fn can_recurse(&self) -> bool { match self { CurrentFunction::Module => true, CurrentFunction::ModuleWithShadowingArgument => false, CurrentFunction::Anonymous => false, } }}
/// The variables in scope while generating the code for a function.////// User variables are tracked in a map keyed by name, so a shadowed name can be/// given a unique JavaScript identifier. The compiler synthesised variables/// (the `$` case subject, `_pipe`, `_block`, ...) are held in their own fields/// instead. They can't be referenced by user code, and a branch that generates/// directly into its enclosing scope needs to restore the user variables while/// leaving the synthesised counters advanced, so that later code doesn't/// redeclare one of them.#[derive(Debug, Clone, Default)]pub(crate) struct Scope { user_variables: im::HashMap<EcoString, usize>, /// The highest suffix handed out for each user variable still declared in /// the current JS scope, kept across a directly matching `case` branch so a /// variable that leaked out of it is not redeclared by a later `let`. high_water: im::HashMap<EcoString, usize>, assignment: Option<usize>, pipe: Option<usize>, block: Option<usize>, use_assignment: Option<usize>, record_update: Option<usize>, capture: Option<usize>, assert_subject: Option<usize>, assert_fail: Option<usize>,}
impl Scope { fn new(user_variables: im::HashMap<EcoString, usize>) -> Self { Self { user_variables, ..Self::default() } }
/// The counter for a variable name, whether user or synthesised. fn counter(&self, name: &str) -> Option<usize> { match name { ASSIGNMENT_VAR => self.assignment, PIPE_VARIABLE => self.pipe, BLOCK_VARIABLE => self.block, USE_ASSIGNMENT_VARIABLE => self.use_assignment, RECORD_UPDATE_VARIABLE => self.record_update, CAPTURE_VARIABLE => self.capture, ASSERT_SUBJECT_VARIABLE => self.assert_subject, ASSERT_FAIL_VARIABLE => self.assert_fail, _ => self.user_variables.get(name).copied(), } }
/// Set the counter for a variable name, whether user or synthesised. pub(crate) fn set_counter(&mut self, name: &EcoString, value: usize) { match name.as_str() { ASSIGNMENT_VAR => self.assignment = Some(value), PIPE_VARIABLE => self.pipe = Some(value), BLOCK_VARIABLE => self.block = Some(value), USE_ASSIGNMENT_VARIABLE => self.use_assignment = Some(value), RECORD_UPDATE_VARIABLE => self.record_update = Some(value), CAPTURE_VARIABLE => self.capture = Some(value), ASSERT_SUBJECT_VARIABLE => self.assert_subject = Some(value), ASSERT_FAIL_VARIABLE => self.assert_fail = Some(value), _ => { let _ = self.user_variables.insert(name.clone(), value); } } }
/// Advance the counter for a name to its next suffix, skipping any suffix /// already handed out for it in the current scope so a name that leaked out /// of a directly matching branch can't be redeclared. fn advance_counter(&mut self, name: &EcoString) { let in_scope = self.counter(name).map_or(0, |i| i + 1); let high_water = self.high_water.get(name).map_or(0, |i| i + 1); let next = in_scope.max(high_water); self.set_counter(name, next); // Only user variables leak out of a directly matching branch; the // synthesised counters survive the restore on their own. if self.user_variables.contains_key(name) { let _ = self.high_water.insert(name.clone(), next); } }
/// The user variables currently in scope. pub(crate) fn user_variables(&self) -> &im::HashMap<EcoString, usize> { &self.user_variables }
/// Restore previously saved user variables, reverting any counters advanced /// during the branch to their earlier values. Variables introduced in the /// branch with no earlier binding are kept, and the synthesised counters are /// left untouched. pub(crate) fn restore_user_variables(&mut self, previous: &im::HashMap<EcoString, usize>) { self.user_variables.extend(previous.clone()); }}
#[derive(Debug)]pub(crate) struct Generator<'module, 'ast, 'doc> { module_name: EcoString, src_path: EcoString, line_numbers: &'module LineNumbers, function_name: EcoString, function_arguments: Vec<Option<&'module EcoString>>, current_function: CurrentFunction, pub current_scope: Scope, pub function_position: Position, pub scope_position: Position, // We register whether these features are used within an expression so that // the module generator can output a suitable function if it is needed. pub tracker: &'module mut UsageTracker, // We track whether tail call recursion is used so that we can render a loop // at the top level of the function to use in place of pushing new stack // frames. pub tail_recursion_used: bool, /// Statements to be compiled when lifting blocks into statement scope. /// For example, when compiling the following code: /// ```gleam /// let a = { /// let b = 1 /// b + 1 /// } /// ``` /// There will be 2 items in `statement_level`: The first will be `let _block;` /// The second will be the generated code for the block being assigned to `a`. /// This lets use return `_block` as the value that the block evaluated to, /// while still including the necessary code in the output at the right place. /// /// Once the `let` statement has compiled its value, it will add anything accumulated /// in `statement_level` to the generated code, so it will result in: /// /// ```javascript /// let _block; /// {...} /// let a = _block; /// ``` /// statement_level: Vec<Document<'ast, 'doc>>,
/// This will be true if we've generated a `let assert` statement that we know /// is guaranteed to throw. /// This means we can stop code generation for all the following statements /// in the same block! pub let_assert_always_panics: bool,
pub source_map_builder: Option<Rc<RefCell<DebugIgnore<sourcemap::SourceMapBuilder>>>>,}
impl<'module, 'a, 'doc> Generator<'module, 'a, 'doc> { #[allow(clippy::too_many_arguments)] // TODO: FIXME pub fn new( module_name: EcoString, src_path: EcoString, line_numbers: &'module LineNumbers, function_name: EcoString, function_arguments: Vec<Option<&'module EcoString>>, tracker: &'module mut UsageTracker, initial_scope_vars: im::HashMap<EcoString, usize>, source_map_builder: Option<Rc<RefCell<DebugIgnore<sourcemap::SourceMapBuilder>>>>, ) -> Self { let mut current_scope = Scope::new(initial_scope_vars); let mut current_function = CurrentFunction::Module; for &name in function_arguments.iter().flatten() { // Initialise the function arguments current_scope.set_counter(name, 0);
// If any of the function arguments shadow the current function then // recursion is no longer possible. if function_name.as_ref() == name { current_function = CurrentFunction::ModuleWithShadowingArgument; } } Self { tracker, module_name, src_path, line_numbers, function_name, function_arguments, tail_recursion_used: false, current_scope, current_function, function_position: Position::Tail, scope_position: Position::Tail, statement_level: Vec::new(), let_assert_always_panics: false, source_map_builder, } }
pub fn local_var(&mut self, name: &EcoString) -> EcoString { match self.current_scope.counter(name) { None => { self.current_scope.set_counter(name, 0); maybe_escape_identifier(name) } Some(0) => maybe_escape_identifier(name), Some(n) if name == "$" => eco_format!("${n}"), Some(n) => eco_format!("{name}${n}"), } }
pub fn next_local_var(&mut self, name: &EcoString) -> EcoString { self.current_scope.advance_counter(name); self.local_var(name) }
pub fn function_body( &mut self, arena: &'doc DocumentArena<'a, 'doc>, body: &'a [TypedStatement], arguments: &'a [TypedArg], ) -> Document<'a, 'doc> { let body = self.statements(arena, body); if self.tail_recursion_used { self.tail_call_loop(arena, body, arguments) } else { body } }
fn tail_call_loop( &mut self, arena: &'doc DocumentArena<'a, 'doc>, body: Document<'a, 'doc>, arguments: &'a [TypedArg], ) -> Document<'a, 'doc> { let loop_assignments = arena.concat(arguments.iter().flat_map(|arg| { arg.get_variable_name().map(|name| { let var = maybe_escape_identifier(name); docvec![ arena, self.source_map_tracker(arena, arg.location.start), LET_SPACE_DOCUMENT, var, SPACE_EQUAL_LOOP_DOLLAR_DOCUMENT, name, SEMICOLON_DOCUMENT, LINE_DOCUMENT ] }) })); docvec![ arena, WHILE_TRUE_OPEN_PAREN_DOCUMENT, docvec![arena, LINE_DOCUMENT, loop_assignments, body].nest(arena, INDENT), LINE_DOCUMENT, CLOSE_CURLY_DOCUMENT ] }
fn statement( &mut self, arena: &'doc DocumentArena<'a, 'doc>, statement: &'a TypedStatement, ) -> Document<'a, 'doc> { let expression_doc = match statement { Statement::Expression(expression) => self.expression(arena, expression), Statement::Assignment(assignment) => self.assignment(arena, assignment), Statement::Use(use_) => self.expression(arena, &use_.call), Statement::Assert(assert) => self.assert(arena, assert), }; self.add_statement_level(arena, expression_doc) }
fn add_statement_level( &mut self, arena: &'doc DocumentArena<'a, 'doc>, expression: Document<'a, 'doc>, ) -> Document<'a, 'doc> { if self.statement_level.is_empty() { expression } else { let mut statements = std::mem::take(&mut self.statement_level); statements.push(expression); arena.join(statements, LINE_DOCUMENT) } }
pub fn expression( &mut self, arena: &'doc DocumentArena<'a, 'doc>, expression: &'a TypedExpr, ) -> Document<'a, 'doc> { let mut document = match expression { TypedExpr::String { value, .. } => string(arena, value),
TypedExpr::Int { value, .. } => int(arena, value), TypedExpr::Float { float_value, .. } => float_from_value(arena, float_value.value()),
TypedExpr::List { elements, tail, .. } => { self.not_in_tail_position(Some(Ordering::Strict), |this| match tail { Some(tail) => { this.tracker.prepend_used = true; let tail = this.wrap_expression(arena, tail); prepend( arena, elements .iter() .map(|element| this.wrap_expression(arena, element)), tail, ) } None if elements.is_empty() => this.empty_list(), None => { this.tracker.list_used = true; list( arena, elements .iter() .map(|element| this.wrap_expression(arena, element)), ) } }) }
TypedExpr::Tuple { elements, .. } => self.tuple(arena, elements), TypedExpr::TupleIndex { tuple, index, .. } => self.tuple_index(arena, tuple, *index),
TypedExpr::Case { subjects, clauses, compiled_case, .. } => decision::case(arena, compiled_case, clauses, subjects, self),
TypedExpr::Call { fun, arguments, .. } => self.call(arena, fun, arguments), TypedExpr::Fn { arguments, body, kind, .. } => self.fn_(arena, arguments, body, kind),
TypedExpr::RecordAccess { record, label, .. } => { self.record_access(arena, record, label) }
TypedExpr::PositionalAccess { record, index, .. } => { self.positional_access(arena, record, *index) }
TypedExpr::RecordUpdate { updated_record_assigned_name, updated_record, constructor, arguments, .. } => self.record_update( arena, updated_record_assigned_name, updated_record, constructor, arguments, ),
TypedExpr::Var { name, constructor, .. } => self.variable(arena, name, constructor),
TypedExpr::Pipeline { first_value, assignments, finally, .. } => self.pipeline(arena, first_value, assignments.as_slice(), finally),
TypedExpr::Block { statements, .. } => self.block(arena, statements),
TypedExpr::BinOp { operator, left, right, .. } => self.bin_op(arena, operator, left, right),
TypedExpr::Todo { message, location, .. } => self.todo(arena, message.as_ref().map(|m| &**m), location),
TypedExpr::Panic { location, message, .. } => self.panic(arena, location, message.as_ref().map(|m| &**m)),
TypedExpr::BitArray { segments, .. } => self.bit_array(arena, segments),
TypedExpr::ModuleSelect { module_alias, label, constructor, .. } => self.module_select(arena, module_alias, label, constructor),
TypedExpr::NegateBool { value, .. } => { self.negate_with(arena, EXCLAMATION_MARK_DOCUMENT, value) }
TypedExpr::NegateInt { value, .. } => { self.negate_with(arena, MINUS_SPACE_DOCUMENT, value) }
TypedExpr::Echo { expression, message, location, .. } => { let expression = expression .as_ref() .expect("echo with no expression outside of pipe"); let expresion_doc = self.not_in_tail_position(None, |this| this.wrap_expression(arena, expression)); self.echo(arena, expresion_doc, message.as_deref(), location) }
TypedExpr::Invalid { .. } => { panic!("invalid expressions should not reach code generation") } }; if let Position::Statement = self.scope_position && expression_requires_semicolon(expression) { document = document.append(arena, SEMICOLON_DOCUMENT); } if expression.handles_own_return() { docvec![ arena, self.source_map_tracker(arena, expression.location().start), document ] } else { docvec![ arena, self.source_map_tracker(arena, expression.location().start), self.wrap_return(arena, document) ] } }
/// Return the singleton empty list; all empty lists are the same underlying /// reference, which makes comparison faster. fn empty_list(&mut self) -> Document<'a, 'doc> { self.tracker.list_empty_const_used = true; DOLLAR_LIST_DOLLAR_EMPTY_DOLLAR_CONST_DOCUMENT }
fn negate_with( &mut self, arena: &'doc DocumentArena<'a, 'doc>, with: Document<'a, 'doc>, value: &'a TypedExpr, ) -> Document<'a, 'doc> { self.not_in_tail_position(None, |this| { docvec![arena, with, this.wrap_expression(arena, value)] }) }
fn bit_array( &mut self, arena: &'doc DocumentArena<'a, 'doc>, segments: &'a [TypedExprBitArraySegment], ) -> Document<'a, 'doc> { self.tracker.bit_array_literal_used = true;
// Collect all the values used in segments. let segments_array = array( arena, segments.iter().map(|segment| { let value = self.not_in_tail_position(Some(Ordering::Strict), |this| { this.wrap_expression(arena, &segment.value) });
let details = self.bit_array_segment_details(arena, segment);
match details.type_ { BitArraySegmentType::BitArray => { if segment.size().is_some() { self.tracker.bit_array_slice_used = true; docvec![ arena, BIT_ARRAY_SLICE_OPEN_PAREN_DOCUMENT, value, COMMA_ZERO_COMMA_SPACE, details.size, CLOSE_PAREN_DOCUMENT ] } else { value } } BitArraySegmentType::Int => { match (details.size_value, segment.value.as_ref()) { (Some(size_value), TypedExpr::Int { int_value, .. }) if size_value <= SAFE_INT_SEGMENT_MAX_SIZE.into() && (&size_value % BigInt::from(8) == BigInt::ZERO) => { let bytes = bit_array_segment_int_value_to_bytes( int_value.clone(), size_value, segment.endianness(), );
u8_slice(arena, &bytes) }
(Some(size_value), _) if size_value == 8.into() => value,
(Some(size_value), _) if size_value <= 0.into() => EMPTY_DOCUMENT,
_ => { self.tracker.sized_integer_segment_used = true; let size = details.size; let is_big = bool(segment.endianness().is_big()); docvec![ arena, SIZED_INT_OPEN_PAREN_DOCUMENT, value, COMMA_SPACE_DOCUMENT, size, COMMA_SPACE_DOCUMENT, is_big, CLOSE_PAREN_DOCUMENT ] } } } BitArraySegmentType::Float => { self.tracker.float_bit_array_segment_used = true; let size = details.size; let is_big = bool(details.endianness.is_big()); docvec![ arena, SIZED_FLOAT_OPEN_PAREN_DOCUMENT, value, COMMA_SPACE_DOCUMENT, size, COMMA_SPACE_DOCUMENT, is_big, CLOSE_PAREN_DOCUMENT ] } BitArraySegmentType::String(StringEncoding::Utf8) => { self.tracker.string_bit_array_segment_used = true; docvec![ arena, STRING_BITS_OPEN_PAREN_DOCUMENT, value, CLOSE_PAREN_DOCUMENT ] } BitArraySegmentType::String(StringEncoding::Utf16) => { self.tracker.string_utf16_bit_array_segment_used = true; let is_big = bool(details.endianness.is_big()); docvec![ arena, STRING_TO_UTF16_OPEN_PAREN_DOCUMENT, value, COMMA_SPACE_DOCUMENT, is_big, CLOSE_PAREN_DOCUMENT ] } BitArraySegmentType::String(StringEncoding::Utf32) => { self.tracker.string_utf32_bit_array_segment_used = true; let is_big = bool(details.endianness.is_big()); docvec![ arena, STRING_TO_UTF32_OPEN_PAREN_DOCUMENT, value, COMMA_SPACE_DOCUMENT, is_big, CLOSE_PAREN_DOCUMENT ] } BitArraySegmentType::UtfCodepoint(StringEncoding::Utf8) => { self.tracker.codepoint_bit_array_segment_used = true; docvec![ arena, CODEPOINT_BITS_OPEN_PAREN_DOCUMENT, value, CLOSE_PAREN_DOCUMENT ] } BitArraySegmentType::UtfCodepoint(StringEncoding::Utf16) => { self.tracker.codepoint_utf16_bit_array_segment_used = true; let is_big = bool(details.endianness.is_big()); docvec![ arena, CODEPOINT_TO_UTF16_OPEN_PAREN_DOCUMENT, value, COMMA_SPACE_DOCUMENT, is_big, CLOSE_PAREN_DOCUMENT ] } BitArraySegmentType::UtfCodepoint(StringEncoding::Utf32) => { self.tracker.codepoint_utf32_bit_array_segment_used = true; let is_big = bool(details.endianness.is_big()); docvec![ arena, CODEPOINT_TO_UTF32_OPEN_PAREN_DOCUMENT, value, COMMA_SPACE_DOCUMENT, is_big, CLOSE_PAREN_DOCUMENT ] } } }), );
docvec![ arena, TO_BIT_ARRAY_OPEN_PAREN_DOCUMENT, segments_array, CLOSE_PAREN_DOCUMENT ] }
fn bit_array_segment_details( &mut self, arena: &'doc DocumentArena<'a, 'doc>, segment: &'a TypedExprBitArraySegment, ) -> BitArraySegmentDetails<'a, 'doc> { let size = segment.size(); let unit = segment.unit(); let (size_value, size) = match size { Some(TypedExpr::Int { int_value, .. }) => { let size_value = int_value * unit; let size = eco_format!("{size_value}").to_doc(arena); (Some(size_value), size) } Some(size) => { let mut size = self.not_in_tail_position(Some(Ordering::Strict), |this| { this.wrap_expression(arena, size) });
if unit != 1 { size = size.group(arena).append( arena, SPACE_TIMES_SPACE_DOCUMENT.append(arena, unit.to_doc(arena)), ); }
(None, size) }
None => { let size_value: usize = if segment.type_.is_int() { 8 } else { 64 }; (Some(BigInt::from(size_value)), size_value.to_doc(arena)) } };
let type_ = BitArraySegmentType::from_segment(segment);
BitArraySegmentDetails { type_, size, size_value, endianness: segment.endianness(), } }
pub fn wrap_return( &mut self, arena: &'doc DocumentArena<'a, 'doc>, document: Document<'a, 'doc>, ) -> Document<'a, 'doc> { match &self.scope_position { Position::Tail => docvec![arena, RETURN_SPACE_DOCUMENT, document, SEMICOLON_DOCUMENT], Position::Expression(_) | Position::Statement => document, Position::Assign(name) => docvec![ arena, name.clone(), SPACE_EQUAL_SPACE_DOCUMENT, document, SEMICOLON_DOCUMENT ], } }
pub fn not_in_tail_position<CompileFn, Output>( &mut self, // If ordering is None, it is inherited from the parent scope. // It will be None in cases like `!x`, where `x` can be lifted // only if the ordering is already loose. ordering: Option<Ordering>, compile: CompileFn, ) -> Output where CompileFn: Fn(&mut Self) -> Output, { let new_ordering = ordering.unwrap_or(self.scope_position.ordering());
let function_position = std::mem::replace( &mut self.function_position, Position::Expression(new_ordering), ); let scope_position = std::mem::replace(&mut self.scope_position, Position::Expression(new_ordering));
let result = compile(self);
self.function_position = function_position; self.scope_position = scope_position; result }
/// Use the `_block` variable if the expression is JS statement. pub fn wrap_expression( &mut self, arena: &'doc DocumentArena<'a, 'doc>, expression: &'a TypedExpr, ) -> Document<'a, 'doc> { match (expression, &self.scope_position) { (_, Position::Tail | Position::Assign(_)) => self.expression(arena, expression), ( TypedExpr::Panic { .. } | TypedExpr::Todo { .. } | TypedExpr::Case { .. } | TypedExpr::Pipeline { .. } | TypedExpr::RecordUpdate { // Record updates that assign a variable generate multiple statements updated_record_assigned_name: Some(_), .. }, Position::Expression(Ordering::Loose), ) => self.wrap_block(arena, |this| this.expression(arena, expression)), ( TypedExpr::Panic { .. } | TypedExpr::Todo { .. } | TypedExpr::Case { .. } | TypedExpr::Pipeline { .. } | TypedExpr::RecordUpdate { // Record updates that assign a variable generate multiple statements updated_record_assigned_name: Some(_), .. }, Position::Expression(Ordering::Strict), ) => self.immediately_invoked_function_expression( arena, expression, |this, expression| this.expression(arena, expression), ), _ => self.expression(arena, expression), } }
/// Wrap an expression using the `_block` variable if required due to being /// a JS statement, or in parens if required due to being an operator or /// a function literal. pub fn child_expression( &mut self, arena: &'doc DocumentArena<'a, 'doc>, expression: &'a TypedExpr, ) -> Document<'a, 'doc> { match expression { TypedExpr::BinOp { operator, .. } if operator.is_operator_to_wrap() => {} TypedExpr::Fn { .. } => {}
TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::Block { .. } | TypedExpr::Pipeline { .. } | TypedExpr::Var { .. } | TypedExpr::List { .. } | TypedExpr::Call { .. } | TypedExpr::BinOp { .. } | TypedExpr::Case { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::ModuleSelect { .. } | TypedExpr::Tuple { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Todo { .. } | TypedExpr::Panic { .. } | TypedExpr::Echo { .. } | TypedExpr::BitArray { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::NegateBool { .. } | TypedExpr::NegateInt { .. } | TypedExpr::Invalid { .. } => return self.wrap_expression(arena, expression), }
let document = self.expression(arena, expression); match &self.scope_position { // Here the document is a return statement: `return <expr>;` // or an assignment: `_block = <expr>;` Position::Tail | Position::Assign(_) | Position::Statement => document, Position::Expression(_) => { docvec![arena, OPEN_PAREN_DOCUMENT, document, CLOSE_PAREN_DOCUMENT] } } }
/// Wrap an expression in an immediately invoked function expression fn immediately_invoked_function_expression<T, ToDoc>( &mut self, arena: &'doc DocumentArena<'a, 'doc>, statements: &'a T, to_doc: ToDoc, ) -> Document<'a, 'doc> where ToDoc: FnOnce(&mut Self, &'a T) -> Document<'a, 'doc>, { // Save initial state let scope_position = std::mem::replace(&mut self.scope_position, Position::Tail); let statement_level = std::mem::take(&mut self.statement_level);
// Set state for in this iife let current_scope = self.current_scope.clone();
// Generate the expression let result = to_doc(self, statements); let doc = self.add_statement_level(arena, result); let doc = immediately_invoked_function_expression_document(arena, doc);
// Reset self.current_scope = current_scope; self.scope_position = scope_position; self.statement_level = statement_level;
self.wrap_return(arena, doc) }
fn wrap_block<CompileFn>( &mut self, arena: &'doc DocumentArena<'a, 'doc>, compile: CompileFn, ) -> Document<'a, 'doc> where CompileFn: Fn(&mut Self) -> Document<'a, 'doc>, { let block_variable = self.next_local_var(&BLOCK_VARIABLE.into());
// Save initial state let scope_position = std::mem::replace( &mut self.scope_position, Position::Assign(block_variable.clone()), ); let function_position = std::mem::replace( &mut self.function_position, Position::Expression(Ordering::Strict), );
// Generate the expression let statement_doc = compile(self);
// Reset self.scope_position = scope_position; self.function_position = function_position;
self.statement_level.push(docvec![ arena, LET_SPACE_DOCUMENT, block_variable.clone(), SEMICOLON_DOCUMENT ]); self.statement_level.push(statement_doc);
self.wrap_return(arena, block_variable.to_doc(arena)) }
fn variable( &mut self, arena: &'doc DocumentArena<'a, 'doc>, name: &'a EcoString, constructor: &'a ValueConstructor, ) -> Document<'a, 'doc> { match &constructor.variant { ValueConstructorVariant::Record { arity, name: variant_name, .. } => { let type_ = constructor.type_.clone(); self.record_constructor(arena, type_, None, variant_name, name, *arity) } ValueConstructorVariant::ModuleFn { .. } | ValueConstructorVariant::ModuleConstant { .. } | ValueConstructorVariant::LocalVariable { .. } => self.local_var(name).to_doc(arena), } }
fn pipeline( &mut self, arena: &'doc DocumentArena<'a, 'doc>, first_value: &'a TypedPipelineAssignment, assignments: &'a [(TypedPipelineAssignment, PipelineAssignmentKind)], finally: &'a TypedExpr, ) -> Document<'a, 'doc> { let count = assignments.len(); let mut documents = Vec::with_capacity((count + 2) * 2);
let all_assignments = std::iter::once(first_value) .chain(assignments.iter().map(|(assignment, _kind)| assignment));
let mut latest_local_var: Option<EcoString> = None; for assignment in all_assignments { // An echo in a pipeline won't result in an assignment, instead it // just prints the previous variable assigned in the pipeline. if let TypedExpr::Echo { expression: None, message, location, .. } = assignment.value.as_ref() { documents.push(self.not_in_tail_position(Some(Ordering::Strict), |this| { let var = latest_local_var .as_ref() .expect("echo with no previous step in a pipe"); this.echo(arena, var.to_doc(arena), message.as_deref(), location) })); documents.push(SEMICOLON_DOCUMENT); } else { // Otherwise we assign the intermediate pipe value to a variable. let assignment_document = self.not_in_tail_position(Some(Ordering::Strict), |this| { this.simple_variable_assignment( arena, &assignment.name, &assignment.value, assignment.location, ) }); documents.push(self.add_statement_level(arena, assignment_document)); latest_local_var = Some(self.local_var(&assignment.name)); }
documents.push(LINE_DOCUMENT); }
if let TypedExpr::Echo { expression: None, message, location, .. } = finally { let var = latest_local_var.expect("echo with no previous step in a pipe"); documents.push(self.echo(arena, var.to_doc(arena), message.as_deref(), location)); match &self.scope_position { Position::Statement => documents.push(SEMICOLON_DOCUMENT), Position::Expression(_) | Position::Tail | Position::Assign(_) => {} } } else { let finally_doc = self.expression(arena, finally); documents.push(self.add_statement_level(arena, finally_doc)); }
arena.concat(documents).force_break(arena) }
pub(crate) fn expression_flattening_blocks( &mut self, arena: &'doc DocumentArena<'a, 'doc>, expression: &'a TypedExpr, ) -> Document<'a, 'doc> { if let TypedExpr::Block { statements, .. } = expression { self.statements(arena, statements) } else { self.expression(arena, expression) } }
fn block( &mut self, arena: &'doc DocumentArena<'a, 'doc>, statements: &'a Vec1<TypedStatement>, ) -> Document<'a, 'doc> { if statements.len() == 1 { match statements.first() { Statement::Expression(expression) => { return self.child_expression(arena, expression); }
Statement::Assignment(assignment) => match &assignment.kind { AssignmentKind::Let | AssignmentKind::Generated => { return self.child_expression(arena, &assignment.value); } // We can't just return the right-hand side of a `let assert` // assignment; we still need to check that the pattern matches. AssignmentKind::Assert { .. } => {} },
Statement::Use(use_) => return self.child_expression(arena, &use_.call),
// Similar to `let assert`, we can't immediately return the value // that is asserted; we have to actually perform the assertion. Statement::Assert(_) => {} } } match &self.scope_position { Position::Tail | Position::Assign(_) | Position::Statement => { self.block_document(arena, statements) } Position::Expression(Ordering::Strict) => self.immediately_invoked_function_expression( arena, statements, |this, statements| this.statements(arena, statements), ), Position::Expression(Ordering::Loose) => self.wrap_block(arena, |this| { // Save previous scope let current_scope = this.current_scope.clone();
let document = this.block_document(arena, statements);
// Restore previous state this.current_scope = current_scope;
document }), } }
fn block_document( &mut self, arena: &'doc DocumentArena<'a, 'doc>, statements: &'a Vec1<TypedStatement>, ) -> Document<'a, 'doc> { let statements = self.statements(arena, statements); docvec![ arena, OPEN_CURLY_DOCUMENT, docvec![arena, LINE_DOCUMENT, statements].nest(arena, INDENT), LINE_DOCUMENT, CLOSE_CURLY_DOCUMENT ] }
fn statements( &mut self, arena: &'doc DocumentArena<'a, 'doc>, statements: &'a [TypedStatement], ) -> Document<'a, 'doc> { // If there are any statements that need to be printed at statement level, that's // for an outer scope so we don't want to print them inside this one. let statement_level = std::mem::take(&mut self.statement_level); let count = statements.len(); let mut documents = Vec::with_capacity(count * 3); for (i, statement) in statements.iter().enumerate() { if i + 1 < count { let function_position = std::mem::replace(&mut self.function_position, Position::Statement); let scope_position = std::mem::replace(&mut self.scope_position, Position::Statement);
documents.push(self.statement(arena, statement));
self.function_position = function_position; self.scope_position = scope_position;
documents.push(LINE_DOCUMENT); } else { documents.push(self.statement(arena, statement)); }
// If we've generated code for a statement that always throws, we // can skip code generation for all the following ones. if self.let_assert_always_panics { self.let_assert_always_panics = false; break; } } self.statement_level = statement_level; if count == 1 { arena.concat(documents) } else { arena.concat(documents).force_break(arena) } }
fn simple_variable_assignment( &mut self, arena: &'doc DocumentArena<'a, 'doc>, name: &'a EcoString, value: &'a TypedExpr, location: SrcSpan, ) -> Document<'a, 'doc> { // Subject must be rendered before the variable for variable numbering let subject = self.not_in_tail_position(Some(Ordering::Loose), |this| { this.wrap_expression(arena, value) }); let js_name = self.next_local_var(name); let assignment = docvec![ arena, self.source_map_tracker(arena, location.start), LET_SPACE_DOCUMENT, js_name.clone(), SPACE_EQUAL_SPACE_DOCUMENT, subject, SEMICOLON_DOCUMENT ]; let assignment = match &self.scope_position { Position::Expression(_) | Position::Statement => assignment, Position::Tail => docvec![ arena, assignment, LINE_DOCUMENT, RETURN_SPACE_DOCUMENT, js_name, SEMICOLON_DOCUMENT ], Position::Assign(block_variable) => docvec![ arena, assignment, LINE_DOCUMENT, block_variable.clone(), SPACE_EQUAL_SPACE_DOCUMENT, js_name, SEMICOLON_DOCUMENT ], };
assignment.force_break(arena) }
fn assignment( &mut self, arena: &'doc DocumentArena<'a, 'doc>, assignment: &'a TypedAssignment, ) -> Document<'a, 'doc> { let TypedAssignment { pattern, kind, value, compiled_case, location, annotation: _, } = assignment;
// In case the pattern is just a variable, we special case it to // generate just a simple assignment instead of using the decision tree // for the code generation step. if let TypedPattern::Variable { name, .. } = pattern { return self.simple_variable_assignment(arena, name, value, *location); }
docvec![ arena, self.source_map_tracker(arena, location.start), decision::let_(arena, compiled_case, value, kind, self, pattern) ] }
fn assert( &mut self, arena: &'doc DocumentArena<'a, 'doc>, assert: &'a TypedAssert, ) -> Document<'a, 'doc> { let TypedAssert { location, value, message, } = assert;
let message = match message { Some(message) => self.not_in_tail_position(Some(Ordering::Strict), |this| { this.expression(arena, message) }), None => string(arena, "Assertion failed."), };
let check = self.not_in_tail_position(Some(Ordering::Loose), |this| { this.assert_check(arena, value, &message, *location) });
match &self.scope_position { Position::Expression(_) | Position::Statement => check, Position::Tail | Position::Assign(_) => { docvec![ arena, check, LINE_DOCUMENT, self.wrap_return(arena, UNDEFINED_DOCUMENT) ] } } }
fn assert_check( &mut self, arena: &'doc DocumentArena<'a, 'doc>, subject: &'a TypedExpr, message: &Document<'a, 'doc>, location: SrcSpan, ) -> Document<'a, 'doc> { let (subject_document, mut fields) = match subject { TypedExpr::Call { fun, arguments, .. } => { let argument_variables = arguments .iter() .map(|element| { self.not_in_tail_position(Some(Ordering::Strict), |this| { this.assign_to_variable(arena, &element.value) }) }) .collect_vec(); ( self.call_with_doc_arguments(arena, fun, argument_variables.clone()), vec![ ("kind", string(arena, "function_call")), ( "arguments", array( arena, argument_variables.into_iter().zip(arguments).map( |(variable, argument)| { self.asserted_expression( arena, AssertExpression::from_expression(&argument.value), Some(variable), argument.location(), ) }, ), ), ), ], ) }
TypedExpr::BinOp { operator, left, right, .. } => { match operator { BinOp::And => return self.assert_and(arena, left, right, message, location), BinOp::Or => return self.assert_or(arena, left, right, message, location), BinOp::Eq | BinOp::NotEq | BinOp::LtInt | BinOp::LtEqInt | BinOp::LtFloat | BinOp::LtEqFloat | BinOp::GtEqInt | BinOp::GtInt | BinOp::GtEqFloat | BinOp::GtFloat | BinOp::AddInt | BinOp::AddFloat | BinOp::SubInt | BinOp::SubFloat | BinOp::MultInt | BinOp::MultFloat | BinOp::DivInt | BinOp::DivFloat | BinOp::RemainderInt | BinOp::Concatenate => {} }
let left_document = self.not_in_tail_position(Some(Ordering::Loose), |this| { this.assign_to_variable(arena, left) }); let right_document = self.not_in_tail_position(Some(Ordering::Loose), |this| { this.assign_to_variable(arena, right) });
( self.bin_op_with_doc_operands( arena, *operator, left_document, right_document, &left.type_(), ) .surround(arena, OPEN_PAREN_DOCUMENT, CLOSE_PAREN_DOCUMENT), vec![ ("kind", string(arena, "binary_operator")), ("operator", string(arena, operator.name())), ( "left", self.asserted_expression( arena, AssertExpression::from_expression(left), Some(left_document), left.location(), ), ), ( "right", self.asserted_expression( arena, AssertExpression::from_expression(right), Some(right_document), right.location(), ), ), ], ) }
TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::Block { .. } | TypedExpr::Pipeline { .. } | TypedExpr::Var { .. } | TypedExpr::Fn { .. } | TypedExpr::List { .. } | TypedExpr::Case { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::ModuleSelect { .. } | TypedExpr::Tuple { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Todo { .. } | TypedExpr::Panic { .. } | TypedExpr::Echo { .. } | TypedExpr::BitArray { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::NegateBool { .. } | TypedExpr::NegateInt { .. } | TypedExpr::Invalid { .. } => ( self.wrap_expression(arena, subject), vec![ ("kind", string(arena, "expression")), ( "expression", self.asserted_expression( arena, AssertExpression::from_expression(subject), Some(FALSE_LOWERCASE_DOCUMENT), subject.location(), ), ), ], ), };
fields.push(("start", location.start.to_doc(arena))); fields.push(("end", subject.location().end.to_doc(arena))); fields.push(("expression_start", subject.location().start.to_doc(arena)));
docvec![ arena, self.source_map_tracker(arena, location.start), IF_SPACE_OPEN_PAREN_DOCUMENT, docvec![arena, EXCLAMATION_MARK_DOCUMENT, subject_document].nest(arena, INDENT), EMPTY_BREAK_DOCUMENT, CLOSE_PAREN_SPACE_OPEN_CURLY_DOCUMENT, docvec![ arena, LINE_DOCUMENT, self.throw_error(arena, "assert", message, location, fields), ] .nest(arena, INDENT), LINE_DOCUMENT, CLOSE_CURLY_DOCUMENT, ] .group(arena) }
fn negate_bool_expression( &mut self, arena: &'doc DocumentArena<'a, 'doc>, value: &'a TypedExpr, ) -> Document<'a, 'doc> { match value { TypedExpr::BinOp { operator, left, right, .. } => match operator { BinOp::And => self.print_bin_op(arena, left, right, "||"), BinOp::Or => self.print_bin_op(arena, left, right, "&&"), BinOp::Eq => self.equal(arena, left, right, false), BinOp::NotEq => self.equal(arena, left, right, true), BinOp::LtInt | BinOp::LtFloat => self.print_bin_op(arena, left, right, ">="), BinOp::LtEqInt | BinOp::LtEqFloat => self.print_bin_op(arena, left, right, ">"), BinOp::GtInt | BinOp::GtFloat => self.print_bin_op(arena, left, right, "<="), BinOp::GtEqInt | BinOp::GtEqFloat => self.print_bin_op(arena, left, right, "<"), BinOp::AddInt | BinOp::AddFloat | BinOp::SubInt | BinOp::SubFloat | BinOp::MultInt | BinOp::MultFloat | BinOp::DivInt | BinOp::DivFloat | BinOp::RemainderInt | BinOp::Concatenate => unreachable!("type checking should make this impossible"), }, TypedExpr::NegateBool { value, .. } => self.wrap_expression(arena, value), TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::Block { .. } | TypedExpr::Pipeline { .. } | TypedExpr::Var { .. } | TypedExpr::Fn { .. } | TypedExpr::List { .. } | TypedExpr::Call { .. } | TypedExpr::Case { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::ModuleSelect { .. } | TypedExpr::Tuple { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Todo { .. } | TypedExpr::Panic { .. } | TypedExpr::Echo { .. } | TypedExpr::BitArray { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::NegateInt { .. } | TypedExpr::Invalid { .. } => docvec![ arena, EXCLAMATION_MARK_DOCUMENT, self.wrap_expression(arena, value) ], } }
/// In Gleam, the `&&` operator is short-circuiting, meaning that we can't /// pre-evaluate both sides of it, and use them in the exception that is /// thrown. /// Instead, we need to implement this short-circuiting logic ourself. /// /// If we short-circuit, we must leave the second expression unevaluated, /// and signal that using the `unevaluated` variant, as detailed in the /// exception format. For the first expression, we know it must be `false`, /// otherwise we would have continued by evaluating the second expression. /// /// Similarly, if we do evaluate the second expression and fail, we know /// that the first expression must have evaluated to `true`, and the second /// to `false`. This way, we avoid needing to evaluate either expression /// twice. /// /// The generated code then looks something like this: /// ```javascript /// if (expr1) { /// if (!expr2) { /// <throw exception> /// } /// } else { /// <throw exception> /// } /// ``` /// fn assert_and( &mut self, arena: &'doc DocumentArena<'a, 'doc>, left: &'a TypedExpr, right: &'a TypedExpr, message: &Document<'a, 'doc>, location: SrcSpan, ) -> Document<'a, 'doc> { let left_kind = AssertExpression::from_expression(left); let right_kind = AssertExpression::from_expression(right);
let fields_if_short_circuiting = vec![ ("kind", string(arena, "binary_operator")), ("operator", string(arena, "&&")), ( "left", self.asserted_expression( arena, left_kind, Some(FALSE_LOWERCASE_DOCUMENT), left.location(), ), ), ( "right", self.asserted_expression( arena, AssertExpression::Unevaluated, None, right.location(), ), ), ("start", location.start.to_doc(arena)), ("end", right.location().end.to_doc(arena)), ("expression_start", left.location().start.to_doc(arena)), ];
let fields = vec![ ("kind", string(arena, "binary_operator")), ("operator", string(arena, "&&")), ( "left", self.asserted_expression( arena, left_kind, Some(TRUE_LOWERCASE_DOCUMENT), left.location(), ), ), ( "right", self.asserted_expression( arena, right_kind, Some(FALSE_LOWERCASE_DOCUMENT), right.location(), ), ), ("start", location.start.to_doc(arena)), ("end", right.location().end.to_doc(arena)), ("expression_start", left.location().start.to_doc(arena)), ];
let left_value = self.not_in_tail_position(Some(Ordering::Loose), |this| { this.wrap_expression(arena, left) });
let right_value = self.not_in_tail_position(Some(Ordering::Strict), |this| { this.negate_bool_expression(arena, right) });
let right_check = docvec![ arena, LINE_DOCUMENT, IF_SPACE_OPEN_PAREN_DOCUMENT, right_value.nest(arena, INDENT), CLOSE_PAREN_SPACE_OPEN_CURLY_DOCUMENT, docvec![ arena, LINE_DOCUMENT, self.throw_error(arena, "assert", message, location, fields) ] .nest(arena, INDENT), LINE_DOCUMENT, CLOSE_CURLY_DOCUMENT, ];
docvec![ arena, self.source_map_tracker(arena, location.start), IF_SPACE_OPEN_PAREN_DOCUMENT, left_value.nest(arena, INDENT), CLOSE_PAREN_SPACE_OPEN_CURLY_DOCUMENT, right_check.nest(arena, INDENT), LINE_DOCUMENT, CLOSE_CURLY_ELSE_OPEN_CURLY_DOCUMENT, docvec![ arena, LINE_DOCUMENT, self.throw_error( arena, "assert", message, location, fields_if_short_circuiting ) ] .nest(arena, INDENT), LINE_DOCUMENT, CLOSE_CURLY_DOCUMENT ] }
/// Similar to `&&`, `||` is also short-circuiting in Gleam. However, if `||` /// short-circuits, that's because the first expression evaluated to `true`, /// meaning the whole assertion succeeds. This allows us to directly use the /// `||` operator in JavaScript. /// /// The only difference is that due to the nature of `||`, if the assertion fails, /// we know that both sides must have evaluated to `false`, so we don't /// need to store the values of them in variables beforehand. fn assert_or( &mut self, arena: &'doc DocumentArena<'a, 'doc>, left: &'a TypedExpr, right: &'a TypedExpr, message: &Document<'a, 'doc>, location: SrcSpan, ) -> Document<'a, 'doc> { let fields = vec![ ("kind", string(arena, "binary_operator")), ("operator", string(arena, "||")), ( "left", self.asserted_expression( arena, AssertExpression::from_expression(left), Some(FALSE_LOWERCASE_DOCUMENT), left.location(), ), ), ( "right", self.asserted_expression( arena, AssertExpression::from_expression(right), Some(FALSE_LOWERCASE_DOCUMENT), right.location(), ), ), ("start", location.start.to_doc(arena)), ("end", right.location().end.to_doc(arena)), ("expression_start", left.location().start.to_doc(arena)), ];
let left_value = self.not_in_tail_position(Some(Ordering::Loose), |this| { this.child_expression(arena, left) });
let right_value = self.not_in_tail_position(Some(Ordering::Strict), |this| { this.child_expression(arena, right) });
docvec![ arena, LINE_DOCUMENT, self.source_map_tracker(arena, location.start), IF_SPACE_OPEN_PAREN_DOCUMENT, docvec![ arena, EXCLAMATION_MARK_OPEN_PAREN_DOCUMENT, left_value, SPACE_DOUBLE_VERTICAL_BAR_SPACE_DOCUMENT, right_value, CLOSE_PAREN_DOCUMENT ] .nest(arena, INDENT), CLOSE_PAREN_SPACE_OPEN_CURLY_DOCUMENT, docvec![ arena, LINE_DOCUMENT, self.throw_error(arena, "assert", message, location, fields) ] .nest(arena, INDENT), LINE_DOCUMENT, CLOSE_CURLY_DOCUMENT, ] }
fn assign_to_variable( &mut self, arena: &'doc DocumentArena<'a, 'doc>, value: &'a TypedExpr, ) -> Document<'a, 'doc> { if let TypedExpr::Var { .. } = value { self.expression(arena, value) } else { let value = self.wrap_expression(arena, value); let variable = self.next_local_var(&ASSIGNMENT_VAR.into()); let assignment = docvec![ arena, LET_SPACE_DOCUMENT, variable.clone(), SPACE_EQUAL_SPACE_DOCUMENT, value, SEMICOLON_DOCUMENT ]; self.statement_level.push(assignment); variable.to_doc(arena) } }
fn asserted_expression( &mut self, arena: &'doc DocumentArena<'a, 'doc>, kind: AssertExpression, value: Option<Document<'a, 'doc>>, location: SrcSpan, ) -> Document<'a, 'doc> { let kind = match kind { AssertExpression::Literal => string(arena, "literal"), AssertExpression::Expression => string(arena, "expression"), AssertExpression::Unevaluated => string(arena, "unevaluated"), };
let start = location.start.to_doc(arena); let end = location.end.to_doc(arena); let items = if let Some(value) = value { vec![ ("kind", kind), ("value", value), ("start", start), ("end", end), ] } else { vec![("kind", kind), ("start", start), ("end", end)] };
wrap_object( arena, items .into_iter() .map(|(key, value)| (key.to_doc(arena), Some(value))), ) }
fn tuple( &mut self, arena: &'doc DocumentArena<'a, 'doc>, elements: &'a [TypedExpr], ) -> Document<'a, 'doc> { self.not_in_tail_position(Some(Ordering::Strict), |this| { array( arena, elements .iter() .map(|element| this.wrap_expression(arena, element)), ) }) }
fn call( &mut self, arena: &'doc DocumentArena<'a, 'doc>, fun: &'a TypedExpr, arguments: &'a [TypedCallArg], ) -> Document<'a, 'doc> { let arguments = arguments .iter() .map(|element| { self.not_in_tail_position(Some(Ordering::Strict), |this| { this.wrap_expression(arena, &element.value) }) }) .collect_vec();
self.call_with_doc_arguments(arena, fun, arguments) }
fn call_with_doc_arguments( &mut self, arena: &'doc DocumentArena<'a, 'doc>, fun: &'a TypedExpr, arguments: Vec<Document<'a, 'doc>>, ) -> Document<'a, 'doc> { match fun { // Qualified record construction TypedExpr::ModuleSelect { constructor: ModuleValueConstructor::Record { name, .. }, module_alias, .. } => self.wrap_return( arena, construct_record(arena, Some(module_alias), name, arguments), ),
// Record construction TypedExpr::Var { constructor: ValueConstructor { variant: ValueConstructorVariant::Record { .. }, type_, .. }, name, .. } => { if type_.is_result_constructor() { if name == "Ok" { self.tracker.ok_used = true; } else if name == "Error" { self.tracker.error_used = true; } } self.wrap_return(arena, construct_record(arena, None, name, arguments)) }
// Tail call optimisation. If we are calling the current function // and we are in tail position we can avoid creating a new stack // frame, enabling recursion with constant memory usage. TypedExpr::Var { name, .. } if self.function_name == *name && self.current_function.can_recurse() && self.function_position.is_tail() && self.current_scope.counter(name) == Some(0) => { // Record that tail recursion is happening so that we know to // render the loop at the top level of the function. self.tail_recursion_used = true; arena.concat( arguments .into_iter() .zip(&self.function_arguments) .enumerate() .map(|(i, (element, argument))| { let mut doc = EMPTY_DOCUMENT;
if i != 0 { doc = doc.append(arena, LINE_DOCUMENT); } // Create an assignment for each variable created by the function arguments if let Some(name) = argument { doc = docvec![ arena, doc, LOOP_DOLLAR_DOCUMENT, name.to_doc(arena), SPACE_EQUAL_SPACE_DOCUMENT ]; } // Render the value given to the function. Even if it is not // assigned we still render it because the expression may // have some side effects. docvec![arena, doc, element, SEMICOLON_DOCUMENT] }), ) }
TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::Block { .. } | TypedExpr::Pipeline { .. } | TypedExpr::Var { .. } | TypedExpr::Fn { .. } | TypedExpr::List { .. } | TypedExpr::Call { .. } | TypedExpr::BinOp { .. } | TypedExpr::Case { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::ModuleSelect { .. } | TypedExpr::Tuple { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Todo { .. } | TypedExpr::Panic { .. } | TypedExpr::Echo { .. } | TypedExpr::BitArray { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::NegateBool { .. } | TypedExpr::NegateInt { .. } | TypedExpr::Invalid { .. } => { let fun = self.not_in_tail_position(None, |this| -> Document<'_, '_> { let is_fn_literal = matches!(fun, TypedExpr::Fn { .. }); let fun = this.wrap_expression(arena, fun); if is_fn_literal { docvec![arena, OPEN_PAREN_DOCUMENT, fun, CLOSE_PAREN_DOCUMENT] } else { fun } }); let arguments = call_arguments(arena, arguments); self.wrap_return(arena, docvec![arena, fun, arguments]) } } }
fn fn_( &mut self, arena: &'doc DocumentArena<'a, 'doc>, arguments: &'a [TypedArg], body: &'a [TypedStatement], kind: &FunctionLiteralKind, ) -> Document<'a, 'doc> { // New function, this is now the tail position let function_position = std::mem::replace(&mut self.function_position, Position::Tail); let scope_position = std::mem::replace(&mut self.scope_position, Position::Tail);
// And there's a new scope let scope = self.current_scope.clone(); for name in arguments.iter().flat_map(Arg::get_variable_name) { self.current_scope.set_counter(name, 0); }
// This is a new function so track that so that we don't // mistakenly trigger tail call optimisation let mut current_function = CurrentFunction::Anonymous; std::mem::swap(&mut self.current_function, &mut current_function);
// Generate the function body let result = self.statements(arena, body);
// Reset function name, scope, and tail position tracking self.function_position = function_position; self.scope_position = scope_position; self.current_scope = scope; std::mem::swap(&mut self.current_function, &mut current_function);
let mut docs = EMPTY_DOCUMENT;
// If the function is a use then we need to add a source map tracker // before the result to denote that the function is created by the use if let FunctionLiteralKind::Use { location } = kind { docs = docs.append(arena, self.source_map_tracker(arena, location.start)); } docs = docs.append(arena, fun_arguments(arena, arguments, false)); docs = docs.append(arena, SPACE_EQUAL_ARROW_SPACE_OPEN_CURLY_DOCUMENT); docs = docs.append(arena, BREAKABLE_SPACE_DOCUMENT); docs = docs.append(arena, result);
docvec![ arena, docs.nest(arena, INDENT) .append(arena, BREAKABLE_SPACE_DOCUMENT) .group(arena), CLOSE_CURLY_DOCUMENT, ] }
fn record_access( &mut self, arena: &'doc DocumentArena<'a, 'doc>, record: &'a TypedExpr, label: &'a str, ) -> Document<'a, 'doc> { self.not_in_tail_position(None, |this| { let record = this.wrap_expression(arena, record); docvec![arena, record, DOT_DOCUMENT, maybe_escape_property(label)] }) }
fn positional_access( &mut self, arena: &'doc DocumentArena<'a, 'doc>, record: &'a TypedExpr, index: u64, ) -> Document<'a, 'doc> { self.not_in_tail_position(None, |this| { let record = this.wrap_expression(arena, record); docvec![ arena, record, OPEN_SQUARE_DOCUMENT, index, CLOSE_SQUARE_DOCUMENT ] }) }
fn record_update( &mut self, arena: &'doc DocumentArena<'a, 'doc>, updated_record_assigned_name: &'a Option<EcoString>, updated_record: &'a TypedExpr, constructor: &'a TypedExpr, arguments: &'a [TypedCallArg], ) -> Document<'a, 'doc> { match updated_record_assigned_name.as_ref() { Some(name) => { docvec![ arena, self.not_in_tail_position(None, |this| this.simple_variable_assignment( arena, name, updated_record, updated_record.location(), )), LINE_DOCUMENT, self.call(arena, constructor, arguments), ] } None => self.call(arena, constructor, arguments), } }
fn tuple_index( &mut self, arena: &'doc DocumentArena<'a, 'doc>, tuple: &'a TypedExpr, index: u64, ) -> Document<'a, 'doc> { self.not_in_tail_position(None, |this| { let tuple = this.wrap_expression(arena, tuple); docvec![arena, tuple, eco_format!("[{index}]")] }) }
fn bin_op( &mut self, arena: &'doc DocumentArena<'a, 'doc>, name: &'a BinOp, left: &'a TypedExpr, right: &'a TypedExpr, ) -> Document<'a, 'doc> { match name { BinOp::And => self.print_bin_op(arena, left, right, "&&"), BinOp::Or => self.print_bin_op(arena, left, right, "||"), BinOp::LtInt | BinOp::LtFloat => self.print_bin_op(arena, left, right, "<"), BinOp::LtEqInt | BinOp::LtEqFloat => self.print_bin_op(arena, left, right, "<="), BinOp::Eq => self.equal(arena, left, right, true), BinOp::NotEq => self.equal(arena, left, right, false), BinOp::GtInt | BinOp::GtFloat => self.print_bin_op(arena, left, right, ">"), BinOp::GtEqInt | BinOp::GtEqFloat => self.print_bin_op(arena, left, right, ">="), BinOp::Concatenate | BinOp::AddInt | BinOp::AddFloat => { self.print_bin_op(arena, left, right, "+") } BinOp::SubInt | BinOp::SubFloat => self.print_bin_op(arena, left, right, "-"), BinOp::MultInt | BinOp::MultFloat => self.print_bin_op(arena, left, right, "*"), BinOp::RemainderInt => self.remainder_int(arena, left, right), BinOp::DivInt => self.div_int(arena, left, right), BinOp::DivFloat => self.div_float(arena, left, right), } }
fn div_int( &mut self, arena: &'doc DocumentArena<'a, 'doc>, left: &'a TypedExpr, right: &'a TypedExpr, ) -> Document<'a, 'doc> { let left_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| { this.child_expression(arena, left) }); let right_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| { this.child_expression(arena, right) });
// If we have a constant value divided by zero then it's safe to replace // it directly with 0. if left.is_literal() && right.is_zero_compile_time_number() { "0".to_doc(arena) } else if right.is_non_zero_compile_time_number() { let division = if let TypedExpr::BinOp { .. } = left { docvec![ arena, left_doc.surround(arena, OPEN_PAREN_DOCUMENT, CLOSE_PAREN_DOCUMENT), SPACE_SLASH_SPACE_DOCUMENT, right_doc ] } else { docvec![arena, left_doc, SPACE_SLASH_SPACE_DOCUMENT, right_doc] }; docvec![ arena, GLOBAL_THIS_DOT_MATH_DOT_TRUNC_DOCUMENT, wrap_arguments(arena, [division]) ] } else { self.tracker.int_division_used = true; docvec![ arena, DIVIDE_INT_DOCUMENT, wrap_arguments(arena, [left_doc, right_doc]) ] } }
fn remainder_int( &mut self, arena: &'doc DocumentArena<'a, 'doc>, left: &'a TypedExpr, right: &'a TypedExpr, ) -> Document<'a, 'doc> { let left_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| { this.child_expression(arena, left) }); let right_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| { this.child_expression(arena, right) });
// If we have a constant value divided by zero then it's safe to replace // it directly with 0. if left.is_literal() && right.is_zero_compile_time_number() { "0".to_doc(arena) } else if right.is_non_zero_compile_time_number() { if let TypedExpr::BinOp { .. } = left { docvec![ arena, left_doc.surround(arena, OPEN_PAREN_DOCUMENT, CLOSE_PAREN_DOCUMENT), SPACE_MODULO_SPACE_DOCUMENT, right_doc ] } else { docvec![arena, left_doc, SPACE_MODULO_SPACE_DOCUMENT, right_doc] } } else { self.tracker.int_remainder_used = true; docvec![ arena, "remainderInt", wrap_arguments(arena, [left_doc, right_doc]) ] } }
fn div_float( &mut self, arena: &'doc DocumentArena<'a, 'doc>, left: &'a TypedExpr, right: &'a TypedExpr, ) -> Document<'a, 'doc> { let left_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| { this.child_expression(arena, left) }); let right_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| { this.child_expression(arena, right) });
// If we have a constant value divided by zero then it's safe to replace // it directly with 0. if left.is_literal() && right.is_zero_compile_time_number() { "0.0".to_doc(arena) } else if right.is_non_zero_compile_time_number() { if let TypedExpr::BinOp { .. } = left { docvec![ arena, left_doc.surround(arena, OPEN_PAREN_DOCUMENT, CLOSE_PAREN_DOCUMENT), SPACE_SLASH_SPACE_DOCUMENT, right_doc ] } else { docvec![arena, left_doc, SPACE_SLASH_SPACE_DOCUMENT, right_doc] } } else { self.tracker.float_division_used = true; docvec![ arena, "divideFloat", wrap_arguments(arena, [left_doc, right_doc]) ] } }
fn equal( &mut self, arena: &'doc DocumentArena<'a, 'doc>, left: &'a TypedExpr, right: &'a TypedExpr, should_be_equal: bool, ) -> Document<'a, 'doc> { // If it is a simple scalar type then we can use JS' reference identity if is_js_scalar(left.type_()) { let left_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| { this.child_expression(arena, left) }); let right_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| { this.child_expression(arena, right) }); let operator = if should_be_equal { SPACE_TRIPLE_EQUAL_SPACE_DOCUMENT } else { SPACE_EXCLAMATION_MARK_DOUBLE_EQUAL_SPACE_DOCUMENT }; return docvec![arena, left_doc, operator, right_doc]; }
// For comparison with singleton custom types, ie, one with no fields. // If you have some code like this // ```gleam // pub type Wibble { // Wibble // Wobble // } // // pub fn is_wibble(w: Wibble) -> Bool { // w == Wibble // } // ``` // Instead of `isEqual(w, new Wibble())`, generate `w instanceof Wibble` // because the first approach needs to construct a new Wibble, and then call the isEqual function, // which supports any shape of data, and so does a lot of extra logic which isn't necessary.
if let Some(doc) = self.singleton_variant_equality(arena, left, right, should_be_equal) { return doc; }
if let Some(doc) = self.singleton_variant_equality(arena, right, left, should_be_equal) { return doc; }
// Other types must be compared using structural equality let left = self.not_in_tail_position(Some(Ordering::Strict), |this| { this.wrap_expression(arena, left) }); let right = self.not_in_tail_position(Some(Ordering::Strict), |this| { this.wrap_expression(arena, right) });
self.prelude_equal_call(arena, should_be_equal, left, right) }
fn singleton_variant_equality( &mut self, arena: &'doc DocumentArena<'a, 'doc>, left: &'a TypedExpr, right: &'a TypedExpr, should_be_equal: bool, ) -> Option<Document<'a, 'doc>> { match right { TypedExpr::Var { name, constructor: ValueConstructor { variant: ValueConstructorVariant::Record { arity: 0, name: variant_name, .. }, .. }, .. } => { let left_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| { this.wrap_expression(arena, left) }); Some(self.singleton_equal( arena, left_doc, None, name.clone(), should_be_equal, variant_name.clone(), right.type_(), )) } TypedExpr::ModuleSelect { module_alias, constructor: ModuleValueConstructor::Record { arity: 0, name, .. }, .. } => { let left_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| { this.wrap_expression(arena, left) }); Some(self.singleton_equal( arena, left_doc, Some(module_alias), name.clone(), should_be_equal, name.clone(), right.type_(), )) } // Empty lists are implemented as a variant with no fields, so we can // use `instanceof` for a faster check. TypedExpr::List { elements, .. } if elements.is_empty() => { let left_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| { this.wrap_expression(arena, left) }); self.tracker.list_empty_class_used = true; Some(self.singleton_equal( arena, left_doc, None, "$Empty".into(), should_be_equal, "Empty".into(), right.type_(), )) } TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::Block { .. } | TypedExpr::Pipeline { .. } | TypedExpr::Var { .. } | TypedExpr::Fn { .. } | TypedExpr::List { .. } | TypedExpr::Call { .. } | TypedExpr::BinOp { .. } | TypedExpr::Case { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::ModuleSelect { .. } | TypedExpr::Tuple { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Todo { .. } | TypedExpr::Panic { .. } | TypedExpr::Echo { .. } | TypedExpr::BitArray { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::NegateBool { .. } | TypedExpr::NegateInt { .. } | TypedExpr::Invalid { .. } => None, } }
#[allow(clippy::too_many_arguments)] fn singleton_equal( &mut self, arena: &'doc DocumentArena<'a, 'doc>, value: Document<'a, 'doc>, module: Option<&'a str>, name: EcoString, should_be_equal: bool, variant_name: EcoString, type_: Arc<Type>, ) -> Document<'a, 'doc> { // If we're using this variant unqualified, register it as used so that // we know to import it. This `instanceof` check only happens if the // variant has no fields, so we don't need to check that here. if module.is_none() && let Some((package, module, type_name)) = type_.named_type_name_and_package() { _ = self .tracker .variants_used_in_instanceof .insert(TypeVariant { package, module, type_name, name: variant_name, }); } let record = if let Some(module) = module { docvec![arena, DOLLAR_DOCUMENT, module, DOT_DOCUMENT, name] } else { name.to_doc(arena) };
if should_be_equal { docvec![arena, value, SPACE_INSTANCE_OF_SPACE_DOCUMENT, record] } else { docvec![ arena, EXCLAMATION_MARK_OPEN_PAREN_DOCUMENT, value, SPACE_INSTANCE_OF_SPACE_DOCUMENT, record, CLOSE_PAREN_DOCUMENT ] } }
fn equal_with_doc_operands( &mut self, arena: &'doc DocumentArena<'a, 'doc>, left: Document<'a, 'doc>, right: Document<'a, 'doc>, type_: Arc<Type>, should_be_equal: bool, ) -> Document<'a, 'doc> { // If it is a simple scalar type then we can use JS' reference identity if is_js_scalar(type_) { let operator = if should_be_equal { SPACE_TRIPLE_EQUAL_SPACE_DOCUMENT } else { SPACE_EXCLAMATION_MARK_DOUBLE_EQUAL_SPACE_DOCUMENT }; return docvec![arena, left, operator, right]; }
// Other types must be compared using structural equality self.prelude_equal_call(arena, should_be_equal, left, right) }
pub(super) fn prelude_equal_call( &mut self, arena: &'doc DocumentArena<'a, 'doc>, should_be_equal: bool, left: Document<'a, 'doc>, right: Document<'a, 'doc>, ) -> Document<'a, 'doc> { // Record that we need to import the prelude's isEqual function into the module self.tracker.object_equality_used = true; // Construct the call let arguments = wrap_arguments(arena, [left, right]); let operator = if should_be_equal { IS_EQUAL_DOCUMENT } else { EXCLAMATION_MARK_IS_EQUAL_DOCUMENT }; docvec![arena, operator, arguments] }
fn print_bin_op( &mut self, arena: &'doc DocumentArena<'a, 'doc>, left: &'a TypedExpr, right: &'a TypedExpr, op: &'a str, ) -> Document<'a, 'doc> { let left = self.not_in_tail_position(Some(Ordering::Strict), |this| { this.child_expression(arena, left) }); let right = self.not_in_tail_position(Some(Ordering::Strict), |this| { this.child_expression(arena, right) }); docvec![arena, left, SPACE_DOCUMENT, op, SPACE_DOCUMENT, right] }
pub(super) fn bin_op_with_doc_operands( &mut self, arena: &'doc DocumentArena<'a, 'doc>, name: BinOp, left: Document<'a, 'doc>, right: Document<'a, 'doc>, type_: &Arc<Type>, ) -> Document<'a, 'doc> { match name { BinOp::And => docvec![arena, left, SPACE_DOUBLE_AMPERSAND_SPACE_DOCUMENT, right], BinOp::Or => docvec![arena, left, SPACE_DOUBLE_VERTICAL_BAR_SPACE_DOCUMENT, right], BinOp::LtInt | BinOp::LtFloat => { docvec![arena, left, SPACE_LT_INT_SPACE_DOCUMENT, right] } BinOp::LtEqInt | BinOp::LtEqFloat => { docvec![arena, left, SPACE_LT_EQ_INT_SPACE_DOCUMENT, right] } BinOp::Eq => self.equal_with_doc_operands(arena, left, right, type_.clone(), true), BinOp::NotEq => self.equal_with_doc_operands(arena, left, right, type_.clone(), false), BinOp::GtInt | BinOp::GtFloat => { docvec![arena, left, SPACE_GT_INT_SPACE_DOCUMENT, right] } BinOp::GtEqInt | BinOp::GtEqFloat => { docvec![arena, left, SPACE_GT_EQ_INT_SPACE_DOCUMENT, right] } BinOp::Concatenate | BinOp::AddInt | BinOp::AddFloat => { docvec![arena, left, SPACE_PLUS_SPACE_DOCUMENT, right] } BinOp::SubInt | BinOp::SubFloat => { docvec![arena, left, SPACE_MINUS_SPACE_DOCUMENT, right] } BinOp::MultInt | BinOp::MultFloat => { docvec![arena, left, SPACE_TIMES_SPACE_DOCUMENT, right] } BinOp::RemainderInt => { self.tracker.int_remainder_used = true; docvec![ arena, CAMEL_CASE_REMAINDER_INT_DOCUMENT, wrap_arguments(arena, [left, right]) ] } BinOp::DivInt => { self.tracker.int_division_used = true; docvec![ arena, DIVIDE_INT_DOCUMENT, wrap_arguments(arena, [left, right]) ] } BinOp::DivFloat => { self.tracker.float_division_used = true; docvec![ arena, DIVIDE_FLOAT_DOCUMENT, wrap_arguments(arena, [left, right]) ] } } }
fn todo( &mut self, arena: &'doc DocumentArena<'a, 'doc>, message: Option<&'a TypedExpr>, location: &'a SrcSpan, ) -> Document<'a, 'doc> { let message = match message { Some(m) => self.not_in_tail_position(None, |this| this.wrap_expression(arena, m)), None => string( arena, "`todo` expression evaluated. This code has not yet been implemented.", ), }; self.throw_error(arena, "todo", &message, *location, vec![]) }
fn panic( &mut self, arena: &'doc DocumentArena<'a, 'doc>, location: &'a SrcSpan, message: Option<&'a TypedExpr>, ) -> Document<'a, 'doc> { let message = match message { Some(m) => self.not_in_tail_position(None, |this| this.wrap_expression(arena, m)), None => string(arena, "`panic` expression evaluated."), }; self.throw_error(arena, "panic", &message, *location, vec![]) }
pub(crate) fn throw_error<Fields>( &mut self, arena: &'doc DocumentArena<'a, 'doc>, error_name: &'a str, message: &Document<'a, 'doc>, location: SrcSpan, fields: Fields, ) -> Document<'a, 'doc> where Fields: IntoIterator<Item = (&'a str, Document<'a, 'doc>)>, { self.tracker.make_error_used = true; let module = self.module_name.clone().to_doc(arena).surround( arena, DOUBLE_QUOTE_DOCUMENT, DOUBLE_QUOTE_DOCUMENT, ); let function = self.function_name.clone().to_doc(arena).surround( arena, DOUBLE_QUOTE_DOCUMENT, DOUBLE_QUOTE_DOCUMENT, ); let line = self.line_numbers.line_number(location.start).to_doc(arena); let fields = wrap_object( arena, fields.into_iter().map(|(k, v)| (k.to_doc(arena), Some(v))), );
docvec![ arena, self.source_map_tracker(arena, location.start), THROW_MAKE_ERROR_DOCUMENT, wrap_arguments( arena, [ string(arena, error_name), FILEPATH_UPPERCASE_DOCUMENT, module, line, function, *message, fields ] ), ] }
fn module_select( &mut self, arena: &'doc DocumentArena<'a, 'doc>, module: &'a str, label: &'a EcoString, constructor: &'a ModuleValueConstructor, ) -> Document<'a, 'doc> { match constructor { ModuleValueConstructor::Fn { .. } | ModuleValueConstructor::Constant { .. } => { docvec![ arena, DOLLAR_DOCUMENT, module, DOT_DOCUMENT, maybe_escape_identifier(label) ] }
ModuleValueConstructor::Record { name, arity, type_, .. } => self.record_constructor(arena, type_.clone(), Some(module), name, name, *arity), } }
fn echo( &mut self, arena: &'doc DocumentArena<'a, 'doc>, expression: Document<'a, 'doc>, message: Option<&'a TypedExpr>, location: &'a SrcSpan, ) -> Document<'a, 'doc> { self.tracker.echo_used = true;
let message = match message { Some(message) => self.not_in_tail_position(Some(Ordering::Strict), |this| { this.wrap_expression(arena, message) }), None => UNDEFINED_DOCUMENT, };
let echo_arguments = call_arguments( arena, vec![ expression, message, self.src_path.clone().to_doc(arena), self.line_numbers.line_number(location.start).to_doc(arena), ], ); self.wrap_return(arena, docvec![arena, ECHO_DOCUMENT, echo_arguments]) }
pub(crate) fn constant_expression( &mut self, arena: &'doc DocumentArena<'a, 'doc>, context: Context, expression: &'a TypedConstant, ) -> Document<'a, 'doc> { match expression { Constant::Int { value, .. } => int(arena, value), Constant::Float { value, .. } => float(arena, value), Constant::String { value, .. } => string(arena, value), Constant::Tuple { elements, .. } => array( arena, elements .iter() .map(|element| self.constant_expression(arena, context, element)), ),
Constant::List { elements, tail, .. } => { if tail.is_none() && elements.is_empty() { return self.empty_list(); }
self.tracker.list_used = true; let list = match tail { // There's no tail in the list, we join all the elements and // call it a day. None => list( arena, elements .iter() .map(|element| self.constant_expression(arena, context, element)), ),
Some(tail) => match tail.list_elements() { // There's a tail in the list whose elements are all // known at compile time. In this case we replace the // tail with those elements and create a single flat // list. Some(tail_elements) => list( arena, elements .iter() .chain(tail_elements) .map(|element| self.constant_expression(arena, context, element)), ), // There's a tail in the list but we can't really tell // what its elements are at compile time. This means we // have to prepend to this list. None => { self.tracker.prepend_used = true; let tail = self.constant_expression(arena, context, tail); prepend( arena, elements.iter().map(|element| { self.constant_expression(arena, context, element) }), tail, ) } }, }; match context { Context::Constant => docvec![arena, PURE_JAVASCRIPT_COMMENT_DOCUMENT, list], Context::Guard => list, } }
Constant::Record { type_, name, .. } if type_.is_bool() && name == "True" => { TRUE_LOWERCASE_DOCUMENT } Constant::Record { type_, name, .. } if type_.is_bool() && name == "False" => { FALSE_LOWERCASE_DOCUMENT } Constant::Record { type_, .. } if type_.is_nil() => UNDEFINED_DOCUMENT,
Constant::Record { arguments, module, name, type_, .. } => { let tag = expression .constant_record_tag() .expect("record without inferred constructor made it to code generation");
if module.is_none() && type_.is_result() { if tag == "Ok" { self.tracker.ok_used = true; } else { self.tracker.error_used = true; } }
// If there's no arguments, then this is either a constructor // which takes arguments being referenced rather than called, // or a variant with no fields at all. if arguments.is_none() { // If the constructor is not a function that takes arguments, // it effectively has zero arity. let arity = type_.fn_arity().unwrap_or(0) as u16; return self.record_constructor( arena, type_.clone(), module.as_ref().map(|(name, _)| name.as_str()), &tag, name, arity, ); }
// Otherwise we're always constructing a record! Record updates // are fully expanded during type checking, so we just need to // handle the arguments here. let field_values = arguments .iter() .flatten() .map(|argument| self.constant_expression(arena, context, &argument.value)) .collect_vec();
let constructor = construct_record( arena, module.as_ref().map(|(module, _)| module.as_str()), name, field_values, ); match context { Context::Constant => { docvec![arena, PURE_JAVASCRIPT_COMMENT_DOCUMENT, constructor] } Context::Guard => constructor, } } Constant::BitArray { segments, .. } => { let bit_array = self.constant_bit_array(arena, segments, context); match context { Context::Constant => { docvec![arena, PURE_JAVASCRIPT_COMMENT_DOCUMENT, bit_array] } Context::Guard => bit_array, } }
Constant::Var { name, module, .. } => { match (module, context) { (None, Context::Guard) => self.local_var(name).to_doc(arena), (None, Context::Constant) => maybe_escape_identifier(name).to_doc(arena), (Some((module, _)), _) => { // JS keywords can be accessed here, but we must escape anyway // as we escape when exporting such names in the first place, // and the imported name has to match the exported name. docvec![ arena, DOLLAR_DOCUMENT, module, DOT_DOCUMENT, maybe_escape_identifier(name) ] } } }
Constant::StringConcatenation { left, right, .. } => { let left = self.constant_expression(arena, context, left); let right = self.constant_expression(arena, context, right); docvec![arena, left, SPACE_PLUS_SPACE_DOCUMENT, right] }
Constant::RecordUpdate { .. } => { panic!("record updates should not reach code generation") } Constant::Todo { .. } => { panic!("todo constants should not reach code generation") } Constant::Invalid { .. } => { panic!("invalid constants should not reach code generation") } } }
fn constant_bit_array( &mut self, arena: &'doc DocumentArena<'a, 'doc>, segments: &'a [TypedConstantBitArraySegment], context: Context, ) -> Document<'a, 'doc> { self.tracker.bit_array_literal_used = true; let segments_array = array( arena, segments.iter().map(|segment| { let value = match context { Context::Constant => self.constant_expression(arena, context, &segment.value), Context::Guard => self.guard_constant_expression(arena, &segment.value), };
let details = self.constant_bit_array_segment_details(arena, segment, context);
match details.type_ { BitArraySegmentType::BitArray => { if segment.size().is_some() { self.tracker.bit_array_slice_used = true; docvec![ arena, BIT_ARRAY_SLICE_OPEN_PAREN_DOCUMENT, value, COMMA_ZERO_COMMA_SPACE, details.size, CLOSE_PAREN_DOCUMENT ] } else { value } } BitArraySegmentType::Int => { match (details.size_value, segment.value.as_ref()) { (Some(size_value), Constant::Int { int_value, .. }) if size_value <= SAFE_INT_SEGMENT_MAX_SIZE.into() && (&size_value % BigInt::from(8) == BigInt::ZERO) => { let bytes = bit_array_segment_int_value_to_bytes( int_value.clone(), size_value, segment.endianness(), );
u8_slice(arena, &bytes) }
(Some(size_value), _) if size_value == 8.into() => value,
(Some(size_value), _) if size_value <= 0.into() => EMPTY_DOCUMENT,
_ => { self.tracker.sized_integer_segment_used = true; let size = details.size; let is_big = bool(segment.endianness().is_big()); docvec![ arena, SIZED_INT_OPEN_PAREN_DOCUMENT, value, COMMA_SPACE_DOCUMENT, size, COMMA_SPACE_DOCUMENT, is_big, CLOSE_PAREN_DOCUMENT ] } } } BitArraySegmentType::Float => { self.tracker.float_bit_array_segment_used = true; let size = details.size; let is_big = bool(details.endianness.is_big()); docvec![ arena, SIZED_FLOAT_OPEN_PAREN_DOCUMENT, value, COMMA_SPACE_DOCUMENT, size, COMMA_SPACE_DOCUMENT, is_big, CLOSE_PAREN_DOCUMENT ] } BitArraySegmentType::String(StringEncoding::Utf8) => { self.tracker.string_bit_array_segment_used = true; docvec![ arena, STRING_BITS_OPEN_PAREN_DOCUMENT, value, CLOSE_PAREN_DOCUMENT ] } BitArraySegmentType::String(StringEncoding::Utf16) => { self.tracker.string_utf16_bit_array_segment_used = true; let is_big = bool(details.endianness.is_big()); docvec![ arena, STRING_TO_UTF16_OPEN_PAREN_DOCUMENT, value, COMMA_SPACE_DOCUMENT, is_big, CLOSE_PAREN_DOCUMENT ] } BitArraySegmentType::String(StringEncoding::Utf32) => { self.tracker.string_utf32_bit_array_segment_used = true; let is_big = bool(details.endianness.is_big()); docvec![ arena, STRING_TO_UTF32_OPEN_PAREN_DOCUMENT, value, COMMA_SPACE_DOCUMENT, is_big, CLOSE_PAREN_DOCUMENT ] } BitArraySegmentType::UtfCodepoint(StringEncoding::Utf8) => { self.tracker.codepoint_bit_array_segment_used = true; docvec![ arena, CODEPOINT_BITS_OPEN_PAREN_DOCUMENT, value, CLOSE_PAREN_DOCUMENT ] } BitArraySegmentType::UtfCodepoint(StringEncoding::Utf16) => { self.tracker.codepoint_utf16_bit_array_segment_used = true; let is_big = bool(details.endianness.is_big()); docvec![ arena, CODEPOINT_TO_UTF16_OPEN_PAREN_DOCUMENT, value, COMMA_SPACE_DOCUMENT, is_big, CLOSE_PAREN_DOCUMENT ] } BitArraySegmentType::UtfCodepoint(StringEncoding::Utf32) => { self.tracker.codepoint_utf32_bit_array_segment_used = true; let is_big = bool(details.endianness.is_big()); docvec![ arena, CODEPOINT_TO_UTF32_OPEN_PAREN_DOCUMENT, value, COMMA_SPACE_DOCUMENT, is_big, CLOSE_PAREN_DOCUMENT ] } } }), );
docvec![ arena, TO_BIT_ARRAY_OPEN_PAREN_DOCUMENT, segments_array, CLOSE_PAREN_DOCUMENT ] }
fn constant_bit_array_segment_details( &mut self, arena: &'doc DocumentArena<'a, 'doc>, segment: &'a TypedConstantBitArraySegment, context: Context, ) -> BitArraySegmentDetails<'a, 'doc> { let size = segment.size(); let unit = segment.unit(); let (size_value, size) = match size { Some(Constant::Int { int_value, .. }) => { let size_value = int_value * unit; let size = eco_format!("{size_value}").to_doc(arena); (Some(size_value), size) }
Some(size) => { let mut size = match context { Context::Constant => self.constant_expression(arena, context, size), Context::Guard => self.guard_constant_expression(arena, size), }; if unit != 1 { size = size.group(arena).append( arena, SPACE_TIMES_SPACE_DOCUMENT.append(arena, unit.to_doc(arena)), ); }
(None, size) }
None => { let size_value: usize = if segment.type_.is_int() { 8 } else { 64 }; (Some(BigInt::from(size_value)), size_value.to_doc(arena)) } };
let type_ = BitArraySegmentType::from_segment(segment);
BitArraySegmentDetails { type_, size, size_value, endianness: segment.endianness(), } }
pub(crate) fn guard( &mut self, arena: &'doc DocumentArena<'a, 'doc>, guard: &'a TypedClauseGuard, ) -> Document<'a, 'doc> { match guard { ClauseGuard::Invalid { .. } => unreachable!("invalid guard made it to code generation"),
ClauseGuard::Block { value, .. } => { self.guard(arena, value) .surround(arena, OPEN_PAREN_DOCUMENT, CLOSE_PAREN_DOCUMENT) }
ClauseGuard::BinaryOperator { left, right, operator, .. } => { let operator = match operator { BinOp::Eq if is_js_scalar(left.type_()) => TRIPLE_EQUAL_DOCUMENT, BinOp::NotEq if is_js_scalar(left.type_()) => { EXCLAMATION_MARK_DOUBLE_EQUAL_DOCUMENT } BinOp::Eq | BinOp::NotEq => { let should_be_equal = *operator == BinOp::Eq;
// Handle singleton equality optimization for guards if let Some(doc) = self.singleton_variant_guard_equality( arena, left, right, should_be_equal, ) { return doc; }
if let Some(doc) = self.singleton_variant_guard_equality( arena, right, left, should_be_equal, ) { return doc; }
let left_doc = self.guard(arena, left); let right_doc = self.guard(arena, right); return self.prelude_equal_call( arena, should_be_equal, left_doc, right_doc, ); }
BinOp::GtFloat | BinOp::GtInt => GT_INT_DOCUMENT, BinOp::GtEqFloat | BinOp::GtEqInt => GT_EQ_INT_DOCUMENT, BinOp::LtFloat | BinOp::LtInt => LT_INT_DOCUMENT, BinOp::LtEqFloat | BinOp::LtEqInt => LT_EQ_INT_DOCUMENT,
BinOp::AddFloat | BinOp::AddInt | BinOp::Concatenate => ADD_INT_DOCUMENT, BinOp::SubFloat | BinOp::SubInt => SUB_INT_DOCUMENT, BinOp::MultFloat | BinOp::MultInt => MULT_INT_DOCUMENT,
BinOp::DivFloat => { self.tracker.float_division_used = true;
return docvec![ arena, DIVIDE_FLOAT_DOCUMENT, wrap_arguments( arena, [self.guard(arena, left), self.guard(arena, right)] ) ]; }
BinOp::DivInt => { self.tracker.int_division_used = true; return docvec![ arena, DIVIDE_INT_DOCUMENT, wrap_arguments( arena, [self.guard(arena, left), self.guard(arena, right)] ) ]; }
BinOp::RemainderInt => { self.tracker.int_remainder_used = true; return docvec![ arena, CAMEL_CASE_REMAINDER_INT_DOCUMENT, wrap_arguments( arena, [self.guard(arena, left), self.guard(arena, right)] ) ]; }
BinOp::And => AND_DOCUMENT, BinOp::Or => OR_DOCUMENT, };
let left_document = self.wrapped_guard(arena, left); let right_document = self.wrapped_guard(arena, right);
docvec![ arena, left_document, SPACE_DOCUMENT, operator, SPACE_DOCUMENT, right_document ] }
ClauseGuard::Var { name, .. } => self.local_var(name).to_doc(arena),
ClauseGuard::TupleIndex { tuple, index, .. } => { docvec![ arena, self.guard(arena, tuple,), OPEN_SQUARE_DOCUMENT, *index, CLOSE_SQUARE_DOCUMENT ] }
ClauseGuard::FieldAccess { label, container, .. } => docvec![ arena, self.guard(arena, container), DOT_DOCUMENT, maybe_escape_property(label) ],
ClauseGuard::ModuleSelect { module_alias, label, .. } => docvec![arena, DOLLAR_DOCUMENT, module_alias, DOT_DOCUMENT, label],
ClauseGuard::Not { expression, .. } => { docvec![ arena, EXCLAMATION_MARK_DOCUMENT, self.guard(arena, expression,) ] }
ClauseGuard::Constant(constant) => self.guard_constant_expression(arena, constant), } }
fn singleton_variant_guard_equality( &mut self, arena: &'doc DocumentArena<'a, 'doc>, left: &'a TypedClauseGuard, right: &'a TypedClauseGuard, should_be_equal: bool, ) -> Option<Document<'a, 'doc>> { match right { ClauseGuard::Constant(Constant::Record { record_constructor: Some(constructor), module, name, .. }) if let ValueConstructorVariant::Record { arity: 0, name: variant_name, .. } = &constructor.variant => { let left_doc = self.guard(arena, left); Some(self.singleton_equal( arena, left_doc, module.as_ref().map(|(module, _)| module.as_str()), name.clone(), should_be_equal, variant_name.clone(), right.type_(), )) } ClauseGuard::Constant(Constant::List { elements, tail: None, .. }) if elements.is_empty() => { let left_doc = self.guard(arena, left); self.tracker.list_empty_class_used = true; Some(self.singleton_equal( arena, left_doc, None, "$Empty".into(), should_be_equal, "Empty".into(), right.type_(), )) } ClauseGuard::Block { .. } | ClauseGuard::BinaryOperator { .. } | ClauseGuard::Not { .. } | ClauseGuard::Var { .. } | ClauseGuard::TupleIndex { .. } | ClauseGuard::FieldAccess { .. } | ClauseGuard::ModuleSelect { .. } | ClauseGuard::Constant(_) | ClauseGuard::Invalid { .. } => None, } }
fn wrapped_guard( &mut self, arena: &'doc DocumentArena<'a, 'doc>, guard: &'a TypedClauseGuard, ) -> Document<'a, 'doc> { match guard { ClauseGuard::Invalid { .. } => unreachable!("invalid guard made it to code generation"), ClauseGuard::Var { .. } | ClauseGuard::TupleIndex { .. } | ClauseGuard::Constant(_) | ClauseGuard::Not { .. } | ClauseGuard::FieldAccess { .. } | ClauseGuard::Block { .. } => self.guard(arena, guard),
ClauseGuard::BinaryOperator { .. } | ClauseGuard::ModuleSelect { .. } => { docvec![ arena, OPEN_PAREN_DOCUMENT, self.guard(arena, guard), CLOSE_PAREN_DOCUMENT ] } } }
fn guard_constant_expression( &mut self, arena: &'doc DocumentArena<'a, 'doc>, expression: &'a TypedConstant, ) -> Document<'a, 'doc> { match expression { Constant::Tuple { elements, .. } => array( arena, elements .iter() .map(|element| self.guard_constant_expression(arena, element)), ),
Constant::Record { type_, name, .. } if type_.is_bool() && name == "True" => { TRUE_LOWERCASE_DOCUMENT } Constant::Record { type_, name, .. } if type_.is_bool() && name == "False" => { FALSE_LOWERCASE_DOCUMENT } Constant::Record { type_, .. } if type_.is_nil() => UNDEFINED_DOCUMENT,
Constant::BitArray { segments, .. } => { self.constant_bit_array(arena, segments, Context::Guard) }
Constant::Var { name, .. } => self.local_var(name).to_doc(arena),
Constant::Record { .. } | Constant::Int { .. } | Constant::Float { .. } | Constant::String { .. } | Constant::List { .. } | Constant::RecordUpdate { .. } | Constant::StringConcatenation { .. } | Constant::Todo { .. } | Constant::Invalid { .. } => { self.constant_expression(arena, Context::Guard, expression) } } }
pub fn source_map_tracker( &mut self, arena: &'doc DocumentArena<'a, 'doc>, start_index: u32, ) -> Document<'a, 'doc> { create_cursor_position_observer( arena, &self.source_map_builder, self.line_numbers, start_index, ) }
pub(crate) fn record_constructor( &mut self, arena: &'doc DocumentArena<'a, 'doc>, type_: Arc<Type>, qualifier: Option<&'a str>, variant_name: &EcoString, name: &'a EcoString, arity: u16, ) -> Document<'a, 'doc> { if qualifier.is_none() && type_.is_result_constructor() { if name == "Ok" { self.tracker.ok_used = true; } else if name == "Error" { self.tracker.error_used = true; } } if type_.is_bool() && name == "True" { TRUE_LOWERCASE_DOCUMENT } else if type_.is_bool() { FALSE_LOWERCASE_DOCUMENT } else if type_.is_nil() { UNDEFINED_DOCUMENT } else if arity == 0 && let Some((package, module, type_name)) = type_.named_type_name_and_package() { // If the variant has no fields, return the singleton constant so // that all values of the variant are the same underlying reference, // and are faster to compare. match qualifier { Some(module) => docvec![ arena, DOLLAR_DOCUMENT, module, DOT_DOCUMENT, type_name, DOLLAR_DOCUMENT, name, DOLLAR_CONST_DOCUMENT ], None => { if module != self.module_name { let alias = if name == variant_name { None } else { Some(eco_format!("{type_name}${name}$const")) }; // Since this constant is an implementation detail and not // present in Gleam code, we need to track it so that we // import it, as it doesn't appear directly in the `import`s // in the source code. _ = self.tracker.variant_constants_used.insert( TypeVariant { package, module, type_name: type_name.clone(), name: variant_name.clone(), }, alias, ); } docvec![ arena, type_name, DOLLAR_DOCUMENT, name, DOLLAR_CONST_DOCUMENT ] } } } else { let vars = (0..arity).map(|i| eco_format!("var{i}").to_doc(arena)); let body = docvec![ arena, RETURN_SPACE_DOCUMENT, construct_record(arena, qualifier, name, vars.clone()), SEMICOLON_DOCUMENT ]; docvec![ arena, docvec![ arena, wrap_arguments(arena, vars), SPACE_EQUAL_ARROW_SPACE_OPEN_CURLY_DOCUMENT, BREAKABLE_SPACE_DOCUMENT, body ] .nest(arena, INDENT) .append(arena, BREAKABLE_SPACE_DOCUMENT) .group(arena), CLOSE_CURLY_DOCUMENT, ] } }}
#[derive(Clone, Copy)]enum AssertExpression { Literal, Expression, Unevaluated,}
impl AssertExpression { fn from_expression(expression: &TypedExpr) -> Self { if expression.is_literal() { Self::Literal } else { Self::Expression } }}
pub fn int<'a, 'doc>(arena: &'doc DocumentArena<'a, 'doc>, value: &'a str) -> Document<'a, 'doc> { eco_string_int(arena, value.into())}
pub fn eco_string_int<'a, 'doc>( arena: &'doc DocumentArena<'a, 'doc>, value: EcoString,) -> Document<'a, 'doc> { let mut out = EcoString::with_capacity(value.len());
if value.starts_with('-') { out.push('-'); } else if value.starts_with('+') { out.push('+'); } let value = value.trim_start_matches(['+', '-'].as_ref());
let value = if value.starts_with("0x") { out.push_str("0x"); value.trim_start_matches("0x") } else if value.starts_with("0o") { out.push_str("0o"); value.trim_start_matches("0o") } else if value.starts_with("0b") { out.push_str("0b"); value.trim_start_matches("0b") } else { value };
let value = value.trim_start_matches(['0', '_']); if value.is_empty() { out.push('0'); }
out.push_str(value);
out.to_doc(arena)}
pub fn float<'a, 'doc>(arena: &'doc DocumentArena<'a, 'doc>, value: &'a str) -> Document<'a, 'doc> { let mut out = EcoString::with_capacity(value.len());
if value.starts_with('-') { out.push('-'); } else if value.starts_with('+') { out.push('+'); } let value = value.trim_start_matches(['+', '-'].as_ref());
let value = value.trim_start_matches(['0', '_']); if value.starts_with(['.', 'e', 'E']) { out.push('0'); } out.push_str(value);
out.to_doc(arena)}
pub fn float_from_value<'a, 'doc>( arena: &'doc DocumentArena<'a, 'doc>, value: f64,) -> Document<'a, 'doc> { if value.is_infinite() { if value.is_sign_positive() { INFINITY_DOCUMENT } else { MINUS_INFINITY_DOCUMENT } } else if value.is_nan() { // NOTE: this case is probably unnecessary, as this function is only // invoked with `LiteralFloatValue` values, which cannot be nan. NAN_DOCUMENT } else { value.to_doc(arena) }}
/// The context where the constant expression is used, it might be inside a/// function call, or in the definition of another constant.////// Based on the context we might want to annotate pure function calls as/// "@__PURE__".///#[derive(Debug, Clone, Copy)]pub enum Context { Constant, Guard,}
#[derive(Debug)]struct BitArraySegmentDetails<'a, 'doc> { type_: BitArraySegmentType, size: Document<'a, 'doc>, /// The size of the bit array segment stored as a BigInt. /// This has a value when the segment's size is known at compile time. size_value: Option<BigInt>, endianness: Endianness,}
#[derive(Debug, Clone, Copy)]enum BitArraySegmentType { BitArray, Int, Float, String(StringEncoding), UtfCodepoint(StringEncoding),}
impl BitArraySegmentType { fn from_segment<Value>(segment: &BitArraySegment<Value, Arc<Type>>) -> Self { if segment.type_.is_int() { BitArraySegmentType::Int } else if segment.type_.is_float() { BitArraySegmentType::Float } else if segment.type_.is_bit_array() { BitArraySegmentType::BitArray } else if segment.type_.is_string() { let encoding = if segment.has_utf16_option() { StringEncoding::Utf16 } else if segment.has_utf32_option() { StringEncoding::Utf32 } else { StringEncoding::Utf8 }; BitArraySegmentType::String(encoding) } else if segment.type_.is_utf_codepoint() { let encoding = if segment.has_utf16_codepoint_option() { StringEncoding::Utf16 } else if segment.has_utf32_codepoint_option() { StringEncoding::Utf32 } else { StringEncoding::Utf8 }; BitArraySegmentType::UtfCodepoint(encoding) } else { panic!( "Invalid bit array segment type reached code generation: {:?}", segment.type_ ); } }}
pub fn string<'a, 'doc>( arena: &'doc DocumentArena<'a, 'doc>, value: &'a str,) -> Document<'a, 'doc> { if value.contains('\n') { EcoString::from(value.replace('\n', r"\n")) .to_doc(arena) .surround(arena, DOUBLE_QUOTE_DOCUMENT, DOUBLE_QUOTE_DOCUMENT) } else { value .to_doc(arena) .surround(arena, DOUBLE_QUOTE_DOCUMENT, DOUBLE_QUOTE_DOCUMENT) }}
pub(crate) fn array<'a, 'doc, Elements: IntoIterator<Item = Document<'a, 'doc>>>( arena: &'doc DocumentArena<'a, 'doc>, elements: Elements,) -> Document<'a, 'doc> { let elements = arena.join(elements, COMMA_BREAK_DOCUMENT); if elements.is_empty() { // Do not add a trailing comma since that adds an 'undefined' element OPEN_CLOSE_SQUARE_DOCUMENT } else { docvec![ arena, OPEN_SQUARE_DOCUMENT, docvec![arena, EMPTY_BREAK_DOCUMENT, elements].nest(arena, INDENT), TRAILING_COMMA_BREAK_DOCUMENT, CLOSE_SQUARE_DOCUMENT ] .group(arena) }}
pub(crate) fn list<'a, 'doc, I: IntoIterator<Item = Document<'a, 'doc>>>( arena: &'doc DocumentArena<'a, 'doc>, elements: I,) -> Document<'a, 'doc>where I::IntoIter: DoubleEndedIterator,{ let array = array(arena, elements); docvec![ arena, TO_LIST_OPEN_PAREN_DOCUMENT, array, CLOSE_PAREN_DOCUMENT ]}
fn prepend<'a, 'doc, I: IntoIterator<Item = Document<'a, 'doc>>>( arena: &'doc DocumentArena<'a, 'doc>, elements: I, tail: Document<'a, 'doc>,) -> Document<'a, 'doc>where I::IntoIter: DoubleEndedIterator + ExactSizeIterator,{ elements.into_iter().rev().fold(tail, |tail, element| { let arguments = call_arguments(arena, [element, tail]); docvec![arena, LIST_PREPEND_DOCUMENT, arguments] })}
fn call_arguments<'a, 'doc, Elements: IntoIterator<Item = Document<'a, 'doc>>>( arena: &'doc DocumentArena<'a, 'doc>, elements: Elements,) -> Document<'a, 'doc> { let elements = arena.join(elements, COMMA_BREAK_DOCUMENT); if elements.is_empty() { return OPEN_CLOSE_PAREN_DOCUMENT; } docvec![ arena, OPEN_PAREN_DOCUMENT, docvec![arena, EMPTY_BREAK_DOCUMENT, elements].nest(arena, INDENT), TRAILING_COMMA_BREAK_DOCUMENT, CLOSE_PAREN_DOCUMENT ] .group(arena)}
pub(crate) fn construct_record<'a, 'doc>( arena: &'doc DocumentArena<'a, 'doc>, module: Option<&'a str>, name: &'a str, arguments: impl IntoIterator<Item = Document<'a, 'doc>>,) -> Document<'a, 'doc> { let mut any_arguments = false; let arguments = arena.join( arguments.into_iter().inspect(|_| { any_arguments = true; }), COMMA_BREAK_DOCUMENT, ); let arguments = docvec![arena, EMPTY_BREAK_DOCUMENT, arguments].nest(arena, INDENT); let name = if let Some(module) = module { docvec![arena, DOLLAR_DOCUMENT, module, DOT_DOCUMENT, name] } else { name.to_doc(arena) }; if any_arguments { docvec![ arena, NEW_SPACE_DOCUMENT, name, OPEN_PAREN_DOCUMENT, arguments, TRAILING_COMMA_BREAK_DOCUMENT, CLOSE_PAREN_DOCUMENT ] .group(arena) } else { docvec![arena, NEW_SPACE_DOCUMENT, name, OPEN_CLOSE_PAREN_DOCUMENT] }}
impl TypedExpr { fn handles_own_return(&self) -> bool { match self { TypedExpr::Todo { .. } | TypedExpr::Call { .. } | TypedExpr::Case { .. } | TypedExpr::Panic { .. } | TypedExpr::Block { .. } | TypedExpr::Echo { .. } | TypedExpr::Pipeline { .. } | TypedExpr::RecordUpdate { .. } => true,
TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::Var { .. } | TypedExpr::Fn { .. } | TypedExpr::List { .. } | TypedExpr::BinOp { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::ModuleSelect { .. } | TypedExpr::Tuple { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::BitArray { .. } | TypedExpr::NegateBool { .. } | TypedExpr::NegateInt { .. } | TypedExpr::Invalid { .. } => false, } }}
impl BinOp { fn is_operator_to_wrap(&self) -> bool { match self { BinOp::And | BinOp::Or | BinOp::Eq | BinOp::NotEq | BinOp::LtInt | BinOp::LtEqInt | BinOp::LtFloat | BinOp::LtEqFloat | BinOp::GtEqInt | BinOp::GtInt | BinOp::GtEqFloat | BinOp::GtFloat | BinOp::AddInt | BinOp::AddFloat | BinOp::SubInt | BinOp::SubFloat | BinOp::MultFloat | BinOp::DivInt | BinOp::DivFloat | BinOp::RemainderInt | BinOp::Concatenate => true, BinOp::MultInt => false, } }}
pub fn is_js_scalar(t: Arc<Type>) -> bool { t.is_int() || t.is_float() || t.is_bool() || t.is_nil() || t.is_string()}
fn expression_requires_semicolon(expression: &TypedExpr) -> bool { match expression { TypedExpr::Int { .. } | TypedExpr::Fn { .. } | TypedExpr::Var { .. } | TypedExpr::List { .. } | TypedExpr::Call { .. } | TypedExpr::Echo { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::BinOp { .. } | TypedExpr::Tuple { .. } | TypedExpr::NegateInt { .. } | TypedExpr::BitArray { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::NegateBool { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::ModuleSelect { .. } => true,
TypedExpr::Todo { .. } | TypedExpr::Case { .. } | TypedExpr::Panic { .. } | TypedExpr::Pipeline { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::Invalid { .. } | TypedExpr::Block { .. } => false, }}
/// Wrap a document in an immediately invoked function expressionfn immediately_invoked_function_expression_document<'a, 'doc>( arena: &'doc DocumentArena<'a, 'doc>, document: Document<'a, 'doc>,) -> Document<'a, 'doc> { docvec![ arena, docvec![ arena, OPEN_PAREN_OPEN_CLOSE_PAREN_EQUAL_ARROW_OPEN_CURLY_DOCUMENT, BREAKABLE_SPACE_DOCUMENT, document ] .nest(arena, INDENT), BREAKABLE_SPACE_DOCUMENT, CLOSE_CURLY_CLOSE_PAREN_OPEN_CLOSE_PAREN_DOCUMENT, ] .group(arena)}
fn u8_slice<'a, 'doc>(arena: &'doc DocumentArena<'a, 'doc>, bytes: &[u8]) -> Document<'a, 'doc> { arena.join( bytes.iter().map(|byte| byte.to_doc(arena)), COMMA_SPACE_DOCUMENT, )}