Under flera år försökte jag mig på att utveckla mitt eget programmeringsspråk. Jag ville skapa, enligt min mening, ett så enkelt, fullt funktionellt och bekvämt språk som möjligt.
I den här artikeln vill jag lyfta fram huvudstadierna i mitt arbete och till att börja med beskriva det skapade konceptet för språket och dess första implementering, som jag för närvarande arbetar med.
Låt mig säga i förväg att jag skrev hela projektet i Free Pascal, eftersom... program på den kan monteras för ett stort antal plattformar, och kompilatorn själv producerar mycket optimerade binärfiler (jag samlar alla komponenter i projektet med O2-flaggan).
Språkkörningstid
Först och främst är det värt att prata om den virtuella maskinen som jag var tvungen att skriva för att köra framtida applikationer på mitt språk. Jag bestämde mig för att implementera en stack-arkitektur, kanske, eftersom det var det enklaste sättet. Jag hittade inte en enda normal artikel om hur man gör detta på ryska, så efter att ha bekantat mig med det engelskspråkiga materialet satte jag mig och designade och skrev min egen cykel. Därefter kommer jag att presentera mina "avancerade" idéer och utvecklingar i denna fråga.
Stackimplementering
Uppenbarligen, högst upp i den virtuella datorn är stacken. I min implementering fungerar det i block. I huvudsak är detta en enkel uppsättning pekare och en variabel för att lagra indexet för toppen av stacken.
När den initieras skapas en array med 256 element. Om fler pekare skjuts på stapeln, ökar dess storlek med nästa 256 element. Följaktligen, när element tas bort från stapeln, justeras dess storlek.
Den virtuella datorn använder flera stackar:
- Huvudstack.
- En stack för att lagra returpunkter.
- Sophämtningsstack.
- Försök/fånga/slutligen blockera hanterarstack.
Konstanter och variabler
Den här är enkel. Konstanter hanteras i en separat liten kodbit och är tillgängliga i framtida applikationer via statiska adresser. Variabler är en rad pekare av en viss storlek, åtkomst till dess celler utförs av index - d.v.s. statisk adress. Variabler kan skjutas till toppen av stapeln eller läsas därifrån. Egentligen, därför att Medan våra variabler i huvudsak lagrar pekare till värden i VM-minnet, domineras språket av att arbeta med implicita pekare.
Skräp samlare
I min virtuella dator är den halvautomatisk. De där. utvecklaren bestämmer själv när han ska ringa sophämtaren. Det fungerar inte med en vanlig pekarräknare, som i Python, Perl, Ruby, Lua, etc. Det implementeras genom ett markörsystem. De där. när en variabel är avsedd att tilldelas ett temporärt värde läggs en pekare till detta värde till sopsamlarens stack. I framtiden går samlaren snabbt igenom den redan förberedda listan med pekare.
Hantera försök/fånga/slutligen blockerar
Som i alla moderna språk är undantagshantering en viktig komponent. VM-kärnan är inlindad i ett try..catch-block, som kan återgå till kodexekvering efter att ha fångat ett undantag genom att skjuta upp lite information om det i stacken. I applikationskoden kan du definiera försök/fånga/slutligen kodblock, ange ingångspunkter vid catch (undantagshanterare) och slutligen/slut (slutet på blocket).
Multithreading
Det stöds på VM-nivå. Det är enkelt och bekvämt att använda. Det fungerar utan ett avbrottssystem, så koden bör exekveras i flera trådar flera gånger snabbare respektive.
Externa bibliotek för virtuella datorer
Det finns inget sätt att göra utan detta. VM stöder import, liknande hur det är implementerat på andra språk. Du kan skriva en del av koden i Mash och en del av koden på modersmål och sedan länka dem till ett.
Översättare från Mash-språk på hög nivå till bytekod för virtuella datorer
Mellanspråk
För att snabbt skriva en översättare från ett komplext språk till VM-kod utvecklade jag först ett mellanspråk. Resultatet blev ett assemblerliknande fruktansvärt skådespel som det inte är någon speciell poäng med att överväga här. Jag kommer bara att säga att på denna nivå bearbetar översättaren de flesta konstanter och variabler, beräknar deras statiska adresser och adresserna till ingångspunkter.
Översättararkitektur
Jag valde inte den bästa arkitekturen för implementering. Översättaren bygger inte ett kodträd, som andra översättare gör. Han tittar på början av strukturen. De där. om kodbiten som tolkas ser ut som "while <condition>:", så är det uppenbart att detta är en while-loopkonstruktion och måste bearbetas som en while-loopkonstruktion. Något som liknar ett komplext switch-case.
Tack vare denna arkitektoniska lösning visade sig översättaren inte vara särskilt snabb. Lättheten att modifiera den har dock ökat avsevärt. Jag lade till de nödvändiga strukturerna snabbare än mitt kaffe kunde svalna. Fullständigt OOP-stöd implementerades på mindre än en vecka.
Kodoptimering
Här kunde det förstås ha implementerats bättre (och kommer att genomföras, men senare, så fort man kommer runt). Än så länge vet optimeraren bara hur man skär bort oanvänd kod, konstanter och importer från monteringen. Dessutom ersätts flera konstanter med samma värde med en. Det är allt.
Mash språk
Grundläggande språkbegrepp
Huvudtanken var att utveckla ett så funktionellt och enkelt språk som möjligt. Jag tycker att utvecklingen klarar sin uppgift med råge.
Kodblock, procedurer och funktioner
Alla konstruktioner i språket öppnas med kolon. : och stängs av operatören änden.
Procedurer och funktioner deklareras som proc respektive func. Argumenten listas inom parentes. Allt är som de flesta andra språk.
Operatör avkastning du kan returnera ett värde från en funktion, operator bryta låter dig avsluta proceduren/funktionen (om den är utanför slingorna).
Exempelkod:
...
func summ(a, b):
return a + b
end
proc main():
println(summ(inputln(), inputln()))
end
Designar som stöds
- Slingor: för..slut, medan..slut, tills..slut
- Villkor: om...[else..]slut, byt..[ärende..slut..][else..]slut
- Metoder: proc <name>():... end, func <name>():... end
- Etikett & gå till: <namn>:, hoppa <namn>
- Räkna upp uppräkningar och konstantmatriser.
variabler
Översättaren kan fastställa dem automatiskt, eller om utvecklaren skriver var innan de definierar dem.
Kodexempel:
a ?= 10
b ?= a + 20
var a = 10, b = a + 20
Globala och lokala variabler stöds.
OOP
Nåväl, vi har kommit till det läckraste ämnet. Mash stöder alla objektorienterade programmeringsparadigm. De där. klasser, arv, polymorfism (inklusive dynamisk), dynamisk automatisk reflektion och introspektion (fullständig).
Utan vidare är det bättre att bara ge kodexempel.
En enkel klass och att arbeta med den:
uses <bf>
uses <crt>
class MyClass:
var a, b
proc Create, Free
func Summ
end
proc MyClass::Create(a, b):
$a = new(a)
$b = new(b)
end
proc MyClass::Free():
Free($a, $b)
$rem()
end
func MyClass::Summ():
return $a + $b
end
proc main():
x ?= new MyClass(10, 20)
println(x->Summ())
x->Free()
end
Kommer ut: 30.
Arv och polymorfism:
uses <bf>
uses <crt>
class MyClass:
var a, b
proc Create, Free
func Summ
end
proc MyClass::Create(a, b):
$a = new(a)
$b = new(b)
end
proc MyClass::Free():
Free($a, $b)
$rem()
end
func MyClass::Summ():
return $a + $b
end
class MyNewClass(MyClass):
func Summ
end
func MyNewClass::Summ():
return ($a + $b) * 2
end
proc main():
x ?= new MyNewClass(10, 20)
println(x->Summ())
x->Free()
end
Kommer ut: 60.
Hur är det med dynamisk polymorfism? Ja, det här är en reflektion!:
uses <bf>
uses <crt>
class MyClass:
var a, b
proc Create, Free
func Summ
end
proc MyClass::Create(a, b):
$a = new(a)
$b = new(b)
end
proc MyClass::Free():
Free($a, $b)
$rem()
end
func MyClass::Summ():
return $a + $b
end
class MyNewClass(MyClass):
func Summ
end
func MyNewClass::Summ():
return ($a + $b) * 2
end
proc main():
x ?= new MyClass(10, 20)
x->Summ ?= MyNewClass::Summ
println(x->Summ())
x->Free()
end
Kommer ut: 60.
Låt oss nu ta en stund för att introspektera efter enkla värden och klasser:
uses <bf>
uses <crt>
class MyClass:
var a, b
end
proc main():
x ?= new MyClass
println(BoolToStr(x->type == MyClass))
x->rem()
println(BoolToStr(typeof(3.14) == typeReal))
end
Kommer att skriva ut: sant, sant.
Om uppdragsoperatörer och explicita tips
Operatorn ?= används för att tilldela en variabel en pekare till ett värde i minnet.
Operatorn = ändrar ett värde i minnet med hjälp av en pekare från en variabel.
Och nu lite om tydliga tips. Jag lade till dem i språket så att de finns.
@<variabel> — ta en explicit pekare till en variabel.
?<variabel> — få en variabel med pekaren.
@= — tilldela ett värde till en variabel med en explicit pekare till den.
Exempelkod:
uses <bf>
uses <crt>
proc main():
var a = 10, b
b ?= @a
PrintLn(b)
b ?= ?b
PrintLn(b)
b++
PrintLn(a)
InputLn()
end
Kommer att mata ut: något nummer, 10, 11.
Prova..[fånga..][äntligen..]slut
Exempelkod:
uses <bf>
uses <crt>
proc main():
println("Start")
try:
println("Trying to do something...")
a ?= 10 / 0
catch:
println(getError())
finally:
println("Finally")
end
println("End")
inputln()
end
Planer för framtiden
Jag fortsätter titta och titta på GraalVM & Tryffel. Min runtime-miljö har ingen JIT-kompilator, så prestandamässigt är den för närvarande bara konkurrenskraftig med Python. Jag hoppas att jag kommer att kunna implementera JIT-kompilering baserad på GraalVM eller LLVM.
förvaret
Du kan leka med utvecklingen och själv följa projektet.
Tack för att du läste till slutet om du gjorde det.
Källa: will.com