Programmeringsskolan

Brottsadvokat

Medlem
Reg
19 Jan 2019
Inlägg
9
Hej!

Nu provar jag att oannonserat starta en programmeringsskola. Skulle ingen vara direkt intresserad kommer det förhoppningsvis i alla fall hjälpa mig att reda ut tankarna och bli en bättre pedagog, och därmed en bättre programmerare själv. Jag började programmera när var 10 år, jag är nu ca 40 år, och har kodat i de flesta vanliga språk (och en del ovanliga). Det bästa nybörjarspråket är enligt mig Tcl, så jag kommer att basera mycket som jag skriver på det. Alla lärdomar och åsikter är givetvis intressanta, men Tcl är det högnivå-/scriptspråk som jag har valt att fortsätta att programmera i.

JavaScripts positiva sida är den vackra, naturliga objektorienteringen, och att språket p.g.a. det kunde designas att vara nästan helt separerat från gränssnittet. Själva språket är exakt detsamma vare sig du kodar ett vanligt program för Windows, en server eller en webbsida, det är bara objekten som du jobbar med - flyttar, kopierar, läser, manipulerar, visar - som skiljer sig. Browsers, menyer och sidor är t.ex. objekt på webben. Filer kan t.ex. vara objekt i ett desktopprogram.

Den friheten har Tcl löst hyfsat, om än lite krystat, genom att allt är kommandon istället för att allt är objekt. Vill du omdefiniera den inbyggda proceduren "puts", som normalt, utan extra argument skriver ut texten man ger den på standard input, dvs vanligtvis skärmen, följt av en radbrytning, varsågod. Sen kan du fortsätta scriptet precis som vanligt och ingen kommer att märka skillnaden.

Möjligheten att göra rekursiva interpreters, som är vanliga i Tcl och faktiskt skapas automatiskt i t.ex. varje if- och while-sats och procedur, "safe", bygger på att en utvald del av alla funktioner/kommandon/procedurer (de är alla samma sak i Tcl) stängs av, t.ex. kommandona för att kunna läsa och skriva filer eller öppna en socket.

Något skydd från DOS (denial of service) finns inte inbyggt, men finns det egentligen i något språk? Startar man en safe slave interpreter och dödar den efter lämplig tid, när den förväntas ha utfört sin uppgift, kan man köra all Tcl-kod som någon förhoppningsvis kan komma på.
 
Haloperidol sa:
Jag är intresserad! Vad kan man koda för kul i TCL då :)?

Byter till mitt originalkonto och överger det alternativa. Att skapa flera konton är en dålig jävla ovana jag har.

Jo alltså... man kan skapa allt utom sånt som behöver gå väldigt snabbt, t.ex. FPS-spelgrafik eller långa matematiska beräkningar. Fördelen med Tcl är att det verkligen är scriptspråket bland andra, alltså sådana språk som går längre i möjligheten att enkelt skriva ihop någonting, på få rader kod. Och när man prioriterar det blir nackdelen istället hastigheten att köra programmen. Jag har inte de exakta siffrorna (googla t.ex. "programming languages benchmarks") men säg att Tcl kanske är 10 ggr långsammare än andra scriptspråk, som i sin tur är 10 ggr långsammare än "riktiga" språk som C och C++.

Jag har tre textfiler liggandes som jag skrev för ett par år sen och har tänkt finslipa, men det blir aldrig av så det är bättre att posta dem.
 
Ett Tcl-script är bara text. Det behöver antingen interpreteras/tolkas (texten läses och körs av någon "riktig" körbar kod i ett program) eller kompileras (omvandlas till riktig körbar kod).

KOMPILERING

Kompilering diskuteras på http://wiki.tcl.tk/855. Ett av de enklaste sätten att kompilera ett Tcl-script är med freeWrap, ett annat är med köpversionen av ActiveTcl. I den vanliga Tcl-distributionen följer inte någon kompilator...

INTERPRETERING

...men en Tcl-interpreter som är skriven i C. Vem som helst som har ett C-program som behöver kunna köra Tcl-script, kan länka ihop sitt program med den och sen köra Tcl-script bara genom att skicka dem via diverse C-anrop till interpretern. tclsh, som också följer med distributionen, är ett exempel på ett sånt program. tclsh gör inte mycket mer än att ta antingen ett script via ett filnamn som argument, eller ett inskrivet script på standard input om inget filnamn ges, och skicka det till Tcl-interpretern, som gör jobbet. tclsh är det program man använder för att köra stand-alone Tcl-script.

