fastspin compiler for P2: Assembly, Spin, BASIC, and C in one compiler

1171820222329

Comments

  • dnalor wrote: »
    ersmith wrote: »
    We could also provide a switch to explicitly dump labels (and any other debug information we can think of) into a file for the debugger. What format would be convenient for that?
    Your list file is now:
    HUB-address Cog-address hex |asm

    I would prefer a csv file (semicolon or tab as delimiter) for easy tokenization:
    HUB-address;Cog-address;hex;asm

    A user would want to set a breakpoint in the sourcefile. For that I would need sourcefile-name and sourcefile-line, that I can find the address for the breakpoints.
    sourcefile-name;sourcfile-line;HUB-address;Cog-address;hex;asm
    Would that be doable?

    I think it would be do-able. The fastspin file that would need changing is backends/outdat/outlst.c. I like the idea of including the source file name and line number, and those are available in the LineInfo structure that we're already using to print the source code.

    Do you have an actual use-case for this now, or is it just a suggestion for future use?
  • ersmith wrote: »

    I think it would be do-able. The fastspin file that would need changing is backends/outdat/outlst.c. I like the idea of including the source file name and line number, and those are available in the LineInfo structure that we're already using to print the source code.

    Do you have an actual use-case for this now, or is it just a suggestion for future use?
    I want to build a source level debugger.
    Ah outlst.c, I see, should not be too difficult, but I would need some Windows-IDE with a debugger for stepping thru some of the code to get the whole picture.
    Commandline and makefiles are not my world.


    οἶδα οὐκ εἰδώς
  • dnalor wrote: »
    Commandline and makefiles are not my world.
    They certainly aren't going away. I spent a long time wondering how much effort I should put into learning more of the general shell tools but once done it's well worth it.

    "We suspect that ALMA will allow us to observe this rare form of CO in many other discs.
    By doing that, we can more accurately measure their mass, and determine whether
    scientists have systematically been underestimating how much matter they contain."
  • dnalor wrote: »
    Commandline and makefiles are not my world.
    Me too.

    IMHO They were obsoleted more than 20 years ago, yet they still persist. It’s a *nix thing and again, IMHO, that is what prevents *nix from replacing Windoze. *nix shells just don’t work like windoze or mac.
    My Prop boards: P8XBlade2, RamBlade, CpuBlade, TriBlade
    Prop OS (also see Sphinx, PropDos, PropCmd, Spinix)
    Website: www.clusos.com
    Prop Tools (Index) , Emulators (Index) , ZiCog (Z80)
  • evanhevanh Posts: 7,888
    edited 2019-08-21 - 08:33:45
    But they do work well. And googling for answers makes a big diff to getting up to speed.

    EDIT: Cluso, I'd argue for 30 years ago for the Mac and Amiga and Archimedes. But that is another era. The PC kind of destroyed it.

    "We suspect that ALMA will allow us to observe this rare form of CO in many other discs.
    By doing that, we can more accurately measure their mass, and determine whether
    scientists have systematically been underestimating how much matter they contain."
  • evanh wrote: »
    But they do work well. And googling for answers makes a big diff to getting up to speed.

    EDIT: Cluso, I'd argue for 30 years ago for the Mac and Amiga and Archimedes. But that is another era. The PC kind of destroyed it.

    And the P2 will be here to take us back there to 30 years ago to reverse that mistake.

    If my ringnet using streamers really works with more then one P2, clustering gets easy, software wise.

    My fingers are itching to try it out, but I just have one P2.

    So I am playing with FastSpin, and check out BASIC and C. @ersmith is doing a tremendous job there, this fits like hand in a glove. Mixing the Languages in one Project is simply wonderful.

    Enjoy!

    Mike
    I am just another Code Monkey.
    A determined coder can write COBOL programs in any language. -- Author unknown.
    Press any key to continue, any other key to quit

    The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this post are to be interpreted as described in RFC 2119.
  • They were not made obsolite at all.

    What did happen is new options became available. That means options for people. Great!

    None of it is obsolite. There are compelling use cases all around, including automated ones. both remain economically relevant.

    Really, the more robust argument is the new options are highly desirable. Truth right there.

    No need to devalue otherwise relevant, time tested, production proven means and methods.


    Do not taunt Happy Fun Ball! @opengeekorg ---> Be Excellent To One Another SKYPE = acuity_doug
    Parallax colors simplified: https://forums.parallax.com/discussion/123709/commented-graphics-demo-spin<br>
  • Eric: Does Fastspin support closures in any of its languages? What about nested function definitions?
  • David Betz wrote: »
    Eric: Does Fastspin support closures in any of its languages? What about nested function definitions?
    https://github.com/totalspectrum/spin2cpp/blob/master/doc/basic.md#closures
    ◁ propeller-wiki ▷ ◁ FastSpin ▷ ◁ Help Spin at RosettaCode.org ▷ ◁ No Source – No Go! ▷ ◁ DK-E ▷ ◁ :-D ▷ ◁ Stay OmmmmmmPtimistic! ▷ ◁ Why Asimov's Laws of Robotics Don't Work ▷ ◁ DNA is a four letter word. ▷ ◁ Stop slavery! Free all mitochondria! ▷
  • Wow. Fastspin is the bees knees! I've been playing with it all afternoon. It's just become my go-to RAD platform for the Prop.

    ERSmith: I've put together a "want list" and some observations. Shall I just post it all right here, or shall I take it to a private message?

    I'm like a kid on Christmas morning here...
  • David Betz wrote: »
    Eric: Does Fastspin support closures in any of its languages? What about nested function definitions?

    Yeti's already posted a link to the documentation. At the moment only BASIC has closures and nested function definitions. It even has anonymous functions.
  • JRoark wrote: »
    Wow. Fastspin is the bees knees! I've been playing with it all afternoon. It's just become my go-to RAD platform for the Prop.

    ERSmith: I've put together a "want list" and some observations. Shall I just post it all right here, or shall I take it to a private message?

    I'm like a kid on Christmas morning here...

    Glad you're having fun! Feel free to post requests and observations here. There are some users like @yeti who can also help (and who sometimes surprise me with their fastspin discoveries :) ).
  • All of my comments pertain to Fastspin V3.9.30 running under Win10 on a Dell Inspiron 7000-series laptop. Target device is set to a Prop1, and the language used is BASIC. I'm approaching this from the viewpoint of a teacher/mentor, so some of the comments I make may not be immediately seen as important by others with a different viewpoint.

    === Editor Stuff: ===
    1). In the editor, can you make TAB characters expand to a configurable size? Presently it seems hardcoded to 8 chars, but after several indents that puts you halfway across the screen. User-specified tab expansion from 2 to (??) characters would be wonderful.
    'default behavior, tab=8 char spaces:
    for a = 1 to 10
            for b = 3 to 5
                    c = a * b
            next b
    next a
    
    
    'desired (configurable) behavior with tabs expanded to variable spaces (in this case 3 spaces per tab char)
    for a = 1 to 10
       for b = 3 to 5
          c = a * b
       next b
    next a
    
    

    2). In the compiler output pane, can you add a line that specifically says "No Errors" (if successful) and another that gives the machine time/date of completion? My suggestion stems from the fact that on short programs, a successful compile is so fast that sometimes I'm left wondering if the compile actually happened. Unless the byte count changes, there isn't any clear feedback that it's done. And if you really want to make teachers happy, put the machine name in there (so we can see when/where the work was done! heheh)

    Current behavior:
    "./bin/fastspin" -l -O1 -L "./include" "D:/Spin2Gui/test1.bas"
    Propeller Spin/PASM Compiler 'FastSpin' (c) 2011-2019 Total Spectrum Software Inc.
    Version 3.9.30 Compiled on: Sep  6 2019
    test1.bas
    fmt.c
    strlen.c
    test1.pasm
    Done.
    Program size is 2908 bytes
    

    Desired behavior:
    "./bin/fastspin" -l -O1 -L "./include" "D:/Spin2Gui/test1.bas"
    Propeller Spin/PASM Compiler 'FastSpin' (c) 2011-2019 Total Spectrum Software Inc.
    Version 3.9.30 Compiled on: Sep  6 2019
    test1.bas
    fmt.c
    strlen.c
    test1.pasm
    Done. No errors. 
    Program size is 2908 bytes
    Finished at 12:34:56 Jan 01, 2019 on MACHINENAME
    

    3). Can you make the compiler output pane resizable in the vertical direction via doing a grab-and-drag on the separator bar? Screen real estate gets precious as the font size increases (many folks have visual impairments) and getting an extra line or two on-screen would really be helpful.

    4). Clicking on Edit--> Select A Font brings up a font dialogue box, but there are some nits...
    4A). No default font shows as being selected (ie, the "Font:" listbox header is blank.
    4B). No font sample shows-up in the "Sample" frame.
    4C). The "Script" widget is blank and unpopulated.
    4D). Checking/UnChecking the "Strikeout" and "Underline" checkboxes reset the font size to default (10)
    4E). Checking the "Strikeout" or "Underline" checkboxes changes the text in the editor correctly (except for 4D), but if you bring the font dialogue back up, the "Strikeout" and "Underline" checkboxes do not reflect the state of the now italic/strike-thru text.

    5). Can you implement an option to turn line numbering on and off in the editor? And maybe introduce a space or two between the line numbers and the first character of the user editing area to decramp the left margin?

    6). Could you make it so right-clicking on a tab could bring-up a menu with the usual Save/Close options, instead of having to go back to the top menu to accomplish these operations?


    ======= Compiler Stuff =======

    7). There seems to be a conflict between explicit and implied variable referencing. Specifically, the compiler seems to incorporate any type-declaration character (!,%,$, etc) as part of the variable name.
    option explicit
    dim b% as integer
    b% = &hFFFF		'<-- works just fine
    b = &hFFFF              '<-- gives "error: Unknown symbol b%"
    

    Conversely, this also fails:
    option explicit
    dim b as integer
    b% = &hFFFF		'<-- gives "error: Unknown symbol b%". Shouldn't give an error.
    b = &hFFFF              '<-- works just fine, as it should.
    

    This is another permutation of the explicit/implied reference bug:
    option explicit
    dim b% as string	'<-- this should throw an error but it does not
    b% = "test"		'<-- this should throw an error but does not
    b = "test"		'<-- this should not throw an error but does! ("error: Unknown symbol b")
    



    ERSmith: I want to make it clear that many of these are just nits and me being greedy because I have a new toy. :) Please don't take any of these "bugs" as expressed or implied criticism. Quite the opposite. I *really* like FastSpin, so I'm putting these up here in the hope they'll be considered and maybe advance the effort.
  • David BetzDavid Betz Posts: 13,480
    edited 2019-09-07 - 23:07:25
    ersmith wrote: »
    David Betz wrote: »
    Eric: Does Fastspin support closures in any of its languages? What about nested function definitions?

    Yeti's already posted a link to the documentation. At the moment only BASIC has closures and nested function definitions. It even has anonymous functions.
    How did you close over variables in outer scopes? Can I write a function that returns a function that closes over its arguments or do you only support downward closures?

  • @JRoak in your example, b and b% and b$ have to be unique variables. Don't think of those suffixes as a cast or type conversion. They really are just (by default) shorthand for the variable type. I remember one of the manuals expressing this.

    In pretty much any ms basic, B and B$ refer to two different variables.

    Yes i totally agree with your suggestion about a timestamp and a few other things.
  • whicker wrote: »
    @JRoak in your example, b and b% and b$ have to be unique variables. Don't think of those suffixes as a cast or type conversion. They really are just (by default) shorthand for the variable type. I remember one of the manuals expressing this.

    In pretty much any ms basic, B and B$ refer to two different variables.

    Yes i totally agree with your suggestion about a timestamp and a few other things.

    Interesting. I still maintain some MS VB6 and PDS7.1 stuff. In both of those languages once you “Dim B As Integer”, you can reference the resulting var as “B”, or “B%” and they all ref the same variable. And any later attempts to create a “B$” (or “B#”, etc) will throw an error.

    So I learned something new! That makes more sense now. Thanks.

  • yetiyeti Posts: 585
    edited 2019-09-07 - 23:25:43
    $ cat xyzzy.bas 
    dim as integer a$
    
    a$=1330
    print a$+7
    
    $ fastspin xyzzy.bas 
    Propeller Spin/PASM Compiler 'FastSpin' (c) 2011-2019 Total Spectrum Software Inc.
    Version 3.9.30 Compiled on: Sep  5 2019
    xyzzy.bas
    fmt.c
    strlen.c
    xyzzy.pasm
    Done.
    Program size is 2796 bytes
    
    $ spinsim -b xyzzy.binary 
    1337
    
    I can understand that this really looks "a bit unusual".



    And an other thing: In common oldtimer BASICs you could have a variable and an array of the same name without conflict:
    A>mbasic
    BASIC-85 Rev. 5.29
    [CP/M Version]
    Copyright 1985-1986  $  by Microsoft
    Created: 28-Jul-85
    38968 Bytes free
    Ok
    10 x(1)=1330
    20 x=7
    30 print x(1)+x
    run
     1337 
    Ok
    █
    

    While FlexBASIC sees it differently, a bit more C like:
    $ cat -n clzzl.bas # -n adds line numbers
         1  dim as integer x(9)
         2  x(1)=1330
         3  x=7
         4  print x(1)+x
    
    $ fastspin clzzl.bas 
    Propeller Spin/PASM Compiler 'FastSpin' (c) 2011-2019 Total Spectrum Software Inc.
    Version 3.9.30 Compiled on: Sep  5 2019
    clzzl.bas
    clzzl.bas(3) error: incompatible types in assignment
    
    I remember some old sources where coders liked to use e.g. a variable L as index to the array L(...).
    (e.g. in wumpus.bas)

    A new millenium, a new "basiC".
    ◁ propeller-wiki ▷ ◁ FastSpin ▷ ◁ Help Spin at RosettaCode.org ▷ ◁ No Source – No Go! ▷ ◁ DK-E ▷ ◁ :-D ▷ ◁ Stay OmmmmmmPtimistic! ▷ ◁ Why Asimov's Laws of Robotics Don't Work ▷ ◁ DNA is a four letter word. ▷ ◁ Stop slavery! Free all mitochondria! ▷
  • A blast from the past! I sort of remember that trick. As I recall the reference without any array ordinal as an argument returned the value in the zeroth array element, ie “x” returned the same value as “x(0)”, but “x” was the faster choice and saved a few bytes.

    The last CP/M MBASIC version I used was Basic-80 on an S-100 based Action Computer Enterprise Discovery 1600 multiprocessor. 64k of RAM, a Priam hard drive, and a SOROC IQ-120 terminal. Good times.

    You must be old... like me. :)
  • yetiyeti Posts: 585
    edited 2019-09-07 - 23:55:43
    list
    10 C(0)=10
    20 C(1)=11
    30 C=12
    40 PRINT C(0)
    50 PRINT C
    Ok
    run
     10 
     12 
    Ok
    █
    
    In MBASIC and siblings c and c(0) are different.
    ◁ propeller-wiki ▷ ◁ FastSpin ▷ ◁ Help Spin at RosettaCode.org ▷ ◁ No Source – No Go! ▷ ◁ DK-E ▷ ◁ :-D ▷ ◁ Stay OmmmmmmPtimistic! ▷ ◁ Why Asimov's Laws of Robotics Don't Work ▷ ◁ DNA is a four letter word. ▷ ◁ Stop slavery! Free all mitochondria! ▷
  • i tried yeti's code in a strict ANSI basic and this was the result:
    320 x 240 - 1K
  • whickerwhicker Posts: 483
    edited 2019-09-09 - 05:53:37
    Unfortunately the string handling is still malfunctioning in 3.9.30

    Here is a screenshot of a simple program loop.
    It is me who is typing in all the 9's.

    It first only prints the first character of the string in subsequent print statements.
    then the next time as I'm entering more 9's, it gives the !!! out of memory !!!
    basic%20strings%203.9.30.png

  • I can only test it on P1.
    As long as I have no doubts, I prefer SpinSim.
    $ cat werre.bas 
    dim R$ as string
    dim A as integer
    
    print "Hi"
    FOR A = 1 to 10 : print A, : NEXT A
    print
    
    HERE:
    INPUT "Enter a string"; R$
    PRINT "You typed:"; R$
    GOTO HERE
    
    $ fastspin werre.bas 
    Propeller Spin/PASM Compiler 'FastSpin' (c) 2011-2019 Total Spectrum Software Inc.
    Version 3.9.30 Compiled on: Sep  8 2019
    werre.bas
    readdata.spin
    fmt.c
    gcptrs.spin
    strlen.c
    werre.pasm
    Done.
    Program size is 7624 bytes
    
    $ spinsim -b werre.binary 
    Hi
    1       2       3       4       5       6       7       8       9       10
    Enter a string? test
    You typed:test
    Enter a string? 1 2 test
    You typed:1 2 test
    Enter a string? mooooooooooooooooooooooooooooo
    You typed:mooooooooooooooooooooooooooooo
    Enter a string? █
    
    ◁ propeller-wiki ▷ ◁ FastSpin ▷ ◁ Help Spin at RosettaCode.org ▷ ◁ No Source – No Go! ▷ ◁ DK-E ▷ ◁ :-D ▷ ◁ Stay OmmmmmmPtimistic! ▷ ◁ Why Asimov's Laws of Robotics Don't Work ▷ ◁ DNA is a four letter word. ▷ ◁ Stop slavery! Free all mitochondria! ▷
  • yes, yeti, but keep it looping by typing lots of text characters and hitting Enter. Does it eventually run out of memory?
  • JRoarkJRoark Posts: 94
    edited 2019-09-09 - 12:03:53
    I seem to recall that strings have a 255 character limit. This includes INPUT, etc.

    EDIT: Yup. According to the doc, the string functions allocate from the heap, and the heap defaults to 256 bytes.
    Memory allocation
    FlexBASIC supports allocation of memory and garbage collection. Memory allocation is done from a small built-in heap. This heap defaults to 256 bytes in size on Propeller 1, and 4096 bytes on Propeller 2. This may be changed by defining a constant HEAPSIZE in the top level file of the program.

    And on the next page, this:
    String functions
    String functions and operators like left$, right$, and + (string concatenation) also work with allocated memory. If there is not enough memory to allocate for a string, these functions/operators will return nil.

    So try changing the HEAPSIZE and see what happens.
  • JRoark wrote: »
    All of my comments pertain to Fastspin V3.9.30 running under Win10 on a Dell Inspiron 7000-series laptop. Target device is set to a Prop1, and the language used is BASIC. I'm approaching this from the viewpoint of a teacher/mentor, so some of the comments I make may not be immediately seen as important by others with a different viewpoint.

    Thanks for your comments and suggestions! Your editor comments all make sense. I'm not sure how much I can modify the editor (which is basically just a tcl/tk widget I'm using, I didn't write it myself), but I'll look into it and see what I can do.
    7). There seems to be a conflict between explicit and implied variable referencing. Specifically, the compiler seems to incorporate any type-declaration character (!,%,$, etc) as part of the variable name.
    That's a feature, not a bug :). It's the way things worked in Commodore 64 BASIC: "A", "A$", and "A%" were all distinct variables. I think I have documented this in the basic.pdf file, but perhaps I should put in some more details.

    I realize not all BASICs do it this way, and I'm open to suggestions on changing it. We'll probably never get complete consensus, but if a lot of people prefer that the compiler keep the type subscript apart from the name I think that should be possible.

    Thank you for your suggestions, it's always good to get feedback!

    Eric

  • David Betz wrote: »
    ersmith wrote: »
    David Betz wrote: »
    Eric: Does Fastspin support closures in any of its languages? What about nested function definitions?

    Yeti's already posted a link to the documentation. At the moment only BASIC has closures and nested function definitions. It even has anonymous functions.
    How did you close over variables in outer scopes? Can I write a function that returns a function that closes over its arguments or do you only support downward closures?

    If a function returns a function (i.e. creates a closure) then its local variables, including arguments, are allocated on the heap instead of the stack. You certainly can write a function that closes over its arguments. There are some examples in the documentation. I hope the examples are clear, but please let me know if there's some way to improve them!
  • @whicker: yes, it certainly is possible (even easy) to run out of heap space, especially on P1. If you're doing a lot of string operations, or planning to input long strings, it would be prudent to declare a large HEAPSIZE, e.g.:
    const HEAPSIZE = 8192
    
    or even larger on P2 (where we have lots of memory).
  • JRoarkJRoark Posts: 94
    edited 2019-09-09 - 21:45:56
    Well, if its a feature, then heck... I’ll use it! :smile:

    If you get a chance to play with the editor, the ability exists currently to mark a block of text, but right clicking on the highlighted block does nothing. If you want to copy the block you can Ctrl+C, or you can hit DEL to delete, but having the usual right-click popul menu with copy/cut/delete would be a wonderful thing.

    One other thing, and I think it was previously reported and you fixed it, but it seems to be back: in an IF THEN ELSE construct, if all you have is a comment between the IF and ELSE, the compiler it throws an error. I’ll post sample code and the actual error when I get home tonight.

    I freakin’ love this thing!

    EDIT #1:
    Here is the code that breaks as explained above.
    if i% > 0 then
       'do nothing   '<-- this throws an "error: syntax error, unexpected else".  The compiler wants *something* to execute between an IF and an ELSE.
    else
       i% = i% - 1   'subtract one
    end if
    
    This code works just fine
    if i% > 0 then
       'do nothing
       i% = i%        '<-- silly, but it gives the compiler something to do and prevents throwing an error
    else
       i% = i% - 1   'subtract one
    end if
    

    EDIT #2:
    This code also throws a "error: syntax error, unexpected end"
    if i% > 0 then
       i% = i% - 1   'subtract one   
    else
       'do nothing   '<-- this throws an "error: syntax error, unexpected end". 
    end if
    
    Clearly the compiler wants something to do between all of the elements of the IF-ELSE-END IF

    EDIT #3:
    Looks like this same type of error extends into the SELECT CASE.
    This code works fine:
    select case a$
    case "A"
       b$ = "It was an A"
    case "B"
       b$ = "It was an B"
    end select
    
    This code throws an error:
    select case a$
    case "A"     
       'do nothing    '<-- this throws a "error: syntax error, unexpected case"
    case "B"
       b$ = "It was an B"
    end select
    
    This also throws a (different) error:
    select case a$
    case "A"     
       b$ = "It was an A"
    case "B"
       'do nothing    '<-- this throws a "error: syntax error, unexpected end"   
    end select
    

    EDIT #4:
    There is an odd bug in the "Commodore C64 Compatibility Feature". :smile: (ducking!)
    dim a$ as string   '<-- works fine
    dim b% as integer  '<-- works fine
    dim c! as single   '<-- throws "error: syntax error, unexpected !, expecting $end or end of line or end or ':'"
    
  • okay, so I tried the HEAPSIZE suggestion:
    const HEAPSIZE = 8192
    dim R$ as string
    dim A as integer
    
    print "Hi"
    
    HERE:
    INPUT "Enter a string"; R$
    PRINT "You typed:"; R$
    GOTO HERE
    


    That's 240 A's. It only takes 2 of these to cause the weirdness (printing only the 1st character).
    At the bottom, it's only printing "Y" instead of "You typed:", and only "A" instead of the 240 A's, and then "E" instead of "Enter a string".


    677 x 342 - 30K
  • yetiyeti Posts: 585
    edited 2019-09-09 - 18:42:51
    JRoark wrote: »
    One other thing, and I think it was previously reported and you fixed it, but it seems to be back: in an IF THEN ELSE construct, if all you have is a comment between the IF and ELSE, the compiler it throws an error. I’ll post sample code and the actual error when I get home tonight.
    Yipyip! Here's the code...
    $ cat if-x-then-remark-endif.bas 
    if 1 then
      ' then branch for sale
    end if
    $ fastspin if-x-then-remark-endif.bas
    Propeller Spin/PASM Compiler 'FastSpin' (c) 2011-2019 Total Spectrum Software Inc.
    Version 3.9.30 Compiled on: Sep  8 2019
    if-x-then-remark-endif.bas
    if-x-then-remark-endif.bas(3) error: syntax error, unexpected end
    
    JRoark wrote: »
    I freakin’ love this thing!
    Just found something unbeleivable at http://xahlee.info/comp/unicode_egyptian_hieroglyph.html!
    An ancient egyptian wondering about his chip being able to run FlexBASIC?
    𓀨
    Edit: Attached magnified snapshot.
    135 x 112 - 6K
    ◁ propeller-wiki ▷ ◁ FastSpin ▷ ◁ Help Spin at RosettaCode.org ▷ ◁ No Source – No Go! ▷ ◁ DK-E ▷ ◁ :-D ▷ ◁ Stay OmmmmmmPtimistic! ▷ ◁ Why Asimov's Laws of Robotics Don't Work ▷ ◁ DNA is a four letter word. ▷ ◁ Stop slavery! Free all mitochondria! ▷
Sign In or Register to comment.