TEXTO-PLANO ===================================================================== Revista digital 03 | 03 --------------------------------------------------------------------- 03 | Subleq y Forth ~anthk --------------------------------------------------------------------- Subleq y Forth. Subleq. Una máquina virtual y un lenguaje de programación donde todo son números. No, no estamos en 1950. Dejadme explicaros cosas como "One Instruction Set Computer", o en castellano, computador de una sola instrucción. Luego está Forth, viejo amigo de los 70 (casi como Subleq) donde se ha usado hasta en trastos de la NASA. ¿Viejuno? Os voy a demostrar lo que consiguen los sistemas de computación desde reglas supuestamente simples. Subleq solo posee una instrucción, con 3 operandos: A, B, C. Es decir, un computador SUBLEQ leerá las instrucciones de 3 en 3. En realidad son tres: SUBLEQ, INPUT (entrada) y OUTPUT (salida). Dados dichos operadores A B y C, restará la dirección donde *apunte* A de la dirección donde apunte igualmente B. También posee un contandor de instrucciones en C, donde indica el número a leer de la 'cinta'. Si donde apunte B restando de donde apunte A es igual o menor de 0, salta a donde apunte la dirección C. Esto es lioso, pero lo explico mejor. En el ejemplo de la Wikipedia: 3 4 6 7 7 7 3 4 0 Primero empezará a leer la primera fila: 3 4 6. 'A' tomará el valor donde apunte el 3. Recordemos que en computación se empieza a contar desde cero, así que en realidad vale el primer 7. 'A' ahora vale 7. Lo mismo con B, donde toma el valor del segundo 7. 'B' ahora vale 7. Ejecutamos la operación subleq, 7-7 = 0. La direccion B [4+1] ahora vale 0, que es el 5o número para los humanos. Saltamos a C, que tiene como dirección 6+1. Ergo, toca ir a 6+1, 7o número, donde vale 3. El estado actual es éste: 3 4 6 <= ejecuto y salto a 6+1 7 0 7 3 4 0 3 4 6 7-7 7 <= cambiado, salto a 6+1, 3 4 0 3 4 6 7-14 7 3 4 0 <= ejecuto, y salto a 0 3 4 6 7-21 7 <= cambio y a 0, inicio 3 4 0 3 4 6 <= ejecuto 7-21 7 3 4 0 Así cambia la "RAM" de la máquina SUBLEQ en cada instrucción. De la wikipedia, por igual, vuelco el trazado de la ejecución: 0: 3 4 6 A=7 B=0 6: 3 4 0 A=7 B=-7 0: 3 4 6 A=7 B=-14 6: 3 4 0 A=7 B=-21 0: 3 4 6 A=7 B=-28 Como se imprime algo en ASCII: tomemos de la wiki el ejemplo con "hi". h en ascii es 72 e i 105. Ergo, esquematizado: Direccion_ASCII_de_h -1 3 Direccion_Ascii_de_i -1 6 0 0 -1 72 105 0 Cambiamos direccion ASCII por, contando, la dirección 9, donde ponemos 72 y lo mismo hacemos en la dirección de I, en 10. Mirad las direcciones: 0 1 2 3 4 5 6 7 8 9 10 11 Ergo, para escribir un Hi: - Escribe lo que apunte en A:9 y salta a 3 (3+1). - En 3+1 me encuentro que escriba i, obedezco, y salto a 6 (6+1). - A vale 0, B vale 0, resto, salto a C, vale -1, es dirección inválda. - Si C vale -1, significa que cierre el programa. Hemos terminado. 9 -1 3 105 -1 6 0 0 -1 72 105 0 Todo esto al ejecutarse en subleq/muxleq devuelve: hi ¿Enrevesado? Pues pensar que bajo subleq/muxleq (la versión de muxleq donde las instrucciones se multiplexan cual canales de TV por cable ganando muchísimo en velocidad) hay hasta intérpretes de Eforth. Si quéreis probar un Eforth de forma rápida, necesitáis como mínimo un BSD/Linux o Windows con Min-C _Y_ los buildtools-6.1.exe que se consiguen más abajo en esta página: https://minc.commandlinerevolution.nl/english/home.html Una vez abierta una terminal en Linux/BSD o de MIn-C en Windows git clone https://github.com/howerj/muxleq cd muxleq make muxleq Para que ese Forth tenga soporte de flotantes (operaciones con decimales), mejor ayuda y demás, editamos muxleq.fth y dejamos así las siguientes líneas 1 constant opt.multi ( Add in large "pause" primitive ) 1 constant opt.editor ( Add in Text Editor ) 1 constant opt.info ( Add info printing function ) 0 constant opt.generate-c ( Generate C code ) 1 constant opt.better-see ( Replace 'see' with better version ) 1 constant opt.control ( Add in more control structures ) 0 constant opt.allocate ( Add in "allocate"/"free" ) 1 constant opt.float ( Add in floating point code ) 0 constant opt.glossary ( Add in "glossary" word ) 1 constant opt.optimize ( Enable extra optimization ) 1 constant opt.divmod ( Use "opDivMod" primitive ) 0 constant opt.self ( self-interpreter [NOT WORKING] ) Dejando el resto del archivo tal cual, se entiende. Compilamos el muxleq.dec donde es la ristra SUBLEQ para nuestro muxleq: ./muxleq ./muxleq.dec < muxleq.fth > new.dec Tardará un _buen_ rato, minutos, así que id a por un café y volver. Para probarlo (no hace falta generar el DEC cada vez que queráis arrancarlo solo se hace UNA vez): ./muxleq new.dec Se sale con 'bye'. ¿Cómo lo hace? Forth es un lenguaje que se programa 'al vuelo'. Usa palabras que son funciones guardadas en memoria. Opera tal que si la entrada dejada en la pila es un número, lo deja tal cual . Si no, busca en el diccionario una palabra (órden) y la ejecuta. Para imprimir una cadena, es como .", y con " se finaliza el operar con textos. Solo que EForth es especial, y para ello usa .( ) , dejando solo ." para usarse en palabras que creemos nosotros. Creemos una palabra que diga 'hola mundo'. Primero, veamos que ." falla sin ser algo compilado: ." hola mundo " falla Bien, creamos ahora la palabra 'hola': : hola ." hola mundo " ; hola hola mundo ok .( hola mundo ) hola mundo ok Este Forth ha sido creado a subleq casi por sí mismo, es decir, gran parte de Forth está escrito en muxleq.fth. Por ejemplo, los números flotantes han sido simulados con números enteros. Muchas otras operaciones son casi como las multiplicaciones de la escuela: suma con acarreo hasta que el resultado coincida. Esto es un Forth funcional, un lenguaje de programación en una máquina virtual/computador/CPU más simple que una patata, y es perfectmente operativo. Las operaciones son RPN, es decir, se opera con los operadores al final. Por ejemplo, para sumar 2 + 2, lanzamos 2 2 +. Como ejemplo de una calculadora básica, puedes consultar el artículo sobre calculadoras HP tipo RPN que ha escrito ~peron en este mismo número. Ejemplo de 2+2 en RPN: 2 2 + Eso ha dejado 2 en una pila, que es como opera Forth. Luego deja otro dos "encima": [2] [2] Para ver el contenido de la pila, usamos '.s'. .s 2 2 ok Agrega un + encima: [+] [2] [2] Como es un operador de suma, lo evalúa y deja 4 en la pila: [4] Lo sacamos: . 4 ok Ojo, . saca el valor de la pila y lo muestra, pero .s nos dejará la pila intacta. Veamos un caso más complicado. Para una suma y raíz de cuadrados, podemos usar fsqrt. Pero es flotante, así que debemos usar una notación especial y convertir antes los números a flotantes. Para ello se usa la palabra 'f'. 2 f 2 f Los puntos flotantes se convierten a algo totalmente distinto en la pila. Vedlo vosotros: .s -32768 16386 -32768 16386 ok Para sumarlos: f+ Como los flotantes van de dos en dos, existe en Forth la palabra 2DUP, que toma los dos últimos valores en la pila y los duplica. Convierte a b en a b a b. Hacemos eso para probar lo que devuelve un número flotante: 2dup Los sacamos e imprimimos en formato flotante con f. . Vedlo: f. 4.000 ok Ahora, para la raíz cuadrada de 2: 2 f fsqrt f. 1.414 ok Lista de palabras disponibles: words fasin fatan2 fatan fasinh facosh fatanh f** flog flog2 flnp1 fln agm sins fhypot filog2 f sqrt f~ ftan fcos fsin fsincos frad fdeg fln10 fln2 fe f2pi fhpi fpi fs. e. e e.r ftanh fsincosh fcosh fsinh fexpm1 falog fexp exp finv f1- f1+ fone fmod fround floor f>s fix fliteral fconstant f f. f.r f# s>f d>f fwithin fmax fmin f0>= f0<= f0< f0> f= f<= f>= f> f< f- fzero f+ f/ um/ f0= fsq f* f2/ f2* fsign fnegate fnip f2drop fdrop -frot frot ftuck f2dup fover fswap fdup faligned falign f, f! f @ precision set-precision float+ floats fcopysign fdepth fabs cos sin cordic */ */mod sm/rem m* 2literal 2variable 2constant d. 2, 2over dabs 2swap d0<> d0= d= d- d2/ d2* arshift convert spaces /string 1+! 2- 2+ macro +loop loop ?do do leave k j i unloop endcase endof of case many rpick editor quit load evaluate set-input get-input list blank block buffer empty-buffers flush save-buffers update b/ buf at-xy page bell ms [if] [else] [then] defined dump see compile-only immediate postpone \ .( ( abort" $" ." exit rdrop r> >r marker does> >body user constant variable create next aft for else repea t while then again until if begin recurse ' :noname : ; [char] char word definitions +order -order ( order) get-order interpret compile, literal compile find search-wordlist cfa nfa compare .s number? >number . u. u.r sign <# #s # #> hold parse -trailing query tib expect accept echo / mod /mod m/mod um/mod * um* d+ dnegate um+ abort throw catch space erase fill cmove type +string count c, , allot align aligned source 2r> 2>r 2@ 2! source-id min max c! c@ lshift +! pick set-current get-current cr emit key key? ?exit execute cell- cells cell+ cell abs s>d negate within u<= u>= u> u< <= >= 0>= 0< > < 0<= 0<> 0> <> = 2dup 2drop -rot rot r@ ?dup tuck nip [ ] decimal hex rp! rp@ sp! sp@ here bl spa n >in state hld dpl base scr blk context #vocs pad this root-voc current ! @ 2/ and or xor inve rt over ) 1- 1+ 2* 0= rshift swap drop dup bye - + eforth words only forth system forth-wordlist set -order ok Todo esto en subleq+EForth. Muxleq, realmente, pero las instrucciones son las mismas. El fichero new.dec son instrucciones subleq. Sacad las primeas líneas: openbsd$ cat new.dec | sed 11q 0 0 131 25 2174 128 -1 1 16 64 0 Número de líneas: openbsd$ wc -l new.dec 9018 new.dec Nada mal, ¿no? Si queréis aprender más sobre Forth, en inglés: https://www.forth.com/starting-forth/ en castellano: http://www.disc.ua.es/~gil/forth.pdf Cambiarán algunas cosas pero el 90% será lo mismo. EForth no tiene open-file pero se puede meter el código en una cadena y será cuasi equivalente a leer un fichero línea por línea. Quizá, si es posible, una adaptación del famoso Sokoban para lanzarlo en Muxleq + Eforth con todos los niveles de un programa normal. .( Feliz hackeo, y en el próximo número seguiré mostando trastos inútiles pero divertidos. .) : sentidovida 100 7 /mod 3 * . ; sentidovida ~anthk ---------------------------------------------------------------------