Två andra exempel på program som länkar med och använder Tcl-interpretern är eggdrop och expect.

INSTALLERA

Det finns två stora Tcl-distributioner:

1. Den vanliga, som på 95% av alla Linux- och Mac OS-system redan är installerad eller finns tillgänglig som paket, annars går det att bygga den från källkoden. Tcl har medvetet gjorts med ett få antal funktioner för att göra det så litet som möjligt när man länkar in det i ett C-program. En massa extra, nyttiga funktioner/paket finns i Tcllib, som man måste installera separat om man behöver någon av dem. En annan utökning av Tcl är det grafiska gränssnittet Tk, som man vanligtvis installerar tillsammans med Tcl, men inte alls behöver, om man t.ex. inte har grafik på sin dator.

2. På Windows är det absolut enklaste att installera företaget ActiveStates distribution ActiveTcl, som är gratis (förutom kompileraren) och inkluderar Tcllib, Tk och mycket mer. Vill man inte installera något alls, kan man ladda hem freewrapTCLSH/freeWrap och döpa om freewrap.exe till tclsh.exe respektive wish.exe, för en helt fristående tclsh/wish som kan läggas i vilken mapp som helst.

Med några väldigt få undantag (http://wiki.tcl.tk/405), är Tcl bakåtkompatibelt. Ett script skrivet för Tcl 8.0 kan köras i interpretern för 8.6. Trots det, installerar de vanliga distributionerna inte t.ex. programmet tclsh med namnet "tclsh", utan med versionsnamnet efter, t.ex. "tclsh8.5". Det är bra om man av någon anledning vill ha fler än en version av Tcl installerat, men ger problem när script förväntar sig att hitta ett program med namnet "tclsh", t.ex. längst upp i ett UNIX-script: `#!/usr/local/bin/tclsh´ eller i ett BAT-script: `tclsh.exe mittscript.tcl`. Det rekommenderas att man manuellt länkar den senaste tclshX.X man har installerad till namnet "tclsh", t.ex. i UNIX: `cd /usr/local/bin && ln -s tclsh8.6 tclsh`. Detsamma gäller wish/wish.exe, som är som tclsh men med den grafiska delen inbyggd, dvs man använder det för att starta Tcl-script som ska använda Tk.

VERSIONER

Tcl 8.6.0 har precis släppts, men det är av mindre intresse om man vill skriva Tcl-script som alla kan köra. En lämpligare version är i så fall 8.4.19. Den är den sista, mest buggfixade versionen på det numera stängda 8.4-trädet, och den släpptes 2008 så man kan anta att alla har uppgraderat till minst den. 8.5-trädet är fortfarande öppet.

MER INFORMATION

De två viktiga Tcl-siterna är huvudsiten http://www.tcl.tk/ och wikisiten http://wiki.tcl.tk/.
 
För länge sen när jag började scripta för att /bin/sh inte räckte till, fanns det två språk som alltid var installerade på alla Linuxdistar: Perl och Tcl. Perls syntax är komplicerad med massor av undantag, speciella tecken och teckensekvenser. Tcl är precis tvärtom:

Ett Tcl-script består helt och hållet av ett antal kommandon separerade av radbrytning och/eller semikolon, där varje kommando består av ett antal ord separerade av whitespace (förutom radbrytning som, som sagt, separerar hela kommandon). Det första ordet är kommandonamnet och resterande ord är argumenten som skickas till kommandot. Kommandot kan vara inbyggt i språket, från ett laddat paket, en egenskriven procedur osv, alla anropas på samma sätt...

...t.o.m. kontrollstrukturerna som for, foreach, if-then-elseif-else, proc (för att skapa procedurer), switch och while. T.ex. tar kommandot for fyra argument: ett script som körs innan loopen startar, ett aritmetiskt uttryck som evalueras före varje iteration, ett script (kroppen) som körs så länge uttrycket är sant och ett script (uppdateringen) som körs efter kroppen.

Ordet "script" betyder här, och i all Tcl-dokumentationen, noll eller fler kommandon, och inte något som ligger i en fil (även om en fil naturligtvis kan innehålla ett script).

=====

Fast Tcls berömmelse på den tiden berodde nästan helt på den otroligt lättprogrammerade grafiska delen: Tk. Nu finns Tk till andra språk också, och det finns andra suveräna alternativ som Qt, så Tcl har fallit i glömska. Nuförtiden är det framförallt Perl och Python som kommer förinstallerade på Linuxsystemen.



Ja, det var tänkt att man skulle scripta C-program med det, som man gör med Lua nu, eller att det skulle fungera som klistret mellan olika programdelar skrivna i C. Det kunde antagligen ha blivit språket för client side-script i webbläsare, men JavaScript kom.
 
Jag fyller på med resten av Tcl-syntaxen om nån behöver. Den förklaras i dokumentationen också, men inte lika bra:

GRUPPERING

Om man behöver inkludera något av dessa tecken (tecknen för ordseparering: whitespace, tecknen för kommandoseparering: radbrytning och semikolon och tecknet för scriptavslutning: ]) som del av ett ord, är ett sätt att escape:a det med backslash:

