A simple procedural programming language is considered, each program of which can input integer values, process them and output new integer values as result. A program is a block with description of local integer variables and procedures and a list of statements. The language has data processing statements: assignment, input, output, conditional, loop, procedure call and block. Main purpose of the block is to enter local data (integer variables and procedures) that are used in the body of the block – a list of operators. The scope of the name of the local data described in the block is the text of the block except for nested blocks, where this name is redefined. A mechanism of automatic memory allocation for variables entered in the block is also associated with the block. Memory for local variables is allocated when entering a block, and freed when exiting a block. A block containing only a list of statements is valid. The procedure has a name, list of formal parameters, and a body – a statement (most often a block). Formal parameters are applied only in its body. A procedure is calculated by the procedure call statement, whose actual parameters are only variables. Parameters are passed by reference (pass-by-reference).A formal specification of a programming language is a description of its syntax and semantics. A concrete syntax, finite set of rules, singles out syntactically correct sequences of symbols of the alphabet of language. To describe the semantics of a language, as a rule, abstract syntax is used, adding contextual conditions to it. The task of semantics is to introduce the denotations (“meanings”) of the basic constructions of language and semantic functions that build the denotations of complex syntactic constructions based on the denotations of their components, including the program.The article provides a specification of a procedural programming language that uses the extended Backus-Naur form to describe a concrete syntax, and the tools of the functional language Haskell to describe other parts. Abstract syntax is defined by the types Program, Proc, Stmt, Expr and Op. Additional contextual conditions are predicates that use information about program data. Most of the context conditions are related to the correct use of data in the program. The leading predicate that checks the context conditions of the program pr is iswfProgram pr.The language denotations are based on the Work type. The value of this type – a tuple (inp, stg, out) models the environment in which the language program is executed: inp - input data, stg – memory containing variable values, out – resulting data. The semantics of main constructions procedure, statement and expression are functions of the type Work -> Work or Work -> Integer. The semantics of the program is a function of the type [Integer] -> [Integer]. Semantic functions build these denotations according to syntactic constructions, which are described by abstract syntax – Proc, Stmt, Expr, Program types. The semantics of the program (Program) pr is built by function iProgram pr.All functions: contextual conditions, denotations and semantic functions are pure functions. Using Haskell tools, a function called parsePLL is built, which connects concrete and abstract syntax. It is shown how by combining the functions parsePLL, iswfProgram and iProgram you can get a procedural language – a pure function with the name interpret.