summary refs log tree commit diff stats
path: root/lib/pure/parseutils.nim
Commit message (Expand)AuthorAgeFilesLines
* added strscans stdlib moduleAraq2016-05-101-0/+16
* split too long linesAraq2016-05-101-5/+9
* Remove dead codeAnatoly Galiulin2016-03-311-11/+1
* Add parseUInt and parseBiggestUInt functions to stdlib (parseutils, strutils)Anatoly Galiulin2016-03-301-0/+51
* fixes strutils.unescape; refs #3634Andreas Rumpf2016-01-171-2/+5
* fixes #2909Araq2015-06-151-16/+16
* Turn some test outputs into actual testsOleh Prypin2015-04-211-3/+5
* Don't run non-test code when defined(testing)Oleh Prypin2015-04-211-1/+3
* Fix typosFederico Ceratto2015-02-151-1/+1
* Fix typosFederico Ceratto2015-02-151-1/+1
* fixes #2041Araq2015-01-311-2/+2
* bugfix: don't overwrite the number value in case of a parsing errorAraq2015-01-271-2/+3
* got rid of old code that used to be required for bootstrappingAraq2014-12-081-90/+5
* Nimrod renamed to NimAraq2014-08-281-2/+2
* big renameAraq2014-08-271-5/+7
* renamefestAraq2014-08-231-3/+3
* parseBiggestFloat is now builtinAraq2014-07-161-82/+84
* Fixed parsing of float literals.Reimer Behrends2014-05-231-44/+50
* 'nil' as a statement is deprecated, use an empty 'discard' insteadAraq2014-01-191-1/+1
* case consistency part 4Araq2013-12-271-7/+7
* case consistency part 1Araq2013-12-271-2/+2
* Explains parseHex initialization quirk.Grzegorz Adam Hankiewicz2013-07-161-2/+26
* made parseBiggestFloat faster for large exponentsAraq2013-07-011-3/+12
* Removes executable bit for text files.Grzegorz Adam Hankiewicz2013-03-161-0/+0
* The httpserver module now supports POST requests. Fix for captureBetween in t...dom962012-05-061-2/+2
* added system.setControlCHook, system.writeStackTraceAraq2012-02-191-1/+2
* bugfix: semfold supports merging of '&'Araq2012-02-191-1/+5
* year 2012 for most copyright headersAraq2012-01-021-1/+1
* Added ftpclient module. Fixed docs in sockets module. Added dll tests to test...dom962011-11-041-0/+7
* bugfix: new GCC version requires -ldl to come after object filesAraq2011-11-031-2/+22
* compilation cache: mostly working; generics not yetAraq2011-10-251-28/+28
* bugfix: $ escaping in interpolatedFragmentsAraq2011-09-261-6/+12
* bugfix: internal error in evalFieldAccess; parseutils.interpolatedFragments o...Araq2011-09-261-86/+68
* using statement (ala C#) implemented as macro (added as test).Zahary Karadjov2011-09-201-29/+40
* Moved the parseAST magics to evals.nimZahary Karadjov2011-09-201-0/+91
* bugfix: proper cache for generic instantiationsAraq2011-07-211-1/+1
* deprecated system.copy: use system.substr insteadAraq2011-05-141-4/+4
* hashtables: 1st version; parseutils additionsAraq2011-04-181-1/+23
* $ for strtabs; skipUntil, skipWhile for parseutilsAraq2011-04-051-3/+13
* inlining of the write barrier for dllsAndreas Rumpf2010-08-081-6/+14
* fixed pango/pangoutils new wrappersAndreas Rumpf2010-02-261-0/+0
* continued work on html/xmlparserrumpf_a@web.de2010-02-141-9/+0
* cleanup of library docsAndreas Rumpf2010-02-041-3/+4
* added system.del; delete; insertAndreas Rumpf2010-02-021-0/+0
* parseutils addedrumpf_a@web.de2010-01-311-0/+225
515'>515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532
 Agaram <vc@akkartik.com>  2016-04-10 20:47:44 -0700
committer  Kartik K. Agaram <vc@akkartik.com>  2016-04-10 20:55:10 -0700

2829 - issues while switching to 'put'' href='/akkartik/mu/commit/072channel.mu?h=main&id=5f141f6a461bfaf23cb75603b18c09d65a8d79af'>5f141f6a ^
3ac52339 ^
5f141f6a ^
4038d416 ^

760f683f ^
77d5b5d6 ^
104854ca ^
ebea4c3f ^
760f683f ^
5b1219ca ^
f8e6e864 ^


4038d416 ^
f8e6e864 ^


17622b5a ^
f8e6e864 ^




05fe4be5 ^
f8e6e864 ^

4038d416 ^
05fe4be5 ^
f8e6e864 ^
3ef2f7df ^
a0331a9b ^
7a84094a ^
3ef2f7df ^

502d2ea5 ^
d31d70b6 ^
4038d416 ^

7a84094a ^
17622b5a ^
502d2ea5 ^
d31d70b6 ^
4038d416 ^
d31d70b6 ^
fd484d9c ^
f8e6e864 ^

4038d416 ^

760f683f ^
77d5b5d6 ^
104854ca ^
ebea4c3f ^
3d748ce1 ^
760f683f ^
f8e6e864 ^


4038d416 ^
f8e6e864 ^


17622b5a ^
502d2ea5 ^
f8e6e864 ^



05fe4be5 ^
5b1219ca ^
f8e6e864 ^

4038d416 ^
05fe4be5 ^
d31d70b6 ^
7a84094a ^
a0331a9b ^
d31d70b6 ^
5b22547b ^
760f683f ^
5b22547b ^
5d293761 ^
d31d70b6 ^
4038d416 ^

7a84094a ^
17622b5a ^
502d2ea5 ^
d31d70b6 ^
4038d416 ^
d31d70b6 ^
fd484d9c ^
f8e6e864 ^

4038d416 ^

760f683f ^
77d5b5d6 ^
104854ca ^
760f683f ^
a66ad533 ^
17622b5a ^
502d2ea5 ^
5b22547b ^
a66ad533 ^
a66ad533 ^

4038d416 ^

ad8161f3 ^
760f683f ^

7a84094a ^

4038d416 ^
22b30692 ^
ad8161f3 ^

4038d416 ^




ad8161f3 ^
760f683f ^
ad8161f3 ^
760f683f ^
7a84094a ^

4038d416 ^
22b30692 ^
ad8161f3 ^

4038d416 ^




ad8161f3 ^
760f683f ^
ad8161f3 ^

760f683f ^
7a84094a ^

4038d416 ^
22b30692 ^
ad8161f3 ^

4038d416 ^




ad8161f3 ^
4038d416 ^
760f683f ^

4038d416 ^
ad8161f3 ^

4038d416 ^
7a84094a ^

4038d416 ^
ad8161f3 ^
7a84094a ^
4038d416 ^
ad8161f3 ^
7a84094a ^
4038d416 ^
22b30692 ^
ad8161f3 ^



4038d416 ^


4038d416 ^

ad8161f3 ^
760f683f = contains(gGlobalOptions, optGenScript)
  of "threads": result = contains(gGlobalOptions, optThreads)
  of "taintmode": result = contains(gGlobalOptions, optTaintMode)
  of "tlsemulation": result = contains(gGlobalOptions, optTlsEmulation)
  of "implicitstatic": result = contains(gOptions, optImplicitStatic)
  of "patterns": result = contains(gOptions, optPatterns)
  else: invalidCmdLineOption(passCmd1, switch, info)
  
proc processPath(path: string, notRelativeToProj = false): string =
  let p = if notRelativeToProj or os.isAbsolute(path) or
              '$' in path or path[0] == '.': 
            path 
          else:
            options.gProjectPath / path
  result = unixToNativePath(p % ["nimrod", getPrefixDir(), "lib", libpath,
    "home", removeTrailingDirSep(os.getHomeDir()),
    "projectname", options.gProjectName,
    "projectpath", options.gProjectPath])

proc trackDirty(arg: string, info: TLineInfo) =
  var a = arg.split(',')
  if a.len != 4: localError(info, errTokenExpected,
                            "DIRTY_BUFFER,ORIGINAL_FILE,LINE,COLUMN")
  var line, column: int
  if parseUtils.parseInt(a[2], line) <= 0:
    localError(info, errInvalidNumber, a[1])
  if parseUtils.parseInt(a[3], column) <= 0:
    localError(info, errInvalidNumber, a[2])
  
  gDirtyBufferIdx = a[0].fileInfoIdx
  gDirtyOriginalIdx = a[1].fileInfoIdx
 
  optTrackPos = newLineInfo(gDirtyBufferIdx, line, column)
  msgs.addCheckpoint(optTrackPos)

proc track(arg: string, info: TLineInfo) = 
  var a = arg.split(',')
  if a.len != 3: localError(info, errTokenExpected, "FILE,LINE,COLUMN")
  var line, column: int
  if parseUtils.parseInt(a[1], line) <= 0:
    localError(info, errInvalidNumber, a[1])
  if parseUtils.parseInt(a[2], column) <= 0:
    localError(info, errInvalidNumber, a[2])
  optTrackPos = newLineInfo(a[0], line, column)
  msgs.addCheckpoint(optTrackPos)

proc dynlibOverride(switch, arg: string, pass: TCmdLinePass, info: TLineInfo) =
  if pass in {passCmd2, passPP}:
    expectArg(switch, arg, pass, info)
    options.inclDynlibOverride(arg)

proc processSwitch(switch, arg: string, pass: TCmdLinePass, info: TLineInfo) = 
  var 
    theOS: TSystemOS
    cpu: TSystemCPU
    key, val: string
  case switch.normalize
  of "path", "p": 
    expectArg(switch, arg, pass, info)
    addPath(processPath(arg), info)
  of "babelpath":
    if pass in {passCmd2, passPP} and not options.gNoBabelPath:
      expectArg(switch, arg, pass, info)
      let path = processPath(arg, notRelativeToProj=true)
      babelpath(path, info)
  of "nobabelpath":
    expectNoArg(switch, arg, pass, info)
    options.gNoBabelPath = true
  of "excludepath":
    expectArg(switch, arg, pass, info)
    let path = processPath(arg)
    lists.excludeStr(options.searchPaths, path)
    lists.excludeStr(options.lazyPaths, path)
  of "nimcache":
    expectArg(switch, arg, pass, info)
    options.nimcacheDir = processPath(arg)
  of "out", "o": 
    expectArg(switch, arg, pass, info)
    options.outFile = arg
  of "mainmodule", "m":
    expectArg(switch, arg, pass, info)
    optMainModule = arg
  of "define", "d": 
    expectArg(switch, arg, pass, info)
    defineSymbol(arg)
  of "undef", "u": 
    expectArg(switch, arg, pass, info)
    undefSymbol(arg)
  of "compile": 
    expectArg(switch, arg, pass, info)
    if pass in {passCmd2, passPP}: processCompile(arg)
  of "link": 
    expectArg(switch, arg, pass, info)
    if pass in {passCmd2, passPP}: addFileToLink(arg)
  of "debuginfo": 
    expectNoArg(switch, arg, pass, info)
    incl(gGlobalOptions, optCDebug)
  of "embedsrc":
    expectNoArg(switch, arg, pass, info)
    incl(gGlobalOptions, optEmbedOrigSrc)
  of "compileonly", "c": 
    expectNoArg(switch, arg, pass, info)
    incl(gGlobalOptions, optCompileOnly)
  of "nolinking": 
    expectNoArg(switch, arg, pass, info)
    incl(gGlobalOptions, optNoLinking)
  of "nomain": 
    expectNoArg(switch, arg, pass, info)
    incl(gGlobalOptions, optNoMain)
  of "forcebuild", "f": 
    expectNoArg(switch, arg, pass, info)
    incl(gGlobalOptions, optForceFullMake)
  of "project":
    expectNoArg(switch, arg, pass, info)
    gWholeProject = true
  of "gc": 
    expectArg(switch, arg, pass, info)
    case arg.normalize
    of "boehm": 
      gSelectedGC = gcBoehm
      defineSymbol("boehmgc")
    of "refc":
      gSelectedGC = gcRefc
    of "v2":
      gSelectedGC = gcV2
    of "markandsweep":
      gSelectedGC = gcMarkAndSweep
      defineSymbol("gcmarkandsweep")
    of "generational":
      gSelectedGC = gcGenerational
      defineSymbol("gcgenerational")
    of "none":
      gSelectedGC = gcNone
      defineSymbol("nogc")
    else: localError(info, errNoneBoehmRefcExpectedButXFound, arg)
  of "warnings", "w": processOnOffSwitch({optWarns}, arg, pass, info)
  of "warning": processSpecificNote(arg, wWarning, pass, info)
  of "hint": processSpecificNote(arg, wHint, pass, info)
  of "hints": processOnOffSwitch({optHints}, arg, pass, info)
  of "threadanalysis": processOnOffSwitchG({optThreadAnalysis}, arg, pass, info)
  of "stacktrace": processOnOffSwitch({optStackTrace}, arg, pass, info)
  of "linetrace": processOnOffSwitch({optLineTrace}, arg, pass, info)
  of "debugger": 
    processOnOffSwitch({optEndb}, arg, pass, info)
    if optEndb in gOptions: defineSymbol("endb")
    else: undefSymbol("endb")
  of "profiler": 
    processOnOffSwitch({optProfiler}, arg, pass, info)
    if optProfiler in gOptions: defineSymbol("profiler")
    else: undefSymbol("profiler")
  of "checks", "x": processOnOffSwitch(ChecksOptions, arg, pass, info)
  of "floatchecks":
    processOnOffSwitch({optNaNCheck, optInfCheck}, arg, pass, info)
  of "infchecks": processOnOffSwitch({optInfCheck}, arg, pass, info)
  of "nanchecks": processOnOffSwitch({optNaNCheck}, arg, pass, info)
  of "objchecks": processOnOffSwitch({optObjCheck}, arg, pass, info)
  of "fieldchecks": processOnOffSwitch({optFieldCheck}, arg, pass, info)
  of "rangechecks": processOnOffSwitch({optRangeCheck}, arg, pass, info)
  of "boundchecks": processOnOffSwitch({optBoundsCheck}, arg, pass, info)
  of "overflowchecks": processOnOffSwitch({optOverflowCheck}, arg, pass, info)
  of "linedir": processOnOffSwitch({optLineDir}, arg, pass, info)
  of "assertions", "a": processOnOffSwitch({optAssert}, arg, pass, info)
  of "deadcodeelim": processOnOffSwitchG({optDeadCodeElim}, arg, pass, info)
  of "threads": processOnOffSwitchG({optThreads}, arg, pass, info)
  of "tlsemulation": processOnOffSwitchG({optTlsEmulation}, arg, pass, info)
  of "taintmode": processOnOffSwitchG({optTaintMode}, arg, pass, info)
  of "implicitstatic":
    processOnOffSwitch({optImplicitStatic}, arg, pass, info)
  of "patterns":
    processOnOffSwitch({optPatterns}, arg, pass, info)
  of "opt":
    expectArg(switch, arg, pass, info)
    case arg.normalize
    of "speed": 
      incl(gOptions, optOptimizeSpeed)
      excl(gOptions, optOptimizeSize)
    of "size": 
      excl(gOptions, optOptimizeSpeed)
      incl(gOptions, optOptimizeSize)
    of "none":
      excl(gOptions, optOptimizeSpeed)
      excl(gOptions, optOptimizeSize)
    else: localError(info, errNoneSpeedOrSizeExpectedButXFound, arg)
  of "app": 
    expectArg(switch, arg, pass, info)
    case arg.normalize
    of "gui":
      incl(gGlobalOptions, optGenGuiApp)
      defineSymbol("executable")
      defineSymbol("guiapp")
    of "console":
      excl(gGlobalOptions, optGenGuiApp)
      defineSymbol("executable")
      defineSymbol("consoleapp")
    of "lib":
      incl(gGlobalOptions, optGenDynLib)
      excl(gGlobalOptions, optGenGuiApp)
      defineSymbol("library")
      defineSymbol("dll")
    of "staticlib":
      incl(gGlobalOptions, optGenStaticLib)
      excl(gGlobalOptions, optGenGuiApp)
      defineSymbol("library")
      defineSymbol("staticlib")
    else: localError(info, errGuiConsoleOrLibExpectedButXFound, arg)
  of "passc", "t": 
    expectArg(switch, arg, pass, info)
    if pass in {passCmd2, passPP}: extccomp.addCompileOption(arg)
  of "passl", "l": 
    expectArg(switch, arg, pass, info)
    if pass in {passCmd2, passPP}: extccomp.addLinkOption(arg)
  of "cincludes":
    expectArg(switch, arg, pass, info)
    if pass in {passCmd2, passPP}: cIncludes.add arg.processPath
  of "clibdir":
    expectArg(switch, arg, pass, info)
    if pass in {passCmd2, passPP}: cLibs.add arg.processPath
  of "clib":
    expectArg(switch, arg, pass, info)
    if pass in {passCmd2, passPP}: cLinkedLibs.add arg.processPath
  of "header":
    headerFile = arg
    incl(gGlobalOptions, optGenIndex)
  of "index":
    processOnOffSwitchG({optGenIndex}, arg, pass, info)
  of "import":
    expectArg(switch, arg, pass, info)
    if pass in {passCmd2, passPP}: implicitImports.add arg
  of "include":
    expectArg(switch, arg, pass, info)
    if pass in {passCmd2, passPP}: implicitIncludes.add arg
  of "listcmd": 
    expectNoArg(switch, arg, pass, info)
    incl(gGlobalOptions, optListCmd)
  of "genmapping": 
    expectNoArg(switch, arg, pass, info)
    incl(gGlobalOptions, optGenMapping)
  of "os": 
    expectArg(switch, arg, pass, info)
    if pass in {passCmd1, passPP}: 
      theOS = platform.nameToOS(arg)
      if theOS == osNone: localError(info, errUnknownOS, arg)
      elif theOS != platform.hostOS: 
        setTarget(theOS, targetCPU)
        condsyms.initDefines()
  of "cpu": 
    expectArg(switch, arg, pass, info)
    if pass in {passCmd1, passPP}: 
      cpu = platform.nameToCPU(arg)
      if cpu == cpuNone: localError(info, errUnknownCPU, arg)
      elif cpu != platform.hostCPU: 
        setTarget(targetOS, cpu)
        condsyms.initDefines()
  of "run", "r": 
    expectNoArg(switch, arg, pass, info)
    incl(gGlobalOptions, optRun)
  of "verbosity": 
    expectArg(switch, arg, pass, info)
    gVerbosity = parseInt(arg)
  of "parallelbuild": 
    expectArg(switch, arg, pass, info)
    gNumberOfProcessors = parseInt(arg)
  of "version", "v": 
    expectNoArg(switch, arg, pass, info)
    writeVersionInfo(pass)
  of "advanced": 
    expectNoArg(switch, arg, pass, info)
    writeAdvancedUsage(pass)
  of "help", "h": 
    expectNoArg(switch, arg, pass, info)
    helpOnError(pass)
  of "symbolfiles": 
    processOnOffSwitchG({optSymbolFiles}, arg, pass, info)
  of "skipcfg": 
    expectNoArg(switch, arg, pass, info)
    incl(gGlobalOptions, optSkipConfigFile)
  of "skipprojcfg": 
    expectNoArg(switch, arg, pass, info)
    incl(gGlobalOptions, optSkipProjConfigFile)
  of "skipusercfg":
    expectNoArg(switch, arg, pass, info)
    incl(gGlobalOptions, optSkipUserConfigFile)
  of "skipparentcfg":
    expectNoArg(switch, arg, pass, info)
    incl(gGlobalOptions, optSkipParentConfigFiles)
  of "genscript": 
    expectNoArg(switch, arg, pass, info)
    incl(gGlobalOptions, optGenScript)
  of "lib":
    expectArg(switch, arg, pass, info)
    libpath = processPath(arg, notRelativeToProj=true)
  of "putenv": 
    expectArg(switch, arg, pass, info)
    splitSwitch(arg, key, val, pass, info)
    os.putEnv(key, val)
  of "cc": 
    expectArg(switch, arg, pass, info)
    setCC(arg)
  of "track":
    expectArg(switch, arg, pass, info)
    track(arg, info)
  of "trackdirty":
    expectArg(switch, arg, pass, info)
    trackDirty(arg, info)
  of "suggest": 
    expectNoArg(switch, arg, pass, info)
    incl(gGlobalOptions, optSuggest)
  of "def":
    expectNoArg(switch, arg, pass, info)
    incl(gGlobalOptions, optDef)
  of "eval":
    expectArg(switch, arg, pass, info)
    gEvalExpr = arg
  of "context":
    expectNoArg(switch, arg, pass, info)
    incl(gGlobalOptions, optContext)
  of "usages":
    expectNoArg(switch, arg, pass, info)
    incl(gGlobalOptions, optUsages)
  of "stdout":
    expectNoArg(switch, arg, pass, info)
    incl(gGlobalOptions, optStdout)
  of "listfullpaths":
    expectNoArg(switch, arg, pass, info)
    gListFullPaths = true
  of "dynliboverride":
    dynlibOverride(switch, arg, pass, info)
  of "cs":
    expectArg(switch, arg, pass, info)
    case arg
    of "partial": idents.firstCharIsCS = true
    of "none": idents.firstCharIsCS = false
    else: localError(info, errGenerated,
      "'partial' or 'none' expected, but found " & arg)
  else:
    if strutils.find(switch, '.') >= 0: options.setConfigVar(switch, arg)
    else: invalidCmdLineOption(pass, switch, info)
  
proc processCommand(switch: string, pass: TCmdLinePass) =
  var cmd, arg: string
  splitSwitch(switch, cmd, arg, pass, gCmdLineInfo)
  processSwitch(cmd, arg, pass, gCmdLineInfo)
ref='#n470'>470
471
472
473
474
475
476

                                                                         
 

                             

                                                                            
 


                                                                              


                  
               
                                                                  
                          
                                                  
   
                         

                                  


   
                         
                                                                            

                             


                                                                              
                

 



                                                                              
                      


                      
                      

 
                                                                     
             
                  
                                                       

                                              
                                                              
                                            
                                           
                                  
                                     
                                 
                                       

 
                                                           
             
                  
                                     
                                               
                         


                                                   
   


                                                              
                                    




                                                                           
                              

                               
   
                              
                                          
                            
                                                     
                                          

                                                                                          
                           
                     

                                        
                                      
                                              
                        
                  
   
              
                                             

                                                          

 
                                                                          
             
                  
                                   
                                        
                                              


                                                   
   


                                                              
                                      
                       



                                                                            
                              
                        

                               
   
                              
                   
                                          
                                                     
                                        
                  
                                 
                                                              
                          
                     

                                        
                                      
                                              
                        
                  
   
              
                                             

                                                          

 
                                                  
             
                  
                                              
   
                                      
                   
                       
   

 

                                 
               

                                                  

                                              
   
                         

                         




                                        
               
                                                
                          
                                                

                                              
   
                         

                         




                                       
               
                                                                  

                               
                                                  

                                              
   
                         

                         




                       
               
                              

                                                                  
                            

                               
                              

                                              
                                                      
                          
                                              
                                                     
                               
                                              
   
                         



                                                     


   

                                     
               

                                                  

                                      
   
                         

                     




                                  

                                                                  
                          

                                      
   
                         

                     




                             
               

                                                                  
                          

                                      
   
                         

                     




                                
               

                                                                  

                               

                                      
   
                         

                     

   
 



                                                                   
                         
              


                                                            
                                                                                                     
                                                

                  
                                             

                                            
                                            

                  
                                             








                                                                                           
                               
                                           
                   
 
                            
                                           

                        
                                          
                                

                                
 
 


                                                                         
                                                        


                                             

                                          




                                                                                                       
                                                       


                             
                                         


                                      
                            
                                             



                                 
                                          


                           
                                                 

                  
                                       


                     
                                                 
                                                                                                           
             
                  
                  
                           
   
                                  

                                                             
                     
                                      
                   


                                           
                                     
                               
                                 
         
                                                       
                                                      
                                
                                                    
                                                          
         



                                   
                            
                                            
                         

          
                                   
                   
                                                
                                       
     
                                           
                    
                                       
                                           
                   

          


                                        
            
     

        



                                            
               


                                                                           
                                               
                     

                                                                        
                                                                            
                                            
                                          
                          

                                                                                         

                            
                                            
                                          
                          

                                                                               

                            
                                            
                                          
                          

                                                                               

                                  
                                            
                                          
                                    
                            
                                                                                       
                                  




                        
# Mu synchronizes between routines using channels rather than locks, like
# Erlang and Go.
#
# Key properties of channels:
#
#   a) Writing to a full channel or reading from an empty one will put the
#   current routine in 'waiting' state until the operation can be completed.
#
#   b) Writing to a channel implicitly performs a deep copy. This prevents
#   addresses from being shared between routines, and therefore eliminates all
#   possibility of race conditions.

scenario channel [
  run [
    local-scope
    source:&:source:num, sink:&:sink:num <- new-channel 3/capacity
    sink <- write sink, 34
    10:num/raw, 11:bool/raw, source <- read source
  ]
  memory-should-contain [
    10 <- 34
    11 <- 0  # read was successful
  ]
]

container channel:_elem [
  lock:bool  # inefficient but simple: serialize all reads as well as writes
  first-full:num  # for write
  first-free:num  # for read
  # A circular buffer contains values from index first-full up to (but not
  # including) index first-empty. The reader always modifies it at first-full,
  # while the writer always modifies it at first-empty.
  data:&:@:_elem
]

# Since channels have two ends, and since it's an error to use either end from
# multiple routines, let's distinguish the ends.

container source:_elem [
  chan:&:channel:_elem
]

container sink:_elem [
  chan:&:channel:_elem
]

def new-channel capacity:num -> in:&:source:_elem, out:&:sink:_elem [
  local-scope
  load-ingredients
  result:&:channel:_elem <- new {(channel _elem): type}
  *result <- put *result, first-full:offset, 0
  *result <- put *result, first-free:offset, 0
  capacity <- add capacity, 1  # unused slot for 'full?' below
  data:&:@:_elem <- new _elem:type, capacity
  *result <- put *result, data:offset, data
  in <- new {(source _elem): type}
  *in <- put *in, chan:offset, result
  out <- new {(sink _elem): type}
  *out <- put *out, chan:offset, result
]

def write out:&:sink:_elem, val:_elem -> out:&:sink:_elem [
  local-scope
  load-ingredients
  assert out, [write to null channel]
  chan:&:channel:_elem <- get *out, chan:offset
  <channel-write-initial>
  # block until lock is acquired AND queue has room
  lock:location <- get-location *chan, lock:offset
#?   $print [write], 10/newline
  {
#?     $print [trying to acquire lock for writing], 10/newline
    wait-for-reset-then-set lock
#?     $print [lock acquired for writing], 10/newline
    full?:bool <- channel-full? chan
    break-unless full?
#?     $print [but channel is full; relinquishing lock], 10/newline
    # channel is full; relinquish lock and give a reader the opportunity to
    # create room on it
    reset lock
    current-routine-is-blocked
    switch  # avoid spinlocking
    loop
  }
  current-routine-is-unblocked
#?   $print [performing write], 10/newline
  # store a deep copy of val
  circular-buffer:&:@:_elem <- get *chan, data:offset
  free:num <- get *chan, first-free:offset
  val-copy:_elem <- deep-copy val  # on this instruction rests all Mu's concurrency-safety
  *circular-buffer <- put-index *circular-buffer, free, val-copy
  # mark its slot as filled
  free <- add free, 1
  {
    # wrap free around to 0 if necessary
    len:num <- length *circular-buffer
    at-end?:bool <- greater-or-equal free, len
    break-unless at-end?
    free <- copy 0
  }
  # write back
  *chan <- put *chan, first-free:offset, free
#?   $print [relinquishing lock after writing], 10/newline
  reset lock
]

def read in:&:source:_elem -> result:_elem, eof?:bool, in:&:source:_elem [
  local-scope
  load-ingredients
  assert in, [read on null channel]
  eof? <- copy 0/false  # default result
  chan:&:channel:_elem <- get *in, chan:offset
  # block until lock is acquired AND queue has data
  lock:location <- get-location *chan, lock:offset
#?   $print [read], 10/newline
  {
#?     $print [trying to acquire lock for reading], 10/newline
    wait-for-reset-then-set lock
#?     $print [lock acquired for reading], 10/newline
    empty?:bool <- channel-empty? chan
    break-unless empty?
#?     $print [but channel is empty; relinquishing lock], 10/newline
    # channel is empty; relinquish lock and give a writer the opportunity to
    # add to it
    reset lock
    current-routine-is-blocked
    <channel-read-empty>
    switch  # avoid spinlocking
    loop
  }
  current-routine-is-unblocked
  # pull result off
  full:num <- get *chan, first-full:offset
  circular-buffer:&:@:_elem <- get *chan, data:offset
  result <- index *circular-buffer, full
  # clear the slot
  empty:&:_elem <- new _elem:type
  *circular-buffer <- put-index *circular-buffer, full, *empty
  # mark its slot as empty
  full <- add full, 1
  {
    # wrap full around to 0 if necessary
    len:num <- length *circular-buffer
    at-end?:bool <- greater-or-equal full, len
    break-unless at-end?
    full <- copy 0
  }
  # write back
  *chan <- put *chan, first-full:offset, full
#?   $print [relinquishing lock after reading], 10/newline
  reset lock
]

def clear in:&:source:_elem -> in:&:source:_elem [
  local-scope
  load-ingredients
  chan:&:channel:_elem <- get *in, chan:offset
  {
    empty?:bool <- channel-empty? chan
    break-if empty?
    _, _, in <- read in
  }
]

scenario channel-initialization [
  run [
    local-scope
    source:&:source:num <- new-channel 3/capacity
    chan:&:channel:num <- get *source, chan:offset
    10:num/raw <- get *chan, first-full:offset
    11:num/raw <- get *chan, first-free:offset
  ]
  memory-should-contain [
    10 <- 0  # first-full
    11 <- 0  # first-free
  ]
]

scenario channel-write-increments-free [
  run [
    local-scope
    _, sink:&:sink:num <- new-channel 3/capacity
    sink <- write sink, 34
    chan:&:channel:num <- get *sink, chan:offset
    10:num/raw <- get *chan, first-full:offset
    11:num/raw <- get *chan, first-free:offset
  ]
  memory-should-contain [
    10 <- 0  # first-full
    11 <- 1  # first-free
  ]
]

scenario channel-read-increments-full [
  run [
    local-scope
    source:&:source:num, sink:&:sink:num <- new-channel 3/capacity
    sink <- write sink, 34
    _, _, source <- read source
    chan:&:channel:num <- get *source, chan:offset
    10:num/raw <- get *chan, first-full:offset
    11:num/raw <- get *chan, first-free:offset
  ]
  memory-should-contain [
    10 <- 1  # first-full
    11 <- 1  # first-free
  ]
]

scenario channel-wrap [
  run [
    local-scope
    # channel with just 1 slot
    source:&:source:num, sink:&:sink:num <- new-channel 1/capacity
    chan:&:channel:num <- get *source, chan:offset
    # write and read a value
    sink <- write sink, 34
    _, _, source <- read source
    # first-free will now be 1
    10:num/raw <- get *chan, first-free:offset
    11:num/raw <- get *chan, first-free:offset
    # write second value, verify that first-free wraps
    sink <- write sink, 34
    20:num/raw <- get *chan, first-free:offset
    # read second value, verify that first-full wraps
    _, _, source <- read source
    30:num/raw <- get *chan, first-full:offset
  ]
  memory-should-contain [
    10 <- 1  # first-free after first write
    11 <- 1  # first-full after first read
    20 <- 0  # first-free after second write, wrapped
    30 <- 0  # first-full after second read, wrapped
  ]
]

scenario channel-new-empty-not-full [
  run [
    local-scope
    source:&:source:num <- new-channel 3/capacity
    chan:&:channel:num <- get *source, chan:offset
    10:bool/raw <- channel-empty? chan
    11:bool/raw <- channel-full? chan
  ]
  memory-should-contain [
    10 <- 1  # empty?
    11 <- 0  # full?
  ]
]

scenario channel-write-not-empty [
  run [
    source:&:source:num, sink:&:sink:num <- new-channel 3/capacity
    chan:&:channel:num <- get *source, chan:offset
    sink <- write sink, 34
    10:bool/raw <- channel-empty? chan
    11:bool/raw <- channel-full? chan
  ]
  memory-should-contain [
    10 <- 0  # empty?
    11 <- 0  # full?
  ]
]

scenario channel-write-full [
  run [
    local-scope
    source:&:source:num, sink:&:sink:num <- new-channel 1/capacity
    chan:&:channel:num <- get *source, chan:offset
    sink <- write sink, 34
    10:bool/raw <- channel-empty? chan
    11:bool/raw <- channel-full? chan
  ]
  memory-should-contain [
    10 <- 0  # empty?
    11 <- 1  # full?
  ]
]

scenario channel-read-not-full [
  run [
    local-scope
    source:&:source:num, sink:&:sink:num <- new-channel 1/capacity
    chan:&:channel:num <- get *source, chan:offset
    sink <- write sink, 34
    _, _, source <- read source
    10:bool/raw <- channel-empty? chan
    11:bool/raw <- channel-full? chan
  ]
  memory-should-contain [
    10 <- 1  # empty?
    11 <- 0  # full?
  ]
]

## cancelling channels

# every channel comes with a boolean signifying if it's been closed
# initially this boolean is false
container channel:_elem [
  closed?:bool
]

# a channel can be closed from either the source or the sink
# both routines can modify the 'closed?' bit, but they can only ever set it, so this is a benign race
def close x:&:source:_elem -> x:&:source:_elem [
  local-scope
  load-ingredients
  chan:&:channel:_elem <- get *x, chan:offset
  *chan <- put *chan, closed?:offset, 1/true
]
def close x:&:sink:_elem -> x:&:sink:_elem [
  local-scope
  load-ingredients
  chan:&:channel:_elem <- get *x, chan:offset
  *chan <- put *chan, closed?:offset, 1/true
]

# once a channel is closed from one side, no further operations are expected from that side
# if a channel is closed for reading,
#   no further writes will be let through
# if a channel is closed for writing,
#   future reads continue until the channel empties,
#   then the channel is also closed for reading
after <channel-write-initial> [
  closed?:bool <- get *chan, closed?:offset
  return-if closed?
]
after <channel-read-empty> [
  closed?:bool <- get *chan, closed?:offset
  {
    break-unless closed?
    empty-result:&:_elem <- new _elem:type
    current-routine-is-unblocked
    return *empty-result, 1/true
  }
]

## helpers

# An empty channel has first-empty and first-full both at the same value.
def channel-empty? chan:&:channel:_elem -> result:bool [
  local-scope
  load-ingredients
  # return chan.first-full == chan.first-free
  full:num <- get *chan, first-full:offset
  free:num <- get *chan, first-free:offset
  result <- equal full, free
]

# A full channel has first-empty just before first-full, wasting one slot.
# (Other alternatives: https://en.wikipedia.org/wiki/Circular_buffer#Full_.2F_Empty_Buffer_Distinction)
def channel-full? chan:&:channel:_elem -> result:bool [
  local-scope
  load-ingredients
  # tmp = chan.first-free + 1
  tmp:num <- get *chan, first-free:offset
  tmp <- add tmp, 1
  {
    # if tmp == chan.capacity, tmp = 0
    len:num <- capacity chan
    at-end?:bool <- greater-or-equal tmp, len
    break-unless at-end?
    tmp <- copy 0
  }
  # return chan.first-full == tmp
  full:num <- get *chan, first-full:offset
  result <- equal full, tmp
]

def capacity chan:&:channel:_elem -> result:num [
  local-scope
  load-ingredients
  q:&:@:_elem <- get *chan, data:offset
  result <- length *q
]

# helper for channels of characters in particular
def buffer-lines in:&:source:char, buffered-out:&:sink:char -> buffered-out:&:sink:char, in:&:source:char [
  local-scope
  load-ingredients
  # repeat forever
  eof?:bool <- copy 0/false
  {
    line:&:buffer <- new-buffer 30
    # read characters from 'in' until newline, copy into line
    {
      +next-character
      c:char, eof?:bool, in <- read in
      break-if eof?
      # drop a character on backspace
      {
        # special-case: if it's a backspace
        backspace?:bool <- equal c, 8
        break-unless backspace?
        # drop previous character
        {
          buffer-length:num <- get *line, length:offset
          buffer-empty?:bool <- equal buffer-length, 0
          break-if buffer-empty?
          buffer-length <- subtract buffer-length, 1
          *line <- put *line, length:offset, buffer-length
        }
        # and don't append this one
        loop +next-character:label
      }
      # append anything else
      line <- append line, c
      line-done?:bool <- equal c, 10/newline
      break-if line-done?
      loop
    }
    # copy line into 'buffered-out'
    i:num <- copy 0
    line-contents:text <- get *line, data:offset
    max:num <- get *line, length:offset
    {
      done?:bool <- greater-or-equal i, max
      break-if done?
      c:char <- index *line-contents, i
      buffered-out <- write buffered-out, c
      i <- add i, 1
      loop
    }
    {
      break-unless eof?
      buffered-out <- close buffered-out
      return
    }
    loop
  }
]

scenario buffer-lines-blocks-until-newline [
  run [
    local-scope
    source:&:source:char, sink:&:sink:char <- new-channel 10/capacity
    _, buffered-stdin:&:sink:char/buffered-stdin <- new-channel 10/capacity
    buffered-chan:&:channel:char <- get *buffered-stdin, chan:offset
    empty?:bool <- channel-empty? buffered-chan
    assert empty?, [ 
F buffer-lines-blocks-until-newline: channel should be empty after init]
    # buffer stdin into buffered-stdin, try to read from buffered-stdin
    buffer-routine:num <- start-running buffer-lines, source, buffered-stdin
    wait-for-routine-to-block buffer-routine
    empty? <- channel-empty? buffered-chan
    assert empty?:bool, [ 
F buffer-lines-blocks-until-newline: channel should be empty after buffer-lines bring-up]
    # write 'a'
    sink <- write sink, 97/a
    restart buffer-routine
    wait-for-routine-to-block buffer-routine
    empty? <- channel-empty? buffered-chan
    assert empty?:bool, [ 
F buffer-lines-blocks-until-newline: channel should be empty after writing 'a']
    # write 'b'
    sink <- write sink, 98/b
    restart buffer-routine
    wait-for-routine-to-block buffer-routine
    empty? <- channel-empty? buffered-chan
    assert empty?:bool, [ 
F buffer-lines-blocks-until-newline: channel should be empty after writing 'b']
    # write newline
    sink <- write sink, 10/newline
    restart buffer-routine
    wait-for-routine-to-block buffer-routine
    empty? <- channel-empty? buffered-chan
    data-emitted?:bool <- not empty?
    assert data-emitted?, [ 
F buffer-lines-blocks-until-newline: channel should contain data after writing newline]
    trace 1, [test], [reached end]
  ]
  trace-should-contain [
    test: reached end
  ]
]