Källkod:
sallad abc\;\ 123 ;# kommandot sallad får 1 argument: "abc; 123"

Ett annat är att quote:a med citationstecken " eller braces { och }. Inuti en sådan quote, behandlas nämnda tecken precis som vanliga tecken.

Källkod:
sallad "abc; 123" ;# samma sak

Man kan inte quote:a bara en del av ett ord. Det första tecknet måste vara " eller { och det sista " respektive }.

Källkod:
% puts "abc123" ;# OK
abc123
% puts abc"123" ;# citationstecknen behandlas som vanliga tecken (blir en del av värdet)
abc"123"
% puts "abc"123 ;# error
extra characters after close-quote

Med citationstecken " grupperar man bara. Substitutionerna (se nedan) sker fortfarande som vanligt. {} däremot är till för "ren" quote:ning. Mellan { och } är det bara fem saker som har speciell betydelse:

\<radbrytning><whitespace>
{
}
\{
\}

I Tcl har man ofta ett script inuti ett script inuti ett script osv... så nästlade quote:s är nästan nödvändigt, och {} är just det. Om { påträffas i en brace-quote, ökas antalet } som behövs för att avsluta quoten, och { minskar det.

Källkod:
proc {width height} {
  if {...} {
    while {...} {
      ...
    }
  }
}

proc får två argument: "width height" och "<radbrytning> if {" osv till det sista }, eftersom måsvingarna matchar dit.

Att språket matchar antalet öppnande och stängande måsvingar, gör att om en inte ska påbörja eller avsluta en quote, måste man escape:a den med backslash.

Källkod:
while {1} { puts "} är ett tecken" } ;# fel

Källkod:
while {1} { puts "\} är ett tecken" } ;# OK

Källkod:
if {1} {
  # kommentar med { inuti
  bla bla bla
} ;# också åt skogen

SUBSTITUTION

Det finns tre sorters substitution:

\<tecken> har olika betydelser.
$<variabelnamn> byts ut mot innehållet i variabeln.
[kommando argument argument...] byts ut mot vad kommandot returnerar.

Ett vanligt misstag är...

Källkod:
set $minutes 15

...där kommandot set aldrig får namnet minutes. Tcl-interpretern byter ut $minutes mot innehållet i variabeln minutes, och set får två argument: detta innehåll och "15". Om det inte ens finns någon minutes-variabel, kastas ett error-exception.

Det som sustitueras, däremot, påverkar inte ordgrupperingen. Substitution sker bara en gång. Det som substituerats fram genomgår ingen mer parse:ning, och det påverkar dessutom inte ordgrupperingen:

Källkod:
set a 10
set b \$a ;# sätt b till strängen "$a"
puts $b ;# skriver ut "$a", inte "10"

RADFORTSÄTTNING

\<radbrytning><noll-eller-fler-whitespace-tecken> byts ut mot ett enda mellanslag.

AVANCERAT OM SYNTAX

Två saker i dokumentationen kan ge en felaktig bild av hur parsern går igenom ett script: För det första påstås det att radfortsättnings-sekvenser först substitueras i en separat genomgång av scriptet, vilket inte stämmer. De substitueras samtidigt som andra backslash-sekvenser:

Källkod:
puts \\
abc

...skriver inte ut " abc". Det skriver ut "\" och kör kommandot abc. För det andra kan man få uppfattningen att ordgruppering sker före substitution, och det stämmer inte heller. I...

Källkod:
dallas x[read stdin]x

...får dallas inte två argument ("x[read" och "stdin]x") utan bara ett: "x" följt av hela stdin följt av "x". Parsern gör egentligen bara en genomgång - den går igenom scriptet ett tecken i taget, och beroende på tecknet kan den byta "state". När ett \ påträffas går den in i backslash mode. När ett { påträffas och den inte är i backslash mode, går den in i brace quoting mode. Så fort ett [ påträffas och den inte är i brace quoting mode, anropar den sig själv rekursivt och slutar parse:a själv tills den har fått resultatet från detta script, och substituerar in det.

Skillnaden: med { måste man escape:a alla { och }. När interpretern stöter på en [, letar inte interpretern efter den avslutande ] själv, utan den slutar att tolka helt och anropar en till interpreter rekursivt. Den interpretern börjar med tecknet efter [.
 
KOMMENTARER

Om ett #-tecken är det första tecknet av det första ordet (dvs kommando-ordet), ignoreras resten av raden. Eftersom en kommentar måste börja där ett kommando börjar, fungerar inte det här...

Källkod:
puts "Hej!" # user greeting

...utan man får göra så här...

Källkod:
puts "Hej!" ;# user greeting

I tidiga versioner av Tcl, fungerade #<text> som andra kommandon genom att argumenten parse:ades men sen inte skickades någonstans. Det gav problem som de här:

Källkod:
% # kommentar [med brackets] i
invalid command name "med"
% # kom ihåg att initiera $width med 640
can't read "width": no such variable

Det fanns en fördel med det - att man kunde kommentera ut en hel kodsnutt:

Källkod:
# {
  bla bla bla
  while {bla} {
    bla bla
  }
}

Nuförtiden har man skapat en specielregel för kommentarer: argumenten parse:as inte ens, utan allt efter # ignoreras helt. Det gör det enklare, men bryter på ett sätt Tcl's tanke, och skapar på det sättet ett annat problem...

MER INFO

Tcl Syntax Summary
 
Är ni självlärda eller studerat IT? Så fascinerande hur duktiga ni är. Jag försökte lära mig lite på egen hand men fick inte grepp alls.
 
Källkod:
#!/bin/sh
# the next line restarts using tclsh \
exec tclsh "$0" "$@"

package require Tcl 8.5

set sum 0
while {[gets stdin line] > -1} {
    incr sum $line
}
puts $sum

Här är ett komplet exempel på ett Tcl-script. Vi kommer att gå igenom det del för del.

Källkod:
#!/bin/sh
# the next line restarts using tclsh \
exec tclsh "$0" "$@"

Som nämnts är Tcl-script text och behöver köras av ett program om man inte kompilerar det. På Unix-baserade system som BSD, Linux, Mac OS X m.fl finns ett knep för att kunna behandla texten som ett körbart program. Man sätter körbar-flaggan på filen med `chmod +x`, anger på första raden efter `#!` vilken tolk som automatiskt ska köra scriptet, och kan sen skriva kommandot `scriptet.tcl <argumenten>` istället för `tclsh scriptet.tcl <argumenten>`.

En enklare variant av den här tre-radiga konstruktionen är en enda rad `#!/usr/local/bin/tclsh`, men eftersom Tcl inte alltid medföljer alla system är det inte säkert att tclsh ligger på den platsen. Det kan t.ex. ligga lokalt installerad i användarens hemmapp. Det de tre raderna gör är att starta standardskalet sh, som letar reda på tclsh i ens PATH.

Källkod:
package require Tcl 8.5

`package require` laddar ett externt paket som inte är inbyggt i själva språket. Det kan vara ett man har skrivit själv i C eller något från Tcllib, t.ex. för att kryptera data, läsa zip-filer eller skicka/ta emot email.

Det kan även användas, som här, för att kräva en särskild Tcl-version eller högre/senare. Om användaren kör scriptet med Tcl 8.4 eller lägre/tidigare, kastas ett fel redan vid den här punkten och scriptet avbryts med ett felmeddelande. Det är trevligare för användaren än att scriptet kraschar först senare när ett kommando som introducerades i 8.5 dyker upp.

Källkod:
set sum 0

Sätter variabeln sum till 0. En del kommandon, inklusive incr, sätter automatiskt icke-existerande variabler till 0 om ett sånt namn dyker upp, så i praktiken behövs det inte i det här programmet. Men en del kommandon gör det inte, utan kastar ett fel, så det är lika bra att göra det till en vana att initiera variabler själv. Koden skulle ju kunna ändras i framtiden.

Här är det på sin plats att förklara hur felhantering fungerar i Tcl, vilket blir nästa inlägg!

Fula_Gubben sa:
Är ni självlärda eller studerat IT? Så fascinerande hur duktiga ni är. Jag försökte lära mig lite på egen hand men fick inte grepp alls.

Jag är självlärd, började programmera när jag var 10 år och har slukat en del digitalteknik, elektronik, fysik och matematik också. Jag är väldigt intresserad av naturvetenskap, i alla fall de teoretiska bitarna.
 
Tramssexuell sa:
så i praktiken behövs det inte i det här programmet.

Jag korrigerar mig själv. Redan i den här koden behövs initieringen av sum. Om användaren skulle avsluta efter att inte ha skrivit ett enda tal, dvs noll stycken, körs aldrig inc och puts ger felmeddelandet `can't read "sum": no such variable`.

Felhantering

Felhantering är som allt annat i Tcl mycket enkelt. Ett fel som genereras, propageras tillbaka genom anropshistoriken, den s.k. stacken. Fångas det inte någonstans på vägen med kommandot catch, utan når ända upp till filnivån och ut därifrån, avbryts scriptet med ett felmeddelande. Varje nivå lägger till sin egen information till detta meddelande, för att det ska vara lätt att spåra felet.

Vi går händelserna i förväg och skapar ett eget kommando/prodecur/subrutin, som vi sedan använder/anropar (kärt barn har många namn). Proceduren skriver ut information om användarens dator. Vi använder återigen namnet på ett kommando som inte finns för att generera felet, men det skulle lika gärna kunna genereras av t.ex. ett försök att addera ett heltal med ett efternamn, öppna en fil som inte finns eller dividera med 0.

Källkod:
proc printSysInfo {} {
    global tcl_platform

    puts "Operativsystem: $tcl_platform(os)"
    puta "OS-version: $tcl_platform(osVersion)"
    puts "Processor: $tcl_platform(machine)"
}

# `while {1} <script>` kör <script> i all oändlighet
while {1} {
    puts "Visa datorinformation? (y/n)"
    gets stdin answer
    if {[string equal $answer "y"]} {
        printSysInfo
        puts "Klar!"
    }
}

När Tcl stöter på det felaktiga puts-kommandot, avbryter det scriptet, i det här fallet innehållet i proceduren, och felrapporterar till platsen där proceduren anropades - inuti if-satsens script - istället för att returnera normalt Det tredje puts-kommandot körs aldrig.

If-satsen avbryts och återgår till while-loopens script. "Klar!" srivs aldrig ut.

Osv tills allt är uppnystat. Eftersom felet inte fångas någonstans på vägen, avbryts huvudscriptet (det i filen) med ett felmeddelande:

Källkod:
Visa datorinformation? (y/n)
y
Operativsystem: CYGWIN_NT-10.0-WOW
invalid command name "puta"
    while executing
"puta "OS-version: $tcl_platform(osVersion)""
    (procedure "printSysInfo" line 5)
    invoked from within
"printSysInfo"
    ("while" body line 5)
    invoked from within
"while {1} {
    puts "Visa datorinformation? (y/n)"
    gets stdin answer
    if {[string equal $answer "y"]} {
        printSysInfo
        puts "Kla..."
    (file "error.tcl" line 9)

Catch tar ett script (dvs kod) som argument, kör det och returnerar 0 om det gick bra, eller icke-noll om scriptet kastade ett fel. Vi skulle kunna lägga till en till vårt script. Kod och utskrift innan:

Källkod:
package require Tcl 8.5

Källkod:
version conflict for package "Tcl": have 8.4.20, need 8.5
    while executing
"package require Tcl 8.5"
    (file "sum.tcl" line 5)

Kod och utskrift efter:

Källkod:
if {[catch {package require Tcl 8.5}]} {
    # användarens Tcl är från stenåldern, skriv ut något spydigt
    puts "du suger"
    exit 1
}

Källkod:
du suger

Ett fel kan avsiktgligt genereras, med valfritt felmeddelande, med kommandot error. Det används oftast när något är kritiskt fel, t.ex. när en fil inte går att öppna eller en nätverksuppkoppling går att skapa, och mer sällan när användaren ger felaktig input. Skriver en användare 123456789 som postnummer, kan det vara lämpigare att fråga honom/henne igen.

Källkod:
if {![file exists "customers.db"]} {
    error "customer database doesn't exist"
}
 
Topp